Browse Source

Fix CoordinateInterface race conditions

To ensure that there are really, really, REALLY no race conditions related
to coordinate interfaces, we now run through all modifiers before we
export and ask them if they need to make Coordinate Interfaces. I was
hearing some comments about clickables warping around. This sounds like
physical coordinate issues to me...
pull/10/head
Adam Johnson 10 years ago
parent
commit
a3716a8c35
  1. 40
      korman/exporter/convert.py
  2. 12
      korman/exporter/manager.py
  3. 3
      korman/exporter/material.py
  4. 2
      korman/exporter/mesh.py
  5. 2
      korman/exporter/physics.py
  6. 12
      korman/nodes/node_conditions.py
  7. 16
      korman/nodes/node_core.py
  8. 3
      korman/nodes/node_messages.py
  9. 8
      korman/properties/modifiers/avatar.py
  10. 3
      korman/properties/modifiers/base.py
  11. 9
      korman/properties/modifiers/logic.py

40
korman/exporter/convert.py

@ -32,6 +32,7 @@ class Exporter:
def __init__(self, op): def __init__(self, op):
self._op = op # Blender export operator self._op = op # Blender export operator
self._objects = [] self._objects = []
self.actors = set()
@property @property
def age_name(self): def age_name(self):
@ -58,6 +59,10 @@ class Exporter:
# us to export (both in the Age and Object Properties)... fun # us to export (both in the Age and Object Properties)... fun
self._collect_objects() self._collect_objects()
# Step 2.5: Run through all the objects we collected in Step 2 and see if any relationships
# that the artist made requires something to have a CoordinateInterface
self._harvest_actors()
# Step 3: Export all the things! # Step 3: Export all the things!
self._export_scene_objects() self._export_scene_objects()
@ -135,8 +140,8 @@ class Exporter:
def _export_actor(self, so, bo): def _export_actor(self, so, bo):
"""Exports a Coordinate Interface if we need one""" """Exports a Coordinate Interface if we need one"""
if self.mgr.has_coordiface(bo): if self.has_coordiface(bo):
self.export_coordinate_interface(so, bo) self._export_coordinate_interface(so, bo)
# If this object has a parent, then we will need to go upstream and add ourselves to the # If this object has a parent, then we will need to go upstream and add ourselves to the
# parent's CoordinateInterface... Because life just has to be backwards. # parent's CoordinateInterface... Because life just has to be backwards.
@ -154,7 +159,7 @@ class Exporter:
The object may not appear in the correct location or animate properly.".format( The object may not appear in the correct location or animate properly.".format(
bo.name, parent.name)) bo.name, parent.name))
def export_coordinate_interface(self, so, bl, name=None): def _export_coordinate_interface(self, so, bl, name=None):
"""Ensures that the SceneObject has a CoordinateInterface""" """Ensures that the SceneObject has a CoordinateInterface"""
if not so.coord: if not so.coord:
ci = self.mgr.find_create_object(plCoordinateInterface, bl=bl, so=so, name=name) ci = self.mgr.find_create_object(plCoordinateInterface, bl=bl, so=so, name=name)
@ -207,6 +212,35 @@ class Exporter:
else: else:
print(" No material(s) on the ObData, so no drawables") print(" No material(s) on the ObData, so no drawables")
def _harvest_actors(self):
for bl_obj in self._objects:
for mod in bl_obj.plasma_modifiers.modifiers:
if mod.enabled:
self.actors.update(mod.harvest_actors())
# This is a little hacky, but it's an edge case... I guess?
# We MUST have CoordinateInterfaces for EnvironmentMaps (DCMs, bah)
for texture in bpy.data.textures:
envmap = getattr(texture, "environment_map", None)
if envmap is not None:
viewpt = envmap.viewpoint_object
if viewpt is not None:
self.actors.add(viewpt.name)
def has_coordiface(self, bo):
if bo.type in {"CAMERA", "EMPTY", "LAMP"}:
return True
if bo.parent is not None:
return True
if bo.name in self.actors:
return True
for mod in bo.plasma_modifiers.modifiers:
if mod.enabled:
if mod.requires_actor:
return True
return False
def _post_process_scene_objects(self): def _post_process_scene_objects(self):
print("\nPostprocessing SceneObjects...") print("\nPostprocessing SceneObjects...")
for bl_obj in self._objects: for bl_obj in self._objects:

