Sleepy's weight implementation swapped in

This commit is contained in:
Will Snyder 2020-10-16 10:34:39 -05:00
commit 1bf6b6f9ab
4 changed files with 92 additions and 111 deletions

View File

@ -27,18 +27,25 @@ class ModelTransform:
translation: Vector = field(default_factory=Vector) translation: Vector = field(default_factory=Vector)
rotation: Quaternion = field(default_factory=Quaternion) rotation: Quaternion = field(default_factory=Quaternion)
@dataclass
class VertexWeight:
""" Class representing a vertex weight in a .msh file. """
weight: float = 1.0
bone: int = 0
@dataclass @dataclass
class GeometrySegment: class GeometrySegment:
""" Class representing a 'SEGM' section in a .msh file. """ """ Class representing a 'SEGM' section in a .msh file. """
material_name: str = "" material_name: str = field(default_factory=str)
positions: List[Vector] = field(default_factory=list) positions: List[Vector] = field(default_factory=list)
normals: List[Vector] = field(default_factory=list) normals: List[Vector] = field(default_factory=list)
colors: List[List[float]] = None colors: List[List[float]] = None
texcoords: List[Vector] = field(default_factory=list) texcoords: List[Vector] = field(default_factory=list)
weights: List[Tuple[int, float]] = None weights: List[List[VertexWeight]] = None
polygons: List[List[int]] = field(default_factory=list) polygons: List[List[int]] = field(default_factory=list)
triangles: List[List[int]] = field(default_factory=list) triangles: List[List[int]] = field(default_factory=list)
@ -64,11 +71,11 @@ class Model:
transform: ModelTransform = field(default_factory=ModelTransform) transform: ModelTransform = field(default_factory=ModelTransform)
bone_map: List[str] = None
geometry: List[GeometrySegment] = None geometry: List[GeometrySegment] = None
collisionprimitive: CollisionPrimitive = None collisionprimitive: CollisionPrimitive = None
vgroups_to_modelnames_map : Dict[int, str] = None
@dataclass @dataclass
class Animation: class Animation:
""" Class representing 'CYCL' + 'KFR3' sections in a .msh file """ """ Class representing 'CYCL' + 'KFR3' sections in a .msh file """

View File

@ -10,7 +10,7 @@ from .msh_model import *
from .msh_model_utilities import * from .msh_model_utilities import *
from .msh_utilities import * from .msh_utilities import *
SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE", "ARMATURE"} SKIPPED_OBJECT_TYPES = {"LATTICE", "CAMERA", "LIGHT", "SPEAKER", "LIGHT_PROBE"}
MESH_OBJECT_TYPES = {"MESH", "CURVE", "SURFACE", "META", "FONT", "GPENCIL"} MESH_OBJECT_TYPES = {"MESH", "CURVE", "SURFACE", "META", "FONT", "GPENCIL"}
MAX_MSH_VERTEX_COUNT = 32767 MAX_MSH_VERTEX_COUNT = 32767
@ -22,7 +22,6 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]:
parents = create_parents_set() parents = create_parents_set()
models_list: List[Model] = [] models_list: List[Model] = []
skeleton: bpy.types.Armature = None
for uneval_obj in select_objects(export_target): for uneval_obj in select_objects(export_target):
if uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents: if uneval_obj.type in SKIPPED_OBJECT_TYPES and uneval_obj.name not in parents:
@ -31,13 +30,13 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]:
if apply_modifiers: if apply_modifiers:
obj = uneval_obj.evaluated_get(depsgraph) obj = uneval_obj.evaluated_get(depsgraph)
else: else:
obj = uneval_obj obj = uneval_obj
if uneval_obj.type == "ARMATURE":
continue
check_for_bad_lod_suffix(obj) check_for_bad_lod_suffix(obj)
if obj.type == "ARMATURE":
models_list += expand_armature(obj)
local_translation, local_rotation, _ = obj.matrix_local.decompose() local_translation, local_rotation, _ = obj.matrix_local.decompose()
model = Model() model = Model()
@ -47,30 +46,8 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]:
model.transform.rotation = convert_rotation_space(local_rotation) model.transform.rotation = convert_rotation_space(local_rotation)
model.transform.translation = convert_vector_space(local_translation) model.transform.translation = convert_vector_space(local_translation)
print("Adding model: " + model.name)
if obj.parent is not None:
if obj.parent.type == "ARMATURE":
model.parent = "DummyRoot"
skeleton = obj.parent
parent_bone_name = obj.parent_bone
if parent_bone_name == "":
model.parent = obj.parent.parent
else:
model.parent = parent_bone_name
if model.model_type == ModelType.SKIN:
model.vgroups_to_modelnames_map = {}
for i, vgroup in enumerate(obj.vertex_groups):
vgroups_to_modelnames_map[i] = vgroup.name
if obj.type in MESH_OBJECT_TYPES: if obj.type in MESH_OBJECT_TYPES:
mesh = obj.to_mesh() model.geometry = create_mesh_geometry(mesh, obj.vertex_groups)
model.geometry = create_mesh_geometry(mesh, model.model_type == ModelType.SKIN)
obj.to_mesh_clear() obj.to_mesh_clear()
_, _, world_scale = obj.matrix_world.decompose() _, _, world_scale = obj.matrix_world.decompose()
@ -86,38 +63,14 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]:
if get_is_collision_primitive(obj): if get_is_collision_primitive(obj):
model.collisionprimitive = get_collision_primitive(obj) model.collisionprimitive = get_collision_primitive(obj)
models_list.append(model) if obj.vertex_groups:
model.bone_map = [group.name for group in obj.vertex_groups]
for bone in skeleton.data.bones:
model = Model()
model.name = bone.name
model.model_type = ModelType.BONE
model.hidden = False
local_translation, local_rotation, _ = bone.matrix_local.decompose()
model.transform.rotation = convert_rotation_space(local_rotation)
model.transform.translation = convert_vector_space(local_translation)
print("Adding bone: " + model.name)
parent_name = bone.parent
if parent_name is not None:
model.parent = parent_name
else:
if skeleton.parent is not None:
model.parent = skeleton.parent.name
else:
model.parent = None
models_list.append(model) models_list.append(model)
return models_list return models_list
def create_parents_set() -> Set[str]: def create_parents_set() -> Set[str]:
""" Creates a set with the names of the Blender objects from the current scene """ Creates a set with the names of the Blender objects from the current scene
that have at least one child. """ that have at least one child. """
@ -130,7 +83,8 @@ def create_parents_set() -> Set[str]:
return parents return parents
def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[GeometrySegment]:
def create_mesh_geometry(mesh: bpy.types.Mesh, has_weights: bool) -> List[GeometrySegment]:
""" Creates a list of GeometrySegment objects from a Blender mesh. """ Creates a list of GeometrySegment objects from a Blender mesh.
Does NOT create triangle strips in the GeometrySegment however. """ Does NOT create triangle strips in the GeometrySegment however. """
@ -143,7 +97,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet
material_count = max(len(mesh.materials), 1) material_count = max(len(mesh.materials), 1)
segments: List[GeometrySegment] = [GeometrySegment() for i in range(material_count)] segments: List[GeometrySegment] = [GeometrySegment() for i in range(material_count)]
vertex_cache: List[Dict[Tuple[float], int]] = [dict() for i in range(material_count)] vertex_cache = [dict() for i in range(material_count)]
vertex_remap: List[Dict[Tuple[int, int], int]] = [dict() for i in range(material_count)] vertex_remap: List[Dict[Tuple[int, int], int]] = [dict() for i in range(material_count)]
polygons: List[Set[int]] = [set() for i in range(material_count)] polygons: List[Set[int]] = [set() for i in range(material_count)]
@ -151,6 +105,10 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet
for segment in segments: for segment in segments:
segment.colors = [] segment.colors = []
if has_weights:
for segment in segments:
segment.weights = []
for segment, material in zip(segments, mesh.materials): for segment, material in zip(segments, mesh.materials):
segment.material_name = material.name segment.material_name = material.name
@ -189,6 +147,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet
for v in mesh.vertex_colors.active.data[loop_index].color: for v in mesh.vertex_colors.active.data[loop_index].color:
yield v yield v
if segment.weights is not None:
for v in mesh.vertices[vertex_index].groups:
yield v.group
yield v.weight
vertex_cache_entry = tuple(get_cache_vertex()) vertex_cache_entry = tuple(get_cache_vertex())
cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index) cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index)
@ -204,16 +167,6 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet
segment.positions.append(convert_vector_space(mesh.vertices[vertex_index].co)) segment.positions.append(convert_vector_space(mesh.vertices[vertex_index].co))
segment.normals.append(convert_vector_space(vertex_normal)) segment.normals.append(convert_vector_space(vertex_normal))
if is_skinned:
for i,grp_el in mesh.vertices[vertex_index].groups:
segment.weights.append(tuple(grp_el.group, grp_el.weight))
print("Adding weight to group {grp_el.group} of value {grp_el.weight}")
if i > 3: #will have to look into aramture/skin settings for limiting envolopes to 4 weights...
break
while i < 3:
segment.weights.append(tuple(0,0.0))
i+=1
if mesh.uv_layers.active is None: if mesh.uv_layers.active is None:
segment.texcoords.append(Vector((0.0, 0.0))) segment.texcoords.append(Vector((0.0, 0.0)))
else: else:
@ -222,6 +175,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet
if segment.colors is not None: if segment.colors is not None:
segment.colors.append(list(mesh.vertex_colors.active.data[loop_index].color)) segment.colors.append(list(mesh.vertex_colors.active.data[loop_index].color))
if segment.weights is not None:
groups = mesh.vertices[vertex_index].groups
segment.weights.append([VertexWeight(v.weight, v.group) for v in groups])
return new_index return new_index
@ -242,11 +200,12 @@ def create_mesh_geometry(mesh: bpy.types.Mesh, is_skinned : bool) -> List[Geomet
def get_model_type(obj: bpy.types.Object) -> ModelType: def get_model_type(obj: bpy.types.Object) -> ModelType:
""" Get the ModelType for a Blender object. """ """ Get the ModelType for a Blender object. """
if obj.parent_type == "ARMATURE":
return ModelType.SKIN
if obj.type in MESH_OBJECT_TYPES: if obj.type in MESH_OBJECT_TYPES:
return ModelType.STATIC if obj.vertex_groups:
return ModelType.SKIN
else:
return ModelType.STATIC
return ModelType.NULL return ModelType.NULL
@ -382,6 +341,31 @@ def select_objects(export_target: str) -> List[bpy.types.Object]:
return objects + parents return objects + parents
def expand_armature(obj: bpy.types.Object) -> List[Model]:
bones: List[Model] = []
for bone in obj.data.bones:
model = Model()
transform = bone.matrix_local
if bone.parent:
transform = bone.parent.matrix_local.inverted() @ transform
model.parent = bone.parent.name
else:
model.parent = obj.name
local_translation, local_rotation, _ = transform.decompose()
model.model_type = ModelType.BONE
model.name = bone.name
model.transform.rotation = convert_rotation_space(local_rotation)
model.transform.translation = convert_vector_space(local_translation)
bones.append(model)
return bones
def convert_vector_space(vec: Vector) -> Vector: def convert_vector_space(vec: Vector) -> Vector:
return Vector((-vec.x, vec.z, vec.y)) return Vector((-vec.x, vec.z, vec.y))

View File

@ -71,21 +71,6 @@ def create_scene(generate_triangle_strips: bool, apply_modifiers: bool, export_t
scene.models = reparent_model_roots(scene.models) scene.models = reparent_model_roots(scene.models)
#now that we've collected all models, we should remap WGHT indices...
'''
names_to_indices = {}
for i,model in enumerate(scene.models):
names_to_indices[model.name] = i;
for model in scene.models:
if model.model_type == ModelType.SKIN:
for segment in model.geometry:
for i in range(len(segment.weights)):
vgroup_index = segment.weights[i][0]
segment.weights[i][0] = names_to_indices[model.vgroups_to_modelnames_map[vgroup_index]]
'''
scene.materials = remove_unused_materials(scene.materials, scene.models) scene.materials = remove_unused_materials(scene.materials, scene.models)
scene.anims = gather_animdata(bpy.context.scene.objects["Armature"]) scene.anims = gather_animdata(bpy.context.scene.objects["Armature"])

View File

@ -19,6 +19,7 @@ def save_scene(output_file, scene: Scene):
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)}
material_index: Dict[str, int] = {} material_index: Dict[str, int] = {}
with msh2.create_child("MATL") as matl: with msh2.create_child("MATL") as matl:
@ -26,7 +27,7 @@ def save_scene(output_file, scene: Scene):
for index, model in enumerate(scene.models): for index, model in enumerate(scene.models):
with msh2.create_child("MODL") as modl: with msh2.create_child("MODL") as modl:
_write_modl(modl, model, index, material_index, scene) _write_modl(modl, model, index, material_index, model_index)
with hedr.create_child("ANM2") as anm2: #simple for now with hedr.create_child("ANM2") as anm2: #simple for now
for anim in scene.anims: for anim in scene.anims:
@ -103,7 +104,7 @@ def _write_matd(matd: Writer, material_name: str, material: Material):
with matd.create_child("TX3D") as tx3d: with matd.create_child("TX3D") as tx3d:
tx3d.write_string(material.texture3) tx3d.write_string(material.texture3)
def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str, int], scene: Scene): def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str, int], model_index: Dict[str, int]):
with modl.create_child("MTYP") as mtyp: with modl.create_child("MTYP") as mtyp:
mtyp.write_u32(model.model_type.value) mtyp.write_u32(model.model_type.value)
@ -129,13 +130,10 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str
for segment in model.geometry: for segment in model.geometry:
with geom.create_child("SEGM") as segm: with geom.create_child("SEGM") as segm:
_write_segm(segm, segment, material_index) _write_segm(segm, segment, material_index)
'''
if model.model_type == ModelType.SKIN: if model.bone_map:
with modl.create_child("ENVL") as envl: with geom.create_child("ENVL") as envl:
envl.write_u32(len(scene.models)) _write_envl(envl, model, model_index)
for i in range(len(scene.models)):
envl.write_u32(i)
'''
if model.collisionprimitive is not None: if model.collisionprimitive is not None:
with modl.create_child("SWCI") as swci: with modl.create_child("SWCI") as swci:
@ -160,21 +158,16 @@ def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str
for position in segment.positions: for position in segment.positions:
posl.write_f32(position.x, position.y, position.z) posl.write_f32(position.x, position.y, position.z)
if segment.weights:
with segm.create_child("WGHT") as wght:
_write_wght(wght, segment.weights)
with segm.create_child("NRML") as nrml: with segm.create_child("NRML") as nrml:
nrml.write_u32(len(segment.normals)) nrml.write_u32(len(segment.normals))
for normal in segment.normals: for normal in segment.normals:
nrml.write_f32(normal.x, normal.y, normal.z) nrml.write_f32(normal.x, normal.y, normal.z)
'''
if segment.weights is not None:
with segm.create_child("WGHT") as wght:
wght.write_u32(len(segment.weights) / 4)
for weight in segment.weights:
wght.write_u32(weight[0])
wght.write_f32(weight[1])
'''
if segment.colors is not None: if segment.colors is not None:
with segm.create_child("CLRL") as clrl: with segm.create_child("CLRL") as clrl:
clrl.write_u32(len(segment.colors)) clrl.write_u32(len(segment.colors))
@ -220,7 +213,7 @@ def _write_anm2(anm2: Writer, anim: Animation):
cycl.write_u32(1) cycl.write_u32(1)
cycl.write_string(anim.name) cycl.write_string(anim.name)
for _ in range(64 - (len(anim.name) + 1)): for _ in range(63 - len(anim.name) ):
cycl.write_u8(0) cycl.write_u8(0)
cycl.write_f32(10.0) #test framerate cycl.write_f32(10.0) #test framerate
@ -247,10 +240,22 @@ def _write_anm2(anm2: Writer, anim: Animation):
kfr3.write_f32(xform.rotation.x, xform.rotation.y, xform.rotation.z, xform.rotation.w) kfr3.write_f32(xform.rotation.x, xform.rotation.y, xform.rotation.z, xform.rotation.w)
def _write_wght(wght: Writer, weights: List[List[VertexWeight]]):
wght.write_u32(len(weights))
for weight_list in weights:
weight_list += [VertexWeight(0.0, 0)] * 4
weight_list = sorted(weight_list, key=lambda w: w.weight, reverse=True)
weight_list = weight_list[:4]
total_weight = max(sum(map(lambda w: w.weight, weight_list)), 1e-5)
for weight in weight_list:
wght.write_i32(weight.bone)
wght.write_f32(weight.weight / total_weight)
def _write_envl(envl: Writer, model: Model, model_index: Dict[str, int]):
envl.write_u32(len(model.bone_map))
for bone_name in model.bone_map:
envl.write_u32(model_index[bone_name])