Browse Source

Camera transform and FOV animations

pull/105/head
Adam Johnson 6 years ago
parent
commit
c656b0e51a
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 100
      korman/exporter/animation.py
  2. 15
      korman/exporter/camera.py
  3. 2
      korman/exporter/convert.py
  4. 9
      korman/nodes/node_messages.py
  5. 11
      korman/properties/prop_camera.py
  6. 2
      korman/ui/modifiers/region.py
  7. 21
      korman/ui/ui_camera.py

100
korman/exporter/animation.py

@ -51,7 +51,10 @@ class AnimationConverter:
# form of separation, but Blender's NLA editor is way confusing and appears to not work with
# things that aren't the typical position, rotation, scale animations.
applicators = []
applicators.append(self._convert_transform_animation(bo.name, obj_fcurves, bo.matrix_basis))
if isinstance(bo.data, bpy.types.Camera):
applicators.append(self._convert_camera_animation(bo, so, obj_fcurves, data_fcurves))
else:
applicators.append(self._convert_transform_animation(bo.name, obj_fcurves, bo.matrix_basis))
if bo.plasma_modifiers.soundemit.enabled:
applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit))
if isinstance(bo.data, bpy.types.Lamp):
@ -102,6 +105,84 @@ class AnimationConverter:
atcanim.easeOutMax = 1.0
atcanim.easeOutLength = 1.0
def _convert_camera_animation(self, bo, so, obj_fcurves, data_fcurves):
if data_fcurves:
# The hard part about this crap is that FOV animations are not stored in ATC Animations
# instead, FOV animation keyframes are held inside of the camera modifier. Cyan's solution
# in PlasmaMAX appears to be for any xform keyframe, add two messages to the camera modifier
# representing the FOV at that point. Makes more sense to me to use each FOV keyframe instead
fov_fcurve = next((i for i in data_fcurves if i.data_path == "plasma_camera.settings.fov"), None)
if fov_fcurve:
# NOTE: this is another critically important key ordering in the SceneObject modifier
# list. CameraModifier calls into AGMasterMod code that assumes the AGModifier
# is already available. Should probably consider adding some code to libHSPlasma
# to order the SceneObject modifier key vector at some point.
anim_key = self.get_animation_key(bo)
camera = self._mgr.find_create_object(plCameraModifier, so=so)
cam_key = camera.key
aspect, fps = (3.0 / 4.0), self._bl_fps
degrees = math.degrees
fov_fcurve.update()
# Seeing as how we're transforming the data entirely, we'll just use the fcurve itself
# instead of our other animation helpers. But ugh does this mess look like sloppy C.
keyframes = fov_fcurve.keyframe_points
num_keyframes = len(keyframes)
has_fov_anim = bool(num_keyframes)
i = 0
while i < num_keyframes:
this_keyframe = keyframes[i]
next_keyframe = keyframes[0] if i+1 == num_keyframes else keyframes[i+1]
# So remember, these are messages. When we hit a keyframe, we're dispatching a message
# representing the NEXT desired FOV.
this_frame_time = this_keyframe.co[0] / fps
next_frame_num, next_frame_value = next_keyframe.co
next_frame_time = next_frame_num / fps
# This message is held on the camera modifier and sent to the animation... It calls
# back when the animation reaches the keyframe time, causing the FOV message to be sent.
cb_msg = plEventCallbackMsg()
cb_msg.event = kTime
cb_msg.eventTime = this_frame_time
cb_msg.index = i
cb_msg.repeats = -1
cb_msg.addReceiver(cam_key)
anim_msg = plAnimCmdMsg()
anim_msg.animName = "(Entire Animation)"
anim_msg.time = this_frame_time
anim_msg.sender = anim_key
anim_msg.addReceiver(anim_key)
anim_msg.addCallback(cb_msg)
anim_msg.setCmd(plAnimCmdMsg.kAddCallbacks, True)
camera.addMessage(anim_msg, anim_key)
# This is the message actually changes the FOV. Interestingly, it is sent at
# export-time and while playing the game, the camera modifier just steals its
# parameters and passes them to the brain. Can't make this stuff up.
cam_msg = plCameraMsg()
cam_msg.addReceiver(cam_key)
cam_msg.setCmd(plCameraMsg.kAddFOVKeyFrame, True)
cam_config = cam_msg.config
cam_config.accel = next_frame_time # Yassss...
cam_config.fovW = degrees(next_frame_value)
cam_config.fovH = degrees(next_frame_value * aspect)
camera.addFOVInstruction(cam_msg)
i += 1
else:
has_fov_anim = False
else:
has_fov_anim = False
# 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.name, obj_fcurves, bo.matrix_basis, allow_empty=has_fov_anim)
camera = locals().get("camera", self._mgr.find_create_object(plCameraModifier, so=so))
camera.animated = applicator is not None
return applicator
def _convert_lamp_color_animation(self, name, fcurves, lamp):
if not fcurves:
return None
@ -271,15 +352,16 @@ class AnimationConverter:
applicator.channel = channel
yield applicator
def _convert_transform_animation(self, name, fcurves, xform):
if not fcurves:
def _convert_transform_animation(self, name, fcurves, xform, allow_empty=False):
if not fcurves and not allow_empty:
return None
pos = self.make_pos_controller(fcurves, xform)
rot = self.make_rot_controller(fcurves, xform)
scale = self.make_scale_controller(fcurves, xform)
if pos is None and rot is None and scale is None:
return None
if not allow_empty:
return None
tm = plCompoundController()
tm.X = pos
@ -317,6 +399,16 @@ class AnimationConverter:
master = self._mgr.find_create_object(plAGMasterMod, so=so, bl=bo)
return mod, master
def get_animation_key(self, bo, so=None):
# we might be controlling more than one animation. isn't that cute?
# https://www.youtube.com/watch?v=hspNaoxzNbs
# (but obviously this is not wrong...)
group_mod = bo.plasma_modifiers.animation_group
if group_mod.enabled:
return self._mgr.find_create_key(plMsgForwarder, bl=bo, so=so, name=group_mod.key_name)
else:
return self.get_anigraph_keys(bo, so)[1]
def make_matrix44_controller(self, fcurves, pos_path, scale_path, pos_default, scale_default):
def convert_matrix_keyframe(**kwargs):
pos = kwargs.get(pos_path)

