diff --git a/Diamond/Mesh.cs b/Diamond/Mesh.cs index 68af24b..f85cbbd 100644 --- a/Diamond/Mesh.cs +++ b/Diamond/Mesh.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using Diamond.Buffers; using Newtonsoft.Json; +using OpenTK; using OpenTK.Graphics.OpenGL4; namespace Diamond @@ -54,15 +56,89 @@ namespace Diamond } } + [VertexData] + public struct ObjVertex + { + [VertexPointer("position", 3)] + public Vector3 Position; + + [VertexPointer("uv", 2)] + public Vector2 UV; + + [VertexPointer("normal", 3)] + public Vector3 Normal; + + public ObjVertex(Vector3 position, Vector2 uv, Vector3 normal) + { + Position = position; + UV = uv; + Normal = normal; + } + } + public static class Mesh { - public static Mesh FromJson(string json) where T : struct + public static Mesh FromJson(string file) where T : struct { - var vertices = JsonConvert.DeserializeObject(json); + var vertices = JsonConvert.DeserializeObject(File.ReadAllText(file)); return new Mesh(vertices); } - //todo add fromObj + public static Mesh FromObj(string file) + { + var lines = File.ReadAllLines(file); + + var vs = new List(); + var vts = new List(); + var vns = new List(); + var faces = new List(); + + foreach (var line in lines) + { + if (line.StartsWith("#")) continue; + + var items = line.Split(' '); + + switch (items[0]) + { + case "v": + var v = new Vector3( + float.Parse(items[1]), + float.Parse(items[2]), + float.Parse(items[3])); + vs.Add(v); + break; + case "vt": + var vt = new Vector2( + float.Parse(items[1]), + 1-float.Parse(items[2])); + vts.Add(vt); + break; + case "vn": + var vn = new Vector3( + float.Parse(items[1]), + float.Parse(items[2]), + float.Parse(items[3])); + vns.Add(vn); + break; + case "f": + for (var i = 1; i < 4; i++) + { + var inds = items[i].Split('/'); + var vi = vs[int.Parse(inds[0]) - 1]; + var vti = vts[int.Parse(inds[1]) - 1]; + var vni = vns[int.Parse(inds[2]) - 1]; + var f = new ObjVertex(vi, vti, vni); + faces.Add(f); + } + break; + } + } + + var vertices = faces.ToArray(); + + return new Mesh(vertices); + } public static T[] Join(params Mesh[] meshes) where T : struct => Join((IEnumerable>) meshes); diff --git a/hexworld/HexRender.cs b/hexworld/HexRender.cs index bcf4b98..04fd13b 100644 --- a/hexworld/HexRender.cs +++ b/hexworld/HexRender.cs @@ -19,27 +19,31 @@ namespace hexworld #region GLObjects - private Program _pgm; + private Program _jsonPgm; + private Program _objPgm; private Texture _grass; private Texture _stone; private Texture _gray; - private GLBuffer _tileGLBuffer; - private GLBuffer _vertexGLBuffer; + private GLBuffer _tileBuffer; + private GLBuffer _vertexBuffer; + private GLBuffer _objBuffer; protected override void OnClosed(EventArgs e) { base.OnClosed(e); - _pgm.Dispose(); + _jsonPgm?.Dispose(); + _objPgm?.Dispose(); - _tileGLBuffer.Dispose(); - _vertexGLBuffer.Dispose(); + _tileBuffer?.Dispose(); + _vertexBuffer?.Dispose(); + _objBuffer?.Dispose(); - _grass.Dispose(); - _stone.Dispose(); - _gray.Dispose(); + _grass?.Dispose(); + _stone?.Dispose(); + _gray?.Dispose(); } #endregion @@ -50,13 +54,16 @@ namespace hexworld private SubArray _grassTiles; private SubArray _stoneTiles; private SubArray _grayTiles; + private SubArray _tableTiles; private Mesh _cubeMesh; private Mesh _panelMesh; private Mesh _sidesMesh; + private Mesh _objMesh; private Tile[] _allTiles; private Vertex[] _allVertices; + private ObjVertex[] _allObjVertices; private double _time; @@ -76,34 +83,54 @@ namespace hexworld { base.OnLoad(e); - var vsPath = @"res\s.vs.glsl"; - var fsPath = @"res\s.fs.glsl"; - - using (var vs = Shader.FromFile(vsPath, ShaderType.VertexShader)) - using (var fs = Shader.FromFile(fsPath, ShaderType.FragmentShader)) + using (var vs = Shader.FromFile(@"res\s.vs.glsl", ShaderType.VertexShader)) + using (var fs = Shader.FromFile(@"res\s.fs.glsl", ShaderType.FragmentShader)) { if (!vs.Compiled | !fs.Compiled) { Debug.WriteLine("Failed to compile shaders:"); - Debug.WriteLineIf(!vs.Compiled, $"Vertex Log:\n{vs.Log}"); - Debug.WriteLineIf(!fs.Compiled, $"Fragment Log:\n{fs.Log}"); + Debug.WriteLineIf(!vs.Compiled, $"Vertex Log ({vs.Id}):\n{vs.Log}"); + Debug.WriteLineIf(!fs.Compiled, $"Fragment Log ({fs.Id}):\n{fs.Log}"); Exit(); return; } - _pgm = Program.FromShaders(vs, fs); + _jsonPgm = Program.FromShaders(vs, fs); - if (!_pgm.Link()) + if (!_jsonPgm.Link()) { - Debug.WriteLine($"Failed to link program:\n{_pgm.Log}"); + Debug.WriteLine($"Failed to link program ({_jsonPgm.Id}):\n{_jsonPgm.Log}"); Exit(); return; } } - _cubeMesh = Mesh.FromJson(File.ReadAllText(@"res\data_vert_cubes.json")); - _panelMesh = Mesh.FromJson(File.ReadAllText(@"res\data_vert_panels.json")); - _sidesMesh = Mesh.FromJson(File.ReadAllText(@"res\data_vert_sides.json")); + using (var vs = Shader.FromFile(@"res\obj.vs.glsl", ShaderType.VertexShader)) + using (var fs = Shader.FromFile(@"res\obj.fs.glsl", ShaderType.FragmentShader)) + { + if (!vs.Compiled | !fs.Compiled) + { + Debug.WriteLine("Failed to compile shaders:"); + Debug.WriteLineIf(!vs.Compiled, $"Vertex Log ({vs.Id}):\n{vs.Log}"); + Debug.WriteLineIf(!fs.Compiled, $"Fragment Log ({fs.Id}):\n{fs.Log}"); + Exit(); + return; + } + + _objPgm = Program.FromShaders(vs, fs); + + if (!_objPgm.Link()) + { + Debug.WriteLine($"Failed to link program ({_objPgm.Id}):\n{_jsonPgm.Log}"); + Exit(); + return; + } + } + + _cubeMesh = Mesh.FromJson(@"res\data_vert_cubes.json"); + _panelMesh = Mesh.FromJson(@"res\data_vert_panels.json"); + _sidesMesh = Mesh.FromJson(@"res\data_vert_sides.json"); + _objMesh = Mesh.FromObj(@"res\table.obj"); _grassTiles = new SubArray( JsonConvert.DeserializeObject(File.ReadAllText(@"res\data_tile_grass.json"))); @@ -111,18 +138,21 @@ namespace hexworld JsonConvert.DeserializeObject(File.ReadAllText(@"res\data_tile_stone.json"))); _grayTiles = new SubArray( JsonConvert.DeserializeObject(File.ReadAllText(@"res\data_tile_gray.json"))); + _tableTiles = new SubArray( + JsonConvert.DeserializeObject(File.ReadAllText(@"res\data_tile_table.json"))); + + + _allTiles = SubArray.Join(_stoneTiles, _grassTiles, _grayTiles, _tableTiles); + _tileBuffer = new GLBuffer(BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw); + _tileBuffer.Data(_allTiles); - _allTiles = SubArray.Join(_stoneTiles, _grassTiles, _grayTiles); _allVertices = Mesh.Join(_panelMesh, _cubeMesh, _sidesMesh); + _vertexBuffer = new GLBuffer(BufferTarget.ArrayBuffer, BufferUsageHint.StaticDraw); + _vertexBuffer.Data(_allVertices); - _tileGLBuffer = new GLBuffer(BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw); - _tileGLBuffer.Data(_allTiles); - - _vertexGLBuffer = new GLBuffer(BufferTarget.ArrayBuffer, BufferUsageHint.StaticDraw); - _vertexGLBuffer.Data(_allVertices); - - _pgm.SetAttribPointers(_tileGLBuffer); - _pgm.SetAttribPointers(_vertexGLBuffer); + _allObjVertices = Mesh.Join(_objMesh); + _objBuffer = new GLBuffer(BufferTarget.ArrayBuffer); + _objBuffer.Data(_allObjVertices); _grass = Texture.FromBitmap(new Bitmap(@"res\grass.png")); _stone = Texture.FromBitmap(new Bitmap(@"res\stone.png")); @@ -146,9 +176,9 @@ namespace hexworld (float) (Math.Sin((_time + ti.Position.X - ti.Position.Y / 1.5) / 1.5) * .25); } - _tileGLBuffer.SubData(_grassTiles); + _tileBuffer.SubData(_grassTiles); - _tileGLBuffer.Bind(); + _tileBuffer.Bind(); GL.BufferSubData(BufferTarget.ArrayBuffer, (IntPtr) (5 * 3 * sizeof(float)), (IntPtr) (16 * 3 * sizeof(float)), _grassTiles.ToArray()); } @@ -166,33 +196,43 @@ namespace hexworld GL.DepthFunc(DepthFunction.Lequal); GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha); - GL.Enable(EnableCap.CullFace); - GL.CullFace(CullFaceMode.Back); +// GL.Enable(EnableCap.CullFace); +// GL.CullFace(CullFaceMode.Back); - _pgm.Use(); + _jsonPgm.Use(); + + _jsonPgm.SetAttribPointers(_tileBuffer); + _jsonPgm.SetAttribPointers(_vertexBuffer); _grass.Bind(0); _stone.Bind(1); _gray.Bind(2); - GL.Uniform1(_pgm.GetUniform("tex"), 0); - GL.UniformMatrix4(_pgm.GetUniform("view"), false, ref _view); - GL.UniformMatrix4(_pgm.GetUniform("proj"), false, ref _proj); + GL.Uniform1(_jsonPgm.GetUniform("tex"), 0); + GL.UniformMatrix4(_jsonPgm.GetUniform("view"), false, ref _view); + GL.UniformMatrix4(_jsonPgm.GetUniform("proj"), false, ref _proj); _cubeMesh.DrawInstanced(_grassTiles); - GL.Uniform1(_pgm.GetUniform("tex"), 1); - GL.UniformMatrix4(_pgm.GetUniform("view"), false, ref _view); - GL.UniformMatrix4(_pgm.GetUniform("proj"), false, ref _proj); + GL.Uniform1(_jsonPgm.GetUniform("tex"), 1); _panelMesh.DrawInstanced(_stoneTiles); - GL.Uniform1(_pgm.GetUniform("tex"), 2); - GL.UniformMatrix4(_pgm.GetUniform("view"), false, ref _view); - GL.UniformMatrix4(_pgm.GetUniform("proj"), false, ref _proj); + GL.Uniform1(_jsonPgm.GetUniform("tex"), 2); _sidesMesh.DrawInstanced(_grayTiles); + _objPgm.Use(); + + _objPgm.SetAttribPointers(_tileBuffer); + _objPgm.SetAttribPointers(_objBuffer); + + GL.Uniform1(_jsonPgm.GetUniform("tex"), 2); + GL.UniformMatrix4(_jsonPgm.GetUniform("view"), false, ref _view); + GL.UniformMatrix4(_jsonPgm.GetUniform("proj"), false, ref _proj); + + _objMesh.DrawInstanced(_tableTiles); + SwapBuffers(); } } diff --git a/hexworld/hexworld.csproj b/hexworld/hexworld.csproj index eb97775..ca8cb8d 100644 --- a/hexworld/hexworld.csproj +++ b/hexworld/hexworld.csproj @@ -62,6 +62,9 @@ Always + + Always + Always @@ -73,9 +76,15 @@ + + Always + Always + + Always + Always @@ -85,7 +94,10 @@ Always - + + Designer + Always + diff --git a/hexworld/res/data_tile_table.json b/hexworld/res/data_tile_table.json new file mode 100644 index 0000000..7048bf1 --- /dev/null +++ b/hexworld/res/data_tile_table.json @@ -0,0 +1,6 @@ +[ + { "pos": { "x": 3, "y": 3, "z": 1 } }, + { "pos": { "x": -3, "y": 3, "z": 1 } }, + { "pos": { "x": 3, "y": -3, "z": 1 } }, + { "pos": { "x": -3, "y": -3, "z": 1 } }, +] \ No newline at end of file diff --git a/hexworld/res/gray.png b/hexworld/res/gray.png index 2b8dd16..f5a3bd4 100644 Binary files a/hexworld/res/gray.png and b/hexworld/res/gray.png differ diff --git a/hexworld/res/obj.fs.glsl b/hexworld/res/obj.fs.glsl new file mode 100644 index 0000000..632231d --- /dev/null +++ b/hexworld/res/obj.fs.glsl @@ -0,0 +1,14 @@ +#version 440 + +in vec2 vcoord; +in float light; + +uniform sampler2D tex; + +void main () +{ + vec4 color = texture(tex, vcoord); + color.w = 1; + color.xyz *= light; + gl_FragColor = clamp(color, 0, 1); +} \ No newline at end of file diff --git a/hexworld/res/obj.vs.glsl b/hexworld/res/obj.vs.glsl new file mode 100644 index 0000000..b64839a --- /dev/null +++ b/hexworld/res/obj.vs.glsl @@ -0,0 +1,22 @@ +#version 440 + +in vec3 position; +in vec3 glbpos; +in vec2 uv; +in vec3 normal; + +out vec2 vcoord; +out float light; + +uniform mat4 view; +uniform mat4 proj; + +void main () +{ + mat4 pv = proj * view; + vec3 pos = glbpos + position; + gl_Position = pv * vec4(pos, 1); + vcoord = uv; + + light = dot(vec3(0, 0, 1), normal) / 2 + .5; +} \ No newline at end of file diff --git a/hexworld/res/table.obj b/hexworld/res/table.obj new file mode 100644 index 0000000..71b00c4 --- /dev/null +++ b/hexworld/res/table.obj @@ -0,0 +1,251 @@ +# Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +v 0.500000 -0.500000 -0.636364 +v 0.500000 -0.500000 0.363636 +v -0.500000 -0.500000 0.363636 +v -0.500000 -0.500000 -0.636364 +v -0.277778 -0.500000 -0.636364 +v -0.277778 -0.500000 -0.414141 +v -0.277778 -0.500000 0.141414 +v 0.277778 -0.500000 0.141414 +v 0.277778 -0.500000 -0.414141 +v 0.277778 -0.500000 -0.636364 +v 0.500000 -0.277778 -0.636364 +v 0.277778 -0.277778 -0.636364 +v 0.500000 0.500000 -0.636364 +v 0.500000 0.500000 0.363636 +v 0.500000 -0.277778 -0.414141 +v 0.500000 -0.277778 0.141414 +v 0.500000 0.277778 0.141414 +v 0.500000 0.277778 -0.414141 +v 0.500000 0.277778 -0.636364 +v -0.500000 0.500000 0.363636 +v -0.500000 0.500000 -0.636364 +v -0.500000 0.277778 -0.636364 +v -0.500000 0.277778 -0.414141 +v -0.500000 0.277778 0.141414 +v -0.500000 -0.277778 0.141414 +v -0.500000 -0.277778 -0.414141 +v -0.500000 -0.277778 -0.636364 +v -0.277778 -0.277778 -0.636364 +v 0.277778 0.500000 -0.636364 +v 0.277778 0.277778 -0.636364 +v 0.277778 0.500000 -0.414141 +v 0.277778 0.500000 0.141414 +v -0.277778 0.500000 0.141414 +v -0.277778 0.500000 -0.414141 +v -0.277778 0.500000 -0.636364 +v -0.277778 0.277778 -0.636364 +v -0.277778 0.277778 -0.414141 +v -0.277778 0.277778 0.141414 +v -0.277778 -0.277778 0.141414 +v -0.277778 -0.277778 -0.414141 +v 0.277778 0.277778 0.141414 +v 0.277778 -0.277778 0.141414 +v 0.277778 0.277778 -0.414141 +v 0.277778 -0.277778 -0.414141 +vt 0.7500 1.0000 +vt 0.5000 1.0000 +vt 0.6944 0.8889 +vt 0.5000 0.5000 +vt 0.4444 0.3889 +vt 0.5000 0.3889 +vt 1.0000 1.0000 +vt 0.8056 0.8889 +vt 0.0000 -0.0000 +vt 0.2500 0.5000 +vt 0.0000 0.5000 +vt 0.2500 1.0000 +vt 0.3056 0.8889 +vt 0.2500 0.3889 +vt 0.3056 0.5000 +vt 0.2500 0.5000 +vt 0.5000 -0.0000 +vt 0.4444 0.1111 +vt 0.4444 -0.0000 +vt 0.0000 1.0000 +vt 0.1944 0.8889 +vt 0.2500 -0.0000 +vt 0.3056 0.1111 +vt 0.2500 0.1111 +vt 0.5000 0.6111 +vt 0.5556 0.8889 +vt 0.5000 0.8889 +vt 0.2500 0.1111 +vt 0.3056 0.3889 +vt 0.2500 0.3889 +vt 1.0000 0.8889 +vt 0.9444 0.6111 +vt 1.0000 0.6111 +vt 0.5000 0.5000 +vt 0.5556 0.6111 +vt 0.2500 0.8889 +vt 0.1944 0.6389 +vt 0.2500 0.6389 +vt 0.4444 0.1111 +vt 0.3056 0.1111 +vt 1.0000 0.5000 +vt 0.9444 0.5000 +vt 0.4444 -0.0000 +vt 0.3056 -0.0000 +vt 0.1944 0.5000 +vt 0.7500 0.6111 +vt 0.8056 0.8889 +vt 0.7500 0.8889 +vt 0.3056 0.5000 +vt 0.4444 0.3889 +vt 0.4444 0.5000 +vt 0.5000 0.3889 +vt 0.5000 0.1111 +vt 0.2500 0.6111 +vt 0.3056 0.8889 +vt 0.2500 0.8889 +vt 0.8056 0.5000 +vt 0.8056 0.6111 +vt 0.5000 0.8889 +vt 0.4444 0.6111 +vt 0.5000 0.6111 +vt 0.7500 0.8889 +vt 0.6944 0.6111 +vt 0.7500 0.6111 +vt -0.0000 0.6111 +vt 0.0556 0.8889 +vt -0.0000 0.8889 +vt 0.3056 0.5000 +vt 0.3056 0.6111 +vt 0.5000 0.5000 +vt 0.4444 0.5000 +vt 0.6944 0.5000 +vt -0.0000 0.5000 +vt 0.0556 0.6111 +vt 0.6944 0.5000 +vt 0.7500 0.5000 +vt 0.6944 0.6111 +vt 0.5000 0.5000 +vt 0.5556 0.8889 +vt 0.5556 0.5000 +vt 0.5556 0.6111 +vt 0.4444 0.5000 +vt 0.9444 0.5000 +vt 1.0000 0.5000 +vt 0.9444 0.6111 +vt 0.8056 0.6111 +vt 0.8056 0.5000 +vt 0.9444 0.8889 +vt 0.2500 -0.0000 +vt 0.4444 0.5000 +vt 0.4444 0.6111 +vt 0.2500 0.5000 +vt 0.3056 0.6111 +vt 0.3056 0.5000 +vt 0.4444 0.8889 +vt 0.3056 0.3889 +vt 0.5000 0.1111 +vt 0.1944 0.5000 +vt 0.1944 0.6111 +vt 0.0000 0.5000 +vt 0.0556 0.8889 +vt 0.0556 0.5000 +vt 0.0556 0.6111 +vt 0.3056 -0.0000 +vt 0.9444 0.8889 +vt 0.5556 0.5000 +vt 0.1944 0.8889 +vt 0.2500 0.5000 +vt 0.7500 0.5000 +vt 0.4444 0.8889 +vt 0.6944 0.8889 +vt 0.2500 0.5000 +vt 0.7500 0.5000 +vt 0.0556 0.5000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 2/1/1 3/2/1 8/3/1 +f 1/4/2 12/5/2 11/6/2 +f 14/7/3 2/1/3 16/8/3 +f 3/9/4 14/10/4 20/11/4 +f 3/2/5 20/12/5 24/13/5 +f 27/14/2 5/15/2 4/16/2 +f 13/17/2 30/18/2 29/19/2 +f 20/12/6 14/20/6 33/21/6 +f 21/22/2 36/23/2 22/24/2 +f 23/25/1 38/26/1 24/27/1 +f 24/28/2 39/29/2 25/30/2 +f 33/31/3 37/32/3 34/33/3 +f 22/34/1 37/35/1 23/25/1 +f 25/36/6 40/37/6 26/38/6 +f 41/39/2 39/29/2 38/40/2 +f 35/41/3 37/32/3 36/42/3 +f 32/43/2 38/40/2 33/44/2 +f 28/45/6 26/38/6 40/37/6 +f 6/46/3 39/47/3 7/48/3 +f 7/49/2 42/50/2 8/51/2 +f 16/52/2 41/39/2 17/53/2 +f 31/54/5 41/55/5 32/56/5 +f 6/46/3 28/57/3 40/58/3 +f 8/59/5 44/60/5 9/61/5 +f 17/62/1 43/63/1 18/64/1 +f 15/65/6 42/66/6 16/67/6 +f 31/54/5 30/68/5 43/69/5 +f 10/70/5 44/60/5 12/71/5 +f 30/72/1 18/64/1 43/63/1 +f 11/73/6 44/74/6 15/65/6 +f 10/75/1 1/76/1 9/77/1 +f 3/2/1 4/78/1 7/79/1 +f 5/80/1 6/81/1 4/78/1 +f 8/3/1 9/77/1 1/76/1 +f 4/78/1 6/81/1 7/79/1 +f 8/3/1 1/76/1 2/1/1 +f 3/2/1 7/79/1 8/3/1 +f 1/4/2 10/82/2 12/5/2 +f 19/83/3 13/84/3 18/85/3 +f 2/1/3 1/76/3 15/86/3 +f 1/76/3 11/87/3 15/86/3 +f 18/85/3 13/84/3 14/7/3 +f 17/88/3 18/85/3 14/7/3 +f 2/1/3 15/86/3 16/8/3 +f 16/8/3 17/88/3 14/7/3 +f 3/9/4 2/89/4 14/10/4 +f 27/90/5 4/78/5 26/91/5 +f 20/12/5 21/92/5 23/93/5 +f 21/92/5 22/94/5 23/93/5 +f 26/91/5 4/78/5 3/2/5 +f 25/95/5 26/91/5 3/2/5 +f 20/12/5 23/93/5 24/13/5 +f 24/13/5 25/95/5 3/2/5 +f 27/14/2 28/96/2 5/15/2 +f 13/17/2 19/97/2 30/18/2 +f 35/98/6 21/92/6 34/99/6 +f 14/20/6 13/100/6 32/101/6 +f 29/102/6 31/103/6 13/100/6 +f 33/21/6 34/99/6 21/92/6 +f 13/100/6 31/103/6 32/101/6 +f 33/21/6 21/92/6 20/12/6 +f 14/20/6 32/101/6 33/21/6 +f 21/22/2 35/104/2 36/23/2 +f 23/25/1 37/35/1 38/26/1 +f 24/28/2 38/40/2 39/29/2 +f 33/31/3 38/105/3 37/32/3 +f 22/34/1 36/106/1 37/35/1 +f 25/36/6 39/107/6 40/37/6 +f 41/39/2 42/50/2 39/29/2 +f 35/41/3 34/33/3 37/32/3 +f 32/43/2 41/39/2 38/40/2 +f 28/45/6 27/108/6 26/38/6 +f 6/46/3 40/58/3 39/47/3 +f 7/49/2 39/29/2 42/50/2 +f 16/52/2 42/50/2 41/39/2 +f 31/54/5 43/69/5 41/55/5 +f 6/46/3 5/109/3 28/57/3 +f 8/59/5 42/110/5 44/60/5 +f 17/62/1 41/111/1 43/63/1 +f 15/65/6 44/74/6 42/66/6 +f 31/54/5 29/112/5 30/68/5 +f 10/70/5 9/61/5 44/60/5 +f 30/72/1 19/113/1 18/64/1 +f 11/73/6 12/114/6 44/74/6