md5: add md5anim

This commit is contained in:
Zack Buhman 2025-05-13 00:52:03 -05:00
parent efe6470afe
commit 1a290c03d2
7 changed files with 29507 additions and 2 deletions

View File

@ -47,8 +47,10 @@ using vec3 = vec<3, float>;
using vec4 = vec<4, float>; using vec4 = vec<4, float>;
using mat4x4 = mat<4, 4, float>; using mat4x4 = mat<4, 4, float>;
#include "md5/md5.h" #include "md5/md5mesh.h"
#include "model/boblamp/boblamp.h" #include "md5/md5anim.h"
#include "model/boblamp/boblamp_mesh.h"
#include "model/boblamp/boblamp_anim.h"
static int joint_ix_sel = 0; static int joint_ix_sel = 0;

29
md5/md5anim.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
struct md5_anim_hierarchy {
const char * name;
int parent_index;
int flags;
int start_index;
};
struct md5_anim_bounds {
vec3 min;
vec3 max;
};
struct md5_anim_base_frame {
vec3 pos;
vec4 orient;
};
struct md5_anim {
int num_frames;
int num_joints;
int frame_rate;
int num_animated_components;
md5_anim_hierarchy * hierarchy;
md5_anim_bounds * bounds;
md5_anim_base_frame * base_frame;
float ** frame;
};

187
md5/md5anim.py Normal file
View File

@ -0,0 +1,187 @@
import sys
from dataclasses import dataclass, field
from pprint import pprint
@dataclass
class MD5AnimFrame:
value: list[float] = field(default_factory=lambda: list())
@dataclass
class MD5AnimBaseFrame:
pos_x: float = None
pos_y: float = None
pos_z: float = None
orient_x: float = None
orient_y: float = None
orient_z: float = None
@dataclass
class MD5AnimBounds:
min_x: float = None
min_y: float = None
min_z: float = None
max_x: float = None
max_y: float = None
max_z: float = None
@dataclass
class MD5AnimHierarchy:
name: str = None
parent_index: int = None
flags: int = None
start_index: int = None
@dataclass
class MD5Anim:
num_frames: int = None
num_joints: int = None
frame_rate: int = None
num_animated_components: int = None
hierarchy: list[MD5AnimHierarchy] = field(default_factory=lambda: list())
bounds: list[MD5AnimBounds] = field(default_factory=lambda: list())
base_frame: list[MD5AnimBaseFrame] = field(default_factory=lambda: list())
frame: list[MD5AnimFrame] = field(default_factory=lambda: list())
def parse_header(l, ix, md5anim):
assert l[ix+0] == "MD5Version 10"
assert l[ix+1].startswith("commandline")
assert l[ix+2].startswith("numFrames ")
assert l[ix+3].startswith("numJoints ")
assert l[ix+4].startswith("frameRate ")
assert l[ix+5].startswith("numAnimatedComponents ")
md5anim.num_frames = int(l[ix+2].removeprefix("numFrames "), 10)
md5anim.num_joints = int(l[ix+3].removeprefix("numJoints "), 10)
md5anim.frame_rate = int(l[ix+4].removeprefix("frameRate "), 10)
md5anim.num_animated_components = int(l[ix+5].removeprefix("numAnimatedComponents "), 10)
return ix + 6
def parse_hierarchy(l, ix, md5anim):
s = l[ix]
lc = s.split("//", maxsplit=1)
if len(lc) == 2:
line, comment = lc
elif len(lc) == 1:
line = lc
comment = None
else:
assert False, len(lc)
tokens = line.split()
hierarchy = MD5AnimHierarchy()
# name
name = tokens[0]
assert name.startswith('"') and name.endswith('"')
hierarchy.name = name[1:-1]
hierarchy.parent_index = int(tokens[1], 10)
hierarchy.flags = int(tokens[2], 10)
hierarchy.start_index = int(tokens[3], 10)
md5anim.hierarchy.append(hierarchy)
return ix + 1
def parse_bounds(l, ix, md5anim):
line = l[ix]
tokens = line.split()
bounds = MD5AnimBounds()
assert tokens[0] == "("
bounds.min_x = float(tokens[1])
bounds.min_y = float(tokens[2])
bounds.min_z = float(tokens[3])
assert tokens[4] == ")"
assert tokens[5] == "("
bounds.max_x = float(tokens[6])
bounds.max_y = float(tokens[7])
bounds.max_z = float(tokens[8])
assert tokens[9] == ")"
md5anim.bounds.append(bounds)
return ix + 1
def parse_base_frame(l, ix, md5anim):
line = l[ix]
tokens = line.split()
base_frame = MD5AnimBaseFrame()
assert tokens[0] == "("
base_frame.pos_x = float(tokens[1])
base_frame.pos_y = float(tokens[2])
base_frame.pos_z = float(tokens[3])
assert tokens[4] == ")"
assert tokens[5] == "("
base_frame.orient_x = float(tokens[6])
base_frame.orient_y = float(tokens[7])
base_frame.orient_z = float(tokens[8])
assert tokens[9] == ")"
md5anim.base_frame.append(base_frame)
return ix + 1
def parse_frame(l, ix, md5anim):
frame = MD5AnimFrame()
while l[ix] != "}":
tokens = l[ix].split()
for token in tokens:
value = float(token)
frame.value.append(value)
ix += 1
assert len(frame.value) == md5anim.num_animated_components
md5anim.frame.append(frame)
return ix
def parse_ordered_list(l, ix, md5anim):
assert l[ix].endswith("{"), l[ix]
string, _ = l[ix].rsplit(maxsplit=1)
ix += 1
if string == "hierarchy":
while l[ix] != "}":
ix = parse_hierarchy(l, ix, md5anim)
elif string == "bounds":
while l[ix] != "}":
ix = parse_bounds(l, ix, md5anim)
elif string == "baseframe":
while l[ix] != "}":
ix = parse_base_frame(l, ix, md5anim)
elif string.startswith("frame"):
frame, frame_ix = string.split()
assert int(frame_ix) == len(md5anim.frame)
while l[ix] != "}":
ix = parse_frame(l, ix, md5anim)
assert l[ix] == "}", l[ix]
ix += 1
return ix
def parse_file(l):
ix = 0
md5anim = MD5Anim()
ix = parse_header(l, ix, md5anim)
while ix < len(l):
ix = parse_ordered_list(l, ix, md5anim)
assert len(md5anim.hierarchy) == md5anim.num_joints, (len(md5anim.hierarchy))
assert len(md5anim.bounds) == md5anim.num_frames, (len(md5anim.bounds))
assert len(md5anim.base_frame) == md5anim.num_joints, (len(md5anim.base_frame))
assert len(md5anim.frame) == md5anim.num_frames, (len(md5anim.frame))
return md5anim
if __name__ == "__main__":
with open(sys.argv[1], 'r') as f:
buf = f.read()
l = [i.strip() for i in buf.split('\n') if i.strip()]
md5anim = parse_file(l)
pprint(md5anim)

