Browse Source

Merge pull request #175 from Hoikas/fix_phys_xform

Fix #101
pull/176/head
Adam Johnson 4 years ago committed by GitHub
parent
commit
c98f3fe919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 132
      korman/exporter/physics.py
  2. 39
      korman/nodes/node_conditions.py
  3. 23
      korman/nodes/node_logic.py
  4. 10
      korman/properties/modifiers/avatar.py
  5. 35
      korman/properties/modifiers/physics.py
  6. 21
      korman/properties/modifiers/region.py
  7. 30
      korman/properties/modifiers/water.py

132
korman/exporter/physics.py

@ -30,6 +30,22 @@ def _set_phys_prop(prop, sim, phys, value=True):
class PhysicsConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
self._bounds_converters = {
"box": self._export_box,
"sphere": self._export_sphere,
"hull": self._export_hull,
"trimesh": self._export_trimesh,
}
def _apply_props(self, simIface, physical, props):
for i in props.get("properties", []):
_set_phys_prop(getattr(plSimulationInterface, i), simIface, physical)
for i in props.get("losdbs", []):
physical.LOSDBs |= getattr(plSimDefs, i)
for i in props.get("report_groups", []):
physical.reportGroup |= 1 << getattr(plSimDefs, i)
for i in props.get("collide_groups", []):
physical.collideGroup |= 1 << getattr(plSimDefs, i)
def _convert_indices(self, mesh):
indices = []
@ -42,7 +58,7 @@ class PhysicsConverter:
indices += (v[0], v[2], v[3],)
return indices
def _convert_mesh_data(self, bo, physical, indices=True, mesh_func=None):
def _convert_mesh_data(self, bo, physical, local_space, indices=True, mesh_func=None):
try:
mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False)
except:
@ -54,8 +70,7 @@ class PhysicsConverter:
if mesh_func is not None:
mesh_func(mesh)
# We can only use the plPhysical xforms if there is a CI...
if self._exporter().has_coordiface(bo):
if local_space:
mesh.update(calc_tessface=indices)
physical.pos = hsVector3(*mat.to_translation())
physical.rot = utils.quaternion(mat.to_quaternion())
@ -79,12 +94,11 @@ class PhysicsConverter:
else:
return vertices
def generate_flat_proxy(self, bo, so, z_coord=None, name=None):
def generate_flat_proxy(self, bo, so, **kwargs):
"""Generates a flat physical object"""
if so.sim is None:
if name is None:
name = bo.name
z_coord = kwargs.pop("z_coord", None)
if so.sim is None:
simIface = self._mgr.add_object(pl=plSimulationInterface, bl=bo)
physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo, name=name)
@ -94,7 +108,7 @@ class PhysicsConverter:
mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False)
with TemporaryObject(mesh, bpy.data.meshes.remove):
# We will apply all xform, seeing as how this is a special case...
# No mass and no emedded xform, so we force worldspace collision.
mesh.transform(bo.matrix_world)
mesh.update(calc_tessface=True)
@ -111,22 +125,36 @@ class PhysicsConverter:
physical.verts = vertices
physical.indices = self._convert_indices(mesh)
physical.boundsType = plSimDefs.kProxyBounds
group_name = kwargs.get("member_group")
if group_name:
physical.memberGroup = getattr(plSimDefs, group_name)
else:
simIface = so.sim.object
physical = simIface.physical.object
if name is not None:
physical.key.name = name
return (simIface, physical)
def generate_physical(self, bo, so, bounds, name=None):
"""Generates a physical object for the given object pair"""
member_group = getattr(plSimDefs, kwargs.get("member_group", "kGroupLOSOnly"))
if physical.memberGroup != member_group and member_group != plSimDefs.kGroupLOSOnly:
self._report.warn("{}: Physical memberGroup overwritten!", bo.name)
physical.memberGroup = member_group
self._apply_props(simIface, physical, kwargs)
def generate_physical(self, bo, so, **kwargs):
"""Generates a physical object for the given object pair.
The following optional arguments are allowed:
- bounds: (defaults to collision modifier setting)
- member_group: str attribute of plSimDefs, defaults to kGroupStatic
NOTE that kGroupLOSOnly generation will only succeed if no one else
has generated this physical in another group
- properties: sequence of str bit names from plSimulationInterface
- losdbs: sequence of str bit names from plSimDefs
- report_groups: sequence of str bit names from plSimDefs
- collide_groups: sequence of str bit names from plSimDefs
"""
if so.sim is None:
if name is None:
name = bo.name
simIface = self._mgr.add_object(pl=plSimulationInterface, bl=bo)
physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo, name=name)
physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo)
ver = self._mgr.getVer()
simIface.physical = physical.key
physical.object = so.key
@ -137,6 +165,37 @@ class PhysicsConverter:
if self.is_dedicated_subworld(subworld, sanity_check=False):
physical.subWorld = self._mgr.find_create_key(plHKSubWorld, bl=subworld)
# Export the collision modifier here since we like stealing from it anyway.
mod = bo.plasma_modifiers.collision
bounds = kwargs.get("bounds", mod.bounds)
if mod.enabled:
physical.friction = mod.friction
physical.restitution = mod.restitution
if mod.dynamic:
if ver <= pvPots:
physical.collideGroup = (1 << plSimDefs.kGroupDynamic) | \
(1 << plSimDefs.kGroupStatic)
physical.memberGroup = plSimDefs.kGroupDynamic
physical.mass = mod.mass
_set_phys_prop(plSimulationInterface.kStartInactive, simIface, physical,
value=mod.start_asleep)
elif not mod.avatar_blocker:
physical.memberGroup = plSimDefs.kGroupLOSOnly
else:
physical.memberGroup = plSimDefs.kGroupStatic
# Line of Sight DB
if mod.camera_blocker:
physical.LOSDBs |= plSimDefs.kLOSDBCameraBlockers
_set_phys_prop(plSimulationInterface.kCameraAvoidObject, simIface, physical)
if mod.terrain:
physical.LOSDBs |= plSimDefs.kLOSDBAvatarWalkable
else:
group_name = kwargs.get("member_group")
if group_name:
physical.memberGroup = getattr(plSimDefs, group_name)
# Ensure this thing is set up properly for animations.
# This was previously the collision modifier's postexport method, but that
# would miss cases where we have animated detectors (subworlds!!!)
@ -147,7 +206,6 @@ class PhysicsConverter:
yield bo
bo = bo.parent
ver = self._mgr.getVer()
for i in _iter_object_tree(bo, ver == pvMoul):
if i.plasma_object.has_transform_animation:
tree_xformed = True
@ -176,23 +234,30 @@ class PhysicsConverter:
if physical.mass == 0.0:
physical.mass = 1.0
getattr(self, "_export_{}".format(bounds))(bo, physical)
if ver <= pvPots:
local_space = physical.mass > 0.0
else:
local_space = self._exporter().has_coordiface(bo)
self._bounds_converters[bounds](bo, physical, local_space)
else:
simIface = so.sim.object
physical = simIface.physical.object
if name is not None:
physical.key.name = name
return (simIface, physical)
member_group = getattr(plSimDefs, kwargs.get("member_group", "kGroupLOSOnly"))
if physical.memberGroup != member_group and member_group != plSimDefs.kGroupLOSOnly:
self._report.warn("{}: Physical memberGroup overwritten!", bo.name, indent=2)
physical.memberGroup = member_group
def _export_box(self, bo, physical):
self._apply_props(simIface, physical, kwargs)
def _export_box(self, bo, physical, local_space):
"""Exports box bounds based on the object"""
physical.boundsType = plSimDefs.kBoxBounds
vertices = self._convert_mesh_data(bo, physical, indices=False)
vertices = self._convert_mesh_data(bo, physical, local_space, indices=False)
physical.calcBoxBounds(vertices)
def _export_hull(self, bo, physical):
def _export_hull(self, bo, physical, local_space):
"""Exports convex hull bounds based on the object"""
physical.boundsType = plSimDefs.kHullBounds
@ -212,20 +277,21 @@ class PhysicsConverter:
bpy.ops.mesh.convex_hull(use_existing_faces=False)
bpy.ops.object.mode_set(mode="OBJECT")
physical.verts = self._convert_mesh_data(bo, physical, indices=False, mesh_func=_bake_hull)
physical.verts = self._convert_mesh_data(bo, physical, local_space, indices=False,
mesh_func=_bake_hull)
def _export_sphere(self, bo, physical):
def _export_sphere(self, bo, physical, local_space):
"""Exports sphere bounds based on the object"""
physical.boundsType = plSimDefs.kSphereBounds
vertices = self._convert_mesh_data(bo, physical, indices=False)
vertices = self._convert_mesh_data(bo, physical, local_space, indices=False)
physical.calcSphereBounds(vertices)
def _export_trimesh(self, bo, physical):
def _export_trimesh(self, bo, physical, local_space):
"""Exports an object's mesh as exact physical bounds"""
physical.boundsType = plSimDefs.kExplicitBounds
vertices, indices = self._convert_mesh_data(bo, physical)
vertices, indices = self._convert_mesh_data(bo, physical, local_space)
physical.verts = vertices
physical.indices = indices
@ -244,3 +310,7 @@ class PhysicsConverter:
@property
def _mgr(self):
return self._exporter().mgr
@property
def _report(self):
return self._exporter().report

