/// <summary> /// Gets the name of the first model on a CGFX file. /// Returns null if the file doesn't contain any model. /// </summary> /// <param name="data"></param> /// <returns></returns> public static string getName(MemoryStream data) { BinaryReader input = new BinaryReader(data); 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 + 8, SeekOrigin.Begin); List<dictEntry> models = getDictionary(input); if (models.Count > 0) { data.Seek(models[0].dataOffset + 0xc, SeekOrigin.Begin); string name = IOUtils.readString(input, getRelativeOffset(input)); data.Close(); return name; } else { data.Close(); return null; } }
/// <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; }