15
korman/exporter/camera.py

@ -67,19 +67,25 @@ class CameraConverter:
brain.setFlags(plCameraBrain1.kSpeedUpWhenRunning, True)
def export_camera(self, so, bo, camera_type, camera_props):
brain = getattr(self, "_export_{}_camera".format(camera_type))(so, bo, camera_props, allow_anim)
mod = self._export_camera_modifier(so, bo, camera_props)
brain = getattr(self, "_export_{}_camera".format(camera_type))(so, bo, camera_props)
mod.brain = brain.key
def _export_camera_modifier(self, so, bo, props):
mod = self._mgr.find_create_object(plCameraModifier, so=so)
# PlasmaMAX allows the user to specify the horizontal OR vertical FOV, but not both.
# We only allow setting horizontal FOV (how often do you look up?), however.
# Plasma assumes 4:3 aspect ratio...
mod = self._mgr.find_create_object(plCameraModifier, so=so)
# Plasma assumes 4:3 aspect ratio..
fov = props.fov
mod.fovW, mod.fovH = math.degrees(fov), math.degrees(fov * (3.0 / 4.0))
# TODO: do we need to do something about animations here?
# This junk is only valid for animated cameras...
# Animation exporter should have already run by this point :)
if mod.animated:
mod.startAnimOnPush = props.start_on_push
mod.stopAnimOnPop = props.stop_on_pop
mod.resetAnimOnPop = props.reset_on_pop
return mod
def _export_circle_camera(self, so, bo, props):
@ -134,7 +140,6 @@ class CameraConverter:
def _export_fixed_camera(self, so, bo, props):
brain = self._mgr.find_create_object(plCameraBrain1_Fixed, so=so)
self._convert_brain(so, bo, props, brain)
# TODO: animations???
return brain
def _export_follow_camera(self, so, bo, props):

