Browse Source

Merge pull request #395 from Hoikas/entry_cam

Entry Cameras for the Spawn Point Modifier
pull/256/merge
Adam Johnson 3 months ago committed by GitHub
parent
commit
29b03a5f08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 52
      korman/exporter/utils.py
  2. 32
      korman/properties/modifiers/base.py
  3. 90
      korman/properties/modifiers/logic.py
  4. 19
      korman/ui/modifiers/logic.py

52
korman/exporter/utils.py

@ -20,6 +20,7 @@ import bpy
import mathutils
from contextlib import contextmanager
import enum
from typing import *
from PyHSPlasma import *
@ -122,21 +123,54 @@ def create_camera_object(name: str) -> bpy.types.Object:
bpy.context.scene.objects.link(cam_obj)
return cam_obj
def create_cube_region(name: str, size: float, owner_object: bpy.types.Object) -> bpy.types.Object:
class RegionOrigin(enum.Enum):
center = enum.auto()
bottom = enum.auto()
def create_cube_region(name: str, size: float, owner_object: bpy.types.Object, origin: RegionOrigin = RegionOrigin.center) -> bpy.types.Object:
"""Create a cube shaped region object"""
# Proxy over to the generic rectangular prism implementation to
# ensure that it is well tested.
return create_box_region(name, (size, size, size), owner_object, origin)
def create_box_region(
name: str, size: Union[Tuple[float, float, float], mathutils.Vector],
owner_object: bpy.types.Object,
origin: RegionOrigin = RegionOrigin.center
) -> bpy.types.Object:
"""Create a rectangular prism region object"""
if not isinstance(size, mathutils.Vector):
size = mathutils.Vector(size)
region_object = BMeshObject(name)
region_object.plasma_object.enabled = True
region_object.plasma_object.page = owner_object.plasma_object.page
region_object.hide_render = True
with region_object as bm:
bmesh.ops.create_cube(bm, size=(size))
bmesh.ops.transform(
bm,
matrix=mathutils.Matrix.Translation(
owner_object.matrix_world.translation - region_object.matrix_world.translation
),
space=region_object.matrix_world, verts=bm.verts
)
center = owner_object.matrix_world.translation
if origin == RegionOrigin.bottom:
center = center.copy()
center.z += size.z * 0.5
vert_src = [
(center.x + size.x * 0.5, center.y + size.y * 0.5, center.z + size.z * 0.5),
(center.x + size.x * 0.5, center.y + size.y * 0.5, center.z - size.z * 0.5),
(center.x + size.x * 0.5, center.y - size.y * 0.5, center.z + size.z * 0.5),
(center.x + size.x * 0.5, center.y - size.y * 0.5, center.z - size.z * 0.5),
(center.x - size.x * 0.5, center.y + size.y * 0.5, center.z + size.z * 0.5),
(center.x - size.x * 0.5, center.y + size.y * 0.5, center.z - size.z * 0.5),
(center.x - size.x * 0.5, center.y - size.y * 0.5, center.z + size.z * 0.5),
(center.x - size.x * 0.5, center.y - size.y * 0.5, center.z - size.z * 0.5),
]
verts = [bm.verts.new(i) for i in vert_src]
new_face = bm.faces.new
new_face((verts[0], verts[1], verts[3], verts[2])) # X+
new_face((verts[4], verts[5], verts[7], verts[6])) # X-
new_face((verts[0], verts[1], verts[5], verts[4])) # Y+
new_face((verts[2], verts[3], verts[7], verts[6])) # Y-
new_face((verts[0], verts[2], verts[6], verts[4])) # Z+
new_face((verts[1], verts[3], verts[7], verts[5])) # Z-
return region_object.release()
@contextmanager

32
korman/properties/modifiers/base.py

