//Subdivies a list of triangles into 8 regions.
        //When the max prism count is rearched, it divides again.
        private List <ModelGroup> CreateModelDivision(Vector3 position, List <Triangle> triangles, Vector3 boxSize, int level = 0)
        {
            //Version 1 uses one single model so skip dividing them.
            //Models only split if their poly count is too high, so add a check for it.
            if (Version < FileVersion.Version2 || triangles.Count < MaxModelPrismCount && level == 0)
            {
                ModelGroup model = new ModelGroup();
                model.Triangles.AddRange(triangles);
                model.BlockIndex         = 0;
                model.MergedBlockIndices = new List <int>()
                {
                    1, 2, 3, 4, 5, 6, 7
                };
                return(new List <ModelGroup>()
                {
                    model
                });
            }

            //Create a fixed set of 8 model groups.
            ModelGroup[] modelRoots = new ModelGroup[8];
            int          index      = 0;

            for (int z = 0; z < 2; z++)
            {
                for (int y = 0; y < 2; y++)
                {
                    for (int x = 0; x < 2; x++)
                    {
                        //Create a model group for each region
                        ModelGroup model = new ModelGroup();

                        //Get the position of the current region
                        Vector3 cubePosition = position + boxSize * new Vector3(x, y, z);

                        List <Triangle> containedTriangles = new List <Triangle>();
                        for (int i = 0; i < triangles.Count; i++)
                        {
                            //Check for intersecting triangles in the current region.
                            if (TriangleBoxIntersect.TriBoxOverlap(triangles[i], cubePosition + boxSize / 2f, boxSize / 2f))
                            {
                                containedTriangles.Add(triangles[i]);
                            }
                        }

                        if (containedTriangles.Count >= MaxModelPrismCount)
                        {
                            DebugLogger.WriteLine($"Dividing model at {containedTriangles.Count} polygons.");
                        }

                        if (level > 2)
                        {
                            DebugLogger.WriteError($"Warning! Your KCL has over 3 division levels and may fall through!");
                        }

                        //If the children have too many Prisms, divide into 8 more regions as children.
                        if (containedTriangles.Count >= MaxModelPrismCount)
                        {
                            model.Children = CreateModelDivision(cubePosition, containedTriangles, boxSize / 2f, level + 1);
                        }
                        else //Set the triangle list for this region. If it is empty, it will be skipped later
                        {
                            model.Triangles = containedTriangles;
                        }

                        model.BlockIndex  = index;
                        modelRoots[index] = model;
                        index++;
                    }
                }
            }
            return(modelRoots.ToList());
        }
