Skip to content

Commit f2e043d

Browse files
authored
Merge pull request #8 from KeppyMarbles/autodif-stuffs
Prep for autodif plugin
2 parents 41e972b + a7eacca commit f2e043d

4 files changed

Lines changed: 152 additions & 43 deletions

File tree

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# IO DIF
22

33
Blender plugin to import and export MBG Torque DIF interiors and Torque Constructor CSX files.
4-
Supported Blender Versions: 2.8.0 to 4.3
4+
Supported Blender Versions: 2.8.0 to 4.5
55

66
## Note
77

@@ -75,18 +75,24 @@ Located in the object properties panel
7575
- Marker Path: a curve object that describes the path of the moving platform. Each point will become a Marker
7676
- Marker Type: the smoothing to use on each marker
7777
- Total Time: the amount of time to complete the path
78-
- Starting Time: the time that the platform should begin
78+
- Start Time: the time that the platform should begin
79+
- Constant Speed: if the marker durations should instead be calculated to maintain a consistent speed
80+
- Speed: max speed in units per second
81+
- Start Index: Calculates Start Time based on marker index
82+
- Pause Duration: The time that the platform should spend at zero-length segments
7983
- Game Entity: represents an entity in the dif such as items
8084
- Game Class: the class of the entity such as "Item", "StaticShape", etc
8185
- Datablock: the datablock of the item.
8286
- Properties: a list of additional key value pairs which will be set to the object on Create Subs
8387
- Path Trigger: represents a trigger that will be added to the MustChange group
8488
- Datablock: the trigger datablock, MBG's types are TriggerGotoTarget and TriggerGotoDelayTarget
8589
- Pathed Interior: the target object
90+
- Calculate Target Time: if targetTime property should be created from a target marker index
91+
- Target Index: the marker to target
8692

8793
## Limitations
8894

89-
- 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
95+
- Limited Game Entity rotation support: rotation field is not properly applied to Game Entities in vanilla Torque
9096

9197
## Previews
9298

blender_plugin/io_dif/__init__.py

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"author": "RandomityGuy",
4646
"description": "Dif import and export plugin for blender",
4747
"blender": (2, 80, 0),
48-
"version": (1, 3, 2),
48+
"version": (1, 3, 3),
4949
"location": "File > Import-Export",
5050
"warning": "",
5151
"category": "Import-Export",
@@ -88,6 +88,7 @@ def set_marker_path(self, context):
8888
spline.type = 'POLY'
8989

9090
class InteriorSettings(bpy.types.PropertyGroup):
91+
# Interiors
9192
interior_type: EnumProperty(
9293
name="Interior Entity Type",
9394
items=(
@@ -99,15 +100,15 @@ class InteriorSettings(bpy.types.PropertyGroup):
99100
default="static_interior",
100101
description="How this object should be interpreted for the exporter.",
101102
)
102-
103103
marker_path: PointerProperty(type=bpy.types.Curve, name="Marker Path", description="The path to create markers from.", update=set_marker_path)
104-
pathed_interior_target: PointerProperty(type=bpy.types.Object, name="Pathed Interior Target", description="The platform to trigger.")
105-
game_entity_datablock: StringProperty(name="Datablock")
106-
game_entity_gameclass: StringProperty(name="Game Class")
107-
game_entity_properties: CollectionProperty(
108-
type=InteriorKVP, name="Custom Properties"
109-
)
110-
104+
constant_speed: BoolProperty(name = "Constant Speed", description = "If the marker durations should be based on speed instead of total time.", default=True)
105+
speed: FloatProperty(name="Speed", description="The speed that the platform should be moving at. If using Accelerate smoothing, this is max speed.", default=1, min=0.01, max=100)
106+
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, min=1)
107+
start_time: IntProperty(name="Start Time", description="The time in the path (in ms) that the platform should be at level restart.", default=0, min=0)
108+
start_index: IntProperty(name="Start Index", description="The marker that the platform should be at level restart (0 is 1st marker).", default=0, min=0, soft_max=10)
109+
pause_duration: IntProperty(name = "Pause Duration", description="At a path segment of length 0, the platform will wait this long (in ms).", default=0, min=0, soft_max=10000)
110+
reverse: BoolProperty(name = "Reverse", description = "If the platform should loop backwards (if not using a trigger).")
111+
111112
marker_type: EnumProperty(
112113
name="Marker Type",
113114
items=(
@@ -118,9 +119,18 @@ class InteriorSettings(bpy.types.PropertyGroup):
118119
description="The type of smoothing that should be applied to all markers exported from the path.",
119120
)
120121

121-
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)
122-
start_time: IntProperty(name="Starting Time", description="The time in the path (in ms) that the platform should be at level restart.", default=0)
123-
reverse: BoolProperty(name = "Reverse", description = "If the platform should loop backwards (if not using a trigger).")
122+
# Triggers
123+
pathed_interior_target: PointerProperty(type=bpy.types.Object, name="Pathed Interior Target", description="The platform to trigger.")
124+
target_marker: BoolProperty(name = "Calculate Target Time", description="If enabled, the targetTime will be calculated to be at a specific marker.", default=True)
125+
target_index: IntProperty(name = "Target Index", description="The marker to target (0 is 1st marker).", default=0, min=0, soft_max=10)
126+
127+
# Entities
128+
game_entity_datablock: StringProperty(name="Datablock")
129+
game_entity_gameclass: StringProperty(name="Game Class")
130+
game_entity_properties: CollectionProperty(
131+
type=InteriorKVP, name="Custom Properties"
132+
)
133+
124134