2
korman/exporter/convert.py

@ -229,8 +229,8 @@ class Exporter:
# sort, and barf out a CI.
sceneobject = self.mgr.find_create_object(plSceneObject, bl=bl_obj)
self._export_actor(sceneobject, bl_obj)
export_fn(sceneobject, bl_obj)
self.animation.convert_object_animations(bl_obj, sceneobject)
export_fn(sceneobject, bl_obj)
# And now we puke out the modifiers...
for mod in bl_obj.plasma_modifiers.modifiers:

9
korman/nodes/node_messages.py

@ -243,14 +243,7 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
if self.anim_type == "OBJECT":
if not obj.plasma_object.has_animation_data:
self.raise_error("invalid animation")
group = obj.plasma_modifiers.animation_group
if group.enabled:
# we might be controlling more than one animation. isn't that cute?
# https://www.youtube.com/watch?v=hspNaoxzNbs
# (but obviously this is not wrong...)
target = exporter.mgr.find_create_key(plMsgForwarder, bl=obj, name=group.key_name)
else:
_agmod_trash, target = exporter.animation.get_anigraph_keys(obj)
target = exporter.animation.get_animation_key(obj)
else:
material = self.target_material
if material is None:

11
korman/properties/prop_camera.py

@ -172,6 +172,17 @@ class PlasmaCameraProperties(bpy.types.PropertyGroup):
description="Radius at which the circle camera should orbit the Point of Attention",
min=0.0, default=8.5, options={"HIDDEN"})
# Animation
start_on_push = BoolProperty(name="Start on Push",
description="Start playing the camera's animation when the camera is activated",
options=set())
stop_on_pop = BoolProperty(name="Pause on Pop",
description="Pauses the camera's animation when the camera is no longer activated",
options=set())
reset_on_pop = BoolProperty(name="Reset on Pop",
description="Reset the camera's animation to the beginning when the camera is no longer activated",
options=set())
def get_circle_radius(self, bo):
"""Gets the circle camera radius for this camera when it is attached to the given Object"""
assert bo is not None

2
korman/ui/modifiers/region.py

@ -21,7 +21,7 @@ def camera_rgn(modifier, layout, context):
if modifier.camera_type == "manual":
layout.prop(modifier, "camera_object", icon="CAMERA_DATA")
else:
draw_camera_properties("follow", modifier.auto_camera, layout, context)
draw_camera_properties("follow", modifier.auto_camera, layout, context, True)
def footstep(modifier, layout, context):
layout.prop(modifier, "bounds")

21
korman/ui/ui_camera.py

@ -44,7 +44,7 @@ class PlasmaCameraTransitionPanel(CameraButtonsPanel, bpy.types.Panel):
pass
def draw_camera_properties(cam_type, props, layout, context):
def draw_camera_properties(cam_type, props, layout, context, force_no_anim=False):
trans = props.transition
def _draw_gated_prop(layout, props, gate_prop, actual_prop):
@ -53,6 +53,17 @@ def draw_camera_properties(cam_type, props, layout, context):
row = row.row(align=True)
row.active = getattr(props, gate_prop)
row.prop(props, actual_prop)
def _is_camera_animated(cam_type, props, context, force_no_anim):
if force_no_anim or cam_type == "rail":
return False
# Check for valid animation data on either the object or the camera
# TODO: Should probably check for valid FCurve channels at some point???
for i in (props.id_data, context.object):
if i.animation_data is None:
continue
if i.animation_data.action is not None:
return True
return False
# Point of Attention
split = layout.split()
@ -140,3 +151,11 @@ def draw_camera_properties(cam_type, props, layout, context):
row = col.row(align=True)
row.active = props.circle_center is None
row.prop(props, "circle_radius_ui")
# Animated Camera Stuff
col = split.column()
col.active = _is_camera_animated(cam_type, props, context, force_no_anim)
col.label("Animation:")
col.prop(props, "start_on_push")
col.prop(props, "stop_on_pop")
col.prop(props, "reset_on_pop")

Loading…
Cancel
Save