Last active
September 7, 2017 23:23
-
-
Save scurest/3c67b60e900f4142436730eef8b68f92 to your computer and use it in GitHub Desktop.
Generates MetalRoughSpheres
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python | |
"""Generates MetalRoughSpheres. | |
Output is written to MetalRoughSpheres.gltf and MetalRoughSpheres.bin. | |
""" | |
from math import sqrt | |
import base64 | |
import json | |
import struct | |
# http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html | |
class Icosphere: | |
def vert(self, co, uv): | |
mag = sqrt(sum([x**2 for x in co])) | |
normalized = [x/mag for x in co] | |
self.positions.append(normalized) | |
self.uvs.append(uv) | |
def __init__(self): | |
"""Build a regular icosahedron.""" | |
self.positions = [] | |
self.uvs = [] | |
self.tris = [] | |
self.vert([-0.525731, 0.850651, 0.0], [0.3397, 0.694]) | |
self.vert([-0.850651, -0.0, 0.525731], [0.3397, 0.5089]) | |
self.vert([0.0, 0.525731, 0.850651], [0.5, 0.6015]) | |
self.vert([0.525731, 0.850651, 0.0], [0.5, 0.7866]) | |
self.vert([0.525731, 0.850651, 0.0], [0.3397, 0.8791]) | |
self.vert([0.0, 0.525731, -0.850651], [0.1794, 0.7866]) | |
self.vert([-0.850651, 0.0, -0.525731], [0.1794, 0.6015]) | |
self.vert([0.525731, 0.850651, 0.0], [0.6603, 0.694]) | |
self.vert([0.850651, -0.0, 0.525731], [0.6603, 0.5089]) | |
self.vert([0.0, -0.525731, 0.850651], [0.5, 0.4164]) | |
self.vert([-0.525731, -0.850651, -0.0], [0.1794, 0.4164]) | |
self.vert([0.0, -0.525731, -0.850651], [0.0191, 0.694]) | |
self.vert([0.0, 0.525731, -0.850651], [0.9809, 0.3238]) | |
self.vert([0.525731, 0.850651, 0.0], [0.9809, 0.5089]) | |
self.vert([0.850651, 0.0, -0.525731], [0.8206, 0.4164]) | |
self.vert([0.525731, -0.850651, -0.0], [0.6603, 0.3238]) | |
self.vert([-0.525731, -0.850651, -0.0], [0.5, 0.2313]) | |
self.vert([-0.525731, -0.850651, -0.0], [0.6603, 0.1388]) | |
self.vert([0.0, -0.525731, -0.850651], [0.8206, 0.2313]) | |
self.vert([-0.525731, -0.850651, -0.0], [0.3397, 0.3238]) | |
self.vert([-0.525731, -0.850651, -0.0], [0.0191, 0.5089]) | |
self.vert([0.525731, 0.850651, 0.0], [0.8206, 0.6015]) | |
self.tris.append([0, 1, 2]) | |
self.tris.append([0, 2, 3]) | |
self.tris.append([0, 4, 5]) | |
self.tris.append([0, 5, 6]) | |
self.tris.append([0, 6, 1]) | |
self.tris.append([7, 2, 8]) | |
self.tris.append([2, 1, 9]) | |
self.tris.append([1, 6, 10]) | |
self.tris.append([6, 5, 11]) | |
self.tris.append([12, 13, 14]) | |
self.tris.append([15, 8, 9]) | |
self.tris.append([15, 9, 16]) | |
self.tris.append([15, 17, 18]) | |
self.tris.append([15, 18, 14]) | |
self.tris.append([15, 14, 8]) | |
self.tris.append([9, 8, 2]) | |
self.tris.append([19, 9, 1]) | |
self.tris.append([11, 20, 6]) | |
self.tris.append([14, 18, 12]) | |
self.tris.append([8, 14, 21]) | |
def refine(self): | |
midpoint_cache = {} | |
def midpoint(v1, v2): | |
v1, v2 = sorted((v1, v2)) | |
if (v1, v2) in midpoint_cache: | |
return midpoint_cache[(v1, v2)] | |
midpos = [(x+y)/2 for x,y in zip(self.positions[v1], self.positions[v2])] | |
miduv = [(x+y)/2 for x,y in zip(self.uvs[v1], self.uvs[v2])] | |
self.vert(midpos, miduv) | |
index = len(self.positions) - 1 | |
midpoint_cache[(v1, v2)] = index | |
return index | |
new_tris = [] | |
for tri in self.tris: | |
# /\ /\ | |
# / \ ==> /__\ | |
# / \ ==> /\ /\ | |
# /_____ \ /__\/__\ | |
a = midpoint(tri[0], tri[1]) | |
b = midpoint(tri[1], tri[2]) | |
c = midpoint(tri[2], tri[0]) | |
new_tris.append([tri[0], a, c]) | |
new_tris.append([tri[1], b, a]) | |
new_tris.append([tri[2], c, b]) | |
new_tris.append([a, b, c]) | |
self.tris = new_tris | |
class MetalRoughSpheres: | |
def add_buffer_data(self, data, align=1): | |
off = len(self.buf) | |
if off % align != 0: | |
self.buf += 'b\0' * ((- off) % align) | |
self.buf += data | |
return off | |
def add_buffer_view(self, data, align=1, stride=None): | |
data_off = self.add_buffer_data(data, align=align) | |
view = { | |
'buffer': 0, | |
'byteOffset': data_off, | |
'byteLength': len(data), | |
} | |
if stride: | |
view['byteStride'] = stride | |
self.gltf['bufferViews'].append(view) | |
buffer_view_idx = len(self.gltf['bufferViews']) - 1 | |
return buffer_view_idx | |
def add_accessor(self, accessor): | |
self.gltf['accessors'].append(accessor) | |
return len(self.gltf['accessors']) - 1 | |
def finish(self, gltf_path, bin_path): | |
"""Write the output.""" | |
self.gltf['buffers'] = [{ | |
'uri': bin_path, | |
'byteLength': len(self.buf), | |
}] | |
with open(gltf_path, 'w+') as fp: | |
json.dump(self.gltf, fp, indent=4) | |
with open(bin_path, 'wb+') as fp: | |
fp.write(self.buf) | |
def create_spheres(self): | |
# Build an icosphere | |
icosphere = Icosphere() | |
for _ in range(0, 4): | |
icosphere.refine() | |
pos = [x for v in icosphere.positions for x in v] | |
uvs = [x for v in icosphere.uvs for x in v] | |
indices = [i for tri in icosphere.tris for i in tri] | |
assert(len(indices) < 2**16) # Make sure we can use u16 for the index data | |
# Write buffers/bufferViews/accessors for the icosphere positions/indices. | |
# These are reused for every sphere. | |
pos_data = struct.pack('<%sf' % len(pos), *pos) | |
index_data = struct.pack('<%sH' % len(indices), *indices) | |
pos_buffer_view =self.add_buffer_view(pos_data, align=4) | |
index_buffer_view =self.add_buffer_view(index_data, align=2) | |
pos_accessor = self.add_accessor({ | |
'bufferView': pos_buffer_view, | |
'type': 'VEC3', | |
'componentType': 5126, # float | |
'count': len(pos)//3, | |
'min': [-1, -1, -1], | |
'max': [1, 1, 1], | |
}) | |
index_accessor = self.add_accessor({ | |
'bufferView': index_buffer_view, | |
'type': 'SCALAR', | |
'componentType': 5123, # ushort | |
'count': len(indices), | |
}) | |
# Write data for the texcoords for every sphere. | |
# We'll pack them all into one bufferview. | |
uv_data_list = [] | |
# Scale down and move the UVs away from the gutters where the labels are. | |
# |gutter|col1| col2 | col3 |... | |
# The UVs should fit into col1. | |
col_width = 1/14 | |
gutter_width = 24/1024 | |
scale = col_width - gutter_width | |
for off in range(0, len(uvs), 2): | |
uvs[off] *= scale | |
uvs[off] += gutter_width | |
uvs[off+1] *= scale | |
for du in [0, col_width]: | |
for i in range(0, 7): | |
for j in range(0, 7): | |
new_uvs = uvs[:] | |
# Translate into position | |
for off in range(0, len(new_uvs), 2): | |
new_uvs[off] += i * 2 * col_width + du | |
new_uvs[off+1] += j * 2 * col_width | |
uv_data_list.append(struct.pack('<%sf' % len(new_uvs), *new_uvs)) | |
uv_data = b''.join(uv_data_list) | |
uv_buffer_view = self.add_buffer_view(uv_data, align=4, stride=8) | |
uv_byte_length = len(uvs) * 4 | |
# Create the grid of spheres; one mesh/texcoord accessor for every sphere. | |
num_spheres = 7 * 7 * 2 | |
self.gltf['scene'] = 0 | |
self.gltf['scenes'] = [ { 'nodes': [0] } ] | |
self.gltf['nodes'] = [{ | |
'scale': [0.4, 0.4, 0.4], | |
'children': list(range(1, 1+num_spheres)), | |
}] | |
idx = 0 | |
for z in [0, 8]: | |
for i in range(0, 7): | |
for j in range(0, 7): | |
uv_accessor = self.add_accessor({ | |
'bufferView': uv_buffer_view, | |
'type': 'VEC2', | |
'componentType': 5126, # float | |
'count': len(uvs)//2, | |
'byteOffset': idx * uv_byte_length, | |
}) | |
self.gltf['meshes'].append({ | |
'primitives': [{ | |
'attributes': { | |
'POSITION': pos_accessor, | |
'NORMAL': pos_accessor, | |
'TEXCOORD_0': uv_accessor | |
}, | |
'indices': index_accessor, | |
'material': 0, | |
}] | |
}) | |
mesh_id = len(self.gltf['meshes']) - 1 | |
x = -3*i + 9 | |
y = -3*j + 9 | |
self.gltf['nodes'].append({ | |
'name': 'M(%d, %d)' % (i,j), # useful for debugging | |
'mesh': mesh_id, | |
'translation': [x, y, z], | |
}) | |
idx += 1 | |
def create_labels(self): | |
# Create the metal/non-metal and rough/smooth labels. | |
# This data was dumped out of MetalRoughSpheres.blend. | |
pos = [ | |
4.607307, -12.359449, 0, | |
10.713691, -11.081038, 0, | |
10.713691, -12.359448, 0, | |
-11.874729, -12.359450, 0, | |
-3.906647, -11.081039, 0, | |
-3.906647, -12.359449, 0, | |
10.982860, 3.929994, 0, | |
12.186588, 10.969941, 0, | |
12.186589, 3.929994, 0, | |
10.713691, -12.359448, 8, | |
4.607307, -11.081039, 8, | |
4.607307, -12.359449, 8, | |
12.186590, -11.753379, 0, | |
10.982860, -3.775491, 0, | |
12.186589, -3.775488, 0, | |
-3.906648, -12.359449, 8, | |
-11.874729, -11.081040, 8, | |
-11.874729, -12.359450, 8, | |
12.186589, 3.929994, 8, | |
10.982859, 10.969941, 8, | |
10.982860, 3.929994, 8, | |
10.982861, -11.753379, 8, | |
12.186589, -3.775491, 8, | |
10.982860, -3.775488, 8, | |
4.607307, -11.081039, 0, | |
-11.874729, -11.081040, 0, | |
10.982859, 10.969941, 0, | |
10.713691, -11.081038, 8, | |
10.982861, -11.753379, 0, | |
-3.906648, -11.081039, 8, | |
12.186588, 10.969941, 8, | |
12.186590, -11.753379, 8, | |
] | |
normals = [ | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, 1, | |
0, 0, -1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
] | |
uvs = [ | |
0.1175, 0.9988, | |
0.0205, 0.9785, | |
0.0205, 0.9988, | |
0.9870, 0.9988, | |
0.8604, 0.9785, | |
0.8604, 0.9988, | |
0.0205, 0.1284, | |
0.0014, 0.0166, | |
0.0014, 0.1284, | |
0.1175, 0.9988, | |
0.0205, 0.9785, | |
0.0205, 0.9988, | |
0.0014, 0.9988, | |
0.0205, 0.8721, | |
0.0014, 0.8721, | |
0.9870, 0.9988, | |
0.8604, 0.9785, | |
0.8604, 0.9988, | |
0.0205, 0.1284, | |
0.0014, 0.0166, | |
0.0014, 0.1284, | |
0.0014, 0.9988, | |
0.0205, 0.8721, | |
0.0014, 0.8721, | |
0.1175, 0.9785, | |
0.9870, 0.9785, | |
0.0205, 0.0166, | |
0.1175, 0.9785, | |
0.0205, 0.9988, | |
0.9870, 0.9785, | |
0.0205, 0.0166, | |
0.0205, 0.9988, | |
] | |
indices = [ | |
0, 1, 2, | |
3, 4, 5, | |
6, 7, 8, | |
9, 10, 11, | |
12, 13, 14, | |
15, 16, 17, | |
18, 19, 20, | |
21, 22, 23, | |
0, 24, 1, | |
3, 25, 4, | |
6, 26, 7, | |
9, 27, 10, | |
12, 28, 13, | |
15, 29, 16, | |
18, 30, 19, | |
21, 31, 22, | |
] | |
assert(len(indices) < 2**8) | |
pos_data = struct.pack('<%sf' % len(pos), *pos) | |
normal_data = struct.pack('<%sf' % len(normals), *normals) | |
uv_data = struct.pack('<%sf' % len(uvs), *uvs) | |
index_data = struct.pack('<%sB' % len(indices), *indices) | |
pos_buffer_view =self.add_buffer_view(pos_data, align=4) | |
normal_buffer_view =self.add_buffer_view(normal_data, align=4) | |
uv_buffer_view =self.add_buffer_view(uv_data, align=4) | |
index_buffer_view =self.add_buffer_view(index_data, align=1) | |
pos_accessor = self.add_accessor({ | |
'bufferView': pos_buffer_view, | |
'type': 'VEC3', | |
'componentType': 5126, # float | |
'count': len(pos)//3, | |
'min': [-11.874729, -12.35945, 0], | |
'max': [12.186589, 10.969941, 8], | |
}) | |
normal_accessor = self.add_accessor({ | |
'bufferView': normal_buffer_view, | |
'type': 'VEC3', | |
'componentType': 5126, # float | |
'count': len(normals)//3, | |
}) | |
uv_accessor = self.add_accessor({ | |
'bufferView': uv_buffer_view, | |
'type': 'VEC2', | |
'componentType': 5126, # float | |
'count': len(uvs)//2, | |
}) | |
index_accessor = self.add_accessor({ | |
'bufferView': index_buffer_view, | |
'type': 'SCALAR', | |
'componentType': 5121, # ubyte | |
'count': len(indices), | |
}) | |
self.gltf['meshes'].append({ | |
'name': 'Labels', | |
'primitives': [{ | |
'attributes': { | |
'POSITION': pos_accessor, | |
'NORMAL': normal_accessor, | |
'TEXCOORD_0': uv_accessor, | |
}, | |
'indices': index_accessor, | |
'material': 0, | |
}] | |
}) | |
mesh_id = len(self.gltf['meshes']) - 1 | |
self.gltf['nodes'].append({ | |
'name': 'Labels', | |
'mesh': mesh_id, | |
}) | |
node_id = len(self.gltf['nodes']) - 1 | |
self.gltf['nodes'][0]['children'].append(node_id) | |
def __init__(self): | |
self.buf = b'' | |
self.gltf = { | |
'asset': { 'version' : '2.0' }, | |
'nodes': [], | |
'meshes': [], | |
'accessors': [], | |
'materials': [ | |
{ | |
'pbrMetallicRoughness': { | |
'baseColorTexture': { 'index': 0 }, | |
'metallicRoughnessTexture': { 'index': 1 }, | |
} | |
} | |
], | |
'textures': [ | |
{ 'sampler': 0, 'source': 0 }, | |
{ 'sampler': 0, 'source': 1 }, | |
], | |
'images': [ | |
{ 'uri': 'Spheres_BaseColor.png' }, | |
{ 'uri': 'Spheres_MetalRough.png' }, | |
], | |
'samplers': [ | |
{ | |
'magFilter': 9729, | |
'minFilter': 9986, | |
'wrapS': 33071, | |
'wrapT': 33071 | |
} | |
], | |
'bufferViews': [], | |
'buffers': [], | |
} | |
self.create_spheres() | |
self.create_labels() | |
mrs = MetalRoughSpheres() | |
mrs.finish('MetalRoughSpheres.gltf', 'MetalRoughSpheres.bin') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment