wavefront obj support

This commit is contained in:
2017-02-27 03:40:54 -05:00
parent 6385f62e20
commit 7a187c346e
8 changed files with 470 additions and 49 deletions

View File

@@ -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<T> FromJson<T>(string json) where T : struct
public static Mesh<T> FromJson<T>(string file) where T : struct
{
var vertices = JsonConvert.DeserializeObject<T[]>(json);
var vertices = JsonConvert.DeserializeObject<T[]>(File.ReadAllText(file));
return new Mesh<T>(vertices);
}
//todo add fromObj
public static Mesh<ObjVertex> FromObj(string file)
{
var lines = File.ReadAllLines(file);
var vs = new List<Vector3>();
var vts = new List<Vector2>();
var vns = new List<Vector3>();
var faces = new List<ObjVertex>();
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<ObjVertex>(vertices);
}
public static T[] Join<T>(params Mesh<T>[] meshes) where T : struct => Join((IEnumerable<Mesh<T>>) meshes);

View File

@@ -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<Tile> _tileGLBuffer;
private GLBuffer<Vertex> _vertexGLBuffer;
private GLBuffer<Tile> _tileBuffer;
private GLBuffer<Vertex> _vertexBuffer;
private GLBuffer<ObjVertex> _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<Tile> _grassTiles;
private SubArray<Tile> _stoneTiles;
private SubArray<Tile> _grayTiles;
private SubArray<Tile> _tableTiles;
private Mesh<Vertex> _cubeMesh;
private Mesh<Vertex> _panelMesh;
private Mesh<Vertex> _sidesMesh;
private Mesh<ObjVertex> _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<Vertex>(File.ReadAllText(@"res\data_vert_cubes.json"));
_panelMesh = Mesh.FromJson<Vertex>(File.ReadAllText(@"res\data_vert_panels.json"));
_sidesMesh = Mesh.FromJson<Vertex>(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<Vertex>(@"res\data_vert_cubes.json");
_panelMesh = Mesh.FromJson<Vertex>(@"res\data_vert_panels.json");
_sidesMesh = Mesh.FromJson<Vertex>(@"res\data_vert_sides.json");
_objMesh = Mesh.FromObj(@"res\table.obj");
_grassTiles = new SubArray<Tile>(
JsonConvert.DeserializeObject<Tile[]>(File.ReadAllText(@"res\data_tile_grass.json")));
@@ -111,18 +138,21 @@ namespace hexworld
JsonConvert.DeserializeObject<Tile[]>(File.ReadAllText(@"res\data_tile_stone.json")));
_grayTiles = new SubArray<Tile>(
JsonConvert.DeserializeObject<Tile[]>(File.ReadAllText(@"res\data_tile_gray.json")));
_tableTiles = new SubArray<Tile>(
JsonConvert.DeserializeObject<Tile[]>(File.ReadAllText(@"res\data_tile_table.json")));
_allTiles = SubArray.Join(_stoneTiles, _grassTiles, _grayTiles, _tableTiles);
_tileBuffer = new GLBuffer<Tile>(BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw);
_tileBuffer.Data(_allTiles);
_allTiles = SubArray.Join(_stoneTiles, _grassTiles, _grayTiles);
_allVertices = Mesh.Join(_panelMesh, _cubeMesh, _sidesMesh);
_vertexBuffer = new GLBuffer<Vertex>(BufferTarget.ArrayBuffer, BufferUsageHint.StaticDraw);
_vertexBuffer.Data(_allVertices);
_tileGLBuffer = new GLBuffer<Tile>(BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw);
_tileGLBuffer.Data(_allTiles);
_vertexGLBuffer = new GLBuffer<Vertex>(BufferTarget.ArrayBuffer, BufferUsageHint.StaticDraw);
_vertexGLBuffer.Data(_allVertices);
_pgm.SetAttribPointers(_tileGLBuffer);
_pgm.SetAttribPointers(_vertexGLBuffer);
_allObjVertices = Mesh.Join(_objMesh);
_objBuffer = new GLBuffer<ObjVertex>(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();
}
}

View File

@@ -62,6 +62,9 @@
<None Include="res\data_tile_gray.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\data_tile_table.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\data_tile_stone.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@@ -73,9 +76,15 @@
</None>
<None Include="OpenTK.dll.config" />
<None Include="packages.config" />
<None Include="res\obj.fs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\s.fs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\obj.vs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\s.vs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@@ -85,7 +94,10 @@
<None Include="res\data_tile_grass.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\sides.obj" />
<None Include="res\table.obj">
<SubType>Designer</SubType>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="res\grass.png">

View File

@@ -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 } },
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

14
hexworld/res/obj.fs.glsl Normal file
View File

@@ -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);
}

22
hexworld/res/obj.vs.glsl Normal file
View File

@@ -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;
}

251
hexworld/res/table.obj Normal file
View File

@@ -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