From 6e00ae79cd0d10e864175481e822ed5577e31240 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 14 Jul 2014 23:24:15 -0400 Subject: [PATCH] Basic physical collision modifier --- korman/exporter/convert.py | 2 + korman/exporter/physics.py | 116 ++++++++++++++++++++++++ korman/exporter/utils.py | 4 + korman/properties/modifiers/__init__.py | 1 + korman/properties/modifiers/physics.py | 92 +++++++++++++++++++ korman/ui/modifiers/__init__.py | 1 + korman/ui/modifiers/physics.py | 40 ++++++++ 7 files changed, 256 insertions(+) create mode 100644 korman/exporter/physics.py create mode 100644 korman/properties/modifiers/physics.py create mode 100644 korman/ui/modifiers/physics.py diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 210e7cd..b74cca8 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -22,6 +22,7 @@ from . import explosions from . import logger from . import manager from . import mesh +from . import physics from . import utils class Exporter: @@ -42,6 +43,7 @@ class Exporter: self.mgr = manager.ExportManager(globals()[self._op.version]) self.mesh = mesh.MeshConverter(self) self.report = logger.ExportAnalysis() + self.physics = physics.PhysicsConverter(self) # Step 1: Gather a list of objects that we need to export # We should do this first so we can sanity check diff --git a/korman/exporter/physics.py b/korman/exporter/physics.py new file mode 100644 index 0000000..102a26c --- /dev/null +++ b/korman/exporter/physics.py @@ -0,0 +1,116 @@ +# This file is part of Korman. +# +# Korman is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Korman is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Korman. If not, see . + +import mathutils +from PyHSPlasma import * +import weakref + +from . import utils + +class PhysicsConverter: + def __init__(self, exporter): + self._exporter = weakref.ref(exporter) + + def _convert_mesh_data(self, bo, physical, indices=True): + mesh = bo.data + mesh.update(calc_tessface=True) + + mat = bo.matrix_world + if not self._mgr.has_coordiface(bo): + physical.rot = utils.quaternion(mat.to_quaternion()) + physical.pos = utils.vector3(mat.to_translation()) + + # Physicals can't have scale... + scale = mat.to_scale() + if scale[0] == 1.0 and scale[1] == 1.0 and scale[2] == 1.0: + # Whew, don't need to do any math! + vertices = [hsVector3(i.co.x, i.co.y, i.co.z) for i in mesh.vertices] + else: + # Dagnabbit... + vertices = [hsVector3(i.co.x * scale.x, i.co.y * scale.y, i.co.z * scale.z) for i in mesh.vertices] + + if indices: + 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 (vertices, indices) + else: + return vertices + + def generate_physical(self, bo, so, name=None): + """Generates a physical object for the given object pair""" + 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) + + so.sim = simIface.key + simIface.physical = physical.key + physical.object = so.key + physical.sceneNode = self._mgr.get_scene_node(bl=bo) + else: + simIface = so.sim.object + physical = simIface.physical.object + if name is not None: + physical.key.name = name + + return (simIface, physical) + + def export(self, bo, physical, bounds): + getattr(self, "_export_{}".format(bounds))(bo, physical) + + def _export_box(self, bo, physical): + """Exports box bounds based on the object""" + physical.boundsType = plSimDefs.kBoxBounds + + vertices = self._convert_mesh_data(bo, physical, indices=False) + physical.calcBoxBounds(vertices) + + def _export_hull(self, bo, physical): + """Exports convex hull bounds based on the object""" + physical.boundsType = plSimDefs.kHullBounds + + vertices = self._convert_mesh_data(bo, physical, indices=False) + # --- TODO --- + # Until we have real convex hull processing, simply dump the verts into the physical + # Note that PyPRP has always done this... PhysX will optimize this for us. So, it's not + # the end of the world (but it is evil). + physical.verts = vertices + + def _export_sphere(self, bo, physical): + """Exports sphere bounds based on the object""" + physical.boundsType = plSimDefs.kSphereBounds + + vertices = self._convert_mesh_data(bo, physical, indices=False) + physical.calcSphereBounds(vertices) + + def _export_trimesh(self, bo, physical): + """Exports an object's mesh as exact physical bounds""" + physical.boundsType = plSimDefs.kExplicitBounds + + vertices, indices = self._convert_mesh_data(bo, physical) + physical.verts = vertices + physical.indices = indices + + @property + def _mgr(self): + return self._exporter().mgr diff --git a/korman/exporter/utils.py b/korman/exporter/utils.py index 39a71dc..297b116 100644 --- a/korman/exporter/utils.py +++ b/korman/exporter/utils.py @@ -29,6 +29,10 @@ def matrix44(blmat): hsmat[i, 3] = blmat[i][3] return hsmat +def quaternion(blquat): + """Converts a mathutils.Quaternion to an hsQuat""" + return hsQuat(blquat.x, blquat.y, blquat.z, blquat.w) + def vector3(blvec): """Converts a mathutils.Vector to an hsVector3""" return hsVector3(blvec.x, blvec.y, blvec.z) diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py index cd52643..c0cef55 100644 --- a/korman/properties/modifiers/__init__.py +++ b/korman/properties/modifiers/__init__.py @@ -18,6 +18,7 @@ import inspect from .base import PlasmaModifierProperties from .logic import * +from .physics import * class PlasmaModifiers(bpy.types.PropertyGroup): def determine_next_id(self): diff --git a/korman/properties/modifiers/physics.py b/korman/properties/modifiers/physics.py new file mode 100644 index 0000000..cb004f9 --- /dev/null +++ b/korman/properties/modifiers/physics.py @@ -0,0 +1,92 @@ +# This file is part of Korman. +# +# Korman is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Korman is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Korman. If not, see . + +import bpy +from bpy.props import * +from PyHSPlasma import * + +from .base import PlasmaModifierProperties + +# These are the kinds of physical bounds Plasma can work with. +# This sequence is acceptable in any EnumProperty +bounds_types = ( + ("box", "Bounding Box", "Use a perfect bounding box"), + ("sphere", "Bounding Sphere", "Use a perfect bounding sphere"), + ("hull", "Convex Hull", "Use a convex set encompasing all vertices"), + ("trimesh", "Triangle Mesh", "Use the exact triangle mesh (SLOW!)") +) + +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 PlasmaCollider(PlasmaModifierProperties): + pl_id = "collision" + + bl_category = "Physics" + bl_label = "Collision" + bl_icon = "MOD_PHYSICS" + bl_description = "Simple physical collider" + + bounds = EnumProperty(name="Bounds Type", description="", items=bounds_types) + + avatar_blocker = BoolProperty(name="Blocks Avatars", description="Object blocks avatars", default=True) + camera_blocker = BoolProperty(name="Blocks Camera", description="Object blocks the camera", default=True) + + friction = FloatProperty(name="Friction", min=0.0, default=0.5) + restitution = FloatProperty(name="Restitution", description="Coefficient of collision elasticity", min=0.0, max=1.0) + terrain = BoolProperty(name="Terrain", description="Object represents the ground", default=False) + + dynamic = BoolProperty(name="Dynamic", description="Object can be influenced by other objects (ie is kickable)", default=False) + mass = FloatProperty(name="Mass", description="Mass of object in pounds", min=0.0, default=1.0) + start_asleep = BoolProperty(name="Start Asleep", description="Object is not active until influenced by another object", default=False) + + def created(self, obj): + self.display_name = "{}_Collision".format(obj.name) + + def export(self, exporter, bo, so): + simIface, physical = exporter.physics.generate_physical(bo, so, self.display_name) + + # Common props + physical.friction = self.friction + physical.restitution = self.restitution + + # Collision groups and such + if not self.avatar_blocker: + physical.collideGroup = plSimDefs.kGroupLOSOnly + + if self.dynamic: + physical.memberGroup = plSimDefs.kGroupDynamic + physical.mass = self.mass + _set_phys_prop(plSimulationInterface.kStartInactive, simIface, physical, value=self.start_asleep) + 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 + + # Pass this off to the PhysicsConverter to export the meat + exporter.physics.export(bo, physical, self.bounds) + + @property + def requires_actor(self): + return self.dynamic diff --git a/korman/ui/modifiers/__init__.py b/korman/ui/modifiers/__init__.py index a8b2222..2facd58 100644 --- a/korman/ui/modifiers/__init__.py +++ b/korman/ui/modifiers/__init__.py @@ -14,3 +14,4 @@ # along with Korman. If not, see . from .logic import * +from .physics import * diff --git a/korman/ui/modifiers/physics.py b/korman/ui/modifiers/physics.py new file mode 100644 index 0000000..fc72225 --- /dev/null +++ b/korman/ui/modifiers/physics.py @@ -0,0 +1,40 @@ +# This file is part of Korman. +# +# Korman is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Korman is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Korman. If not, see . + +def collision(modifier, layout, context): + layout.prop(modifier, "bounds") + layout.separator() + + split = layout.split() + col = split.column() + col.prop(modifier, "avatar_blocker") + col.prop(modifier, "camera_blocker") + col.prop(modifier, "terrain") + + col = split.column() + col.prop(modifier, "friction") + col.prop(modifier, "restitution") + layout.separator() + + split = layout.split() + col = split.column() + col.prop(modifier, "dynamic") + row = col.row() + row.active = modifier.dynamic + row.prop(modifier, "start_asleep") + + col = split.column() + col.active = modifier.dynamic + col.prop(modifier, "mass")