using System; using System.Collections.Generic; using NW4RTools; using NW4RTools.Models; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace NW4RTools.Models.OpenGL { public class GLModel { public ResFile SourceFile { get; private set; } public Model SourceModel { get; private set; } public GLModel(ResFile rf, string modelName) { SourceFile = rf; SourceModel = rf.GetGroup("3DModels(NW4R)")[modelName]; // cache some stuff try { m_textureGroup = rf.GetGroup("Textures(NW4R)"); } catch (KeyNotFoundException) { // boo } } private class MyNode { Node RealNode; MyNode Parent; List Children; } private ResDict m_textureGroup; private Dictionary m_glTextures; private Dictionary m_glMaterials; private Dictionary m_glShapes; private Matrix4[] m_matrices; private MyNode[] m_nodesByMtxID; private MyNode[] m_nodes; #region Initialisation public void Prepare(IGraphicsContext context) { // convert every texture m_glTextures = new Dictionary(); foreach (var material in SourceModel.Materials) { foreach (var texInfo in material.Value.TextureInfos) { if (!m_glTextures.ContainsKey(texInfo.TextureName)) { var newTex = new GLTexture(); newTex.Load(m_textureGroup[texInfo.TextureName]); m_glTextures.Add(texInfo.TextureName, newTex); } } } // convert every material m_glMaterials = new Dictionary(); foreach (var material in SourceModel.Materials.Values) { m_glMaterials.Add(material, MakeMaterialDisplayList(material)); } // convert every shape m_glShapes = new Dictionary(); foreach (var shape in SourceModel.Shapes.Values) { m_glShapes.Add(shape, MakeShapeDisplayList(shape)); } // allocate stuff for nodes m_matrices = new Matrix4[SourceModel.MatrixIDtoNodeID.Length]; // build the hierarchy BuildNodeHierarchy(); } private void BuildNodeHierarchy() { var nodeTree = SourceModel.Bytecode["NodeTree"]; foreach (var insn in nodeTree.Instructions) { if (insn.GetOp() == ByteCode.OpType.AssignNodeToParentMtx) { MyNode n = new MyNode(); } } } #endregion private static readonly TextureUnit[] TextureUnits = new TextureUnit[] { TextureUnit.Texture0, TextureUnit.Texture1, TextureUnit.Texture2, TextureUnit.Texture3, TextureUnit.Texture4, TextureUnit.Texture5, TextureUnit.Texture6, TextureUnit.Texture7, TextureUnit.Texture8, TextureUnit.Texture9, TextureUnit.Texture10, TextureUnit.Texture11, TextureUnit.Texture12, TextureUnit.Texture13, TextureUnit.Texture14, TextureUnit.Texture15 }; // GX_CLAMP, GX_REPEAT, GX_MIRROR private static readonly TextureWrapMode[] GXtoGLTextureWrapModes = new TextureWrapMode[] { TextureWrapMode.ClampToEdge, TextureWrapMode.Repeat, TextureWrapMode.MirroredRepeat }; private Material m_currentMaterial; private void ClearCache() { m_currentMaterial = null; } private void LoadMaterial(Material m) { if (m == m_currentMaterial) return; m_currentMaterial = m; m_glMaterials[m].Execute(); } private void DrawShape(ByteCode.DrawShapeInstruction insn) { LoadMaterial(SourceModel.Materials[insn.MaterialID]); // HACKY TEST !! GL.MatrixMode(MatrixMode.Modelview); var node = SourceModel.Nodes[insn.NodeID]; var m = node.NodeMatrix; /*double cosX = Math.Cos(node.Rotation.x / 180 * Math.PI); double cosY = Math.Cos(node.Rotation.y / 180 * Math.PI); double cosZ = Math.Cos(node.Rotation.z / 180 * Math.PI); double sinX = Math.Sin(node.Rotation.x / 180 * Math.PI); double sinY = Math.Sin(node.Rotation.y / 180 * Math.PI); double sinZ = Math.Sin(node.Rotation.z / 180 * Math.PI); var nodeMatrix = new Matrix4d(); nodeMatrix.M11 = node.Scale.x * cosY * cosZ; nodeMatrix.M12 = node.Scale.y * (sinX * cosZ * sinY - cosX * sinZ); nodeMatrix.M13 = node.Scale.z * (sinX * sinZ + cosX * cosZ * sinY); nodeMatrix.M14 = node.Translation.x; nodeMatrix.M21 = node.Scale.x * sinZ * cosY; nodeMatrix.M22 = node.Scale.y * (sinX * sinZ * sinY + cosZ * cosX); nodeMatrix.M23 = node.Scale.z * (cosX * sinZ * sinY - sinX * cosZ); nodeMatrix.M24 = node.Translation.y; nodeMatrix.M31 = -node.Scale.x * sinY; nodeMatrix.M32 = node.Scale.y * sinX * cosY; nodeMatrix.M33 = node.Scale.z * cosX * cosY; nodeMatrix.M34 = node.Translation.z; nodeMatrix.M41 = 0; nodeMatrix.M42 = 0; nodeMatrix.M43 = 0; nodeMatrix.M44 = 1; GL.PushMatrix(); GL.MultTransposeMatrix(ref nodeMatrix); //*/ GL.PushMatrix(); GL.MultTransposeMatrix(new float[] { m.v00, m.v01, m.v02, m.v03, m.v10, m.v11, m.v12, m.v13, m.v20, m.v21, m.v22, m.v23, 0, 0, 0, 1 }); //GL.Translate(node.Translation.x, node.Translation.y, node.Translation.z); var shape = SourceModel.Shapes[insn.ShapeID]; m_glShapes[shape].Execute(); GL.PopMatrix(); } public void Render(IGraphicsContext context) { // This'll be fun. ClearCache(); //CalculateNodes(); if (SourceModel.Bytecode.ContainsKey("DrawOpa")) { foreach (var insn in SourceModel.Bytecode["DrawOpa"].Instructions) { if (insn is ByteCode.DrawShapeInstruction) { DrawShape(insn as ByteCode.DrawShapeInstruction); } } } if (SourceModel.Bytecode.ContainsKey("DrawXlu")) { foreach (var insn in SourceModel.Bytecode["DrawXlu"].Instructions) { if (insn is ByteCode.DrawShapeInstruction) { DrawShape(insn as ByteCode.DrawShapeInstruction); } } } } private void BindTextureInfo(TextureInfo info) { m_glTextures[info.TextureName].Bind(TextureTarget.Texture2D); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)GXtoGLTextureWrapModes[(int)info.WrapS]); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)GXtoGLTextureWrapModes[(int)info.WrapT]); } #region Display Lists private GLDisplayList MakeMaterialDisplayList(Material m) { var displayList = new GLDisplayList(); displayList.Begin(); if (m.TextureInfos.Count > 0) { GL.Enable(EnableCap.Texture2D); TextureInfo[] texInfoArray = new TextureInfo[8]; foreach (var texInfo in m.TextureInfos) { texInfoArray[texInfo.TexMapID] = texInfo; } for (int i = 0; i < 8; i++) { GL.ActiveTexture(TextureUnits[i]); GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Modulate); if (texInfoArray[i] == null) { GL.BindTexture(TextureTarget.Texture2D, 0); } else { BindTextureInfo(texInfoArray[i]); } } } else { GL.Disable(EnableCap.Texture2D); } displayList.End(); return displayList; } private GLDisplayList MakeShapeDisplayList(Shape shape) { var displayList = new GLDisplayList(); displayList.Begin(); var firstDL = new InputStream(shape.DisplayList1); firstDL.Seek(0x0C); UInt32 vtxDesc1 = firstDL.ReadUInt32(); firstDL.Seek(0x12); UInt32 vtxDesc2 = firstDL.ReadUInt32(); firstDL.Seek(0x22); UInt32 vtxAttr1 = firstDL.ReadUInt32(); firstDL.Seek(0x28); UInt32 vtxAttr2 = firstDL.ReadUInt32(); firstDL.Seek(0x2E); UInt32 vtxAttr3 = firstDL.ReadUInt32(); var vs = new VertexSettings(); vs.SetDesc(vtxDesc1, vtxDesc2); vs.SetAttrFmt(vtxAttr1, vtxAttr2, vtxAttr3); InputStream dl = new InputStream(shape.DisplayList2); while (true) { if (dl.AtEnd) break; byte cmd = dl.ReadByte(); if (cmd == 0) break; if ((cmd & (int)GXCommand.DrawPrimitiveMask) != 0) { PrimitiveType prim = (PrimitiveType)((cmd >> (byte)GXCommand.PrimitiveShiftAmount) & 7); int vtxCount = dl.ReadUInt16(); if ((cmd & 7) != 0) Console.WriteLine("WARNING!! Not using vertex attribute table 0"); // first, parse it into a list of vertices GXIndexedVertex[] vtxs = new GXIndexedVertex[vtxCount]; for (int i = 0; i < vtxCount; i++) { vtxs[i].LoadFrom(dl, vs); } switch (prim) { case PrimitiveType.Quads: GL.Begin(BeginMode.Quads); break; case PrimitiveType.Triangles: GL.Begin(BeginMode.Triangles); break; case PrimitiveType.TriangleStrip: GL.Begin(BeginMode.TriangleStrip); break; case PrimitiveType.TriangleFan: GL.Begin(BeginMode.TriangleFan); break; case PrimitiveType.Lines: GL.Begin(BeginMode.Lines); break; case PrimitiveType.LineStrip: GL.Begin(BeginMode.LineStrip); break; case PrimitiveType.Points: GL.Begin(BeginMode.Points); break; default: Console.WriteLine("UNIMPLEMENTED PRIMITIVE TYPE {0}", prim); throw new NotImplementedException(); } for (int i = 0; i < vtxCount; i++) { for (int j = 0; j < 8; j++) { if (vs.TexCoordDesc[j] != VertexSettings.DescType.None) { GL.MultiTexCoord2(TextureUnits[j], shape.TexCoordData[j].Data[vtxs[i].TexCoords[j]]); } } // TODO: normals GL.Vertex3(shape.PosData.Data[vtxs[i].Position]); } GL.End(); } else { // some other command switch ((GXCommand)cmd) { case GXCommand.LoadPosMtxFromArray: int targetID = dl.ReadUInt16(); int sourceID = (dl.ReadUInt16() & 0xFFF) / 12; Console.WriteLine("Load PosMtx from array: {0} => {1}", targetID, sourceID); break; case GXCommand.LoadNrmMtxFromArray: dl.Skip(4); Console.WriteLine("UNIMPLEMENTED: Load NrmMtx from array"); break; case GXCommand.LoadTexCoordMtxFromArray: dl.Skip(4); Console.WriteLine("UNIMPLEMENTED: Load TexCoordMtx from array"); break; case GXCommand.LoadLightFromArray: dl.Skip(4); Console.WriteLine("UNIMPLEMENTED: Load Light from array"); break; default: Console.WriteLine("UNKNOWN DL COMMAND"); break; } } } displayList.End(); return displayList; } #endregion } }