initial vertex weights implementation
This commit is contained in:
parent
b56fa79a19
commit
dac3ade7a4
|
@ -27,17 +27,24 @@ 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)
|
||||||
# TODO: Skin support.
|
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)
|
||||||
|
@ -63,5 +70,7 @@ 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
|
||||||
|
|
|
@ -34,6 +34,9 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]:
|
||||||
|
|
||||||
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()
|
||||||
|
@ -48,7 +51,7 @@ def gather_models(apply_modifiers: bool, export_target: str) -> List[Model]:
|
||||||
|
|
||||||
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)
|
model.geometry = create_mesh_geometry(mesh, obj.vertex_groups)
|
||||||
obj.to_mesh_clear()
|
obj.to_mesh_clear()
|
||||||
|
|
||||||
_, _, world_scale = obj.matrix_world.decompose()
|
_, _, world_scale = obj.matrix_world.decompose()
|
||||||
|
@ -64,6 +67,9 @@ 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)
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -80,7 +86,7 @@ def create_parents_set() -> Set[str]:
|
||||||
|
|
||||||
return parents
|
return parents
|
||||||
|
|
||||||
def create_mesh_geometry(mesh: bpy.types.Mesh) -> 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. """
|
||||||
|
|
||||||
|
@ -93,7 +99,7 @@ def create_mesh_geometry(mesh: bpy.types.Mesh) -> List[GeometrySegment]:
|
||||||
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)]
|
||||||
|
|
||||||
|
@ -101,6 +107,10 @@ def create_mesh_geometry(mesh: bpy.types.Mesh) -> List[GeometrySegment]:
|
||||||
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
|
||||||
|
|
||||||
|
@ -139,6 +149,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh) -> List[GeometrySegment]:
|
||||||
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)
|
||||||
|
|
||||||
|
@ -162,6 +177,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh) -> List[GeometrySegment]:
|
||||||
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
|
||||||
|
|
||||||
for tri in mesh.loop_triangles:
|
for tri in mesh.loop_triangles:
|
||||||
|
@ -181,9 +201,11 @@ def create_mesh_geometry(mesh: bpy.types.Mesh) -> List[GeometrySegment]:
|
||||||
|
|
||||||
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. """
|
||||||
# TODO: Skinning support, etc
|
|
||||||
|
|
||||||
if obj.type in MESH_OBJECT_TYPES:
|
if obj.type in MESH_OBJECT_TYPES:
|
||||||
|
if obj.vertex_groups:
|
||||||
|
return ModelType.SKIN
|
||||||
|
else:
|
||||||
return ModelType.STATIC
|
return ModelType.STATIC
|
||||||
|
|
||||||
return ModelType.NULL
|
return ModelType.NULL
|
||||||
|
@ -320,6 +342,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))
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,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:
|
||||||
|
@ -24,7 +25,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)
|
_write_modl(modl, model, index, material_index, model_index)
|
||||||
|
|
||||||
with hedr.create_child("CL1L"):
|
with hedr.create_child("CL1L"):
|
||||||
pass
|
pass
|
||||||
|
@ -97,7 +98,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]):
|
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)
|
||||||
|
|
||||||
|
@ -124,6 +125,10 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str
|
||||||
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.bone_map:
|
||||||
|
with geom.create_child("ENVL") as envl:
|
||||||
|
_write_envl(envl, model, model_index)
|
||||||
|
|
||||||
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:
|
||||||
swci.write_u32(model.collisionprimitive.shape.value)
|
swci.write_u32(model.collisionprimitive.shape.value)
|
||||||
|
@ -147,6 +152,10 @@ 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))
|
||||||
|
|
||||||
|
@ -189,3 +198,23 @@ def _write_segm(segm: Writer, segment: GeometrySegment, material_index: Dict[str
|
||||||
|
|
||||||
for index in islice(strip, 2, len(strip)):
|
for index in islice(strip, 2, len(strip)):
|
||||||
strp.write_u16(index)
|
strp.write_u16(index)
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
Loading…
Reference in New Issue