From fa74ed03141fc8c6cb3c3c21bee523eb7a41731c Mon Sep 17 00:00:00 2001 From: KeppyMarbles Date: Thu, 11 Sep 2025 00:01:23 -0500 Subject: [PATCH 1/3] Path Trigger support and more MP options --- blender_plugin/io_dif/__init__.py | 57 ++++- blender_plugin/io_dif/export_dif.py | 332 ++++++++++++++++++---------- rust/difbuilder/src/lib.rs | 183 +++++++++++++-- rust/libdifbuilder/src/builder.rs | 4 +- 4 files changed, 424 insertions(+), 152 deletions(-) diff --git a/blender_plugin/io_dif/__init__.py b/blender_plugin/io_dif/__init__.py index e0186c5..dec8549 100644 --- a/blender_plugin/io_dif/__init__.py +++ b/blender_plugin/io_dif/__init__.py @@ -79,26 +79,48 @@ def execute(self, context): dif_props: InteriorSettings = context.object.dif_props prop = dif_props.game_entity_properties.remove(self.delete_id) return {"FINISHED"} - + + +def set_marker_path(self, context): + curve = self.marker_path + if curve: + for spline in curve.splines: + spline.type = 'POLY' class InteriorSettings(bpy.types.PropertyGroup): interior_type: EnumProperty( name="Interior Entity Type", items=( - ("static_interior", "InteriorResource", "Normal static interior"), - ("pathed_interior", "PathedInterior", "Moving interior"), - ("game_entity", "Game Entity", "An entity in the game"), + ("static_interior", "Interior Resource", "Normal static interior"), + ("pathed_interior", "Pathed Interior", "Moving interior"), + ("game_entity", "Game Entity", "A game object"), + ("path_trigger", "Path Trigger", "A trigger for a pathed interior"), ), default="static_interior", + description="How this object should be interpreted for the exporter.", ) - - marker_path: PointerProperty(type=bpy.types.Curve, name="Marker Path") + + marker_path: PointerProperty(type=bpy.types.Curve, name="Marker Path", description="The path to create markers from.", update=set_marker_path) + pathed_interior_target: PointerProperty(type=bpy.types.Object, name="Pathed Interior Target", description="The platform to trigger.") game_entity_datablock: StringProperty(name="Datablock") game_entity_gameclass: StringProperty(name="Game Class") game_entity_properties: CollectionProperty( type=InteriorKVP, name="Custom Properties" ) + + marker_type: EnumProperty( + name="Marker Type", + items=( + ("linear", "Linear", "Linear interpolation"), + ("spline", "Spline", "Centripetal Catmull–Rom path"), + ("accelerate", "Accelerate", "Sinusoidal easing"), + ), + description="The type of smoothing that should be applied to all markers exported from the path.", + ) + total_time: IntProperty(name="Total Time", description="The total time (in ms) from path start to end. Equally divided across each marker on export.", default=3000) + start_time: IntProperty(name="Starting Time", description="The time in the path (in ms) that the platform should be at level restart.", default=0) + reverse: BoolProperty(name = "Reverse", description = "If the platform should loop backwards (if not using a trigger).") class InteriorPanel(bpy.types.Panel): bl_label = "DIF properties" @@ -110,17 +132,32 @@ class InteriorPanel(bpy.types.Panel): def draw(self, context): layout = self.layout obj = context - sublayout = layout.row() - sublayout.prop(context.object.dif_props, "interior_type") + sublayout.prop(context.object.dif_props, "interior_type") #TODO only show this on relevant objects? + + if(isinstance(context.object, bpy.types.Curve)): + sublayout = layout.row() + sublayout.prop(context.object.dif_props, "marker_type") + if context.object.dif_props.interior_type == "pathed_interior": sublayout = layout.row() sublayout.prop(context.object.dif_props, "marker_path") - if context.object.dif_props.interior_type == "game_entity": + sublayout = layout.row() + sublayout.prop(context.object.dif_props, "marker_type") + sublayout = layout.row() + sublayout.prop(context.object.dif_props, "total_time") + sublayout = layout.row() + sublayout.prop(context.object.dif_props, "start_time") + sublayout = layout.row() + sublayout.prop(context.object.dif_props, "reverse") + if context.object.dif_props.interior_type in ["game_entity", "path_trigger"]: sublayout = layout.row() sublayout.prop(context.object.dif_props, "game_entity_datablock") sublayout = layout.row() - sublayout.prop(context.object.dif_props, "game_entity_gameclass") + if context.object.dif_props.interior_type == "path_trigger": + sublayout.prop(context.object.dif_props, "pathed_interior_target") + else: + sublayout.prop(context.object.dif_props, "game_entity_gameclass") sublayout = layout.row() sublayout.label(text="Properties:") sublayout = layout.row() diff --git a/blender_plugin/io_dif/export_dif.py b/blender_plugin/io_dif/export_dif.py index c1494c1..ca97b57 100644 --- a/blender_plugin/io_dif/export_dif.py +++ b/blender_plugin/io_dif/export_dif.py @@ -51,6 +51,9 @@ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_float), ] difbuilderlib.new_marker_list.restype = ctypes.c_void_p @@ -61,6 +64,13 @@ ctypes.c_int, ctypes.c_int, ] +difbuilderlib.new_trigger_id_list.restype = ctypes.c_void_p +difbuilderlib.dispose_trigger_id_list.argtypes = [ctypes.c_void_p] +difbuilderlib.push_trigger_id.argtypes = [ + ctypes.c_void_p, + ctypes.c_int, +] + difbuilderlib.add_game_entity.argtypes = [ ctypes.c_void_p, ctypes.c_char_p, @@ -78,6 +88,7 @@ difbuilderlib.add_trigger.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), + ctypes.POINTER(ctypes.c_float), ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p, @@ -105,9 +116,20 @@ def __init__(self): def __del__(self): difbuilderlib.dispose_marker_list(self.__ptr__) - def push_marker(self, vec, msToNext, initialPathPosition): + def push_marker(self, vec, msToNext, smoothing_type): vecarr = (ctypes.c_float * len(vec))(*vec) - difbuilderlib.push_marker(self.__ptr__, vecarr, msToNext, initialPathPosition) + difbuilderlib.push_marker(self.__ptr__, vecarr, msToNext, smoothing_type) + + +class TriggerIDList: + def __init__(self): + self.__ptr__ = difbuilderlib.new_trigger_id_list() + + def __del__(self): + difbuilderlib.dispose_trigger_id_list(self.__ptr__) + + def push_trigger_id(self, num): + difbuilderlib.push_trigger_id(self.__ptr__, num) class DIFDict: @@ -137,20 +159,26 @@ def write_dif(self, path): self.__ptr__, ctypes.create_string_buffer(path.encode("ascii")) ) - def add_game_entity(self, gameClass, datablock, position, scale, properties: dict): - vecarr = (ctypes.c_float * len(position))(*position) - propertydict = DIFDict() - for key in properties: - propertydict.add_kvp(key, properties[key]) - propertydict.add_kvp("scale", "%.5f %.5f %.5f" % (scale[0], scale[1], scale[2])) - if gameClass == "Trigger": - propertydict.add_kvp("polyhedron", "0 0 0 1 0 0 0 -1 0 0 0 1") + def add_game_entity(self, entity): + vecarr = (ctypes.c_float * len(entity.position))(*entity.position) difbuilderlib.add_game_entity( self.__ptr__, - ctypes.create_string_buffer(gameClass.encode("ascii")), - ctypes.create_string_buffer(datablock.encode("ascii")), + ctypes.create_string_buffer(entity.gameclass.encode("ascii")), + ctypes.create_string_buffer(entity.datablock.encode("ascii")), vecarr, - propertydict.__ptr__, + entity.properties.__ptr__, + ) + + def add_trigger(self, trigger): + pos_vecarr = (ctypes.c_float * len(trigger.position))(*trigger.position) + size_vecarr = (ctypes.c_float * len(trigger.size))(*trigger.size) + difbuilderlib.add_trigger( + self.__ptr__, + pos_vecarr, + size_vecarr, + ctypes.create_string_buffer(trigger.name.encode("ascii")), + ctypes.create_string_buffer(trigger.datablock.encode("ascii")), + trigger.properties.__ptr__, ) @@ -182,20 +210,9 @@ def add_triangle(self, p1, p2, p3, uv1, uv2, uv3, n, material): self.__ptr__, p3arr, p2arr, p1arr, uv3arr, uv2arr, uv1arr, narr, mat ) - def add_pathed_interior(self, dif: Dif, markerlist: MarkerList): - difbuilderlib.add_pathed_interior(self.__ptr__, dif.__ptr__, markerlist.__ptr__) - - # NONFUNCTIONAL, TRIGGERS ARENT GETTING CREATED WHEN PRESSING CREATE SUBS - def add_trigger(self, datablock, name, position, scale, props: DIFDict): - posarr = (ctypes.c_float * len(position))(*position) - props.add_kvp("scale", f"{scale[0]} {scale[1]} {scale[2]}") - difbuilderlib.add_trigger( - self.__ptr__, - posarr, - ctypes.create_string_buffer(name.encode("ascii")), - ctypes.create_string_buffer(datablock.encode("ascii")), - props.__ptr__, - ) + def add_pathed_interior(self, mp): + vecarr = (ctypes.c_float * len(mp.offset))(*mp.offset) + difbuilderlib.add_pathed_interior(self.__ptr__, mp.dif.__ptr__, mp.marker_list.__ptr__, mp.trigger_id_list.__ptr__, mp.properties.__ptr__, vecarr) def build(self, mbonly, bspmode, pointepsilon, planeepsilon, splitepsilon): return Dif(difbuilderlib.build(self.__ptr__, mbonly, bspmode, pointepsilon, planeepsilon, splitepsilon, update_status_c)) @@ -258,107 +275,179 @@ def get_offset(depsgraph, applymodifiers=True): off = [((maxv[i] - minv[i]) / 2) + 50 for i in range(0, 3)] return off + +class GamePathedInterior: + def __init__(self, ob: Object, triggers: list[Object], offset, flip, double, usematnames, mbonly=True, bspmode="Fast", pointepsilon=1e-6, planeepsilon=1e-5, splitepsilon=1e-4): + difbuilder = DifBuilder() -def build_pathed_interior(ob: Object, marker_ob: Curve, offset, flip, double, usematnames, mbonly=True, bspmode="Fast", pointepsilon=1e-6, planeepsilon=1e-5, splitepsilon=1e-4): - difbuilder = DifBuilder() - - mesh = ob.to_mesh() + mesh = ob.to_mesh() - mesh.calc_loop_triangles() - if bpy.app.version < (4, 0, 0): - mesh.calc_normals_split() + mesh.calc_loop_triangles() + if bpy.app.version < (4, 0, 0): + mesh.calc_normals_split() - mesh_verts = mesh.vertices + mesh_verts = mesh.vertices - if mesh.uv_layers != None and mesh.uv_layers.active != None: - active_uv_layer = mesh.uv_layers.active.data - else: - active_uv_layer = mesh.attributes.get('UVMap') + if mesh.uv_layers != None and mesh.uv_layers.active != None: + active_uv_layer = mesh.uv_layers.active.data + else: + active_uv_layer = mesh.attributes.get('UVMap') - mesh_verts = mesh.vertices + for tri_idx in mesh.loop_triangles: + tri: bpy.types.MeshLoopTriangle = tri_idx - active_uv_layer = mesh.uv_layers.active.data + rawp1 = mesh_verts[tri.vertices[0]].co + rawp2 = mesh_verts[tri.vertices[1]].co + rawp3 = mesh_verts[tri.vertices[2]].co - for tri_idx in mesh.loop_triangles: - tri: bpy.types.MeshLoopTriangle = tri_idx + p1 = [rawp1[i] + offset[i] for i in range(0, 3)] + p2 = [rawp2[i] + offset[i] for i in range(0, 3)] + p3 = [rawp3[i] + offset[i] for i in range(0, 3)] - rawp1 = mesh_verts[tri.vertices[0]].co - rawp2 = mesh_verts[tri.vertices[1]].co - rawp3 = mesh_verts[tri.vertices[2]].co + uv1 = active_uv_layer[tri.loops[0]].uv[:] + uv2 = active_uv_layer[tri.loops[1]].uv[:] + uv3 = active_uv_layer[tri.loops[2]].uv[:] - p1 = [rawp1[i] + offset[i] for i in range(0, 3)] - p2 = [rawp2[i] + offset[i] for i in range(0, 3)] - p3 = [rawp3[i] + offset[i] for i in range(0, 3)] + n = tri.normal - uv1 = active_uv_layer[tri.loops[0]].uv[:] - uv2 = active_uv_layer[tri.loops[1]].uv[:] - uv3 = active_uv_layer[tri.loops[2]].uv[:] + material = ( + resolve_texture(mesh.materials[tri.material_index], usematnames) + if tri.material_index != None + else "NULL" + ) - n = tri.normal + if not flip: + difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) + if double: + difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) + else: + difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) + if double: + difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) - material = ( - resolve_texture(mesh.materials[tri.material_index], usematnames) - if tri.material_index != None - else "NULL" - ) + bspvalue = None + if bspmode == "Fast": + bspvalue = 0 + elif bspmode == "Exhaustive": + bspvalue = 1 + else: + bspvalue = 2 + + dif = difbuilder.build(mbonly, bspvalue, pointepsilon, planeepsilon, splitepsilon) + + marker_ob = ob.dif_props.marker_path + + marker_list = MarkerList() + + if(marker_ob): + marker_pts = ( + marker_ob.splines[0].bezier_points + if (len(marker_ob.splines[0].bezier_points) != 0) + else marker_ob.splines[0].points + ) + msToNext = int(ob.dif_props.total_time / (len(marker_pts)-1)) + + path_type = ob.dif_props.marker_type + if path_type == "linear": + smoothing_type = 0 + elif path_type == "spline": + smoothing_type = 1 + elif path_type == "accelerate": + smoothing_type = 2 + + curve_transform = None + curve_obj = next((obj for obj in bpy.data.objects if obj.data == marker_ob.original), None) + if curve_obj: + curve_transform = curve_obj.matrix_world + + for index, pt in enumerate(marker_pts): + co = pt.co + if len(co) == 4: + co = Vector((co.x, co.y, co.z)) + + if(curve_transform): + co = curve_transform @ co + + if index == len(marker_pts)-1: + marker_list.push_marker(co, 0, smoothing_type) + else: + marker_list.push_marker(co, msToNext, smoothing_type) - if not flip: - difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) - if double: - difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) else: - difbuilder.add_triangle(p3, p2, p1, uv3, uv2, uv1, n, material) - if double: - difbuilder.add_triangle(p1, p2, p3, uv1, uv2, uv3, n, material) + marker_list.push_marker(ob.location, ob.dif_props.total_time, 0) + marker_list.push_marker(ob.location, 0, 0) - bspvalue = None - if bspmode == "Fast": - bspvalue = 0 - elif bspmode == "Exhaustive": - bspvalue = 1 - else: - bspvalue = 2 + trigger_id_list = TriggerIDList() - dif = difbuilder.build(mbonly, bspvalue, pointepsilon, planeepsilon, splitepsilon) + if(ob.dif_props.reverse): + initial_target_position = -2 + else: + initial_target_position = -1 - marker_pts = ( - marker_ob.splines[0].bezier_points - if (len(marker_ob.splines[0].bezier_points) != 0) - else marker_ob.splines[0].points - ) - msToNext = int((marker_ob.path_duration / len(marker_pts))) - initialPathPosition = int(marker_ob.eval_time) + for index, trigger in enumerate(triggers): + if trigger.target_object is ob: + trigger_id_list.push_trigger_id(index) + initial_target_position = 0 - marker_list = MarkerList() + ob.to_mesh_clear() - for pt in marker_pts: - marker_list.push_marker(pt.co, msToNext, initialPathPosition) + self.dif = dif + self.marker_list = marker_list + self.trigger_id_list = trigger_id_list - ob.to_mesh_clear() + propertydict = DIFDict() + propertydict.add_kvp("initialTargetPosition", str(initial_target_position)) + propertydict.add_kvp("initialPosition", str(ob.dif_props.start_time)) - return (dif, marker_list) + self.properties = propertydict + self.offset = [-(ob.location[i] + offset[i]) for i in range(0, 3)] -def build_game_entity(ob: Object): - props = ob.dif_props - propertydict = {} - for prop in props.game_entity_properties: - propertydict[prop.key] = prop.value +class GameEntity: + def __init__(self, ob, offset): + props = ob.dif_props - axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle() - axis_ang = ( - axis_ang_raw[1], - axis_ang_raw[0][0], - axis_ang_raw[0][1], - axis_ang_raw[0][2], - ) + propertydict = DIFDict() + for prop in props.game_entity_properties: + propertydict.add_kvp(prop.key, prop.value) - return ( - props.game_entity_datablock, - props.game_entity_gameclass, - propertydict, - ob.scale, - ) + propertydict.add_kvp("scale", "%.5f %.5f %.5f" % (ob.scale[0], ob.scale[1], ob.scale[2])) + + axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle() + + #TODO fix or remove + from math import degrees + propertydict.add_kvp("rotation", "%.5f %.5f %.5f %.5f" % ( + axis_ang_raw[0][0], + axis_ang_raw[0][1], + axis_ang_raw[0][2], + degrees(axis_ang_raw[1])) + ) + + if props.game_entity_gameclass == "Trigger": + propertydict.add_kvp("polyhedron", "0 0 0 1 0 0 0 -1 0 0 0 1") + + self.position = [ob.location[i] + offset[i] for i in range(0, 3)] + self.datablock = props.game_entity_datablock + self.gameclass = props.game_entity_gameclass + self.properties = propertydict + + +class GameTrigger: + def __init__(self, ob, offset): + props = ob.dif_props + + propertydict = DIFDict() + for prop in props.game_entity_properties: + propertydict.add_kvp(prop.key, prop.value) + + self.position = [ob.location[i] + offset[i] for i in range(0, 3)] + self.size = [ob.scale[0], -ob.scale[1], ob.scale[2]] + self.datablock = props.game_entity_datablock + self.properties = propertydict + self.name = "MustChange" + + self.target_object = ob.dif_props.pathed_interior_target def save( @@ -454,7 +543,8 @@ def save_mesh(obj: Object, mesh: Mesh, offset, flip=False, double=False): tris += 1 mp_list = [] - game_entities: list[Object] = [] + game_entities: list[GameEntity] = [] + triggers: list[GameTrigger] = [] def is_object_instance_selected(object_instance): # For instanced objects we check selection of their instancer (more accurately: check @@ -490,7 +580,10 @@ def is_object_instance_visible(object_instance): dif_props = ob_eval.dif_props if dif_props.interior_type == "game_entity": - game_entities.append(ob_eval) + game_entities.append(GameEntity(ob_eval, off)) + + if dif_props.interior_type == "path_trigger": + triggers.append(GameTrigger(ob_eval, off)) try: me = ob_eval.to_mesh() @@ -507,7 +600,7 @@ def is_object_instance_visible(object_instance): ob_eval.to_mesh_clear() if dif_props.interior_type == "pathed_interior": - mp_list.append((ob_eval, dif_props.marker_path)) + mp_list.append(ob_eval) # handle object instances for these versions, ew code duplication if bpy.app.version >= (3, 1, 0) and applymodifiers: @@ -529,7 +622,10 @@ def is_object_instance_visible(object_instance): dif_props = ob_eval.dif_props if dif_props.interior_type == "game_entity": - game_entities.append(ob_eval) + game_entities.append(GameEntity(ob_eval, off)) + + if dif_props.interior_type == "path_trigger": + triggers.append(GameTrigger(ob_eval, off)) try: me = ob_eval.to_mesh() @@ -547,12 +643,12 @@ def is_object_instance_visible(object_instance): ob_eval.to_mesh_clear() if dif_props.interior_type == "pathed_interior": - mp_list.append((ob_eval, dif_props.marker_path)) + mp_list.append(ob_eval) mp_difs = [] - for (mp, curve) in mp_list: - mp_difs.append(build_pathed_interior(mp, curve, off, flip, double, usematnames, mbonly, bspmode, pointepsilon, planeepsilon, splitepsilon)) + for mp in mp_list: + mp_difs.append(GamePathedInterior(mp, triggers, off, flip, double, usematnames, mbonly, bspmode, pointepsilon, planeepsilon, splitepsilon)) bspvalue = None if bspmode == "Fast": @@ -565,20 +661,16 @@ def is_object_instance_visible(object_instance): if tris != 0: for i in range(0, len(builders)): if i == 0: - for (mpdif, markerlist) in mp_difs: - builders[i].add_pathed_interior(mpdif, markerlist) + for mp in mp_difs: + builders[i].add_pathed_interior(mp) dif = builders[i].build(mbonly, bspvalue, pointepsilon, planeepsilon, splitepsilon) if i == 0: for ge in game_entities: - entity = build_game_entity(ge) - dif.add_game_entity( - entity[1], - entity[0], - [ge.location[i] + off[i] for i in range(0, 3)], - entity[3], - entity[2], - ) + dif.add_game_entity(ge) + + for trigger in triggers: + dif.add_trigger(trigger) dif.write_dif(str(Path(filepath).with_suffix("")) + str(i) + ".dif") diff --git a/rust/difbuilder/src/lib.rs b/rust/difbuilder/src/lib.rs index b96da88..5996097 100644 --- a/rust/difbuilder/src/lib.rs +++ b/rust/difbuilder/src/lib.rs @@ -3,24 +3,17 @@ use std::{ collections::HashMap, ffi::{c_char, CStr, CString}, - io::Cursor, sync::Arc, thread, time::Instant, }; -use ::dif::types::Point3F; use cgmath::Quaternion; use dif::{ - dif::Dif, - game_entity::GameEntity, - interior::Interior, - interior_path_follower::{InteriorPathFollower, WayPoint}, - io::{Version, Writable}, - types::{Dictionary, Point2F}, + dif::Dif, game_entity::GameEntity, interior::Interior, interior_path_follower::{InteriorPathFollower, WayPoint}, io::{Version, Writable}, trigger::{Trigger, Polyhedron, PolyhedronEdge}, types::{Dictionary, Point2F, Point3F, PlaneF} }; use difbuilder::{ - builder::{self, ProgressEventListener, Triangle}, + builder::{self, ProgressEventListener}, set_convert_configuration, }; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; @@ -128,6 +121,9 @@ pub struct TriangleRaw { pub struct PathedInteriorImpl { pub interior: Interior, pub waypoints: Vec, + pub trigger_ids: Vec, + pub properties: Dictionary, + pub offset: Point3F, } pub struct DifBuilderImpl { @@ -139,6 +135,10 @@ pub struct MarkerListImpl { pub markers: Vec, } +pub struct TriggerIdListImpl { + pub trigger_ids: Vec, +} + #[no_mangle] pub extern "C" fn new_difbuilder() -> *const DifBuilderImpl { Arc::into_raw(Arc::new(DifBuilderImpl { @@ -196,16 +196,33 @@ pub unsafe extern "C" fn push_marker( ptr: *mut MarkerListImpl, pos: *const f32, ms_to_next: i32, - initial_target_position: i32, + smoothing_type: u32, ) { ptr.as_mut().unwrap().markers.push(WayPoint { ms_to_next: ms_to_next as u32, position: Point3F::new(*pos, *pos.offset(1), *pos.offset(2)), - smoothing_type: 0, + smoothing_type: smoothing_type, rotation: Quaternion::new(1.0, 0.0, 0.0, 0.0), }); } +#[no_mangle] +pub extern "C" fn new_trigger_id_list() -> *const TriggerIdListImpl { + Arc::into_raw(Arc::new(TriggerIdListImpl { + trigger_ids: Vec::new(), + })) +} + +#[no_mangle] +pub unsafe extern "C" fn dispose_trigger_id_list(ptr: *const TriggerIdListImpl) { + Arc::decrement_strong_count(ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn push_trigger_id(ptr: *mut TriggerIdListImpl, trigger_id: u32) { + ptr.as_mut().unwrap().trigger_ids.push(trigger_id); +} + #[no_mangle] pub unsafe extern "C" fn add_game_entity( ptr: *mut Dif, @@ -252,13 +269,133 @@ pub unsafe extern "C" fn add_triangle( #[no_mangle] pub unsafe extern "C" fn add_trigger( - ptr: *mut DifBuilderImpl, - pos: *const f32, - name: *const u8, - datablock: *const u8, + ptr: *mut Dif, + pos_vec: *const f32, + size_vec: *const f32, + name: *const c_char, + datablock: *const c_char, props: *const Dictionary, ) { - // Do nothing + let pos = Point3F::new(*pos_vec, *pos_vec.offset(1), *pos_vec.offset(2)); + let size = Point3F::new(*size_vec, *size_vec.offset(1), *size_vec.offset(2)); + ptr.as_mut().unwrap().triggers.push(Trigger { + name: CStr::from_ptr(name).to_str().unwrap().to_owned(), + datablock: CStr::from_ptr(datablock).to_str().unwrap().to_owned(), + offset: Point3F::new(0.0, 0.0, 0.0), + properties: props.as_ref().unwrap().clone(), + polyhedron: Polyhedron { + point_list: vec![ + Point3F::new(pos.x, pos.y, pos.z + size.z), + Point3F::new(pos.x, pos.y + size.y, pos.z + size.z), + Point3F::new(pos.x + size.x, pos.y + size.y, pos.z + size.z), + Point3F::new(pos.x + size.x, pos.y, pos.z + size.z), + Point3F::new(pos.x, pos.y, pos.z), + Point3F::new(pos.x, pos.y + size.y, pos.z), + Point3F::new(pos.x + size.x, pos.y + size.y, pos.z), + Point3F::new(pos.x + size.x, pos.y, pos.z), + ], + plane_list: vec![ + PlaneF { + normal: Point3F::new(-1.0, 0.0, 0.0), + distance: pos.x, + }, + PlaneF { + normal: Point3F::new(0.0, 1.0, 0.0), + distance: pos.y + size.y, + }, + PlaneF { + normal: Point3F::new(1.0, 0.0, 0.0), + distance: pos.x + size.x, + }, + PlaneF { + normal: Point3F::new(0.0, -1.0, 0.0), + distance: pos.y, + }, + PlaneF { + normal: Point3F::new(0.0, 0.0, 1.0), + distance: pos.z + size.z, + }, + PlaneF { + normal: Point3F::new(0.0, 0.0, -1.0), + distance: pos.z, + }, + ], + edge_list: vec![ + PolyhedronEdge { + face0: 0, + face1: 4, + vertex0: 0, + vertex1: 1, + }, + PolyhedronEdge { + face0: 5, + face1: 0, + vertex0: 4, + vertex1: 5, + }, + PolyhedronEdge { + face0: 3, + face1: 0, + vertex0: 0, + vertex1: 4, + }, + PolyhedronEdge { + face0: 1, + face1: 4, + vertex0: 1, + vertex1: 2, + }, + PolyhedronEdge { + face0: 5, + face1: 6, + vertex0: 5, + vertex1: 1, + }, + PolyhedronEdge { + face0: 0, + face1: 1, + vertex0: 1, + vertex1: 5, + }, + PolyhedronEdge { + face0: 2, + face1: 4, + vertex0: 2, + vertex1: 3, + }, + PolyhedronEdge { + face0: 5, + face1: 2, + vertex0: 6, + vertex1: 7, + }, + PolyhedronEdge { + face0: 1, + face1: 2, + vertex0: 2, + vertex1: 6, + }, + PolyhedronEdge { + face0: 3, + face1: 4, + vertex0: 3, + vertex1: 0, + }, + PolyhedronEdge { + face0: 5, + face1: 3, + vertex0: 7, + vertex1: 4, + }, + PolyhedronEdge { + face0: 2, + face1: 3, + vertex0: 3, + vertex1: 7, + }, + ], + }, + }); } #[no_mangle] @@ -266,10 +403,16 @@ pub unsafe extern "C" fn add_pathed_interior( ptr: *mut DifBuilderImpl, dif: *mut Dif, marker_list: *const MarkerListImpl, + trigger_id_list: *const TriggerIdListImpl, + props: *const Dictionary, + offset: *const f32, ) { - let mut pathed_interior = PathedInteriorImpl { + let pathed_interior = PathedInteriorImpl { interior: dif.as_mut().unwrap().interiors.swap_remove(0), waypoints: marker_list.as_ref().unwrap().markers.clone(), + trigger_ids: trigger_id_list.as_ref().unwrap().trigger_ids.clone(), + properties: props.as_ref().unwrap().clone(), + offset: Point3F::new(*offset, *offset.offset(1), *offset.offset(2)), }; ptr.as_mut().unwrap().pathed_interiors.push(pathed_interior); } @@ -334,15 +477,15 @@ pub unsafe extern "C" fn build( datablock: "PathedDefault".to_owned(), interior_res_index: sub_index as u32, name: "MustChange".to_owned(), - offset: Point3F::new(0.0, 0.0, 0.0), + offset: pathed_interior.offset, total_ms: pathed_interior .waypoints .iter() .map(|wp| wp.ms_to_next) .sum(), way_points: pathed_interior.waypoints.clone(), - trigger_ids: vec![], - properties: Dictionary::new(), + trigger_ids: pathed_interior.trigger_ids.clone(), + properties: pathed_interior.properties.clone(), }); } diff --git a/rust/libdifbuilder/src/builder.rs b/rust/libdifbuilder/src/builder.rs index 3412cb5..7792228 100644 --- a/rust/libdifbuilder/src/builder.rs +++ b/rust/libdifbuilder/src/builder.rs @@ -208,8 +208,8 @@ impl DIFBuilder { progress_report_callback: &mut dyn ProgressEventListener, ) -> (Interior, BSPReport) { self.interior.bounding_box = get_bounding_box(&self.brushes); - self.interior.bounding_box.min -= Point3F::new(3.0, 3.0, 3.0); - self.interior.bounding_box.max += Point3F::new(3.0, 3.0, 3.0); + //self.interior.bounding_box.min -= Point3F::new(3.0, 3.0, 3.0); + //self.interior.bounding_box.max += Point3F::new(3.0, 3.0, 3.0); self.interior.bounding_sphere = get_bounding_sphere(&self.brushes); self.export_brushes(progress_report_callback); self.interior.zones.push(Zone { From c179c22c591d5a96cadec45a0fa5ba611ef02d4b Mon Sep 17 00:00:00 2001 From: Keppy <147150384+KeppyMarbles@users.noreply.github.com> Date: Thu, 11 Sep 2025 00:18:39 -0500 Subject: [PATCH 2/3] Update README.md --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4144632..9d23b7e 100644 --- a/README.md +++ b/README.md @@ -71,18 +71,21 @@ Located in the object properties panel - Interior Entity Type: - InteriorResource: normal static interior type - - PathedInterior: moving platform type interior - - Marker Path: a curve object that describes the path of the moving platform - - initialPathPosition: set using the "Evaluation Time" parameter located in Curve > Object Data Properties > Path Animation - - totalPathTime: time it takes for the moving platform to complete the path, set using the "Frames" parameter located in Curve > Object Data Properties > Path Animation. + - PathedInterior: moving platform + - Marker Path: a curve object that describes the path of the moving platform. Each point will become a Marker + - Marker Type: the smoothing to use on each marker + - Total Time: the amount of time to complete the path + - Starting Time: the time that the platform should begin - Game Entity: represents an entity in the dif such as items - - Game Class: the class of the entity such as "Item", "StaticShape",etc + - Game Class: the class of the entity such as "Item", "StaticShape", etc - Datablock: the datablock of the item. - Properties: a list of additional key value pairs which will be set to the object on Create Subs + - Path Trigger: represents a trigger that will be added to the MustChange group + - Datablock: the trigger datablock, MBG's types are TriggerGotoTarget and TriggerGotoDelayTarget + - Pathed Interior: the target object ## Limitations -- No Trigger support: I tried but Torque was being Torque even when I successfully embedded them into difs. - No Game Entity rotation support: there isnt even a rotation field for Game Entities in difs, and torque doesnt even use the rotation field explicitly passed as a property ## Previews From 0e770bdcfa4e331a8d33203fdda96b9a4c0f6237 Mon Sep 17 00:00:00 2001 From: KeppyMarbles Date: Thu, 11 Sep 2025 16:53:12 -0500 Subject: [PATCH 3/3] Import path triggers --- blender_plugin/io_dif/import_dif.py | 51 ++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/blender_plugin/io_dif/import_dif.py b/blender_plugin/io_dif/import_dif.py index 6ae6b49..f9c3bc2 100644 --- a/blender_plugin/io_dif/import_dif.py +++ b/blender_plugin/io_dif/import_dif.py @@ -255,8 +255,10 @@ def load( itr = pathedInteriors[mover.interiorResIndex] itr: Object = itr.copy() base = scene.collection.objects.link(itr) - itr.location = [pos.x, pos.y, pos.z] + itr.location = [-pos.x, -pos.y, -pos.z] itr.dif_props.interior_type = "pathed_interior" + itr.dif_props.start_time = int(mover.properties.h.get("initialPosition", 0)) + itr.dif_props.reverse = mover.properties.h.get("initialTargetPosition", 0) == "-2" waypoints: list[WayPoint] = mover.wayPoint @@ -267,10 +269,8 @@ def load( curve = bpy.data.curves.new("markers", type="CURVE") curve.dimensions = "3D" - spline = curve.splines.new(type="NURBS") + spline = curve.splines.new(type="POLY") spline.points.add(len(markerpts) - 1) - spline.order_u = 2 - spline.resolution_u = 20 for p, new_co in zip(spline.points, markerpts): p.co = new_co + (1.0,) @@ -280,6 +280,47 @@ def load( itr.dif_props.marker_path = curve + total_time = 0 + for pt in waypoints: + total_time += pt.msToNext + itr.dif_props.total_time = total_time + + first_type = waypoints[0].smoothingType + if first_type == 0: + itr.dif_props.marker_type = "linear" + elif first_type == 1: + itr.dif_props.marker_type = "spline" + elif first_type == 2: + itr.dif_props.marker_type = "accelerate" + + for trigger_id in mover.triggerId: + trigger = dif.triggers[trigger_id] + tobj = bpy.data.objects.new(trigger.datablock, None) + tobj.dif_props.interior_type = "path_trigger" + tobj.dif_props.pathed_interior_target = itr + tobj.dif_props.game_entity_datablock = trigger.datablock + for key in trigger.properties.h: + prop = tobj.dif_props.game_entity_properties.add() + prop.key = key + prop.value = trigger.properties.get(key) + + t_min = mathutils.Vector((float('inf'), float('inf'), float('inf'))) + t_max = mathutils.Vector((-float('inf'), -float('inf'), -float('inf'))) + for p in trigger.polyhedron.pointList: + t_min.x = min(t_min.x, p.x) + t_min.y = min(t_min.y, p.y) + t_min.z = min(t_min.z, p.z) + + t_max.x = max(t_max.x, p.x) + t_max.y = max(t_max.y, p.y) + t_max.z = max(t_max.z, p.z) + + tobj.location = t_min + tobj.scale = mathutils.Vector((t_max.x - t_min.x, t_max.y - t_min.y, t_max.z - t_min.z)) + tobj.location.y += tobj.scale.y + tobj.location += mathutils.Vector((trigger.offset.x, trigger.offset.y, trigger.offset.z)) + scene.collection.objects.link(tobj) + if dif.gameEntities != None: for ge in dif.gameEntities: g: GameEntity = ge @@ -293,7 +334,7 @@ def load( prop.key = key prop.value = g.properties.get(key) scene.collection.objects.link(gobj) - + context.view_layer.update() axis_min = [1000000000] * 3