94
md5/md5anim_gen.py Normal file
View File

@ -0,0 +1,94 @@
from generate import renderer
import sys
import md5anim
from md5mesh_gen import vec2, vec3, vec4, unit_quaternion_w
def _render_md5_anim_hierarchy(h):
yield f'.name = "{h.name}",'
yield f".parent_index = {h.parent_index},"
yield f".flags = {h.flags},"
yield f".start_index = {h.start_index},"
def render_md5_anim_hierarchy(prefix, hierarchy):
yield f"struct md5_anim_hierarchy {prefix}_hierarchy[] = {{"
for h in hierarchy:
yield "{"
yield from _render_md5_anim_hierarchy(h)
yield "},"
yield "};"
def _render_md5_anim_bounds(b):
min = vec3(b.min_x, b.min_y, b.min_z)
max = vec3(b.max_x, b.max_y, b.max_z)
yield f".min = {min},"
yield f".max = {max},"
def render_md5_anim_bounds(prefix, bounds):
yield f"struct md5_anim_bounds {prefix}_bounds[] = {{"
for b in bounds:
yield "{"
yield from _render_md5_anim_bounds(b)
yield "},"
yield "};"
def _render_md5_anim_base_frame(bf):
pos = vec3(bf.pos_x, bf.pos_x, bf.pos_x)
w = unit_quaternion_w(bf.orient_x, bf.orient_y, bf.orient_z)
orient = vec4(bf.orient_x, bf.orient_y, bf.orient_z, w)
yield f".pos = {pos},"
yield f".orient = {orient},"
def render_md5_anim_base_frame(prefix, base_frame):
yield f"struct md5_anim_base_frame {prefix}_base_frame[] = {{"
for bf in base_frame:
yield "{"
yield from _render_md5_anim_base_frame(bf)
yield "},"
yield "};"
def render_md5_anim_frames(prefix, frame):
for i, anim_frame in enumerate(frame):
yield f"float {prefix}_{i}_frame[] = {{"
for f in anim_frame.value:
yield f"{f:.6f},"
yield "};"
def render_md5_anim_frame(prefix, frame):
yield from render_md5_anim_frames(prefix, frame)
yield f"float * {prefix}_frame[] = {{"
for i, anim_frame in enumerate(frame):
yield f"{prefix}_{i}_frame,"
yield "};"
def render_md5_anim(prefix, a):
yield f".num_frames = {a.num_frames},"
yield f".num_joints = {a.num_joints},"
yield f".frame_rate = {a.frame_rate},"
yield f".num_animated_components = {a.num_animated_components},"
yield f".hierarchy = {prefix}_hierarchy,"
yield f".bounds = {prefix}_bounds,"
yield f".base_frame = {prefix}_base_frame,"
yield f".frame = {prefix}_frame,"
def render_anim(prefix, a):
yield from render_md5_anim_hierarchy(prefix, a.hierarchy)
yield from render_md5_anim_bounds(prefix, a.bounds)
yield from render_md5_anim_base_frame(prefix, a.base_frame)
yield from render_md5_anim_frame(prefix, a.frame)
yield f"md5_anim {prefix}_anim {{"
yield from render_md5_anim(prefix, a)
yield "};"
def render_all(prefix, a):
yield from render_anim(prefix, a)
if __name__ == "__main__":
with open(sys.argv[1], 'r') as f:
buf = f.read()
l = [i.strip() for i in buf.split('\n') if i.strip()]
a = md5anim.parse_file(l)
render, out = renderer()
render(render_all(sys.argv[2], a))
sys.stdout.write(out.getvalue())

29193
model/boblamp/boblamp_anim.h Normal file

File diff suppressed because it is too large Load Diff