Rewrite mesh exporter to support armature & poses
This commit is contained in:
parent
fc47633c42
commit
de1950f1a6
3 changed files with 229 additions and 53 deletions
|
|
@ -5,6 +5,7 @@
|
|||
#include <psemek/gfx/array.hpp>
|
||||
#include <psemek/gfx/buffer.hpp>
|
||||
#include <psemek/gfx/attribs.hpp>
|
||||
#include <psemek/gfx/armature.hpp>
|
||||
|
||||
#include <psemek/geom/vector.hpp>
|
||||
#include <psemek/geom/point.hpp>
|
||||
|
|
@ -341,29 +342,17 @@ namespace psemek::gfx
|
|||
load_instance(instances.data(), instances.size(), usage);
|
||||
}
|
||||
|
||||
using pose_library = std::unordered_map<std::string_view, util::span<bone_transform<float> const>>;
|
||||
|
||||
struct imported_mesh
|
||||
{
|
||||
attribs_description attribs;
|
||||
util::span<char const> vertices;
|
||||
util::span<std::uint32_t const> indices;
|
||||
|
||||
struct bone
|
||||
{
|
||||
std::uint32_t parent;
|
||||
util::span<bone const> bones;
|
||||
|
||||
static constexpr std::uint32_t null = std::uint32_t(-1);
|
||||
};
|
||||
|
||||
util::span<bone> bones;
|
||||
|
||||
struct bone_pose
|
||||
{
|
||||
geom::vector<float, 3> translation;
|
||||
float scale;
|
||||
geom::quaternion<float> rotation;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string_view, util::span<bone_pose>> poses;
|
||||
pose_library poses;
|
||||
};
|
||||
|
||||
imported_mesh load_mesh(std::string_view data);
|
||||
|
|
|
|||
|
|
@ -212,37 +212,111 @@ namespace psemek::gfx
|
|||
imported_mesh load_mesh(std::string_view data)
|
||||
{
|
||||
// These should be in sync with convert-mesh.py
|
||||
static std::uint32_t const POSITION_MASK = 1;
|
||||
static std::uint32_t const NORMALS_MASK = 2;
|
||||
static std::uint32_t const COLORS_MASK = 4;
|
||||
static std::uint32_t const TEXCOORDS_MASK = 8;
|
||||
static std::uint32_t const SECTION_MESH = 1;
|
||||
static std::uint32_t const SECTION_BONES = 2;
|
||||
static std::uint32_t const SECTION_POSE = 3;
|
||||
|
||||
static std::uint32_t const POSITION_MASK = 1 << 0;
|
||||
static std::uint32_t const NORMALS_MASK = 1 << 1;
|
||||
static std::uint32_t const COLORS_MASK = 1 << 2;
|
||||
static std::uint32_t const TEXCOORDS_MASK = 1 << 3;
|
||||
static std::uint32_t const WEIGHTS_MASK = 1 << 4;
|
||||
|
||||
std::uint32_t vertex_format = 0;
|
||||
|
||||
util::binary_istream s{data};
|
||||
|
||||
imported_mesh result;
|
||||
|
||||
auto vertex_format = s.read<std::uint32_t>();
|
||||
auto parse_section_mesh = [&]
|
||||
{
|
||||
vertex_format = s.read<std::uint32_t>();
|
||||
|
||||
if (vertex_format & POSITION_MASK)
|
||||
result.attribs += make_attribs_description<geom::point<float, 3>>();
|
||||
if (vertex_format & POSITION_MASK)
|
||||
result.attribs += make_attribs_description<geom::point<float, 3>>();
|
||||
|
||||
if (vertex_format & NORMALS_MASK)
|
||||
result.attribs += make_attribs_description<geom::vector<float, 3>>();
|
||||
if (vertex_format & NORMALS_MASK)
|
||||
result.attribs += make_attribs_description<geom::vector<float, 3>>();
|
||||
|
||||
if (vertex_format & COLORS_MASK)
|
||||
result.attribs += make_attribs_description<gfx::normalized<geom::vector<std::uint8_t, 4>>>();
|
||||
if (vertex_format & COLORS_MASK)
|
||||
result.attribs += make_attribs_description<gfx::normalized<geom::vector<std::uint8_t, 4>>>();
|
||||
|
||||
if (vertex_format & TEXCOORDS_MASK)
|
||||
result.attribs += make_attribs_description<geom::vector<float, 2>>();
|
||||
if (vertex_format & TEXCOORDS_MASK)
|
||||
result.attribs += make_attribs_description<geom::vector<float, 2>>();
|
||||
|
||||
auto vertex_count = s.read<std::uint32_t>();
|
||||
auto vertex_ptr = s.read_raw(vertex_count * result.attribs.vertex_size);
|
||||
if (vertex_format & WEIGHTS_MASK)
|
||||
result.attribs += make_attribs_description<gfx::integer<geom::vector<std::uint8_t, 2>>, gfx::normalized<geom::vector<std::uint8_t, 2>>>();
|
||||
|
||||
auto index_count = s.read<std::uint32_t>();
|
||||
auto index_ptr = reinterpret_cast<std::uint32_t const *>(s.read_raw(index_count * sizeof(std::uint32_t)));
|
||||
auto vertex_count = s.read<std::uint32_t>();
|
||||
auto vertex_ptr = s.read_raw(vertex_count * result.attribs.vertex_size);
|
||||
|
||||
result.vertices = {vertex_ptr, vertex_ptr + vertex_count * result.attribs.vertex_size};
|
||||
result.indices = {index_ptr, index_ptr + index_count};
|
||||
auto index_count = s.read<std::uint32_t>();
|
||||
auto index_ptr = s.read_ptr<std::uint32_t>(index_count);
|
||||
|
||||
result.vertices = {vertex_ptr, vertex_ptr + vertex_count * result.attribs.vertex_size};
|
||||
result.indices = {index_ptr, index_ptr + index_count};
|
||||
};
|
||||
|
||||
auto parse_section_bones = [&]
|
||||
{
|
||||
auto bone_count = s.read<std::uint32_t>();
|
||||
auto bone_ptr = s.read_ptr<bone>(bone_count);
|
||||
|
||||
result.bones = {bone_ptr, bone_ptr + bone_count};
|
||||
};
|
||||
|
||||
auto parse_section_pose = [&]
|
||||
{
|
||||
auto name_length = s.read<std::uint32_t>();
|
||||
auto name_ptr = s.read_raw(name_length);
|
||||
|
||||
auto pose_count = s.read<std::uint32_t>();
|
||||
if (pose_count != result.bones.size())
|
||||
throw std::runtime_error("Number of transforms in a pose must be equal to the number of bones");
|
||||
auto pose_ptr = s.read_ptr<bone_transform<float>>(pose_count);
|
||||
|
||||
std::string_view name(name_ptr, name_ptr + name_length);
|
||||
|
||||
result.poses[name] = {pose_ptr, pose_ptr + pose_count};
|
||||
};
|
||||
|
||||
bool had_section_mesh = false;
|
||||
bool had_section_bones = false;
|
||||
|
||||
while (!s.eof())
|
||||
{
|
||||
auto section_type = s.read<std::uint32_t>();
|
||||
|
||||
switch (section_type)
|
||||
{
|
||||
case SECTION_MESH:
|
||||
if (had_section_mesh)
|
||||
throw std::runtime_error("Section 'mesh' must not repeat");
|
||||
parse_section_mesh();
|
||||
had_section_mesh = true;
|
||||
break;
|
||||
case SECTION_BONES:
|
||||
if (had_section_bones)
|
||||
throw std::runtime_error("Section 'bones' must not repeat");
|
||||
if (!had_section_mesh)
|
||||
throw std::runtime_error("Section 'bones' must come after section 'mesh'");
|
||||
if ((vertex_format & WEIGHTS_MASK) == 0)
|
||||
throw std::runtime_error("Section 'bones' requires weights in vertex format");
|
||||
parse_section_bones();
|
||||
had_section_bones = true;
|
||||
break;
|
||||
case SECTION_POSE:
|
||||
if (!had_section_bones)
|
||||
throw std::runtime_error("Section 'pose' must come after section 'bones'");
|
||||
parse_section_pose();
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Unknown section code " + util::to_string(section_type));
|
||||
}
|
||||
}
|
||||
|
||||
if (!had_section_mesh)
|
||||
throw std::runtime_error("Section 'mesh' must be present");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ import struct
|
|||
|
||||
bpy.ops.object.mode_set(mode = 'OBJECT')
|
||||
|
||||
obj = None
|
||||
mesh = None
|
||||
flat = False
|
||||
if 'mesh' in bpy.data.objects:
|
||||
mesh = bpy.data.objects['mesh'].data
|
||||
obj = bpy.data.objects['mesh']
|
||||
print('Using smooth normals')
|
||||
if 'mesh_flat' in bpy.data.objects:
|
||||
mesh = bpy.data.objects['mesh_flat'].data
|
||||
obj = bpy.data.objects['mesh_flat']
|
||||
print('Using flat normals')
|
||||
flat = True
|
||||
assert mesh
|
||||
mesh = obj.data
|
||||
|
||||
colors = None
|
||||
if len(mesh.vertex_colors) > 0:
|
||||
|
|
@ -26,27 +27,33 @@ if len(mesh.uv_layers) > 0:
|
|||
texcoords = mesh.uv_layers.active.data
|
||||
print('Found texture coordinates')
|
||||
|
||||
skeleton = None
|
||||
if 'skeleton' in bpy.data.objects:
|
||||
skeleton = bpy.data.objects['skeleton'].data
|
||||
print('Found skeleton')
|
||||
armature = None
|
||||
bone_names = None
|
||||
if 'armature' in bpy.data.objects:
|
||||
armature = bpy.data.objects['armature']
|
||||
print('Found armature with {} bones'.format(len(armature.data.bones)))
|
||||
bone_names = [b.name for b in armature.data.bones]
|
||||
|
||||
POSITION_MASK = 1
|
||||
NORMAL_MASK = 2
|
||||
COLOR_MASK = 4
|
||||
TEXCOORD_MASK = 8
|
||||
WEIGHTS_MASK = 16
|
||||
|
||||
vertex_format = POSITION_MASK | NORMAL_MASK
|
||||
if colors:
|
||||
vertex_format |= COLOR_MASK
|
||||
if texcoords:
|
||||
vertex_format |= TEXCOORD_MASK
|
||||
print("Using vertex format", format(vertex_format, '04b'))
|
||||
if armature:
|
||||
vertex_format |= WEIGHTS_MASK
|
||||
print("Using vertex format", format(vertex_format, '05b'))
|
||||
|
||||
vertex_coords = []
|
||||
vertex_normals = []
|
||||
vertex_colors = []
|
||||
vertex_texcoords = []
|
||||
vertex_weights = []
|
||||
|
||||
indices = []
|
||||
|
||||
|
|
@ -63,6 +70,23 @@ if flat:
|
|||
vertex_colors.append(tuple(colors[li].color))
|
||||
if texcoords:
|
||||
vertex_texcoords.append(tuple(texcoords[li].uv))
|
||||
if armature:
|
||||
weights = []
|
||||
for g in v.groups:
|
||||
name = obj.vertex_groups[g.group].name
|
||||
if name in bone_names:
|
||||
weights.append((bone_names.index(name), g.weight))
|
||||
for g, w in weights:
|
||||
if g >= len(armature.data.bones):
|
||||
print("Invalid vertex group index {} for armature weights".format(g), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
weights = list(sorted(weights, key=lambda p: -p[1]))[:2]
|
||||
while len(weights) < 2:
|
||||
weights.append((0,0.0))
|
||||
weights = list(sorted(weights, key=lambda p: -p[1]))[:2]
|
||||
W = sum(p[1] for p in weights)
|
||||
weights = [(i,w/W) for i,w in weights]
|
||||
vertex_weights[vi] = weights
|
||||
indices.append(i)
|
||||
|
||||
else:
|
||||
|
|
@ -74,6 +98,8 @@ else:
|
|||
vertex_colors = [None] * len(vertex_coords)
|
||||
if texcoords:
|
||||
vertex_texcoords = [None] * len(vertex_coords)
|
||||
if armature:
|
||||
vertex_weights = [None] * len(vertex_coords)
|
||||
|
||||
for p in mesh.loop_triangles:
|
||||
for li in p.loops:
|
||||
|
|
@ -84,6 +110,23 @@ else:
|
|||
vertex_colors[vi] = tuple(colors[li].color)
|
||||
if texcoords:
|
||||
vertex_texcoords[vi] = tuple(texcoords[li].uv)
|
||||
if armature:
|
||||
weights = []
|
||||
for g in v.groups:
|
||||
name = obj.vertex_groups[g.group].name
|
||||
if name in bone_names:
|
||||
weights.append((bone_names.index(name), g.weight))
|
||||
for g, w in weights:
|
||||
if g >= len(armature.data.bones):
|
||||
print("Invalid vertex group index {} for armature weights".format(g), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
weights = list(sorted(weights, key=lambda p: -p[1]))[:2]
|
||||
while len(weights) < 2:
|
||||
weights.append((0,0.0))
|
||||
W = sum(p[1] for p in weights)
|
||||
weights = [(i,w/W) for i,w in weights]
|
||||
vertex_weights[vi] = weights
|
||||
|
||||
|
||||
assert (len(indices) % 3) == 0
|
||||
assert len(vertex_coords) == len(vertex_normals)
|
||||
|
|
@ -91,6 +134,14 @@ if colors:
|
|||
assert len(vertex_coords) == len(vertex_colors)
|
||||
if texcoords:
|
||||
assert len(vertex_coords) == len(vertex_texcoords)
|
||||
if armature:
|
||||
assert len(vertex_coords) == len(vertex_weights)
|
||||
|
||||
|
||||
class Uint8:
|
||||
def __init__(self, value):
|
||||
self.value = int(value)
|
||||
|
||||
|
||||
if colors:
|
||||
for i in range(len(vertex_colors)):
|
||||
|
|
@ -99,6 +150,21 @@ if colors:
|
|||
c = (c << 8) | int(max(0, min(255, vertex_colors[i][k] * 255)))
|
||||
vertex_colors[i] = c
|
||||
|
||||
if armature:
|
||||
for i in range(len(vertex_weights)):
|
||||
gs = []
|
||||
for g, w in vertex_weights[i]:
|
||||
gs.append(Uint8(g))
|
||||
|
||||
ws = []
|
||||
for g, w in vertex_weights[i]:
|
||||
ws.append(Uint8(w * 255.0))
|
||||
ws[0].value += 255 - sum(w.value for w in ws)
|
||||
|
||||
assert len(gs) == 2
|
||||
assert len(ws) == 2
|
||||
vertex_weights[i] = tuple(gs + ws)
|
||||
|
||||
vertices = []
|
||||
for i in range(len(vertex_coords)):
|
||||
attribs = [vertex_coords[i], vertex_normals[i]]
|
||||
|
|
@ -106,21 +172,59 @@ for i in range(len(vertex_coords)):
|
|||
attribs.append(vertex_colors[i])
|
||||
if texcoords:
|
||||
attribs.append(vertex_texcoords[i])
|
||||
if armature:
|
||||
attribs.append(vertex_weights[i])
|
||||
vertices.append(tuple(attribs))
|
||||
|
||||
print(len(vertices), 'vertices')
|
||||
print(len(indices), 'indices')
|
||||
|
||||
if skeleton is not None:
|
||||
if armature is not None:
|
||||
bones = []
|
||||
for b in skeleton.data.bones:
|
||||
for b in armature.data.bones:
|
||||
p = None
|
||||
if b.parent is None:
|
||||
bones.append(-1)
|
||||
p = -1
|
||||
else:
|
||||
pi = list(skeleton.data.bones).index(b.parent)
|
||||
bones.append(pi)
|
||||
p = list(armature.data.bones).index(b.parent)
|
||||
bones.append((p, tuple(b.head_local), tuple(map(tuple, b.matrix_local.to_3x3()))))
|
||||
|
||||
poses = []
|
||||
for index, pose in enumerate(bpy.data.objects['armature'].pose_library.pose_markers):
|
||||
print("Found pose", pose.name)
|
||||
|
||||
# [rotation, scale, translation]
|
||||
data = []
|
||||
for i in range(len(bone_names)):
|
||||
data.append([[None, None, None, None], [None, None, None], [None, None, None]])
|
||||
|
||||
marker = armature.pose_library.pose_markers[index]
|
||||
frame = marker.frame
|
||||
action = bpy.data.actions[armature.pose_library.name]
|
||||
for g in action.groups:
|
||||
if not g.name in bone_names:
|
||||
continue
|
||||
bone_index = bone_names.index(g.name)
|
||||
for ch in g.channels:
|
||||
value = ch.evaluate(float(frame))
|
||||
if ch.data_path.find('rotation_quaternion') != -1:
|
||||
data[bone_index][0][ch.array_index] = value
|
||||
elif ch.data_path.find('scale') != -1:
|
||||
data[bone_index][1][ch.array_index] = value
|
||||
elif ch.data_path.find('location') != -1:
|
||||
data[bone_index][2][ch.array_index] = value
|
||||
|
||||
new_data = []
|
||||
for d in data:
|
||||
rotation = tuple([d[0][1], d[0][2], d[0][3], d[0][0]])
|
||||
scale = d[1][0]
|
||||
translation = tuple(d[2])
|
||||
new_data.append((rotation, scale, translation))
|
||||
poses.append((pose.name, new_data))
|
||||
|
||||
def to_bytes(obj):
|
||||
if type(obj) == Uint8:
|
||||
return struct.pack('<B', obj.value)
|
||||
if type(obj) == int:
|
||||
if obj < 0:
|
||||
return struct.pack('<i', obj)
|
||||
|
|
@ -138,12 +242,21 @@ def to_bytes(obj):
|
|||
for x in obj:
|
||||
res += to_bytes(x)
|
||||
return res
|
||||
if type(obj) == str:
|
||||
b = obj.encode('utf-8')
|
||||
return struct.pack('<I', len(b)) + b
|
||||
|
||||
if skeleton is not None:
|
||||
# TODO: support skeletons
|
||||
assert False
|
||||
else:
|
||||
data = to_bytes((vertex_format, vertices, indices))
|
||||
SECTION_MESH = 1
|
||||
SECTION_BONES = 2
|
||||
SECTION_POSE = 3
|
||||
|
||||
data = to_bytes((SECTION_MESH, vertex_format, vertices, indices))
|
||||
|
||||
if armature:
|
||||
data += to_bytes((SECTION_BONES, bones))
|
||||
|
||||
for name, pdata in poses:
|
||||
data += to_bytes((SECTION_POSE, name, pdata))
|
||||
|
||||
filename = sys.argv[sys.argv.index('--') + 1]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue