DummyRoot data injection for ZA, matrix_local-with-bone-parent bug workaround, ENVL indicies start from one (fixing ZE bug), auto transform application for children of bones in a skeleton used for skinning done but commented out for further decision
This commit is contained in:
parent
20ad9a48d5
commit
791a033d08
|
@ -32,7 +32,10 @@ def extract_anim(armature: bpy.types.Armature) -> Animation:
|
||||||
anim.bone_transforms[bone.name] = []
|
anim.bone_transforms[bone.name] = []
|
||||||
|
|
||||||
for frame in range(num_frames):
|
for frame in range(num_frames):
|
||||||
|
|
||||||
|
#if frame % 10 == 0:
|
||||||
|
# print("Sample frame {}:".format(frame))
|
||||||
|
|
||||||
frame_time = framerange.x + frame * increment
|
frame_time = framerange.x + frame * increment
|
||||||
bpy.context.scene.frame_set(frame_time)
|
bpy.context.scene.frame_set(frame_time)
|
||||||
|
|
||||||
|
@ -50,6 +53,9 @@ def extract_anim(armature: bpy.types.Armature) -> Animation:
|
||||||
xform.rotation = convert_rotation_space(rot)
|
xform.rotation = convert_rotation_space(rot)
|
||||||
xform.translation = convert_vector_space(loc)
|
xform.translation = convert_vector_space(loc)
|
||||||
|
|
||||||
|
#if frame % 10 == 0:
|
||||||
|
# print("\t{:10}: loc {:15} rot {:15}".format(bone.name, vec_to_str(xform.translation), quat_to_str(xform.rotation)))
|
||||||
|
|
||||||
anim.bone_transforms[bone.name].append(xform)
|
anim.bone_transforms[bone.name].append(xform)
|
||||||
|
|
||||||
return anim
|
return anim
|
||||||
|
|
|
@ -38,23 +38,37 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
|
||||||
models_list += expand_armature(obj)
|
models_list += expand_armature(obj)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if skeleton_only:
|
|
||||||
continue
|
|
||||||
|
|
||||||
model = Model()
|
model = Model()
|
||||||
model.name = obj.name
|
model.name = obj.name
|
||||||
model.model_type = get_model_type(obj)
|
model.model_type = get_model_type(obj, skeleton_only)
|
||||||
model.hidden = get_is_model_hidden(obj)
|
model.hidden = get_is_model_hidden(obj)
|
||||||
|
|
||||||
transform = obj.matrix_local
|
transform = obj.matrix_local
|
||||||
|
transform_reset = Matrix.Identity(4)
|
||||||
|
|
||||||
if obj.parent_bone:
|
if obj.parent_bone:
|
||||||
model.parent = obj.parent_bone
|
model.parent = obj.parent_bone
|
||||||
transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ transform
|
|
||||||
|
# matrix_local, when called on an armature child also parented to a bone, appears to be broken.
|
||||||
|
# At the very least, the results contradict the docs...
|
||||||
|
armature_relative_transform = obj.parent.matrix_world.inverted() @ obj.matrix_world
|
||||||
|
bone_relative_transform = obj.parent.data.bones[obj.parent_bone].matrix_local.inverted() @ armature_relative_transform
|
||||||
|
|
||||||
|
transform = bone_relative_transform
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Since the transforms of direct bone children are discarded by ZEngine (but not ZEditor), we apply the transform
|
||||||
|
# before geometry extraction, then apply the inversion after.
|
||||||
|
if obj.type in MESH_OBJECT_TYPES:
|
||||||
|
obj.data.transform(bone_relative_transform)
|
||||||
|
transform_reset = bone_relative_transform.inverted()
|
||||||
|
transform = Matrix.Identity(4)
|
||||||
|
'''
|
||||||
else:
|
else:
|
||||||
if obj.parent is not None:
|
if obj.parent is not None:
|
||||||
if obj.parent.type == "ARMATURE":
|
if obj.parent.type == "ARMATURE":
|
||||||
model.parent = obj.parent.parent.name
|
model.parent = obj.parent.parent.name
|
||||||
|
transform = obj.parent.matrix_local @ transform
|
||||||
else:
|
else:
|
||||||
model.parent = obj.parent.name
|
model.parent = obj.parent.name
|
||||||
|
|
||||||
|
@ -63,6 +77,7 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
|
||||||
model.transform.translation = convert_vector_space(local_translation)
|
model.transform.translation = convert_vector_space(local_translation)
|
||||||
|
|
||||||
if obj.type in MESH_OBJECT_TYPES:
|
if obj.type in MESH_OBJECT_TYPES:
|
||||||
|
|
||||||
mesh = obj.to_mesh()
|
mesh = obj.to_mesh()
|
||||||
model.geometry = create_mesh_geometry(mesh, obj.vertex_groups)
|
model.geometry = create_mesh_geometry(mesh, obj.vertex_groups)
|
||||||
obj.to_mesh_clear()
|
obj.to_mesh_clear()
|
||||||
|
@ -70,21 +85,25 @@ def gather_models(apply_modifiers: bool, export_target: str, skeleton_only: bool
|
||||||
_, _, world_scale = obj.matrix_world.decompose()
|
_, _, world_scale = obj.matrix_world.decompose()
|
||||||
world_scale = convert_scale_space(world_scale)
|
world_scale = convert_scale_space(world_scale)
|
||||||
scale_segments(world_scale, model.geometry)
|
scale_segments(world_scale, model.geometry)
|
||||||
|
|
||||||
for segment in model.geometry:
|
for segment in model.geometry:
|
||||||
if len(segment.positions) > MAX_MSH_VERTEX_COUNT:
|
if len(segment.positions) > MAX_MSH_VERTEX_COUNT:
|
||||||
raise RuntimeError(f"Object '{obj.name}' has resulted in a .msh geometry segment that has "
|
raise RuntimeError(f"Object '{obj.name}' has resulted in a .msh geometry segment that has "
|
||||||
f"more than {MAX_MSH_VERTEX_COUNT} vertices! Split the object's mesh up "
|
f"more than {MAX_MSH_VERTEX_COUNT} vertices! Split the object's mesh up "
|
||||||
f"and try again!")
|
f"and try again!")
|
||||||
|
if obj.vertex_groups:
|
||||||
|
model.bone_map = [group.name for group in obj.vertex_groups]
|
||||||
|
|
||||||
|
obj.data.transform(transform_reset)
|
||||||
|
|
||||||
|
|
||||||
if get_is_collision_primitive(obj):
|
if get_is_collision_primitive(obj):
|
||||||
model.collisionprimitive = get_collision_primitive(obj)
|
model.collisionprimitive = get_collision_primitive(obj)
|
||||||
|
|
||||||
if obj.vertex_groups:
|
|
||||||
model.bone_map = [group.name for group in obj.vertex_groups]
|
|
||||||
|
|
||||||
models_list.append(model)
|
models_list.append(model)
|
||||||
|
|
||||||
|
|
||||||
return models_list
|
return models_list
|
||||||
|
|
||||||
|
|
||||||
|
@ -215,10 +234,10 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[Geomet
|
||||||
|
|
||||||
return segments
|
return segments
|
||||||
|
|
||||||
def get_model_type(obj: bpy.types.Object) -> ModelType:
|
def get_model_type(obj: bpy.types.Object, skel_only: bool) -> ModelType:
|
||||||
""" Get the ModelType for a Blender object. """
|
""" Get the ModelType for a Blender object. """
|
||||||
|
|
||||||
if obj.type in MESH_OBJECT_TYPES:
|
if obj.type in MESH_OBJECT_TYPES and not skel_only:
|
||||||
if obj.vertex_groups:
|
if obj.vertex_groups:
|
||||||
return ModelType.SKIN
|
return ModelType.SKIN
|
||||||
else:
|
else:
|
||||||
|
@ -372,7 +391,11 @@ def expand_armature(obj: bpy.types.Object) -> List[Model]:
|
||||||
transform = bone.parent.matrix_local.inverted() @ transform
|
transform = bone.parent.matrix_local.inverted() @ transform
|
||||||
model.parent = bone.parent.name
|
model.parent = bone.parent.name
|
||||||
else:
|
else:
|
||||||
model.parent = "tst_prop"
|
model.parent = obj.parent.name
|
||||||
|
for child_obj in obj.children:
|
||||||
|
if child_obj.vertex_groups and not get_is_model_hidden(obj) and not obj.parent_bone:
|
||||||
|
model.parent = child_obj.name
|
||||||
|
|
||||||
|
|
||||||
local_translation, local_rotation, _ = transform.decompose()
|
local_translation, local_rotation, _ = transform.decompose()
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,26 @@ import mathutils
|
||||||
import math
|
import math
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
|
|
||||||
|
|
||||||
|
def inject_dummy_data(model : Model):
|
||||||
|
""" Adds a triangle and material to the scene root when exporting skeletons to satisfy ZenAsset. """
|
||||||
|
model.hidden = True
|
||||||
|
|
||||||
|
dummy_seg = GeometrySegment()
|
||||||
|
dummy_seg.material_name = ""
|
||||||
|
|
||||||
|
dummy_seg.positions = [Vector((0.0,0.1,0.0)), Vector((0.1,0.0,0.0)), Vector((0.0,0.0,0.1))]
|
||||||
|
dummy_seg.normals = [Vector((0.0,1.0,0.0)), Vector((1.0,0.0,0.0)), Vector((0.0,0.0,1.0))]
|
||||||
|
dummy_seg.texcoords = [Vector((0.1,0.1)), Vector((0.2,0.2)), Vector((0.3,0.3))]
|
||||||
|
tri = [[0,1,2]]
|
||||||
|
dummy_seg.triangles = tri
|
||||||
|
dummy_seg.polygons = tri
|
||||||
|
dummy_seg.triangle_strips = tri
|
||||||
|
|
||||||
|
model.geometry = [dummy_seg]
|
||||||
|
model.model_type = ModelType.STATIC
|
||||||
|
|
||||||
|
|
||||||
def scale_segments(scale: Vector, segments: List[GeometrySegment]):
|
def scale_segments(scale: Vector, segments: List[GeometrySegment]):
|
||||||
""" Scales are positions in the GeometrySegment list. """
|
""" Scales are positions in the GeometrySegment list. """
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,10 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t
|
||||||
if "Armature" in bpy.context.scene.objects.keys():
|
if "Armature" in bpy.context.scene.objects.keys():
|
||||||
scene.anims = [extract_anim(bpy.context.scene.objects["Armature"])]
|
scene.anims = [extract_anim(bpy.context.scene.objects["Armature"])]
|
||||||
|
|
||||||
|
root = scene.models[0]
|
||||||
|
if skel_only and root.model_type == ModelType.NULL:
|
||||||
|
inject_dummy_data(root)
|
||||||
|
|
||||||
return scene
|
return scene
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ def save_scene(output_file, scene: Scene, is_animated: bool):
|
||||||
with msh2.create_child("SINF") as sinf:
|
with msh2.create_child("SINF") as sinf:
|
||||||
_write_sinf(sinf, scene)
|
_write_sinf(sinf, scene)
|
||||||
|
|
||||||
model_index: Dict[str, int] = {model.name:i for i, model in enumerate(scene.models)}
|
model_index: Dict[str, int] = {model.name:(i+1) for i, model in enumerate(scene.models)}
|
||||||
material_index: Dict[str, int] = {}
|
material_index: Dict[str, int] = {}
|
||||||
|
|
||||||
with msh2.create_child("MATL") as matl:
|
with msh2.create_child("MATL") as matl:
|
||||||
|
@ -27,7 +27,7 @@ def save_scene(output_file, scene: Scene, is_animated: bool):
|
||||||
|
|
||||||
for index, model in enumerate(scene.models):
|
for index, model in enumerate(scene.models):
|
||||||
|
|
||||||
print(model.name)
|
#print("Name: {:.10}, Pos: {:15}, Rot: {:15}, Parent: {}".format(model.name, vec_to_str(model.transform.translation), quat_to_str(model.transform.rotation), model.parent))
|
||||||
|
|
||||||
with msh2.create_child("MODL") as modl:
|
with msh2.create_child("MODL") as modl:
|
||||||
_write_modl(modl, model, index, material_index, model_index)
|
_write_modl(modl, model, index, material_index, model_index)
|
||||||
|
@ -48,16 +48,9 @@ def save_scene(output_file, scene: Scene, is_animated: bool):
|
||||||
|
|
||||||
def _write_sinf(sinf: Writer, scene: Scene):
|
def _write_sinf(sinf: Writer, scene: Scene):
|
||||||
with sinf.create_child("NAME") as name:
|
with sinf.create_child("NAME") as name:
|
||||||
name.write_string("spa1_prop_impdoor_rig")
|
name.write_string(scene.name)
|
||||||
|
|
||||||
with sinf.create_child("FRAM") as fram:
|
with sinf.create_child("FRAM") as fram:
|
||||||
min_index = 0
|
|
||||||
max_index = 1
|
|
||||||
|
|
||||||
if scene.anims and len(scene.anims) > 0:
|
|
||||||
min_index = min([anim.start_index for anim in scene.anims])
|
|
||||||
max_index = min([anim.end_index for anim in scene.anims])
|
|
||||||
|
|
||||||
fram.write_i32(0, 1)
|
fram.write_i32(0, 1)
|
||||||
fram.write_f32(29.97)
|
fram.write_f32(29.97)
|
||||||
|
|
||||||
|
@ -126,7 +119,7 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str
|
||||||
mtyp.write_u32(model.model_type.value)
|
mtyp.write_u32(model.model_type.value)
|
||||||
|
|
||||||
with modl.create_child("MNDX") as mndx:
|
with modl.create_child("MNDX") as mndx:
|
||||||
mndx.write_u32(index)
|
mndx.write_u32(index + 1)
|
||||||
|
|
||||||
with modl.create_child("NAME") as name:
|
with modl.create_child("NAME") as name:
|
||||||
name.write_string(model.name)
|
name.write_string(model.name)
|
||||||
|
@ -135,13 +128,9 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str
|
||||||
with modl.create_child("PRNT") as prnt:
|
with modl.create_child("PRNT") as prnt:
|
||||||
prnt.write_string(model.parent)
|
prnt.write_string(model.parent)
|
||||||
|
|
||||||
if model.model_type != ModelType.NULL and model.model_type != ModelType.BONE:
|
if model.hidden:
|
||||||
if model.hidden or "Dummy" in model.name or "hp_" in model.name:
|
with modl.create_child("FLGS") as flgs:
|
||||||
with modl.create_child("FLGS") as flgs:
|
flgs.write_u32(1)
|
||||||
flgs.write_u32(1)
|
|
||||||
for seg in model.geometry:
|
|
||||||
seg.texcoords = None
|
|
||||||
|
|
||||||
|
|
||||||
with modl.create_child("TRAN") as tran:
|
with modl.create_child("TRAN") as tran:
|
||||||
_write_tran(tran, model.transform)
|
_write_tran(tran, model.transform)
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
|
||||||
|
|
||||||
|
def vec_to_str(vec):
|
||||||
|
return "({:.4},{:.4},{:.4})".format(vec.x,vec.y,vec.z)
|
||||||
|
|
||||||
|
def quat_to_str(quat):
|
||||||
|
return "({:.4},{:.4},{:.4},{:.4})".format(quat.x, quat.y, quat.z, quat.w)
|
||||||
|
|
||||||
def add_vec(l: Vector, r: Vector) -> Vector:
|
def add_vec(l: Vector, r: Vector) -> Vector:
|
||||||
return Vector(v0 + v1 for v0, v1 in zip(l, r))
|
return Vector(v0 + v1 for v0, v1 in zip(l, r))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue