mirror of https://github.com/H-uru/korman.git
13 changed files with 1032 additions and 38 deletions
@ -0,0 +1,225 @@ |
|||||||
|
# This file is part of Korman. |
||||||
|
# |
||||||
|
# Korman is free software: you can redistribute it and/or modify |
||||||
|
# it under the terms of the GNU General Public License as published by |
||||||
|
# the Free Software Foundation, either version 3 of the License, or |
||||||
|
# (at your option) any later version. |
||||||
|
# |
||||||
|
# Korman is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
import bpy |
||||||
|
import math |
||||||
|
from PyHSPlasma import * |
||||||
|
import weakref |
||||||
|
|
||||||
|
from .explosions import * |
||||||
|
from .. import helpers |
||||||
|
from . import utils |
||||||
|
|
||||||
|
class CameraConverter: |
||||||
|
def __init__(self, exporter): |
||||||
|
self._exporter = weakref.ref(exporter) |
||||||
|
|
||||||
|
def _convert_brain(self, so, bo, camera_props, brain): |
||||||
|
trans_props = camera_props.transition |
||||||
|
|
||||||
|
brain.poaOffset = hsVector3(*camera_props.poa_offset) |
||||||
|
if camera_props.poa_type == "object": |
||||||
|
brain.subject = self._mgr.find_create_key(plSceneObject, bl=camera_props.poa_object) |
||||||
|
|
||||||
|
brain.xPanLimit = camera_props.x_pan_angle / 2.0 |
||||||
|
brain.zPanLimit = camera_props.y_pan_angle / 2.0 |
||||||
|
brain.panSpeed = camera_props.pan_rate |
||||||
|
if camera_props.limit_zoom: |
||||||
|
brain.setFlags(plCameraBrain1.kZoomEnabled, True) |
||||||
|
brain.zoomMax = camera_props.zoom_max * (4.0 / 3.0) |
||||||
|
brain.zoomMin = camera_props.zoom_min * (4.0 / 3.0) |
||||||
|
brain.zoomRate = camera_props.zoom_rate |
||||||
|
|
||||||
|
brain.acceleration = trans_props.pos_acceleration |
||||||
|
brain.deceleration = trans_props.pos_deceleration |
||||||
|
brain.velocity = trans_props.pos_velocity |
||||||
|
brain.poaAcceleration = trans_props.poa_acceleration |
||||||
|
brain.poaDeceleration = trans_props.poa_deceleration |
||||||
|
brain.poaVelocity = trans_props.poa_velocity |
||||||
|
|
||||||
|
if isinstance(brain, plCameraBrain1_Avatar): |
||||||
|
brain.setFlags(plCameraBrain1.kCutPos, trans_props.pos_cut) |
||||||
|
brain.setFlags(plCameraBrain1.kCutPOA, trans_props.poa_cut) |
||||||
|
else: |
||||||
|
brain.setFlags(plCameraBrain1.kCutPos, True) |
||||||
|
brain.setFlags(plCameraBrain1.kCutPOA, True) |
||||||
|
if camera_props.poa_type == "avatar": |
||||||
|
brain.setFlags(plCameraBrain1.kFollowLocalAvatar, True) |
||||||
|
if camera_props.maintain_los: |
||||||
|
brain.setFlags(plCameraBrain1.kMaintainLOS, True) |
||||||
|
if camera_props.poa_worldspace: |
||||||
|
brain.setFlags(plCameraBrain1.kWorldspacePOA, True) |
||||||
|
if camera_props.pos_worldspace: |
||||||
|
brain.setFlags(plCameraBrain1.kWorldspacePos, True) |
||||||
|
if camera_props.ignore_subworld: |
||||||
|
brain.setFlags(plCameraBrain1.kIgnoreSubworldMovement, True) |
||||||
|
if camera_props.fall_vertical: |
||||||
|
brain.setFlags(plCameraBrain1.kVerticalWhenFalling, True) |
||||||
|
if camera_props.fast_run: |
||||||
|
brain.setFlags(plCameraBrain1.kSpeedUpWhenRunning, True) |
||||||
|
|
||||||
|
def export_camera(self, so, bo, camera_type, camera_props, camera_trans=[]): |
||||||
|
brain = getattr(self, "_export_{}_camera".format(camera_type))(so, bo, camera_props) |
||||||
|
mod = self._export_camera_modifier(so, bo, camera_props, camera_trans) |
||||||
|
mod.brain = brain.key |
||||||
|
|
||||||
|
def _export_camera_modifier(self, so, bo, props, trans): |
||||||
|
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.. |
||||||
|
fov = props.fov |
||||||
|
mod.fovW, mod.fovH = math.degrees(fov), math.degrees(fov * (3.0 / 4.0)) |
||||||
|
|
||||||
|
# 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 |
||||||
|
|
||||||
|
for manual_trans in trans: |
||||||
|
if not manual_trans.enabled or manual_trans.mode == "auto": |
||||||
|
continue |
||||||
|
cam_trans = plCameraModifier.CamTrans() |
||||||
|
if manual_trans.camera: |
||||||
|
cam_trans.transTo = self._mgr.find_create_key(plCameraModifier, bl=manual_trans.camera) |
||||||
|
cam_trans.ignore = manual_trans.mode == "ignore" |
||||||
|
|
||||||
|
trans_info = manual_trans.transition |
||||||
|
cam_trans.cutPos = trans_info.pos_cut |
||||||
|
cam_trans.cutPOA = trans_info.poa_cut |
||||||
|
cam_trans.accel = trans_info.pos_acceleration |
||||||
|
cam_trans.decel = trans_info.pos_deceleration |
||||||
|
cam_trans.velocity = trans_info.pos_velocity |
||||||
|
cam_trans.poaAccel = trans_info.poa_acceleration |
||||||
|
cam_trans.poaDecel = trans_info.poa_deceleration |
||||||
|
cam_trans.poaVelocity = trans_info.poa_velocity |
||||||
|
mod.addTrans(cam_trans) |
||||||
|
|
||||||
|
return mod |
||||||
|
|
||||||
|
def _export_circle_camera(self, so, bo, props): |
||||||
|
brain = self._mgr.find_create_object(plCameraBrain1_Circle, so=so) |
||||||
|
self._convert_brain(so, bo, props, brain) |
||||||
|
|
||||||
|
# Circle Camera specific stuff ahoy! |
||||||
|
if props.poa_type == "avatar": |
||||||
|
brain.circleFlags |= plCameraBrain1_Circle.kCircleLocalAvatar |
||||||
|
elif props.poa_type == "object": |
||||||
|
brain.poaObject = self._mgr.find_create_key(plSceneObject, bl=props.poa_object) |
||||||
|
else: |
||||||
|
self._report.warn("Circle Camera '{}' has no Point of Attention. Is this intended?", bo.name, indent=3) |
||||||
|
if props.circle_pos == "farthest": |
||||||
|
brain.circleFlags |= plCameraBrain1_Circle.kFarthest |
||||||
|
|
||||||
|
# If no center object is specified, we will use the camera object's location. |
||||||
|
# We will use a simple vector for this object to make life simpler, however, |
||||||
|
# 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) |
||||||
|
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 |
||||||
|
brain.circleFlags |= plCameraBrain1_Circle.kHasCenterObject |
||||||
|
|
||||||
|
# PlasmaMAX forces these values so the circle camera is rather slow, which is somewhat |
||||||
|
# sensible to me. Fast circular motion seems like a poor idea to me... If we want these |
||||||
|
# values to be customizable, we probably want add some kind of range limitation or at least |
||||||
|
# a flashing red light in the UI... |
||||||
|
brain.acceleration = 10.0 |
||||||
|
brain.deceleration = 10.0 |
||||||
|
brain.velocity = 15.0 |
||||||
|
|
||||||
|
# Related to above, circle cameras use a slightly different velocity method. |
||||||
|
# This is stored in Plasma as a fraction of the circle's circumference. It makes |
||||||
|
# more sense to me to present it in terms of degrees per second, however. |
||||||
|
# NOTE that Blender returns radians!!! |
||||||
|
brain.cirPerSec = props.circle_velocity / (2 * math.pi) |
||||||
|
|
||||||
|
# I consider this clever... If we have a center object, we use the magnitude of the displacement |
||||||
|
# of the camera's object to the center object (at frame 0) to determine the radius. If no center |
||||||
|
# object is specified, we allow the user to specify the radius. |
||||||
|
# Well, it's clever until you realize it's the same thing Cyan did in PlasmaMAX... But it's harder |
||||||
|
# here because Blendsucks. |
||||||
|
brain.radius = props.get_circle_radius(bo) |
||||||
|
|
||||||
|
# Done already? |
||||||
|
return brain |
||||||
|
|
||||||
|
def _export_fixed_camera(self, so, bo, props): |
||||||
|
if props.anim_enabled: |
||||||
|
self._exporter().animation.convert_object_animations(bo, so) |
||||||
|
brain = self._mgr.find_create_object(plCameraBrain1_Fixed, so=so) |
||||||
|
self._convert_brain(so, bo, props, brain) |
||||||
|
return brain |
||||||
|
|
||||||
|
def _export_follow_camera(self, so, bo, props): |
||||||
|
brain = self._mgr.find_create_object(plCameraBrain1_Avatar, so=so) |
||||||
|
self._convert_brain(so, bo, props, brain) |
||||||
|
|
||||||
|
# Follow camera specific stuff ahoy! |
||||||
|
brain.offset = hsVector3(*props.pos_offset) |
||||||
|
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 |
||||||
|
if props.rail_pos == "farthest": |
||||||
|
path.flags |= plAnimPath.kFarthest |
||||||
|
path.length = end / bpy.context.scene.render.fps |
||||||
|
rail.path = path |
||||||
|
brain.rail = rail.key |
||||||
|
|
||||||
|
return brain |
||||||
|
|
||||||
|
@property |
||||||
|
def _mgr(self): |
||||||
|
return self._exporter().mgr |
||||||
|
|
||||||
|
@property |
||||||
|
def _report(self): |
||||||
|
return self._exporter().report |
@ -0,0 +1,246 @@ |
|||||||
|
# This file is part of Korman. |
||||||
|
# |
||||||
|
# Korman is free software: you can redistribute it and/or modify |
||||||
|
# it under the terms of the GNU General Public License as published by |
||||||
|
# the Free Software Foundation, either version 3 of the License, or |
||||||
|
# (at your option) any later version. |
||||||
|
# |
||||||
|
# Korman is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
import bpy |
||||||
|
from bpy.props import * |
||||||
|
import math |
||||||
|
|
||||||
|
from .. import idprops |
||||||
|
|
||||||
|
camera_types = [("circle", "Circle Camera", "The camera circles a fixed point"), |
||||||
|
("follow", "Follow Camera", "The camera follows an object"), |
||||||
|
("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): |
||||||
|
poa_acceleration = FloatProperty(name="PoA Acceleration", |
||||||
|
description="Rate the camera's Point of Attention tracking velocity increases in feet per second squared", |
||||||
|
min=-100.0, max=100.0, precision=0, default=60.0, |
||||||
|
unit="ACCELERATION", options=set()) |
||||||
|
poa_deceleration = FloatProperty(name="PoA Deceleration", |
||||||
|
description="Rate the camera's Point of Attention tracking velocity decreases in feet per second squared", |
||||||
|
min=-100.0, max=100.0, precision=0, default=60.0, |
||||||
|
unit="ACCELERATION", options=set()) |
||||||
|
poa_velocity = FloatProperty(name="PoA Velocity", |
||||||
|
description="Maximum velocity of the camera's Point of Attention tracking", |
||||||
|
min=-100.0, max=100.0, precision=0, default=60.0, |
||||||
|
unit="VELOCITY", options=set()) |
||||||
|
poa_cut = BoolProperty(name="Cut", |
||||||
|
description="The camera immediately begins tracking the Point of Attention", |
||||||
|
options=set()) |
||||||
|
|
||||||
|
pos_acceleration = FloatProperty(name="Position Acceleration", |
||||||
|
description="Rate the camera's positional velocity increases in feet per second squared", |
||||||
|
min=-100.0, max=100.0, precision=0, default=60.0, |
||||||
|
unit="ACCELERATION", options=set()) |
||||||
|
pos_deceleration = FloatProperty(name="Position Deceleration", |
||||||
|
description="Rate the camera's positional velocity decreases in feet per second squared", |
||||||
|
min=-100.0, max=100.0, precision=0, default=60.0, |
||||||
|
unit="ACCELERATION", options=set()) |
||||||
|
pos_velocity = FloatProperty(name="Position Max Velocity", |
||||||
|
description="Maximum positional velocity of the camera", |
||||||
|
min=-100.0, max=100.0, precision=0, default=60.0, |
||||||
|
unit="VELOCITY", options=set()) |
||||||
|
pos_cut = BoolProperty(name="Cut", |
||||||
|
description="The camera immediately moves to its new position", |
||||||
|
options=set()) |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaManualTransition(bpy.types.PropertyGroup): |
||||||
|
camera = PointerProperty(name="Camera", |
||||||
|
description="The camera from which this transition is intended", |
||||||
|
type=bpy.types.Object, |
||||||
|
poll=idprops.poll_camera_objects, |
||||||
|
options=set()) |
||||||
|
transition = PointerProperty(type=PlasmaTransition, options=set()) |
||||||
|
mode = EnumProperty(name="Transition Mode", |
||||||
|
description="Type of transition that should occur between the two cameras", |
||||||
|
items=[("ignore", "Ignore Camera", "Ignore this camera and do not transition"), |
||||||
|
("auto", "Auto", "Auto transition as defined by the two cameras' properies"), |
||||||
|
("manual", "Manual", "Manually defined transition")], |
||||||
|
default="auto", |
||||||
|
options=set()) |
||||||
|
enabled = BoolProperty(name="Enabled", |
||||||
|
description="Export this transition", |
||||||
|
default=True, |
||||||
|
options=set()) |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraProperties(bpy.types.PropertyGroup): |
||||||
|
# Point of Attention |
||||||
|
poa_type = EnumProperty(name="Point of Attention", |
||||||
|
description="The point of attention that this camera tracks", |
||||||
|
items=[("avatar", "Track Local Player", "Camera tracks the player's avatar"), |
||||||
|
("object", "Track Object", "Camera tracks an object in the scene"), |
||||||
|
("none", "Don't Track", "Camera does not track anything")], |
||||||
|
options=set()) |
||||||
|
poa_object = PointerProperty(name="PoA Object", |
||||||
|
description="Object the camera should track as its Point of Attention", |
||||||
|
type=bpy.types.Object, |
||||||
|
options=set()) |
||||||
|
poa_offset = FloatVectorProperty(name="PoA Offset", |
||||||
|
description="Offset from the point of attention's origin to track", |
||||||
|
soft_min=-50.0, soft_max=50.0, |
||||||
|
size=3, default=(0.0, 0.0, 3.0), |
||||||
|
options=set()) |
||||||
|
poa_worldspace = BoolProperty(name="Worldspace Offset", |
||||||
|
description="Point of Attention Offset is in worldspace coordinates", |
||||||
|
options=set()) |
||||||
|
|
||||||
|
# Position Offset |
||||||
|
pos_offset = FloatVectorProperty(name="Position Offset", |
||||||
|
description="Offset the camera's position", |
||||||
|
soft_min=-50.0, soft_max=50.0, |
||||||
|
size=3, default=(0.0, 10.0, 3.0), |
||||||
|
options=set()) |
||||||
|
pos_worldspace = BoolProperty(name="Worldspace Offset", |
||||||
|
description="Position offset is in worldspace coordinates", |
||||||
|
options=set()) |
||||||
|
|
||||||
|
# Default Transition |
||||||
|
transition = PointerProperty(type=PlasmaTransition, options=set()) |
||||||
|
|
||||||
|
# Limit Panning |
||||||
|
x_pan_angle = FloatProperty(name="X Degrees", |
||||||
|
description="Maximum camera pan angle in the X direction", |
||||||
|
min=0.0, max=math.radians(180.0), precision=0, default=math.radians(90.0), |
||||||
|
subtype="ANGLE", options=set()) |
||||||
|
y_pan_angle = FloatProperty(name="Y Degrees", |
||||||
|
description="Maximum camera pan angle in the Y direction", |
||||||
|
min=0.0, max=math.radians(180.0), precision=0, default=math.radians(90.0), |
||||||
|
subtype="ANGLE", options=set()) |
||||||
|
pan_rate = FloatProperty(name="Pan Velocity", |
||||||
|
description="", |
||||||
|
min=0.0, precision=1, default=50.0, |
||||||
|
unit="VELOCITY", options=set()) |
||||||
|
|
||||||
|
# Zooming |
||||||
|
fov = FloatProperty(name="Default FOV", |
||||||
|
description="Horizontal Field of View angle", |
||||||
|
min=0.0, max=math.radians(180.0), precision=0, default=math.radians(70.0), |
||||||
|
subtype="ANGLE") |
||||||
|
limit_zoom = BoolProperty(name="Limit Zoom", |
||||||
|
description="The camera allows zooming per artist limitations", |
||||||
|
options=set()) |
||||||
|
zoom_max = FloatProperty(name="Max FOV", |
||||||
|
description="Maximum camera FOV when zooming", |
||||||
|
min=0.0, max=math.radians(180.0), precision=0, default=math.radians(120.0), |
||||||
|
subtype="ANGLE", options=set()) |
||||||
|
zoom_min = FloatProperty(name="Min FOV", |
||||||
|
description="Minimum camera FOV when zooming", |
||||||
|
min=0.0, max=math.radians(180.0), precision=0, default=math.radians(35.0), |
||||||
|
subtype="ANGLE", options=set()) |
||||||
|
zoom_rate = FloatProperty(name="Zoom Velocity", |
||||||
|
description="Velocity of the camera's zoom in degrees per second", |
||||||
|
min=0.0, max=180.0, precision=0, default=90.0, |
||||||
|
unit="VELOCITY", options=set()) |
||||||
|
|
||||||
|
# Miscellaneous Movement Props |
||||||
|
maintain_los = BoolProperty(name="Maintain LOS", |
||||||
|
description="The camera should maintain line-of-sight with the object it's tracking", |
||||||
|
options=set()) |
||||||
|
fall_vertical = BoolProperty(name="Fall Camera", |
||||||
|
description="The camera will orient itself vertically when the local player begins falling", |
||||||
|
options=set()) |
||||||
|
fast_run = BoolProperty(name="Faster When Falling", |
||||||
|
description="The camera's velocity will have a floor when the local player is falling", |
||||||
|
options=set()) |
||||||
|
ignore_subworld = BoolProperty(name="Ignore Subworld Movement", |
||||||
|
description="The camera will not be parented to any subworlds", |
||||||
|
options=set()) |
||||||
|
|
||||||
|
# Core Type Properties |
||||||
|
primary_camera = BoolProperty(name="Primary Camera", |
||||||
|
description="The camera should be considered the Age's primary camera.", |
||||||
|
options=set()) |
||||||
|
|
||||||
|
# Cricle Camera |
||||||
|
def _get_circle_radius(self): |
||||||
|
# This is coming from the UI, so we need to get the active object from |
||||||
|
# Blender's context and pass that on to the actual getter. |
||||||
|
return self.get_circle_radius(bpy.context.object) |
||||||
|
def _set_circle_radius(self, value): |
||||||
|
# Don't really care about error checking... |
||||||
|
self.circle_radius_value = value |
||||||
|
|
||||||
|
circle_center = PointerProperty(name="Center", |
||||||
|
description="Center of the circle camera's orbit", |
||||||
|
type=bpy.types.Object, |
||||||
|
options=set()) |
||||||
|
circle_pos = EnumProperty(name="Position on Circle", |
||||||
|
description="The point on the circle the camera moves to", |
||||||
|
items=[("closest", "Closest Point", "The camera moves to the point on the circle closest to the Point of Attention"), |
||||||
|
("farthest", "Farthest Point", "The camera moves to the point on the circle farthest from the Point of Attention")], |
||||||
|
options=set()) |
||||||
|
circle_velocity = FloatProperty(name="Velocity", |
||||||
|
description="Velocity of the circle camera in degrees per second", |
||||||
|
min=0.0, max=math.radians(360.0), precision=0, default=math.radians(36.0), |
||||||
|
subtype="ANGLE", options=set()) |
||||||
|
circle_radius_ui = FloatProperty(name="Radius", |
||||||
|
description="Radius at which the circle camera should orbit the Point of Attention", |
||||||
|
min=0.0, get=_get_circle_radius, set=_set_circle_radius, options=set()) |
||||||
|
circle_radius_value = FloatProperty(name="INTERNAL: Radius", |
||||||
|
description="Radius at which the circle camera should orbit the Point of Attention", |
||||||
|
min=0.0, default=8.5, options={"HIDDEN"}) |
||||||
|
|
||||||
|
# Animation |
||||||
|
anim_enabled = BoolProperty(name="Animation Enabled", |
||||||
|
description="Export the camera's animation", |
||||||
|
default=True, |
||||||
|
options=set()) |
||||||
|
start_on_push = BoolProperty(name="Start on Push", |
||||||
|
description="Start playing the camera's animation when the camera is activated", |
||||||
|
default=True, |
||||||
|
options=set()) |
||||||
|
stop_on_pop = BoolProperty(name="Pause on Pop", |
||||||
|
description="Pauses the camera's animation when the camera is no longer activated", |
||||||
|
default=True, |
||||||
|
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()) |
||||||
|
|
||||||
|
# Rail |
||||||
|
rail_pos = EnumProperty(name="Position on Rail", |
||||||
|
description="The point on the rail the camera moves to", |
||||||
|
items=[("closest", "Closest Point", "The camera moves to the point on the rail closest to the Point of Attention"), |
||||||
|
("farthest", "Farthest Point", "The camera moves to the point on the rail farthest from the Point of Attention")], |
||||||
|
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 |
||||||
|
if self.circle_center is not None: |
||||||
|
vec = bo.location - self.circle_center.location |
||||||
|
return vec.magnitude |
||||||
|
return self.circle_radius_value |
||||||
|
|
||||||
|
def harvest_actors(self): |
||||||
|
if self.poa_type == "object": |
||||||
|
return set((self.poa_object.name),) |
||||||
|
return set() |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCamera(bpy.types.PropertyGroup): |
||||||
|
camera_type = EnumProperty(name="Camera Type", |
||||||
|
description="", |
||||||
|
items=camera_types, |
||||||
|
options=set()) |
||||||
|
settings = PointerProperty(type=PlasmaCameraProperties, options=set()) |
||||||
|
transitions = CollectionProperty(type=PlasmaManualTransition, |
||||||
|
name="Transitions", |
||||||
|
description="", |
||||||
|
options=set()) |
||||||
|
active_transition_index = IntProperty(options={"HIDDEN"}) |
@ -0,0 +1,272 @@ |
|||||||
|
# This file is part of Korman. |
||||||
|
# |
||||||
|
# Korman is free software: you can redistribute it and/or modify |
||||||
|
# it under the terms of the GNU General Public License as published by |
||||||
|
# the Free Software Foundation, either version 3 of the License, or |
||||||
|
# (at your option) any later version. |
||||||
|
# |
||||||
|
# Korman is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
import bpy |
||||||
|
|
||||||
|
from .. import helpers |
||||||
|
from . import ui_list |
||||||
|
|
||||||
|
def _draw_alert_prop(layout, props, the_prop, cam_type, alert_cam="", min=None, max=None, **kwargs): |
||||||
|
can_alert = not alert_cam or alert_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): |
||||||
|
row = layout.row(align=True) |
||||||
|
row.prop(props, gate_prop, text="") |
||||||
|
row = row.row(align=True) |
||||||
|
row.active = getattr(props, gate_prop) |
||||||
|
row.prop(props, actual_prop) |
||||||
|
|
||||||
|
def draw_camera_manipulation_props(layout, cam_type, props): |
||||||
|
# Camera Panning |
||||||
|
split = layout.split() |
||||||
|
col = split.column() |
||||||
|
col.label("Limit Panning:") |
||||||
|
col.prop(props, "x_pan_angle") |
||||||
|
col.prop(props, "y_pan_angle") |
||||||
|
|
||||||
|
# Camera Zoom |
||||||
|
col = split.column() |
||||||
|
col.label("Field of View:") |
||||||
|
col.prop(props, "fov") |
||||||
|
_draw_gated_prop(col, props, "limit_zoom", "zoom_min") |
||||||
|
_draw_gated_prop(col, props, "limit_zoom", "zoom_max") |
||||||
|
_draw_gated_prop(col, props, "limit_zoom", "zoom_rate") |
||||||
|
|
||||||
|
def draw_camera_mode_props(layout, cam_type, props): |
||||||
|
# Point of Attention |
||||||
|
split = layout.split() |
||||||
|
col = split.column() |
||||||
|
col.label("Camera Mode:") |
||||||
|
col = col.column() |
||||||
|
col.alert = cam_type != "fixed" and props.poa_type == "none" |
||||||
|
col.prop(props, "poa_type", text="") |
||||||
|
col.alert = False |
||||||
|
row = col.row() |
||||||
|
row.active = props.poa_type == "object" |
||||||
|
row.prop(props, "poa_object", text="") |
||||||
|
col.separator() |
||||||
|
col.prop(props, "primary_camera") |
||||||
|
|
||||||
|
# Miscellaneous |
||||||
|
col = split.column() |
||||||
|
col.label("Tracking Settings:") |
||||||
|
col.prop(props, "maintain_los") |
||||||
|
col.prop(props, "fall_vertical") |
||||||
|
col.prop(props, "fast_run") |
||||||
|
col.prop(props, "ignore_subworld") |
||||||
|
|
||||||
|
def draw_camera_poa_props(layout, cam_type, props): |
||||||
|
trans = props.transition |
||||||
|
|
||||||
|
# PoA Tracking |
||||||
|
split = layout.split() |
||||||
|
col = split.column() |
||||||
|
col.label("Default Tracking Transition:") |
||||||
|
col.prop(trans, "poa_acceleration", text="Acceleration") |
||||||
|
col.prop(trans, "poa_deceleration", text="Deceleration") |
||||||
|
col.prop(trans, "poa_velocity", text="Maximum Velocity") |
||||||
|
col = col.column() |
||||||
|
col.active = cam_type == "follow" |
||||||
|
col.prop(trans, "poa_cut", text="Cut Animation") |
||||||
|
|
||||||
|
# PoA Offset |
||||||
|
col = split.column() |
||||||
|
col.label("Point of Attention Offset:") |
||||||
|
col.prop(props, "poa_offset", text="") |
||||||
|
col.prop(props, "poa_worldspace") |
||||||
|
|
||||||
|
def draw_camera_pos_props(layout, cam_type, props): |
||||||
|
trans = props.transition |
||||||
|
|
||||||
|
# Position Tracking (only for follow cams) |
||||||
|
split = layout.split() |
||||||
|
col = split.column() |
||||||
|
|
||||||
|
# Position Transitions |
||||||
|
col.active = cam_type != "circle" |
||||||
|
col.label("Default Position Transition:") |
||||||
|
_draw_alert_prop(col, trans, "pos_acceleration", cam_type, |
||||||
|
alert_cam="rail", max=10.0, text="Acceleration") |
||||||
|
_draw_alert_prop(col, trans, "pos_deceleration", cam_type, |
||||||
|
alert_cam="rail", max=10.0, text="Deceleration") |
||||||
|
_draw_alert_prop(col, trans, "pos_velocity", cam_type, |
||||||
|
alert_cam="rail", max=10.0, text="Maximum Velocity") |
||||||
|
col = col.column() |
||||||
|
col.active = cam_type == "follow" |
||||||
|
col.prop(trans, "pos_cut", text="Cut Animation") |
||||||
|
|
||||||
|
# Position Offsets |
||||||
|
col = split.column() |
||||||
|
col.active = cam_type == "follow" |
||||||
|
col.label("Position Offset:") |
||||||
|
col.prop(props, "pos_offset", text="") |
||||||
|
col.prop(props, "pos_worldspace") |
||||||
|
|
||||||
|
def draw_circle_camera_props(layout, props): |
||||||
|
# Circle Camera Stuff |
||||||
|
layout.prop(props, "circle_center") |
||||||
|
layout.prop(props, "circle_pos") |
||||||
|
layout.prop(props, "circle_velocity") |
||||||
|
row = layout.row(align=True) |
||||||
|
row.active = props.circle_center is None |
||||||
|
row.prop(props, "circle_radius_ui") |
||||||
|
|
||||||
|
class CameraButtonsPanel: |
||||||
|
bl_space_type = "PROPERTIES" |
||||||
|
bl_region_type = "WINDOW" |
||||||
|
bl_context = "data" |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def poll(cls, context): |
||||||
|
return (context.camera and context.scene.render.engine == "PLASMA_GAME") |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraTypePanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "" |
||||||
|
bl_options = {"HIDE_HEADER"} |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
self.layout.prop(camera, "camera_type") |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraModePanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "Camera Tracking" |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
draw_camera_mode_props(self.layout, camera.camera_type, camera.settings) |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraAttentionPanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "Point of Attention Tracking" |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
draw_camera_poa_props(self.layout, camera.camera_type, camera.settings) |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraPositionPanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "Position Tracking" |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
draw_camera_pos_props(self.layout, camera.camera_type, camera.settings) |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraCirclePanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "Circle Camera" |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
draw_circle_camera_props(self.layout, camera.settings) |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def poll(cls, context): |
||||||
|
return super().poll(context) and context.camera.plasma_camera.camera_type == "circle" |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraAnimationPanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "Camera Animation" |
||||||
|
bl_options = {"DEFAULT_CLOSED"} |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
layout = self.layout |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
props = camera.settings |
||||||
|
|
||||||
|
split = layout.split() |
||||||
|
col = split.column() |
||||||
|
col.label("Animation:") |
||||||
|
col.active = props.anim_enabled and any(helpers.fetch_fcurves(context.object)) |
||||||
|
col.prop(props, "start_on_push") |
||||||
|
col.prop(props, "stop_on_pop") |
||||||
|
col.prop(props, "reset_on_pop") |
||||||
|
|
||||||
|
col = split.column() |
||||||
|
col.active = camera.camera_type == "rail" |
||||||
|
col.label("Rail:") |
||||||
|
col.prop(props, "rail_pos", text="") |
||||||
|
|
||||||
|
def draw_header(self, context): |
||||||
|
self.layout.active = any(helpers.fetch_fcurves(context.object)) |
||||||
|
self.layout.prop(context.camera.plasma_camera.settings, "anim_enabled", text="") |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraViewPanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "Camera Lens" |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
draw_camera_manipulation_props(self.layout, camera.camera_type, camera.settings) |
||||||
|
|
||||||
|
|
||||||
|
class TransitionListUI(bpy.types.UIList): |
||||||
|
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0): |
||||||
|
if item.camera is None: |
||||||
|
layout.label("[Default Transition]") |
||||||
|
else: |
||||||
|
layout.label(item.camera.name, icon="CAMERA_DATA") |
||||||
|
layout.prop(item, "enabled", text="") |
||||||
|
|
||||||
|
|
||||||
|
class PlasmaCameraTransitionPanel(CameraButtonsPanel, bpy.types.Panel): |
||||||
|
bl_label = "Transitions" |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
layout = self.layout |
||||||
|
camera = context.camera.plasma_camera |
||||||
|
|
||||||
|
ui_list.draw_list(layout, "TransitionListUI", "camera", camera, "transitions", |
||||||
|
"active_transition_index", rows=3, maxrows=4) |
||||||
|
|
||||||
|
try: |
||||||
|
item = camera.transitions[camera.active_transition_index] |
||||||
|
trans = item.transition |
||||||
|
except: |
||||||
|
pass |
||||||
|
else: |
||||||
|
layout.separator() |
||||||
|
box = layout.box() |
||||||
|
box.prop(item, "camera", text="Transition From") |
||||||
|
box.prop(item, "mode") |
||||||
|
|
||||||
|
box.separator() |
||||||
|
split = box.split() |
||||||
|
split.active = item.mode == "manual" |
||||||
|
|
||||||
|
col = split.column() |
||||||
|
col.label("Tracking Transition:") |
||||||
|
col.prop(trans, "poa_acceleration", text="Acceleration") |
||||||
|
col.prop(trans, "poa_deceleration", text="Deceleration") |
||||||
|
col.prop(trans, "poa_velocity", text="Maximum Velocity") |
||||||
|
col.prop(trans, "poa_cut", text="Cut Transition") |
||||||
|
|
||||||
|
col = split.column() |
||||||
|
col.label("Position Transition:") |
||||||
|
col.prop(trans, "pos_acceleration", text="Acceleration") |
||||||
|
col.prop(trans, "pos_deceleration", text="Deceleration") |
||||||
|
col.prop(trans, "pos_velocity", text="Maximum Velocity") |
||||||
|
col.prop(trans, "pos_cut", text="Cut Transition") |
Loading…
Reference in new issue