Browse Source

Merge pull request #53 from Hoikas/swimming

Swim Regions
pull/54/head
Adam Johnson 8 years ago committed by GitHub
parent
commit
42f795b944
  1. 5
      korman/exporter/explosions.py
  2. 62
      korman/exporter/physics.py
  3. 6
      korman/nodes/node_python.py
  4. 152
      korman/properties/modifiers/water.py
  5. 39
      korman/ui/modifiers/water.py

5
korman/exporter/explosions.py

@ -23,6 +23,11 @@ class BlenderOptionNotSupportedError(ExportError):
super(ExportError, self).__init__("Unsupported Blender Option: '{}'".format(opt)) super(ExportError, self).__init__("Unsupported Blender Option: '{}'".format(opt))
class ExportAssertionError(ExportError):
def __init__(self):
super(ExportError, self).__init__("Assertion failed")
class TooManyUVChannelsError(ExportError): class TooManyUVChannelsError(ExportError):
def __init__(self, obj, mat): def __init__(self, obj, mat):
msg = "There are too many UV Textures on the material '{}' associated with object '{}'.".format( msg = "There are too many UV Textures on the material '{}' associated with object '{}'.".format(

62
korman/exporter/physics.py

@ -18,6 +18,7 @@ import mathutils
from PyHSPlasma import * from PyHSPlasma import *
import weakref import weakref
from .explosions import ExportAssertionError
from ..helpers import TemporaryObject from ..helpers import TemporaryObject
from . import utils from . import utils
@ -25,6 +26,17 @@ class PhysicsConverter:
def __init__(self, exporter): def __init__(self, exporter):
self._exporter = weakref.ref(exporter) self._exporter = weakref.ref(exporter)
def _convert_indices(self, mesh):
indices = []
for face in mesh.tessfaces:
v = face.vertices
if len(v) == 3:
indices += v
elif len(v) == 4:
indices += (v[0], v[1], v[2],)
indices += (v[0], v[2], v[3],)
return indices
def _convert_mesh_data(self, bo, physical, indices=True): def _convert_mesh_data(self, bo, physical, indices=True):
mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False) mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False)
mat = bo.matrix_world mat = bo.matrix_world
@ -51,18 +63,50 @@ class PhysicsConverter:
vertices = [hsVector3(i.co.x, i.co.y, i.co.z) for i in mesh.vertices] vertices = [hsVector3(i.co.x, i.co.y, i.co.z) for i in mesh.vertices]
if indices: if indices:
indices = [] return (vertices, self._convert_indices(mesh))
for face in mesh.tessfaces:
v = face.vertices
if len(v) == 3:
indices += v
elif len(v) == 4:
indices += (v[0], v[1], v[2],)
indices += (v[0], v[2], v[3],)
return (vertices, indices)
else: else:
return vertices return vertices
def generate_flat_proxy(self, bo, so, z_coord=None, name=None):
"""Generates a flat physical object"""
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)
simIface.physical = physical.key
physical.object = so.key
physical.sceneNode = self._mgr.get_scene_node(bl=bo)
mesh = bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False)
with TemporaryObject(mesh, bpy.data.meshes.remove) as mesh:
# We will apply all xform, seeing as how this is a special case...
mesh.transform(bo.matrix_world)
mesh.update(calc_tessface=True)
if z_coord is None:
# Ensure all vertices are coplanar
z_coords = [i.co.z for i in mesh.vertices]
delta = max(z_coords) - min(z_coords)
if delta > 0.0002:
raise ExportAssertionError()
vertices = [hsVector3(i.co.x, i.co.y, i.co.z) for i in mesh.vertices]
else:
# Flatten out all points to the given Z-coordinate
vertices = [hsVector3(i.co.x, i.co.y, z_coord) for i in mesh.vertices]
physical.verts = vertices
physical.indices = self._convert_indices(mesh)
physical.boundsType = plSimDefs.kProxyBounds
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): def generate_physical(self, bo, so, bounds, name=None):
"""Generates a physical object for the given object pair""" """Generates a physical object for the given object pair"""
if so.sim is None: if so.sim is None:

6
korman/nodes/node_python.py

