4 Commits

Author SHA1 Message Date
SleepKiller
dac3ade7a4 initial vertex weights implementation 2020-10-16 13:40:27 +13:00
SleepKiller
b56fa79a19 update version number 2020-02-04 14:44:22 +13:00
SleepKiller
47fa855b78 fixed colour packing order 2020-02-04 14:28:34 +13:00
SleepKiller
2010dd21b2 correct -keepmaterial documentation
Thanks to Fox!
2020-01-31 04:32:11 +13:00
6 changed files with 98 additions and 15 deletions

View File

@@ -1,7 +1,7 @@
bl_info = { bl_info = {
'name': 'SWBF .msh export', 'name': 'SWBF .msh export',
'author': 'SleepKiller', 'author': 'SleepKiller',
"version": (0, 2, 0), "version": (0, 2, 1),
'blender': (2, 80, 0), 'blender': (2, 80, 0),
'location': 'File > Import-Export', 'location': 'File > Import-Export',
'description': 'Export as SWBF .msh file', 'description': 'Export as SWBF .msh file',

View File

@@ -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

View File

@@ -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))

View File

@@ -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])

View File

@@ -23,8 +23,8 @@ def min_vec(l: Vector, r: Vector) -> Vector:
def pack_color(color) -> int: def pack_color(color) -> int:
packed = 0 packed = 0
packed |= (int(color[0] * 255.0 + 0.5) << 8) packed |= (int(color[0] * 255.0 + 0.5) << 16)
packed |= (int(color[1] * 255.0 + 0.5) << 16) packed |= (int(color[1] * 255.0 + 0.5) << 8)
packed |= (int(color[2] * 255.0 + 0.5)) packed |= (int(color[2] * 255.0 + 0.5))
packed |= (int(color[3] * 255.0 + 0.5) << 24) packed |= (int(color[3] * 255.0 + 0.5) << 24)

View File

@@ -536,9 +536,7 @@ Keep all named objects in the .msh file as hardpoints.
#### -keepmaterial #### -keepmaterial
- Usage Example: `-keepmaterial override_texture` - Usage Example: `-keepmaterial override_texture`
By default material names are not saved in .model files. And meshes referencing differently named materials but with identical names may be merged together to boost performance. Prevents the named object being marged with other objects by modelmunge and gives the object's .model material the same name as the object.
By specifying "-keepmaterial" for a material modelmunge is instructed to keep the name of a material around and to not merge meshes using the material with others that aren't.
This is used with the "OverrideTexture", "OverrideTexture2" and "WheelTexture" .odf properties. This is used with the "OverrideTexture", "OverrideTexture2" and "WheelTexture" .odf properties.