Browse Source

Camera transform and FOV animations

pull/105/head
Adam Johnson 7 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 # 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. # things that aren't the typical position, rotation, scale animations.
applicators = [] 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: if bo.plasma_modifiers.soundemit.enabled:
applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit)) applicators.extend(self._convert_sound_volume_animation(bo.name, obj_fcurves, bo.plasma_modifiers.soundemit))
if isinstance(bo.data, bpy.types.Lamp): if isinstance(bo.data, bpy.types.Lamp):
@ -102,6 +105,84 @@ class AnimationConverter:
atcanim.easeOutMax = 1.0 atcanim.easeOutMax = 1.0
atcanim.easeOutLength = 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): def _convert_lamp_color_animation(self, name, fcurves, lamp):
if not fcurves: if not fcurves:
return None return None
@ -271,15 +352,16 @@ class AnimationConverter:
applicator.channel = channel applicator.channel = channel
yield applicator yield applicator
def _convert_transform_animation(self, name, fcurves, xform): def _convert_transform_animation(self, name, fcurves, xform, allow_empty=False):
if not fcurves: if not fcurves and not allow_empty:
return None return None
pos = self.make_pos_controller(fcurves, xform) pos = self.make_pos_controller(fcurves, xform)
rot = self.make_rot_controller(fcurves, xform) rot = self.make_rot_controller(fcurves, xform)
scale = self.make_scale_controller(fcurves, xform) scale = self.make_scale_controller(fcurves, xform)
if pos is None and rot is None and scale is None: if pos is None and rot is None and scale is None:
return None if not allow_empty:
return None
tm = plCompoundController() tm = plCompoundController()
tm.X = pos tm.X = pos
@ -317,6 +399,16 @@ class AnimationConverter:
master = self._mgr.find_create_object(plAGMasterMod, so=so, bl=bo) master = self._mgr.find_create_object(plAGMasterMod, so=so, bl=bo)
return mod, master 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 make_matrix44_controller(self, fcurves, pos_path, scale_path, pos_default, scale_default):
def convert_matrix_keyframe(**kwargs): def convert_matrix_keyframe(**kwargs):
pos = kwargs.get(pos_path) pos = kwargs.get(pos_path)

15
korman/exporter/camera.py

@ -67,19 +67,25 @@ class CameraConverter:
brain.setFlags(plCameraBrain1.kSpeedUpWhenRunning, True) brain.setFlags(plCameraBrain1.kSpeedUpWhenRunning, True)
def export_camera(self, so, bo, camera_type, camera_props): 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) 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 mod.brain = brain.key
def _export_camera_modifier(self, so, bo, props): 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. # 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. # We only allow setting horizontal FOV (how often do you look up?), however.
# Plasma assumes 4:3 aspect ratio... # Plasma assumes 4:3 aspect ratio..
mod = self._mgr.find_create_object(plCameraModifier, so=so)
fov = props.fov fov = props.fov
mod.fovW, mod.fovH = math.degrees(fov), math.degrees(fov * (3.0 / 4.0)) 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 return mod
def _export_circle_camera(self, so, bo, props): def _export_circle_camera(self, so, bo, props):
@ -134,7 +140,6 @@ class CameraConverter:
def _export_fixed_camera(self, so, bo, props): def _export_fixed_camera(self, so, bo, props):
brain = self._mgr.find_create_object(plCameraBrain1_Fixed, so=so) brain = self._mgr.find_create_object(plCameraBrain1_Fixed, so=so)
self._convert_brain(so, bo, props, brain) self._convert_brain(so, bo, props, brain)
# TODO: animations???
return brain return brain
def _export_follow_camera(self, so, bo, props): 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. # sort, and barf out a CI.
sceneobject = self.mgr.find_create_object(plSceneObject, bl=bl_obj) sceneobject = self.mgr.find_create_object(plSceneObject, bl=bl_obj)
self._export_actor(sceneobject, bl_obj) self._export_actor(sceneobject, bl_obj)
export_fn(sceneobject, bl_obj)
self.animation.convert_object_animations(bl_obj, sceneobject) self.animation.convert_object_animations(bl_obj, sceneobject)
export_fn(sceneobject, bl_obj)
# And now we puke out the modifiers... # And now we puke out the modifiers...
for mod in bl_obj.plasma_modifiers.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 self.anim_type == "OBJECT":
if not obj.plasma_object.has_animation_data: if not obj.plasma_object.has_animation_data:
self.raise_error("invalid animation") self.raise_error("invalid animation")
group = obj.plasma_modifiers.animation_group target = exporter.animation.get_animation_key(obj)
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)
else: else:
material = self.target_material material = self.target_material
if material is None: 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", description="Radius at which the circle camera should orbit the Point of Attention",
min=0.0, default=8.5, options={"HIDDEN"}) 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): def get_circle_radius(self, bo):
"""Gets the circle camera radius for this camera when it is attached to the given Object""" """Gets the circle camera radius for this camera when it is attached to the given Object"""
assert bo is not None 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": if modifier.camera_type == "manual":
layout.prop(modifier, "camera_object", icon="CAMERA_DATA") layout.prop(modifier, "camera_object", icon="CAMERA_DATA")
else: 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): def footstep(modifier, layout, context):
layout.prop(modifier, "bounds") layout.prop(modifier, "bounds")

21
korman/ui/ui_camera.py

@ -44,7 +44,7 @@ class PlasmaCameraTransitionPanel(CameraButtonsPanel, bpy.types.Panel):
pass 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 trans = props.transition
def _draw_gated_prop(layout, props, gate_prop, actual_prop): 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 = row.row(align=True)
row.active = getattr(props, gate_prop) row.active = getattr(props, gate_prop)
row.prop(props, actual_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 # Point of Attention
split = layout.split() split = layout.split()
@ -140,3 +151,11 @@ def draw_camera_properties(cam_type, props, layout, context):
row = col.row(align=True) row = col.row(align=True)
row.active = props.circle_center is None row.active = props.circle_center is None
row.prop(props, "circle_radius_ui") 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