/// <summary> /// Creates a Index Buffer for a Mesh, trying to repeat as less Vertices as possible. /// </summary> /// <param name="mesh">The Mesh that should be optimized</param> /// <returns></returns> public static optimizedMesh optimizeMesh(RenderBase.OMesh mesh) { optimizedMesh output = new optimizedMesh(); output.hasNormal = mesh.hasNormal; output.hasTangent = mesh.hasTangent; output.hasColor = mesh.hasColor; output.hasNode = mesh.hasNode; output.hasWeight = mesh.hasWeight; output.texUVCount = mesh.texUVCount; for (int i = 0; i < mesh.vertices.Count; i++) { bool found = false; for (int j = 1; j <= optimizerLookBack; j++) { int p = output.vertices.Count - j; if (p < 0 || p >= output.vertices.Count) { break; } if (output.vertices[p].Equals(mesh.vertices[i])) { output.indices.Add((uint)p); found = true; break; } } if (!found) { output.vertices.Add(mesh.vertices[i]); output.indices.Add((uint)(output.vertices.Count - 1)); } } return(output); }
/// <summary> /// Imports a Source Model from file. /// </summary> /// <param name="fileName">The complete file name</param> /// <returns></returns> public static RenderBase.OModelGroup import(string fileName) { StreamReader reader = File.OpenText(fileName); RenderBase.OModelGroup model = new RenderBase.OModelGroup(); RenderBase.OModel mdl = new RenderBase.OModel(); mdl.name = Path.GetFileNameWithoutExtension(fileName); mdl.transform = new RenderBase.OMatrix(); List <smdNode> nodeList = new List <smdNode>(); while (reader.BaseStream.Position < reader.BaseStream.Length) { string line = readLine(reader); string[] parameters = Regex.Split(line, "\\s+"); switch (parameters[0]) { case "version": if (parameters.Length == 1) { MessageBox.Show( "Corrupted SMD file! The version isn't specified!", "SMD Importer", MessageBoxButtons.OK, MessageBoxIcon.Error); return(null); } else if (parameters[1] != "1") { MessageBox.Show("Unknow SMD version!", "SMD Importer", MessageBoxButtons.OK, MessageBoxIcon.Error); return(null); } break; case "nodes": line = readLine(reader); parameters = Regex.Split(line, "\\s+"); while (parameters[0] != "end") { if (parameters.Length == 3) { smdNode node = new smdNode(); node.index = int.Parse(parameters[0]); int nameStart = parameters[1].IndexOf("\"") + 1; node.name = parameters[1].Substring(nameStart, parameters[1].LastIndexOf("\"") - nameStart); node.parentId = int.Parse(parameters[2]); nodeList.Add(node); } line = readLine(reader); parameters = Regex.Split(line, "\\s+"); } break; case "skeleton": bool isReference = false; int timeIndex = -1; line = readLine(reader); parameters = Regex.Split(line, "\\s+"); RenderBase.OSkeletalAnimationBone[] boneArray = null; while (parameters[0] != "end") { if (parameters[0] == "time") { timeIndex = int.Parse(parameters[1]); } else { if (timeIndex > -1 && parameters.Length == 7) { int nodeIndex = int.Parse(parameters[0]); float translationX = float.Parse(parameters[1], CultureInfo.InvariantCulture); float translationY = float.Parse(parameters[2], CultureInfo.InvariantCulture); float translationZ = float.Parse(parameters[3], CultureInfo.InvariantCulture); float rotationX = float.Parse(parameters[4], CultureInfo.InvariantCulture); float rotationY = float.Parse(parameters[5], CultureInfo.InvariantCulture); float rotationZ = float.Parse(parameters[6], CultureInfo.InvariantCulture); if (timeIndex == 0) { RenderBase.OBone bone = new RenderBase.OBone(); foreach (smdNode node in nodeList) { if (node.index == nodeIndex) { bone.name = node.name; bone.parentId = (short)node.parentId; } } bone.translation = new RenderBase.OVector3(translationX, translationY, translationZ); bone.rotation = new RenderBase.OVector3(rotationX, rotationY, rotationZ); bone.scale = new RenderBase.OVector3(1, 1, 1); bone.absoluteScale = new RenderBase.OVector3(bone.scale); mdl.skeleton.Add(bone); } else { if (!isReference) { boneArray = new RenderBase.OSkeletalAnimationBone[mdl.skeleton.Count]; int index = 0; foreach (RenderBase.OBone b in mdl.skeleton) { RenderBase.OSkeletalAnimationBone bone = new RenderBase.OSkeletalAnimationBone(); bone.name = b.name; bone.translationX.exists = true; bone.translationY.exists = true; bone.translationZ.exists = true; bone.rotationX.exists = true; bone.rotationY.exists = true; bone.rotationZ.exists = true; //Translation bone.translationX.interpolation = bone.translationY.interpolation = bone.translationZ.interpolation = RenderBase.OInterpolationMode.linear; bone.translationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.translation.x, 0)); bone.translationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.translation.y, 0)); bone.translationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.translation.z, 0)); //Rotation bone.rotationX.interpolation = bone.rotationY.interpolation = bone.rotationZ.interpolation = RenderBase.OInterpolationMode.linear; bone.rotationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.rotation.x, 0)); bone.rotationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.rotation.y, 0)); bone.rotationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.rotation.z, 0)); boneArray[index++] = bone; } isReference = true; } boneArray[nodeIndex].translationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(translationX, timeIndex)); boneArray[nodeIndex].translationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(translationY, timeIndex)); boneArray[nodeIndex].translationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(translationZ, timeIndex)); boneArray[nodeIndex].rotationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(rotationX, timeIndex)); boneArray[nodeIndex].rotationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(rotationY, timeIndex)); boneArray[nodeIndex].rotationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(rotationZ, timeIndex)); } } } line = readLine(reader); parameters = Regex.Split(line, "\\s+"); } if (isReference) { RenderBase.OSkeletalAnimation anim = new RenderBase.OSkeletalAnimation(); anim.frameSize = timeIndex; anim.name = Path.GetFileNameWithoutExtension(fileName); for (int i = 0; i < boneArray.Length; i++) { boneArray[i].translationX.endFrame = timeIndex; boneArray[i].translationY.endFrame = timeIndex; boneArray[i].translationZ.endFrame = timeIndex; boneArray[i].rotationX.endFrame = timeIndex; boneArray[i].rotationY.endFrame = timeIndex; boneArray[i].rotationZ.endFrame = timeIndex; } anim.bone.AddRange(boneArray); model.skeletalAnimation.list.Add(anim); } break; case "triangles": line = readLine(reader); parameters = Regex.Split(line, "\\s+"); RenderBase.OMesh obj = new RenderBase.OMesh(); int materialId = 0; string oldTexture = null; obj.hasNormal = true; int count = 0; while (parameters[0] != "end") { if (count == 0) { string texture = parameters[0]; if (texture != oldTexture && oldTexture != null) { mdl.material.Add(getMaterial(oldTexture)); obj.materialId = (ushort)materialId++; mdl.mesh.Add(obj); obj = new RenderBase.OMesh(); } oldTexture = texture; } else { if (parameters.Length >= 10) { int parentBone; float x, y, z; float nx, ny, nz; float u, v; int joints; parentBone = int.Parse(parameters[0]); x = float.Parse(parameters[1], CultureInfo.InvariantCulture); y = float.Parse(parameters[2], CultureInfo.InvariantCulture); z = float.Parse(parameters[3], CultureInfo.InvariantCulture); nx = float.Parse(parameters[4], CultureInfo.InvariantCulture); ny = float.Parse(parameters[5], CultureInfo.InvariantCulture); nz = float.Parse(parameters[6], CultureInfo.InvariantCulture); u = float.Parse(parameters[7], CultureInfo.InvariantCulture); v = float.Parse(parameters[8], CultureInfo.InvariantCulture); joints = int.Parse(parameters[9]); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; int j = 10; for (int i = 0; i < joints; i++) { int joint = int.Parse(parameters[j++]); float weight = float.Parse(parameters[j++], CultureInfo.InvariantCulture); vertex.node.Add(joint); vertex.weight.Add(weight); obj.hasNode = true; obj.hasWeight = true; } vertex.position = new RenderBase.OVector3(x, y, z); vertex.normal = new RenderBase.OVector3(nx, ny, nz); vertex.texture0 = new RenderBase.OVector2(u, v); obj.vertices.Add(vertex); } } line = readLine(reader); parameters = Regex.Split(line, "\\s+"); count = (count + 1) % 4; } //Add the last object mdl.material.Add(getMaterial(oldTexture)); obj.materialId = (ushort)materialId++; mdl.mesh.Add(obj); break; } } model.model.Add(mdl); return(model); }
/// <summary> /// Loads a CGFX file. /// Note that CGFX must start at offset 0x0 (don't try using it for CGFXs inside containers). /// </summary> /// <param name="data">Stream of the CGFX file.</param> /// <returns></returns> public static RenderBase.OModelGroup load(Stream data) { BinaryReader input = new BinaryReader(data); RenderBase.OModelGroup models = new RenderBase.OModelGroup(); cgfxHeader header = new cgfxHeader(); header.magic = IOUtils.readString(input, 0, 4); header.endian = input.ReadUInt16(); header.length = input.ReadUInt16(); header.revision = input.ReadUInt32(); header.fileLength = input.ReadUInt32(); header.entries = input.ReadUInt32(); data.Seek(header.length, SeekOrigin.Begin); dataHeader dataHeader = new dataHeader(); dataHeader.magic = IOUtils.readString(input, (uint)data.Position, 4); dataHeader.length = input.ReadUInt32(); dataHeader.models = getDictionary(input); dataHeader.textures = getDictionary(input); dataHeader.lookUpTables = getDictionary(input); dataHeader.materials = getDictionary(input); dataHeader.shaders = getDictionary(input); dataHeader.cameras = getDictionary(input); dataHeader.lights = getDictionary(input); dataHeader.fogs = getDictionary(input); dataHeader.scenes = getDictionary(input); dataHeader.skeletalAnimations = getDictionary(input); dataHeader.materialAnimations = getDictionary(input); dataHeader.visibilityAnimations = getDictionary(input); dataHeader.cameraAnimations = getDictionary(input); dataHeader.lightAnimations = getDictionary(input); dataHeader.emitters = getDictionary(input); //Textures foreach (dictEntry textureEntry in dataHeader.textures) { data.Seek(textureEntry.dataOffset, SeekOrigin.Begin); uint type = input.ReadUInt32(); string txobMagic = IOUtils.readString(input, (uint)data.Position, 4); uint revision = input.ReadUInt32(); string name = IOUtils.readString(input, getRelativeOffset(input)); uint userDataEntries = input.ReadUInt32(); uint userDataOffset = getRelativeOffset(input); int height = (int)input.ReadUInt32(); int width = (int)input.ReadUInt32(); uint openGLFormat = input.ReadUInt32(); uint openGLType = input.ReadUInt32(); uint mipmapLevels = input.ReadUInt32(); uint textureObject = input.ReadUInt32(); uint locationFlags = input.ReadUInt32(); RenderBase.OTextureFormat format = (RenderBase.OTextureFormat)input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); uint dataLength = input.ReadUInt32(); uint dataOffset = getRelativeOffset(input); uint dynamicAllocator = input.ReadUInt32(); uint bitsPerPixel = input.ReadUInt32(); uint locationAddress = input.ReadUInt32(); uint memoryAddress = input.ReadUInt32(); byte[] buffer = new byte[dataLength]; data.Seek(dataOffset, SeekOrigin.Begin); input.Read(buffer, 0, buffer.Length); models.texture.Add(new RenderBase.OTexture(TextureCodec.decode(buffer, width, height, format), name)); } //Skeletal animations foreach (dictEntry skeletalAnimationEntry in dataHeader.skeletalAnimations) { data.Seek(skeletalAnimationEntry.dataOffset, SeekOrigin.Begin); RenderBase.OSkeletalAnimation skeletalAnimation = new RenderBase.OSkeletalAnimation(); string canmMagic = IOUtils.readString(input, (uint)input.BaseStream.Position, 4); uint revision = input.ReadUInt32(); skeletalAnimation.name = IOUtils.readString(input, getRelativeOffset(input)); string targetAnimationGroupName = IOUtils.readString(input, getRelativeOffset(input)); skeletalAnimation.loopMode = (RenderBase.OLoopMode)input.ReadUInt32(); skeletalAnimation.frameSize = input.ReadSingle(); List<dictEntry> memberAnimationDataDictionary = getDictionary(input); uint userDataEntries = input.ReadUInt32(); uint userDataOffset = getRelativeOffset(input); foreach (dictEntry entry in memberAnimationDataDictionary) { RenderBase.OSkeletalAnimationBone bone = new RenderBase.OSkeletalAnimationBone(); bone.name = IOUtils.readString(input, entry.nameOffset); data.Seek(entry.dataOffset, SeekOrigin.Begin); uint boneFlags = input.ReadUInt32(); string bonePath = IOUtils.readString(input, getRelativeOffset(input)); if ((revision >> 24) < 7) data.Seek(8, SeekOrigin.Current); cgfxSegmentType segmentType = (cgfxSegmentType)input.ReadUInt32(); switch (segmentType) { case cgfxSegmentType.transform: data.Seek(0xc, SeekOrigin.Current); uint notExistMask = 0x80000; uint constantMask = 0x200; for (int j = 0; j < 2; j++) { for (int axis = 0; axis < 3; axis++) { bool notExist = (boneFlags & notExistMask) > 0; bool constant = (boneFlags & 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 = getRelativeOffset(input); 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; } notExistMask <<= 1; constantMask <<= 1; data.Seek(4, SeekOrigin.Current); } break; case cgfxSegmentType.transformQuaternion: bone.isFrameFormat = true; uint rotationOffset = getRelativeOffset(input); uint translationOffset = getRelativeOffset(input); uint scaleOffset = getRelativeOffset(input); if ((boneFlags & 0x10) == 0) { bone.rotationQuaternion.exists = true; data.Seek(rotationOffset, SeekOrigin.Begin); bone.rotationQuaternion.startFrame = input.ReadSingle(); bone.rotationQuaternion.endFrame = input.ReadSingle(); input.ReadUInt32(); uint constantFlags = input.ReadUInt32(); if ((constantFlags & 1) > 0) { bone.rotationQuaternion.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle())); } else { uint rotationEntries = (uint)(bone.rotationQuaternion.endFrame - bone.rotationQuaternion.startFrame); for (int j = 0; j < rotationEntries; j++) { bone.rotationQuaternion.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle())); uint flags = input.ReadUInt32(); } } } else data.Seek(4, SeekOrigin.Current); if ((boneFlags & 8) == 0) { bone.translation.exists = true; data.Seek(translationOffset, SeekOrigin.Begin); bone.translation.startFrame = input.ReadSingle(); bone.translation.endFrame = input.ReadSingle(); input.ReadUInt32(); uint constantFlags = input.ReadUInt32(); if ((constantFlags & 1) > 0) { bone.translation.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 0)); } else { uint translationEntries = (uint)(bone.rotationQuaternion.endFrame - bone.rotationQuaternion.startFrame); for (int j = 0; j < translationEntries; j++) { bone.translation.vector.Add(new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 0)); uint flags = input.ReadUInt32(); } } } else data.Seek(4, SeekOrigin.Current); break; } skeletalAnimation.bone.Add(bone); } models.skeletalAnimation.list.Add(skeletalAnimation); } //Models foreach (dictEntry modelEntry in dataHeader.models) { data.Seek(modelEntry.dataOffset, SeekOrigin.Begin); cmdlHeader cmdlHeader = new cmdlHeader(); uint flags = input.ReadUInt32(); cmdlHeader.hasSkeleton = (flags & 0x80) > 0; string cmdlMagic = IOUtils.readString(input, (uint)input.BaseStream.Position, 4); uint revision = input.ReadUInt32(); cmdlHeader.modelName = IOUtils.readString(input, getRelativeOffset(input)); cmdlHeader.userDataEntries = input.ReadUInt32(); cmdlHeader.userDataDictionaryOffset = getRelativeOffset(input); input.ReadUInt32(); flags = input.ReadUInt32(); cmdlHeader.isBranchVisible = (flags & 1) > 0; cmdlHeader.childCount = input.ReadUInt32(); input.ReadUInt32(); //Unused cmdlHeader.animationGroupEntries = input.ReadUInt32(); cmdlHeader.animationGroupDictionaryOffset = getRelativeOffset(input); cmdlHeader.transformScale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); cmdlHeader.transformRotate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); cmdlHeader.transformTranslate = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); cmdlHeader.localMatrix = getMatrix(input); cmdlHeader.worldMatrix = getMatrix(input); cmdlHeader.objectEntries = input.ReadUInt32(); cmdlHeader.objectPointerTableOffset = getRelativeOffset(input); cmdlHeader.materials = getDictionary(input); cmdlHeader.shapeEntries = input.ReadUInt32(); cmdlHeader.shapePointerTableOffset = getRelativeOffset(input); cmdlHeader.objectNodes = getDictionary(input); flags = input.ReadUInt32(); cmdlHeader.isVisible = (flags & 1) > 0; cmdlHeader.isNonUniformScalable = (flags & 0x100) > 0; cmdlHeader.cullMode = (RenderBase.OModelCullingMode)input.ReadUInt32(); cmdlHeader.layerId = input.ReadUInt32(); if (cmdlHeader.hasSkeleton) cmdlHeader.skeletonOffset = getRelativeOffset(input); RenderBase.OModel model = new RenderBase.OModel(); model.name = cmdlHeader.modelName; model.transform = cmdlHeader.worldMatrix; //Materials foreach (dictEntry materialEntry in cmdlHeader.materials) { data.Seek(materialEntry.dataOffset, SeekOrigin.Begin); RenderBase.OMaterial material = new RenderBase.OMaterial(); flags = input.ReadUInt32(); string mtobMagic = IOUtils.readString(input, (uint)input.BaseStream.Position, 4); revision = input.ReadUInt32(); material.name = IOUtils.readString(input, getRelativeOffset(input)); uint userDataEntries = input.ReadUInt32(); uint userDataOffset = getRelativeOffset(input); flags = input.ReadUInt32(); material.isFragmentLightEnabled = (flags & 1) > 0; material.isVertexLightEnabled = (flags & 2) > 0; material.isHemiSphereLightEnabled = (flags & 4) > 0; material.isHemiSphereOcclusionEnabled = (flags & 8) > 0; material.isFogEnabled = (flags & 0x10) > 0; material.rasterization.isPolygonOffsetEnabled = (flags & 0x20) > 0; uint textureCoordinatesConfig = input.ReadUInt32(); uint translucencyKind = input.ReadUInt32(); /* * Material color */ MeshUtils.getColorFloat(input); //Emission (stored as float4) MeshUtils.getColorFloat(input); //Ambient (stored as float4) MeshUtils.getColorFloat(input); //Diffuse (stored as float4) MeshUtils.getColorFloat(input); //Specular 0 (stored as float4) MeshUtils.getColorFloat(input); //Specular 1 (stored as float4) MeshUtils.getColorFloat(input); //Constant 0 (stored as float4) MeshUtils.getColorFloat(input); //Constant 1 (stored as float4) MeshUtils.getColorFloat(input); //Constant 2 (stored as float4) MeshUtils.getColorFloat(input); //Constant 3 (stored as float4) MeshUtils.getColorFloat(input); //Constant 4 (stored as float4) MeshUtils.getColorFloat(input); //Constant 5 (stored as float4) 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); /* * Rasterization */ material.rasterization.isPolygonOffsetEnabled = (input.ReadUInt32() & 1) > 0; material.rasterization.cullMode = (RenderBase.OCullMode)input.ReadUInt32(); material.rasterization.polygonOffsetUnit = input.ReadSingle(); data.Seek(0xc, SeekOrigin.Current); /* * Fragment operation */ //Depth operation flags = input.ReadUInt32(); PICACommandReader depthCommands = new PICACommandReader(data, 4, true); material.fragmentOperation.depth = depthCommands.getDepthTest(); material.fragmentOperation.depth.isTestEnabled = (flags & 1) > 0; material.fragmentOperation.depth.isMaskEnabled = (flags & 2) > 0; //Blend operation RenderBase.OBlendMode blendMode = RenderBase.OBlendMode.notUsed; switch (input.ReadUInt32()) { case 0: blendMode = RenderBase.OBlendMode.notUsed; break; case 1: blendMode = RenderBase.OBlendMode.blend; break; case 2: blendMode = RenderBase.OBlendMode.blend; break; //Separate blend case 3: blendMode = RenderBase.OBlendMode.logical; break; } Color blendColor = MeshUtils.getColorFloat(input); PICACommandReader blendCommands = new PICACommandReader(data, 5, true); material.fragmentOperation.blend = blendCommands.getBlendOperation(); material.fragmentOperation.blend.mode = blendMode; material.fragmentOperation.blend.blendColor = blendColor; //Stencil operation input.ReadUInt32(); PICACommandReader stencilCommands = new PICACommandReader(data, 4, true); material.fragmentOperation.stencil = stencilCommands.getStencilTest(); /* * Texture coordinates */ uint usedTextureCoordinates = input.ReadUInt32(); for (int i = 0; i < 3; i++) { RenderBase.OTextureCoordinator coordinator = new RenderBase.OTextureCoordinator(); uint sourceCoordinate = input.ReadUInt32(); coordinator.projection = (RenderBase.OTextureProjection)input.ReadUInt32(); coordinator.referenceCamera = input.ReadUInt32(); uint matrixMode = input.ReadUInt32(); coordinator.scaleU = input.ReadSingle(); coordinator.scaleV = input.ReadSingle(); coordinator.rotate = input.ReadSingle(); coordinator.translateU = input.ReadSingle(); coordinator.translateV = input.ReadSingle(); bool isEnabled = (input.ReadUInt32() & 1) > 0; RenderBase.OMatrix transformMatrix = getMatrix(input); material.textureCoordinator[i] = coordinator; } /* * Texture mappers */ uint[] mapperOffsets = new uint[4]; mapperOffsets[0] = getRelativeOffset(input); mapperOffsets[1] = getRelativeOffset(input); mapperOffsets[2] = getRelativeOffset(input); mapperOffsets[3] = getRelativeOffset(input); long position = data.Position; for (int i = 0; i < 3; i++) { if (mapperOffsets[i] != 0) { data.Seek(mapperOffsets[i], SeekOrigin.Begin); flags = input.ReadUInt32(); uint dynamicAllocator = input.ReadUInt32(); uint textureHeaderOffset = getRelativeOffset(input); uint samplerOffset = getRelativeOffset(input); PICACommandReader textureCommands = new PICACommandReader(data, 13, true); switch (i) { case 0: material.textureMapper[i] = textureCommands.getTexUnit0Mapper(); material.textureMapper[i].borderColor = textureCommands.getTexUnit0BorderColor(); data.Seek(textureHeaderOffset + 0x18, SeekOrigin.Begin); material.name0 = IOUtils.readString(input, getRelativeOffset(input)); break; case 1: material.textureMapper[i] = textureCommands.getTexUnit1Mapper(); material.textureMapper[i].borderColor = textureCommands.getTexUnit1BorderColor(); data.Seek(textureHeaderOffset + 0x18, SeekOrigin.Begin); material.name1 = IOUtils.readString(input, getRelativeOffset(input)); break; case 2: material.textureMapper[i] = textureCommands.getTexUnit2Mapper(); material.textureMapper[i].borderColor = textureCommands.getTexUnit2BorderColor(); data.Seek(textureHeaderOffset + 0x18, SeekOrigin.Begin); material.name2 = IOUtils.readString(input, getRelativeOffset(input)); break; } data.Seek(samplerOffset, SeekOrigin.Begin); Color borderColor = MeshUtils.getColorFloat(input); //Not needed, we already got from Commands buffer material.textureMapper[i].LODBias = input.ReadSingle(); } } data.Seek(position, SeekOrigin.Begin); uint shaderOffset = getRelativeOffset(input); uint fragmentShaderOffset = getRelativeOffset(input); uint shaderProgramDescriptionIndex = input.ReadUInt32(); uint shaderParametersCount = input.ReadUInt32(); uint shaderParametersPointerTableOffset = getRelativeOffset(input); material.lightSetIndex = input.ReadUInt32(); material.fogIndex = input.ReadUInt32(); uint shadingParametersHash = input.ReadUInt32(); uint shaderParametersHash = input.ReadUInt32(); uint textureCoordinatorsHash = input.ReadUInt32(); uint textureSamplersHash = input.ReadUInt32(); uint textureMappersHash = input.ReadUInt32(); uint materialColorHash = input.ReadUInt32(); uint rasterizationHash = input.ReadUInt32(); uint fragmentLightingHash = input.ReadUInt32(); uint fragmentLightingTableHash = input.ReadUInt32(); uint fragmentLightingTableParametersHash = input.ReadUInt32(); uint textureCombinersHash = input.ReadUInt32(); uint alphaTestHash = input.ReadUInt32(); uint fragmentOperationHash = input.ReadUInt32(); uint materialId = input.ReadUInt32(); /* * Shader */ if (shaderOffset != 0) { data.Seek(shaderOffset, SeekOrigin.Begin); flags = input.ReadUInt32(); string shdrMagic = IOUtils.readString(input, (uint)data.Position, 4); revision = input.ReadUInt32(); string shaderName = IOUtils.readString(input, getRelativeOffset(input)); userDataEntries = input.ReadUInt32(); userDataOffset = getRelativeOffset(input); string referenceShaderName = IOUtils.readString(input, getRelativeOffset(input)); input.ReadUInt32(); material.shaderReference = new RenderBase.OReference(shaderName, referenceShaderName); } /* * Fragment shader */ if (fragmentShaderOffset != 0) { data.Seek(fragmentShaderOffset, SeekOrigin.Begin); material.fragmentShader.bufferColor = MeshUtils.getColorFloat(input); flags = input.ReadUInt32(); material.fragmentShader.lighting.isClampHighLight = (flags & 1) > 0; material.fragmentShader.lighting.isDistribution0Enabled = (flags & 2) > 0; material.fragmentShader.lighting.isDistribution1Enabled = (flags & 4) > 0; material.fragmentShader.lighting.isGeometryFactor0Enabled = (flags & 8) > 0; material.fragmentShader.lighting.isGeometryFactor1Enabled = (flags & 0x10) > 0; material.fragmentShader.lighting.isReflectionEnabled = (flags & 0x20) > 0; material.fragmentShader.layerConfig = input.ReadUInt32(); material.fragmentShader.lighting.fresnelConfig = (RenderBase.OFresnelConfig)input.ReadUInt32(); material.fragmentShader.bump.texture = (RenderBase.OBumpTexture)input.ReadUInt32(); material.fragmentShader.bump.mode = (RenderBase.OBumpMode)input.ReadUInt32(); flags = input.ReadUInt32(); material.fragmentShader.bump.isBumpRenormalize = (flags & 1) > 0; uint fragmentLightingTableOffset = getRelativeOffset(input); position = data.Position; data.Seek(fragmentLightingTableOffset, SeekOrigin.Begin); material.fragmentShader.lighting.reflectanceRSampler = getFragmentSampler(input, getRelativeOffset(input)); material.fragmentShader.lighting.reflectanceGSampler = getFragmentSampler(input, getRelativeOffset(input)); material.fragmentShader.lighting.reflectanceBSampler = getFragmentSampler(input, getRelativeOffset(input)); material.fragmentShader.lighting.distribution0Sampler = getFragmentSampler(input, getRelativeOffset(input)); material.fragmentShader.lighting.distribution1Sampler = getFragmentSampler(input, getRelativeOffset(input)); material.fragmentShader.lighting.fresnelSampler = getFragmentSampler(input, getRelativeOffset(input)); data.Seek(position, SeekOrigin.Begin); input.ReadUInt32(); PICACommandReader combiner0Commands = new PICACommandReader(data, 6, true); input.ReadUInt32(); PICACommandReader combiner1Commands = new PICACommandReader(data, 6, true); input.ReadUInt32(); PICACommandReader combiner2Commands = new PICACommandReader(data, 6, true); input.ReadUInt32(); PICACommandReader combiner3Commands = new PICACommandReader(data, 6, true); input.ReadUInt32(); PICACommandReader combiner4Commands = new PICACommandReader(data, 6, true); input.ReadUInt32(); PICACommandReader combiner5Commands = new PICACommandReader(data, 6, true); material.fragmentShader.textureCombiner[0] = combiner0Commands.getTevStage(0); material.fragmentShader.textureCombiner[1] = combiner1Commands.getTevStage(1); material.fragmentShader.textureCombiner[2] = combiner2Commands.getTevStage(2); material.fragmentShader.textureCombiner[3] = combiner3Commands.getTevStage(3); material.fragmentShader.textureCombiner[4] = combiner4Commands.getTevStage(4); material.fragmentShader.textureCombiner[5] = combiner5Commands.getTevStage(5); PICACommandReader alphaCommands = new PICACommandReader(data, 2, true); material.fragmentShader.alphaTest = alphaCommands.getAlphaTest(); } model.material.Add(material); } //Skeleton bool isSkeletonTranslateAnimationEnabled; if (cmdlHeader.hasSkeleton) { data.Seek(cmdlHeader.skeletonOffset, SeekOrigin.Begin); flags = input.ReadUInt32(); string skeletonMagic = IOUtils.readString(input, (uint)input.BaseStream.Position, 4); //SOBJ revision = input.ReadUInt32(); string name = IOUtils.readString(input, getRelativeOffset(input)); input.ReadUInt32(); input.ReadUInt32(); List<dictEntry> skeletonDictionary = getDictionary(input); uint rootBoneOffset = getRelativeOffset(input); cgfxSkeletonScalingRule scalingRule = (cgfxSkeletonScalingRule)input.ReadUInt32(); isSkeletonTranslateAnimationEnabled = (input.ReadUInt32() & 1) > 0; foreach (dictEntry boneEntry in skeletonDictionary) { data.Seek(boneEntry.dataOffset, SeekOrigin.Begin); RenderBase.OBone bone = new RenderBase.OBone(); bone.name = IOUtils.readString(input, getRelativeOffset(input)); uint boneFlags = input.ReadUInt32(); bone.isSegmentScaleCompensate = (boneFlags & 0x20) > 0; uint boneId = input.ReadUInt32(); bone.parentId = (short)input.ReadInt32(); int parentOffset = input.ReadInt32(); int childOffset = input.ReadInt32(); int previousSiblingOffset = input.ReadInt32(); int nextSiblingOffset = input.ReadInt32(); 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 localMatrix = getMatrix(input); RenderBase.OMatrix worldMatrix = getMatrix(input); RenderBase.OMatrix invBaseMatrix = getMatrix(input); bone.billboardMode = (RenderBase.OBillboardMode)input.ReadInt32(); uint userDataEntries = input.ReadUInt32(); uint userDataOffset = getRelativeOffset(input); model.skeleton.Add(bone); } } List<RenderBase.OMatrix> skeletonTransform = new List<RenderBase.OMatrix>(); for (int index = 0; index < model.skeleton.Count; index++) { RenderBase.OMatrix transform = new RenderBase.OMatrix(); transformSkeleton(model.skeleton, index, ref transform); skeletonTransform.Add(transform); } //Shapes List<cgfxShapeEntry> shapeHeader = new List<cgfxShapeEntry>(); for (int i = 0; i < cmdlHeader.shapeEntries; i++) { data.Seek(cmdlHeader.shapePointerTableOffset + (i * 4), SeekOrigin.Begin); data.Seek(getRelativeOffset(input), SeekOrigin.Begin); cgfxShapeEntry shape = new cgfxShapeEntry(); flags = input.ReadUInt32(); string sobjMagic = IOUtils.readString(input, (uint)input.BaseStream.Position, 4); revision = input.ReadUInt32(); shape.name = IOUtils.readString(input, getRelativeOffset(input)); shape.userDataEntries = input.ReadUInt32(); shape.userDataDictionaryOffset = getRelativeOffset(input); flags = input.ReadUInt32(); shape.boundingBoxOffset = getRelativeOffset(input); shape.positionOffset = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); shape.facesGroupEntries = input.ReadUInt32(); shape.facesGroupOffset = getRelativeOffset(input); input.ReadUInt32(); shape.vertexGroupEntries = input.ReadUInt32(); shape.vertexGroupOffset = getRelativeOffset(input); shape.blendShapeOffset = getRelativeOffset(input); shapeHeader.Add(shape); } List<RenderBase.OMesh> shapes = new List<RenderBase.OMesh>(); foreach (cgfxShapeEntry shapeEntry in shapeHeader) { RenderBase.OMesh shape = new RenderBase.OMesh(); data.Seek(shapeEntry.vertexGroupOffset, SeekOrigin.Begin); data.Seek(getRelativeOffset(input), SeekOrigin.Begin); input.ReadUInt32(); //Useless name offset input.ReadUInt32(); //Useless User Data entries input.ReadUInt32(); //Useless User Data offset uint bufferObject = input.ReadUInt32(); uint locationFlag = input.ReadUInt32(); uint vshAttributesBufferLength = input.ReadUInt32(); uint vshAttributesBufferOffset = getRelativeOffset(input); uint locationAddress = input.ReadUInt32(); uint memoryArea = input.ReadUInt32(); uint vshAttributesBufferStride = input.ReadUInt32(); uint vshAttributesBufferComponentsEntries = input.ReadUInt32(); uint vshAttributesBufferComponentsOffset = getRelativeOffset(input); List<attributeFormat> vshAttributeFormats = new List<attributeFormat>(); for (int i = 0; i < vshAttributesBufferComponentsEntries; i++) { data.Seek(vshAttributesBufferComponentsOffset + (i * 4), SeekOrigin.Begin); data.Seek(getRelativeOffset(input), SeekOrigin.Begin); attributeFormat format = new attributeFormat(); flags = input.ReadUInt32(); format.attribute = (PICACommand.vshAttribute)input.ReadUInt32(); format.isInterleaved = input.ReadUInt32() == 2; bufferObject = input.ReadUInt32(); locationFlag = input.ReadUInt32(); uint attributesStreamLength = input.ReadUInt32(); uint attributesStreamOffset = getRelativeOffset(input); locationAddress = input.ReadUInt32(); memoryArea = input.ReadUInt32(); format.type = (attributeFormatType)(input.ReadUInt32() & 0xf); format.attributeLength = input.ReadUInt32(); format.scale = input.ReadSingle(); format.offset = input.ReadUInt32(); vshAttributeFormats.Add(format); } //Faces for (int faceIndex = 0; faceIndex < shapeEntry.facesGroupEntries; faceIndex++) { data.Seek(shapeEntry.facesGroupOffset + (faceIndex * 4), SeekOrigin.Begin); data.Seek(getRelativeOffset(input), SeekOrigin.Begin); uint nodeListEntries = input.ReadUInt32(); uint nodeListOffset = getRelativeOffset(input); RenderBase.OSkinningMode skinningMode = RenderBase.OSkinningMode.none; switch (input.ReadUInt32()) //Skinning Mode { case 0: skinningMode = RenderBase.OSkinningMode.none; break; case 1: skinningMode = RenderBase.OSkinningMode.rigidSkinning; break; case 2: skinningMode = RenderBase.OSkinningMode.smoothSkinning; break; } uint faceMainHeaderEntries = input.ReadUInt32(); uint faceMainHeaderOffset = getRelativeOffset(input); //Bone nodes List<uint> nodeList = new List<uint>(); data.Seek(nodeListOffset, SeekOrigin.Begin); for (int i = 0; i < nodeListEntries; i++) nodeList.Add(input.ReadUInt32()); //Face-related stuff data.Seek(faceMainHeaderOffset, SeekOrigin.Begin); data.Seek(getRelativeOffset(input), SeekOrigin.Begin); uint faceDescriptorEntries = input.ReadUInt32(); uint faceDescriptorOffset = getRelativeOffset(input); input.ReadUInt32(); input.ReadUInt32(); input.ReadUInt32(); data.Seek(faceDescriptorOffset, SeekOrigin.Begin); data.Seek(getRelativeOffset(input), SeekOrigin.Begin); PICACommand.indexBufferFormat idxBufferFormat = (PICACommand.indexBufferFormat)((input.ReadUInt32() & 2) >> 1); input.ReadUInt32(); uint idxBufferLength = input.ReadUInt32(); uint idxBufferOffset = getRelativeOffset(input); for (int attribute = 0; attribute < vshAttributeFormats.Count; attribute++) { attributeFormat format = vshAttributeFormats[attribute]; switch (format.attribute) { case PICACommand.vshAttribute.normal: shape.hasNormal = true; break; case PICACommand.vshAttribute.tangent: shape.hasTangent = true; break; case PICACommand.vshAttribute.color: shape.hasColor = true; break; case PICACommand.vshAttribute.textureCoordinate0: shape.texUVCount = Math.Max(shape.texUVCount, 1); break; case PICACommand.vshAttribute.textureCoordinate1: shape.texUVCount = Math.Max(shape.texUVCount, 2); break; case PICACommand.vshAttribute.textureCoordinate2: shape.texUVCount = Math.Max(shape.texUVCount, 3); break; } } if (nodeList.Count > 0) { shape.hasNode = true; shape.hasWeight = true; } data.Seek(idxBufferOffset, SeekOrigin.Begin); for (int i = 0; i < idxBufferLength; i++) { ushort index = 0; switch (idxBufferFormat) { case PICACommand.indexBufferFormat.unsignedShort: index = input.ReadUInt16(); i++; break; case PICACommand.indexBufferFormat.unsignedByte: index = input.ReadByte(); break; } long dataPosition = data.Position; long vertexOffset = vshAttributesBufferOffset + (index * vshAttributesBufferStride); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; for (int attribute = 0; attribute < vshAttributeFormats.Count; attribute++) { attributeFormat format = vshAttributeFormats[attribute]; if (format.attribute == PICACommand.vshAttribute.boneWeight) format.type = attributeFormatType.unsignedByte; data.Seek(vertexOffset + format.offset, SeekOrigin.Begin); RenderBase.OVector4 vector = getVector(input, format); switch (format.attribute) { case PICACommand.vshAttribute.position: float x = (vector.x * format.scale) + shapeEntry.positionOffset.x; float y = (vector.y * format.scale) + shapeEntry.positionOffset.y; float z = (vector.z * format.scale) + shapeEntry.positionOffset.z; vertex.position = new RenderBase.OVector3(x, y, z); break; case PICACommand.vshAttribute.normal: vertex.normal = new RenderBase.OVector3(vector.x * format.scale, vector.y * format.scale, vector.z * format.scale); break; case PICACommand.vshAttribute.tangent: vertex.tangent = new RenderBase.OVector3(vector.x * format.scale, vector.y * format.scale, vector.z * format.scale); break; case PICACommand.vshAttribute.color: uint r = MeshUtils.saturate((vector.x * format.scale) * 0xff); uint g = MeshUtils.saturate((vector.y * format.scale) * 0xff); uint b = MeshUtils.saturate((vector.z * format.scale) * 0xff); uint a = MeshUtils.saturate((vector.w * format.scale) * 0xff); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); break; case PICACommand.vshAttribute.textureCoordinate0: vertex.texture0 = new RenderBase.OVector2(vector.x * format.scale, vector.y * format.scale); break; case PICACommand.vshAttribute.textureCoordinate1: vertex.texture1 = new RenderBase.OVector2(vector.x * format.scale, vector.y * format.scale); break; case PICACommand.vshAttribute.textureCoordinate2: vertex.texture2 = new RenderBase.OVector2(vector.x * format.scale, vector.y * format.scale); break; case PICACommand.vshAttribute.boneIndex: int b0 = (int)vector.x; int b1 = (int)vector.y; int b2 = (int)vector.z; int b3 = (int)vector.w; if (b0 < nodeList.Count && format.attributeLength > 0) vertex.node.Add((int)nodeList[b0]); if (skinningMode == RenderBase.OSkinningMode.smoothSkinning) { if (b1 < nodeList.Count && format.attributeLength > 1) vertex.node.Add((int)nodeList[b1]); if (b2 < nodeList.Count && format.attributeLength > 2) vertex.node.Add((int)nodeList[b2]); if (b3 < nodeList.Count && format.attributeLength > 3) vertex.node.Add((int)nodeList[b3]); } break; case PICACommand.vshAttribute.boneWeight: if (format.attributeLength > 0) vertex.weight.Add(vector.x * format.scale); if (skinningMode == RenderBase.OSkinningMode.smoothSkinning) { if (format.attributeLength > 1) vertex.weight.Add(vector.y * format.scale); if (format.attributeLength > 2) vertex.weight.Add(vector.z * format.scale); if (format.attributeLength > 3) vertex.weight.Add(vector.w * format.scale); } 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((int)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.node[0] < skeletonTransform.Count) { if (vertex.weight.Count == 0) vertex.weight.Add(1); vertex.position = RenderBase.OVector3.transform(vertex.position, skeletonTransform[vertex.node[0]]); } } MeshUtils.calculateBounds(model, vertex); shape.vertices.Add(vertex); data.Seek(dataPosition, SeekOrigin.Begin); } } shapes.Add(shape); } //Objects List<cgfxObjectEntry> objectHeader = new List<cgfxObjectEntry>(); for (int i = 0; i < cmdlHeader.objectEntries; i++) { data.Seek(cmdlHeader.objectPointerTableOffset + (i * 4), SeekOrigin.Begin); data.Seek(getRelativeOffset(input), SeekOrigin.Begin); cgfxObjectEntry obj = new cgfxObjectEntry(); flags = input.ReadUInt32(); string msobMagic = IOUtils.readString(input, (uint)input.BaseStream.Position, 4); revision = input.ReadUInt32(); obj.name = IOUtils.readString(input, getRelativeOffset(input)); obj.userDataEntries = input.ReadUInt32(); obj.userDataDictionaryOffset = getRelativeOffset(input); obj.shapeIndex = input.ReadUInt32(); obj.materialId = input.ReadUInt32(); obj.ownerModelOffset = getRelativeOffset(input); obj.isVisible = (input.ReadByte() & 1) > 0; obj.renderPriority = input.ReadByte(); obj.objectNodeVisibilityIndex = input.ReadUInt16(); obj.currentPrimitiveIndex = input.ReadUInt16(); //Theres still a bunch of stuff after this, but isn't really needed objectHeader.Add(obj); } List<objectNode> objectNodeList = new List<objectNode>(); foreach (dictEntry objectNodeEntry in cmdlHeader.objectNodes) { objectNode node = new objectNode(); data.Seek(objectNodeEntry.dataOffset, SeekOrigin.Begin); node.name = IOUtils.readString(input, getRelativeOffset(input)); node.isVisible = input.ReadUInt32() == 1; objectNodeList.Add(node); } foreach (cgfxObjectEntry obj in objectHeader) { RenderBase.OMesh modelObject = shapes[(int)obj.shapeIndex]; if (objectNodeList.Count > 0) { modelObject.name = objectNodeList[obj.objectNodeVisibilityIndex].name ; modelObject.isVisible = objectNodeList[obj.objectNodeVisibilityIndex].isVisible; } modelObject.materialId = (ushort)obj.materialId; modelObject.renderPriority = obj.renderPriority; model.mesh.Add(modelObject); } models.model.Add(model); } data.Close(); return models; }
/// <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; }
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); }
/// <summary> /// Imports a Wavefront OBJ model from file. /// </summary> /// <param name="fileName">The complete file name</param> /// <returns></returns> public RenderBase.OModelGroup import(string fileName) { RenderBase.OModelGroup output = new RenderBase.OModelGroup(); RenderBase.OModel model = new RenderBase.OModel(); string obj = File.ReadAllText(fileName); List <RenderBase.OVector3> vertices = new List <RenderBase.OVector3>(); List <RenderBase.OVector3> normals = new List <RenderBase.OVector3>(); List <RenderBase.OVector2> uvs = new List <RenderBase.OVector2>(); List <RenderBase.OVertex> currVertices = new List <RenderBase.OVertex>(); string name = string.Empty, oldName; string[] lines = obj.Split((char)0xa); foreach (string l in lines) { string line = l.Trim(); string[] lineParams = Regex.Split(line, "\\s+"); switch (lineParams[0]) { case "v": case "vn": RenderBase.OVector3 pvec = new RenderBase.OVector3(); pvec.x = float.Parse(lineParams[1], CultureInfo.InvariantCulture); pvec.y = float.Parse(lineParams[2], CultureInfo.InvariantCulture); pvec.z = float.Parse(lineParams[3], CultureInfo.InvariantCulture); if (lineParams[0] == "v") { vertices.Add(pvec); } else { normals.Add(pvec); } break; case "vt": RenderBase.OVector2 tvec = new RenderBase.OVector2(); tvec.x = float.Parse(lineParams[1], CultureInfo.InvariantCulture); tvec.y = float.Parse(lineParams[2], CultureInfo.InvariantCulture); uvs.Add(tvec); break; case "f": string[][] vtx = new string[lineParams.Length - 1][]; for (int i = 0; i < lineParams.Length - 1; i++) { vtx[i] = lineParams[i + 1].Split('/'); } for (int i = 0; i < lineParams.Length - 1; i++) { RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.position = vertices[int.Parse(vtx[i][0]) - 1]; if (vtx[i].Length > 1 && vtx[i][1] != string.Empty) { vertex.texture0 = uvs[int.Parse(vtx[i][1]) - 1]; } if (vtx[i].Length > 2) { vertex.normal = normals[int.Parse(vtx[i][2]) - 1]; } vertex.diffuseColor = 0xffffffff; if (i > 2) { currVertices.Add(currVertices[currVertices.Count - 3]); currVertices.Add(currVertices[currVertices.Count - 2]); currVertices.Add(vertex); } else { currVertices.Add(vertex); } } break; case "g": oldName = name; if (lineParams.Length > 1) { name = lineParams[1]; } else { name = "mesh"; } if (currVertices.Count > 0) { RenderBase.OMesh mesh = new RenderBase.OMesh(); mesh.vertices = currVertices; mesh.name = oldName; mesh.hasNormal = true; mesh.texUVCount = 1; model.mesh.Add(mesh); currVertices = new List <RenderBase.OVertex>(); } break; } } if (currVertices.Count > 0) { RenderBase.OMesh mesh = new RenderBase.OMesh(); mesh.vertices = currVertices; mesh.name = name; mesh.hasNormal = true; mesh.texUVCount = 1; model.mesh.Add(mesh); } model.material.Add(new RenderBase.OMaterial()); output.model.Add(model); return(output); }
/// <summary> /// Imports a Source Model from file. /// </summary> /// <param name="fileName">The complete file name</param> /// <returns></returns> public static RenderBase.OModelGroup import(string fileName) { StreamReader reader = File.OpenText(fileName); RenderBase.OModelGroup model = new RenderBase.OModelGroup(); RenderBase.OModel mdl = new RenderBase.OModel(); mdl.name = Path.GetFileNameWithoutExtension(fileName); mdl.transform = new RenderBase.OMatrix(); List<smdNode> nodeList = new List<smdNode>(); while (reader.BaseStream.Position < reader.BaseStream.Length) { string line = readLine(reader); string[] parameters = Regex.Split(line, "\\s+"); switch (parameters[0]) { case "version": if (parameters.Length == 1) { MessageBox.Show( "Corrupted SMD file! The version isn't specified!", "SMD Importer", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } else if (parameters[1] != "1") { MessageBox.Show("Unknow SMD version!", "SMD Importer", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } break; case "nodes": line = readLine(reader); parameters = Regex.Split(line, "\\s+"); while (parameters[0] != "end") { if (parameters.Length == 3) { smdNode node = new smdNode(); node.index = int.Parse(parameters[0]); int nameStart = parameters[1].IndexOf("\"") + 1; node.name = parameters[1].Substring(nameStart, parameters[1].LastIndexOf("\"") - nameStart); node.parentId = int.Parse(parameters[2]); nodeList.Add(node); } line = readLine(reader); parameters = Regex.Split(line, "\\s+"); } break; case "skeleton": bool isReference = false; int timeIndex = -1; line = readLine(reader); parameters = Regex.Split(line, "\\s+"); RenderBase.OSkeletalAnimationBone[] boneArray = null; while (parameters[0] != "end") { if (parameters[0] == "time") { timeIndex = int.Parse(parameters[1]); } else { if (timeIndex > -1 && parameters.Length == 7) { int nodeIndex = int.Parse(parameters[0]); float translationX = float.Parse(parameters[1], CultureInfo.InvariantCulture); float translationY = float.Parse(parameters[2], CultureInfo.InvariantCulture); float translationZ = float.Parse(parameters[3], CultureInfo.InvariantCulture); float rotationX = float.Parse(parameters[4], CultureInfo.InvariantCulture); float rotationY = float.Parse(parameters[5], CultureInfo.InvariantCulture); float rotationZ = float.Parse(parameters[6], CultureInfo.InvariantCulture); if (timeIndex == 0) { RenderBase.OBone bone = new RenderBase.OBone(); foreach (smdNode node in nodeList) if (node.index == nodeIndex) { bone.name = node.name; bone.parentId = (short)node.parentId; } bone.translation = new RenderBase.OVector3(translationX, translationY, translationZ); bone.rotation = new RenderBase.OVector3(rotationX, rotationY, rotationZ); bone.scale = new RenderBase.OVector3(1, 1, 1); bone.absoluteScale = new RenderBase.OVector3(bone.scale); mdl.skeleton.Add(bone); } else { if (!isReference) { boneArray = new RenderBase.OSkeletalAnimationBone[mdl.skeleton.Count]; int index = 0; foreach (RenderBase.OBone b in mdl.skeleton) { RenderBase.OSkeletalAnimationBone bone = new RenderBase.OSkeletalAnimationBone(); bone.name = b.name; bone.translationX.exists = true; bone.translationY.exists = true; bone.translationZ.exists = true; bone.rotationX.exists = true; bone.rotationY.exists = true; bone.rotationZ.exists = true; //Translation bone.translationX.interpolation = bone.translationY.interpolation = bone.translationZ.interpolation = RenderBase.OInterpolationMode.linear; bone.translationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.translation.x, 0)); bone.translationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.translation.y, 0)); bone.translationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.translation.z, 0)); //Rotation bone.rotationX.interpolation = bone.rotationY.interpolation = bone.rotationZ.interpolation = RenderBase.OInterpolationMode.linear; bone.rotationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.rotation.x, 0)); bone.rotationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.rotation.y, 0)); bone.rotationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(b.rotation.z, 0)); boneArray[index++] = bone; } isReference = true; } boneArray[nodeIndex].translationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(translationX, timeIndex)); boneArray[nodeIndex].translationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(translationY, timeIndex)); boneArray[nodeIndex].translationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(translationZ, timeIndex)); boneArray[nodeIndex].rotationX.keyFrames.Add(new RenderBase.OAnimationKeyFrame(rotationX, timeIndex)); boneArray[nodeIndex].rotationY.keyFrames.Add(new RenderBase.OAnimationKeyFrame(rotationY, timeIndex)); boneArray[nodeIndex].rotationZ.keyFrames.Add(new RenderBase.OAnimationKeyFrame(rotationZ, timeIndex)); } } } line = readLine(reader); parameters = Regex.Split(line, "\\s+"); } if (isReference) { RenderBase.OSkeletalAnimation anim = new RenderBase.OSkeletalAnimation(); anim.frameSize = timeIndex; anim.name = Path.GetFileNameWithoutExtension(fileName); for (int i = 0; i < boneArray.Length; i++) { boneArray[i].translationX.endFrame = timeIndex; boneArray[i].translationY.endFrame = timeIndex; boneArray[i].translationZ.endFrame = timeIndex; boneArray[i].rotationX.endFrame = timeIndex; boneArray[i].rotationY.endFrame = timeIndex; boneArray[i].rotationZ.endFrame = timeIndex; } anim.bone.AddRange(boneArray); model.skeletalAnimation.list.Add(anim); } break; case "triangles": line = readLine(reader); parameters = Regex.Split(line, "\\s+"); RenderBase.OMesh obj = new RenderBase.OMesh(); int materialId = 0; string oldTexture = null; obj.hasNormal = true; int count = 0; while (parameters[0] != "end") { if (count == 0) { string texture = parameters[0]; if (texture != oldTexture && oldTexture != null) { mdl.material.Add(getMaterial(oldTexture)); obj.materialId = (ushort)materialId++; mdl.mesh.Add(obj); obj = new RenderBase.OMesh(); } oldTexture = texture; } else { if (parameters.Length >= 10) { int parentBone; float x, y, z; float nx, ny, nz; float u, v; int joints; parentBone = int.Parse(parameters[0]); x = float.Parse(parameters[1], CultureInfo.InvariantCulture); y = float.Parse(parameters[2], CultureInfo.InvariantCulture); z = float.Parse(parameters[3], CultureInfo.InvariantCulture); nx = float.Parse(parameters[4], CultureInfo.InvariantCulture); ny = float.Parse(parameters[5], CultureInfo.InvariantCulture); nz = float.Parse(parameters[6], CultureInfo.InvariantCulture); u = float.Parse(parameters[7], CultureInfo.InvariantCulture); v = float.Parse(parameters[8], CultureInfo.InvariantCulture); joints = int.Parse(parameters[9]); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; int j = 10; for (int i = 0; i < joints; i++) { int joint = int.Parse(parameters[j++]); float weight = float.Parse(parameters[j++], CultureInfo.InvariantCulture); vertex.node.Add(joint); vertex.weight.Add(weight); obj.hasNode = true; obj.hasWeight = true; } vertex.position = new RenderBase.OVector3(x, y, z); vertex.normal = new RenderBase.OVector3(nx, ny, nz); vertex.texture0 = new RenderBase.OVector2(u, v); obj.vertices.Add(vertex); } } line = readLine(reader); parameters = Regex.Split(line, "\\s+"); count = (count + 1) % 4; } //Add the last object mdl.material.Add(getMaterial(oldTexture)); obj.materialId = (ushort)materialId++; mdl.mesh.Add(obj); break; } } model.model.Add(mdl); return model; }
/// <summary> /// Loads a SMES file. /// Note that SMES must start at offset 0x0. /// </summary> /// <param name="data">Stream of the SMES file.</param> /// <returns></returns> public static void load(Stream data, RenderBase.OModel model, bool ignoreMaterial = false) { BinaryReader input = new BinaryReader(data); //The node indices points to the index directly relative to the tree index on the Skeleton //Therefore, we must build a table to translate from the Skeleton Index to the absolute Bone Tree Index List<int> nodeBinding = new List<int>(); buildNodeBinding(model.skeleton, 0, ref nodeBinding); string smesMagic = IOUtils.readStringWithLength(input, 4); uint dataTableOffset = input.ReadUInt32(); uint meshCount = input.ReadUInt32(); for (int meshIndex = 0; meshIndex < meshCount; meshIndex++) { //Mesh descriptor data.Seek(0xc + meshIndex * 0x20, SeekOrigin.Begin); uint vector3TableEntries = input.ReadUInt32(); uint vector3TableOffset = input.ReadUInt32(); uint attributesCount = input.ReadUInt32(); uint primitiveCount = input.ReadUInt32(); uint vertexBufferOffset = input.ReadUInt32(); uint indexSectionOffset = input.ReadUInt32(); uint renderPriority = input.ReadUInt32(); uint materialIndex = input.ReadUInt32(); //Index Buffer section data.Seek(indexSectionOffset, SeekOrigin.Begin); RenderBase.OMesh obj = new RenderBase.OMesh(); if (!ignoreMaterial) obj.materialId = (ushort)materialIndex; obj.isVisible = true; obj.hasNormal = true; obj.hasTangent = true; obj.texUVCount = 1; ushort[] nodes = null; uint skinningMode = 0; bool hasData = true; while (hasData) { string magic = IOUtils.readStringWithLength(input, 4); uint sectionLength = input.ReadUInt32(); long startOffset = data.Position; switch (magic) { case "SKIN": skinningMode = input.ReadUInt32(); break; //0 = rigid, 1 = smooth case "BONI": nodes = new ushort[input.ReadUInt16()]; for (int n = 0; n < nodes.Length; n++) nodes[n] = input.ReadUInt16(); obj.hasNode = true; obj.hasWeight = true; break; case "IDX ": input.ReadUInt16(); ushort indicesCount = input.ReadUInt16(); //Note: The Stride seems to always be 0x40 uint vertexStride = (indexSectionOffset - vertexBufferOffset) / primitiveCount; for (int i = 0; i < indicesCount; i++) { ushort index = input.ReadUInt16(); long position = data.Position; data.Seek(vertexBufferOffset + index * vertexStride, SeekOrigin.Begin); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; vertex.position = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); vertex.normal = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); vertex.tangent = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); vertex.texture0 = new RenderBase.OVector2(input.ReadSingle(), input.ReadSingle()); if (model.skeleton.Count > 0) { if (nodes != null && nodes.Length > 0) { byte b0 = input.ReadByte(); byte b1 = input.ReadByte(); byte b2 = input.ReadByte(); byte b3 = input.ReadByte(); if (b0 < nodes.Length) vertex.node.Add(nodeBinding[nodes[b0]]); if (skinningMode > 0) { if (b1 < nodes.Length) vertex.node.Add(nodeBinding[nodes[b1]]); if (b2 < nodes.Length) vertex.node.Add(nodeBinding[nodes[b2]]); if (b3 < nodes.Length) vertex.node.Add(nodeBinding[nodes[b3]]); } } vertex.weight.Add(input.ReadSingle()); if (skinningMode > 0) { vertex.weight.Add(input.ReadSingle()); vertex.weight.Add(input.ReadSingle()); vertex.weight.Add(input.ReadSingle()); } } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); data.Seek(position, SeekOrigin.Begin); } break; default: hasData = false; break; } data.Seek(startOffset + sectionLength, SeekOrigin.Begin); } model.mesh.Add(obj); } data.Close(); }
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; }
private mesh createMesh(RenderBase.OMesh input) { mesh output; output.descriptor.nodes = new List <uint>(); output.descriptor.attributes = new List <attributeDescriptor>(); output.descriptor.attributes.Add(new attributeDescriptor(0, 0, 1f)); //Position if (input.hasNormal) { output.descriptor.attributes.Add(new attributeDescriptor(1, 0, 1f)); } if (input.texUVCount > 0) { output.descriptor.attributes.Add(new attributeDescriptor(3, 0, 1f)); } if (input.hasNode) { output.descriptor.attributes.Add(new attributeDescriptor(5, 1, 1f)); } if (input.hasWeight) { output.descriptor.attributes.Add(new attributeDescriptor(6, 1, 0.00392156862f)); } MeshUtils.optimizedMesh optimized = MeshUtils.optimizeMesh(input); using (MemoryStream vertexStream = new MemoryStream()) { BinaryWriter writer = new BinaryWriter(vertexStream); foreach (RenderBase.OVertex vtx in optimized.vertices) { writer.Write(vtx.position.x); writer.Write(vtx.position.y); writer.Write(vtx.position.z); if (optimized.hasNormal) { writer.Write(vtx.normal.x); writer.Write(vtx.normal.y); writer.Write(vtx.normal.z); } if (optimized.texUVCount > 0) { writer.Write(vtx.texture0.x); writer.Write(vtx.texture0.y); } if (optimized.hasNode) { for (int i = 0; i < 2; i++) { if (i < vtx.node.Count) { int nodeIndex = output.descriptor.nodes.IndexOf((uint)vtx.node[i]); if (nodeIndex == -1) { writer.Write((byte)output.descriptor.nodes.Count); output.descriptor.nodes.Add((uint)vtx.node[i]); } else { writer.Write((byte)nodeIndex); } } else { writer.Write((byte)0); } } } if (optimized.hasWeight) { for (int i = 0; i < 2; i++) { if (i < vtx.weight.Count) { writer.Write((byte)(vtx.weight[i] * byte.MaxValue)); } else { writer.Write((byte)0); } } } } output.vertexBuffer = vertexStream.ToArray(); } using (MemoryStream indexStream = new MemoryStream()) { BinaryWriter writer = new BinaryWriter(indexStream); foreach (uint index in optimized.indices) { writer.Write((ushort)index); } output.indexBuffer = indexStream.ToArray(); } 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); }
/// <summary> /// Loads a Fantasy Life ZMDL model. /// Note that ZMDL must start at offset 0x0 (don't try using it for ZMDLs inside containers). /// </summary> /// <param name="data">Stream of the ZMDL file.</param> /// <returns></returns> public static RenderBase.OModelGroup load(Stream data) { BinaryReader input = new BinaryReader(data); RenderBase.OModelGroup models = new RenderBase.OModelGroup(); RenderBase.OModel model = new RenderBase.OModel(); model.name = "model"; string zmdlMagic = IOUtils.readString(input, 0, 4); data.Seek(0x20, SeekOrigin.Begin); uint materialsOffset = input.ReadUInt32(); uint skeletonOffset = input.ReadUInt32(); uint modelOffset = input.ReadUInt32(); ushort materialsCount = input.ReadUInt16(); ushort bonesCount = input.ReadUInt16(); ushort modelObjectsCount = input.ReadUInt16(); ushort unknowCount = input.ReadUInt16(); //Materials List <byte> materialObjectBinding = new List <byte>(); for (int materialIndex = 0; materialIndex < materialsCount; materialIndex++) { RenderBase.OMaterial material = new RenderBase.OMaterial(); material.name = IOUtils.readString(input, (uint)(materialsOffset + materialIndex * 0xb4)); data.Seek(materialsOffset + (materialIndex * 0xb4) + 0x94, SeekOrigin.Begin); uint textureReferenceOffset = input.ReadUInt32(); uint objectReferenceIndexOffset = input.ReadUInt32(); data.Seek(objectReferenceIndexOffset, SeekOrigin.Begin); materialObjectBinding.Add(input.ReadByte()); material.name0 = IOUtils.readString(input, textureReferenceOffset); data.Seek(textureReferenceOffset + 0x40, SeekOrigin.Begin); while ((data.Position & 3) != 0) { input.ReadByte(); //Align Word } data.Seek(0x30, SeekOrigin.Current); //Unknown matrix (possibly UV transform) input.ReadUInt32(); input.ReadByte(); input.ReadByte(); byte wrap = input.ReadByte(); input.ReadByte(); model.material.Add(material); } //Skeleton for (int boneIndex = 0; boneIndex < bonesCount; boneIndex++) { RenderBase.OBone bone = new RenderBase.OBone(); bone.name = IOUtils.readString(input, (uint)(skeletonOffset + boneIndex * 0xcc)); data.Seek(skeletonOffset + (boneIndex * 0xcc) + 0x40, SeekOrigin.Begin); data.Seek(0x64, SeekOrigin.Current); //Unknow matrices, probably transform and other stuff bone.translation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.rotation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.scale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.absoluteScale = new RenderBase.OVector3(bone.scale); bone.parentId = input.ReadInt16(); input.ReadUInt16(); model.skeleton.Add(bone); } //Meshes for (int objIndex = 0; objIndex < modelObjectsCount; objIndex++) { RenderBase.OMesh obj = new RenderBase.OMesh(); obj.name = string.Format("mesh_{0}", objIndex); data.Seek(modelOffset + objIndex * 0xc4, SeekOrigin.Begin); attributeEntry[] attributes = new attributeEntry[9]; for (int attribute = 0; attribute < 9; attribute++) { attributes[attribute].floats = new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); attributes[attribute].offset = input.ReadByte() * 4; attributes[attribute].attributeLength = input.ReadByte(); attributes[attribute].stride = input.ReadUInt16() * 4; if (attributes[attribute].attributeLength > 0) { switch (attribute) { case aNormal: obj.hasNormal = true; break; case aColor: obj.hasColor = true; break; case aTex0: obj.texUVCount = 1; break; case aNode: obj.hasNode = true; break; case aWeight: obj.hasWeight = true; break; } } } int vertexStride = attributes[8].stride; uint facesHeaderOffset = input.ReadUInt32(); uint facesHeaderEntries = input.ReadUInt32(); uint vertexBufferOffset = input.ReadUInt32(); uint vertexBufferLength = input.ReadUInt32() * 4; for (int faceIndex = 0; faceIndex < facesHeaderEntries; faceIndex++) { data.Seek(facesHeaderOffset + faceIndex * 0x14, SeekOrigin.Begin); uint boneNodesOffset = input.ReadUInt32(); uint boneNodesEntries = input.ReadUInt32(); uint indexBufferOffset = input.ReadUInt32(); uint indexBufferPrimitiveCount = input.ReadUInt32(); input.ReadUInt32(); data.Seek(boneNodesOffset, SeekOrigin.Begin); List <byte> nodeList = new List <byte>(); for (int n = 0; n < boneNodesEntries; n++) { nodeList.Add(input.ReadByte()); } data.Seek(indexBufferOffset, SeekOrigin.Begin); for (int face = 0; face < indexBufferPrimitiveCount; face++) { ushort index = input.ReadUInt16(); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; long position = data.Position; long vertexOffset = vertexBufferOffset + index * vertexStride; data.Seek(vertexOffset + attributes[aPosition].offset, SeekOrigin.Begin); vertex.position = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); if (attributes[aNormal].attributeLength > 0) { data.Seek(vertexOffset + attributes[aNormal].offset, SeekOrigin.Begin); vertex.normal = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); } if (attributes[aColor].attributeLength > 0) { data.Seek(vertexOffset + attributes[aColor].offset, SeekOrigin.Begin); uint r = MeshUtils.saturate(input.ReadSingle() * 0xff); uint g = MeshUtils.saturate(input.ReadSingle() * 0xff); uint b = MeshUtils.saturate(input.ReadSingle() * 0xff); uint a = MeshUtils.saturate(input.ReadSingle() * 0xff); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); } if (attributes[aTex0].attributeLength > 0) { data.Seek(vertexOffset + attributes[aTex0].offset, SeekOrigin.Begin); vertex.texture0 = new RenderBase.OVector2(input.ReadSingle(), input.ReadSingle()); } for (int boneIndex = 0; boneIndex < attributes[aNode].attributeLength; boneIndex++) { data.Seek(vertexOffset + attributes[aNode].offset + (boneIndex * 4), SeekOrigin.Begin); vertex.node.Add(nodeList[(int)input.ReadSingle()]); } for (int boneWeight = 0; boneWeight < attributes[aWeight].attributeLength; boneWeight++) { data.Seek(vertexOffset + attributes[aWeight].offset + (boneWeight * 4), SeekOrigin.Begin); vertex.weight.Add(input.ReadSingle()); } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); data.Seek(position, SeekOrigin.Begin); } } int materialId = materialObjectBinding.IndexOf((byte)objIndex); if (materialId > -1) { obj.materialId = (ushort)materialId; } model.mesh.Add(obj); } data.Close(); models.model.Add(model); return(models); }
/// <summary> /// Loads a Fantasy Life ZMDL model. /// Note that ZMDL must start at offset 0x0 (don't try using it for ZMDLs inside containers). /// </summary> /// <param name="data">Stream of the ZMDL file.</param> /// <returns></returns> public static RenderBase.OModelGroup load(Stream data) { BinaryReader input = new BinaryReader(data); RenderBase.OModelGroup models = new RenderBase.OModelGroup(); RenderBase.OModel model = new RenderBase.OModel(); model.name = "model"; string zmdlMagic = IOUtils.readString(input, 0, 4); data.Seek(0x20, SeekOrigin.Begin); uint materialsOffset = input.ReadUInt32(); uint skeletonOffset = input.ReadUInt32(); uint modelOffset = input.ReadUInt32(); ushort materialsCount = input.ReadUInt16(); ushort bonesCount = input.ReadUInt16(); ushort modelObjectsCount = input.ReadUInt16(); ushort unknowCount = input.ReadUInt16(); //Materials List<byte> materialObjectBinding = new List<byte>(); for (int materialIndex = 0; materialIndex < materialsCount; materialIndex++) { RenderBase.OMaterial material = new RenderBase.OMaterial(); material.name = IOUtils.readString(input, (uint)(materialsOffset + materialIndex * 0xb4)); data.Seek(materialsOffset + (materialIndex * 0xb4) + 0x94, SeekOrigin.Begin); uint textureReferenceOffset = input.ReadUInt32(); uint objectReferenceIndexOffset = input.ReadUInt32(); data.Seek(objectReferenceIndexOffset, SeekOrigin.Begin); materialObjectBinding.Add(input.ReadByte()); material.name0 = IOUtils.readString(input, textureReferenceOffset); data.Seek(textureReferenceOffset + 0x40, SeekOrigin.Begin); while ((data.Position & 3) != 0) input.ReadByte(); //Align Word data.Seek(0x30, SeekOrigin.Current); //Unknown matrix (possibly UV transform) input.ReadUInt32(); input.ReadByte(); input.ReadByte(); byte wrap = input.ReadByte(); input.ReadByte(); model.material.Add(material); } //Skeleton for (int boneIndex = 0; boneIndex < bonesCount; boneIndex++) { RenderBase.OBone bone = new RenderBase.OBone(); bone.name = IOUtils.readString(input, (uint)(skeletonOffset + boneIndex * 0xcc)); data.Seek(skeletonOffset + (boneIndex * 0xcc) + 0x40, SeekOrigin.Begin); data.Seek(0x64, SeekOrigin.Current); //Unknow matrices, probably transform and other stuff bone.translation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.rotation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.scale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.absoluteScale = new RenderBase.OVector3(bone.scale); bone.parentId = input.ReadInt16(); input.ReadUInt16(); model.skeleton.Add(bone); } //Meshes for (int objIndex = 0; objIndex < modelObjectsCount; objIndex++) { RenderBase.OMesh obj = new RenderBase.OMesh(); obj.name = string.Format("mesh_{0}", objIndex); data.Seek(modelOffset + objIndex * 0xc4, SeekOrigin.Begin); attributeEntry[] attributes = new attributeEntry[9]; for (int attribute = 0; attribute < 9; attribute++) { attributes[attribute].floats = new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); attributes[attribute].offset = input.ReadByte() * 4; attributes[attribute].attributeLength = input.ReadByte(); attributes[attribute].stride = input.ReadUInt16() * 4; if (attributes[attribute].attributeLength > 0) { switch (attribute) { case aNormal: obj.hasNormal = true; break; case aColor: obj.hasColor = true; break; case aTex0: obj.texUVCount = 1; break; case aNode: obj.hasNode = true; break; case aWeight: obj.hasWeight = true; break; } } } int vertexStride = attributes[8].stride; uint facesHeaderOffset = input.ReadUInt32(); uint facesHeaderEntries = input.ReadUInt32(); uint vertexBufferOffset = input.ReadUInt32(); uint vertexBufferLength = input.ReadUInt32() * 4; for (int faceIndex = 0; faceIndex < facesHeaderEntries; faceIndex++) { data.Seek(facesHeaderOffset + faceIndex * 0x14, SeekOrigin.Begin); uint boneNodesOffset = input.ReadUInt32(); uint boneNodesEntries = input.ReadUInt32(); uint indexBufferOffset = input.ReadUInt32(); uint indexBufferPrimitiveCount = input.ReadUInt32(); input.ReadUInt32(); data.Seek(boneNodesOffset, SeekOrigin.Begin); List<byte> nodeList = new List<byte>(); for (int n = 0; n < boneNodesEntries; n++) nodeList.Add(input.ReadByte()); data.Seek(indexBufferOffset, SeekOrigin.Begin); for (int face = 0; face < indexBufferPrimitiveCount; face++) { ushort index = input.ReadUInt16(); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; long position = data.Position; long vertexOffset = vertexBufferOffset + index * vertexStride; data.Seek(vertexOffset + attributes[aPosition].offset, SeekOrigin.Begin); vertex.position = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); if (attributes[aNormal].attributeLength > 0) { data.Seek(vertexOffset + attributes[aNormal].offset, SeekOrigin.Begin); vertex.normal = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); } if (attributes[aColor].attributeLength > 0) { data.Seek(vertexOffset + attributes[aColor].offset, SeekOrigin.Begin); uint r = MeshUtils.saturate(input.ReadSingle() * 0xff); uint g = MeshUtils.saturate(input.ReadSingle() * 0xff); uint b = MeshUtils.saturate(input.ReadSingle() * 0xff); uint a = MeshUtils.saturate(input.ReadSingle() * 0xff); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); } if (attributes[aTex0].attributeLength > 0) { data.Seek(vertexOffset + attributes[aTex0].offset, SeekOrigin.Begin); vertex.texture0 = new RenderBase.OVector2(input.ReadSingle(), input.ReadSingle()); } for (int boneIndex = 0; boneIndex < attributes[aNode].attributeLength; boneIndex++) { data.Seek(vertexOffset + attributes[aNode].offset + (boneIndex * 4), SeekOrigin.Begin); vertex.node.Add(nodeList[(int)input.ReadSingle()]); } for (int boneWeight = 0; boneWeight < attributes[aWeight].attributeLength; boneWeight++) { data.Seek(vertexOffset + attributes[aWeight].offset + (boneWeight * 4), SeekOrigin.Begin); vertex.weight.Add(input.ReadSingle()); } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); data.Seek(position, SeekOrigin.Begin); } } int materialId = materialObjectBinding.IndexOf((byte)objIndex); if (materialId > -1) obj.materialId = (ushort)materialId; model.mesh.Add(obj); } data.Close(); models.model.Add(model); return models; }
/// <summary> /// Loads a SMES file. /// Note that SMES must start at offset 0x0. /// </summary> /// <param name="data">Stream of the SMES file.</param> /// <returns></returns> public static void load(Stream data, RenderBase.OModel model, bool ignoreMaterial = false) { BinaryReader input = new BinaryReader(data); //The node indices points to the index directly relative to the tree index on the Skeleton //Therefore, we must build a table to translate from the Skeleton Index to the absolute Bone Tree Index List <int> nodeBinding = new List <int>(); buildNodeBinding(model.skeleton, 0, ref nodeBinding); string smesMagic = IOUtils.readStringWithLength(input, 4); uint dataTableOffset = input.ReadUInt32(); uint meshCount = input.ReadUInt32(); for (int meshIndex = 0; meshIndex < meshCount; meshIndex++) { //Mesh descriptor data.Seek(0xc + meshIndex * 0x20, SeekOrigin.Begin); uint vector3TableEntries = input.ReadUInt32(); uint vector3TableOffset = input.ReadUInt32(); uint attributesCount = input.ReadUInt32(); uint primitiveCount = input.ReadUInt32(); uint vertexBufferOffset = input.ReadUInt32(); uint indexSectionOffset = input.ReadUInt32(); uint renderPriority = input.ReadUInt32(); uint materialIndex = input.ReadUInt32(); //Index Buffer section data.Seek(indexSectionOffset, SeekOrigin.Begin); RenderBase.OMesh obj = new RenderBase.OMesh(); if (!ignoreMaterial) { obj.materialId = (ushort)materialIndex; } obj.isVisible = true; obj.hasNormal = true; obj.hasTangent = true; obj.texUVCount = 1; ushort[] nodes = null; uint skinningMode = 0; bool hasData = true; while (hasData) { string magic = IOUtils.readStringWithLength(input, 4); uint sectionLength = input.ReadUInt32(); long startOffset = data.Position; switch (magic) { case "SKIN": skinningMode = input.ReadUInt32(); break; //0 = rigid, 1 = smooth case "BONI": nodes = new ushort[input.ReadUInt16()]; for (int n = 0; n < nodes.Length; n++) { nodes[n] = input.ReadUInt16(); } obj.hasNode = true; obj.hasWeight = true; break; case "IDX ": input.ReadUInt16(); ushort indicesCount = input.ReadUInt16(); //Note: The Stride seems to always be 0x40 uint vertexStride = (indexSectionOffset - vertexBufferOffset) / primitiveCount; for (int i = 0; i < indicesCount; i++) { ushort index = input.ReadUInt16(); long position = data.Position; data.Seek(vertexBufferOffset + index * vertexStride, SeekOrigin.Begin); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; vertex.position = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); vertex.normal = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); vertex.tangent = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); vertex.texture0 = new RenderBase.OVector2(input.ReadSingle(), input.ReadSingle()); if (model.skeleton.Count > 0) { if (nodes != null && nodes.Length > 0) { byte b0 = input.ReadByte(); byte b1 = input.ReadByte(); byte b2 = input.ReadByte(); byte b3 = input.ReadByte(); if (b0 < nodes.Length) { vertex.node.Add(nodeBinding[nodes[b0]]); } if (skinningMode > 0) { if (b1 < nodes.Length) { vertex.node.Add(nodeBinding[nodes[b1]]); } if (b2 < nodes.Length) { vertex.node.Add(nodeBinding[nodes[b2]]); } if (b3 < nodes.Length) { vertex.node.Add(nodeBinding[nodes[b3]]); } } } vertex.weight.Add(input.ReadSingle()); if (skinningMode > 0) { vertex.weight.Add(input.ReadSingle()); vertex.weight.Add(input.ReadSingle()); vertex.weight.Add(input.ReadSingle()); } } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); data.Seek(position, SeekOrigin.Begin); } break; default: hasData = false; break; } data.Seek(startOffset + sectionLength, SeekOrigin.Begin); } model.mesh.Add(obj); } data.Close(); }
/// <summary> /// Imports a Wavefront OBJ model from file. /// </summary> /// <param name="fileName">The complete file name</param> /// <returns></returns> public static RenderBase.OModelGroup import(string fileName) { RenderBase.OModelGroup output = new RenderBase.OModelGroup(); RenderBase.OModel model = new RenderBase.OModel(); string obj = File.ReadAllText(fileName); List<RenderBase.OVector3> vertices = new List<RenderBase.OVector3>(); List<RenderBase.OVector3> normals = new List<RenderBase.OVector3>(); List<RenderBase.OVector2> uvs = new List<RenderBase.OVector2>(); List<RenderBase.OVertex> currVertices = new List<RenderBase.OVertex>(); string name = string.Empty, oldName; string[] lines = obj.Split((char)0xa); foreach (string l in lines) { string line = l.Trim(); string[] lineParams = Regex.Split(line, "\\s+"); switch (lineParams[0]) { case "v": case "vn": RenderBase.OVector3 pvec = new RenderBase.OVector3(); pvec.x = float.Parse(lineParams[1], CultureInfo.InvariantCulture); pvec.y = float.Parse(lineParams[2], CultureInfo.InvariantCulture); pvec.z = float.Parse(lineParams[3], CultureInfo.InvariantCulture); if (lineParams[0] == "v") vertices.Add(pvec); else normals.Add(pvec); break; case "vt": RenderBase.OVector2 tvec = new RenderBase.OVector2(); tvec.x = float.Parse(lineParams[1], CultureInfo.InvariantCulture); tvec.y = float.Parse(lineParams[2], CultureInfo.InvariantCulture); uvs.Add(tvec); break; case "f": string[][] vtx = new string[lineParams.Length - 1][]; for (int i = 0; i < lineParams.Length - 1; i++) { vtx[i] = lineParams[i + 1].Split('/'); } for (int i = 0; i < lineParams.Length - 1; i++) { RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.position = vertices[int.Parse(vtx[i][0]) - 1]; if (vtx[i].Length > 1 && vtx[i][1] != string.Empty) vertex.texture0 = uvs[int.Parse(vtx[i][1]) - 1]; if (vtx[i].Length > 2) vertex.normal = normals[int.Parse(vtx[i][2]) - 1]; vertex.diffuseColor = 0xffffffff; if (i > 2) { currVertices.Add(currVertices[currVertices.Count - 3]); currVertices.Add(currVertices[currVertices.Count - 2]); currVertices.Add(vertex); } else currVertices.Add(vertex); } break; case "g": oldName = name; if (lineParams.Length > 1) name = lineParams[1]; else name = "mesh"; if (currVertices.Count > 0) { RenderBase.OMesh mesh = new RenderBase.OMesh(); mesh.vertices = currVertices; mesh.name = oldName; mesh.hasNormal = true; mesh.texUVCount = 1; model.mesh.Add(mesh); currVertices = new List<RenderBase.OVertex>(); } break; } } if (currVertices.Count > 0) { RenderBase.OMesh mesh = new RenderBase.OMesh(); mesh.vertices = currVertices; mesh.name = name; mesh.hasNormal = true; mesh.texUVCount = 1; model.mesh.Add(mesh); } model.material.Add(new RenderBase.OMaterial()); output.model.Add(model); return output; }