Browse Source

Implement subworlds

This commit includes some bonus improvements for animated parenting
situations that I discovered through sciencing the various MOUL and PotS
PRPs. Hopefully it all works as advertised.
pull/85/head
Adam Johnson 7 years ago
parent
commit
ccf78561f1
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 1
      korman/exporter/convert.py
  2. 63
      korman/exporter/physics.py
  3. 3
      korman/idprops.py
  4. 80
      korman/properties/modifiers/physics.py
  5. 71
      korman/properties/modifiers/region.py
  6. 10
      korman/properties/prop_object.py
  7. 5
      korman/ui/modifiers/physics.py
  8. 6
      korman/ui/modifiers/region.py

1
korman/exporter/convert.py

@ -307,6 +307,7 @@ class Exporter:
self.report.progress_advance()
self.report.progress_range = len(self._objects)
inc_progress = self.report.progress_increment
self.report.msg("\nPost-Processing SceneObjects...")
mat_mgr = self.mesh.material
for bl_obj in self._objects:

63
korman/exporter/physics.py

@ -18,10 +18,15 @@ import mathutils
from PyHSPlasma import *
import weakref
from .explosions import ExportAssertionError
from .explosions import ExportError, ExportAssertionError
from ..helpers import TemporaryObject
from . import utils
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)
phys.setProperty(prop, value)
class PhysicsConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
@ -123,6 +128,50 @@ class PhysicsConverter:
physical.object = so.key
physical.sceneNode = self._mgr.get_scene_node(bl=bo)
# Got subworlds?
subworld = bo.plasma_object.subworld
if self.is_dedicated_subworld(subworld, sanity_check=False):
physical.subWorld = self._mgr.find_create_key(plHKSubWorld, bl=subworld)
# 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!!!)
def _iter_object_tree(bo, stop_at_subworld):
while bo is not None:
if stop_at_subworld and self.is_dedicated_subworld(bo, sanity_check=False):
return
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
break
else:
tree_xformed = False
if tree_xformed:
bo_xformed = bo.plasma_object.has_transform_animation
# MOUL: only objects that have animation data are kPhysAnim
if ver != pvMoul or bo_xformed:
_set_phys_prop(plSimulationInterface.kPhysAnim, simIface, physical)
# PotS: objects inheriting parent animation only are not pinned
# MOUL: animated objects in subworlds are not pinned
if bo_xformed and (ver != pvMoul or subworld is None):
_set_phys_prop(plSimulationInterface.kPinned, simIface, physical)
# MOUL: child objects are kPassive
if ver == pvMoul and bo.parent is not None:
_set_phys_prop(plSimulationInterface.kPassive, simIface, physical)
# FilterCoordinateInterfaces are kPassive
if bo.plasma_object.ci_type == plFilterCoordInterface:
_set_phys_prop(plSimulationInterface.kPassive, simIface, physical)
# If the mass is zero, then we will fail to animate. Fix that.
if physical.mass == 0.0:
physical.mass = 1.0
getattr(self, "_export_{}".format(bounds))(bo, physical)
else:
simIface = so.sim.object
@ -176,6 +225,18 @@ class PhysicsConverter:
physical.verts = vertices
physical.indices = indices
def is_dedicated_subworld(self, bo, sanity_check=True):
"""Determines if a subworld object defines an alternate physics world"""
if bo is None:
return False
subworld_mod = bo.plasma_modifiers.subworld_def
if not subworld_mod.enabled:
if sanity_check:
raise ExportError("'{}' is not a subworld".format(bo.name))
else:
return False
return subworld_mod.is_dedicated_subworld(self._exporter())
@property
def _mgr(self):
return self._exporter().mgr

3
korman/idprops.py

@ -133,6 +133,9 @@ def poll_mesh_objects(self, value):
def poll_softvolume_objects(self, value):
return value.plasma_modifiers.softvolume.enabled
def poll_subworld_objects(self, value):
return value.plasma_modifiers.subworld_def.enabled
def poll_visregion_objects(self, value):
return value.plasma_modifiers.visregion.enabled

80
korman/properties/modifiers/physics.py

@ -18,6 +18,7 @@ from bpy.props import *
from PyHSPlasma import *
from .base import PlasmaModifierProperties
from ...exporter import ExportError
# These are the kinds of physical bounds Plasma can work with.
# This sequence is acceptable in any EnumProperty
@ -84,32 +85,6 @@ class PlasmaCollider(PlasmaModifierProperties):
if self.terrain:
physical.LOSDBs |= plSimDefs.kLOSDBAvatarWalkable
def _make_physical_movable(self, so):
sim = so.sim
if sim is not None:
sim = sim.object
phys = sim.physical.object
_set_phys_prop(plSimulationInterface.kPhysAnim, sim, phys)
# If the mass is zero, then we will fail to animate. Fix that.
if phys.mass == 0.0:
phys.mass = 1.0
# set kPinned so it doesn't fall through
_set_phys_prop(plSimulationInterface.kPinned, sim, phys)
# Do the same for child objects
for child in so.coord.object.children:
self._make_physical_movable(child.object)
def post_export(self, exporter, bo, so):
test_bo = bo
while test_bo is not None:
if test_bo.plasma_object.has_transform_animation:
self._make_physical_movable(so)
break
test_bo = test_bo.parent
@property
def key_name(self):
return "{}_Collision".format(self.id_data.name)
@ -117,3 +92,56 @@ class PlasmaCollider(PlasmaModifierProperties):
@property
def requires_actor(self):
return self.dynamic
class PlasmaSubworld(PlasmaModifierProperties):
pl_id = "subworld_def"
bl_category = "Physics"
bl_label = "Subworld"
bl_description = "Subworld definition"
bl_icon = "WORLD"
sub_type = EnumProperty(name="Subworld Type",
description="Specifies the physics strategy to use for this subworld",
items=[("auto", "Auto", "Korman will decide which physics strategy to use"),
("dynamicav", "Dynamic Avatar", "Allows the avatar to affected by dynamic physicals"),
("subworld", "Separate World", "Causes all objects to be placed in a separate physics simulation")],
default="auto",
options=set())
gravity = FloatVectorProperty(name="Gravity",
description="Subworld's gravity defined in feet per second squared",
size=3, default=(0.0, 0.0, -32.174), precision=3,
subtype="ACCELERATION", unit="ACCELERATION")
def export(self, exporter, bo, so):
if self.is_dedicated_subworld(exporter):
# NOTE to posterity... Cyan's PotS/Havok subworlds appear to have a
# plHKPhysical object that is set as LOSOnly convex hull. They appear to
# be a bounding box. PyPRP generated PRPs do not do this and work just fine,
# however, so this is probably just a quirk of the Havok-era PlasmaMAX
subworld = exporter.mgr.find_create_object(plHKSubWorld, so=so)
subworld.gravity = hsVector3(*self.gravity)
def is_dedicated_subworld(self, exporter):
if exporter.mgr.getVer() != pvMoul:
return True
if self.sub_type == "subworld":
return True
elif self.sub_type == "dynamicav":
return False
else:
return not self.property_unset("gravity")
def post_export(self, exporter, bo, so):
# It appears PotS does something really fancy with subworlds under the hood such that
# if you make a subworld that has collision, it will get into an infinite loop in
# plCoordinateInterface::IGetRoot. Not really sure why this happens (nor do I care),
# but we definitely don't want it to happen.
if bo.type != "EMPTY":
exporter.report.warn("Subworld '{}' is attached to a '{}'--this should be an empty.", bo.name, bo.type, indent=1)
if so.sim:
if exporter.mgr.getVer() > pvPots:
exporter.report.port("Subworld '{}' has physics data--this will cause PotS to crash.", bo.name, indent=1)
else:
raise ExportError("Subworld '{}' cannot have physics data (should be an empty).".format(bo.name))

71
korman/properties/modifiers/region.py

