/// <summary> /// Creates a new geometry animation /// </summary> /// <param name="frame">Start frame</param> /// <param name="step">Animation speed</param> /// <param name="maxFrame"></param> /// <param name="model"></param> /// <param name="motion"></param> /// <param name="texListPtr"></param> public LandEntryMotion(float frame, float step, float maxFrame, NJObject model, Action action, uint texListPtr) { Frame = frame; Step = step; MaxFrame = maxFrame; Model = model; MotionAction = action; TexListPtr = texListPtr; }
private static void PrepareModel( this NJObject obj, List <RenderMesh> opaque, List <RenderMesh> transparent, BufferingBridge buffer, Camera cam, NJObject activeObj, Matrix4?parentWorld, bool weighted) { Matrix4 world = obj.LocalMatrix; if (parentWorld.HasValue) { world *= parentWorld.Value; } if (obj.Attach != null && obj.Attach.MeshData.Length > 0) { // if a model is weighted, then the buffered vertex positions/normals will have to be set to world space, which means that world and normal matrix should be identities if (weighted) { buffer.LoadToCache(obj.Attach.MeshData, world, obj == activeObj); } else if (!buffer.IsBuffered(obj.Attach.MeshData[0])) { buffer.LoadToCache(obj.Attach.MeshData, null, false); } RenderMatrices matrices = weighted ? new(cam.ViewMatrix * cam.ProjectionMatrix) : new(world, world *cam.ViewMatrix *cam.ProjectionMatrix); var meshes = obj.Attach.GetDisplayMeshes(); if (meshes.opaque.Length > 0) { opaque.Add(new RenderMesh(meshes.opaque, matrices)); } if (meshes.transparent.Length > 0) { transparent.Add(new RenderMesh(meshes.transparent, matrices)); } } for (int i = 0; i < obj.ChildCount; i++) { obj[i].PrepareModel(opaque, transparent, buffer, cam, activeObj, world, weighted); } }
/// <summary> /// Reads a geometry animation from a byte array /// </summary> /// <param name="source">Byte source</param> /// <param name="address">Address at which the geometry animation is located</param> /// <param name="imageBase">Image base for all addresses</param> /// <param name="format">Attach format</param> /// <param name="DX">Whether the animation is for sadx</param> /// <param name="labels">C struct labels</param> /// <param name="models">Models that have already been read</param> /// <param name="attaches">Attaches that have already been read</param> /// <returns></returns> public static LandEntryMotion Read(byte[] source, uint address, uint imageBase, AttachFormat format, bool DX, Dictionary <uint, string> labels, Dictionary <uint, Attach> attaches) { float frame = source.ToSingle(address); float step = source.ToSingle(address + 4); float maxFrame = source.ToSingle(address + 8); uint modelAddress = source.ToUInt32(address + 0xC) - imageBase; NJObject model = NJObject.Read(source, modelAddress, imageBase, format, DX, labels, attaches); uint motionAddress = source.ToUInt32(address + 0x10) - imageBase; Action action = Action.Read(source, motionAddress, imageBase, format, DX, labels, attaches); uint texListPtr = source.ToUInt32(address + 0x14); return(new LandEntryMotion(frame, step, maxFrame, model, action, texListPtr)); }
internal static void GetModelLine(NJObject obj, List <Vector3> lines, Matrix4?parentWorld) { Matrix4 world = obj.LocalMatrix; if (parentWorld.HasValue) { world *= parentWorld.Value; lines.Add(Vector3.Transform(Vector3.Zero, parentWorld.Value)); lines.Add(Vector3.Transform(Vector3.Zero, world)); } for (int i = 0; i < obj.ChildCount; i++) { GetModelLine(obj[i], lines, world); } }
/// <summary> /// Reads an action from a byte array /// </summary> /// <param name="source">Byte source</param> /// <param name="address">Address at which the action is located</param> /// <param name="imagebase">Image base for all addresses</param> /// <param name="format">Attach format</param> /// <param name="DX">Whether the file is for sadx</param> /// <param name="labels">C struct labels</param> /// <param name="attaches">Attaches that have already been read</param> /// <returns></returns> public static Action Read(byte[] source, uint address, uint imagebase, AttachFormat format, bool DX, Dictionary <uint, string> labels, Dictionary <uint, Attach> attaches) { uint mdlAddress = source.ToUInt32(address); if (mdlAddress == 0) { throw new FormatException($"Action at {address:X8} does not have a model!"); } mdlAddress -= imagebase; NJObject mdl = NJObject.Read(source, mdlAddress, imagebase, format, DX, labels, attaches); uint aniAddress = source.ToUInt32(address + 4); if (aniAddress == 0) { throw new FormatException($"Action at {address:X8} does not have a model!"); } aniAddress -= imagebase; Motion mtn = Motion.Read(source, ref aniAddress, imagebase, (uint)mdl.Count(), labels); return(new(mdl, mtn)); }
/// <summary> /// Creates a new geometry animation /// </summary> /// <param name="frame">Start frame</param> /// <param name="step">Animation speed</param> public LandEntryMotion(float frame, float step, float maxFrame, NJObject model, Motion motion, uint texListPtr) : this(frame, step, maxFrame, model, new Action(model, motion), texListPtr) { }
/// <summary> /// Create a new action /// </summary> /// <param name="model"></param> /// <param name="animation"></param> public Action(NJObject model, Motion animation) { Model = model; Animation = animation; }
/// <summary> /// Converts a buffer model to the GC format /// </summary> /// <param name="model">The model to converter</param> /// <param name="optimize">Whether to optimize the attaches</param> /// <param name="ignoreWeights">If conversion should still happen, despite weights existing</param> /// <param name="forceUpdate">Whether to convert, regardless of whether the attaches are already GC</param> public static void ConvertModelToGC(NJObject model, bool optimize = true, bool ignoreWeights = false, bool forceUpdate = false) { if (model.Parent != null) { throw new FormatException($"Model {model.Name} is not hierarchy root!"); } if (model.AttachFormat == AttachFormat.GC && !forceUpdate) { return; } if (model.HasWeight && !ignoreWeights) { throw new FormatException("Model is weighted, cannot convert to basic format!"); } AttachHelper.ProcessWeightlessModel(model, (cacheAtc, ogAtc) => { // getting the vertex information Vector3[] positions = new Vector3[cacheAtc.vertices.Length]; Vector3[] normals = new Vector3[positions.Length]; for (int i = 0; i < positions.Length; i++) { var vtx = cacheAtc.vertices[i]; positions[i] = vtx.Position; normals[i] = vtx.Normal; } // getting the corner information int cornerCount = 0; for (int i = 0; i < cacheAtc.corners.Length; i++) { cornerCount += cacheAtc.corners[i].Length; } Vector2[] texcoords = new Vector2[cornerCount]; Color[] colors = new Color[cornerCount]; Corner[][] corners = new Corner[cacheAtc.corners.Length][]; ushort cornerIndex = 0; for (int i = 0; i < corners.Length; i++) { BufferCorner[] bufferCorners = cacheAtc.corners[i]; Corner[] meshCorners = new Corner[bufferCorners.Length]; for (int j = 0; j < bufferCorners.Length; j++) { BufferCorner bcorner = bufferCorners[j]; texcoords[cornerIndex] = bcorner.Texcoord; colors[cornerIndex] = bcorner.Color; meshCorners[j] = new Corner() { PositionIndex = bcorner.VertexIndex, NormalIndex = bcorner.VertexIndex, UV0Index = cornerIndex, Color0Index = cornerIndex }; cornerIndex++; } corners[i] = meshCorners; } bool hasUVs = texcoords.Any(x => x != default); // if it has no normals, always use colors (even if they are all white) bool hasColors = colors.Any(x => x != Color.White) || !normals.Any(x => x != Vector3.UnitY); // Puttin together the vertex sets VertexSet[] vertexData = new VertexSet[2 + (hasUVs ? 1 : 0)]; IndexAttributeParameter iaParam = new() { IndexAttributes = IndexAttributes.HasPosition }; if (positions.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.Position16BitIndex; } vertexData[0] = new VertexSet(positions, false); if (hasColors) { iaParam.IndexAttributes |= IndexAttributes.HasColor; if (colors.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.Color16BitIndex; } vertexData[1] = new VertexSet(colors); } else { iaParam.IndexAttributes |= IndexAttributes.HasNormal; if (normals.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.Normal16BitIndex; } vertexData[1] = new VertexSet(normals, true); } if (hasUVs) { iaParam.IndexAttributes |= IndexAttributes.HasUV; if (texcoords.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.UV16BitIndex; } vertexData[2] = new VertexSet(texcoords); } // stitching polygons together BufferMaterial currentMaterial = null; Mesh ProcessBufferMesh(int index) { // generating parameter info List <IParameter> parameters = new(); BufferMaterial cacheMaterial = cacheAtc.materials[index]; if (currentMaterial == null) { parameters.Add(new VtxAttrFmtParameter(VertexAttribute.Position)); parameters.Add(new VtxAttrFmtParameter(hasColors ? VertexAttribute.Color0 : VertexAttribute.Normal)); if (hasUVs) { parameters.Add(new VtxAttrFmtParameter(VertexAttribute.Tex0)); } parameters.Add(iaParam); if (cacheMaterial == null) { currentMaterial = new BufferMaterial() { MaterialAttributes = MaterialAttributes.noSpecular }; } else { currentMaterial = cacheMaterial; } parameters.Add(new LightingParameter() { LightingAttributes = LightingParameter.DefaultLighting.LightingAttributes, ShadowStencil = currentMaterial.ShadowStencil }); parameters.Add(new BlendAlphaParameter() { SourceAlpha = currentMaterial.SourceBlendMode, DestAlpha = currentMaterial.DestinationBlendmode }); parameters.Add(new AmbientColorParameter() { AmbientColor = currentMaterial.Ambient }); TextureParameter texParam = new(); texParam.TextureID = (ushort)currentMaterial.TextureIndex; if (!currentMaterial.ClampU) { texParam.Tiling |= GCTileMode.RepeatU; } if (!currentMaterial.ClampV) { texParam.Tiling |= GCTileMode.RepeatV; } if (currentMaterial.MirrorU) { texParam.Tiling |= GCTileMode.MirrorU; } if (currentMaterial.MirrorV) { texParam.Tiling |= GCTileMode.MirrorV; } parameters.Add(texParam); parameters.Add(Unknown9Parameter.DefaultValues); parameters.Add(new TexCoordGenParameter() { TexCoordID = currentMaterial.TexCoordID, TexGenType = currentMaterial.TexGenType, TexGenSrc = currentMaterial.TexGenSrc, MatrixID = currentMaterial.MatrixID }); } else { if (currentMaterial.ShadowStencil != cacheMaterial.ShadowStencil) { parameters.Add(new LightingParameter() { ShadowStencil = cacheMaterial.ShadowStencil }); } if (currentMaterial.SourceBlendMode != cacheMaterial.SourceBlendMode || currentMaterial.DestinationBlendmode != cacheMaterial.DestinationBlendmode) { parameters.Add(new BlendAlphaParameter() { SourceAlpha = cacheMaterial.SourceBlendMode, DestAlpha = cacheMaterial.DestinationBlendmode }); } if (currentMaterial.Ambient != cacheMaterial.Ambient) { parameters.Add(new AmbientColorParameter() { AmbientColor = cacheMaterial.Ambient }); } if (currentMaterial.TextureIndex != cacheMaterial.TextureIndex || currentMaterial.MirrorU != cacheMaterial.MirrorU || currentMaterial.MirrorV != cacheMaterial.MirrorV || currentMaterial.ClampU != cacheMaterial.ClampU || currentMaterial.ClampV != cacheMaterial.ClampV) { TextureParameter texParam = new(); texParam.TextureID = (ushort)cacheMaterial.TextureIndex; if (!cacheMaterial.ClampU) { texParam.Tiling |= GCTileMode.RepeatU; } if (!cacheMaterial.ClampV) { texParam.Tiling |= GCTileMode.RepeatV; } if (cacheMaterial.MirrorU) { texParam.Tiling |= GCTileMode.MirrorU; } if (cacheMaterial.MirrorV) { texParam.Tiling |= GCTileMode.MirrorV; } parameters.Add(texParam); } if (currentMaterial.TexCoordID != cacheMaterial.TexCoordID || currentMaterial.TexGenType != cacheMaterial.TexGenType || currentMaterial.TexGenSrc != cacheMaterial.TexGenSrc || currentMaterial.MatrixID != cacheMaterial.MatrixID) { parameters.Add(new TexCoordGenParameter() { TexCoordID = cacheMaterial.TexCoordID, TexGenType = cacheMaterial.TexGenType, TexGenSrc = cacheMaterial.HasAttribute(MaterialAttributes.normalMapping) ? TexGenSrc.Normal : cacheMaterial.TexGenSrc, MatrixID = cacheMaterial.MatrixID }); } currentMaterial = cacheMaterial; } // note: a single triangle polygon can only carry 0xFFFF corners, so about 22k tris Corner[] triangleCorners = corners[index]; List <Poly> polygons = new(); if (triangleCorners.Length > 0xFFFF) { int remainingLength = triangleCorners.Length; int offset = 0; while (remainingLength > 0) { Corner[] finalCorners = new Corner[Math.Max(0xFFFF, remainingLength)]; Array.Copy(triangleCorners, offset, finalCorners, 0, finalCorners.Length); offset += finalCorners.Length; remainingLength -= finalCorners.Length; Poly triangle = new(PolyType.Triangles, finalCorners); polygons.Add(triangle); }
public static void ConvertModelFromChunk(NJObject model, bool optimize = true) { if (model.Parent != null) { throw new FormatException($"Model {model.Name} is not hierarchy root!"); } HashSet <ChunkAttach> attaches = new(); NJObject[] models = model.GetObjects(); foreach (NJObject obj in models) { if (obj.Attach == null) { continue; } if (obj.Attach.Format != AttachFormat.CHUNK) { throw new FormatException("Not all Attaches inside the model are a CHUNK attaches! Cannot convert"); } ChunkAttach atc = (ChunkAttach)obj.Attach; attaches.Add(atc); } Array.Clear(PolyChunkCache, 0, PolyChunkCache.Length); foreach (ChunkAttach atc in attaches) { List <BufferMesh> meshes = new(); BufferVertex[] vertices = null; bool continueWeight = false; if (atc.VertexChunks != null) { for (int i = 0; i < atc.VertexChunks.Length; i++) { VertexChunk cnk = atc.VertexChunks[i]; List <BufferVertex> vertexList = new(); if (!cnk.HasWeight) { for (int j = 0; j < cnk.Vertices.Length; j++) { ChunkVertex vtx = cnk.Vertices[j]; vertexList.Add(new BufferVertex(vtx.Position, vtx.Normal, (ushort)(j + cnk.IndexOffset))); } } else { for (int j = 0; j < cnk.Vertices.Length; j++) { ChunkVertex vtx = cnk.Vertices[j]; vertexList.Add(new BufferVertex(vtx.Position, vtx.Normal, (ushort)(vtx.Index + cnk.IndexOffset), vtx.Weight)); } } vertices = vertexList.ToArray(); continueWeight = cnk.WeightStatus != WeightStatus.Start; if (i < atc.VertexChunks.Length - 1) { meshes.Add(new BufferMesh(vertices, continueWeight)); } } } List <PolyChunk> active = new(); if (atc.PolyChunks != null) { int cacheID = -1; foreach (PolyChunk cnk in atc.PolyChunks) { switch (cnk.Type) { case ChunkType.Bits_CachePolygonList: PolyChunkCachePolygonList cacheListCnk = (PolyChunkCachePolygonList)cnk; cacheID = cacheListCnk.List; if (PolyChunkCache.Length <= cacheID) { Array.Resize(ref PolyChunkCache, cacheID + 1); } PolyChunkCache[cacheID] = new List <PolyChunk>(); break; case ChunkType.Bits_DrawPolygonList: PolyChunkDrawPolygonList drawListCnk = (PolyChunkDrawPolygonList)cnk; active.AddRange(PolyChunkCache[drawListCnk.List]); break; default: if (cacheID > -1) { PolyChunkCache[cacheID].Add(cnk); } else { active.Add(cnk); } break; } } } if (active.Count > 0) { BufferMaterial material = new() { MaterialAttributes = MaterialAttributes.useTexture }; foreach (PolyChunk cnk in active) { switch (cnk.Type) { case ChunkType.Bits_BlendAlpha: PolyChunkBlendAlpha blendCnk = (PolyChunkBlendAlpha)cnk; material.SourceBlendMode = blendCnk.SourceAlpha; material.DestinationBlendmode = blendCnk.DestinationAlpha; break; case ChunkType.Bits_MipmapDAdjust: PolyChunksMipmapDAdjust mipmapCnk = (PolyChunksMipmapDAdjust)cnk; material.MipmapDistanceAdjust = mipmapCnk.MipmapDAdjust; break; case ChunkType.Bits_SpecularExponent: PolyChunkSpecularExponent specularCnk = (PolyChunkSpecularExponent)cnk; material.SpecularExponent = specularCnk.SpecularExponent; break; case ChunkType.Tiny_TextureID: case ChunkType.Tiny_TextureID2: PolyChunkTextureID textureCnk = (PolyChunkTextureID)cnk; material.TextureIndex = textureCnk.TextureID; material.MirrorU = textureCnk.MirrorU; material.MirrorV = textureCnk.MirrorV; material.ClampU = textureCnk.ClampU; material.ClampV = textureCnk.ClampV; material.AnisotropicFiltering = textureCnk.SuperSample; material.TextureFiltering = textureCnk.FilterMode; break; case ChunkType.Material: case ChunkType.Material_Diffuse: case ChunkType.Material_Ambient: case ChunkType.Material_DiffuseAmbient: case ChunkType.Material_Specular: case ChunkType.Material_DiffuseSpecular: case ChunkType.Material_AmbientSpecular: case ChunkType.Material_DiffuseAmbientSpecular: case ChunkType.Material_Diffuse2: case ChunkType.Material_Ambient2: case ChunkType.Material_DiffuseAmbient2: case ChunkType.Material_Specular2: case ChunkType.Material_DiffuseSpecular2: case ChunkType.Material_AmbientSpecular2: case ChunkType.Material_DiffuseAmbientSpecular2: PolyChunkMaterial materialCnk = (PolyChunkMaterial)cnk; material.SourceBlendMode = materialCnk.SourceAlpha; material.DestinationBlendmode = materialCnk.DestinationAlpha; if (materialCnk.Diffuse.HasValue) { material.Diffuse = materialCnk.Diffuse.Value; } if (materialCnk.Ambient.HasValue) { material.Ambient = materialCnk.Ambient.Value; } if (materialCnk.Specular.HasValue) { material.Specular = materialCnk.Specular.Value; material.SpecularExponent = materialCnk.SpecularExponent; } break; case ChunkType.Strip_Strip: case ChunkType.Strip_StripUVN: case ChunkType.Strip_StripUVH: case ChunkType.Strip_StripNormal: case ChunkType.Strip_StripUVNNormal: case ChunkType.Strip_StripUVHNormal: case ChunkType.Strip_StripColor: case ChunkType.Strip_StripUVNColor: case ChunkType.Strip_StripUVHColor: case ChunkType.Strip_Strip2: case ChunkType.Strip_StripUVN2: case ChunkType.Strip_StripUVH2: PolyChunkStrip stripCnk = (PolyChunkStrip)cnk; material.SetAttribute(MaterialAttributes.Flat, stripCnk.FlatShading); material.SetAttribute(MaterialAttributes.noAmbient, stripCnk.IgnoreAmbient); material.SetAttribute(MaterialAttributes.noDiffuse, stripCnk.IgnoreLight); material.SetAttribute(MaterialAttributes.noSpecular, stripCnk.IgnoreSpecular); material.SetAttribute(MaterialAttributes.normalMapping, stripCnk.EnvironmentMapping); material.UseAlpha = stripCnk.UseAlpha; material.Culling = !stripCnk.DoubleSide; List <BufferCorner> corners = new(); List <uint> triangles = new(); foreach (var s in stripCnk.Strips) { uint l = (uint)corners.Count; bool rev = s.Reversed; for (uint i = 2; i < s.Corners.Length; i++) { uint li = l + i; if (!rev) { triangles.AddRange(new uint[] { li - 2, li - 1, li }); } else { triangles.AddRange(new uint[] { li - 1, li - 2, li }); } rev = !rev; } foreach (var c in s.Corners) { corners.Add(new BufferCorner(c.Index, c.Color, c.Texcoord)); } } if (vertices != null) { meshes.Add(new BufferMesh(vertices, continueWeight, corners.ToArray(), triangles.ToArray(), material.Clone())); vertices = null; } else { meshes.Add(new BufferMesh(corners.ToArray(), triangles.ToArray(), material.Clone())); } break; } } } else if (vertices != null) { meshes.Add(new BufferMesh(vertices, continueWeight)); } if (optimize) { for (int i = 0; i < meshes.Count; i++) { meshes[i].Optimize(); } } atc.MeshData = meshes.ToArray(); } }
public static Contents Read(ModelRoot gltfModel, bool importTextures, float?animationFPS) { // First we'll get the textures, by far the easiest part TextureSet textures = null; if (importTextures && gltfModel.LogicalTextures.Count > 0) { textures = new(); foreach (var t in gltfModel.LogicalTextures) { string name = t.Name ?? $"Tex_{t}"; textures.Textures.Add(new SAArchive.Texture(name, GetBitmap(t.PrimaryImage.Content))); } } // lets first set up the object hierarchy Dictionary <Node, NJObject> objectsPairs = new(); List <NJObject> roots = new(); foreach (var n in gltfModel.LogicalNodes) { if (n.VisualParent == null) { roots.Add(FromNode(n, objectsPairs)); } } NJObject root; if (roots.Count > 1) { root = new NJObject() { Name = "Root" }; root.AddChildren(roots); } else { root = roots[0]; } Dictionary <Mesh, Attach> nonWeightAttaches = new(); NJObject[] objects = root.GetObjects(); foreach (NJObject njo in objects) { Node node = objectsPairs.First(x => x.Value == njo).Key; if (node.Mesh == null) { continue; } if (node.Skin == null) { if (!nonWeightAttaches.TryGetValue(node.Mesh, out Attach atc)) { atc = FromNoWeight(node.Mesh); nonWeightAttaches.Add(node.Mesh, atc); } njo.Attach = atc; } else { Skin skin = node.Skin; NJObject[] bones = new NJObject[skin.JointsCount]; for (int i = 0; i < skin.JointsCount; i++) { (Node bone, _) = skin.GetJoint(i); bones[i] = objectsPairs[bone]; } var(vertices, polydata) = FromWeight(node.Mesh); Matrix4x4 meshMatrix = node.GetWorldMatrix(null, 0); AttachHelper.FromWeightedBuffer(bones, meshMatrix, vertices, polydata); } } // lastly, we load the animations Motion[] animations; if (animationFPS.HasValue) { animations = new Motion[gltfModel.LogicalAnimations.Count]; for (int i = 0; i < animations.Length; i++) { animations[i] = GetAnimation(gltfModel.LogicalAnimations[i], gltfModel.LogicalNodes.Count, animationFPS.Value); } } else { animations = Array.Empty <Motion>(); } return(new Contents(root, textures, animations)); }
public Contents(NJObject root, TextureSet textures, Motion[] animations) { Root = root; Textures = textures; Animations = animations; }
public static List <(DisplayTask task, List <RenderMesh> opaque, List <RenderMesh> transparent)> PrepareModels(IReadOnlyCollection <GameTask> tasks, NJObject active, Camera cam, BufferingBridge buffer) { List <(DisplayTask task, List <RenderMesh> opaque, List <RenderMesh> transparent)> result = new(); foreach (GameTask t in tasks) { t.Display(); if (t is DisplayTask dtsk && dtsk.Model != null) { List <RenderMesh> opaque = new(); List <RenderMesh> transparent = new(); dtsk.Model.PrepareModel(opaque, transparent, buffer, cam, active, null, dtsk.Model.HasWeight); result.Add((dtsk, opaque, transparent)); } } return(result); }
public VmModelHead(NJObject objectData) { ObjectData = objectData; }
/// <summary> /// Converts the buffer data of a model to BASIC attaches /// </summary> /// <param name="model">The tip of the model hierarchy to convert</param> /// <param name="optimize">Whether to optimize the data</param> /// <param name="ignoreWeights">Convert regardless of weight information being lost</param> /// <param name="forceUpdate">Still convert, even if the attaches are Basic already</param> public static void ConvertModelToBasic(NJObject model, bool optimize = true, bool ignoreWeights = false, bool forceUpdate = false) { if (model.Parent != null) { throw new FormatException($"Model {model.Name} is not hierarchy root!"); } if (model.AttachFormat == AttachFormat.BASIC && !forceUpdate) { return; } if (model.HasWeight && !ignoreWeights) { throw new FormatException("Model is weighted, cannot convert to basic format!"); } AttachHelper.ProcessWeightlessModel(model, (cacheAtc, ogAtc) => { // getting the vertex information Vector3[] positions = new Vector3[cacheAtc.vertices.Length]; Vector3[] normals = new Vector3[positions.Length]; bool hasNormals = false; for (int i = 0; i < positions.Length; i++) { var vtx = cacheAtc.vertices[i]; positions[i] = vtx.Position; normals[i] = vtx.Normal; if (vtx.Normal != Vector3.UnitY) { hasNormals = true; } } if (!hasNormals) { normals = null; } // putting together polygons Mesh[] meshes = new Mesh[cacheAtc.corners.Length]; Material[] materials = new Material[cacheAtc.corners.Length]; for (int i = 0; i < cacheAtc.corners.Length; i++) { // creating the material Material mat = new(); BufferMaterial bmat = cacheAtc.materials[i]; if (bmat != null) { mat.DiffuseColor = bmat.Diffuse; mat.SpecularColor = bmat.Specular; mat.Exponent = bmat.SpecularExponent; mat.TextureID = bmat.TextureIndex; mat.FilterMode = bmat.TextureFiltering; mat.MipmapDAdjust = bmat.MipmapDistanceAdjust; mat.SuperSample = bmat.AnisotropicFiltering; mat.ClampU = bmat.ClampU; mat.ClampV = bmat.ClampV; mat.MirrorU = bmat.MirrorU; mat.MirrorV = bmat.MirrorV; mat.UseAlpha = bmat.UseAlpha; mat.SourceAlpha = bmat.SourceBlendMode; mat.DestinationAlpha = bmat.DestinationBlendmode; mat.DoubleSided = !bmat.Culling; mat.IgnoreLighting = bmat.HasAttribute(MaterialAttributes.noDiffuse); mat.IgnoreSpecular = bmat.HasAttribute(MaterialAttributes.noSpecular); mat.UseTexture = bmat.HasAttribute(MaterialAttributes.useTexture); mat.EnvironmentMap = bmat.HasAttribute(MaterialAttributes.normalMapping); } materials[i] = mat; // creating the polygons BufferCorner[] bCorners = cacheAtc.corners[i]; IPoly[] triangles = new IPoly[bCorners.Length / 3]; Vector2[] texcoords = new Vector2[bCorners.Length]; Color[] colors = new Color[bCorners.Length]; Triangle current = new(); for (int j = 0; j < bCorners.Length; j++) { BufferCorner corner = bCorners[j]; int vIndex = j % 3; current.Indices[vIndex] = corner.VertexIndex; if (vIndex == 2) { triangles[(j - 2) / 3] = current; current = new Triangle(); } texcoords[j] = corner.Texcoord; colors[j] = corner.Color; } bool hasTexcoords = texcoords.Any(x => x != default); bool hasColors = colors.Any(x => x != Color.White); Mesh basicmesh = new (BASICPolyType.Triangles, triangles, false, hasColors, hasTexcoords, (ushort)i); if (hasColors) { basicmesh.Colors = colors; } if (hasTexcoords) { basicmesh.Texcoords = texcoords; } meshes[i] = basicmesh; }