Browse Source

Rail Cameras

pull/105/head
Adam Johnson 6 years ago
parent
commit
e21b8d7032
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 37
      korman/exporter/animation.py
  2. 43
      korman/exporter/camera.py
  3. 9
      korman/exporter/convert.py
  4. 13
      korman/exporter/utils.py
  5. 13
      korman/helpers.py
  6. 3
      korman/properties/prop_camera.py
  7. 18
      korman/ui/ui_camera.py

37
korman/exporter/animation.py

@ -353,6 +353,21 @@ class AnimationConverter:
yield applicator yield applicator
def _convert_transform_animation(self, name, fcurves, xform, allow_empty=False): def _convert_transform_animation(self, name, fcurves, xform, allow_empty=False):
tm = self.convert_transform_controller(fcurves, xform, allow_empty)
if tm is None and not allow_empty:
return None
applicator = plMatrixChannelApplicator()
applicator.enabled = True
applicator.channelName = name
channel = plMatrixControllerChannel()
channel.controller = tm
applicator.channel = channel
channel.affine = utils.affine_parts(xform)
return applicator
def convert_transform_controller(self, fcurves, xform, allow_empty=False):
if not fcurves and not allow_empty: if not fcurves and not allow_empty:
return None return None
@ -367,27 +382,7 @@ class AnimationConverter:
tm.X = pos tm.X = pos
tm.Y = rot tm.Y = rot
tm.Z = scale tm.Z = scale
return tm
applicator = plMatrixChannelApplicator()
applicator.enabled = True
applicator.channelName = name
channel = plMatrixControllerChannel()
channel.controller = tm
applicator.channel = channel
# Decompose the matrix into the 90s-era 3ds max affine parts sillyness
# All that's missing now is something like "(c) 1998 HeadSpin" oh wait...
affine = hsAffineParts()
affine.T = hsVector3(*xform.to_translation())
affine.K = hsVector3(*xform.to_scale())
affine.F = -1.0 if xform.determinant() < 0.0 else 1.0
rot = xform.to_quaternion()
affine.Q = utils.quaternion(rot)
rot.normalize()
affine.U = utils.quaternion(rot)
channel.affine = affine
return applicator
def get_anigraph_keys(self, bo=None, so=None): def get_anigraph_keys(self, bo=None, so=None):
mod = self._mgr.find_create_key(plAGModifier, so=so, bl=bo) mod = self._mgr.find_create_key(plAGModifier, so=so, bl=bo)

43
korman/exporter/camera.py

@ -19,6 +19,8 @@ from PyHSPlasma import *
import weakref import weakref
from .explosions import * from .explosions import *
from .. import helpers
from . import utils
class CameraConverter: class CameraConverter:
def __init__(self, exporter): def __init__(self, exporter):
@ -67,7 +69,7 @@ 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) brain = getattr(self, "_export_{}_camera".format(camera_type))(so, bo, camera_props)
mod = self._export_camera_modifier(so, bo, camera_props) mod = self._export_camera_modifier(so, bo, camera_props)
mod.brain = brain.key mod.brain = brain.key
@ -138,6 +140,7 @@ class CameraConverter:
return brain return brain
def _export_fixed_camera(self, so, bo, props): def _export_fixed_camera(self, so, bo, props):
self._exporter().animation.convert_object_animations(bo, so)
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)
return brain return brain
@ -150,6 +153,44 @@ class CameraConverter:
brain.offset = hsVector3(*camera_props.pos_offset) brain.offset = hsVector3(*camera_props.pos_offset)
return brain return brain
def _export_rail_camera(self, so, bo, props):
brain = self._mgr.find_create_object(plCameraBrain1_Fixed, so=so)
self._convert_brain(so, bo, props, brain)
rail = self._mgr.find_create_object(plRailCameraMod, so=so)
rail.followFlags |= plLineFollowMod.kForceToLine
if props.poa_type == "object":
rail.followMode = plLineFollowMod.kFollowObject
rail.refObj = self._mgr.find_create_key(plSceneObject, bl=props.poa_object)
else:
rail.followMode = plLineFollowMod.kFollowLocalAvatar
if bo.parent:
rail.pathParent = self._mgr.find_create_key(plSceneObject, bl=bo.parent)
# The rail is defined by a position controller in Plasma. Cyan uses a separate
# 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.matrix_basis)
if pos_ctrl is None:
raise ExportError("'{}': Rail Camera lacks appropriate rail keyframes".format(bo.name))
path = plAnimPath()
path.controller = pos_ctrl
path.affineParts = utils.affine_parts(bo.matrix_local)
begin, end = bo.animation_data.action.frame_range
for fcurve in pos_fcurves:
f1, f2 = fcurve.evaluate(begin), fcurve.evaluate(end)
if abs(f1 - f2) > 0.001:
break
else:
# The animation is a loop
path.flags |= plAnimPath.kWrap
path.length = end / bpy.context.scene.render.fps
rail.path = path
brain.rail = rail.key
return brain
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr

9
korman/exporter/convert.py

