From a3716a8c35b1004997962128c4cd0002ca0a427b Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 11 Jul 2015 00:25:42 -0400 Subject: [PATCH] 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... --- korman/exporter/convert.py | 40 +++++++++++++++++++++++++-- korman/exporter/manager.py | 12 -------- korman/exporter/material.py | 3 +- korman/exporter/mesh.py | 2 +- korman/exporter/physics.py | 2 +- korman/nodes/node_conditions.py | 12 ++------ korman/nodes/node_core.py | 16 ++++++----- korman/nodes/node_messages.py | 3 ++ korman/properties/modifiers/avatar.py | 8 +++++- korman/properties/modifiers/base.py | 3 ++ korman/properties/modifiers/logic.py | 9 +++--- 11 files changed, 69 insertions(+), 41 deletions(-) diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 9eace31..6f43726 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -32,6 +32,7 @@ class Exporter: def __init__(self, op): self._op = op # Blender export operator self._objects = [] + self.actors = set() @property def age_name(self): @@ -58,6 +59,10 @@ class Exporter: # us to export (both in the Age and Object Properties)... fun 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! self._export_scene_objects() @@ -135,8 +140,8 @@ class Exporter: def _export_actor(self, so, bo): """Exports a Coordinate Interface if we need one""" - if self.mgr.has_coordiface(bo): - self.export_coordinate_interface(so, bo) + if self.has_coordiface(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 # 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( 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""" if not so.coord: ci = self.mgr.find_create_object(plCoordinateInterface, bl=bl, so=so, name=name) @@ -207,6 +212,35 @@ class Exporter: else: 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): print("\nPostprocessing SceneObjects...") for bl_obj in self._objects: diff --git a/korman/exporter/manager.py b/korman/exporter/manager.py index ecbab28..d7d4d1e 100644 --- a/korman/exporter/manager.py +++ b/korman/exporter/manager.py @@ -220,18 +220,6 @@ class ExportManager: else: 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): relpath, ageFile = os.path.split(path) ageName = os.path.splitext(ageFile)[0] diff --git a/korman/exporter/material.py b/korman/exporter/material.py index dabea3e..3775162 100644 --- a/korman/exporter/material.py +++ b/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 # 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) - 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 # Ensure POT diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 8cfe019..4301f9a 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -132,7 +132,7 @@ class MeshConverter: geospan.addPermaProjs(i) # 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.worldToLocal = hsMatrix44() else: diff --git a/korman/exporter/physics.py b/korman/exporter/physics.py index e0e52c6..4653865 100644 --- a/korman/exporter/physics.py +++ b/korman/exporter/physics.py @@ -31,7 +31,7 @@ class PhysicsConverter: with TemporaryObject(mesh, bpy.data.meshes.remove) as mesh: # 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) physical.pos = utils.vector3(mat.to_translation()) quat = mat.to_quaternion() diff --git a/korman/nodes/node_conditions.py b/korman/nodes/node_conditions.py index b79c75b..9c6e9a5 100644 --- a/korman/nodes/node_conditions.py +++ b/korman/nodes/node_conditions.py @@ -52,8 +52,6 @@ class PlasmaClickableNode(PlasmaNodeBase, bpy.types.Node): if clickable_bo is None: self.raise_error("invalid Clickable object: '{}'".format(self.clickable), tree) 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: clickable_bo = parent_bo clickable_so = parent_so @@ -101,10 +99,8 @@ class PlasmaClickableNode(PlasmaNodeBase, bpy.types.Node): face_target = self.find_input_socket("facing") face_target.convert_subcondition(exporter, tree, clickable_bo, clickable_so, logicmod) - @property - def requires_actor(self): - face_target = self.find_input_socket("facing") - return face_target.enable_condition + def harvest_actors(self): + return (self.clickable,) class PlasmaClickableRegionNode(PlasmaNodeBase, bpy.types.Node): @@ -189,10 +185,6 @@ class PlasmaFacingTargetNode(PlasmaNodeBase, bpy.types.Node): layout.prop(self, "directional") layout.prop(self, "tolerance") - @property - def requires_actor(self): - return True - class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): bl_color = (0.0, 0.267, 0.247, 1.0) diff --git a/korman/nodes/node_core.py b/korman/nodes/node_core.py index bdd282d..81099aa 100644 --- a/korman/nodes/node_core.py +++ b/korman/nodes/node_core.py @@ -95,6 +95,9 @@ class PlasmaNodeBase: return i raise KeyError(key) + def harvest_actors(self): + return set() + 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""" if isinstance(in_key, str): @@ -166,13 +169,12 @@ class PlasmaNodeTree(bpy.types.NodeTree): for node in self.nodes: 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 def poll(cls, context): 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 diff --git a/korman/nodes/node_messages.py b/korman/nodes/node_messages.py index 6583505..96c6e2a 100644 --- a/korman/nodes/node_messages.py +++ b/korman/nodes/node_messages.py @@ -274,6 +274,9 @@ class PlasmaOneShotMsgNode(PlasmaMessageNode, bpy.types.Node): else: return exporter.mgr.find_create_key(plOneShotMod, name=name, so=so) + def harvest_actors(self): + return (self.pos,) + @property def has_callbacks(self): return bool(self.marker) diff --git a/korman/properties/modifiers/avatar.py b/korman/properties/modifiers/avatar.py index 2131abb..cd9a7a3 100644 --- a/korman/properties/modifiers/avatar.py +++ b/korman/properties/modifiers/avatar.py @@ -66,6 +66,11 @@ class PlasmaSittingBehavior(PlasmaModifierProperties, PlasmaModifierLogicWiz): # Now, export the node tree self.node_tree.export(exporter, bo, so) + def harvest_actors(self): + if self.facing_enabled: + return (self.clickable_obj,) + return () + def logicwiz(self, bo): tree = self.node_tree nodes = tree.nodes @@ -105,4 +110,5 @@ class PlasmaSittingBehavior(PlasmaModifierProperties, PlasmaModifierLogicWiz): @property def requires_actor(self): - return self.facing_enabled + # This should be an empty, really... + return True diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py index 7f15211..3aa81ed 100644 --- a/korman/properties/modifiers/base.py +++ b/korman/properties/modifiers/base.py @@ -29,6 +29,9 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup): def enabled(self): return self.display_order >= 0 + def harvest_actors(self): + return () + @property def requires_actor(self): """Indicates if this modifier requires the object to be a movable actor""" diff --git a/korman/properties/modifiers/logic.py b/korman/properties/modifiers/logic.py index 9ee7dd3..4d1e1a0 100644 --- a/korman/properties/modifiers/logic.py +++ b/korman/properties/modifiers/logic.py @@ -62,12 +62,11 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties): if version in our_versions: i.node_tree.export(exporter, bo, so) - @property - def requires_actor(self): + def harvest_actors(self): + actors = set() for i in self.logic_groups: - if i.node_tree.requires_actor: - return True - return False + actors.update(i.node_tree.harvest_actors()) + return actors class PlasmaSpawnPoint(PlasmaModifierProperties):