//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()); }
/// <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"); }