diff --git a/korman/exporter/camera.py b/korman/exporter/camera.py
new file mode 100644
index 0000000..7524e70
--- /dev/null
+++ b/korman/exporter/camera.py
@@ -0,0 +1,104 @@
+# 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 .
+
+import bpy
+import math
+from PyHSPlasma import *
+import weakref
+
+from .explosions import *
+
+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 isinstance(brain, plCameraBrain1_Avatar):
+ brain.offset = hsVector3(*camera_props.pos_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 trans_props.pos_cut:
+ brain.setFlags(plCameraBrain1.kCutPos, True)
+ if trans_props.poa_cut:
+ 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):
+ 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):
+ # 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)
+ 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?
+ return mod
+
+ 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):
+ brain = self._mgr.find_create_object(plCameraBrain1_Avatar, so=so)
+ self._convert_brain(so, bo, props, brain)
+ return brain
+
+ @property
+ def _mgr(self):
+ return self._exporter().mgr
+
+ @property
+ def _report(self):
+ return self._exporter().report
diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py
index 8bf5da0..75d7206 100644
--- a/korman/exporter/convert.py
+++ b/korman/exporter/convert.py
@@ -20,6 +20,7 @@ from PyHSPlasma import *
import time
from . import animation
+from . import camera
from . import explosions
from . import etlight
from . import logger
@@ -52,6 +53,7 @@ class Exporter:
self.light = rtlight.LightConverter(self)
self.animation = animation.AnimationConverter(self)
self.sumfile = sumfile.SumFile()
+ self.camera = camera.CameraConverter(self)
# Step 0.8: Init the progress mgr
self.mesh.add_progress_presteps(self.report)
@@ -236,6 +238,11 @@ class Exporter:
mod.export(self, bl_obj, sceneobject)
inc_progress()
+ def _export_camera_blobj(self, so, bo):
+ # Hey, guess what? Blender's camera data is utter crap!
+ camera = bo.data.plasma_camera
+ self.camera.export_camera(so, bo, camera.camera_type, camera.settings)
+
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
# or add a silly special case :(
diff --git a/korman/idprops.py b/korman/idprops.py
index 1423943..66328d2 100644
--- a/korman/idprops.py
+++ b/korman/idprops.py
@@ -124,6 +124,9 @@ def poll_animated_objects(self, value):
return True
return False
+def poll_camera_objects(self, value):
+ return value.type == "CAMERA"
+
def poll_empty_objects(self, value):
return value.type == "EMPTY"
diff --git a/korman/properties/__init__.py b/korman/properties/__init__.py
index dc98a70..aed2561 100644
--- a/korman/properties/__init__.py
+++ b/korman/properties/__init__.py
@@ -15,6 +15,7 @@
import bpy
+from .prop_camera import *
from .prop_lamp import *
from . import modifiers
from .prop_object import *
@@ -23,6 +24,7 @@ from .prop_world import *
def register():
+ bpy.types.Camera.plasma_camera = bpy.props.PointerProperty(type=PlasmaCamera)
bpy.types.Lamp.plasma_lamp = bpy.props.PointerProperty(type=PlasmaLamp)
bpy.types.Object.plasma_net = bpy.props.PointerProperty(type=PlasmaNet)
bpy.types.Object.plasma_object = bpy.props.PointerProperty(type=PlasmaObject)
diff --git a/korman/properties/modifiers/region.py b/korman/properties/modifiers/region.py
index 99cde54..dfff86a 100644
--- a/korman/properties/modifiers/region.py
+++ b/korman/properties/modifiers/region.py
@@ -22,6 +22,7 @@ from ...helpers import TemporaryObject
from ... import idprops
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
+from ..prop_camera import PlasmaCameraProperties
from .physics import bounds_types
footstep_surface_ids = {
@@ -58,6 +59,66 @@ footstep_surfaces = [("dirt", "Dirt", "Dirt"),
("woodfloor", "Wood Floor", "Wood Floor"),
("woodladder", "Wood Ladder", "Wood Ladder")]
+class PlasmaCameraRegion(PlasmaModifierProperties):
+ pl_id = "camera_rgn"
+
+ bl_category = "Region"
+ bl_label = "Camera Region"
+ bl_description = "Camera Region"
+ bl_icon = "CAMERA_DATA"
+
+ camera_type = EnumProperty(name="Camera Type",
+ description="What kind of camera should be used?",
+ items=[("auto", "Auto Follow Camera", "Automatically generated camera"),
+ ("manual", "Manual Camera", "User specified camera object")],
+ options=set())
+ camera_object = PointerProperty(name="Camera",
+ description="Switches to this camera",
+ type=bpy.types.Object,
+ poll=idprops.poll_camera_objects,
+ options=set())
+ auto_camera = PointerProperty(type=PlasmaCameraProperties, options=set())
+
+ def export(self, exporter, bo, so):
+ if self.camera_type == "manual":
+ if self.camera_object is None:
+ raise ExportError("Camera Modifier '{}' does not specify a valid camera object".format(self.id_data.name))
+ camera_so_key = exporter.mgr.find_create_key(plSceneObject, bl=self.camera_object)
+ camera_props = self.camera_object.data.plasma_camera.settings
+ else:
+ # Wheedoggy! We get to export the doggone camera now.
+ camera_props = self.auto_camera
+ exporter.camera.export_camera(so, bo, "follow", camera_props)
+
+ # Setup physical stuff
+ phys_mod = bo.plasma_modifiers.collision
+ simIface, physical = exporter.physics.generate_physical(bo, so, phys_mod.bounds, self.key_name)
+ physical.memberGroup = plSimDefs.kGroupDetector
+ physical.reportGroup = 1 << plSimDefs.kGroupAvatar
+ simIface.setProperty(plSimulationInterface.kPinned, True)
+ physical.setProperty(plSimulationInterface.kPinned, True)
+
+ # I don't feel evil enough to make this generate a logic tree...
+ msg = plCameraMsg()
+ msg.BCastFlags |= plMessage.kLocalPropagate | plMessage.kBCastByType
+ msg.setCmd(plCameraMsg.kRegionPushCamera)
+ if camera_props.primary_camera:
+ msg.setCmd(plCameraMsg.kSetAsPrimary)
+ msg.newCam = camera_so_key
+
+ region = exporter.mgr.find_create_object(plCameraRegionDetector, so=so)
+ region.addMessage(msg)
+
+ def harvest_actors(self):
+ if self.camera_type == "manual":
+ if self.camera_object is None:
+ raise ExportError("Camera Modifier '{}' does not specify a valid camera object".format(self.id_data.name))
+ camera = self.camera_object.data.plasma_camera.settings
+ else:
+ camera = self.auto_camera
+ return camera.harvest_actors()
+
+
class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
pl_id = "footstep"
diff --git a/korman/properties/prop_camera.py b/korman/properties/prop_camera.py
new file mode 100644
index 0000000..fd5b7fe
--- /dev/null
+++ b/korman/properties/prop_camera.py
@@ -0,0 +1,160 @@
+# 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 .
+
+import bpy
+from bpy.props import *
+import math
+
+camera_types = [("follow", "Follow Camera", "Camera follows an object."),
+ ("fixed", "Fixed Camera", "Camera is fixed in one location.")]
+
+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 Transition",
+ 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 Transition",
+ description="The camera immediately moves to its new position",
+ 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())
+
+ 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=PlasmaTransition,
+ name="Transitions",
+ description="",
+ options=set())
diff --git a/korman/render.py b/korman/render.py
index 393b9eb..e51304e 100644
--- a/korman/render.py
+++ b/korman/render.py
@@ -36,6 +36,10 @@ properties_material.MATERIAL_PT_transp.COMPAT_ENGINES.add("PLASMA_GAME")
properties_material.MATERIAL_PT_shadow.COMPAT_ENGINES.add("PLASMA_GAME")
del properties_material
+from bl_ui import properties_data_camera
+properties_data_camera.DATA_PT_context_camera.COMPAT_ENGINES.add("PLASMA_GAME")
+del properties_data_camera
+
from bl_ui import properties_data_mesh
properties_data_mesh.DATA_PT_uv_texture.COMPAT_ENGINES.add("PLASMA_GAME")
properties_data_mesh.DATA_PT_vertex_colors.COMPAT_ENGINES.add("PLASMA_GAME")
diff --git a/korman/ui/__init__.py b/korman/ui/__init__.py
index c1f3e3f..dde0d43 100644
--- a/korman/ui/__init__.py
+++ b/korman/ui/__init__.py
@@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see .
+from .ui_camera import *
from .ui_lamp import *
from .ui_list import *
from .ui_menus import *
diff --git a/korman/ui/modifiers/region.py b/korman/ui/modifiers/region.py
index 58b6b23..80dc272 100644
--- a/korman/ui/modifiers/region.py
+++ b/korman/ui/modifiers/region.py
@@ -14,6 +14,14 @@
# along with Korman. If not, see .
import bpy
+from ..ui_camera import draw_camera_properties
+
+def camera_rgn(modifier, layout, context):
+ layout.prop(modifier, "camera_type")
+ if modifier.camera_type == "manual":
+ layout.prop(modifier, "camera_object", icon="CAMERA_DATA")
+ else:
+ draw_camera_properties("follow", modifier.auto_camera, layout, context)
def footstep(modifier, layout, context):
layout.prop(modifier, "bounds")
diff --git a/korman/ui/ui_camera.py b/korman/ui/ui_camera.py
new file mode 100644
index 0000000..43a3e71
--- /dev/null
+++ b/korman/ui/ui_camera.py
@@ -0,0 +1,128 @@
+# 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 .
+
+import bpy
+
+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 PlasmaCameraPanel(CameraButtonsPanel, bpy.types.Panel):
+ bl_label = "Plasma Camera"
+
+ def draw(self, context):
+ camera = context.camera.plasma_camera
+ layout = self.layout
+
+ layout.prop(camera, "camera_type")
+ layout.separator()
+ draw_camera_properties(camera.camera_type, camera.settings, layout, context)
+
+
+class PlasmaCameraTransitionPanel(CameraButtonsPanel, bpy.types.Panel):
+ bl_label = "Plasma Transitions"
+
+ def draw(self, context):
+ pass
+
+
+def draw_camera_properties(cam_type, props, layout, context):
+ trans = props.transition
+
+ 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)
+
+ # Point of Attention
+ split = layout.split()
+ col = split.column()
+ col.label("Camera Mode:")
+ col = col.column()
+ col.alert = cam_type == "follow" 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")
+
+ # PoA Tracking
+ layout.separator()
+ 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.prop(trans, "poa_cut")
+
+ # PoA Offset
+ col = split.column()
+ col.label("Point of Attention Offset:")
+ col.prop(props, "poa_offset", text="")
+ col.prop(props, "poa_worldspace")
+
+ # Position Tracking (only for follow cams)
+ layout.separator()
+ split = layout.split()
+ col = split.column()
+
+ # Position Transitions
+ col.label("Default 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")
+
+ # 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")
+
+ # Camera Panning
+ layout.separator()
+ 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")