Browse Source

Merge pull request #395 from Hoikas/entry_cam

Entry Cameras for the Spawn Point Modifier
Adam Johnson 10 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 import mathutils
from contextlib import contextmanager from contextlib import contextmanager
import enum
from typing import * from typing import *
from PyHSPlasma import * from PyHSPlasma import *
@ -122,21 +123,54 @@ def create_camera_object(name: str) -> bpy.types.Object:
bpy.context.scene.objects.link(cam_obj) bpy.context.scene.objects.link(cam_obj)
return 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""" """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 = BMeshObject(name)
region_object.plasma_object.enabled = True region_object.plasma_object.enabled = True
region_object.plasma_object.page = owner_object.plasma_object.page region_object.plasma_object.page = owner_object.plasma_object.page
region_object.hide_render = True region_object.hide_render = True
with region_object as bm: with region_object as bm:
bmesh.ops.create_cube(bm, size=(size)) center = owner_object.matrix_world.translation
bmesh.ops.transform( if origin == RegionOrigin.bottom:
bm, center = center.copy()
matrix=mathutils.Matrix.Translation( center.z += size.z * 0.5
owner_object.matrix_world.translation - region_object.matrix_world.translation vert_src = [
), (center.x + size.x * 0.5, center.y + size.y * 0.5, center.z + size.z * 0.5),
space=region_object.matrix_world, verts=bm.verts (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() return region_object.release()
@contextmanager @contextmanager

32
korman/properties/modifiers/base.py

@ -13,12 +13,17 @@
# 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 __future__ import annotations
import bpy import bpy
from bpy.props import * from bpy.props import *
import abc import abc
import itertools import itertools
from typing import Any, Dict, FrozenSet, Optional from typing import *
if TYPE_CHECKING:
from ...nodes.node_python import *
from ... import helpers from ... import helpers
@ -202,24 +207,23 @@ class PlasmaModifierLogicWiz:
pfm_node.update() pfm_node.update()
return pfm_node 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`. """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 attribute nodes that require multiple values, the `value` may be set to None and
for object linkage, you should specify the optional `attribute_type` to ensure the proper handled in your code."""
attribute type is found. For attribute nodes that require multiple values, the `value` may attribute_defn = next((i for i in pfm_node.attributes if i.attribute_name == attribute_name), None)
be set to None and handled in your code.""" if attribute_defn is None:
from ...nodes.node_python import PlasmaAttribute, PlasmaAttribNodeBase raise KeyError(attribute_name)
if attribute_type is None:
assert len(kwargs) == 1 and "value" in kwargs, \ from ...nodes.node_python import PlasmaAttribNodeBase
"In order to deduce the attribute_type, exactly one attribute value must be passed as a kw named `value`" attribute_type = attribute_defn.attribute_type
attribute_type = PlasmaAttribute.type_LUT.get(kwargs["value"].__class__)
node_cls = next((i for i in PlasmaAttribNodeBase.__subclasses__() if attribute_type in i.pl_attrib), None) 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( assert node_cls is not None, \
self.id_data.name, attribute_name, attribute_type 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 = pfm_node.id_data.nodes.new(node_cls.bl_idname)
node.link_output(pfm_node, "pfm", attribute_name) node.link_output(pfm_node, "pfm", attribute_name)
assert kwargs
for i, j in kwargs.items(): for i, j in kwargs.items():
setattr(node, i, j) setattr(node, i, j)
return node return node

90
korman/properties/modifiers/logic.py

@ -13,16 +13,35 @@
# 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 __future__ import annotations
import bmesh import bmesh
import bpy import bpy
from bpy.props import * from bpy.props import *
import mathutils import mathutils
from PyHSPlasma import * 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 ...addon_prefs import game_versions
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from ...exporter import ExportError, utils from ...exporter import ExportError, utils
from ... import idprops 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): class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup):
version = EnumProperty(name="Version", 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)) 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" pl_id = "spawnpoint"
bl_category = "Logic" bl_category = "Logic"
@ -84,10 +103,73 @@ class PlasmaSpawnPoint(PlasmaModifierProperties):
bl_description = "Point at which avatars link into the Age" bl_description = "Point at which avatars link into the Age"
bl_object_types = {"EMPTY"} 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): def export(self, exporter, bo, so):
# Not much to this modifier... It's basically a flag that tells the engine, "hey, this is a exporter.mgr.add_object(pl=plSpawnModifier, so=so, name=self.key_name)
# 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)
@property @property
def requires_actor(self): 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 # 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 __future__ import annotations
import bpy import bpy
from typing import *
if TYPE_CHECKING:
from ...properties.modifiers.logic import *
from .. import ui_list from .. import ui_list
class LogicListUI(bpy.types.UIList): class LogicListUI(bpy.types.UIList):
@ -36,8 +43,18 @@ def advanced_logic(modifier, layout, context):
layout.row().prop_menu_enum(logic, "version") layout.row().prop_menu_enum(logic, "version")
layout.prop(logic, "node_tree", icon="NODETREE") 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.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): def maintainersmarker(modifier, layout, context):
layout.label(text="Positive Y is North, positive Z is up.") layout.label(text="Positive Y is North, positive Z is up.")

Loading…
Cancel
Save