/// <summary> /// Loads the data from the given <paramref name="stream"/>. /// </summary> /// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <param name="loadOctree"><c>true</c> to also load the octree referencing triangles.</param> public void Load(Stream stream, bool loadOctree) { using (BinaryDataReader reader = new BinaryDataReader(stream, true) { ByteOrder = ByteOrder.BigEndian }) { long modelPosition = reader.Position; // Read the header. int positionArrayOffset = reader.ReadInt32(); int normalArrayOffset = reader.ReadInt32(); int triangleArrayOffset = reader.ReadInt32(); int octreeOffset = reader.ReadInt32(); Unknown1 = reader.ReadSingle(); MinCoordinate = reader.ReadVector3F(); CoordinateMask = reader.ReadVector3(); CoordinateShift = reader.ReadVector3(); Unknown2 = reader.ReadSingle(); // Read the positions. reader.Position = modelPosition + positionArrayOffset; // Mostly unrequired, data is successive. int positionCount = (normalArrayOffset - positionArrayOffset) / Vector3F.SizeInBytes; Positions = reader.ReadVector3Fs(positionCount); // Read the normals. reader.Position = modelPosition + normalArrayOffset; // Mostly unrequired, data is successive. int normalCount = (triangleArrayOffset - normalArrayOffset) / Vector3F.SizeInBytes; Normals = reader.ReadVector3Fs(normalCount); // Read the triangles. reader.Position = modelPosition + triangleArrayOffset; // Mostly unrequired, data is successive. int triangleCount = (octreeOffset - triangleArrayOffset) / Marshal.SizeOf <Triangle>(); Triangles = reader.ReadTriangles(triangleCount); // Read the octree. if (loadOctree) { reader.Position = modelPosition + octreeOffset; // Mostly unrequired, data is successive. int nodeCount = ((~CoordinateMask.X >> CoordinateShift.X) + 1) * ((~CoordinateMask.Y >> CoordinateShift.X) + 1) * ((~CoordinateMask.Z >> CoordinateShift.X) + 1); ModelOctreeRoots = new ModelOctreeNode[nodeCount]; for (int i = 0; i < nodeCount; i++) { ModelOctreeRoots[i] = new ModelOctreeNode(null, reader, modelPosition + octreeOffset); } // Reader is now behind the last octree key, not at end of the model / behind the last separator. } } }
/// <summary> /// Loads the data from the given <paramref name="stream"/>. /// </summary> /// <param name="stream">The <see cref="Stream"/> to load the data from.</param> /// <param name="loadOctree"><c>true</c> to also load the octree referencing triangles.</param> /// <param name="leaveOpen"><c>true</c> to leave <paramref name="stream"/> open after loading the instance. /// </param> public void Load(Stream stream, bool loadOctree = true, bool leaveOpen = false, ByteOrder Endianness = ByteOrder.LittleEndian) { using (BinaryDataReader reader = new BinaryDataReader(stream, leaveOpen)) { reader.ByteOrder = Endianness; long modelPosition = reader.Position; // Read the header. int positionArrayOffset = reader.ReadInt32(); int normalArrayOffset = reader.ReadInt32(); int triangleArrayOffset = reader.ReadInt32(); int octreeOffset = reader.ReadInt32(); reader.Seek(sizeof(float)); // Unknown value always being 30.0. MinCoordinate = reader.ReadVector3F(); CoordinateMask = reader.ReadVector3(); CoordinateShift = reader.ReadVector3(); reader.Seek(sizeof(float)); // Unknown value always being 25.0. // Read the positions. reader.Position = modelPosition + positionArrayOffset; // Mostly unrequired, data is successive. int positionCount = (normalArrayOffset - positionArrayOffset) / Vector3F.SizeInBytes; Positions = new List <Vector3F>(reader.ReadVector3Fs(positionCount)); // Read the normals. reader.Position = modelPosition + normalArrayOffset; // Mostly unrequired, data is successive. int normalCount = (triangleArrayOffset - normalArrayOffset) / Vector3F.SizeInBytes; Normals = new List <Vector3F>(reader.ReadVector3Fs(normalCount)); // Read the triangles. reader.Position = modelPosition + triangleArrayOffset; // Mostly unrequired, data is successive. int triangleCount = (octreeOffset - triangleArrayOffset) / Marshal.SizeOf <KclFace>(); Faces = reader.ReadTriangles(triangleCount); // Read the octree. if (loadOctree) { reader.Position = modelPosition + octreeOffset; // Mostly unrequired, data is successive. int nodeCount = ((~CoordinateMask.X >> CoordinateShift.X) + 1) * ((~CoordinateMask.Y >> CoordinateShift.X) + 1) * ((~CoordinateMask.Z >> CoordinateShift.X) + 1); ModelOctreeRoots = new ModelOctreeNode[nodeCount]; for (int i = 0; i < nodeCount; i++) { ModelOctreeRoots[i] = new ModelOctreeNode(reader, modelPosition + octreeOffset); } // Reader is now behind the last octree key, not at end of the model / behind the last separator. } } }
/// <summary> /// Initializes a new instance of the <see cref="KclModel"/> class, created from the given /// <paramref name="objModel"/>. /// </summary> /// <param name="objModel">The <see cref="ObjModel"/> to create the collision data from.</param> internal KclModel(ObjModel objModel) { if (objModel.Faces.Count > UInt16.MaxValue) { throw new InvalidOperationException("KCL models must not have more than 65535 triangles."); } // Transfer the faces to collision faces and find the smallest and biggest coordinates. Positions = new List <Vector3F>(objModel.Positions); Normals = new List <Vector3F>(objModel.Normals); Vector3F minCoordinate = new Vector3F(Single.MaxValue, Single.MaxValue, Single.MaxValue); Vector3F maxCoordinate = new Vector3F(Single.MinValue, Single.MinValue, Single.MinValue); KclFace[] faces = new KclFace[objModel.Faces.Count]; Triangle triangle = new Triangle(); Dictionary <ushort, Triangle> triangles = new Dictionary <ushort, Triangle>(objModel.Faces.Count); ushort i = 0; foreach (ObjFace objFace in objModel.Faces) { KclFace face = new KclFace() { PositionIndex = (ushort)objFace.Vertices[0].PositionIndex, Normal1Index = (ushort)objFace.Vertices[0].NormalIndex, Normal2Index = (ushort)objFace.Vertices[1].NormalIndex, Normal3Index = (ushort)objFace.Vertices[2].NormalIndex }; // Get the position vectors and find the smallest and biggest coordinates. for (int j = 0; j < 3; j++) { Vector3F position = Positions[objFace.Vertices[j].PositionIndex]; 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, minCoordinate.X); maxCoordinate.Y = Math.Max(position.X, minCoordinate.Y); maxCoordinate.Z = Math.Max(position.X, minCoordinate.Z); triangle.Vertices[j] = position; } // Compute the face direction (normal) and add it to the normal list. face.DirectionIndex = (ushort)(Normals.Count); Normals.Add(triangle.Normal); triangles.Add(i, triangle); faces[i++] = face; } minCoordinate += _minCoordinatePadding; maxCoordinate += _maxCoordinatePadding; MinCoordinate = minCoordinate; Faces = faces; // Compute the octree. Vector3F size = maxCoordinate - minCoordinate; Vector3 exponents = new Vector3( Maths.GetNext2Exponent(size.X), Maths.GetNext2Exponent(size.Y), Maths.GetNext2Exponent(size.Z)); int cubeSizePower = Maths.GetNext2Exponent(Math.Min(Math.Min(size.X, size.Y), size.Z)); int cubeSize = 1 << cubeSizePower; CoordinateShift = new Vector3( cubeSizePower, exponents.X - cubeSizePower, exponents.X - cubeSizePower + exponents.Y - cubeSizePower); CoordinateMask = new Vector3( (int)(0xFFFFFFFF << exponents.X), (int)(0xFFFFFFFF << exponents.Y), (int)(0xFFFFFFFF << exponents.Z)); Vector3 cubeCounts = new Vector3( Math.Max(1, (1 << exponents.X) / cubeSize), Math.Max(1, (1 << exponents.Y) / cubeSize), Math.Max(1, (1 << exponents.Z) / cubeSize)); // Generate the root nodes, which are square cubes required to cover all of the model. ModelOctreeRoots = new ModelOctreeNode[cubeCounts.X * cubeCounts.Y * cubeCounts.Z]; i = 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++) { Vector3F cubePosition = new Vector3F(x, y, z) * cubeSize + minCoordinate; ModelOctreeRoots[i++] = new ModelOctreeNode(triangles, cubePosition, cubeSize, _maxTrianglesInCube, _minCubeSize); } } } }