@ -17,7 +17,7 @@ import bpy
from bpy.props import *
from PyHSPlasma import *
from ...exporter import ExportError
from ...exporter import ExportError, ExportAssertionError
from ...helpers import TemporaryObject
from ... import idprops
@ -240,3 +240,72 @@ class PlasmaSoftVolume(idprops.IDPropMixin, PlasmaModifierProperties):
def _idprop_sources(self):
return {"node_tree_name": bpy.data.node_groups}
class PlasmaSubworldRegion(PlasmaModifierProperties):
pl_id = "subworld_rgn"
bl_category = "Region"
bl_label = "Subworld Region"
bl_description = "Subworld transition region"
subworld = PointerProperty(name="Subworld",
description="Subworld to transition into",
type=bpy.types.Object,
poll=idprops.poll_subworld_objects)
transition = EnumProperty(name="Transition",
description="When to transition to the new subworld",
items=[("enter", "On Enter", "Transition when the avatar enters the region"),
("exit", "On Exit", "Transition when the avatar exits the region")],
default="enter",
options=set())
def export(self, exporter, bo, so):
# Due to the fact that our subworld modifier can produce both RidingAnimatedPhysical
# and [HK|PX]Subworlds depending on the situation, this could get hairy, fast.
# Start by surveying the lay of the land.
from_sub, to_sub = bo.plasma_object.subworld, self.subworld
from_isded = exporter.physics.is_dedicated_subworld(from_sub)
to_isded = exporter.physics.is_dedicated_subworld(to_sub)
if 1:
def get_log_text(bo, isded):
main = "[Main World]" if bo is None else bo.name
sub = "Subworld" if isded or bo is None else "RidingAnimatedPhysical"
return main, sub
from_name, from_type = get_log_text(from_sub, from_isded)
to_name, to_type = get_log_text(to_sub, to_isded)
exporter.report.msg("Transition from '{}' ({}) to '{}' ({})",
from_name, from_type, to_name, to_type,
indent=2)
# I think the best solution here is to not worry about the excitement mentioned above.
# If we encounter anything truly interesting, we can fix it in CWE more easily IMO because
# the game actually knows more about the avatar's state than we do here in the exporter.
if to_isded or (from_isded and to_sub is None):
region = exporter.mgr.find_create_object(plSubworldRegionDetector, so=so)
if to_sub is not None:
region.subworld = exporter.mgr.find_create_key(plSceneObject, bl=to_sub)
region.onExit = self.transition == "exit"
else:
msg = plRideAnimatedPhysMsg()
msg.BCastFlags |= plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
msg.sender = so.key
msg.entering = to_sub is not None
# In Cyan's PlasmaMAX RAP detector, it acts as more of a traditional region
# that changes us over to a dynamic character controller on region enter and
# reverts on region exit. We're going for an approach that is backwards compatible
# with subworlds, so our enter/exit regions are separate. Here, enter/exit message
# corresponds with when we should trigger the transition.
region = exporter.mgr.find_create_object(plRidingAnimatedPhysicalDetector, so=so)
if self.transition == "enter":
region.enterMsg = msg
elif self.transition == "exit":
region.exitMsg = msg
else:
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

10
korman/properties/prop_object.py

@ -81,6 +81,16 @@ class PlasmaObject(bpy.types.PropertyGroup):
return {"location", "rotation_euler", "scale"} & data_paths
return False
@property
def subworld(self):
bo = self.id_data
while bo is not None:
if bo.plasma_modifiers.subworld_def.enabled:
return bo
else:
bo = bo.parent
return None
class PlasmaNet(bpy.types.PropertyGroup):
manual_sdl = BoolProperty(name="Override SDL",

5
korman/ui/modifiers/physics.py

@ -38,3 +38,8 @@ def collision(modifier, layout, context):
col = split.column()
col.active = modifier.dynamic
col.prop(modifier, "mass")
def subworld_def(modifier, layout, context):
layout.prop(modifier, "sub_type")
if modifier.sub_type != "dynamicav":
layout.prop(modifier, "gravity")

6
korman/ui/modifiers/region.py

@ -39,3 +39,9 @@ def softvolume(modifier, layout, context):
col = split.column()
col.prop(modifier, "invert")
col.prop(modifier, "soft_distance")
def subworld_rgn(modifier, layout, context):
layout.prop(modifier, "subworld")
collision_mod = modifier.id_data.plasma_modifiers.collision
layout.prop(collision_mod, "bounds")
layout.prop(modifier, "transition")

Loading…
Cancel
Save