39
korman/nodes/node_conditions.py

@ -91,17 +91,10 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
bounds = phys_mod.bounds if phys_mod.enabled else self.bounds
# The actual physical object that does the cursor LOS
made_the_phys = (clickable_so.sim is None)
phys_name = "{}_ClickableLOS".format(clickable_bo.name)
simIface, physical = exporter.physics.generate_physical(clickable_bo, clickable_so, bounds, phys_name)
simIface.setProperty(plSimulationInterface.kPinned, True)
physical.setProperty(plSimulationInterface.kPinned, True)
if made_the_phys:
# we assume that the collision modifier will do this if they want it to be intangible
physical.memberGroup = plSimDefs.kGroupLOSOnly
if physical.mass == 0.0:
physical.mass = 1.0
physical.LOSDBs |= plSimDefs.kLOSDBUIItems
exporter.physics.generate_physical(clickable_bo, clickable_so, bounds=bounds,
member_group="kGroupLOSOnly",
properties=["kPinned"],
losdbs=["kLOSDBUIItems"])
# Picking Detector -- detect when the physical is clicked
detector = self._find_create_object(plPickingDetector, exporter, bl=clickable_bo, so=clickable_so)
@ -195,10 +188,9 @@ class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.t
bounds = phys_mod.bounds if phys_mod.enabled else self.bounds
# Our physical is a detector and it only detects avatars...
phys_name = "{}_ClickableAvRegion".format(region_bo.name)
simIface, physical = exporter.physics.generate_physical(region_bo, region_so, bounds, phys_name)
physical.memberGroup = plSimDefs.kGroupDetector
physical.reportGroup |= 1 << plSimDefs.kGroupAvatar
exporter.physics.generate_physical(region_bo, region_so, bounds=bounds,
member_group="kGroupDetector",
report_groups=["kGroupAvatar"])
# I'm glad this crazy mess made sense to someone at Cyan...
# ObjectInVolumeDetector can notify multiple logic mods. This implies we could share this
@ -352,9 +344,9 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
report_on = EnumProperty(name="Triggerers",
description="What triggers this region?",
options={"ANIMATABLE", "ENUM_FLAG"},
items=[("avatar", "Avatars", "Avatars trigger this region"),
("dynamics", "Dynamics", "Any non-avatar dynamic physical object (eg kickables)")],
default={"avatar"})
items=[("kGroupAvatar", "Avatars", "Avatars trigger this region"),
("kGroupDynamic", "Dynamics", "Any non-avatar dynamic physical object (eg kickables)")],
default={"kGroupAvatar"})
input_sockets = OrderedDict([
("enter", {
@ -436,14 +428,9 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
interface.addIntfKey(key)
# Don't forget to export the physical object itself!
# [trollface.jpg]
simIface, physical = exporter.physics.generate_physical(region_bo, region_so, self.bounds, "{}_VolumeSensor".format(region_bo.name))
physical.memberGroup = plSimDefs.kGroupDetector
if "avatar" in self.report_on:
physical.reportGroup |= 1 << plSimDefs.kGroupAvatar
if "dynamics" in self.report_on:
physical.reportGroup |= 1 << plSimDefs.kGroupDynamic
exporter.physics.generate_physical(region_bo, region_so, bounds=self.bounds,
member_group="kGroupDetector",
report_groups=self.report_on)
def _export_volume_event(self, exporter, bo, so, event, settings):
if event == plVolumeSensorConditionalObject.kTypeEnter:

23
korman/nodes/node_logic.py

@ -19,7 +19,7 @@ from collections import OrderedDict
from PyHSPlasma import *
from .node_core import *
from ..properties.modifiers.physics import bounds_types, bounds_type_index
from ..properties.modifiers.physics import bounds_types, bounds_type_index, bounds_type_str
from .. import idprops
class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
@ -37,7 +37,7 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
return bounds_type_index("hull")
def _set_bounds(self, value):
if self.region_object is not None:
self.region_object.plasma_modifiers.collision.bounds = value
self.region_object.plasma_modifiers.collision.bounds = bounds_type_str(value)
region_object = PointerProperty(name="Region",
description="Region object's name",
@ -99,14 +99,17 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
excludergn.addSafePoint(exporter.mgr.find_create_key(plSceneObject, bl=safept))
# Ensure the region is exported
phys_name = "{}_XRgn".format(self.region_object.name)
simIface, physical = exporter.physics.generate_physical(self.region_object, region_so, self.bounds, phys_name)
simIface.setProperty(plSimulationInterface.kPinned, True)
physical.setProperty(plSimulationInterface.kPinned, True)
physical.LOSDBs |= plSimDefs.kLOSDBUIBlockers
if exporter.mgr.getVer() < pvMoul:
physical.memberGroup = plSimDefs.kGroupDetector
physical.collideGroup |= 1 << plSimDefs.kGroupDynamic
if exporter.mgr.getVer() <= pvPots:
member_group = "kGroupDetector"
collide_groups = ["kGroupDynamic"]
else:
member_group = "kGroupStatic"
collide_groups = []
exporter.physics.generate_physical(self.region_object, region_so, bounds=self.bounds,
properties=["kPinned"],
losdbs=["kLOSDBUIBlockers"],
member_group=member_group,
collide_groups=collide_groups)
@property
def export_once(self):

10
korman/properties/modifiers/avatar.py

@ -72,15 +72,9 @@ class PlasmaLadderModifier(PlasmaModifierProperties):
mod.ladderView.normalize()
# Generate the detector's physical bounds
det_name = "{}_LadderDetector".format(self.id_data.name)
bounds = "hull" if not bo.plasma_modifiers.collision.enabled else bo.plasma_modifiers.collision.bounds
simIface, physical = exporter.physics.generate_physical(bo, so, bounds, det_name)
physical.memberGroup = plSimDefs.kGroupDetector
physical.reportGroup |= 1 << plSimDefs.kGroupAvatar
physical.setProperty(plSimulationInterface.kPinned, True)
simIface.setProperty(plSimulationInterface.kPinned, True)
if physical.mass == 0.0:
physical.mass = 1.0
exporter.physics.generate_physical(bo, so, bounds=bounds, member_group="kGroupDetector",
report_groups=["kGroupAvatar"], properties=["kPinned"])
@property
def requires_actor(self):

35
korman/properties/modifiers/physics.py

@ -32,6 +32,9 @@ bounds_types = (
def bounds_type_index(key):
return list(zip(*bounds_types))[0].index(key)
def bounds_type_str(idx):
return bounds_types[idx][0]
def _set_phys_prop(prop, sim, phys, value=True):
"""Sets properties on plGenericPhysical and plSimulationInterface (seeing as how they are duped)"""
sim.setProperty(prop, value)
@ -60,36 +63,8 @@ class PlasmaCollider(PlasmaModifierProperties):
start_asleep = BoolProperty(name="Start Asleep", description="Object is not active until influenced by another object", default=False)
def export(self, exporter, bo, so):
simIface, physical = exporter.physics.generate_physical(bo, so, self.bounds, self.key_name)
# Common props
physical.friction = self.friction
physical.restitution = self.restitution
# Collision groups and such
if self.dynamic:
if exporter.mgr.getVer() < pvMoul:
physical.collideGroup = (1 << plSimDefs.kGroupDynamic) | (1 << plSimDefs.kGroupStatic)
physical.memberGroup = plSimDefs.kGroupDynamic
physical.mass = self.mass
_set_phys_prop(plSimulationInterface.kStartInactive, simIface, physical, value=self.start_asleep)
elif not self.avatar_blocker:
# the UI is kind of misleading on this count. oh well.
physical.memberGroup = plSimDefs.kGroupLOSOnly
else:
physical.memberGroup = plSimDefs.kGroupStatic
# Line of Sight DB
if self.camera_blocker:
physical.LOSDBs |= plSimDefs.kLOSDBCameraBlockers
# This appears to be dead in CWE, but we'll set it anyway
_set_phys_prop(plSimulationInterface.kCameraAvoidObject, simIface, physical)
if self.terrain:
physical.LOSDBs |= plSimDefs.kLOSDBAvatarWalkable
@property
def key_name(self):
return "{}_Collision".format(self.id_data.name)
# All modifier properties are examined by this little stinker...
exporter.physics.generate_physical(bo, so)
@property
def requires_actor(self):

21
korman/properties/modifiers/region.py

@ -97,11 +97,9 @@ class PlasmaCameraRegion(PlasmaModifierProperties):
# 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)
exporter.physics.generate_physical(bo, so, member_group="kGroupDetector",
report_groups=["kGroupAvatar"],
properties=["kPinned"])
# I don't feel evil enough to make this generate a logic tree...
msg = plCameraMsg()
@ -188,12 +186,8 @@ class PlasmaPanicLinkRegion(PlasmaModifierProperties):
default=True)
def export(self, exporter, bo, so):
phys_mod = bo.plasma_modifiers.collision
simIface, physical = exporter.physics.generate_physical(bo, so, phys_mod.bounds, self.key_name)
# Now setup the region detector properties
physical.memberGroup = plSimDefs.kGroupDetector
physical.reportGroup = 1 << plSimDefs.kGroupAvatar
exporter.physics.generate_physical(bo, so, member_group="kGroupDetector",
report_groups=["kGroupAvatar"])
# Finally, the panic link region proper
reg = exporter.mgr.add_object(plPanicLinkRegion, name=self.key_name, so=so)
@ -375,6 +369,5 @@ class PlasmaSubworldRegion(PlasmaModifierProperties):
raise ExportAssertionError()
# Fancy pants region collider type shit
simIface, physical = exporter.physics.generate_physical(bo, so, self.id_data.plasma_modifiers.collision.bounds, self.key_name)
physical.memberGroup = plSimDefs.kGroupDetector
physical.reportGroup |= 1 << plSimDefs.kGroupAvatar
exporter.physics.generate_physical(bo, so, member_group="kGroupDetector",
report_groups=["kGroupAvatar"])

30
korman/properties/modifiers/water.py

@ -111,28 +111,26 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
# the surface of the water BUT wave sets are supposed to conform to the bottom of the
# pool. Therefore, we need to flatten out a temporary mesh in that case.
# Ohey! CWE doesn't let you swim at all if the surface isn't flat...
swim_phys_name = "{}_SwimSurfaceLOS".format(bo.name)
losdbs = ["kLOSDBSwimRegion"]
member_group = "kGroupLOSOnly" if exporter.mgr.getVer() != pvMoul else "kGroupStatic"
if bo.plasma_modifiers.water_basic.enabled:
simIface, physical = exporter.physics.generate_flat_proxy(bo, so, bo.location[2], swim_phys_name)
exporter.physics.generate_flat_proxy(bo, so, z_coord=bo.location[2],
member_group=member_group,
losdbs=losdbs)
else:
simIface, physical = exporter.physics.generate_physical(bo, so, "trimesh", swim_phys_name)
physical.LOSDBs |= plSimDefs.kLOSDBSwimRegion
if exporter.mgr.getVer() != pvMoul:
physical.memberGroup = plSimDefs.kGroupLOSOnly
exporter.physics.generate_physical(bo, so, bounds="trimesh",
member_group=member_group,
losdbs=losdbs)
# Detector region bounds
if self.region is not None:
region_so = exporter.mgr.find_create_object(plSceneObject, bl=self.region)
# Good news: if this phys has already been exported, this is basically a noop
det_name = "{}_SwimDetector".format(self.region.name)
bounds = self.region.plasma_modifiers.collision.bounds
simIface, physical = exporter.physics.generate_physical(self.region, region_so, bounds, det_name)
if exporter.mgr.getVer() == pvMoul:
physical.memberGroup = plSimDefs.kGroupDetector
else:
physical.memberGroup = plSimDefs.kGroupLOSOnly
physical.reportGroup |= 1 << plSimDefs.kGroupAvatar
member_group = "kGroupDetector" if exporter.mgr.getVer() == "pvMoul" else "kGroupLOSOnly"
exporter.physics.generate_physical(self.region, region_so,
member_group=member_group,
report_groups=["kGroupAvatar"])
# I am a little concerned if we already have a plSwimDetector... I am not certain how
# well Plasma would tolerate having a plSwimMsg with multiple regions referenced.
@ -140,7 +138,7 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
# What? Me test it?
# I are chicken.
# Mmmmm chicken ***drool***
if exporter.mgr.find_key(plSwimDetector, name=det_name, so=region_so) is None:
if exporter.mgr.find_key(plSwimDetector, so=region_so) is None:
enter_msg, exit_msg = plSwimMsg(), plSwimMsg()
for i in (enter_msg, exit_msg):
i.BCastFlags = plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
@ -149,7 +147,7 @@ class PlasmaSwimRegion(idprops.IDPropObjectMixin, PlasmaModifierProperties, bpy.
enter_msg.isEntering = True
exit_msg.isEntering = False
detector = exporter.mgr.add_object(plSwimDetector, name=det_name, so=region_so)
detector = exporter.mgr.add_object(plSwimDetector, so=region_so)
detector.enterMsg = enter_msg
detector.exitMsg = exit_msg
else:

Loading…
Cancel
Save