125135
class InteriorPanel(bpy.types.Panel):
126136
bl_label = "DIF properties"
@@ -131,7 +141,6 @@ class InteriorPanel(bpy.types.Panel):
131141

132142
def draw(self, context):
133143
layout = self.layout
134-
obj = context
135144
sublayout = layout.row()
136145
sublayout.prop(context.object.dif_props, "interior_type") #TODO only show this on relevant objects?
137146

@@ -145,17 +154,35 @@ def draw(self, context):
145154
sublayout = layout.row()
146155
sublayout.prop(context.object.dif_props, "marker_type")
147156
sublayout = layout.row()
148-
sublayout.prop(context.object.dif_props, "total_time")
149-
sublayout = layout.row()
150-
sublayout.prop(context.object.dif_props, "start_time")
157+
sublayout.prop(context.object.dif_props, "constant_speed")
151158
sublayout = layout.row()
159+
if context.object.dif_props.constant_speed:
160+
sublayout.prop(context.object.dif_props, "speed")
161+
sublayout = layout.row()
162+
sublayout.prop(context.object.dif_props, "start_index")
163+
sublayout = layout.row()
164+
sublayout.prop(context.object.dif_props, "pause_duration")
165+
sublayout = layout.row()
166+
else:
167+
sublayout.prop(context.object.dif_props, "total_time")
168+
sublayout = layout.row()
169+
sublayout.prop(context.object.dif_props, "start_time")
170+
sublayout = layout.row()
171+
152172
sublayout.prop(context.object.dif_props, "reverse")
173+
153174
if context.object.dif_props.interior_type in ["game_entity", "path_trigger"]:
154175
sublayout = layout.row()
155176
sublayout.prop(context.object.dif_props, "game_entity_datablock")
156177
sublayout = layout.row()
157178
if context.object.dif_props.interior_type == "path_trigger":
158179
sublayout.prop(context.object.dif_props, "pathed_interior_target")
180+
sublayout = layout.row()
181+
sublayout.prop(context.object.dif_props, "target_marker")
182+
sublayout = layout.row()
183+
if context.object.dif_props.target_marker:
184+
sublayout.prop(context.object.dif_props, "target_index")
185+
sublayout = layout.row()
159186
else:
160187
sublayout.prop(context.object.dif_props, "game_entity_gameclass")
161188
sublayout = layout.row()
@@ -204,7 +231,7 @@ def execute(self, context):
204231
)
205232
)
206233

207-
if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths:
234+
if bpy.data.is_saved and context.preferences.filepaths.use_relative_paths:
208235
import os
209236

210237
keywords["relpath"] = os.path.dirname(bpy.data.filepath)
@@ -242,7 +269,7 @@ def execute(self, context):
242269
)
243270
)
244271

245-
if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths:
272+
if bpy.data.is_saved and context.preferences.filepaths.use_relative_paths:
246273
import os
247274

248275
keywords["relpath"] = os.path.dirname(bpy.data.filepath)

blender_plugin/io_dif/export_dif.py

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from bpy.types import Curve, Image, Material, Mesh, Object, ShaderNodeTexImage
1212
from bpy_extras.wm_utils.progress_report import ProgressReport, ProgressReportSubstep
13-
from mathutils import Quaternion, Vector
13+
from mathutils import Quaternion, Vector, Matrix
1414

1515
if platform.system() == "Windows":
1616
dllpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "DifBuilderLib.dll")
@@ -105,10 +105,6 @@ def update_status(stop, current, total, status, finish_status):
105105

106106
update_status_c = STATUSFN(update_status)
107107

108-
scene = bpy.context.scene
109-
110-
obj = bpy.context.active_object
111-
112108
class MarkerList:
113109
def __init__(self):
114110
self.__ptr__ = difbuilderlib.new_marker_list()
@@ -275,7 +271,20 @@ def get_offset(depsgraph, applymodifiers=True):
275271
off = [((maxv[i] - minv[i]) / 2) + 50 for i in range(0, 3)]
276272
return off
277273