@ -13,12 +13,17 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
from bpy.props import *
import abc
import itertools
from typing import Any, Dict, FrozenSet, Optional
from typing import *
if TYPE_CHECKING:
from ...nodes.node_python import *
from ... import helpers
@ -202,24 +207,23 @@ class PlasmaModifierLogicWiz:
pfm_node.update()
return pfm_node
def _create_python_attribute(self, pfm_node, attribute_name: str, attribute_type: Optional[str] = None, **kwargs):
def _create_python_attribute(self, pfm_node: PlasmaPythonFileNode, attribute_name: str, **kwargs):
"""Creates and links a Python Attribute Node to the Python File Node given by `pfm_node`.
This will automatically handle simple attribute types such as numbers and strings, however,
for object linkage, you should specify the optional `attribute_type` to ensure the proper
attribute type is found. For attribute nodes that require multiple values, the `value` may
be set to None and handled in your code."""
from ...nodes.node_python import PlasmaAttribute, PlasmaAttribNodeBase
if attribute_type is None:
assert len(kwargs) == 1 and "value" in kwargs, \
"In order to deduce the attribute_type, exactly one attribute value must be passed as a kw named `value`"
attribute_type = PlasmaAttribute.type_LUT.get(kwargs["value"].__class__)
For attribute nodes that require multiple values, the `value` may be set to None and
handled in your code."""
attribute_defn = next((i for i in pfm_node.attributes if i.attribute_name == attribute_name), None)
if attribute_defn is None:
raise KeyError(attribute_name)
from ...nodes.node_python import PlasmaAttribNodeBase
attribute_type = attribute_defn.attribute_type
node_cls = next((i for i in PlasmaAttribNodeBase.__subclasses__() if attribute_type in i.pl_attrib), None)
assert node_cls is not None, "'{}': Unable to find attribute node type for '{}' ('{}')".format(
self.id_data.name, attribute_name, attribute_type
)
assert node_cls is not None, \
f"'{self.id_data.name}': Unable to find attribute node type for '{attribute_name}' ('{attribute_type}')"
node = pfm_node.id_data.nodes.new(node_cls.bl_idname)
node.link_output(pfm_node, "pfm", attribute_name)
assert kwargs
for i, j in kwargs.items():
setattr(node, i, j)
return node

90
korman/properties/modifiers/logic.py

@ -13,16 +13,35 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bmesh
import bpy
from bpy.props import *
import mathutils
from PyHSPlasma import *
from typing import *
if TYPE_CHECKING:
from ...exporter import Exporter
from ...nodes.node_conditions import *
from ...nodes.node_messages import *
from ...nodes.node_responder import *
from ...addon_prefs import game_versions
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from ...exporter import ExportError, utils
from ... import idprops
from .physics import bounds_type_index, bounds_type_str, bounds_types
entry_cam_pfm = {
"filename": "xEntryCam.py",
"attribs": (
{ 'id': 1, 'type': "ptAttribActivator", 'name': "actRegionSensor" },
{ 'id': 2, 'type': "ptAttribSceneobject", 'name': "camera" },
{ 'id': 3, 'type': "ptAttribBoolean", 'name': "undoFirstPerson" },
)
}
class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup):
version = EnumProperty(name="Version",
@ -76,7 +95,7 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties):
return any((i.node_tree.requires_actor for i in self.logic_groups if i.node_tree))
class PlasmaSpawnPoint(PlasmaModifierProperties):
class PlasmaSpawnPoint(PlasmaModifierProperties, PlasmaModifierLogicWiz):
pl_id = "spawnpoint"
bl_category = "Logic"
@ -84,10 +103,73 @@ class PlasmaSpawnPoint(PlasmaModifierProperties):
bl_description = "Point at which avatars link into the Age"
bl_object_types = {"EMPTY"}
def _get_bounds(self) -> int:
if self.exit_region is not None:
return bounds_type_index(self.exit_region.plasma_modifiers.collision.bounds_type)
return bounds_type_index("hull")
def _set_bounds(self, value: int) -> None:
if self.exit_region is not None:
self.exit_region.plasma_modifiers.collision.bounds_type = bounds_type_str(value)
entry_camera = PointerProperty(
name="Entry Camera",
description="Camera to use when the player spawns at this location",
type=bpy.types.Object,
poll=idprops.poll_camera_objects
)
exit_region = PointerProperty(
name="Exit Region",
description="Pop the camera when the player exits this region",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects
)
bounds_type = EnumProperty(
name="Bounds",
description="",
items=bounds_types,
get=_get_bounds,
set=_set_bounds,
default="hull"
)
def pre_export(self, exporter: Exporter, bo: bpy.types.Object) -> None:
if self.entry_camera is None:
return
if self.exit_region is None:
self.exit_region = yield utils.create_box_region(
f"{self.key_name}_ExitRgn", (2.0, 2.0, 6.0),
bo, utils.RegionOrigin.bottom
)
yield self.convert_logic(bo)
def logicwiz(self, bo, tree):
pfm_node = self._create_python_file_node(
tree,
entry_cam_pfm["filename"],
entry_cam_pfm["attribs"]
)
volume_sensor: PlasmaVolumeSensorNode = tree.nodes.new("PlasmaVolumeSensorNode")
volume_sensor.find_input_socket("enter").allow = True
volume_sensor.find_input_socket("exit").allow = True
volume_sensor.region_object = self.exit_region
volume_sensor.bounds = self.bounds_type
volume_sensor.link_output(pfm_node, "satisfies", "actRegionSensor")
self._create_python_attribute(
pfm_node,
"camera",
target_object=self.entry_camera
)
def export(self, exporter, bo, so):
# Not much to this modifier... It's basically a flag that tells the engine, "hey, this is a
# place the avatar can show up." Nice to have a simple one to get started with.
spawn = exporter.mgr.add_object(pl=plSpawnModifier, so=so, name=self.key_name)
exporter.mgr.add_object(pl=plSpawnModifier, so=so, name=self.key_name)
@property
def requires_actor(self):

19
korman/ui/modifiers/logic.py

@ -13,8 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
from typing import *
if TYPE_CHECKING:
from ...properties.modifiers.logic import *
from .. import ui_list
class LogicListUI(bpy.types.UIList):
@ -36,8 +43,18 @@ def advanced_logic(modifier, layout, context):
layout.row().prop_menu_enum(logic, "version")
layout.prop(logic, "node_tree", icon="NODETREE")
def spawnpoint(modifier, layout, context):
def spawnpoint(modifier: PlasmaSpawnPoint, layout, context):
layout.label(text="Avatar faces negative Y.")
layout.separator()
col = layout.column()
col.prop(modifier, "entry_camera", icon="CAMERA_DATA")
sub = col.row()
sub.active = modifier.entry_camera is not None
sub.prop(modifier, "exit_region", icon="MESH_DATA")
sub = col.row()
sub.active = modifier.entry_camera is not None and modifier.exit_region is not None
sub.prop(modifier, "bounds_type")
def maintainersmarker(modifier, layout, context):
layout.label(text="Positive Y is North, positive Z is up.")

Loading…
Cancel
Save