diff --git a/addons/io_scene_swbf_msh/msh_model.py b/addons/io_scene_swbf_msh/msh_model.py index 0dc75ee..d97918f 100644 --- a/addons/io_scene_swbf_msh/msh_model.py +++ b/addons/io_scene_swbf_msh/msh_model.py @@ -18,7 +18,7 @@ class CollisionPrimitiveShape(Enum): # ELLIPSOID = 1 CYLINDER = 2 # MESH = 3 - CUBE = 4 + BOX = 4 @dataclass class ModelTransform: @@ -47,10 +47,10 @@ class GeometrySegment: class CollisionPrimitive: """ Class representing a 'SWCI' section in a .msh file. """ - collision_primitive_shape: CollisionPrimitiveShape - radius: float - height: float - length: float + shape: CollisionPrimitiveShape = CollisionPrimitiveShape.SPHERE + radius: float = 0.0 + height: float = 0.0 + length: float = 0.0 @dataclass class Model: diff --git a/addons/io_scene_swbf_msh/msh_model_gather.py b/addons/io_scene_swbf_msh/msh_model_gather.py index fbf014a..221731c 100644 --- a/addons/io_scene_swbf_msh/msh_model_gather.py +++ b/addons/io_scene_swbf_msh/msh_model_gather.py @@ -2,6 +2,7 @@ Model objects. """ import bpy +import math from typing import List, Set, Dict, Tuple from itertools import zip_longest from .msh_model import * @@ -44,6 +45,9 @@ def gather_models() -> List[Model]: mesh_scale = convert_vector_space(get_object_worldspace_scale(obj)) scale_segments(mesh_scale, model.geometry) + if get_is_collision_primitive(obj): + model.collisionprimitive = get_collision_primitive(obj) + models_list.append(model) return models_list @@ -163,6 +167,56 @@ def get_is_model_hidden(obj: bpy.types.Object) -> bool: return False +def get_is_collision_primitive(obj: bpy.types.Object) -> bool: + """ Gets if a Blender object represents a collision primitive. """ + + name = obj.name.lower() + + return name.startswith("p_") + +def get_collision_primitive(obj: bpy.types.Object) -> CollisionPrimitive: + """ Gets the CollisionPrimitive of an object or raises an error if + it can't. """ + + primitive = CollisionPrimitive() + primitive.shape = get_collision_primitive_shape(obj) + + if primitive.shape == CollisionPrimitiveShape.SPHERE: + # Tolerate a 5% difference to account for icospheres with 2 subdivisions. + if not (math.isclose(obj.dimensions[0], obj.dimensions[1], rel_tol=0.05) and + math.isclose(obj.dimensions[0], obj.dimensions[2], rel_tol=0.05)): + raise RuntimeError(f"Object '{obj.name}' is being used as a sphere collision " + f"primitive but it's dimensions are not uniform!") + + primitive.radius = max(obj.dimensions[0], obj.dimensions[1], obj.dimensions[2]) * 0.5 + elif primitive.shape == CollisionPrimitiveShape.CYLINDER: + if not math.isclose(obj.dimensions[0], obj.dimensions[1], rel_tol=0.001): + raise RuntimeError(f"Object '{obj.name}' is being used as a cylinder collision " + f"primitive but it's X and Y dimensions are not uniform!") + primitive.radius = obj.dimensions[0] * 0.5 + primitive.height = obj.dimensions[2] * 0.5 + elif primitive.shape == CollisionPrimitiveShape.BOX: + primitive.radius = obj.dimensions[0] * 0.5 + primitive.height = obj.dimensions[2] * 0.5 + primitive.length = obj.dimensions[1] * 0.5 + + return primitive + +def get_collision_primitive_shape(obj: bpy.types.Object) -> CollisionPrimitiveShape: + """ Gets the CollisionPrimitiveShape of an object or raises an error if + it can't. """ + + name = obj.name.lower() + + if "sphere" in name or "sphr" in name or "spr" in name: + return CollisionPrimitiveShape.SPHERE + if "cylinder" in name or "cyln" in name or "cyl" in name: + return CollisionPrimitiveShape.CYLINDER + if "box" in name: + return CollisionPrimitiveShape.BOX + + raise RuntimeError(f"Object '{obj.name}' has no primitive type specified in it's name!") + def convert_vector_space(vec: Vector) -> Vector: return Vector(vec.xzy) diff --git a/addons/io_scene_swbf_msh/msh_scene_save.py b/addons/io_scene_swbf_msh/msh_scene_save.py index bb14db0..71914a0 100644 --- a/addons/io_scene_swbf_msh/msh_scene_save.py +++ b/addons/io_scene_swbf_msh/msh_scene_save.py @@ -124,7 +124,12 @@ def _write_modl(modl: Writer, model: Model, index: int, material_index: Dict[str with geom.create_child("SEGM") as segm: _write_segm(segm, segment, material_index) - # TODO: Collision Primitive + if model.collisionprimitive is not None: + with modl.create_child("SWCI") as swci: + swci.write_u32(model.collisionprimitive.shape.value) + swci.write_f32(model.collisionprimitive.radius) + swci.write_f32(model.collisionprimitive.height) + swci.write_f32(model.collisionprimitive.length) def _write_tran(tran: Writer, transform: ModelTransform): tran.write_f32(1.0, 1.0, 1.0) # Scale, ignored by modelmunge