274+
275+
def formatScale(scale):
276+
return "%.5f %.5f %.5f" % (scale[0], scale[1], scale[2])
277+
278+
279+
def formatRotation(axis_ang):
280+
from math import degrees
281+
return "%.5f %.5f %.5f %.5f" % (
282+
axis_ang[0][0],
283+
axis_ang[0][1],
284+
axis_ang[0][2],
285+
degrees(-axis_ang[1]))
278286

287+
279288
class GamePathedInterior:
280289
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):
281290
difbuilder = DifBuilder()
@@ -345,7 +354,6 @@ def __init__(self, ob: Object, triggers: list[Object], offset, flip, double, use
345354
if (len(marker_ob.splines[0].bezier_points) != 0)
346355
else marker_ob.splines[0].points
347356
)
348-
msToNext = int(ob.dif_props.total_time / (len(marker_pts)-1))
349357

350358
path_type = ob.dif_props.marker_type
351359
if path_type == "linear":
@@ -360,25 +368,59 @@ def __init__(self, ob: Object, triggers: list[Object], offset, flip, double, use
360368
if curve_obj:
361369
curve_transform = curve_obj.matrix_world
362370

371+
cum_times = [0] # Used for "target marker" triggers and "start index"
372+
363373
for index, pt in enumerate(marker_pts):
374+
if index == len(marker_pts)-1:
375+
msToNext = 0
376+
else:
377+
if(ob.dif_props.constant_speed):
378+
p0 = Vector(marker_pts[index].co[:3])
379+
p1 = Vector(marker_pts[index+1].co[:3])
380+
marker_dist = (p1 - p0).length
381+
382+
if(ob.dif_props.marker_type == "spline"):
383+
p0 = marker_pts[index-1].co[:3]
384+
p1 = marker_pts[index].co[:3]
385+
p2 = marker_pts[index+1].co[:3]
386+
p3 = marker_pts[(index+2) % len(marker_pts)].co[:3]
387+
length = GamePathedInterior.catmull_rom_length(p0, p1, p2, p3)
388+
else:
389+
length = marker_dist
390+
391+
if(marker_dist < 0.01):
392+
msToNext = ob.dif_props.pause_duration
393+
else:
394+
msToNext = length / (ob.dif_props.speed / 1000)
395+
396+
else:
397+
msToNext = ob.dif_props.total_time / (len(marker_pts)-1)
398+
399+
msToNext = int(max(msToNext, 1))
400+
364401
co = pt.co
365402
if len(co) == 4:
366403
co = Vector((co.x, co.y, co.z))
367404

368405
if(curve_transform):
369406
co = curve_transform @ co
370407

371-
if index == len(marker_pts)-1:
372-
marker_list.push_marker(co, 0, smoothing_type)
373-
else:
374-
marker_list.push_marker(co, msToNext, smoothing_type)
408+
marker_list.push_marker(co, msToNext, smoothing_type)
409+
410+
cum_times.append(cum_times[-1]+msToNext)
375411

376412
else:
377413
marker_list.push_marker(ob.location, ob.dif_props.total_time, 0)
378414
marker_list.push_marker(ob.location, 0, 0)
379415

380416
trigger_id_list = TriggerIDList()
381417

418+
if(ob.dif_props.constant_speed):
419+
marker_idx = min(ob.dif_props.start_index, len(cum_times)-1)
420+
starting_time = cum_times[marker_idx]
421+
else:
422+
starting_time = ob.dif_props.start_time
423+
382424
if(ob.dif_props.reverse):
383425
initial_target_position = -2
384426
else:
@@ -387,7 +429,13 @@ def __init__(self, ob: Object, triggers: list[Object], offset, flip, double, use
387429
for index, trigger in enumerate(triggers):
388430
if trigger.target_object is ob:
389431
trigger_id_list.push_trigger_id(index)
390-
initial_target_position = 0
432+
initial_target_position = starting_time
433+
434+
# Update the trigger target time if using "target marker"
435+
if(trigger.target_marker):
436+
marker_idx = min(trigger.target_index, len(cum_times)-1)
437+
trigger.properties.add_kvp("targetTime", str(cum_times[marker_idx]))
438+
trigger.name = "MustChange_m" + str(marker_idx)
391439

392440
ob.to_mesh_clear()
393441

@@ -397,11 +445,40 @@ def __init__(self, ob: Object, triggers: list[Object], offset, flip, double, use
397445

398446
propertydict = DIFDict()
399447
propertydict.add_kvp("initialTargetPosition", str(initial_target_position))
400-
propertydict.add_kvp("initialPosition", str(ob.dif_props.start_time))
448+
propertydict.add_kvp("initialPosition", str(starting_time))
449+
450+
if(ob.matrix_world != Matrix.Identity(4)):
451+
propertydict.add_kvp("baseScale", formatScale(ob.scale))
452+
axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle()
453+
propertydict.add_kvp("baseRotation", formatRotation(axis_ang_raw))
401454

402455
self.properties = propertydict
403456
self.offset = [-(ob.location[i] + offset[i]) for i in range(0, 3)]
404457

458+
@staticmethod
459+
def catmull_rom(t, p0, p1, p2, p3):
460+
return 0.5 * ((3*p1 - 3*p2 + p3 - p0)*t*t*t
461+
+ (2*p0 - 5*p1 + 4*p2 - p3)*t*t
462+
+ (p2 - p0)*t
463+
+ 2*p1)
464+
465+
@staticmethod
466+
def catmull_rom_length(p0, p1, p2, p3, samples=20):
467+
total_length = 0
468+
last_vec = None
469+
470+
for i in range(0, samples+1):
471+
t = i / samples
472+
x = GamePathedInterior.catmull_rom(t, p0[0], p1[0], p2[0], p3[0])
473+
y = GamePathedInterior.catmull_rom(t, p0[1], p1[1], p2[1], p3[1])
474+
z = GamePathedInterior.catmull_rom(t, p0[2], p1[2], p2[2], p3[2])
475+
new_vec = Vector((x, y, z))
476+
if last_vec:
477+
total_length += (new_vec - last_vec).length
478+
last_vec = new_vec
479+
480+
return total_length
481+
405482

406483
class GameEntity:
407484
def __init__(self, ob, offset):
@@ -411,18 +488,10 @@ def __init__(self, ob, offset):
411488
for prop in props.game_entity_properties:
412489
propertydict.add_kvp(prop.key, prop.value)
413490

414-
propertydict.add_kvp("scale", "%.5f %.5f %.5f" % (ob.scale[0], ob.scale[1], ob.scale[2]))
491+
propertydict.add_kvp("scale", formatScale(ob.scale))
415492

416493
axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle()
417-
418-
#TODO fix or remove
419-
from math import degrees
420-
propertydict.add_kvp("rotation", "%.5f %.5f %.5f %.5f" % (
421-
axis_ang_raw[0][0],
422-
axis_ang_raw[0][1],
423-
axis_ang_raw[0][2],
424-
degrees(axis_ang_raw[1]))
425-
)
494+
propertydict.add_kvp("rotation", formatRotation(axis_ang_raw))
426495

427496
if props.game_entity_gameclass == "Trigger":
428497
propertydict.add_kvp("polyhedron", "0 0 0 1 0 0 0 -1 0 0 0 1")
@@ -441,13 +510,18 @@ def __init__(self, ob, offset):
441510
for prop in props.game_entity_properties:
442511
propertydict.add_kvp(prop.key, prop.value)
443512

513+
#axis_ang_raw: Vector = ob.matrix_world.to_quaternion().to_axis_angle()
514+
#propertydict.add_kvp("rotation", formatRotation(axis_ang_raw))
515+
444516
self.position = [ob.location[i] + offset[i] for i in range(0, 3)]
445517
self.size = [ob.scale[0], -ob.scale[1], ob.scale[2]]
446518
self.datablock = props.game_entity_datablock
447519
self.properties = propertydict
448520
self.name = "MustChange"
449521

450522
self.target_object = ob.dif_props.pathed_interior_target
523+
self.target_marker = ob.dif_props.target_marker
524+
self.target_index = ob.dif_props.target_index
451525

452526

453527
def save(

blender_plugin/io_dif/import_dif.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ def load(
259259
itr.dif_props.interior_type = "pathed_interior"
260260
itr.dif_props.start_time = int(mover.properties.h.get("initialPosition", 0))
261261
itr.dif_props.reverse = mover.properties.h.get("initialTargetPosition", 0) == "-2"
262+
itr.dif_props.constant_speed = False
262263

263264
waypoints: list[WayPoint] = mover.wayPoint
264265

@@ -299,6 +300,7 @@ def load(
299300
tobj.dif_props.interior_type = "path_trigger"
300301
tobj.dif_props.pathed_interior_target = itr
301302
tobj.dif_props.game_entity_datablock = trigger.datablock
303+
tobj.dif_props.target_marker = False
302304
for key in trigger.properties.h:
303305
prop = tobj.dif_props.game_entity_properties.add()
304306
prop.key = key

0 commit comments

Comments
 (0)