/// <summary> /// Interpolates a point between two vectors using Linear Interpolation. /// </summary> /// <param name="a">First vector</param> /// <param name="b">Second vector</param> /// <param name="mu">Value between 0-1 of the interpolation amount</param> /// <returns></returns> public static RenderBase.OVector4 interpolateLinear(RenderBase.OVector4 a, RenderBase.OVector4 b, float mu) { RenderBase.OVector4 output = new RenderBase.OVector4(); output.x = interpolateLinear(a.x, b.x, mu); output.y = interpolateLinear(a.y, b.y, mu); output.z = interpolateLinear(a.z, b.z, mu); output.w = interpolateLinear(a.w, b.w, mu); return(output); }
/// <summary> /// Exports a Model to the Source Model format. /// Note: SMD model specification doesnt support Model and Skeletal Animation on the same SMD. /// See: https://developer.valvesoftware.com/wiki/Studiomdl_Data for more information. /// </summary> /// <param name="model">The Model that will be exported</param> /// <param name="fileName">The output File Name</param> /// <param name="modelIndex">Index of the model to be exported</param> /// <param name="skeletalAnimationIndex">(Optional) Index of the skeletal animation</param> public static void export(RenderBase.OModelGroup model, string fileName, int modelIndex, int skeletalAnimationIndex = -1) { RenderBase.OModel mdl = model.model[modelIndex]; StringBuilder output = new StringBuilder(); output.AppendLine("version 1"); output.AppendLine("nodes"); for (int i = 0; i < mdl.skeleton.Count; i++) { output.AppendLine(i + " \"" + mdl.skeleton[i].name + "\" " + mdl.skeleton[i].parentId); } output.AppendLine("end"); output.AppendLine("skeleton"); if (skeletalAnimationIndex == -1) { output.AppendLine("time 0"); int index = 0; foreach (RenderBase.OBone bone in mdl.skeleton) { string line = index.ToString(); line += " " + getString(bone.translation.x); line += " " + getString(bone.translation.y); line += " " + getString(bone.translation.z); line += " " + getString(bone.rotation.x); line += " " + getString(bone.rotation.y); line += " " + getString(bone.rotation.z); output.AppendLine(line); index++; } } else { bool error = false; for (float frame = 0; frame < model.skeletalAnimation.list[skeletalAnimationIndex].frameSize; frame += 1) { output.AppendLine("time " + ((int)frame).ToString()); for (int index = 0; index < mdl.skeleton.Count; index++) { RenderBase.OBone newBone = new RenderBase.OBone(); newBone.parentId = mdl.skeleton[index].parentId; newBone.rotation = new RenderBase.OVector3(mdl.skeleton[index].rotation); newBone.translation = new RenderBase.OVector3(mdl.skeleton[index].translation); foreach (RenderBase.OSkeletalAnimationBone b in ((RenderBase.OSkeletalAnimation)model.skeletalAnimation.list[skeletalAnimationIndex]).bone) { if (b.isFullBakedFormat) { error = true; } if (b.name == mdl.skeleton[index].name && !b.isFullBakedFormat) { if (b.isFrameFormat) { if (b.translation.exists) { int tFrame = Math.Min((int)frame, b.translation.vector.Count - 1); newBone.translation.x = b.translation.vector[tFrame].x; newBone.translation.y = b.translation.vector[tFrame].y; newBone.translation.z = b.translation.vector[tFrame].z; } if (b.rotationQuaternion.exists) { int qFrame = Math.Min((int)frame, b.rotationQuaternion.vector.Count - 1); newBone.rotation = b.rotationQuaternion.vector[qFrame].toEuler(); } } else { if (b.translationX.exists) { newBone.translation.x = AnimationUtils.getKey(b.translationX, frame); newBone.translation.x *= mdl.skeleton[index].absoluteScale.x; } if (b.translationY.exists) { newBone.translation.y = AnimationUtils.getKey(b.translationY, frame); newBone.translation.y *= mdl.skeleton[index].absoluteScale.y; } if (b.translationZ.exists) { newBone.translation.z = AnimationUtils.getKey(b.translationZ, frame); newBone.translation.z *= mdl.skeleton[index].absoluteScale.z; } if (b.rotationX.exists) { newBone.rotation.x = AnimationUtils.getKey(b.rotationX, frame); } if (b.rotationY.exists) { newBone.rotation.y = AnimationUtils.getKey(b.rotationY, frame); } if (b.rotationZ.exists) { newBone.rotation.z = AnimationUtils.getKey(b.rotationZ, frame); } if (b.isAxisAngle) { if (newBone.rotation.length() == 0) { newBone.rotation = new RenderBase.OVector3(0, 0, 0); } else { RenderBase.OVector4 q = new RenderBase.OVector4(newBone.rotation.normalize(), newBone.rotation.length()); newBone.rotation = q.toEuler(); } } } break; } } string line = index.ToString(); line += " " + getString(newBone.translation.x); line += " " + getString(newBone.translation.y); line += " " + getString(newBone.translation.z); line += " " + getString(newBone.rotation.x); line += " " + getString(newBone.rotation.y); line += " " + getString(newBone.rotation.z); output.AppendLine(line); } } if (error) { MessageBox.Show( "One or more bones uses an animation type unsupported by Source Model!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } output.AppendLine("end"); if (skeletalAnimationIndex == -1) { output.AppendLine("triangles"); uint triangleCount = 0; int objectIndex = 0; foreach (RenderBase.OMesh obj in mdl.mesh) { string textureName = mdl.material[obj.materialId].name0 ?? "material_" + objectIndex.ToString(); foreach (RenderBase.OVertex vertex in obj.vertices) { if (triangleCount == 0) { output.AppendLine(textureName); } string line = "0"; line += " " + getString(vertex.position.x); line += " " + getString(vertex.position.y); line += " " + getString(vertex.position.z); line += " " + getString(vertex.normal.x); line += " " + getString(vertex.normal.y); line += " " + getString(vertex.normal.z); line += " " + getString(vertex.texture0.x); line += " " + getString(vertex.texture0.y); int nodeCount = Math.Min(vertex.node.Count, vertex.weight.Count); line += " " + nodeCount; for (int i = 0; i < nodeCount; i++) { line += " " + vertex.node[i]; line += " " + getString(vertex.weight[i]); } output.AppendLine(line); triangleCount = (triangleCount + 1) % 3; } objectIndex++; } output.AppendLine("end"); } File.WriteAllText(fileName, output.ToString()); }
/// <summary> /// Interpolates a point between two vectors using Linear Interpolation. /// </summary> /// <param name="a">First vector</param> /// <param name="b">Second vector</param> /// <param name="mu">Value between 0-1 of the interpolation amount</param> /// <returns></returns> public static RenderBase.OVector4 interpolateLinear(RenderBase.OVector4 a, RenderBase.OVector4 b, float mu) { RenderBase.OVector4 output = new RenderBase.OVector4(); output.x = interpolateLinear(a.x, b.x, mu); output.y = interpolateLinear(a.y, b.y, mu); output.z = interpolateLinear(a.z, b.z, mu); output.w = interpolateLinear(a.w, b.w, mu); return output; }
/// <summary> /// Loads a BCH file. /// Note that BCH must start at offset 0x0 (don't try using it for BCHs inside containers). /// </summary> /// <param name="data">Memory Stream of the BCH file. The Stream will not be usable after</param> /// <returns></returns> public static RenderBase.OModelGroup load(MemoryStream data) { BinaryReader input = new BinaryReader(data); BinaryWriter writer = new BinaryWriter(data); RenderBase.OModelGroup models = new RenderBase.OModelGroup(); //Primary header bchHeader header = new bchHeader(); header.magic = IOUtils.readString(input, 0); data.Seek(4, SeekOrigin.Current); header.backwardCompatibility = input.ReadByte(); header.forwardCompatibility = input.ReadByte(); header.version = input.ReadUInt16(); header.mainHeaderOffset = input.ReadUInt32(); header.stringTableOffset = input.ReadUInt32(); header.gpuCommandsOffset = input.ReadUInt32(); header.dataOffset = input.ReadUInt32(); if (header.backwardCompatibility > 0x20) header.dataExtendedOffset = input.ReadUInt32(); header.relocationTableOffset = input.ReadUInt32(); header.mainHeaderLength = input.ReadUInt32(); header.stringTableLength = input.ReadUInt32(); header.gpuCommandsLength = input.ReadUInt32(); header.dataLength = input.ReadUInt32(); if (header.backwardCompatibility > 0x20) header.dataExtendedLength = input.ReadUInt32(); header.relocationTableLength = input.ReadUInt32(); header.uninitializedDataSectionLength = input.ReadUInt32(); header.uninitializedDescriptionSectionLength = input.ReadUInt32(); if (header.backwardCompatibility > 7) { header.flags = input.ReadUInt16(); header.addressCount = input.ReadUInt16(); } //Transform relative offsets to absolute offsets, also add extra bits if necessary. //The game does this on RAM after the BCH is loaded, so offsets to data is absolute and points to VRAM. for (uint o = header.relocationTableOffset; o < header.relocationTableOffset + header.relocationTableLength; o += 4) { data.Seek(o, SeekOrigin.Begin); uint value = input.ReadUInt32(); uint offset = value & 0x1ffffff; byte flags = (byte)(value >> 25); switch (flags) { case 0: data.Seek((offset * 4) + header.mainHeaderOffset, SeekOrigin.Begin); writer.Write(peek(input) + header.mainHeaderOffset); break; case 1: data.Seek(offset + header.mainHeaderOffset, SeekOrigin.Begin); writer.Write(peek(input) + header.stringTableOffset); break; case 2: data.Seek((offset * 4) + header.mainHeaderOffset, SeekOrigin.Begin); writer.Write(peek(input) + header.gpuCommandsOffset); break; case 7: case 0xc: data.Seek((offset * 4) + header.mainHeaderOffset, SeekOrigin.Begin); writer.Write(peek(input) + header.dataOffset); break; } //The moron that designed the format used different flags on different versions, instead of keeping compatibility. data.Seek((offset * 4) + header.gpuCommandsOffset, SeekOrigin.Begin); if (header.backwardCompatibility < 6) { switch (flags) { case 0x23: writer.Write(peek(input) + header.dataOffset); break; //Texture case 0x25: writer.Write(peek(input) + header.dataOffset); break; //Vertex case 0x26: writer.Write(((peek(input) + header.dataOffset) & 0x7fffffff) | 0x80000000); break; //Index 16 bits mode case 0x27: writer.Write((peek(input) + header.dataOffset) & 0x7fffffff); break; //Index 8 bits mode } } else if (header.backwardCompatibility < 8) { switch (flags) { case 0x24: writer.Write(peek(input) + header.dataOffset); break; //Texture case 0x26: writer.Write(peek(input) + header.dataOffset); break; //Vertex case 0x27: writer.Write(((peek(input) + header.dataOffset) & 0x7fffffff) | 0x80000000); break; //Index 16 bits mode case 0x28: writer.Write((peek(input) + header.dataOffset) & 0x7fffffff); break; //Index 8 bits mode } } else if (header.backwardCompatibility < 0x21) { switch (flags) { case 0x25: writer.Write(peek(input) + header.dataOffset); break; //Texture case 0x27: writer.Write(peek(input) + header.dataOffset); break; //Vertex case 0x28: writer.Write(((peek(input) + header.dataOffset) & 0x7fffffff) | 0x80000000); break; //Index 16 bits mode case 0x29: writer.Write((peek(input) + header.dataOffset) & 0x7fffffff); break; //Index 8 bits mode } } else { switch (flags) { case 0x25: writer.Write(peek(input) + header.dataOffset); break; //Texture case 0x26: writer.Write(peek(input) + header.dataOffset); break; //Vertex relative to Data Offset case 0x27: writer.Write(((peek(input) + header.dataOffset) & 0x7fffffff) | 0x80000000); break; //Index 16 bits mode relative to Data Offset case 0x28: writer.Write((peek(input) + header.dataOffset) & 0x7fffffff); break; //Index 8 bits mode relative to Data Offset case 0x2b: writer.Write(peek(input) + header.dataExtendedOffset); break; //Vertex relative to Data Extended Offset case 0x2c: writer.Write(((peek(input) + header.dataExtendedOffset) & 0x7fffffff) | 0x80000000); break; //Index 16 bits mode relative to Data Extended Offset case 0x2d: writer.Write((peek(input) + header.dataExtendedOffset) & 0x7fffffff); break; //Index 8 bits mode relative to Data Extended Offset } } } //Content header data.Seek(header.mainHeaderOffset, SeekOrigin.Begin); bchContentHeader contentHeader = new bchContentHeader { modelsPointerTableOffset = input.ReadUInt32(), modelsPointerTableEntries = input.ReadUInt32(), modelsNameOffset = input.ReadUInt32(), materialsPointerTableOffset = input.ReadUInt32(), materialsPointerTableEntries = input.ReadUInt32(), materialsNameOffset = input.ReadUInt32(), shadersPointerTableOffset = input.ReadUInt32(), shadersPointerTableEntries = input.ReadUInt32(), shadersNameOffset = input.ReadUInt32(), texturesPointerTableOffset = input.ReadUInt32(), texturesPointerTableEntries = input.ReadUInt32(), texturesNameOffset = input.ReadUInt32(), materialsLUTPointerTableOffset = input.ReadUInt32(), materialsLUTPointerTableEntries = input.ReadUInt32(), materialsLUTNameOffset = input.ReadUInt32(), lightsPointerTableOffset = input.ReadUInt32(), lightsPointerTableEntries = input.ReadUInt32(), lightsNameOffset = input.ReadUInt32(), camerasPointerTableOffset = input.ReadUInt32(), camerasPointerTableEntries = input.ReadUInt32(), camerasNameOffset = input.ReadUInt32(), fogsPointerTableOffset = input.ReadUInt32(), fogsPointerTableEntries = input.ReadUInt32(), fogsNameOffset = input.ReadUInt32(), skeletalAnimationsPointerTableOffset = input.ReadUInt32(), skeletalAnimationsPointerTableEntries = input.ReadUInt32(), skeletalAnimationsNameOffset = input.ReadUInt32(), materialAnimationsPointerTableOffset = input.ReadUInt32(), materialAnimationsPointerTableEntries = input.ReadUInt32(), materialAnimationsNameOffset = input.ReadUInt32(), visibilityAnimationsPointerTableOffset = input.ReadUInt32(), visibilityAnimationsPointerTableEntries = input.ReadUInt32(), visibilityAnimationsNameOffset = input.ReadUInt32(), lightAnimationsPointerTableOffset = input.ReadUInt32(), lightAnimationsPointerTableEntries = input.ReadUInt32(), lightAnimationsNameOffset = input.ReadUInt32(), cameraAnimationsPointerTableOffset = input.ReadUInt32(), cameraAnimationsPointerTableEntries = input.ReadUInt32(), cameraAnimationsNameOffset = input.ReadUInt32(), fogAnimationsPointerTableOffset = input.ReadUInt32(), fogAnimationsPointerTableEntries = input.ReadUInt32(), fogAnimationsNameOffset = input.ReadUInt32(), scenePointerTableOffset = input.ReadUInt32(), scenePointerTableEntries = input.ReadUInt32(), sceneNameOffset = input.ReadUInt32() }; //Note: NameOffset are PATRICIA trees //Shaders for (int index = 0; index < contentHeader.shadersPointerTableEntries; index++) { data.Seek(contentHeader.shadersPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); uint shaderDataOffset = input.ReadUInt32(); uint shaderDataLength = input.ReadUInt32(); } //Textures for (int index = 0; index < contentHeader.texturesPointerTableEntries; index++) { data.Seek(contentHeader.texturesPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); uint textureCommandsOffset = input.ReadUInt32(); uint textureCommandsWordCount = input.ReadUInt32(); data.Seek(0x14, SeekOrigin.Current); string textureName = readString(input); data.Seek(textureCommandsOffset, SeekOrigin.Begin); PICACommandReader textureCommands = new PICACommandReader(data, textureCommandsWordCount); //Note: It have textures for the 3 texture units. //The other texture units are used with textureCoordinate1 and 2. data.Seek(textureCommands.getTexUnit0Address(), SeekOrigin.Begin); Size textureSize = textureCommands.getTexUnit0Size(); byte[] buffer = new byte[textureSize.Width * textureSize.Height * 4]; input.Read(buffer, 0, buffer.Length); Bitmap texture = TextureCodec.decode( buffer, textureSize.Width, textureSize.Height, textureCommands.getTexUnit0Format()); models.texture.Add(new RenderBase.OTexture(texture, textureName)); } //LookUp Tables for (int index = 0; index < contentHeader.materialsLUTPointerTableEntries; index++) { data.Seek(contentHeader.materialsLUTPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); input.ReadUInt32(); uint samplersCount = input.ReadUInt32(); string name = readString(input); RenderBase.OLookUpTable table = new RenderBase.OLookUpTable(); table.name = name; for (int i = 0; i < samplersCount; i++) { RenderBase.OLookUpTableSampler sampler = new RenderBase.OLookUpTableSampler(); input.ReadUInt32(); uint tableOffset = input.ReadUInt32(); uint tableWordCount = input.ReadUInt32(); sampler.name = readString(input); long dataPosition = data.Position; data.Seek(tableOffset, SeekOrigin.Begin); PICACommandReader lutCommands = new PICACommandReader(data, tableWordCount); sampler.table = lutCommands.getFSHLookUpTable(); table.sampler.Add(sampler); data.Seek(dataPosition, SeekOrigin.Begin); } models.lookUpTable.Add(table); } //Lights for (int index = 0; index < contentHeader.lightsPointerTableEntries; index++) { data.Seek(contentHeader.lightsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OLight light = new RenderBase.OLight(); light.name = readString(input); light.transformScale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); light.transformRotate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); light.transformTranslate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); uint lightFlags = input.ReadUInt32(); switch (lightFlags & 0xf) { case 1: light.lightUse = RenderBase.OLightUse.hemiSphere; break; case 2: light.lightUse = RenderBase.OLightUse.ambient; break; case 5: case 6: case 7: light.lightUse = RenderBase.OLightUse.vertex; switch (lightFlags & 0xf) { case 5: light.lightType = RenderBase.OLightType.directional; break; case 6: light.lightType = RenderBase.OLightType.point; break; case 7: light.lightType = RenderBase.OLightType.spot; break; } break; case 9: case 0xa: case 0xb: light.lightUse = RenderBase.OLightUse.fragment; switch (lightFlags & 0xf) { case 9: light.lightType = RenderBase.OLightType.directional; break; case 0xa: light.lightType = RenderBase.OLightType.point; break; case 0xb: light.lightType = RenderBase.OLightType.spot; break; } break; default: Debug.WriteLine(string.Format("BCH: Warning - Unknow Light Flags {0}", lightFlags.ToString("X8"))); break; } light.isLightEnabled = (lightFlags & 0x100) > 0; light.isTwoSideDiffuse = (lightFlags & 0x10000) > 0; light.isDistanceAttenuationEnabled = (lightFlags & 0x20000) > 0; light.angleSampler.input = (RenderBase.OFragmentSamplerInput)((lightFlags >> 24) & 0xf); light.angleSampler.scale = (RenderBase.OFragmentSamplerScale)((lightFlags >> 28) & 0xf); input.ReadUInt32(); switch (light.lightUse) { case RenderBase.OLightUse.hemiSphere: light.groundColor = MeshUtils.getColorFloat(input); light.skyColor = MeshUtils.getColorFloat(input); light.direction = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); light.lerpFactor = input.ReadSingle(); break; case RenderBase.OLightUse.ambient: light.ambient = MeshUtils.getColor(input); break; case RenderBase.OLightUse.vertex: light.ambient = MeshUtils.getColorFloat(input); light.diffuse = MeshUtils.getColorFloat(input); light.direction = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); light.distanceAttenuationConstant = input.ReadSingle(); light.distanceAttenuationLinear = input.ReadSingle(); light.distanceAttenuationQuadratic = input.ReadSingle(); light.spotExponent = input.ReadSingle(); light.spotCutoffAngle = input.ReadSingle(); break; case RenderBase.OLightUse.fragment: light.ambient = MeshUtils.getColor(input); light.diffuse = MeshUtils.getColor(input); light.specular0 = MeshUtils.getColor(input); light.specular1 = MeshUtils.getColor(input); light.direction = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); input.ReadUInt32(); input.ReadUInt32(); light.attenuationStart = input.ReadSingle(); light.attenuationEnd = input.ReadSingle(); light.distanceSampler.tableName = readString(input); light.distanceSampler.samplerName = readString(input); light.angleSampler.tableName = readString(input); light.angleSampler.samplerName = readString(input); break; } models.light.Add(light); } //Cameras for (int index = 0; index < contentHeader.camerasPointerTableEntries; index++) { data.Seek(contentHeader.camerasPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OCamera camera = new RenderBase.OCamera(); camera.name = readString(input); camera.transformScale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); camera.transformRotate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); camera.transformTranslate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); uint cameraFlags = input.ReadUInt32(); camera.isInheritingTargetRotate = (cameraFlags & 0x10000) > 0; camera.isInheritingTargetTranslate = (cameraFlags & 0x20000) > 0; camera.isInheritingUpRotate = (cameraFlags & 0x40000) > 0; camera.view = (RenderBase.OCameraView)(cameraFlags & 0xf); camera.projection = (RenderBase.OCameraProjection)((cameraFlags >> 8) & 0xf); input.ReadSingle(); uint viewOffset = input.ReadUInt32(); uint projectionOffset = input.ReadUInt32(); data.Seek(viewOffset, SeekOrigin.Begin); camera.target = new RenderBase.OVector3(); camera.rotation = new RenderBase.OVector3(); camera.upVector = new RenderBase.OVector3(); RenderBase.OVector3 target = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); switch (camera.view) { case RenderBase.OCameraView.aimTarget: camera.target = target; camera.twist = input.ReadSingle(); break; case RenderBase.OCameraView.lookAtTarget: camera.target = target; camera.upVector = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); break; case RenderBase.OCameraView.rotate: camera.rotation = target; break; } data.Seek(projectionOffset, SeekOrigin.Begin); camera.zNear = input.ReadSingle(); camera.zFar = input.ReadSingle(); camera.aspectRatio = input.ReadSingle(); switch (camera.projection) { case RenderBase.OCameraProjection.perspective: camera.fieldOfViewY = input.ReadSingle(); break; case RenderBase.OCameraProjection.orthogonal: camera.height = input.ReadSingle(); break; } models.camera.Add(camera); } //Fogs for (int index = 0; index < contentHeader.fogsPointerTableEntries; index++) { data.Seek(contentHeader.fogsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OFog fog = new RenderBase.OFog(); fog.name = readString(input); fog.transformScale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); fog.transformRotate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); fog.transformTranslate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); uint fogFlags = input.ReadUInt32(); fog.fogUpdater = (RenderBase.OFogUpdater)(fogFlags & 0xf); fog.isZFlip = (fogFlags & 0x100) > 0; fog.isAttenuateDistance = (fogFlags & 0x200) > 0; fog.fogColor = MeshUtils.getColor(input); fog.minFogDepth = input.ReadSingle(); fog.maxFogDepth = input.ReadSingle(); fog.fogDensity = input.ReadSingle(); models.fog.Add(fog); } //Skeletal Animations for (int index = 0; index < contentHeader.skeletalAnimationsPointerTableEntries; index++) { data.Seek(contentHeader.skeletalAnimationsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OSkeletalAnimation skeletalAnimation = new RenderBase.OSkeletalAnimation(); skeletalAnimation.name = readString(input); uint animationFlags = input.ReadUInt32(); skeletalAnimation.loopMode = (RenderBase.OLoopMode)(animationFlags & 1); skeletalAnimation.frameSize = input.ReadSingle(); uint boneTableOffset = input.ReadUInt32(); uint boneTableEntries = input.ReadUInt32(); uint metaDataPointerOffset = input.ReadUInt32(); if (metaDataPointerOffset != 0) { data.Seek(metaDataPointerOffset, SeekOrigin.Begin); skeletalAnimation.userData = getMetaData(input); } for (int i = 0; i < boneTableEntries; i++) { data.Seek(boneTableOffset + (i * 4), SeekOrigin.Begin); uint offset = input.ReadUInt32(); RenderBase.OSkeletalAnimationBone bone = new RenderBase.OSkeletalAnimationBone(); data.Seek(offset, SeekOrigin.Begin); bone.name = readString(input); uint animationTypeFlags = input.ReadUInt32(); uint flags = input.ReadUInt32(); input.ReadUInt32(); RenderBase.OSegmentType segmentType = (RenderBase.OSegmentType)((animationTypeFlags >> 16) & 0xf); switch (segmentType) { case RenderBase.OSegmentType.transform: data.Seek(offset + 0x18, SeekOrigin.Begin); uint notExistMask = 0x80000; uint constantMask = 0x200; for (int j = 0; j < 2; j++) { for (int axis = 0; axis < 3; axis++) { bool notExist = (flags & notExistMask) > 0; bool constant = (flags & constantMask) > 0; RenderBase.OAnimationKeyFrameGroup frame = new RenderBase.OAnimationKeyFrameGroup(); frame.exists = !notExist; if (frame.exists) { if (constant) { frame.interpolation = RenderBase.OInterpolationMode.linear; frame.keyFrames.Add(new RenderBase.OAnimationKeyFrame(input.ReadSingle(), 0)); } else { uint frameOffset = input.ReadUInt32(); long position = data.Position; data.Seek(frameOffset, SeekOrigin.Begin); getAnimationKeyFrame(input, frame); data.Seek(position, SeekOrigin.Begin); } } else data.Seek(4, SeekOrigin.Current); if (j == 0) { switch (axis) { case 0: bone.rotationX = frame; break; case 1: bone.rotationY = frame; break; case 2: bone.rotationZ = frame; break; } } else { switch (axis) { case 0: bone.translationX = frame; break; case 1: bone.translationY = frame; break; case 2: bone.translationZ = frame; break; } } notExistMask <<= 1; constantMask <<= 1; } constantMask <<= 1; } break; case RenderBase.OSegmentType.transformQuaternion: bone.isFrameFormat = true; long originalPos = data.Position; uint rotationOffset = input.ReadUInt32(); uint translationOffset = input.ReadUInt32(); if ((flags & 0x10) == 0) { bone.rotationQuaternion.exists = true; data.Seek(rotationOffset, SeekOrigin.Begin); if ((flags & 2) > 0) { bone.rotationQuaternion.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle())); } else { bone.rotationQuaternion.startFrame = input.ReadSingle(); bone.rotationQuaternion.endFrame = input.ReadSingle(); uint rotationFlags = input.ReadUInt32(); uint rotationDataOffset = input.ReadUInt32(); uint rotationEntries = input.ReadUInt32(); data.Seek(rotationDataOffset, SeekOrigin.Begin); for (int j = 0; j < rotationEntries; j++) { bone.rotationQuaternion.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle())); } } } if ((flags & 8) == 0) { bone.translation.exists = true; data.Seek(translationOffset, SeekOrigin.Begin); if ((flags & 1) > 0) { bone.translation.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 0)); } else { bone.translation.startFrame = input.ReadSingle(); bone.translation.endFrame = input.ReadSingle(); uint translationFlags = input.ReadUInt32(); uint translationDataOffset = input.ReadUInt32(); uint translationEntries = input.ReadUInt32(); data.Seek(translationDataOffset, SeekOrigin.Begin); for (int j = 0; j < translationEntries; j++) { bone.translation.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 0)); } } } break; case RenderBase.OSegmentType.transformMatrix: bone.isFullBakedFormat = true; input.ReadUInt32(); uint matrixOffset = input.ReadUInt32(); uint entries = input.ReadUInt32(); data.Seek(matrixOffset, SeekOrigin.Begin); for (int j = 0; j < entries; j++) { RenderBase.OMatrix transform = new RenderBase.OMatrix(); transform.M11 = input.ReadSingle(); transform.M21 = input.ReadSingle(); transform.M31 = input.ReadSingle(); transform.M41 = input.ReadSingle(); transform.M12 = input.ReadSingle(); transform.M22 = input.ReadSingle(); transform.M32 = input.ReadSingle(); transform.M42 = input.ReadSingle(); transform.M13 = input.ReadSingle(); transform.M23 = input.ReadSingle(); transform.M33 = input.ReadSingle(); transform.M43 = input.ReadSingle(); bone.transform.Add(transform); } break; default: throw new Exception(string.Format("BCH: Unknow Segment Type {0} on Skeletal Animation bone {1}! STOP!", segmentType, bone.name)); } skeletalAnimation.bone.Add(bone); } models.skeletalAnimation.list.Add(skeletalAnimation); } //Material Animations for (int index = 0; index < contentHeader.materialAnimationsPointerTableEntries; index++) { data.Seek(contentHeader.materialAnimationsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OMaterialAnimation materialAnimation = new RenderBase.OMaterialAnimation(); materialAnimation.name = readString(input); uint animationFlags = input.ReadUInt32(); materialAnimation.loopMode = (RenderBase.OLoopMode)(animationFlags & 1); materialAnimation.frameSize = input.ReadSingle(); uint dataTableOffset = input.ReadUInt32(); uint dataTableEntries = input.ReadUInt32(); input.ReadUInt32(); uint textureNameTableOffset = input.ReadUInt32(); uint textureNameTableEntries = input.ReadUInt32(); data.Seek(textureNameTableOffset, SeekOrigin.Begin); for (int i = 0; i < textureNameTableEntries; i++) { string name = readString(input); materialAnimation.textureName.Add(name); } for (int i = 0; i < dataTableEntries; i++) { data.Seek(dataTableOffset + (i * 4), SeekOrigin.Begin); uint offset = input.ReadUInt32(); RenderBase.OMaterialAnimationData animationData = new RenderBase.OMaterialAnimationData(); data.Seek(offset, SeekOrigin.Begin); animationData.name = readString(input); uint animationTypeFlags = input.ReadUInt32(); uint flags = input.ReadUInt32(); animationData.type = (RenderBase.OMaterialAnimationType)(animationTypeFlags & 0xff); RenderBase.OSegmentType segmentType = (RenderBase.OSegmentType)((animationTypeFlags >> 16) & 0xf); int segmentCount = 0; switch (segmentType) { case RenderBase.OSegmentType.rgbaColor: segmentCount = 4; break; case RenderBase.OSegmentType.vector2: segmentCount = 2; break; case RenderBase.OSegmentType.single: segmentCount = 1; break; case RenderBase.OSegmentType.integer: segmentCount = 1; break; } for (int j = 0; j < segmentCount; j++) { RenderBase.OAnimationKeyFrameGroup frame = new RenderBase.OAnimationKeyFrameGroup(); data.Seek(offset + 0xc + (j * 4), SeekOrigin.Begin); frame.exists = (flags & (0x100 << j)) == 0; bool constant = (flags & (1 << j)) > 0; if (frame.exists) { if (constant) { frame.interpolation = RenderBase.OInterpolationMode.linear; frame.keyFrames.Add(new RenderBase.OAnimationKeyFrame(input.ReadSingle(), 0)); } else { uint frameOffset = input.ReadUInt32(); data.Seek(frameOffset, SeekOrigin.Begin); getAnimationKeyFrame(input, frame); } } animationData.frameList.Add(frame); } materialAnimation.data.Add(animationData); } models.materialAnimation.list.Add(materialAnimation); } //Visibility Animations for (int index = 0; index < contentHeader.visibilityAnimationsPointerTableEntries; index++) { data.Seek(contentHeader.visibilityAnimationsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OVisibilityAnimation visibilityAnimation = new RenderBase.OVisibilityAnimation(); visibilityAnimation.name = readString(input); uint animationFlags = input.ReadUInt32(); visibilityAnimation.loopMode = (RenderBase.OLoopMode)(animationFlags & 1); visibilityAnimation.frameSize = input.ReadSingle(); uint dataTableOffset = input.ReadUInt32(); uint dataTableEntries = input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); for (int i = 0; i < dataTableEntries; i++) { data.Seek(dataTableOffset + (i * 4), SeekOrigin.Begin); uint offset = input.ReadUInt32(); RenderBase.OVisibilityAnimationData animationData = new RenderBase.OVisibilityAnimationData(); data.Seek(offset, SeekOrigin.Begin); animationData.name = readString(input); uint animationTypeFlags = input.ReadUInt32(); uint flags = input.ReadUInt32(); RenderBase.OSegmentType segmentType = (RenderBase.OSegmentType)((animationTypeFlags >> 16) & 0xf); if (segmentType == RenderBase.OSegmentType.boolean) { RenderBase.OAnimationKeyFrameGroup frame = new RenderBase.OAnimationKeyFrameGroup(); if (segmentType == RenderBase.OSegmentType.boolean) frame = getAnimationKeyFrameBool(input); animationData.visibilityList = frame; } visibilityAnimation.data.Add(animationData); } models.visibilityAnimation.list.Add(visibilityAnimation); } //Light Animations for (int index = 0; index < contentHeader.lightAnimationsPointerTableEntries; index++) { data.Seek(contentHeader.lightAnimationsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OLightAnimation lightAnimation = new RenderBase.OLightAnimation(); lightAnimation.name = readString(input); uint animationFlags = input.ReadUInt32(); lightAnimation.loopMode = (RenderBase.OLoopMode)(animationFlags & 1); lightAnimation.frameSize = input.ReadSingle(); uint dataTableOffset = input.ReadUInt32(); uint dataTableEntries = input.ReadUInt32(); input.ReadUInt32(); uint typeFlags = input.ReadUInt32(); lightAnimation.lightType = (RenderBase.OLightType)((typeFlags & 3) - 1); lightAnimation.lightUse = (RenderBase.OLightUse)((typeFlags >> 2) & 3); for (int i = 0; i < dataTableEntries; i++) { data.Seek(dataTableOffset + (i * 4), SeekOrigin.Begin); uint offset = input.ReadUInt32(); RenderBase.OLightAnimationData animationData = new RenderBase.OLightAnimationData(); data.Seek(offset, SeekOrigin.Begin); animationData.name = readString(input); uint animationTypeFlags = input.ReadUInt32(); uint flags = input.ReadUInt32(); animationData.type = (RenderBase.OLightAnimationType)(animationTypeFlags & 0xff); RenderBase.OSegmentType segmentType = (RenderBase.OSegmentType)((animationTypeFlags >> 16) & 0xf); int segmentCount = 0; switch (segmentType) { case RenderBase.OSegmentType.transform: segmentCount = 9; break; case RenderBase.OSegmentType.rgbaColor: segmentCount = 4; break; case RenderBase.OSegmentType.vector3: segmentCount = 3; break; case RenderBase.OSegmentType.single: segmentCount = 1; break; case RenderBase.OSegmentType.boolean: segmentCount = 1; break; } uint constantMask = 0x40; for (int j = 0; j < segmentCount; j++) { RenderBase.OAnimationKeyFrameGroup frame = new RenderBase.OAnimationKeyFrameGroup(); data.Seek(offset + 0xc + (j * 4), SeekOrigin.Begin); frame.exists = ((flags >> (segmentType == RenderBase.OSegmentType.transform ? 16 : 8)) & (1 << j)) == 0; if (frame.exists) { if (segmentType == RenderBase.OSegmentType.boolean) { frame = getAnimationKeyFrameBool(input); } else { bool constant; if (segmentType == RenderBase.OSegmentType.transform) { constant = (flags & constantMask) > 0; if (j == 5) constantMask <<= 2; else constantMask <<= 1; } else constant = (flags & (1 << j)) > 0; if (constant) { frame.interpolation = RenderBase.OInterpolationMode.linear; frame.keyFrames.Add(new RenderBase.OAnimationKeyFrame(input.ReadSingle(), 0.0f)); } else { uint frameOffset = input.ReadUInt32(); data.Seek(frameOffset, SeekOrigin.Begin); getAnimationKeyFrame(input, frame); } } } animationData.frameList.Add(frame); } lightAnimation.data.Add(animationData); } models.lightAnimation.list.Add(lightAnimation); } //Camera Animations for (int index = 0; index < contentHeader.cameraAnimationsPointerTableEntries; index++) { data.Seek(contentHeader.cameraAnimationsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OCameraAnimation cameraAnimation = new RenderBase.OCameraAnimation(); cameraAnimation.name = readString(input); uint animationFlags = input.ReadUInt32(); cameraAnimation.loopMode = (RenderBase.OLoopMode)(animationFlags & 1); cameraAnimation.frameSize = input.ReadSingle(); uint dataTableOffset = input.ReadUInt32(); uint dataTableEntries = input.ReadUInt32(); input.ReadUInt32(); uint modeFlags = input.ReadUInt32(); cameraAnimation.viewMode = (RenderBase.OCameraView)(modeFlags & 0xf); cameraAnimation.projectionMode = (RenderBase.OCameraProjection)((modeFlags >> 8) & 0xf); for (int i = 0; i < dataTableEntries; i++) { data.Seek(dataTableOffset + (i * 4), SeekOrigin.Begin); uint offset = input.ReadUInt32(); RenderBase.OCameraAnimationData animationData = new RenderBase.OCameraAnimationData(); data.Seek(offset, SeekOrigin.Begin); animationData.name = readString(input); uint animationTypeFlags = input.ReadUInt32(); uint flags = input.ReadUInt32(); animationData.type = (RenderBase.OCameraAnimationType)(animationTypeFlags & 0xff); RenderBase.OSegmentType segmentType = (RenderBase.OSegmentType)((animationTypeFlags >> 16) & 0xf); int segmentCount = 0; switch (segmentType) { case RenderBase.OSegmentType.transform: segmentCount = 9; break; case RenderBase.OSegmentType.vector3: segmentCount = 3; break; case RenderBase.OSegmentType.single: segmentCount = 1; break; } uint constantMask = 0x40; for (int j = 0; j < segmentCount; j++) { RenderBase.OAnimationKeyFrameGroup frame = new RenderBase.OAnimationKeyFrameGroup(); data.Seek(offset + 0xc + (j * 4), SeekOrigin.Begin); frame.exists = ((flags >> (segmentType == RenderBase.OSegmentType.transform ? 16 : 8)) & (1 << j)) == 0; bool constant; if (segmentType == RenderBase.OSegmentType.transform) { constant = (flags & constantMask) > 0; if (j == 5) constantMask <<= 2; else constantMask <<= 1; } else constant = (flags & (1 << j)) > 0; if (frame.exists) { if (constant) { frame.interpolation = RenderBase.OInterpolationMode.linear; frame.keyFrames.Add(new RenderBase.OAnimationKeyFrame(input.ReadSingle(), 0.0f)); } else { uint frameOffset = input.ReadUInt32(); data.Seek(frameOffset, SeekOrigin.Begin); getAnimationKeyFrame(input, frame); } } animationData.frameList.Add(frame); } cameraAnimation.data.Add(animationData); } models.cameraAnimation.list.Add(cameraAnimation); } //Fog Animations for (int index = 0; index < contentHeader.fogAnimationsPointerTableEntries; index++) { data.Seek(contentHeader.fogAnimationsPointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OFogAnimation fogAnimation = new RenderBase.OFogAnimation(); fogAnimation.name = readString(input); uint animationFlags = input.ReadUInt32(); fogAnimation.loopMode = (RenderBase.OLoopMode)(animationFlags & 1); fogAnimation.frameSize = input.ReadSingle(); uint dataTableOffset = input.ReadUInt32(); uint dataTableEntries = input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); for (int i = 0; i < dataTableEntries; i++) { data.Seek(dataTableOffset + (i * 4), SeekOrigin.Begin); uint offset = input.ReadUInt32(); RenderBase.OFogAnimationData animationData = new RenderBase.OFogAnimationData(); data.Seek(offset, SeekOrigin.Begin); animationData.name = readString(input); uint animationTypeFlags = input.ReadUInt32(); uint flags = input.ReadUInt32(); RenderBase.OSegmentType segmentType = (RenderBase.OSegmentType)((animationTypeFlags >> 16) & 0xf); int segmentCount = segmentType == RenderBase.OSegmentType.rgbaColor ? 4 : 0; for (int j = 0; j < segmentCount; j++) { RenderBase.OAnimationKeyFrameGroup frame = new RenderBase.OAnimationKeyFrameGroup(); data.Seek(offset + 0xc + (j * 4), SeekOrigin.Begin); frame.exists = ((flags >> 8) & (1 << j)) == 0; if (frame.exists) { bool constant = (flags & (1 << j)) > 0; if (constant) { frame.interpolation = RenderBase.OInterpolationMode.linear; frame.keyFrames.Add(new RenderBase.OAnimationKeyFrame(input.ReadSingle(), 0.0f)); } else { uint frameOffset = input.ReadUInt32(); data.Seek(frameOffset, SeekOrigin.Begin); getAnimationKeyFrame(input, frame); } } animationData.colorList.Add(frame); } fogAnimation.data.Add(animationData); } models.fogAnimation.list.Add(fogAnimation); } //Scene Environment for (int index = 0; index < contentHeader.scenePointerTableEntries; index++) { data.Seek(contentHeader.scenePointerTableOffset + (index * 4), SeekOrigin.Begin); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); RenderBase.OScene scene = new RenderBase.OScene(); scene.name = readString(input); uint cameraReferenceOffset = input.ReadUInt32(); uint cameraReferenceEntries = input.ReadUInt32(); uint lightReferenceOffset = input.ReadUInt32(); uint lightReferenceEntries = input.ReadUInt32(); uint fogReferenceOffset = input.ReadUInt32(); uint fogReferenceEntries = input.ReadUInt32(); data.Seek(cameraReferenceOffset, SeekOrigin.Begin); for (int i = 0; i < cameraReferenceEntries; i++) { RenderBase.OSceneReference reference = new RenderBase.OSceneReference(); reference.slotIndex = input.ReadUInt32(); reference.name = readString(input); scene.cameras.Add(reference); } data.Seek(lightReferenceOffset, SeekOrigin.Begin); for (int i = 0; i < lightReferenceEntries; i++) { RenderBase.OSceneReference reference = new RenderBase.OSceneReference(); reference.slotIndex = input.ReadUInt32(); reference.name = readString(input); scene.lights.Add(reference); } data.Seek(fogReferenceOffset, SeekOrigin.Begin); for (int i = 0; i < fogReferenceEntries; i++) { RenderBase.OSceneReference reference = new RenderBase.OSceneReference(); reference.slotIndex = input.ReadUInt32(); reference.name = readString(input); scene.fogs.Add(reference); } } //Models for (int modelIndex = 0; modelIndex < contentHeader.modelsPointerTableEntries; modelIndex++) { RenderBase.OModel model = new RenderBase.OModel(); data.Seek(contentHeader.modelsPointerTableOffset + (modelIndex * 4), SeekOrigin.Begin); uint objectsHeaderOffset = input.ReadUInt32(); //Objects header data.Seek(objectsHeaderOffset, SeekOrigin.Begin); bchModelHeader modelHeader; modelHeader.flags = input.ReadByte(); modelHeader.skeletonScalingType = input.ReadByte(); modelHeader.silhouetteMaterialEntries = input.ReadUInt16(); modelHeader.worldTransform = new RenderBase.OMatrix(); modelHeader.worldTransform.M11 = input.ReadSingle(); modelHeader.worldTransform.M21 = input.ReadSingle(); modelHeader.worldTransform.M31 = input.ReadSingle(); modelHeader.worldTransform.M41 = input.ReadSingle(); modelHeader.worldTransform.M12 = input.ReadSingle(); modelHeader.worldTransform.M22 = input.ReadSingle(); modelHeader.worldTransform.M32 = input.ReadSingle(); modelHeader.worldTransform.M42 = input.ReadSingle(); modelHeader.worldTransform.M13 = input.ReadSingle(); modelHeader.worldTransform.M23 = input.ReadSingle(); modelHeader.worldTransform.M33 = input.ReadSingle(); modelHeader.worldTransform.M43 = input.ReadSingle(); modelHeader.materialsTableOffset = input.ReadUInt32(); modelHeader.materialsTableEntries = input.ReadUInt32(); modelHeader.materialsNameOffset = input.ReadUInt32(); modelHeader.verticesTableOffset = input.ReadUInt32(); modelHeader.verticesTableEntries = input.ReadUInt32(); data.Seek(header.backwardCompatibility > 6 ? 0x28 : 0x20, SeekOrigin.Current); modelHeader.skeletonOffset = input.ReadUInt32(); modelHeader.skeletonEntries = input.ReadUInt32(); modelHeader.skeletonNameOffset = input.ReadUInt32(); modelHeader.objectsNodeVisibilityOffset = input.ReadUInt32(); modelHeader.objectsNodeCount = input.ReadUInt32(); modelHeader.modelName = readString(input); modelHeader.objectsNodeNameEntries = input.ReadUInt32(); modelHeader.objectsNodeNameOffset = input.ReadUInt32(); input.ReadUInt32(); //0x0 modelHeader.metaDataPointerOffset = input.ReadUInt32(); model.transform = modelHeader.worldTransform; model.name = modelHeader.modelName; string[] objectName = new string[modelHeader.objectsNodeNameEntries]; data.Seek(modelHeader.objectsNodeNameOffset, SeekOrigin.Begin); int rootReferenceBit = input.ReadInt32(); //Radix tree uint rootLeftNode = input.ReadUInt16(); uint rootRightNode = input.ReadUInt16(); uint rootNameOffset = input.ReadUInt32(); for (int i = 0; i < modelHeader.objectsNodeNameEntries; i++) { int referenceBit = input.ReadInt32(); ushort leftNode = input.ReadUInt16(); ushort rightNode = input.ReadUInt16(); objectName[i] = readString(input); } //Materials for (int index = 0; index < modelHeader.materialsTableEntries; index++) { //Nota: As versões mais antigas tinham o Coordinator já no header do material. //As versões mais recentes tem uma seção reservada só pra ele, por isso possuem tamanho do header menor. if (header.backwardCompatibility < 0x21) data.Seek(modelHeader.materialsTableOffset + (index * 0x58), SeekOrigin.Begin); else data.Seek(modelHeader.materialsTableOffset + (index * 0x2c), SeekOrigin.Begin); RenderBase.OMaterial material = new RenderBase.OMaterial(); uint materialParametersOffset = input.ReadUInt32(); input.ReadUInt32(); //TODO input.ReadUInt32(); input.ReadUInt32(); uint textureCommandsOffset = input.ReadUInt32(); uint textureCommandsWordCount = input.ReadUInt32(); uint materialMapperOffset = 0; if (header.backwardCompatibility < 0x21) { materialMapperOffset = (uint)data.Position; data.Seek(0x30, SeekOrigin.Current); } else materialMapperOffset = input.ReadUInt32(); material.name0 = readString(input); material.name1 = readString(input); material.name2 = readString(input); material.name = readString(input); //Parameters //Same pointer of Materials section. Why? if (materialParametersOffset != 0) { data.Seek(materialParametersOffset, SeekOrigin.Begin); uint hash = input.ReadUInt32(); ushort materialFlags = input.ReadUInt16(); material.isFragmentLightEnabled = (materialFlags & 1) > 0; material.isVertexLightEnabled = (materialFlags & 2) > 0; material.isHemiSphereLightEnabled = (materialFlags & 4) > 0; material.isHemiSphereOcclusionEnabled = (materialFlags & 8) > 0; material.isFogEnabled = (materialFlags & 0x10) > 0; material.rasterization.isPolygonOffsetEnabled = (materialFlags & 0x20) > 0; ushort fragmentFlags = input.ReadUInt16(); material.fragmentShader.bump.isBumpRenormalize = (fragmentFlags & 1) > 0; material.fragmentShader.lighting.isClampHighLight = (fragmentFlags & 2) > 0; material.fragmentShader.lighting.isDistribution0Enabled = (fragmentFlags & 4) > 0; material.fragmentShader.lighting.isDistribution1Enabled = (fragmentFlags & 8) > 0; material.fragmentShader.lighting.isGeometryFactor0Enabled = (fragmentFlags & 0x10) > 0; material.fragmentShader.lighting.isGeometryFactor1Enabled = (fragmentFlags & 0x20) > 0; material.fragmentShader.lighting.isReflectionEnabled = (fragmentFlags & 0x40) > 0; RenderBase.OBlendMode blendMode = (RenderBase.OBlendMode)((fragmentFlags >> 10) & 3); input.ReadUInt32(); for (int i = 0; i < 3; i++) { RenderBase.OTextureCoordinator coordinator; uint projectionAndCamera = input.ReadUInt32(); coordinator.projection = (RenderBase.OTextureProjection)((projectionAndCamera >> 16) & 0xff); coordinator.referenceCamera = projectionAndCamera >> 24; coordinator.scaleU = input.ReadSingle(); coordinator.scaleV = input.ReadSingle(); coordinator.rotate = input.ReadSingle(); coordinator.translateU = input.ReadSingle(); coordinator.translateV = input.ReadSingle(); material.textureCoordinator[i] = coordinator; } material.lightSetIndex = input.ReadUInt16(); material.fogIndex = input.ReadUInt16(); material.materialColor.emission = MeshUtils.getColor(input); material.materialColor.ambient = MeshUtils.getColor(input); material.materialColor.diffuse = MeshUtils.getColor(input); material.materialColor.specular0 = MeshUtils.getColor(input); material.materialColor.specular1 = MeshUtils.getColor(input); material.materialColor.constant0 = MeshUtils.getColor(input); material.materialColor.constant1 = MeshUtils.getColor(input); material.materialColor.constant2 = MeshUtils.getColor(input); material.materialColor.constant3 = MeshUtils.getColor(input); material.materialColor.constant4 = MeshUtils.getColor(input); material.materialColor.constant5 = MeshUtils.getColor(input); material.fragmentOperation.blend.blendColor = MeshUtils.getColor(input); material.materialColor.colorScale = input.ReadSingle(); input.ReadUInt32(); //TODO: Figure out input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); uint fragmentData = input.ReadUInt32(); material.fragmentShader.bump.texture = (RenderBase.OBumpTexture)(fragmentData >> 24); material.fragmentShader.bump.mode = (RenderBase.OBumpMode)((fragmentData >> 16) & 0xff); material.fragmentShader.lighting.fresnelConfig = (RenderBase.OFresnelConfig)((fragmentData >> 8) & 0xff); material.fragmentShader.layerConfig = fragmentData & 0xff; //Some Fragment Lighting related commands... This seems a bit out of place. long position = data.Position; PICACommandReader fshLightingCommands = new PICACommandReader(data, 6, true); PICACommand.fragmentSamplerAbsolute sAbs = fshLightingCommands.getReflectanceSamplerAbsolute(); material.fragmentShader.lighting.reflectanceRSampler.isAbsolute = sAbs.r; material.fragmentShader.lighting.reflectanceGSampler.isAbsolute = sAbs.g; material.fragmentShader.lighting.reflectanceBSampler.isAbsolute = sAbs.b; material.fragmentShader.lighting.distribution0Sampler.isAbsolute = sAbs.d0; material.fragmentShader.lighting.distribution1Sampler.isAbsolute = sAbs.d1; material.fragmentShader.lighting.fresnelSampler.isAbsolute = sAbs.fresnel; PICACommand.fragmentSamplerInput sInput = fshLightingCommands.getReflectanceSamplerInput(); material.fragmentShader.lighting.reflectanceRSampler.input = sInput.r; material.fragmentShader.lighting.reflectanceGSampler.input = sInput.g; material.fragmentShader.lighting.reflectanceBSampler.input = sInput.b; material.fragmentShader.lighting.distribution0Sampler.input = sInput.d0; material.fragmentShader.lighting.distribution1Sampler.input = sInput.d1; material.fragmentShader.lighting.fresnelSampler.input = sInput.fresnel; PICACommand.fragmentSamplerScale sScale = fshLightingCommands.getReflectanceSamplerScale(); material.fragmentShader.lighting.reflectanceRSampler.scale = sScale.r; material.fragmentShader.lighting.reflectanceGSampler.scale = sScale.g; material.fragmentShader.lighting.reflectanceBSampler.scale = sScale.b; material.fragmentShader.lighting.distribution0Sampler.scale = sScale.d0; material.fragmentShader.lighting.distribution1Sampler.scale = sScale.d1; material.fragmentShader.lighting.fresnelSampler.scale = sScale.fresnel; data.Seek(position + (4 * 6), SeekOrigin.Begin); //Just to be sure ;) RenderBase.OConstantColor[] constantList = new RenderBase.OConstantColor[6]; uint constantColor = input.ReadUInt32(); for (int i = 0; i < 6; i++) constantList[i] = (RenderBase.OConstantColor)((constantColor >> (i * 4)) & 0xf); material.rasterization.polygonOffsetUnit = input.ReadSingle(); uint fshCommandsOffset = input.ReadUInt32(); uint fshCommandsWordCount = input.ReadUInt32(); input.ReadUInt32(); material.fragmentShader.lighting.distribution0Sampler.tableName = readString(input); material.fragmentShader.lighting.distribution1Sampler.tableName = readString(input); material.fragmentShader.lighting.fresnelSampler.tableName = readString(input); material.fragmentShader.lighting.reflectanceRSampler.tableName = readString(input); material.fragmentShader.lighting.reflectanceGSampler.tableName = readString(input); material.fragmentShader.lighting.reflectanceBSampler.tableName = readString(input); material.fragmentShader.lighting.distribution0Sampler.samplerName = readString(input); material.fragmentShader.lighting.distribution1Sampler.samplerName = readString(input); material.fragmentShader.lighting.fresnelSampler.samplerName = readString(input); material.fragmentShader.lighting.reflectanceRSampler.samplerName = readString(input); material.fragmentShader.lighting.reflectanceGSampler.samplerName = readString(input); material.fragmentShader.lighting.reflectanceBSampler.samplerName = readString(input); material.shaderReference = new RenderBase.OReference(readString(input)); material.modelReference = new RenderBase.OReference(readString(input)); //User Data if (header.backwardCompatibility > 6) { uint metaDataPointerOffset = input.ReadUInt32(); if (metaDataPointerOffset != 0) { data.Seek(metaDataPointerOffset, SeekOrigin.Begin); material.userData = getMetaData(input); } } //Mapper data.Seek(materialMapperOffset, SeekOrigin.Begin); for (int i = 0; i < 3; i++) { RenderBase.OTextureMapper mapper; uint wrapAndMagFilter = input.ReadUInt32(); uint levelOfDetailAndMinFilter = input.ReadUInt32(); mapper.wrapU = (RenderBase.OTextureWrap)((wrapAndMagFilter >> 8) & 0xff); mapper.wrapV = (RenderBase.OTextureWrap)((wrapAndMagFilter >> 16) & 0xff); mapper.magFilter = (RenderBase.OTextureMagFilter)(wrapAndMagFilter >> 24); mapper.minFilter = (RenderBase.OTextureMinFilter)(levelOfDetailAndMinFilter & 0xff); mapper.minLOD = (levelOfDetailAndMinFilter >> 8) & 0xff; //max 232 mapper.LODBias = input.ReadSingle(); mapper.borderColor = MeshUtils.getColor(input); material.textureMapper[i] = mapper; } //Fragment Shader commands data.Seek(fshCommandsOffset, SeekOrigin.Begin); PICACommandReader fshCommands = new PICACommandReader(data, fshCommandsWordCount); for (byte stage = 0; stage < 6; stage++) material.fragmentShader.textureCombiner[stage] = fshCommands.getTevStage(stage); material.fragmentShader.bufferColor = fshCommands.getFragmentBufferColor(); material.fragmentOperation.blend = fshCommands.getBlendOperation(); material.fragmentOperation.blend.logicalOperation = fshCommands.getColorLogicOperation(); material.fragmentShader.alphaTest = fshCommands.getAlphaTest(); material.fragmentOperation.stencil = fshCommands.getStencilTest(); material.fragmentOperation.depth = fshCommands.getDepthTest(); material.rasterization.cullMode = fshCommands.getCullMode(); material.fragmentOperation.blend.mode = blendMode; } model.material.Add(material); } //Skeleton data.Seek(modelHeader.skeletonOffset, SeekOrigin.Begin); for (int index = 0; index < modelHeader.skeletonEntries; index++) { RenderBase.OBone bone = new RenderBase.OBone(); uint boneFlags = input.ReadUInt32(); bone.billboardMode = (RenderBase.OBillboardMode)((boneFlags >> 16) & 0xf); bone.isSegmentScaleCompensate = (boneFlags & 0x00400000) > 0; bone.parentId = input.ReadInt16(); ushort boneSpacer = input.ReadUInt16(); bone.scale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.rotation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.translation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.absoluteScale = new RenderBase.OVector3(bone.scale); RenderBase.OMatrix boneMatrix = new RenderBase.OMatrix(); boneMatrix.M11 = input.ReadSingle(); boneMatrix.M21 = input.ReadSingle(); boneMatrix.M31 = input.ReadSingle(); boneMatrix.M41 = input.ReadSingle(); boneMatrix.M12 = input.ReadSingle(); boneMatrix.M22 = input.ReadSingle(); boneMatrix.M32 = input.ReadSingle(); boneMatrix.M42 = input.ReadSingle(); boneMatrix.M13 = input.ReadSingle(); boneMatrix.M23 = input.ReadSingle(); boneMatrix.M33 = input.ReadSingle(); boneMatrix.M43 = input.ReadSingle(); bone.name = readString(input); uint metaDataPointerOffset = input.ReadUInt32(); if (metaDataPointerOffset != 0) { long position = data.Position; data.Seek(metaDataPointerOffset, SeekOrigin.Begin); bone.userData = getMetaData(input); data.Seek(position, SeekOrigin.Begin); } model.skeleton.Add(bone); } List<RenderBase.OMatrix> skeletonTransform = new List<RenderBase.OMatrix>(); for (int index = 0; index < modelHeader.skeletonEntries; index++) { RenderBase.OMatrix transform = new RenderBase.OMatrix(); transformSkeleton(model.skeleton, index, ref transform); skeletonTransform.Add(transform); } data.Seek(modelHeader.objectsNodeVisibilityOffset, SeekOrigin.Begin); uint nodeVisibility = input.ReadUInt32(); //Vertices header data.Seek(modelHeader.verticesTableOffset, SeekOrigin.Begin); List<bchObjectEntry> objects = new List<bchObjectEntry>(); for (int index = 0; index < modelHeader.verticesTableEntries; index++) { bchObjectEntry objectEntry = new bchObjectEntry(); objectEntry.materialId = input.ReadUInt16(); ushort flags = input.ReadUInt16(); if (header.backwardCompatibility != 8) objectEntry.isSilhouette = (flags & 1) > 0; objectEntry.nodeId = input.ReadUInt16(); objectEntry.renderPriority = input.ReadUInt16(); objectEntry.vshAttributesBufferCommandsOffset = input.ReadUInt32(); //Buffer 0 objectEntry.vshAttributesBufferCommandsWordCount = input.ReadUInt32(); objectEntry.facesHeaderOffset = input.ReadUInt32(); objectEntry.facesHeaderEntries = input.ReadUInt32(); objectEntry.vshExtraAttributesBufferCommandsOffset = input.ReadUInt32(); //Buffers 1-11 objectEntry.vshExtraAttributesBufferCommandsWordCount = input.ReadUInt32(); objectEntry.centerVector = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); objectEntry.flagsOffset = input.ReadUInt32(); input.ReadUInt32(); //ex: 0x0 fixo objectEntry.boundingBoxOffset = input.ReadUInt32(); objects.Add(objectEntry); } for (int objIndex = 0; objIndex < objects.Count; objIndex++) { if (objects[objIndex].isSilhouette) continue; //TODO: Figure out for what "Silhouette" is used. RenderBase.OMesh obj = new RenderBase.OMesh(); obj.materialId = objects[objIndex].materialId; obj.renderPriority = objects[objIndex].renderPriority; if (objects[objIndex].nodeId < objectName.Length) obj.name = objectName[objects[objIndex].nodeId]; else obj.name = "mesh" + objIndex.ToString(); obj.isVisible = (nodeVisibility & (1 << objects[objIndex].nodeId)) > 0; //Vertices data.Seek(objects[objIndex].vshAttributesBufferCommandsOffset, SeekOrigin.Begin); PICACommandReader vshCommands = new PICACommandReader(data, objects[objIndex].vshAttributesBufferCommandsWordCount); Stack<float> vshAttributesUniformReg6 = vshCommands.getVSHFloatUniformData(6); Stack<float> vshAttributesUniformReg7 = vshCommands.getVSHFloatUniformData(7); RenderBase.OVector4 positionOffset = new RenderBase.OVector4( vshAttributesUniformReg6.Pop(), vshAttributesUniformReg6.Pop(), vshAttributesUniformReg6.Pop(), vshAttributesUniformReg6.Pop()); float texture0Scale = vshAttributesUniformReg7.Pop(); float texture1Scale = vshAttributesUniformReg7.Pop(); float texture2Scale = vshAttributesUniformReg7.Pop(); float boneWeightScale = vshAttributesUniformReg7.Pop(); float positionScale = vshAttributesUniformReg7.Pop(); float normalScale = vshAttributesUniformReg7.Pop(); float tangentScale = vshAttributesUniformReg7.Pop(); float colorScale = vshAttributesUniformReg7.Pop(); //Faces uint facesCount = objects[objIndex].facesHeaderEntries; bool hasFaces = facesCount > 0; uint facesTableOffset = 0; if (!hasFaces) { data.Seek(modelHeader.verticesTableOffset + modelHeader.verticesTableEntries * 0x38, SeekOrigin.Begin); data.Seek(objIndex * 0x1c + 0x10, SeekOrigin.Current); facesTableOffset = input.ReadUInt32(); facesCount = input.ReadUInt32(); } for (uint f = 0; f < facesCount; f++) { RenderBase.OSkinningMode skinningMode = RenderBase.OSkinningMode.none; List<ushort> nodeList = new List<ushort>(); uint idxBufferOffset; PICACommand.indexBufferFormat idxBufferFormat; uint idxBufferTotalVertices; if (hasFaces) { uint baseOffset = objects[objIndex].facesHeaderOffset + f * 0x34; data.Seek(baseOffset, SeekOrigin.Begin); skinningMode = (RenderBase.OSkinningMode)input.ReadUInt16(); ushort nodeIdEntries = input.ReadUInt16(); for (int n = 0; n < nodeIdEntries; n++) nodeList.Add(input.ReadUInt16()); data.Seek(baseOffset + 0x2c, SeekOrigin.Begin); uint faceHeaderOffset = input.ReadUInt32(); uint faceHeaderWordCount = input.ReadUInt32(); data.Seek(faceHeaderOffset, SeekOrigin.Begin); PICACommandReader idxCommands = new PICACommandReader(data, faceHeaderWordCount); idxBufferOffset = idxCommands.getIndexBufferAddress(); idxBufferFormat = idxCommands.getIndexBufferFormat(); idxBufferTotalVertices = idxCommands.getIndexBufferTotalVertices(); } else { data.Seek(facesTableOffset + f * 8, SeekOrigin.Begin); idxBufferOffset = input.ReadUInt32(); idxBufferFormat = PICACommand.indexBufferFormat.unsignedShort; idxBufferTotalVertices = input.ReadUInt32(); } //Carregamento de dados relacionados ao Vertex Shader uint vshAttributesBufferOffset = vshCommands.getVSHAttributesBufferAddress(0); uint vshAttributesBufferStride = vshCommands.getVSHAttributesBufferStride(0); uint vshTotalAttributes = vshCommands.getVSHTotalAttributes(0); PICACommand.vshAttribute[] vshMainAttributesBufferPermutation = vshCommands.getVSHAttributesBufferPermutation(); uint[] vshAttributesBufferPermutation = vshCommands.getVSHAttributesBufferPermutation(0); PICACommand.attributeFormat[] vshAttributesBufferFormat = vshCommands.getVSHAttributesBufferFormat(); for (int attribute = 0; attribute < vshTotalAttributes; attribute++) { switch (vshMainAttributesBufferPermutation[vshAttributesBufferPermutation[attribute]]) { case PICACommand.vshAttribute.normal: obj.hasNormal = true; break; case PICACommand.vshAttribute.tangent: obj.hasTangent = true; break; case PICACommand.vshAttribute.color: obj.hasColor = true; break; case PICACommand.vshAttribute.textureCoordinate0: obj.texUVCount = Math.Max(obj.texUVCount, 1); break; case PICACommand.vshAttribute.textureCoordinate1: obj.texUVCount = Math.Max(obj.texUVCount, 2); break; case PICACommand.vshAttribute.textureCoordinate2: obj.texUVCount = Math.Max(obj.texUVCount, 3); break; } } if (nodeList.Count > 0) { obj.hasNode = true; obj.hasWeight = true; } data.Seek(idxBufferOffset, SeekOrigin.Begin); for (int faceIndex = 0; faceIndex < idxBufferTotalVertices; faceIndex++) { ushort index = 0; switch (idxBufferFormat) { case PICACommand.indexBufferFormat.unsignedShort: index = input.ReadUInt16(); break; case PICACommand.indexBufferFormat.unsignedByte: index = input.ReadByte(); break; } long dataPosition = data.Position; long vertexOffset = vshAttributesBufferOffset + (index * vshAttributesBufferStride); data.Seek(vertexOffset, SeekOrigin.Begin); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; for (int attribute = 0; attribute < vshTotalAttributes; attribute++) { //gdkchan self note: The Attribute type flags are used for something else on Bone Weight (and bone index?) PICACommand.vshAttribute att = vshMainAttributesBufferPermutation[vshAttributesBufferPermutation[attribute]]; PICACommand.attributeFormat format = vshAttributesBufferFormat[vshAttributesBufferPermutation[attribute]]; if (att == PICACommand.vshAttribute.boneWeight) format.type = PICACommand.attributeFormatType.unsignedByte; RenderBase.OVector4 vector = getVector(input, format); switch (att) { case PICACommand.vshAttribute.position: float x = (vector.x * positionScale) + positionOffset.x; float y = (vector.y * positionScale) + positionOffset.y; float z = (vector.z * positionScale) + positionOffset.z; vertex.position = new RenderBase.OVector3(x, y, z); break; case PICACommand.vshAttribute.normal: vertex.normal = new RenderBase.OVector3(vector.x * normalScale, vector.y * normalScale, vector.z * normalScale); break; case PICACommand.vshAttribute.tangent: vertex.tangent = new RenderBase.OVector3(vector.x * tangentScale, vector.y * tangentScale, vector.z * tangentScale); break; case PICACommand.vshAttribute.color: uint r = MeshUtils.saturate((vector.x * colorScale) * 0xff); uint g = MeshUtils.saturate((vector.y * colorScale) * 0xff); uint b = MeshUtils.saturate((vector.z * colorScale) * 0xff); uint a = MeshUtils.saturate((vector.w * colorScale) * 0xff); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); break; case PICACommand.vshAttribute.textureCoordinate0: vertex.texture0 = new RenderBase.OVector2(vector.x * texture0Scale, vector.y * texture0Scale); break; case PICACommand.vshAttribute.textureCoordinate1: vertex.texture1 = new RenderBase.OVector2(vector.x * texture1Scale, vector.y * texture1Scale); break; case PICACommand.vshAttribute.textureCoordinate2: vertex.texture2 = new RenderBase.OVector2(vector.x * texture2Scale, vector.y * texture2Scale); break; case PICACommand.vshAttribute.boneIndex: vertex.node.Add(nodeList[(int)vector.x]); if (skinningMode == RenderBase.OSkinningMode.smoothSkinning) { if (format.attributeLength > 0) vertex.node.Add(nodeList[(int)vector.y]); if (format.attributeLength > 1) vertex.node.Add(nodeList[(int)vector.z]); if (format.attributeLength > 2) vertex.node.Add(nodeList[(int)vector.w]); } break; case PICACommand.vshAttribute.boneWeight: vertex.weight.Add(vector.x * boneWeightScale); if (skinningMode == RenderBase.OSkinningMode.smoothSkinning) { if (format.attributeLength > 0) vertex.weight.Add(vector.y * boneWeightScale); if (format.attributeLength > 1) vertex.weight.Add(vector.z * boneWeightScale); if (format.attributeLength > 2) vertex.weight.Add(vector.w * boneWeightScale); } break; } } //If the node list have 4 or less bones, then there is no need to store the indices per vertex //Instead, the entire list is used, since it supports up to 4 bones. if (vertex.node.Count == 0 && nodeList.Count <= 4) { for (int n = 0; n < nodeList.Count; n++) vertex.node.Add(nodeList[n]); if (vertex.weight.Count == 0) vertex.weight.Add(1); } if (skinningMode != RenderBase.OSkinningMode.smoothSkinning && vertex.node.Count > 0) { //Note: Rigid skinning can have only one bone per vertex //Note2: Vertex with Rigid skinning seems to be always have meshes centered, so is necessary to make them follow the skeleton if (vertex.weight.Count == 0) vertex.weight.Add(1); vertex.position = RenderBase.OVector3.transform(vertex.position, skeletonTransform[vertex.node[0]]); } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); data.Seek(dataPosition, SeekOrigin.Begin); } } //Bounding box if (objects[objIndex].boundingBoxOffset != 0) { data.Seek(objects[objIndex].boundingBoxOffset, SeekOrigin.Begin); uint bBoxDataOffset = input.ReadUInt32(); uint bBoxEntries = input.ReadUInt32(); uint bBoxNameOffset = input.ReadUInt32(); for (int index = 0; index < bBoxEntries; index++) { data.Seek(bBoxDataOffset + (index * 0xc), SeekOrigin.Begin); RenderBase.OOrientedBoundingBox bBox = new RenderBase.OOrientedBoundingBox(); bBox.name = readString(input); uint flags = input.ReadUInt32(); uint dataOffset = input.ReadUInt32(); data.Seek(dataOffset, SeekOrigin.Begin); bBox.centerPosition = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bBox.orientationMatrix.M11 = input.ReadSingle(); bBox.orientationMatrix.M21 = input.ReadSingle(); bBox.orientationMatrix.M31 = input.ReadSingle(); bBox.orientationMatrix.M12 = input.ReadSingle(); bBox.orientationMatrix.M22 = input.ReadSingle(); bBox.orientationMatrix.M32 = input.ReadSingle(); bBox.orientationMatrix.M13 = input.ReadSingle(); bBox.orientationMatrix.M23 = input.ReadSingle(); bBox.orientationMatrix.M33 = input.ReadSingle(); bBox.size = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); obj.boundingBox.Add(bBox); } } model.mesh.Add(obj); } if (modelHeader.metaDataPointerOffset != 0) { data.Seek(modelHeader.metaDataPointerOffset, SeekOrigin.Begin); model.userData = getMetaData(input); } for (int index = 0; index < modelHeader.skeletonEntries; index++) { scaleSkeleton(model.skeleton, index, index); } models.model.Add(model); } data.Close(); return models; }
/// <summary> /// Gets a Vector4 from Data. /// Number of used elements of the Vector4 will depend on the vector type. /// </summary> /// <param name="input">CGFX reader</param> /// <param name="format">Format of the buffer data</param> /// <returns></returns> private static RenderBase.OVector4 getVector(BinaryReader input, attributeFormat format) { RenderBase.OVector4 output = new RenderBase.OVector4(); switch (format.type) { case attributeFormatType.signedByte: if (format.attributeLength > 0) output.x = (sbyte)input.ReadByte(); if (format.attributeLength > 1) output.y = (sbyte)input.ReadByte(); if (format.attributeLength > 2) output.z = (sbyte)input.ReadByte(); if (format.attributeLength > 3) output.w = (sbyte)input.ReadByte(); break; case attributeFormatType.unsignedByte: if (format.attributeLength > 0) output.x = input.ReadByte(); if (format.attributeLength > 1) output.y = input.ReadByte(); if (format.attributeLength > 2) output.z = input.ReadByte(); if (format.attributeLength > 3) output.w = input.ReadByte(); break; case attributeFormatType.signedShort: if (format.attributeLength > 0) output.x = input.ReadInt16(); if (format.attributeLength > 1) output.y = input.ReadInt16(); if (format.attributeLength > 2) output.z = input.ReadInt16(); if (format.attributeLength > 3) output.w = input.ReadInt16(); break; case attributeFormatType.single: if (format.attributeLength > 0) output.x = input.ReadSingle(); if (format.attributeLength > 1) output.y = input.ReadSingle(); if (format.attributeLength > 2) output.z = input.ReadSingle(); if (format.attributeLength > 3) output.w = input.ReadSingle(); break; } return output; }
public static RenderBase.OModel loadModel(Stream data, bool keepOpen = false) { RenderBase.OModel mdl = new RenderBase.OModel(); BinaryReader input = new BinaryReader(data); mdl.name = "model"; long mdlStart = data.Position; data.Seek(0x10, SeekOrigin.Current); ulong mdlMagic = input.ReadUInt64(); //gfmodel string uint mdlLength = input.ReadUInt32(); input.ReadUInt32(); //-1 string[] effectNames = getStrTable(input); string[] textureNames = getStrTable(input); string[] materialNames = getStrTable(input); string[] meshNames = getStrTable(input); input.BaseStream.Seek(0x20, SeekOrigin.Current); //2 float4 (Maybe 2 Quaternions?) mdl.transform = new RenderBase.OMatrix(); mdl.transform.M11 = input.ReadSingle(); mdl.transform.M12 = input.ReadSingle(); mdl.transform.M13 = input.ReadSingle(); mdl.transform.M14 = input.ReadSingle(); mdl.transform.M21 = input.ReadSingle(); mdl.transform.M22 = input.ReadSingle(); mdl.transform.M23 = input.ReadSingle(); mdl.transform.M24 = input.ReadSingle(); mdl.transform.M31 = input.ReadSingle(); mdl.transform.M32 = input.ReadSingle(); mdl.transform.M33 = input.ReadSingle(); mdl.transform.M34 = input.ReadSingle(); mdl.transform.M41 = input.ReadSingle(); mdl.transform.M42 = input.ReadSingle(); mdl.transform.M43 = input.ReadSingle(); mdl.transform.M44 = input.ReadSingle(); uint unkDataLen = input.ReadUInt32(); uint unkDataRelStart = input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); input.BaseStream.Seek(unkDataRelStart + unkDataLen, SeekOrigin.Current); //??? uint bonesCount = input.ReadUInt32(); input.BaseStream.Seek(0xc, SeekOrigin.Current); List <string> boneNames = new List <string>(); for (int b = 0; b < bonesCount; b++) { string boneName = IOUtils.readStringWithLength(input, input.ReadByte()); string parentName = IOUtils.readStringWithLength(input, input.ReadByte()); byte flags = input.ReadByte(); RenderBase.OBone bone = new RenderBase.OBone(); bone.name = boneName; bone.parentId = (short)boneNames.IndexOf(parentName); bone.scale = new RenderBase.OVector3( input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.rotation = new RenderBase.OVector3( input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.translation = new RenderBase.OVector3( input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.absoluteScale = new RenderBase.OVector3(bone.scale); mdl.skeleton.Add(bone); boneNames.Add(boneName); } //Materials List <string> matMeshBinding = new List <string>(); input.BaseStream.Seek(mdlStart + mdlLength + 0x20, SeekOrigin.Begin); for (int m = 0; m < materialNames.Length; m++) { RenderBase.OMaterial mat = new RenderBase.OMaterial(); mat.name = materialNames[m]; ulong matMagic = input.ReadUInt64(); //material string uint matLength = input.ReadUInt32(); input.ReadUInt32(); //-1 long matStart = data.Position; string[] unkNames = new string[4]; for (int n = 0; n < 4; n++) { uint maybeHash = input.ReadUInt32(); byte nameLen = input.ReadByte(); unkNames[n] = IOUtils.readStringWithLength(input, nameLen); } matMeshBinding.Add(unkNames[0]); data.Seek(0xac, SeekOrigin.Current); long textureCoordsStart = data.Position; for (int unit = 0; unit < 3; unit++) { data.Seek(textureCoordsStart + unit * 0x42, SeekOrigin.Begin); uint maybeHash = input.ReadUInt32(); string texName = IOUtils.readStringWithLength(input, input.ReadByte()); if (texName == string.Empty) { break; } switch (unit) { case 0: mat.name0 = texName; break; case 1: mat.name1 = texName; break; case 2: mat.name2 = texName; break; } ushort unitIdx = input.ReadUInt16(); mat.textureCoordinator[unit].scaleU = input.ReadSingle(); mat.textureCoordinator[unit].scaleV = input.ReadSingle(); mat.textureCoordinator[unit].rotate = input.ReadSingle(); mat.textureCoordinator[unit].translateU = input.ReadSingle(); mat.textureCoordinator[unit].translateV = input.ReadSingle(); uint texMapperU = input.ReadUInt32(); uint texMapperV = input.ReadUInt32(); mat.textureMapper[unit].wrapU = (RenderBase.OTextureWrap)(texMapperU & 7); mat.textureMapper[unit].wrapV = (RenderBase.OTextureWrap)(texMapperV & 7); } mdl.material.Add(mat); input.BaseStream.Seek(matStart + matLength, SeekOrigin.Begin); } //Meshes for (int m = 0; m < meshNames.Length; m++) { ulong meshMagic = input.ReadUInt64(); //mesh string uint meshLength = input.ReadUInt32(); input.ReadUInt32(); //-1 long meshStart = data.Position; //Mesh name and other stuff goes here input.BaseStream.Seek(0x80, SeekOrigin.Current); subMeshInfo info = getSubMeshInfo(input); for (int sm = 0; sm < info.count; sm++) { RenderBase.OMesh obj = new RenderBase.OMesh(); obj.isVisible = true; obj.name = info.names[sm]; obj.materialId = (ushort)matMeshBinding.IndexOf(obj.name); ushort[] nodeList = info.nodeLists[sm]; //NOTE: All Addresses on commands are set to 0x99999999 and are probably relocated by game engine PICACommandReader vtxCmdReader = info.cmdBuffers[sm * 3 + 0]; PICACommandReader idxCmdReader = info.cmdBuffers[sm * 3 + 2]; uint vshAttributesBufferStride = vtxCmdReader.getVSHAttributesBufferStride(0); uint vshTotalAttributes = vtxCmdReader.getVSHTotalAttributes(0); PICACommand.vshAttribute[] vshMainAttributesBufferPermutation = vtxCmdReader.getVSHAttributesBufferPermutation(); uint[] vshAttributesBufferPermutation = vtxCmdReader.getVSHAttributesBufferPermutation(0); PICACommand.attributeFormat[] vshAttributesBufferFormat = vtxCmdReader.getVSHAttributesBufferFormat(); for (int attribute = 0; attribute < vshTotalAttributes; attribute++) { switch (vshMainAttributesBufferPermutation[vshAttributesBufferPermutation[attribute]]) { case PICACommand.vshAttribute.normal: obj.hasNormal = true; break; case PICACommand.vshAttribute.tangent: obj.hasTangent = true; break; case PICACommand.vshAttribute.color: obj.hasColor = true; break; case PICACommand.vshAttribute.textureCoordinate0: obj.texUVCount = Math.Max(obj.texUVCount, 1); break; case PICACommand.vshAttribute.textureCoordinate1: obj.texUVCount = Math.Max(obj.texUVCount, 2); break; case PICACommand.vshAttribute.textureCoordinate2: obj.texUVCount = Math.Max(obj.texUVCount, 3); break; } } PICACommand.indexBufferFormat idxBufferFormat = idxCmdReader.getIndexBufferFormat(); uint idxBufferTotalVertices = idxCmdReader.getIndexBufferTotalVertices(); obj.hasNode = true; obj.hasWeight = true; long vtxBufferStart = data.Position; input.BaseStream.Seek(info.vtxLengths[sm], SeekOrigin.Current); long idxBufferStart = data.Position; for (int faceIndex = 0; faceIndex < idxBufferTotalVertices; faceIndex++) { ushort index = 0; switch (idxBufferFormat) { case PICACommand.indexBufferFormat.unsignedShort: index = input.ReadUInt16(); break; case PICACommand.indexBufferFormat.unsignedByte: index = input.ReadByte(); break; } long dataPosition = data.Position; long vertexOffset = vtxBufferStart + (index * vshAttributesBufferStride); data.Seek(vertexOffset, SeekOrigin.Begin); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; // Fix weight problems vertex.weight.Add(1); vertex.weight.Add(0); vertex.weight.Add(0); vertex.weight.Add(0); for (int attribute = 0; attribute < vshTotalAttributes; attribute++) { //gdkchan self note: The Attribute type flags are used for something else on Bone Weight (and bone index?) PICACommand.vshAttribute att = vshMainAttributesBufferPermutation[vshAttributesBufferPermutation[attribute]]; PICACommand.attributeFormat format = vshAttributesBufferFormat[vshAttributesBufferPermutation[attribute]]; if (att == PICACommand.vshAttribute.boneWeight) { format.type = PICACommand.attributeFormatType.unsignedByte; } RenderBase.OVector4 vector = getVector(input, format); switch (att) { case PICACommand.vshAttribute.position: vertex.position = new RenderBase.OVector3(vector.x, vector.y, vector.z); break; case PICACommand.vshAttribute.normal: vertex.normal = new RenderBase.OVector3(vector.x, vector.y, vector.z); break; case PICACommand.vshAttribute.tangent: vertex.tangent = new RenderBase.OVector3(vector.x, vector.y, vector.z); break; case PICACommand.vshAttribute.color: uint r = MeshUtils.saturate(vector.x); uint g = MeshUtils.saturate(vector.y); uint b = MeshUtils.saturate(vector.z); uint a = MeshUtils.saturate(vector.w); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); break; case PICACommand.vshAttribute.textureCoordinate0: vertex.texture0 = new RenderBase.OVector2(vector.x, vector.y); break; case PICACommand.vshAttribute.textureCoordinate1: vertex.texture1 = new RenderBase.OVector2(vector.x, vector.y); break; case PICACommand.vshAttribute.textureCoordinate2: vertex.texture2 = new RenderBase.OVector2(vector.x, vector.y); break; case PICACommand.vshAttribute.boneIndex: addNode(vertex.node, nodeList, (int)vector.x); if (format.attributeLength > 0) { addNode(vertex.node, nodeList, (int)vector.y); } if (format.attributeLength > 1) { addNode(vertex.node, nodeList, (int)vector.z); } if (format.attributeLength > 2) { addNode(vertex.node, nodeList, (int)vector.w); } break; case PICACommand.vshAttribute.boneWeight: vertex.weight[0] = (vector.x / 255f); if (format.attributeLength > 0) { vertex.weight[1] = (vector.y / 255f); } if (format.attributeLength > 1) { vertex.weight[2] = (vector.z / 255f); } if (format.attributeLength > 2) { vertex.weight[3] = (vector.w / 255f); } break; } } //If the node list have 4 or less bones, then there is no need to store the indices per vertex //Instead, the entire list is used, since it supports up to 4 bones. if (vertex.node.Count == 0 && nodeList.Length <= 4) { for (int n = 0; n < nodeList.Length; n++) { vertex.node.Add(nodeList[n]); } if (vertex.weight.Count == 0) { vertex.weight.Add(1); } } MeshUtils.calculateBounds(mdl, vertex); obj.vertices.Add(vertex); data.Seek(dataPosition, SeekOrigin.Begin); } input.BaseStream.Seek(idxBufferStart + info.idxLengths[sm], SeekOrigin.Begin); mdl.mesh.Add(obj); } input.BaseStream.Seek(meshStart + meshLength, SeekOrigin.Begin); } if (!keepOpen) { data.Close(); } return(mdl); }
private static RenderBase.OVector4 getVector(BinaryReader input, PICACommand.attributeFormat format) { RenderBase.OVector4 output = new RenderBase.OVector4(); switch (format.type) { case PICACommand.attributeFormatType.signedByte: output.x = (sbyte)input.ReadByte(); if (format.attributeLength > 0) { output.y = (sbyte)input.ReadByte(); } if (format.attributeLength > 1) { output.z = (sbyte)input.ReadByte(); } if (format.attributeLength > 2) { output.w = (sbyte)input.ReadByte(); } break; case PICACommand.attributeFormatType.unsignedByte: output.x = input.ReadByte(); if (format.attributeLength > 0) { output.y = input.ReadByte(); } if (format.attributeLength > 1) { output.z = input.ReadByte(); } if (format.attributeLength > 2) { output.w = input.ReadByte(); } break; case PICACommand.attributeFormatType.signedShort: output.x = input.ReadInt16(); if (format.attributeLength > 0) { output.y = input.ReadInt16(); } if (format.attributeLength > 1) { output.z = input.ReadInt16(); } if (format.attributeLength > 2) { output.w = input.ReadInt16(); } break; case PICACommand.attributeFormatType.single: output.x = input.ReadSingle(); if (format.attributeLength > 0) { output.y = input.ReadSingle(); } if (format.attributeLength > 1) { output.z = input.ReadSingle(); } if (format.attributeLength > 2) { output.w = input.ReadSingle(); } break; } return(output); }
/// <summary> /// Reads the Model PACKage from Dragon Quest VII. /// </summary> /// <param name="data">Stream of the data</param> /// <returns></returns> public static OContainer load(Stream data) { BinaryReader input = new BinaryReader(data); OContainer output = new OContainer(); List<sectionEntry> mainSection = getSection(input); //World nodes section data.Seek(mainSection[0].offset, SeekOrigin.Begin); List<node> nodes = new List<node>(); List<sectionEntry> worldNodesSection = getSection(input); foreach (sectionEntry entry in worldNodesSection) { data.Seek(entry.offset, SeekOrigin.Begin); node n = new node(); //Geometry node input.ReadUInt32(); //GNOD magic number input.ReadUInt32(); input.ReadUInt32(); n.parentId = input.ReadInt32(); n.name = IOUtils.readString(input, (uint)data.Position); data.Seek(entry.offset + 0x20, SeekOrigin.Begin); n.transform = new RenderBase.OMatrix(); RenderBase.OVector4 t = new RenderBase.OVector4(input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); RenderBase.OVector4 r = new RenderBase.OVector4(input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); RenderBase.OVector4 s = new RenderBase.OVector4(input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); n.transform *= RenderBase.OMatrix.scale(new RenderBase.OVector3(s.x, s.y, s.z)); n.transform *= RenderBase.OMatrix.rotateX(r.x); n.transform *= RenderBase.OMatrix.rotateY(r.y); n.transform *= RenderBase.OMatrix.rotateZ(r.z); n.transform *= RenderBase.OMatrix.translate(new RenderBase.OVector3(t.x, t.y, t.z)); nodes.Add(n); } RenderBase.OMatrix[] nodesTransform = new RenderBase.OMatrix[nodes.Count]; for (int i = 0; i < nodes.Count; i++) { RenderBase.OMatrix transform = new RenderBase.OMatrix(); transformNode(nodes, i, ref transform); nodesTransform[i] = transform; } //Models section data.Seek(mainSection[1].offset, SeekOrigin.Begin); List<sectionEntry> modelsSection = getSection(input); foreach (sectionEntry entry in modelsSection) { data.Seek(entry.offset, SeekOrigin.Begin); //Field Data section /* * Usually have 3 entries. * 1st entry: Model CGFX * 2nd entry: Unknow CGFX, possibly animations * 3rd entry: Another FieldData section, possibly child object */ List<sectionEntry> fieldDataSection = getSection(input); data.Seek(fieldDataSection[0].offset, SeekOrigin.Begin); uint length = fieldDataSection[0].length; while ((length & 0x7f) != 0) length++; //Align byte[] buffer = new byte[length]; input.Read(buffer, 0, buffer.Length); OContainer.fileEntry file = new OContainer.fileEntry(); file.name = CGFX.getName(new MemoryStream(buffer)) + ".bcmdl"; file.data = buffer; output.content.Add(file); } //FILE section data.Seek(mainSection[2].offset, SeekOrigin.Begin); //TODO //Collision section data.Seek(mainSection[3].offset, SeekOrigin.Begin); //TODO //PARM(???) section data.Seek(mainSection[4].offset, SeekOrigin.Begin); //TODO //Textures CGFX data.Seek(mainSection[5].offset, SeekOrigin.Begin); byte[] texBuffer = new byte[mainSection[5].length]; input.Read(texBuffer, 0, texBuffer.Length); OContainer.fileEntry texFile = new OContainer.fileEntry(); texFile.name = "textures.bctex"; texFile.data = texBuffer; output.content.Add(texFile); data.Close(); return output; }
/// <summary> /// Reads the Model PACKage from Dragon Quest VII. /// </summary> /// <param name="data">Stream of the data</param> /// <returns></returns> public static OContainer load(Stream data) { BinaryReader input = new BinaryReader(data); OContainer output = new OContainer(); List <sectionEntry> mainSection = getSection(input); //World nodes section data.Seek(mainSection[0].offset, SeekOrigin.Begin); List <node> nodes = new List <node>(); List <sectionEntry> worldNodesSection = getSection(input); foreach (sectionEntry entry in worldNodesSection) { data.Seek(entry.offset, SeekOrigin.Begin); node n = new node(); //Geometry node input.ReadUInt32(); //GNOD magic number input.ReadUInt32(); input.ReadUInt32(); n.parentId = input.ReadInt32(); n.name = IOUtils.readString(input, (uint)data.Position); data.Seek(entry.offset + 0x20, SeekOrigin.Begin); n.transform = new RenderBase.OMatrix(); RenderBase.OVector4 t = new RenderBase.OVector4(input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); RenderBase.OVector4 r = new RenderBase.OVector4(input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); RenderBase.OVector4 s = new RenderBase.OVector4(input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); n.transform *= RenderBase.OMatrix.scale(new RenderBase.OVector3(s.x, s.y, s.z)); n.transform *= RenderBase.OMatrix.rotateX(r.x); n.transform *= RenderBase.OMatrix.rotateY(r.y); n.transform *= RenderBase.OMatrix.rotateZ(r.z); n.transform *= RenderBase.OMatrix.translate(new RenderBase.OVector3(t.x, t.y, t.z)); nodes.Add(n); } RenderBase.OMatrix[] nodesTransform = new RenderBase.OMatrix[nodes.Count]; for (int i = 0; i < nodes.Count; i++) { RenderBase.OMatrix transform = new RenderBase.OMatrix(); transformNode(nodes, i, ref transform); nodesTransform[i] = transform; } //Models section data.Seek(mainSection[1].offset, SeekOrigin.Begin); List <sectionEntry> modelsSection = getSection(input); foreach (sectionEntry entry in modelsSection) { data.Seek(entry.offset, SeekOrigin.Begin); //Field Data section /* * Usually have 3 entries. * 1st entry: Model CGFX * 2nd entry: Unknow CGFX, possibly animations * 3rd entry: Another FieldData section, possibly child object */ List <sectionEntry> fieldDataSection = getSection(input); data.Seek(fieldDataSection[0].offset, SeekOrigin.Begin); uint length = fieldDataSection[0].length; while ((length & 0x7f) != 0) { length++; //Align } byte[] buffer = new byte[length]; input.Read(buffer, 0, buffer.Length); OContainer.fileEntry file = new OContainer.fileEntry(); file.name = CGFX.getName(new MemoryStream(buffer)) + ".bcmdl"; file.data = buffer; output.content.Add(file); } //FILE section data.Seek(mainSection[2].offset, SeekOrigin.Begin); //TODO //Collision section data.Seek(mainSection[3].offset, SeekOrigin.Begin); //TODO //PARM(???) section data.Seek(mainSection[4].offset, SeekOrigin.Begin); //TODO //Textures CGFX data.Seek(mainSection[5].offset, SeekOrigin.Begin); byte[] texBuffer = new byte[mainSection[5].length]; input.Read(texBuffer, 0, texBuffer.Length); OContainer.fileEntry texFile = new OContainer.fileEntry(); texFile.name = "textures.bctex"; texFile.data = texBuffer; output.content.Add(texFile); data.Close(); return(output); }
public static RenderBase.OModelGroup load(string fileName) { FileStream data = new FileStream(fileName, FileMode.Open); BinaryReader input = new BinaryReader(data); RenderBase.OModelGroup models; RenderBase.OModel model; string extension = Path.GetExtension(fileName).ToLower(); string bchFile = fileName.Replace(extension, ".bch"); bool isBCHLoaded = false; if (File.Exists(bchFile)) { models = BCH.load(bchFile); model = models.model[0]; models.model.Clear(); isBCHLoaded = true; } else { models = new RenderBase.OModelGroup(); model = new RenderBase.OModel(); model.name = "model"; model.material.Add(new RenderBase.OMaterial()); } ushort format = input.ReadUInt16(); bool isDataWithinHeader = format == 4; input.ReadUInt16(); //-1? uint contentFlags = input.ReadUInt32(); bool hasNameTable = (contentFlags & 2) > 0; uint mode = input.ReadUInt32(); uint meshCount = input.ReadUInt32(); List <vtxEntry> vtxDescriptors = new List <vtxEntry>(); List <idxEntry> idxDescriptors = new List <idxEntry>(); for (int i = 0; i < meshCount; i++) { if (mode == 1 && i == 0) { vtxDescriptors.Add(getVtxDescriptor(input)); } uint facesCount = input.ReadUInt32(); for (int j = 0; j < facesCount; j++) { idxEntry face = new idxEntry(); face.meshIndex = i; uint nodesCount = input.ReadUInt32(); for (int k = 0; k < nodesCount; k++) { face.nodeList.Add(input.ReadUInt32()); } face.primitiveCount = input.ReadUInt32(); if (hasNameTable) { face.nameId = input.ReadUInt32(); } if (isDataWithinHeader) { face.buffer = new ushort[face.primitiveCount]; for (int k = 0; k < face.primitiveCount; k++) { face.buffer[k] = input.ReadUInt16(); } alignWord(input); } idxDescriptors.Add(face); } if (mode == 0) { if (isDataWithinHeader) { vtxEntry desc = getVtxDescriptor(input); desc.buffer = new byte[desc.length]; input.Read(desc.buffer, 0, desc.buffer.Length); vtxDescriptors.Add(desc); alignWord(input); } else { vtxDescriptors.Add(getVtxDescriptor(input)); } } } List <string> objNameTable = new List <string>(); if (hasNameTable) { for (int i = 0; i < meshCount; i++) { byte index = input.ReadByte(); objNameTable.Add(IOUtils.readString(input, (uint)data.Position, true)); } } if (!isDataWithinHeader) { align(input); } byte[] vtxBuffer = null; vtxEntry currVertex = null; int faceIndex = 0; for (int i = 0; i < meshCount; i++) { if (mode == 0 || i == 0) { currVertex = vtxDescriptors[i]; if (!isDataWithinHeader) { vtxBuffer = new byte[vtxDescriptors[i].length]; input.Read(vtxBuffer, 0, vtxBuffer.Length); align(input); } else { vtxBuffer = currVertex.buffer; } } RenderBase.OMesh obj; if (isBCHLoaded) { obj = model.mesh[0]; model.mesh.RemoveAt(0); } else { obj = new RenderBase.OMesh(); obj.name = "mesh_" + i.ToString(); } for (int j = 0; j < currVertex.attributes.Count; j++) { switch (currVertex.attributes[j].type) { case vtxAttributeType.normal: obj.hasNormal = true; break; case vtxAttributeType.color: obj.hasColor = true; break; case vtxAttributeType.textureCoordinate0: obj.texUVCount = 1; break; case vtxAttributeType.textureCoordinate1: obj.texUVCount = 2; break; case vtxAttributeType.boneIndex: obj.hasNode = true; break; case vtxAttributeType.boneWeight: obj.hasWeight = true; break; } } for (;;) { int indexBufferPos = 0; for (int j = 0; j < idxDescriptors[faceIndex].primitiveCount; j++) { ushort index; if (isDataWithinHeader) { index = idxDescriptors[faceIndex].buffer[indexBufferPos++]; } else { index = input.ReadUInt16(); } RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; for (int k = 0; k < currVertex.attributes.Count; k++) { vtxAttribute att = currVertex.attributes[k]; int pos = (int)(index * currVertex.stride + att.offset); float scale = att.scale; switch (currVertex.attributes[k].type) { case vtxAttributeType.position: vertex.position = getVector3(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.normal: vertex.normal = getVector3(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.color: RenderBase.OVector4 c = getVector4(vtxBuffer, pos, att.format, scale); uint r = MeshUtils.saturate(c.x * 0xff); uint g = MeshUtils.saturate(c.y * 0xff); uint b = MeshUtils.saturate(c.z * 0xff); uint a = MeshUtils.saturate(c.w * 0xff); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); break; case vtxAttributeType.textureCoordinate0: vertex.texture0 = getVector2(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.textureCoordinate1: vertex.texture1 = getVector2(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.boneIndex: byte n0 = vtxBuffer[pos]; byte n1 = vtxBuffer[pos + 1]; vertex.node.Add((int)idxDescriptors[faceIndex].nodeList[n0]); vertex.node.Add((int)idxDescriptors[faceIndex].nodeList[n1]); break; case vtxAttributeType.boneWeight: RenderBase.OVector2 w = getVector2(vtxBuffer, pos, att.format, scale); vertex.weight.Add(w.x); vertex.weight.Add(w.y); break; } } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); } faceIndex++; if (!isDataWithinHeader) { align(input); } if (faceIndex >= idxDescriptors.Count) { break; } if (idxDescriptors[faceIndex].meshIndex == i) { continue; } break; } model.mesh.Add(obj); } models.model.Add(model); data.Close(); return(models); }