Exemple #2
0
        /// <summary>
        /// Loads the object file data from the given <paramref name="stream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> to load the data from.</param>
        /// <param name="leaveOpen"><c>true</c> to leave <paramref name="stream"/> open after loading the instance.
        /// </param>
        public void Load(Stream stream)
        {
            DebugLogger.WriteLine($"Loading obj file....");

            Meshes    = new List <ObjMesh>();
            Materials = new List <ObjMaterial>();

            ObjMesh currentMesh = new ObjMesh("Mesh");

            HashSet <string> faceHashes = new HashSet <string>();

            Dictionary <ObjFace, int> faceDupes = new Dictionary <ObjFace, int>();

            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                List <Vector3> Positions = new List <Vector3>();
                List <Vector2> TexCoords = new List <Vector2>();
                List <Vector3> Normals   = new List <Vector3>();

                var    enusculture     = new CultureInfo("en-US");
                string currentMaterial = null;
                while (!reader.EndOfStream)
                {
                    string line = reader.ReadLine();
                    line = line.Replace(",", ".");

                    // Ignore empty lines and comments.
                    if (String.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
                    {
                        continue;
                    }

                    string[] args = line.Split(_argSeparators, StringSplitOptions.RemoveEmptyEntries);
                    if (args.Length == 1)
                    {
                        continue;
                    }

                    switch (args[0])
                    {
                    case "o":
                    case "g":
                        currentMesh = new ObjMesh(args.Length > 1 ? args[1] : $"Mesh{Meshes.Count}");
                        Meshes.Add(currentMesh);
                        continue;

                    case "v":
                        Positions.Add(new Vector3(
                                          Single.Parse(args[1], enusculture),
                                          Single.Parse(args[2], enusculture),
                                          Single.Parse(args[3], enusculture)));
                        continue;

                    case "vt":
                        TexCoords.Add(new Vector2(
                                          Single.Parse(args[1], enusculture),
                                          Single.Parse(args[2], enusculture)));
                        continue;

                    case "vn":
                        Normals.Add(new Vector3(
                                        Single.Parse(args[1], enusculture),
                                        Single.Parse(args[2], enusculture),
                                        Single.Parse(args[3], enusculture)));
                        continue;

                    case "f":
                        if (args.Length != 4)
                        {
                            throw new Exception("Obj must be trianglulated!");
                        }

                        int[] indices = new int[3 * 2];     //3 faces, position and normal indices

                        // Only support triangles for now.
                        ObjFace face = new ObjFace()
                        {
                            Vertices = new ObjVertex[3]
                        };
                        face.Material = currentMaterial;
                        for (int i = 0; i < face.Vertices.Length; i++)
                        {
                            string[] vertexArgs    = args[i + 1].Split(_vertexSeparators, StringSplitOptions.None);
                            int      positionIndex = Int32.Parse(vertexArgs[0]) - 1;

                            face.Vertices[i].Position = Positions[positionIndex];

                            if (float.IsNaN(face.Vertices[i].Position.X) ||
                                float.IsNaN(face.Vertices[i].Position.Y) ||
                                float.IsNaN(face.Vertices[i].Position.Z))
                            {
                                face.Vertices = null;
                                break;
                            }

                            if (vertexArgs.Length > 1 && vertexArgs[1] != String.Empty)
                            {
                                face.Vertices[i].TexCoord = TexCoords[Int32.Parse(vertexArgs[1]) - 1];
                            }
                            if (vertexArgs.Length > 2 && vertexArgs[2] != String.Empty)
                            {
                                face.Vertices[i].Normal = Normals[Int32.Parse(vertexArgs[2]) - 1];
                            }
                        }

                        string faceStr = face.ToString();
                        if (faceHashes.Contains(faceStr))
                        {
                            continue;
                        }

                        faceHashes.Add(faceStr);

                        if (face.Vertices != null)
                        {
                            currentMesh.Faces.Add(face);
                        }
                        continue;

                    case "usemtl":
                    {
                        if (args.Length < 2)
                        {
                            continue;
                        }
                        currentMaterial = args[1];
                        continue;
                    }
                    }
                }
            }

            Console.WriteLine($"FACE COUNT {currentMesh.Faces.Count}");

            faceDupes.Clear();

            if (Meshes.Count == 0) //No object or groups present, use one single mesh
            {
                Meshes.Add(currentMesh);
            }
        }
        /// <summary>
        /// Replaces the current collision model from the given
        /// <paramref name="objModel"/>.
        /// </summary>
        /// <param name="objModel">The <see cref="ObjModel"/> to create the collision data from.</param>
        public void Replace(List <Triangle> triangles, CollisionImportSettings settings)
        {
            Stopwatch stopWatch = new Stopwatch();

            stopWatch.Start();

            // Find the smallest and biggest coordinate (and add padding).
            Vector3 minCoordinate = new Vector3(Single.MaxValue, Single.MaxValue, Single.MaxValue);
            Vector3 maxCoordinate = new Vector3(Single.MinValue, Single.MinValue, Single.MinValue);

            DebugLogger.WriteLine($"Replacing Collision...");

            DebugLogger.WriteLine($"Settings:");
            DebugLogger.WriteLine($"-MaxRootSize {settings.MaxRootSize}");
            DebugLogger.WriteLine($"-MaxTrianglesInCube {settings.MaxTrianglesInCube}");
            DebugLogger.WriteLine($"-MinCubeSize {settings.MinCubeSize}");
            DebugLogger.WriteLine($"-PaddingMax {settings.PaddingMax}");
            DebugLogger.WriteLine($"-PaddingMin {settings.PaddingMin}");
            DebugLogger.WriteLine($"-PrismThickness {settings.PrismThickness}");
            DebugLogger.WriteLine($"-SphereRadius {settings.SphereRadius}");

            DebugLogger.WriteLine($"Calculating bounding sizes...");

            for (int i = 0; i < triangles.Count; i++)
            {
                for (int v = 0; v < triangles[i].Vertices.Length; v++)
                {
                    Vector3 position = triangles[i].Vertices[v];
                    minCoordinate.X = Math.Min(position.X, minCoordinate.X);
                    minCoordinate.Y = Math.Min(position.Y, minCoordinate.Y);
                    minCoordinate.Z = Math.Min(position.Z, minCoordinate.Z);
                    maxCoordinate.X = Math.Max(position.X, maxCoordinate.X);
                    maxCoordinate.Y = Math.Max(position.Y, maxCoordinate.Y);
                    maxCoordinate.Z = Math.Max(position.Z, maxCoordinate.Z);
                }
            }

            MinCoordinate = minCoordinate + settings.PaddingMin;
            MaxCoordinate = maxCoordinate + settings.PaddingMax;

            DebugLogger.WriteLine($"MinCoordinate: {MinCoordinate}");
            DebugLogger.WriteLine($"MaxCoordinate: {MaxCoordinate}");

            // Compute square cube size of the world, and with it the coordinate shift for use with the model octree.
            Vector3 size           = MaxCoordinate - MinCoordinate;
            int     worldLengthExp = Maths.GetNext2Exponent(Math.Min(Math.Min(size.X, size.Y), size.Z));
            int     cubeSize       = 1 << worldLengthExp;
            Vector3 exponents      = new Vector3(
                Maths.GetNext2Exponent(size.X),
                Maths.GetNext2Exponent(size.Y),
                Maths.GetNext2Exponent(size.Z));

            CoordinateShift = new Vector3U(
                (uint)(exponents.X),
                (uint)(exponents.Y),
                (uint)(exponents.Z));

            Models = new List <KCLModel>();
            Vector3 boxSize = new Vector3(
                1 << (int)CoordinateShift.X,
                    1 << (int)CoordinateShift.Y,
                    1 << (int)CoordinateShift.Z);

            DebugLogger.WriteLine($"Model Octree Bounds: {boxSize}");

            //Create subdivied triangle models
            var modelRoots = CreateModelDivision(MinCoordinate, triangles, boxSize / 2f);

            //For a model octree, we need 8 octrees per division
            ModelOctreeRoot          = new ModelOctreeNode();
            ModelOctreeRoot.Children = new ModelOctreeNode[ModelOctreeNode.ChildCount];
            for (int i = 0; i < ModelOctreeNode.ChildCount; i++)
            {
                ModelOctreeRoot.Children[i] = new ModelOctreeNode();
            }
            Models.Clear();

            //Load all the model data
            CreateModelOctree(modelRoots, ModelOctreeRoot.Children, settings, 0);
            PrismCount = Models.Sum(x => x.Prisms.Length);

            stopWatch.Stop();

            DebugLogger.WriteLine($"Model Octree:");
            PrintModelOctree(ModelOctreeRoot.Children);

            DebugLogger.WriteLine($"Finished Collsion Generation {stopWatch.Elapsed}");
        }
        public KCLModel(List <Triangle> triangleList, uint baseTriCount,
                        FileVersion version, CollisionImportSettings settings)
        {
            // Transfer the faces to collision faces and find the smallest and biggest coordinates.
            Vector3 minCoordinate = new Vector3(Single.MaxValue, Single.MaxValue, Single.MaxValue);
            Vector3 maxCoordinate = new Vector3(Single.MinValue, Single.MinValue, Single.MinValue);

            List <KclPrism> prismList = new List <KclPrism>();
            Dictionary <ushort, Triangle> triangles = new Dictionary <ushort, Triangle>();

            Positions = new List <Vector3>();
            Normals   = new List <Vector3>();
            Prisms    = new KclPrism[0];
            Version   = version;

            PrismThickness = settings.PrismThickness;
            SphereRadius   = settings.SphereRadius;

            Dictionary <string, int> positionTable = new Dictionary <string, int>();
            Dictionary <string, int> normalTable   = new Dictionary <string, int>();

            ushort triindex = 0;

            for (int i = 0; i < triangleList.Count; i++)
            {
                var triangle = triangleList[i];

                Vector3 direction = Vector3.Cross(
                    triangle.Vertices[1] - triangle.Vertices[0],
                    triangle.Vertices[2] - triangle.Vertices[0]);

                if ((direction.X * direction.X + direction.Y * direction.Y + direction.Z * direction.Z) < 0.01)
                {
                    continue;
                }
                direction = Vector3.Normalize(direction);

                // Get the position vectors and find the smallest and biggest coordinates.
                for (int j = 0; j < 3; j++)
                {
                    Vector3 position = triangle.Vertices[j];
                    minCoordinate.X = Math.Min(position.X, minCoordinate.X);
                    minCoordinate.Y = Math.Min(position.Y, minCoordinate.Y);
                    minCoordinate.Z = Math.Min(position.Z, minCoordinate.Z);
                    maxCoordinate.X = Math.Max(position.X, maxCoordinate.X);
                    maxCoordinate.Y = Math.Max(position.Y, maxCoordinate.Y);
                    maxCoordinate.Z = Math.Max(position.Z, maxCoordinate.Z);
                }

                //Calculate the ABC normal values.
                Vector3 normalA = Vector3.Cross(direction,
                                                triangle.Vertices[2] - triangle.Vertices[0]);

                Vector3 normalB = (-(Vector3.Cross(direction,
                                                   triangle.Vertices[1] - triangle.Vertices[0])));

                Vector3 normalC = Vector3.Cross(direction,
                                                triangle.Vertices[1] - triangle.Vertices[2]);

                //Normalize the ABC normal values.
                normalA = Vector3.Normalize(normalA);
                normalB = Vector3.Normalize(normalB);
                normalC = Vector3.Normalize(normalC);

                //Create a KCL Prism
                KclPrism face = new KclPrism()
                {
                    PositionIndex  = (ushort)IndexOfVertex(triangle.Vertices[0], Positions, positionTable),
                    DirectionIndex = (ushort)IndexOfVertex(direction, Normals, normalTable),
                    Normal1Index   = (ushort)IndexOfVertex(normalA, Normals, normalTable),
                    Normal2Index   = (ushort)IndexOfVertex(normalB, Normals, normalTable),
                    Normal3Index   = (ushort)IndexOfVertex(normalC, Normals, normalTable),
                    GlobalIndex    = baseTriCount + (uint)prismList.Count,
                    CollisionFlags = triangle.Attribute,
                };

                // Compute the face direction (normal) and add it to the normal list.
                triangles.Add((ushort)triindex++, triangle);

                //Compute the length
                float length = Vector3.Dot(triangle.Vertices[1] - triangle.Vertices[0], normalC);
                face.Length = length;

                prismList.Add(face);
            }

            positionTable.Clear();
            normalTable.Clear();

            //No triangles found to intersect the current box, return.
            if (prismList.Count == 0)
            {
                return;
            }

            //Padd the coordinates
            minCoordinate += settings.PaddingMin;
            maxCoordinate += settings.PaddingMax;

            MinCoordinate = minCoordinate;
            Prisms        = prismList.ToArray();

            // Compute the octree.
            Vector3  size      = maxCoordinate - minCoordinate;
            Vector3U exponents = new Vector3U(
                (uint)Maths.GetNext2Exponent(size.X),
                (uint)Maths.GetNext2Exponent(size.Y),
                (uint)Maths.GetNext2Exponent(size.Z));
            int cubeSizePower = Maths.GetNext2Exponent(Math.Min(Math.Min(size.X, size.Y), size.Z));

            if (cubeSizePower > Maths.GetNext2Exponent(settings.MaxRootSize))
            {
                cubeSizePower = Maths.GetNext2Exponent(settings.MaxRootSize);
            }

            int cubeSize = 1 << cubeSizePower;

            CoordinateShift = new Vector3U(
                (uint)cubeSizePower,
                (uint)(exponents.X - cubeSizePower),
                (uint)(exponents.X - cubeSizePower + exponents.Y - cubeSizePower));
            CoordinateMask = new Vector3U(
                (uint)(0xFFFFFFFF << (int)exponents.X),
                (uint)(0xFFFFFFFF << (int)exponents.Y),
                (uint)(0xFFFFFFFF << (int)exponents.Z));
            Vector3U cubeCounts = new Vector3U(
                (uint)Math.Max(1, (1 << (int)exponents.X) / cubeSize),
                (uint)Math.Max(1, (1 << (int)exponents.Y) / cubeSize),
                (uint)Math.Max(1, (1 << (int)exponents.Z) / cubeSize));

            // Generate the root nodes, which are square cubes required to cover all of the model.
            PolygonOctreeRoots = new PolygonOctree[cubeCounts.X * cubeCounts.Y * cubeCounts.Z];


            int cubeBlow = SphereRadius > 0 ? (int)(SphereRadius * 2) : 50;

            DebugLogger.WriteLine($"Octree Distance Bias {cubeBlow}");
            DebugLogger.WriteLine($"Creating Octrees {cubeCounts}");

            int index = 0;

            for (int z = 0; z < cubeCounts.Z; z++)
            {
                for (int y = 0; y < cubeCounts.Y; y++)
                {
                    for (int x = 0; x < cubeCounts.X; x++)
                    {
                        Vector3 cubePosition = minCoordinate + ((float)cubeSize) * new Vector3(x, y, z);
                        PolygonOctreeRoots[index++] = new PolygonOctree(triangles, cubePosition, cubeSize,
                                                                        settings.MaxTrianglesInCube, settings.MaxCubeSize,
                                                                        settings.MinCubeSize, cubeBlow, settings.MaxOctreeDepth);
                    }
                }
            }
            DebugLogger.WriteLine($"Finished Octree");
        }