From 7bc99d072699bc3fabfc2264127aa2fc26d64068 Mon Sep 17 00:00:00 2001 From: Jrius <2261279+Jrius@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:01:54 +0200 Subject: [PATCH 1/6] Replace object.location with matrix_world.translation --- korman/exporter/camera.py | 2 +- korman/exporter/material.py | 2 +- korman/exporter/mesh.py | 2 +- korman/properties/modifiers/avatar.py | 5 +---- korman/properties/modifiers/gui.py | 4 ++-- korman/properties/modifiers/water.py | 8 ++++---- korman/properties/prop_camera.py | 2 +- 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/korman/exporter/camera.py b/korman/exporter/camera.py index c942fde..6b97cba 100644 --- a/korman/exporter/camera.py +++ b/korman/exporter/camera.py @@ -132,7 +132,7 @@ class CameraConverter: # specifying an actual center allows you to do interesting things like animate the center... # Fascinating! Therefore, we will expose the Plasma Object... if props.circle_center is None: - brain.center = hsVector3(*bo.location) + brain.center = hsVector3(*bo.matrix_world.translation) else: brain.centerObject = self._mgr.find_create_key(plSceneObject, bl=props.circle_center) # This flag has no effect in CWE, but I'm using it for correctness' sake diff --git a/korman/exporter/material.py b/korman/exporter/material.py index f316f0c..05095d1 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -779,7 +779,7 @@ class MaterialConverter: # will probably want to steal it for diabolical purposes... In MOUL, root objects are # allowed, but that introduces a gotcha with regard to animated roots and PotS. Also, # sharing root objects with a DCM seems to result in bad problems in game O.o - pl_env.position = hsVector3(*viewpt.location) + pl_env.position = hsVector3(*viewpt.matrix_world.translation) if layer is not None: layer.UVWSrc = plLayerInterface.kUVWReflect diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 8187854..afbf7cf 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -658,7 +658,7 @@ class MeshConverter(_MeshManager): # FIXME: Can some of this be generalized? geospan.props |= (plGeometrySpan.kWaterHeight | plGeometrySpan.kLiteVtxNonPreshaded | plGeometrySpan.kPropReverseSort | plGeometrySpan.kPropNoShadow) - geospan.waterHeight = bo.location[2] + geospan.waterHeight = bo.matrix_world.translation[2] return [_GeoSpan(bo, blmat, geospan)], None else: geospans = [None] * len(materials) diff --git a/korman/properties/modifiers/avatar.py b/korman/properties/modifiers/avatar.py index 3bc2e80..00455e6 100644 --- a/korman/properties/modifiers/avatar.py +++ b/korman/properties/modifiers/avatar.py @@ -64,10 +64,7 @@ class PlasmaLadderModifier(PlasmaModifierProperties): ladderVec = self.facing_object.matrix_world.translation - bo.matrix_world.translation else: # Make our own artificial target -1.0 units back on the local Y axis. - world = bo.matrix_world.copy() - world.invert() - target = bo.location - (mathutils.Vector((0.0, 1.0, 0.0)) * world) - ladderVec = target - bo.matrix_local.translation + ladderVec = mathutils.Vector((0, -1, 0)) * bo.matrix_world.inverted() mod.ladderView = hsVector3(ladderVec.x, ladderVec.y, 0.0) mod.ladderView.normalize() diff --git a/korman/properties/modifiers/gui.py b/korman/properties/modifiers/gui.py index d4921d2..1fc43ab 100644 --- a/korman/properties/modifiers/gui.py +++ b/korman/properties/modifiers/gui.py @@ -230,7 +230,7 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz if self.clickable_region is None: with utils.bmesh_object("{}_Journal_ClkRgn".format(self.key_name)) as (rgn_obj, bm): bmesh.ops.create_cube(bm, size=(6.0)) - bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.location - rgn_obj.location), + bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.matrix_world.translation - rgn_obj.matrix_world.translation), space=rgn_obj.matrix_world, verts=bm.verts) rgn_obj.plasma_object.enabled = True rgn_obj.hide_render = True @@ -466,7 +466,7 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz if self.clickable_region is None: with utils.bmesh_object("{}_LinkingBook_ClkRgn".format(self.key_name)) as (rgn_obj, bm): bmesh.ops.create_cube(bm, size=(6.0)) - rgn_offset = mathutils.Matrix.Translation(self.clickable.location - bo.location) + rgn_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - bo.matrix_world.translation) bmesh.ops.transform(bm, matrix=rgn_offset, space=bo.matrix_world, verts=bm.verts) rgn_obj.plasma_object.enabled = True rgn_obj.hide_render = True diff --git a/korman/properties/modifiers/water.py b/korman/properties/modifiers/water.py index 806883b..891c7fb 100644 --- a/korman/properties/modifiers/water.py +++ b/korman/properties/modifiers/water.py @@ -114,7 +114,7 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy. losdbs = ["kLOSDBSwimRegion"] member_group = "kGroupLOSOnly" if exporter.mgr.getVer() != pvMoul else "kGroupStatic" if bo.plasma_modifiers.water_basic.enabled: - exporter.physics.generate_flat_proxy(bo, so, z_coord=bo.location[2], + exporter.physics.generate_flat_proxy(bo, so, z_coord=bo.matrix_world.translation[2], member_group=member_group, losdbs=losdbs) else: @@ -125,7 +125,7 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy. # Detector region bounds if self.region is not None: region_so = exporter.mgr.find_create_object(plSceneObject, bl=self.region) - + # Good news: if this phys has already been exported, this is basically a noop member_group = "kGroupDetector" if exporter.mgr.getVer() == "pvMoul" else "kGroupLOSOnly" exporter.physics.generate_physical(self.region, region_so, @@ -258,7 +258,7 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ # Stuff we expose state = waveset.state state.rippleScale = self.ripple_scale - state.waterHeight = bo.location[2] + state.waterHeight = bo.matrix_world.translation[2] state.windDir = wind_dir state.specVector = hsVector3(self.noise / 100.0, self.specular_start, self.specular_end) state.specularTint = hsColorRGBA(*self.specular_tint, alpha=self.specular_alpha) @@ -273,7 +273,7 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ state.envCenter = dem.position state.envRefresh = dem.refreshRate else: - state.envCenter = hsVector3(*bo.location) + state.envCenter = hsVector3(*bo.matrix_world.translation) state.envRefresh = 0.0 state.envRadius = self.envmap_radius diff --git a/korman/properties/prop_camera.py b/korman/properties/prop_camera.py index 5d46fc7..0be191f 100644 --- a/korman/properties/prop_camera.py +++ b/korman/properties/prop_camera.py @@ -225,7 +225,7 @@ class PlasmaCameraProperties(bpy.types.PropertyGroup): """Gets the circle camera radius for this camera when it is attached to the given Object""" assert bo is not None if self.circle_center is not None: - vec = bo.location - self.circle_center.location + vec = bo.matrix_world.translation - self.circle_center.matrix_world.translation return vec.magnitude return self.circle_radius_value From a88d49ba636394baf48c7efd2a88af0b0bf9e915 Mon Sep 17 00:00:00 2001 From: Jrius <2261279+Jrius@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:11:03 +0200 Subject: [PATCH 2/6] Eliminate matrix_basis (outside animations) --- korman/exporter/convert.py | 2 +- korman/exporter/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index afba199..9761eb3 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -245,7 +245,7 @@ class Exporter: ci = self.mgr.add_object(ci_cls, bl=bl, so=so) # Now we have the "fun" work of filling in the CI - ci.localToWorld = utils.matrix44(bl.matrix_basis) + ci.localToWorld = utils.matrix44(bl.matrix_world) ci.worldToLocal = ci.localToWorld.inverse() ci.localToParent = utils.matrix44(bl.matrix_local) ci.parentToLocal = ci.localToParent.inverse() diff --git a/korman/exporter/utils.py b/korman/exporter/utils.py index cdae5b7..75405fd 100644 --- a/korman/exporter/utils.py +++ b/korman/exporter/utils.py @@ -102,8 +102,8 @@ def temporary_mesh_object(source : bpy.types.Object) -> bpy.types.Object: obj = bpy.data.objects.new(source.name, source.to_mesh(bpy.context.scene, True, "RENDER")) obj.draw_type = "WIRE" - obj.matrix_basis, obj.matrix_world = source.matrix_basis, source.matrix_world obj.parent = source.parent + obj.matrix_local, obj.matrix_world = source.matrix_local, source.matrix_world bpy.context.scene.objects.link(obj) try: From 518312cff862197b66c96f77e2027b87269216ac Mon Sep 17 00:00:00 2001 From: Jrius <2261279+Jrius@users.noreply.github.com> Date: Tue, 31 Aug 2021 18:46:25 +0200 Subject: [PATCH 3/6] Support nonlocal animations --- korman/exporter/animation.py | 70 ++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/korman/exporter/animation.py b/korman/exporter/animation.py index 81afce9..a421edf 100644 --- a/korman/exporter/animation.py +++ b/korman/exporter/animation.py @@ -58,7 +58,7 @@ class AnimationConverter: if isinstance(bo.data, bpy.types.Camera): applicators.append(self._convert_camera_animation(bo, so, obj_fcurves, data_fcurves, anim_name, start, end)) else: - applicators.append(self._convert_transform_animation(bo, obj_fcurves, bo.matrix_basis, start=start, end=end)) + applicators.append(self._convert_transform_animation(bo, obj_fcurves, bo.matrix_local, bo.matrix_parent_inverse, start=start, end=end)) if bo.plasma_modifiers.soundemit.enabled: applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit, start, end)) if isinstance(bo.data, bpy.types.Lamp): @@ -143,7 +143,7 @@ class AnimationConverter: # If we exported any FOV animation at all, then we need to ensure there is an applicator # returned from here... At bare minimum, we'll need the applicator with an empty # CompoundController. This should be sufficient to keep CWE from crashing... - applicator = self._convert_transform_animation(bo, obj_fcurves, bo.matrix_basis, + applicator = self._convert_transform_animation(bo, obj_fcurves, bo.matrix_local, bo.matrix_parent_inverse, allow_empty=has_fov_anim, start=start, end=end) camera = self._mgr.find_create_object(plCameraModifier, so=so) camera.animated = applicator is not None @@ -338,9 +338,16 @@ class AnimationConverter: applicator.channel = channel yield applicator - def _convert_transform_animation(self, bo, fcurves, xform, *, allow_empty: Optional[bool] = False, + def _convert_transform_animation(self, bo, fcurves, default_xform, adjust_xform, *, allow_empty: Optional[bool] = False, start: Optional[int] = None, end: Optional[int] = None) -> Optional[plMatrixChannelApplicator]: - tm = self.convert_transform_controller(fcurves, bo.rotation_mode, xform, allow_empty=allow_empty, + if adjust_xform != mathutils.Matrix.Identity(4): + self._exporter().report.warn(("{}: Transform animation is not local and may export incorrectly. " + + "Please use Ctrl-P -> Clear Parent Inverse before animating objects to avoid issues.").format(bo.name), indent=1) + else: + # Adjustment matrix is identity, just pass None instead... + adjust_xform = None + + tm = self.convert_transform_controller(fcurves, bo.rotation_mode, default_xform, adjust_xform, allow_empty=allow_empty, start=start, end=end) if tm is None and not allow_empty: return None @@ -351,20 +358,62 @@ class AnimationConverter: channel = plMatrixControllerChannel() channel.controller = tm applicator.channel = channel - channel.affine = utils.affine_parts(xform) + channel.affine = utils.affine_parts(default_xform) return applicator - def convert_transform_controller(self, fcurves, rotation_mode: str, xform, *, + def convert_transform_controller(self, fcurves, rotation_mode: str, default_xform, adjust_xform, *, allow_empty: Optional[bool] = False, start: Optional[int] = None, end: Optional[int] = None) -> Union[None, plCompoundController]: if not fcurves and not allow_empty: return None - pos = self.make_pos_controller(fcurves, "location", xform.to_translation(), start=start, end=end) - rot = self.make_rot_controller(fcurves, rotation_mode, xform, start=start, end=end) - scale = self.make_scale_controller(fcurves, "scale", xform.to_scale(), start=start, end=end) + if adjust_xform is not None: + # We have to edit the keyframes to make the anim local.. + # In many cases this should work fine, but sometimes scale and rotation might + # still cause issues. Also, euler angles need to be converted to quaternion + # and back to eulers, which could cause issues. Not much we can do about it. + adjust_rotation = adjust_xform.to_quaternion() + adjust_scale = adjust_xform.to_scale() + + # Helpers to adjust keyframes in case animation is not local (adjust_xform == identity) + def convert_pos_keyframe(pos): + # Position: can transform to local space without issues. + return tuple(adjust_xform * mathutils.Vector(pos)) + + def convert_rot_keyframe(rot): + # Rotation: may cause issues if scale is present. + if isinstance(rot, mathutils.Quaternion): # quaternion from an axis-angle + return adjust_rotation * rot + elif isinstance(rot, mathutils.Euler): + return (adjust_rotation * rot.to_quaternion()).to_euler(rot.order) + else: # tuple + if len(rot) == 4: # quat in a tuple + return (adjust_rotation * mathutils.Quaternion(rot))[:] + else: # XYZ euler in a tuple + rot = mathutils.Euler(rot, "XYZ").to_quaternion() + return (adjust_rotation * rot).to_euler("XYZ")[:] + + def convert_scale_keyframe(scale): + # Scale: very likely to cause issues. + return (a * b for a, b in zip(adjust_scale, scale)) + + convert_pos = convert_pos_keyframe + convert_rot = convert_rot_keyframe + convert_scale = convert_scale_keyframe + else: + # Don't change the keyframes at all, so we don't risk screwing them up. + convert_pos = None + convert_rot = None + convert_scale = None + + pos = self.make_pos_controller(fcurves, "location", default_xform.to_translation(), + convert=convert_pos, start=start, end=end) + rot = self.make_rot_controller(fcurves, rotation_mode, default_xform, + convert=convert_rot, start=start, end=end) + scale = self.make_scale_controller(fcurves, "scale", default_xform.to_scale(), + convert=convert_scale, start=start, end=end) if pos is None and rot is None and scale is None: if not allow_empty: return None @@ -490,7 +539,8 @@ class AnimationConverter: default_xform = (default_xform[1], default_xform[0].x, default_xform[0].y, default_xform[0].z) if convert is not None: - convert = lambda x: convert(mathutils.Quaternion(x[1:4], x[0]))[:] + convert_original = convert + convert = lambda x: convert_original(mathutils.Quaternion(x[1:4], x[0]))[:] else: convert = lambda x: mathutils.Quaternion(x[1:4], x[0])[:] From bceadbf784ec42bfd28bf3ef37482f5290436315 Mon Sep 17 00:00:00 2001 From: Jrius <2261279+Jrius@users.noreply.github.com> Date: Wed, 1 Sep 2021 19:15:38 +0200 Subject: [PATCH 4/6] A few more fixes after some testing --- korman/exporter/camera.py | 3 ++- korman/exporter/physics.py | 2 +- korman/properties/modifiers/gui.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/korman/exporter/camera.py b/korman/exporter/camera.py index 6b97cba..bed100c 100644 --- a/korman/exporter/camera.py +++ b/korman/exporter/camera.py @@ -204,7 +204,8 @@ class CameraConverter: # path object, but it makes more sense to me to just animate the camera with # the details of the path... pos_fcurves = tuple(i for i in helpers.fetch_fcurves(bo, False) if i.data_path == "location") - pos_ctrl = self._exporter().animation.convert_transform_controller(pos_fcurves, bo.rotation_mode, bo.matrix_basis) + pos_ctrl = self._exporter().animation.convert_transform_controller(pos_fcurves, bo.rotation_mode, + bo.matrix_local, bo.matrix_parent_inverse) if pos_ctrl is None: raise ExportError("'{}': Rail Camera lacks appropriate rail keyframes".format(bo.name)) path = plAnimPath() diff --git a/korman/exporter/physics.py b/korman/exporter/physics.py index e93bc06..5af16ab 100644 --- a/korman/exporter/physics.py +++ b/korman/exporter/physics.py @@ -93,7 +93,7 @@ class PhysicsConverter: if so.sim is None: simIface = self._mgr.add_object(pl=plSimulationInterface, bl=bo) - physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo, name=name) + physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo, name=bo.name) simIface.physical = physical.key physical.object = so.key diff --git a/korman/properties/modifiers/gui.py b/korman/properties/modifiers/gui.py index 1fc43ab..f69996a 100644 --- a/korman/properties/modifiers/gui.py +++ b/korman/properties/modifiers/gui.py @@ -466,8 +466,8 @@ class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz if self.clickable_region is None: with utils.bmesh_object("{}_LinkingBook_ClkRgn".format(self.key_name)) as (rgn_obj, bm): bmesh.ops.create_cube(bm, size=(6.0)) - rgn_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - bo.matrix_world.translation) - bmesh.ops.transform(bm, matrix=rgn_offset, space=bo.matrix_world, verts=bm.verts) + rgn_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - rgn_obj.matrix_world.translation) + bmesh.ops.transform(bm, matrix=rgn_offset, space=rgn_obj.matrix_world, verts=bm.verts) rgn_obj.plasma_object.enabled = True rgn_obj.hide_render = True yield rgn_obj From 8255c6d52c187fffd8780266e22d1b961e4714b9 Mon Sep 17 00:00:00 2001 From: Jrius <2261279+Jrius@users.noreply.github.com> Date: Wed, 1 Sep 2021 21:11:17 +0200 Subject: [PATCH 5/6] Apply suggestions from CR Co-authored-by: Adam Johnson --- korman/exporter/animation.py | 2 +- korman/exporter/physics.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/korman/exporter/animation.py b/korman/exporter/animation.py index a421edf..e335b0c 100644 --- a/korman/exporter/animation.py +++ b/korman/exporter/animation.py @@ -397,7 +397,7 @@ class AnimationConverter: def convert_scale_keyframe(scale): # Scale: very likely to cause issues. - return (a * b for a, b in zip(adjust_scale, scale)) + return [a * b for a, b in zip(adjust_scale, scale)] convert_pos = convert_pos_keyframe convert_rot = convert_rot_keyframe diff --git a/korman/exporter/physics.py b/korman/exporter/physics.py index 5af16ab..202d527 100644 --- a/korman/exporter/physics.py +++ b/korman/exporter/physics.py @@ -93,7 +93,7 @@ class PhysicsConverter: if so.sim is None: simIface = self._mgr.add_object(pl=plSimulationInterface, bl=bo) - physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo, name=bo.name) + physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo) simIface.physical = physical.key physical.object = so.key From 25cb65781a28e59b60adb065e1ed363d4eb2a047 Mon Sep 17 00:00:00 2001 From: Jrius <2261279+Jrius@users.noreply.github.com> Date: Sat, 11 Sep 2021 11:18:03 +0200 Subject: [PATCH 6/6] Correct keyboard shortcut for Clear Parent Inverse. --- korman/exporter/animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/korman/exporter/animation.py b/korman/exporter/animation.py index e335b0c..60b3051 100644 --- a/korman/exporter/animation.py +++ b/korman/exporter/animation.py @@ -342,7 +342,7 @@ class AnimationConverter: start: Optional[int] = None, end: Optional[int] = None) -> Optional[plMatrixChannelApplicator]: if adjust_xform != mathutils.Matrix.Identity(4): self._exporter().report.warn(("{}: Transform animation is not local and may export incorrectly. " + - "Please use Ctrl-P -> Clear Parent Inverse before animating objects to avoid issues.").format(bo.name), indent=1) + "Please use Alt-P -> Clear Parent Inverse before animating objects to avoid issues.").format(bo.name), indent=1) else: # Adjustment matrix is identity, just pass None instead... adjust_xform = None