12
korman/exporter/manager.py

@ -220,18 +220,6 @@ class ExportManager:
else: else:
return key.location return key.location
def has_coordiface(self, bo):
if bo.type in {"CAMERA", "EMPTY", "LAMP"}:
return True
if bo.parent is not None:
return True
for mod in bo.plasma_modifiers.modifiers:
if mod.enabled:
if mod.requires_actor:
return True
return False
def save_age(self, path): def save_age(self, path):
relpath, ageFile = os.path.split(path) relpath, ageFile = os.path.split(path)
ageName = os.path.splitext(ageFile)[0] ageName = os.path.splitext(ageFile)[0]

3
korman/exporter/material.py

@ -396,8 +396,9 @@ class MaterialConverter:
# It matters not whether or not the viewpoint object is a Plasma Object, it is exported as at # It matters not whether or not the viewpoint object is a Plasma Object, it is exported as at
# least a SceneObject and CoordInterface so that we can touch it... # least a SceneObject and CoordInterface so that we can touch it...
# NOTE: that harvest_actor makes sure everyone alread knows we're going to have a CI
root = self._mgr.find_create_key(plSceneObject, bl=bo, name=viewpt.name) root = self._mgr.find_create_key(plSceneObject, bl=bo, name=viewpt.name)
self._exporter().export_coordinate_interface(root.object, bl=bo, name=viewpt.name) self._exporter()._export_coordinate_interface(root.object, bl=bo, name=viewpt.name)
# FIXME: DynamicCamMap Camera # FIXME: DynamicCamMap Camera
# Ensure POT # Ensure POT

2
korman/exporter/mesh.py

@ -132,7 +132,7 @@ class MeshConverter:
geospan.addPermaProjs(i) geospan.addPermaProjs(i)
# If this object has a CI, we don't need xforms here... # If this object has a CI, we don't need xforms here...
if self._mgr.has_coordiface(bo): if self._exporter().has_coordiface(bo):
geospan.localToWorld = hsMatrix44() geospan.localToWorld = hsMatrix44()
geospan.worldToLocal = hsMatrix44() geospan.worldToLocal = hsMatrix44()
else: else:

2
korman/exporter/physics.py

@ -31,7 +31,7 @@ class PhysicsConverter:
with TemporaryObject(mesh, bpy.data.meshes.remove) as mesh: with TemporaryObject(mesh, bpy.data.meshes.remove) as mesh:
# We can only use the plPhysical xforms if there is a CI... # We can only use the plPhysical xforms if there is a CI...
if self._mgr.has_coordiface(bo): if self._exporter().has_coordiface(bo):
mesh.update(calc_tessface=indices) mesh.update(calc_tessface=indices)
physical.pos = utils.vector3(mat.to_translation()) physical.pos = utils.vector3(mat.to_translation())
quat = mat.to_quaternion() quat = mat.to_quaternion()

12
korman/nodes/node_conditions.py

@ -52,8 +52,6 @@ class PlasmaClickableNode(PlasmaNodeBase, bpy.types.Node):
if clickable_bo is None: if clickable_bo is None:
self.raise_error("invalid Clickable object: '{}'".format(self.clickable), tree) self.raise_error("invalid Clickable object: '{}'".format(self.clickable), tree)
clickable_so = exporter.mgr.find_create_object(plSceneObject, bl=clickable_bo) clickable_so = exporter.mgr.find_create_object(plSceneObject, bl=clickable_bo)
# We're deep inside a potentially unrelated node tree...
exporter.export_coordinate_interface(clickable_so, clickable_bo)
else: else:
clickable_bo = parent_bo clickable_bo = parent_bo
clickable_so = parent_so clickable_so = parent_so
@ -101,10 +99,8 @@ class PlasmaClickableNode(PlasmaNodeBase, bpy.types.Node):
face_target = self.find_input_socket("facing") face_target = self.find_input_socket("facing")
face_target.convert_subcondition(exporter, tree, clickable_bo, clickable_so, logicmod) face_target.convert_subcondition(exporter, tree, clickable_bo, clickable_so, logicmod)
@property def harvest_actors(self):
def requires_actor(self): return (self.clickable,)
face_target = self.find_input_socket("facing")
return face_target.enable_condition
class PlasmaClickableRegionNode(PlasmaNodeBase, bpy.types.Node): class PlasmaClickableRegionNode(PlasmaNodeBase, bpy.types.Node):
@ -189,10 +185,6 @@ class PlasmaFacingTargetNode(PlasmaNodeBase, bpy.types.Node):
layout.prop(self, "directional") layout.prop(self, "directional")
layout.prop(self, "tolerance") layout.prop(self, "tolerance")
@property
def requires_actor(self):
return True
class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.0, 0.267, 0.247, 1.0) bl_color = (0.0, 0.267, 0.247, 1.0)

