Browse Source

Implement basic cameras

This implments basic follow and fixed cameras. Currently, animating
cameras and/or their field of view are/is not supported.
pull/105/head
Adam Johnson 7 years ago
parent
commit
da30e3f0c7
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 104
      korman/exporter/camera.py
  2. 7
      korman/exporter/convert.py
  3. 3
      korman/idprops.py
  4. 2
      korman/properties/__init__.py
  5. 61
      korman/properties/modifiers/region.py
  6. 160
      korman/properties/prop_camera.py
  7. 4
      korman/render.py
  8. 1
      korman/ui/__init__.py
  9. 8
      korman/ui/modifiers/region.py
  10. 128
      korman/ui/ui_camera.py

104
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 <http://www.gnu.org/licenses/>.
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

7
korman/exporter/convert.py

@ -20,6 +20,7 @@ from PyHSPlasma import *
import time import time
from . import animation from . import animation
from . import camera
from . import explosions from . import explosions
from . import etlight from . import etlight
from . import logger from . import logger
@ -52,6 +53,7 @@ class Exporter:
self.light = rtlight.LightConverter(self) self.light = rtlight.LightConverter(self)
self.animation = animation.AnimationConverter(self) self.animation = animation.AnimationConverter(self)
self.sumfile = sumfile.SumFile() self.sumfile = sumfile.SumFile()
self.camera = camera.CameraConverter(self)
# Step 0.8: Init the progress mgr # Step 0.8: Init the progress mgr
self.mesh.add_progress_presteps(self.report) self.mesh.add_progress_presteps(self.report)
@ -236,6 +238,11 @@ class Exporter:
mod.export(self, bl_obj, sceneobject) mod.export(self, bl_obj, sceneobject)
inc_progress() 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): 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 # We don't need to do anything here. This function just makes sure we don't error out
# or add a silly special case :( # or add a silly special case :(

3
korman/idprops.py

@ -124,6 +124,9 @@ def poll_animated_objects(self, value):
return True return True
return False return False
def poll_camera_objects(self, value):
return value.type == "CAMERA"
def poll_empty_objects(self, value): def poll_empty_objects(self, value):
return value.type == "EMPTY" return value.type == "EMPTY"

2
korman/properties/__init__.py

@ -15,6 +15,7 @@
import bpy import bpy
from .prop_camera import *
from .prop_lamp import * from .prop_lamp import *
from . import modifiers from . import modifiers
from .prop_object import * from .prop_object import *
@ -23,6 +24,7 @@ from .prop_world import *
def register(): def register():
bpy.types.Camera.plasma_camera = bpy.props.PointerProperty(type=PlasmaCamera)
bpy.types.Lamp.plasma_lamp = bpy.props.PointerProperty(type=PlasmaLamp) bpy.types.Lamp.plasma_lamp = bpy.props.PointerProperty(type=PlasmaLamp)
bpy.types.Object.plasma_net = bpy.props.PointerProperty(type=PlasmaNet) bpy.types.Object.plasma_net = bpy.props.PointerProperty(type=PlasmaNet)
bpy.types.Object.plasma_object = bpy.props.PointerProperty(type=PlasmaObject) bpy.types.Object.plasma_object = bpy.props.PointerProperty(type=PlasmaObject)

61
korman/properties/modifiers/region.py

@ -22,6 +22,7 @@ from ...helpers import TemporaryObject
from ... import idprops from ... import idprops
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from ..prop_camera import PlasmaCameraProperties
from .physics import bounds_types from .physics import bounds_types
footstep_surface_ids = { footstep_surface_ids = {
@ -58,6 +59,66 @@ footstep_surfaces = [("dirt", "Dirt", "Dirt"),
("woodfloor", "Wood Floor", "Wood Floor"), ("woodfloor", "Wood Floor", "Wood Floor"),
("woodladder", "Wood Ladder", "Wood Ladder")] ("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): class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
pl_id = "footstep" pl_id = "footstep"

160
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 <http://www.gnu.org/licenses/>.
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())

4
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") properties_material.MATERIAL_PT_shadow.COMPAT_ENGINES.add("PLASMA_GAME")
del properties_material 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 from bl_ui import properties_data_mesh
properties_data_mesh.DATA_PT_uv_texture.COMPAT_ENGINES.add("PLASMA_GAME") properties_data_mesh.DATA_PT_uv_texture.COMPAT_ENGINES.add("PLASMA_GAME")
properties_data_mesh.DATA_PT_vertex_colors.COMPAT_ENGINES.add("PLASMA_GAME") properties_data_mesh.DATA_PT_vertex_colors.COMPAT_ENGINES.add("PLASMA_GAME")

1
korman/ui/__init__.py

@ -13,6 +13,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
from .ui_camera import *
from .ui_lamp import * from .ui_lamp import *
from .ui_list import * from .ui_list import *
from .ui_menus import * from .ui_menus import *

8
korman/ui/modifiers/region.py

@ -14,6 +14,14 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy 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): def footstep(modifier, layout, context):
layout.prop(modifier, "bounds") layout.prop(modifier, "bounds")

128
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 <http://www.gnu.org/licenses/>.
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")
Loading…
Cancel
Save