@ -229,7 +229,6 @@ 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)
self.animation.convert_object_animations(bl_obj, sceneobject)
export_fn(sceneobject, bl_obj) export_fn(sceneobject, bl_obj)
# And now we puke out the modifiers... # And now we puke out the modifiers...
@ -240,19 +239,19 @@ class Exporter:
def _export_camera_blobj(self, so, bo): def _export_camera_blobj(self, so, bo):
# Hey, guess what? Blender's camera data is utter crap! # Hey, guess what? Blender's camera data is utter crap!
# NOTE: Animation export is dependent on camera type, so we'll do that later.
camera = bo.data.plasma_camera camera = bo.data.plasma_camera
self.camera.export_camera(so, bo, camera.camera_type, camera.settings) self.camera.export_camera(so, bo, camera.camera_type, camera.settings)
def _export_empty_blobj(self, so, bo): def _export_empty_blobj(self, so, bo):
# We don't need to do anything here. This function just makes sure we don't error out self.animation.convert_object_animations(bo, so)
# or add a silly special case :(
pass
def _export_lamp_blobj(self, so, bo): def _export_lamp_blobj(self, so, bo):
# We'll just redirect this to the RT Light converter... self.animation.convert_object_animations(bo, so)
self.light.export_rtlight(so, bo) self.light.export_rtlight(so, bo)
def _export_mesh_blobj(self, so, bo): def _export_mesh_blobj(self, so, bo):
self.animation.convert_object_animations(bo, so)
if bo.data.materials: if bo.data.materials:
self.mesh.export_object(bo) self.mesh.export_object(bo)
else: else:

13
korman/exporter/utils.py

@ -15,6 +15,19 @@
from PyHSPlasma import * from PyHSPlasma import *
def affine_parts(xform):
# Decompose the matrix into the 90s-era 3ds max affine parts sillyness
# All that's missing now is something like "(c) 1998 HeadSpin" oh wait...
affine = hsAffineParts()
affine.T = hsVector3(*xform.to_translation())
affine.K = hsVector3(*xform.to_scale())
affine.F = -1.0 if xform.determinant() < 0.0 else 1.0
rot = xform.to_quaternion()
affine.Q = quaternion(rot)
rot.normalize()
affine.U = quaternion(rot)
return affine
def color(blcolor, alpha=1.0): def color(blcolor, alpha=1.0):
"""Converts a Blender Color into an hsColorRGBA""" """Converts a Blender Color into an hsColorRGBA"""
return hsColorRGBA(blcolor.r, blcolor.g, blcolor.b, alpha) return hsColorRGBA(blcolor.r, blcolor.g, blcolor.b, alpha)

13
korman/helpers.py

@ -51,6 +51,19 @@ class TemporaryObject:
def ensure_power_of_two(value): def ensure_power_of_two(value):
return pow(2, math.floor(math.log(value, 2))) return pow(2, math.floor(math.log(value, 2)))
def fetch_fcurves(id_data, data_fcurves=True):
"""Given a Blender ID, yields its FCurves"""
def _fetch(source):
if source is not None and source.action is not None:
for i in source.action.fcurves:
yield i
# This seems rather unpythonic IMO
for i in _fetch(id_data.animation_data):
yield i
if data_fcurves:
for i in _fetch(id_data.data.animation_data):
yield i
def find_modifier(bo, modid): def find_modifier(bo, modid):
"""Given a Blender Object, finds a given modifier and returns it or None""" """Given a Blender Object, finds a given modifier and returns it or None"""

3
korman/properties/prop_camera.py

@ -19,7 +19,8 @@ import math
camera_types = [("circle", "Circle Camera", "The camera circles a fixed point"), camera_types = [("circle", "Circle Camera", "The camera circles a fixed point"),
("follow", "Follow Camera", "The camera follows an object"), ("follow", "Follow Camera", "The camera follows an object"),
("fixed", "Fixed Camera", "The camera is fixed in one location")] ("fixed", "Fixed Camera", "The camera is fixed in one location"),
("rail", "Rail Camera", "The camera follows an object by moving along a line")]
class PlasmaTransition(bpy.types.PropertyGroup): class PlasmaTransition(bpy.types.PropertyGroup):
poa_acceleration = FloatProperty(name="PoA Acceleration", poa_acceleration = FloatProperty(name="PoA Acceleration",

18
korman/ui/ui_camera.py

@ -47,6 +47,18 @@ class PlasmaCameraTransitionPanel(CameraButtonsPanel, bpy.types.Panel):
def draw_camera_properties(cam_type, props, layout, context, force_no_anim=False): def draw_camera_properties(cam_type, props, layout, context, force_no_anim=False):
trans = props.transition trans = props.transition
def _draw_alert_prop(layout, props, the_prop, cam="", min=None, max=None, **kwargs):
can_alert = not cam or cam == cam_type
if can_alert:
value = getattr(props, the_prop)
if min is not None and value < min:
layout.alert = True
if max is not None and value > max:
layout.alert = True
layout.prop(props, the_prop, **kwargs)
layout.alert = False
else:
layout.prop(props, the_prop, **kwargs)
def _draw_gated_prop(layout, props, gate_prop, actual_prop): def _draw_gated_prop(layout, props, gate_prop, actual_prop):
row = layout.row(align=True) row = layout.row(align=True)
row.prop(props, gate_prop, text="") row.prop(props, gate_prop, text="")
@ -111,9 +123,9 @@ def draw_camera_properties(cam_type, props, layout, context, force_no_anim=False
# Position Transitions # Position Transitions
col.active = cam_type != "circle" col.active = cam_type != "circle"
col.label("Default Position Transition:") col.label("Default Position Transition:")
col.prop(trans, "pos_acceleration", text="Acceleration") _draw_alert_prop(col, trans, "pos_acceleration", cam="rail", max=10.0, text="Acceleration")
col.prop(trans, "pos_deceleration", text="Deceleration") _draw_alert_prop(col, trans, "pos_deceleration", cam="rail", max=10.0, text="Deceleration")
col.prop(trans, "pos_velocity", text="Maximum Velocity") _draw_alert_prop(col, trans, "pos_velocity", cam="rail", max=10.0, text="Maximum Velocity")
col.prop(trans, "pos_cut") col.prop(trans, "pos_cut")
# Position Offsets # Position Offsets

Loading…
Cancel
Save