initial
This commit is contained in:
commit
ee4deb53fb
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
42
generate.py
Normal file
42
generate.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import io
|
||||||
|
|
||||||
|
def should_autonewline(line):
|
||||||
|
return (
|
||||||
|
"static_assert" not in line
|
||||||
|
and "extern" not in line
|
||||||
|
and (len(line.split()) < 2 or line.split()[1] != '=') # hacky; meh
|
||||||
|
)
|
||||||
|
|
||||||
|
def _render(out, lines, indent_length):
|
||||||
|
indent = " "
|
||||||
|
level = 0
|
||||||
|
namespace = 0
|
||||||
|
for l in lines:
|
||||||
|
if l and (l[0] == "}" or l[0] == ")"):
|
||||||
|
level -= indent_length
|
||||||
|
if level < 0:
|
||||||
|
assert namespace >= 0
|
||||||
|
namespace -= 1
|
||||||
|
level = 0
|
||||||
|
|
||||||
|
if len(l) == 0:
|
||||||
|
out.write("\n")
|
||||||
|
else:
|
||||||
|
out.write(indent * level + l + "\n")
|
||||||
|
|
||||||
|
if l and (l[-1] == "{" or l[-1] == "("):
|
||||||
|
if l.startswith("namespace"):
|
||||||
|
namespace += 1
|
||||||
|
else:
|
||||||
|
level += indent_length
|
||||||
|
|
||||||
|
if level == 0 and l and l[-1] == ";":
|
||||||
|
if should_autonewline(l):
|
||||||
|
out.write("\n")
|
||||||
|
return out
|
||||||
|
|
||||||
|
def renderer(indent_length=2):
|
||||||
|
out = io.StringIO()
|
||||||
|
def render(lines):
|
||||||
|
return _render(out, lines, indent_length)
|
||||||
|
return render, out
|
||||||
52
gltf.hpp
Normal file
52
gltf.hpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
template <typename T>
|
||||||
|
struct Array {
|
||||||
|
T * e;
|
||||||
|
int length;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mesh {
|
||||||
|
D3DXVECTOR3 * position;
|
||||||
|
D3DXVECTOR3 * normal;
|
||||||
|
D3DXVECTOR2 * texcoord_0;
|
||||||
|
DWORD * indices;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Skin;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
Skin * skin; // skin index (global)
|
||||||
|
Mesh * mesh; // mesh index (global)
|
||||||
|
D3DXVECTOR3 scale;
|
||||||
|
D3DXVECTOR3 translation;
|
||||||
|
D3DXQUATERNION rotation;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Skin {
|
||||||
|
D3DXMATRIX * inverse_bind_matrices; // accessor
|
||||||
|
Array<Node *> joints;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AnimationChannelPath {
|
||||||
|
ACP__WEIGHTS,
|
||||||
|
ACP__ROTATION,
|
||||||
|
ACP__TRANSLATION,
|
||||||
|
ACP__SCALE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimationSampler {
|
||||||
|
float * input; // accessor index, containing keyframe timestamps
|
||||||
|
void * output; // accessor index, containing keyframe values (type depends on channel target path)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimationChannel {
|
||||||
|
AnimationSampler * sampler; // sampler index, this animation
|
||||||
|
struct {
|
||||||
|
Node * node; // node index
|
||||||
|
AnimationChannelPath path; // property to animate
|
||||||
|
} target;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Animation {
|
||||||
|
Array<AnimationChannel> channels;
|
||||||
|
Array<AnimationSampler> samplers;
|
||||||
|
};
|
||||||
156
gltf.py
Normal file
156
gltf.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import struct
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
|
class GLTF:
|
||||||
|
def __init__(self, json, buffers):
|
||||||
|
# json: dict
|
||||||
|
# buffers: list[memoryview]
|
||||||
|
self.json = json
|
||||||
|
self.buffers = buffers
|
||||||
|
|
||||||
|
class Mesh:
|
||||||
|
def __init__(self, attributes, indices):
|
||||||
|
# attributes: list
|
||||||
|
# indices: list
|
||||||
|
self.attributes = attributes
|
||||||
|
self.indices = indices
|
||||||
|
|
||||||
|
def parse_header(mem, offset):
|
||||||
|
magic, version, length = struct.unpack("<III", mem[offset:offset + 12])
|
||||||
|
assert magic == 0x46546c67
|
||||||
|
assert version == 0x2
|
||||||
|
return offset + 12, length
|
||||||
|
|
||||||
|
def parse_json_chunk(mem, offset):
|
||||||
|
chunk_length, chunk_type = struct.unpack("<II", mem[offset:offset + 8])
|
||||||
|
assert chunk_type == 0x4e4f534a
|
||||||
|
data = json.loads(bytes(mem[offset + 8:offset + 8 + chunk_length]))
|
||||||
|
return offset + 8 + chunk_length, data
|
||||||
|
|
||||||
|
def parse_bin_chunk(mem, offset):
|
||||||
|
chunk_length, chunk_type = struct.unpack("<II", mem[offset:offset + 8])
|
||||||
|
assert chunk_type == 0x004e4942
|
||||||
|
data = mem[offset + 8:offset + 8 + chunk_length]
|
||||||
|
return offset + 8 + chunk_length, data
|
||||||
|
|
||||||
|
def component_type_format(n):
|
||||||
|
return {
|
||||||
|
5120: ("<b", 1), # "BYTE",
|
||||||
|
5121: ("<B", 1), # "UNSIGNED_BYTE",
|
||||||
|
5122: ("<h", 2), # "SHORT",
|
||||||
|
5123: ("<H", 2), # "UNSIGNED_SHORT",
|
||||||
|
5125: ("<I", 4), # "UNSIGNED_INT",
|
||||||
|
5126: ("<f", 4), # "FLOAT",
|
||||||
|
}[n]
|
||||||
|
|
||||||
|
def element_type_count(s):
|
||||||
|
return {
|
||||||
|
"SCALAR": 1,
|
||||||
|
"VEC2": 2,
|
||||||
|
"VEC3": 3,
|
||||||
|
"VEC4": 4,
|
||||||
|
"MAT2": 4,
|
||||||
|
"MAT3": 9,
|
||||||
|
"MAT4": 16,
|
||||||
|
}[s]
|
||||||
|
|
||||||
|
def decode_components(gltf, accessor):
|
||||||
|
components_per_element = element_type_count(accessor["type"])
|
||||||
|
accessor_count = accessor["count"]
|
||||||
|
|
||||||
|
accessor_buffer_view = accessor["bufferView"]
|
||||||
|
buffer_view = gltf.json["bufferViews"][accessor_buffer_view]
|
||||||
|
|
||||||
|
buffer = gltf.buffers[buffer_view["buffer"]]
|
||||||
|
|
||||||
|
accessor_byte_offset = accessor["byteOffset"] if "byteOffset" in accessor else 0
|
||||||
|
buffer_view_byte_offset = buffer_view.get("byteOffset", 0)
|
||||||
|
|
||||||
|
offset = accessor_byte_offset + buffer_view_byte_offset
|
||||||
|
buffer_end = offset + buffer_view["byteLength"]
|
||||||
|
|
||||||
|
accessor_component_type = accessor["componentType"]
|
||||||
|
format, size = component_type_format(accessor_component_type)
|
||||||
|
|
||||||
|
byte_stride = size * components_per_element
|
||||||
|
if "byteStride" in buffer_view:
|
||||||
|
assert buffer_view["byteStride"] >= byte_stride
|
||||||
|
byte_stride = buffer_view["byteStride"]
|
||||||
|
|
||||||
|
components = []
|
||||||
|
for _ in range(accessor_count):
|
||||||
|
for i in range(components_per_element):
|
||||||
|
start = offset + i * size
|
||||||
|
end = offset + i * size + size
|
||||||
|
assert end <= buffer_end
|
||||||
|
c, = struct.unpack(format, buffer[start:end])
|
||||||
|
components.append(c)
|
||||||
|
offset += byte_stride
|
||||||
|
return components
|
||||||
|
|
||||||
|
def decode_accessor(gltf, accessor):
|
||||||
|
components = decode_components(gltf, accessor)
|
||||||
|
components_per_element = element_type_count(accessor["type"])
|
||||||
|
for i in range(accessor["count"]):
|
||||||
|
if accessor["type"] == "SCALAR":
|
||||||
|
yield components[i]
|
||||||
|
else:
|
||||||
|
yield tuple(
|
||||||
|
components[i*components_per_element+j] for j in range(components_per_element)
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_mesh(gltf, mesh):
|
||||||
|
assert len(mesh["primitives"]) == 1
|
||||||
|
primitive, = mesh["primitives"]
|
||||||
|
assert "mode" not in primitive or primitive["mode"] == 4 # triangles
|
||||||
|
|
||||||
|
def decode_glb(mem):
|
||||||
|
offset = 0
|
||||||
|
offset, length = parse_header(mem, offset)
|
||||||
|
offset, json_chunk = parse_json_chunk(mem, offset)
|
||||||
|
offset, bin_chunk = parse_bin_chunk(mem, offset)
|
||||||
|
assert offset == length
|
||||||
|
gltf = GLTF(json_chunk, [bin_chunk])
|
||||||
|
return gltf
|
||||||
|
|
||||||
|
def remove_uri_prefix(uri):
|
||||||
|
prefixes = [
|
||||||
|
"data:application/octet-stream;base64,",
|
||||||
|
"data:application/gltf-buffer;base64,",
|
||||||
|
]
|
||||||
|
for prefix in prefixes:
|
||||||
|
if uri.startswith(prefix):
|
||||||
|
return uri[len(prefix):]
|
||||||
|
assert False, uri
|
||||||
|
|
||||||
|
def decode_gltf(mem):
|
||||||
|
gltf_json = json.loads(bytes(mem))
|
||||||
|
|
||||||
|
buffers = []
|
||||||
|
for buffer in gltf_json["buffers"]:
|
||||||
|
uri = buffer["uri"]
|
||||||
|
uri = remove_uri_prefix(uri)
|
||||||
|
data = base64.b64decode(uri)
|
||||||
|
assert len(data) == buffer["byteLength"]
|
||||||
|
buffers.append(memoryview(data))
|
||||||
|
|
||||||
|
gltf = GLTF(gltf_json, buffers)
|
||||||
|
return gltf
|
||||||
|
|
||||||
|
def decode_file(filename):
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
buf = f.read()
|
||||||
|
mem = memoryview(buf)
|
||||||
|
if filename.lower().endswith(".glb"):
|
||||||
|
return decode_glb(mem)
|
||||||
|
elif filename.lower().endswith(".gltf"):
|
||||||
|
return decode_gltf(mem)
|
||||||
|
else:
|
||||||
|
assert False, filename
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
gltf = decode_file(sys.argv[1])
|
||||||
|
import json
|
||||||
|
print(json.dumps(gltf.json, indent=4))
|
||||||
164
render_cpp.py
Normal file
164
render_cpp.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import sys
|
||||||
|
from pprint import pprint
|
||||||
|
from generate import renderer
|
||||||
|
|
||||||
|
from gltf import decode_file
|
||||||
|
from gltf import validate_mesh
|
||||||
|
from gltf import decode_accessor
|
||||||
|
|
||||||
|
filename = sys.argv[1]
|
||||||
|
gltf = decode_file(filename)
|
||||||
|
|
||||||
|
def type_name(type):
|
||||||
|
return {
|
||||||
|
"SCALAR": "DWORD",
|
||||||
|
"VEC2": "D3DXVECTOR2",
|
||||||
|
"VEC3": "D3DXVECTOR3",
|
||||||
|
"VEC4": "D3DXVECTOR4",
|
||||||
|
# MAT2
|
||||||
|
# MAT3
|
||||||
|
"MAT4": "D3DXMATRIX",
|
||||||
|
}[type]
|
||||||
|
|
||||||
|
def float_s(f):
|
||||||
|
return f"{f:10.7f}f"
|
||||||
|
|
||||||
|
def sv(v, c_type):
|
||||||
|
args = ", ".join(
|
||||||
|
float_s(c) for c in v
|
||||||
|
)
|
||||||
|
return f"{c_type}({args})"
|
||||||
|
|
||||||
|
def mv(v, c_type):
|
||||||
|
assert len(v) == 16
|
||||||
|
assert c_type == "D3DXMATRIX"
|
||||||
|
|
||||||
|
v = [float_s(c) for c in v]
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
D3DXMATRIX({v[ 0]}, {v[ 1]}, {v[ 2]}, {v[ 3]},
|
||||||
|
{v[ 4]}, {v[ 5]}, {v[ 6]}, {v[ 7]},
|
||||||
|
{v[ 8]}, {v[ 9]}, {v[10]}, {v[11]},
|
||||||
|
{v[12]}, {v[13]}, {v[14]}, {v[15]})
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
def render_value(value, c_type):
|
||||||
|
if "MAT" in c_type:
|
||||||
|
return mv(value, c_type)
|
||||||
|
elif "VEC" in c_type:
|
||||||
|
return sv(value, c_type)
|
||||||
|
elif type(value) in {int, float}:
|
||||||
|
return f"{value}"
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def render_accessors(gltf):
|
||||||
|
for accessor_ix, accessor in enumerate(gltf.json["accessors"]):
|
||||||
|
components = list(decode_accessor(gltf, accessor))
|
||||||
|
accessor_name = f"accessor_{accessor_ix}"
|
||||||
|
accessor_type = accessor['type']
|
||||||
|
if type(components[0]) in {int, float}:
|
||||||
|
c_type = "DWORD" if type(components[0]) is int else "float"
|
||||||
|
else:
|
||||||
|
c_type = type_name(accessor_type)
|
||||||
|
yield f"const {c_type} {accessor_name}[] = {{"
|
||||||
|
for v in components:
|
||||||
|
yield f"{render_value(v, c_type)},"
|
||||||
|
yield "};"
|
||||||
|
yield f"const int {accessor_name}_length = (sizeof ({accessor_name})) / (sizeof ({accessor_name}[0]));"
|
||||||
|
|
||||||
|
def render_meshes(gltf):
|
||||||
|
for mesh_ix, mesh in enumerate(gltf.json["meshes"]):
|
||||||
|
validate_mesh(gltf, mesh)
|
||||||
|
primitive, = mesh["primitives"]
|
||||||
|
attributes = primitive["attributes"]
|
||||||
|
position = attributes["POSITION"]
|
||||||
|
normal = attributes.get("NORMAL", None)
|
||||||
|
texcoord_0 = attributes.get("TEXCOORD_0", None)
|
||||||
|
indices = primitive["indices"]
|
||||||
|
yield f"const Mesh mesh_{mesh_ix} = {{"
|
||||||
|
yield f"accessor_{position}, // position"
|
||||||
|
yield f"accessor_{normal}, // normal" if normal is not None else "NULL,"
|
||||||
|
yield f"accessor_{texcoord_0}, // texcoord_0" if texcoord_0 is not None else "NULL,"
|
||||||
|
yield f"accessor_{indices}, // indices"
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
def render_nodes(gltf):
|
||||||
|
for node in gltf.json["nodes"]:
|
||||||
|
if "skin" not in node:
|
||||||
|
continue
|
||||||
|
skin = node["skin"]
|
||||||
|
yield f"const Skin skin_{skin};"
|
||||||
|
|
||||||
|
for node_ix, node in enumerate(gltf.json["nodes"]):
|
||||||
|
skin = f"&skin_{node['skin']}" if "skin" in node else "NULL"
|
||||||
|
mesh = f"&mesh_{node['skin']}" if "mesh" in node else "NULL"
|
||||||
|
|
||||||
|
scale = (1, 1, 1)
|
||||||
|
translation = (0, 0, 0)
|
||||||
|
rotation = (0, 0, 0, 1)
|
||||||
|
if "scale" in node:
|
||||||
|
scale = node["scale"]
|
||||||
|
if "translation" in node:
|
||||||
|
translation = node["translation"]
|
||||||
|
if "rotation" in node:
|
||||||
|
rotation = node["rotation"]
|
||||||
|
|
||||||
|
yield f"const Node node_{node_ix} = {{"
|
||||||
|
yield f"{skin}, // skin"
|
||||||
|
yield f"{mesh}, // mesh"
|
||||||
|
yield f"{render_value(scale, 'D3DXVECTOR3')}, // scale"
|
||||||
|
yield f"{render_value(translation, 'D3DXVECTOR3')}, // translation"
|
||||||
|
yield f"{render_value(rotation, 'D3DXVECTOR4')}, // rotation"
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
def render_skins(gltf):
|
||||||
|
for skin_ix, skin in enumerate(gltf.json["skins"]):
|
||||||
|
yield f"const Node * skin_{skin_ix}__joints[] = {{"
|
||||||
|
for joint in skin["joints"]:
|
||||||
|
yield f"&node_{joint},"
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
for skin_ix, skin in enumerate(gltf.json["skins"]):
|
||||||
|
inverse_bind_matrices = skin["inverseBindMatrices"]
|
||||||
|
yield f"const Skin skin_{skin_ix} = {{"
|
||||||
|
yield f"accessor_{inverse_bind_matrices}, // inverse bind matrices"
|
||||||
|
yield f"{{ skin_{skin_ix}__joints, {len(skin['joints'])} }},"
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
def render_animation_samplers(animation_ix, samplers):
|
||||||
|
for sampler_ix, sampler in enumerate(samplers):
|
||||||
|
yield f"const AnimationSampler animation_{animation_ix}__sampler_{sampler_ix} = {{"
|
||||||
|
yield f"accessor_{sampler['input']}, // input, keyframe timestamps"
|
||||||
|
yield f"accessor_{sampler['output']}, // output, keyframe values (void *)"
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
|
||||||
|
def render_animation_channels(animation_ix, channels):
|
||||||
|
yield f"const AnimationChannel animation_{animation_ix}__channels[] = {{"
|
||||||
|
for channel in channels:
|
||||||
|
sampler = channel["sampler"]
|
||||||
|
target_node = channel["target"]["node"]
|
||||||
|
target_path = channel["target"]["path"]
|
||||||
|
yield f"&animation_{animation_ix}__sampler_{sampler}, // animation sampler"
|
||||||
|
yield "{"
|
||||||
|
yield f"&node_{target_node}, // target node"
|
||||||
|
yield f"ACP__{target_path.upper()}, // target path"
|
||||||
|
yield "},"
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
def render_animations(gltf):
|
||||||
|
for animation_ix, animation in enumerate(gltf.json["animations"]):
|
||||||
|
yield from render_animation_samplers(animation_ix, animation["samplers"])
|
||||||
|
yield from render_animation_channels(animation_ix, animation["channels"])
|
||||||
|
|
||||||
|
def render_gltf(gltf):
|
||||||
|
yield from render_accessors(gltf)
|
||||||
|
yield from render_meshes(gltf)
|
||||||
|
yield from render_nodes(gltf)
|
||||||
|
yield from render_skins(gltf)
|
||||||
|
yield from render_animations(gltf)
|
||||||
|
|
||||||
|
render, out = renderer()
|
||||||
|
render(render_gltf(gltf))
|
||||||
|
print(out.getvalue())
|
||||||
Loading…
x
Reference in New Issue
Block a user