16
korman/nodes/node_core.py

@ -95,6 +95,9 @@ class PlasmaNodeBase:
return i return i
raise KeyError(key) raise KeyError(key)
def harvest_actors(self):
return set()
def link_input(self, tree, node, out_key, in_key): def link_input(self, tree, node, out_key, in_key):
"""Links a given Node's output socket to a given input socket on this Node""" """Links a given Node's output socket to a given input socket on this Node"""
if isinstance(in_key, str): if isinstance(in_key, str):
@ -166,13 +169,12 @@ class PlasmaNodeTree(bpy.types.NodeTree):
for node in self.nodes: for node in self.nodes:
node.export(exporter, self, bo, so) node.export(exporter, self, bo, so)
def harvest_actors(self):
actors = set()
for node in self.nodes:
actors.update(node.harvest_actors())
return actors
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return (context.scene.render.engine == "PLASMA_GAME") return (context.scene.render.engine == "PLASMA_GAME")
@property
def requires_actor(self):
for node in self.nodes:
if node.requires_actor:
return True
return False

3
korman/nodes/node_messages.py

@ -274,6 +274,9 @@ class PlasmaOneShotMsgNode(PlasmaMessageNode, bpy.types.Node):
else: else:
return exporter.mgr.find_create_key(plOneShotMod, name=name, so=so) return exporter.mgr.find_create_key(plOneShotMod, name=name, so=so)
def harvest_actors(self):
return (self.pos,)
@property @property
def has_callbacks(self): def has_callbacks(self):
return bool(self.marker) return bool(self.marker)

8
korman/properties/modifiers/avatar.py

@ -66,6 +66,11 @@ class PlasmaSittingBehavior(PlasmaModifierProperties, PlasmaModifierLogicWiz):
# Now, export the node tree # Now, export the node tree
self.node_tree.export(exporter, bo, so) self.node_tree.export(exporter, bo, so)
def harvest_actors(self):
if self.facing_enabled:
return (self.clickable_obj,)
return ()
def logicwiz(self, bo): def logicwiz(self, bo):
tree = self.node_tree tree = self.node_tree
nodes = tree.nodes nodes = tree.nodes
@ -105,4 +110,5 @@ class PlasmaSittingBehavior(PlasmaModifierProperties, PlasmaModifierLogicWiz):
@property @property
def requires_actor(self): def requires_actor(self):
return self.facing_enabled # This should be an empty, really...
return True

3
korman/properties/modifiers/base.py

@ -29,6 +29,9 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
def enabled(self): def enabled(self):
return self.display_order >= 0 return self.display_order >= 0
def harvest_actors(self):
return ()
@property @property
def requires_actor(self): def requires_actor(self):
"""Indicates if this modifier requires the object to be a movable actor""" """Indicates if this modifier requires the object to be a movable actor"""

9
korman/properties/modifiers/logic.py

@ -62,12 +62,11 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties):
if version in our_versions: if version in our_versions:
i.node_tree.export(exporter, bo, so) i.node_tree.export(exporter, bo, so)
@property def harvest_actors(self):
def requires_actor(self): actors = set()
for i in self.logic_groups: for i in self.logic_groups:
if i.node_tree.requires_actor: actors.update(i.node_tree.harvest_actors())
return True return actors
return False
class PlasmaSpawnPoint(PlasmaModifierProperties): class PlasmaSpawnPoint(PlasmaModifierProperties):

Loading…
Cancel
Save