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/array.hpp>
|
||||||
#include <psemek/gfx/buffer.hpp>
|
#include <psemek/gfx/buffer.hpp>
|
||||||
#include <psemek/gfx/attribs.hpp>
|
#include <psemek/gfx/attribs.hpp>
|
||||||
|
#include <psemek/gfx/armature.hpp>
|
||||||
|
|
||||||
#include <psemek/geom/vector.hpp>
|
#include <psemek/geom/vector.hpp>
|
||||||
#include <psemek/geom/point.hpp>
|
#include <psemek/geom/point.hpp>
|
||||||
|
|
@ -341,29 +342,17 @@ namespace psemek::gfx
|
||||||
load_instance(instances.data(), instances.size(), usage);
|
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
|
struct imported_mesh
|
||||||
{
|
{
|
||||||
attribs_description attribs;
|
attribs_description attribs;
|
||||||
util::span<char const> vertices;
|
util::span<char const> vertices;
|
||||||
util::span<std::uint32_t const> indices;
|
util::span<std::uint32_t const> indices;
|
||||||
|
|
||||||
struct bone
|
util::span<bone const> bones;
|
||||||
{
|
|
||||||
std::uint32_t parent;
|
|
||||||
|
|
||||||
static constexpr std::uint32_t null = std::uint32_t(-1);
|
pose_library poses;
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
imported_mesh load_mesh(std::string_view data);
|
imported_mesh load_mesh(std::string_view data);
|
||||||
|
|
|
||||||
|
|
@ -212,37 +212,111 @@ namespace psemek::gfx
|
||||||
imported_mesh load_mesh(std::string_view data)
|
imported_mesh load_mesh(std::string_view data)
|
||||||
{
|
{
|
||||||
// These should be in sync with convert-mesh.py
|
// These should be in sync with convert-mesh.py
|
||||||
static std::uint32_t const POSITION_MASK = 1;
|
static std::uint32_t const SECTION_MESH = 1;
|
||||||
static std::uint32_t const NORMALS_MASK = 2;
|
static std::uint32_t const SECTION_BONES = 2;
|
||||||
static std::uint32_t const COLORS_MASK = 4;
|
static std::uint32_t const SECTION_POSE = 3;
|
||||||
static std::uint32_t const TEXCOORDS_MASK = 8;
|
|
||||||
|
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};
|
util::binary_istream s{data};
|
||||||
|
|
||||||
imported_mesh result;
|
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)
|
if (vertex_format & POSITION_MASK)
|
||||||
result.attribs += make_attribs_description<geom::point<float, 3>>();
|
result.attribs += make_attribs_description<geom::point<float, 3>>();
|
||||||
|
|
||||||
if (vertex_format & NORMALS_MASK)
|
if (vertex_format & NORMALS_MASK)
|
||||||
result.attribs += make_attribs_description<geom::vector<float, 3>>();
|
result.attribs += make_attribs_description<geom::vector<float, 3>>();
|
||||||
|
|
||||||
if (vertex_format & COLORS_MASK)
|
if (vertex_format & COLORS_MASK)
|
||||||
result.attribs += make_attribs_description<gfx::normalized<geom::vector<std::uint8_t, 4>>>();
|
result.attribs += make_attribs_description<gfx::normalized<geom::vector<std::uint8_t, 4>>>();
|
||||||
|
|
||||||
if (vertex_format & TEXCOORDS_MASK)
|
if (vertex_format & TEXCOORDS_MASK)
|
||||||
result.attribs += make_attribs_description<geom::vector<float, 2>>();
|
result.attribs += make_attribs_description<geom::vector<float, 2>>();
|
||||||
|
|
||||||
auto vertex_count = s.read<std::uint32_t>();
|
if (vertex_format & WEIGHTS_MASK)
|
||||||
auto vertex_ptr = s.read_raw(vertex_count * result.attribs.vertex_size);
|
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 vertex_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_ptr = s.read_raw(vertex_count * result.attribs.vertex_size);
|
||||||
|
|
||||||
result.vertices = {vertex_ptr, vertex_ptr + vertex_count * result.attribs.vertex_size};
|
auto index_count = s.read<std::uint32_t>();
|
||||||
result.indices = {index_ptr, index_ptr + index_count};
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,17 @@ import struct
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode = 'OBJECT')
|
bpy.ops.object.mode_set(mode = 'OBJECT')
|
||||||
|
|
||||||
|
obj = None
|
||||||
mesh = None
|
mesh = None
|
||||||
flat = False
|
flat = False
|
||||||
if 'mesh' in bpy.data.objects:
|
if 'mesh' in bpy.data.objects:
|
||||||
mesh = bpy.data.objects['mesh'].data
|
obj = bpy.data.objects['mesh']
|
||||||
print('Using smooth normals')
|
print('Using smooth normals')
|
||||||
if 'mesh_flat' in bpy.data.objects:
|
if 'mesh_flat' in bpy.data.objects:
|
||||||
mesh = bpy.data.objects['mesh_flat'].data
|
obj = bpy.data.objects['mesh_flat']
|
||||||
print('Using flat normals')
|
print('Using flat normals')
|
||||||
flat = True
|
flat = True
|
||||||
assert mesh
|
mesh = obj.data
|
||||||
|
|
||||||
colors = None
|
colors = None
|
||||||
if len(mesh.vertex_colors) > 0:
|
if len(mesh.vertex_colors) > 0:
|
||||||
|
|
@ -26,27 +27,33 @@ if len(mesh.uv_layers) > 0:
|
||||||
texcoords = mesh.uv_layers.active.data
|
texcoords = mesh.uv_layers.active.data
|
||||||
print('Found texture coordinates')
|
print('Found texture coordinates')
|
||||||
|
|
||||||
skeleton = None
|
armature = None
|
||||||
if 'skeleton' in bpy.data.objects:
|
bone_names = None
|
||||||
skeleton = bpy.data.objects['skeleton'].data
|
if 'armature' in bpy.data.objects:
|
||||||
print('Found skeleton')
|
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
|
POSITION_MASK = 1
|
||||||
NORMAL_MASK = 2
|
NORMAL_MASK = 2
|
||||||
COLOR_MASK = 4
|
COLOR_MASK = 4
|
||||||
TEXCOORD_MASK = 8
|
TEXCOORD_MASK = 8
|
||||||
|
WEIGHTS_MASK = 16
|
||||||
|
|
||||||
vertex_format = POSITION_MASK | NORMAL_MASK
|
vertex_format = POSITION_MASK | NORMAL_MASK
|
||||||
if colors:
|
if colors:
|
||||||
vertex_format |= COLOR_MASK
|
vertex_format |= COLOR_MASK
|
||||||
if texcoords:
|
if texcoords:
|
||||||
vertex_format |= TEXCOORD_MASK
|
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_coords = []
|
||||||
vertex_normals = []
|
vertex_normals = []
|
||||||
vertex_colors = []
|
vertex_colors = []
|
||||||
vertex_texcoords = []
|
vertex_texcoords = []
|
||||||
|
vertex_weights = []
|
||||||
|
|
||||||
indices = []
|
indices = []
|
||||||
|
|
||||||
|
|
@ -63,6 +70,23 @@ if flat:
|
||||||
vertex_colors.append(tuple(colors[li].color))
|
vertex_colors.append(tuple(colors[li].color))
|
||||||
if texcoords:
|
if texcoords:
|
||||||
vertex_texcoords.append(tuple(texcoords[li].uv))
|
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)
|
indices.append(i)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -74,6 +98,8 @@ else:
|
||||||
vertex_colors = [None] * len(vertex_coords)
|
vertex_colors = [None] * len(vertex_coords)
|
||||||
if texcoords:
|
if texcoords:
|
||||||
vertex_texcoords = [None] * len(vertex_coords)
|
vertex_texcoords = [None] * len(vertex_coords)
|
||||||
|
if armature:
|
||||||
|
vertex_weights = [None] * len(vertex_coords)
|
||||||
|
|
||||||
for p in mesh.loop_triangles:
|
for p in mesh.loop_triangles:
|
||||||
for li in p.loops:
|
for li in p.loops:
|
||||||
|
|
@ -84,6 +110,23 @@ else:
|
||||||
vertex_colors[vi] = tuple(colors[li].color)
|
vertex_colors[vi] = tuple(colors[li].color)
|
||||||
if texcoords:
|
if texcoords:
|
||||||
vertex_texcoords[vi] = tuple(texcoords[li].uv)
|
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(indices) % 3) == 0
|
||||||
assert len(vertex_coords) == len(vertex_normals)
|
assert len(vertex_coords) == len(vertex_normals)
|
||||||
|
|
@ -91,6 +134,14 @@ if colors:
|
||||||
assert len(vertex_coords) == len(vertex_colors)
|
assert len(vertex_coords) == len(vertex_colors)
|
||||||
if texcoords:
|
if texcoords:
|
||||||
assert len(vertex_coords) == len(vertex_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:
|
if colors:
|
||||||
for i in range(len(vertex_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)))
|
c = (c << 8) | int(max(0, min(255, vertex_colors[i][k] * 255)))
|
||||||
vertex_colors[i] = c
|
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 = []
|
vertices = []
|
||||||
for i in range(len(vertex_coords)):
|
for i in range(len(vertex_coords)):
|
||||||
attribs = [vertex_coords[i], vertex_normals[i]]
|
attribs = [vertex_coords[i], vertex_normals[i]]
|
||||||
|
|
@ -106,21 +172,59 @@ for i in range(len(vertex_coords)):
|
||||||
attribs.append(vertex_colors[i])
|
attribs.append(vertex_colors[i])
|
||||||
if texcoords:
|
if texcoords:
|
||||||
attribs.append(vertex_texcoords[i])
|
attribs.append(vertex_texcoords[i])
|
||||||
|
if armature:
|
||||||
|
attribs.append(vertex_weights[i])
|
||||||
vertices.append(tuple(attribs))
|
vertices.append(tuple(attribs))
|
||||||
|
|
||||||
print(len(vertices), 'vertices')
|
print(len(vertices), 'vertices')
|
||||||
print(len(indices), 'indices')
|
print(len(indices), 'indices')
|
||||||
|
|
||||||
if skeleton is not None:
|
if armature is not None:
|
||||||
bones = []
|
bones = []
|
||||||
for b in skeleton.data.bones:
|
for b in armature.data.bones:
|
||||||
|
p = None
|
||||||
if b.parent is None:
|
if b.parent is None:
|
||||||
bones.append(-1)
|
p = -1
|
||||||
else:
|
else:
|
||||||
pi = list(skeleton.data.bones).index(b.parent)
|
p = list(armature.data.bones).index(b.parent)
|
||||||
bones.append(pi)
|
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):
|
def to_bytes(obj):
|
||||||
|
if type(obj) == Uint8:
|
||||||
|
return struct.pack('<B', obj.value)
|
||||||
if type(obj) == int:
|
if type(obj) == int:
|
||||||
if obj < 0:
|
if obj < 0:
|
||||||
return struct.pack('<i', obj)
|
return struct.pack('<i', obj)
|
||||||
|
|
@ -138,12 +242,21 @@ def to_bytes(obj):
|
||||||
for x in obj:
|
for x in obj:
|
||||||
res += to_bytes(x)
|
res += to_bytes(x)
|
||||||
return res
|
return res
|
||||||
|
if type(obj) == str:
|
||||||
|
b = obj.encode('utf-8')
|
||||||
|
return struct.pack('<I', len(b)) + b
|
||||||
|
|
||||||
if skeleton is not None:
|
SECTION_MESH = 1
|
||||||
# TODO: support skeletons
|
SECTION_BONES = 2
|
||||||
assert False
|
SECTION_POSE = 3
|
||||||
else:
|
|
||||||
data = to_bytes((vertex_format, vertices, indices))
|
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]
|
filename = sys.argv[sys.argv.index('--') + 1]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue