mirror of https://github.com/H-uru/korman.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
11 KiB
243 lines
11 KiB
# 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) |
|
if camera_props.poa_type != "none": |
|
if camera_props.maintain_los: |
|
brain.setFlags(plCameraBrain1.kMaintainLOS, True) |
|
if camera_props.fall_vertical: |
|
brain.setFlags(plCameraBrain1.kVerticalWhenFalling, True) |
|
if camera_props.fast_run: |
|
brain.setFlags(plCameraBrain1.kSpeedUpWhenRunning, True) |
|
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.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) |
|
|
|
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: |
|
# Don't even bother if a disabled camera is referenced. If we export camera modifier |
|
# for a disabled camera, then it won't get a brain, and the client will crash. |
|
if not manual_trans.camera.plasma_object.enabled: |
|
continue |
|
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(f"Circle Camera '{bo.name}' has no Point of Attention. Is this intended?") |
|
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.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 |
|
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_firstperson_camera(self, so, bo, props): |
|
brain = self._mgr.find_create_object(plCameraBrain1_FirstPerson, so=so) |
|
self._convert_brain(so, bo, props, brain) |
|
|
|
# Copy pasta the follow values for FP cam |
|
brain.offset = hsVector3(*props.pos_offset) |
|
return brain |
|
|
|
def _export_fixed_camera(self, so, bo, props): |
|
anim_mod = bo.plasma_modifiers.animation |
|
if props.anim_enabled and not anim_mod.enabled and bo.plasma_object.has_animation_data: |
|
anim_mod.convert_object_animations(self._exporter(), 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.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() |
|
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 |
|
# to avoid single/duplicate keyframe client crash (per Hoikas) |
|
if any((len(i.keys) == 1 for i in (pos_ctrl.X, pos_ctrl.Y, pos_ctrl.Z) if i is not None)): |
|
raise ExportError("'{}': Rail Camera must have more than one keyframe", bo.name) |
|
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
|
|
|