@ -76,7 +76,8 @@ _attrib_key_types = {
"ptAttribGUIPopUpMenu": plFactory.ClassIndex("pfGUIPopUpMenu"), "ptAttribGUIPopUpMenu": plFactory.ClassIndex("pfGUIPopUpMenu"),
"ptAttribGUISkin": plFactory.ClassIndex("pfGUISkin"), "ptAttribGUISkin": plFactory.ClassIndex("pfGUISkin"),
"ptAttribWaveSet": plFactory.ClassIndex("plWaveSet7"), "ptAttribWaveSet": plFactory.ClassIndex("plWaveSet7"),
"ptAttribSwimCurrent": (plFactory.ClassIndex("plSwimCircularCurrentRegion"), "ptAttribSwimCurrent": (plFactory.ClassIndex("plSwimRegionInterface"),
plFactory.ClassIndex("plSwimCircularCurrentRegion"),
plFactory.ClassIndex("plSwimStraightCurrentRegion")), plFactory.ClassIndex("plSwimStraightCurrentRegion")),
"ptAttribClusterList": plFactory.ClassIndex("plClusterGroup"), "ptAttribClusterList": plFactory.ClassIndex("plClusterGroup"),
"ptAttribMaterialAnimation": plFactory.ClassIndex("plLayerAnimation"), "ptAttribMaterialAnimation": plFactory.ClassIndex("plLayerAnimation"),
@ -457,6 +458,9 @@ class PlasmaAttribObjectNode(PlasmaAttribNodeBase, bpy.types.Node):
agmod = exporter.mgr.find_create_key(plAGModifier, so=ref_so, name=anim.key_name) agmod = exporter.mgr.find_create_key(plAGModifier, so=ref_so, name=anim.key_name)
agmaster = exporter.mgr.find_create_key(plAGMasterModifier, so=ref_so, name=anim.key_name) agmaster = exporter.mgr.find_create_key(plAGMasterModifier, so=ref_so, name=anim.key_name)
return agmaster return agmaster
elif attrib == "ptAttribSwimCurrent":
swimregion = bo.plasma_modifiers.swimregion
return swimregion.get_key(exporter, ref_so)
elif attrib == "ptAttribWaveSet": elif attrib == "ptAttribWaveSet":
waveset = bo.plasma_modifiers.water_basic waveset = bo.plasma_modifiers.water_basic
if not waveset.enabled: if not waveset.enabled:

152
korman/properties/modifiers/water.py

@ -19,7 +19,157 @@ import math
from PyHSPlasma import * from PyHSPlasma import *
from .base import PlasmaModifierProperties from .base import PlasmaModifierProperties
from ...exporter import ExportError from ...exporter import ExportError, ExportAssertionError
class PlasmaSwimRegion(PlasmaModifierProperties, bpy.types.PropertyGroup):
pl_id = "swimregion"
bl_category = "Water"
bl_label = "Swimming Surface"
bl_description = "Surface that the avatar can swim on"
bl_icon = "MOD_WAVE"
_CURRENTS = {
"NONE": plSwimRegionInterface,
"CIRCULAR": plSwimCircularCurrentRegion,
"STRAIGHT": plSwimStraightCurrentRegion,
}
region_name = StringProperty(name="Region",
description="Swimming detector region",
options=set())
down_buoyancy = FloatProperty(name="Downward Buoyancy",
description="Distance the avatar sinks into the water",
min=0.0, max=100.0, default=3.0,
options=set())
up_buoyancy = FloatProperty(name="Up Buoyancy",
description="Distance the avatar rises up after sinking",
min=0.0, max=100.0, default=0.05,
options=set())
up_velocity = FloatProperty(name="Up Velcocity",
description="Rate at which the avatar rises",
min=0.0, max=100.0, default=3.0,
options=set())
current_type = EnumProperty(name="Water Current",
description="",
items=[("NONE", "None", "No current"),
("CIRCULAR", "Circular", "Circular current"),
("STRAIGHT", "Straight", "Straight current")],
options=set())
rotation = FloatProperty(name="Rotation",
description="Rate of rotation about the current object",
min=-100.0, max=100.0, default=1.0,
options=set())
near_distance = FloatProperty(name="Near Distance",
description="Maximum distance at which the current is at the Near Velocity rate",
min=0.0, max=10000.0, default=1.0,
options=set())
far_distance = FloatProperty(name="Far Distance",
description="Distance at which the current is at the Far Velocity rate",
min=0.0, max=10000.0, default=1.0,
options=set())
near_velocity = FloatProperty(name="Near Velocity",
description="Current velocity near the region center",
min=-100.0, max=100.0, default=0.0,
options=set())
far_velocity = FloatProperty(name="Far Velocity",
description="Current velocity far from the region center",
min=-100.0, max=100.0, default=0.0,
options=set())
current_object = StringProperty(name="Current Object",
description="Object whose Y-axis defines the direction of the current",
options=set())
def export(self, exporter, bo, so):
swimIface = self.get_key(exporter, so).object
swimIface.downBuoyancy = self.down_buoyancy
swimIface.upBuoyancy = self.up_buoyancy
swimIface.maxUpwardVel = self.up_velocity
if isinstance(swimIface, plSwimCircularCurrentRegion):
swimIface.rotation = self.rotation
swimIface.pullNearDistSq = pow(self.near_distance, 2)
swimIface.pullFarDistSq = pow(self.far_distance, 2)
swimIface.pullNearVel = self.near_velocity
swimIface.pullFarVel = self.far_velocity
elif isinstance(swimIface, plSwimStraightCurrentRegion):
swimIface.nearDist = self.near_distance
swimIface.farDist = self.far_distance
swimIface.nearVel = self.near_velocity
swimIface.farVel = self.far_velocity
if isinstance(swimIface, (plSwimCircularCurrentRegion, plSwimStraightCurrentRegion)):
if not self.current_object:
raise ExportError("Swimming Surface '{}' does not specify a current object".format(bo.name))
current_bo = bpy.data.objects.get(self.current_object, None)
if current_bo is None:
raise ExportError("Swimming Surface '{}' specifies an invalid current object '{}'".format(bo.name, self.current_object))
swimIface.currentObj = exporter.mgr.find_create_key(plSceneObject, bl=current_bo)
# The surface needs bounds for LOS -- this is generally a flat plane, or I would think...
# NOTE: If the artist has this on a WaveSet, they probably intend for the avatar to swim on
# 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)
if bo.plasma_modifiers.water_basic.enabled:
simIface, physical = exporter.physics.generate_flat_proxy(bo, so, bo.location[2], swim_phys_name)
else:
try:
simIface, physical = exporter.physics.generate_flat_proxy(bo, so, None, swim_phys_name)
except ExportAssertionError:
raise ExportError("Swimming Surface '{}' must be flat".format(bo.name))
physical.LOSDBs |= plSimDefs.kLOSDBSwimRegion
# Detector region bounds
if self.region_name:
region_bo = bpy.data.objects.get(self.region_name, None)
if region_bo is None:
raise ExportError("Swim Surface '{}' references invalid region '{}'".format(bo.name, self.region_name))
region_so = exporter.mgr.find_create_object(plSceneObject, bl=region_bo)
# Good news: if this phys has already been exported, this is basically a noop
det_name = "{}_SwimDetector".format(self.region_name)
bounds = region_bo.plasma_modifiers.collision.bounds
simIface, physical = exporter.physics.generate_physical(region_bo, region_so, bounds, det_name)
physical.memberGroup = plSimDefs.kGroupDetector
physical.reportGroup |= 1 << plSimDefs.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.
# If you're brave, maybe you will test this...
# What? Me test it?
# I are chicken.
# Mmmmm chicken ***drool***
if exporter.mgr.find_key(plSwimDetector, name=det_name, so=region_so) is None:
enter_msg, exit_msg = plSwimMsg(), plSwimMsg()
for i in (enter_msg, exit_msg):
i.BCastFlags = plMessage.kLocalPropagate | plMessage.kPropagateToModifiers
i.sender = region_so.key
i.swimRegion = swimIface.key
enter_msg.isEntering = True
exit_msg.isEntering = False
detector = exporter.mgr.add_object(plSwimDetector, name=det_name, so=region_so)
detector.enterMsg = enter_msg
detector.exitMsg = exit_msg
else:
# OK, I lied. It is perfectly legal to NOT have a detector... Think about Ahnonay,
# if you want to have currents inside of a large body of water, only your main
# swimming surface should have a detector. m'kay? But still, we might want to make note
# of this sitation. Just in case someone is like "WTF! Why am I not swimming?!?!1111111"
# Because you need to have a detector, dummy.
exporter.report.warn("Swimming Surface '{}' does not specify a detector region".format(bo.name), indent=2)
def get_key(self, exporter, so=None):
pClass = self._CURRENTS[self.current_type]
return exporter.mgr.find_create_key(pClass, bl=self.id_data, so=so)
def harvest_actors(self):
if self.current_type != "NONE" and self.current_object:
return set((self.current_object,))
return set()
class PlasmaWaterModifier(PlasmaModifierProperties, bpy.types.PropertyGroup): class PlasmaWaterModifier(PlasmaModifierProperties, bpy.types.PropertyGroup):
pl_id = "water_basic" pl_id = "water_basic"

39
korman/ui/modifiers/water.py

@ -15,6 +15,45 @@
import bpy import bpy
def swimregion(modifier, layout, context):
split = layout.split()
col = split.column()
col.label("Detector Region:")
col.prop_search(modifier, "region_name", bpy.data, "objects", text="")
region_bo = bpy.data.objects.get(modifier.region_name, None)
col = split.column()
col.enabled = region_bo is not None
bounds_src = region_bo if region_bo is not None else modifier.id_data
col.label("Detector Bounds:")
col.prop(bounds_src.plasma_modifiers.collision, "bounds", text="")
split = layout.split()
col = split.column(align=True)
col.label("Buoyancy:")
col.prop(modifier, "down_buoyancy", text="Down")
col.prop(modifier, "up_buoyancy", text="Up")
col = split.column()
col.label("Current:")
col.prop(modifier, "current_type", text="")
if modifier.current_type == "CIRCULAR":
col.prop(modifier, "rotation")
if modifier.current_type != "NONE":
split = layout.split()
col = split.column(align=True)
col.label("Distance:")
col.prop(modifier, "near_distance", text="Near")
col.prop(modifier, "far_distance", text="Far")
col = split.column(align=True)
col.label("Velocity:")
col.prop(modifier, "near_velocity", text="Near")
col.prop(modifier, "far_velocity", text="Far")
layout.prop_search(modifier, "current_object", bpy.data, "objects")
def water_basic(modifier, layout, context): def water_basic(modifier, layout, context):
layout.prop_search(modifier, "wind_object_name", bpy.data, "objects") layout.prop_search(modifier, "wind_object_name", bpy.data, "objects")
layout.prop_search(modifier, "envmap_name", bpy.data, "textures") layout.prop_search(modifier, "envmap_name", bpy.data, "textures")

Loading…
Cancel
Save