4
4
mirror of https://github.com/H-uru/korman.git synced 2025-07-15 10:54:18 +00:00

SoftVolume Modifier

This introduces a basic soft volume interface to Korman. It currently
allows you to define simple convex "soft" regions and somewhat complex
soft regions using the soft volume modifier.
This commit is contained in:
2015-11-23 23:33:31 -05:00
parent 8d3365e13e
commit 7d7af297e4
5 changed files with 332 additions and 0 deletions

View File

@ -26,6 +26,7 @@ from .node_logic import *
from .node_messages import *
from .node_python import *
from .node_responder import *
from .node_softvolume import *
class PlasmaNodeCategory(NodeCategory):
"""Plasma Node Category"""
@ -45,6 +46,7 @@ _kategory_names = {
"LOGIC": "Logic",
"MSG": "Message",
"PYTHON": "Python",
"SV": "Soft Volume",
}
# Now, generate the categories as best we can...

View File

@ -52,6 +52,14 @@ class PlasmaNodeBase:
return None
raise KeyError(key)
def find_inputs(self, key, idname=None):
for i in self.inputs:
if i.alias == key:
if i.links:
node = i.links[0].from_node
if idname is None or idname == node.bl_idname:
yield node
def find_input_socket(self, key):
for i in self.inputs:
if i.alias == key:
@ -223,6 +231,18 @@ class PlasmaNodeBase:
socket.link_limit = link_limit
class PlasmaTreeOutputNodeBase(PlasmaNodeBase):
"""Represents the final output of a node tree"""
def init(self, context):
nodes = self.id_data.nodes
# There can only be one of these nodes per tree, so let's make sure I'm the only one.
for i in nodes:
if isinstance(i, self.__class__) and i != self:
nodes.remove(self)
return
class PlasmaNodeSocketBase:
@property
def alias(self):
@ -265,6 +285,12 @@ class PlasmaNodeTree(bpy.types.NodeTree):
for node in self.nodes:
node.export(exporter, bo, so)
def find_output(self, idname):
for node in self.nodes:
if node.bl_idname == idname:
return node
return None
def harvest_actors(self):
actors = set()
for node in self.nodes:

View File

@ -0,0 +1,195 @@
# 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 <http://www.gnu.org/licenses/>.
import bpy
from bpy.props import *
from collections import OrderedDict
from PyHSPlasma import *
from .node_core import PlasmaNodeBase, PlasmaNodeSocketBase, PlasmaTreeOutputNodeBase
class PlasmaSoftVolumeOutputNode(PlasmaTreeOutputNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeOutputNode"
bl_label = "Soft Volume Output"
input_sockets = OrderedDict([
("input", {
"text": "Final Volume",
"type": "PlasmaSoftVolumeNodeSocket",
}),
])
def get_key(self, exporter, so):
svNode = self.find_input("input")
if svNode is not None:
return svNode.get_key(exporter, so)
return None
class PlasmaSoftVolumeNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.133, 0.094, 0.345, 1.0)
class PlasmaSoftVolumePropertiesNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.067, 0.40, 0.067, 1.0)
class PlasmaSoftVolumePropertiesNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumePropertiesNode"
bl_label = "Soft Volume Properties"
output_sockets = OrderedDict([
("target", {
"text": "Volume",
"type": "PlasmaSoftVolumePropertiesNodeSocket"
}),
])
inside_strength = IntProperty(name="Inside", description="Strength inside the region",
subtype="PERCENTAGE", default=100, min=0, max=100)
outside_strength = IntProperty(name="Outside", description="Strength outside the region",
subtype="PERCENTAGE", default=0, min=0, max=100)
def draw_buttons(self, context, layout):
layout.prop(self, "inside_strength")
layout.prop(self, "outside_strength")
def propagate(self, softvolume):
softvolume.insideStrength = self.inside_strength / 100
softvolume.outsideStrength = self.outside_strength / 100
class PlasmaSoftVolumeReferenceNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeReferenceNode"
bl_label = "Soft Region"
bl_width_default = 150
output_sockets = OrderedDict([
("output", {
"text": "Volume",
"type": "PlasmaSoftVolumeNodeSocket"
}),
])
soft_object = StringProperty(name="Soft Volume",
description="Object whose Soft Volume modifier we should use")
def draw_buttons(self, context, layout):
layout.prop_search(self, "soft_object", bpy.data, "objects", icon="OBJECT_DATA", text="")
def get_key(self, exporter, so):
softvol = bpy.data.objects.get(self.soft_object, None)
if softvol is None:
self.raise_error("Volume Object '{}' not found".format(self.soft_object))
# Don't use SO here because that's the tree owner's SO. This soft region will find or create
# its own SceneObject. Yay!
return softvol.plasma_modifiers.softvolume.get_key(exporter)
class PlasmaSoftVolumeInvertNode(PlasmaNodeBase, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeInvertNode"
bl_label = "Soft Volume Invert"
# The only difference between this and PlasmaSoftVolumeLinkNode is this can only have ONE input
input_sockets = OrderedDict([
("properties", {
"text": "Properties",
"type": "PlasmaSoftVolumePropertiesNodeSocket",
}),
("input", {
"text": "Input Volume",
"type": "PlasmaSoftVolumeNodeSocket",
}),
])
output_sockets = OrderedDict([
("output", {
"text": "Output Volume",
"type": "PlasmaSoftVolumeNodeSocket"
}),
])
def get_key(self, exporter, so):
return exporter.mgr.find_create_key(plSoftVolumeInvert, name=self.key_name, so=so)
def export(self, exporter, bo, so):
parent = self.find_input("input")
if parent is None:
self.raise_error("SoftVolume Invert requires an input volume!")
sv = self.get_key(exporter, so).object
sv.addSubVolume(parent.get_key(exporter, so))
props = self.find_input("properties")
if props is not None:
props.propagate(sv)
else:
sv.insideStrength = 1.0
sv.outsideStrength = 0.0
class PlasmaSoftVolumeLinkNode(PlasmaNodeBase):
input_sockets = OrderedDict([
("properties", {
"text": "Properties",
"type": "PlasmaSoftVolumePropertiesNodeSocket",
}),
("input", {
"text": "Input Volume",
"type": "PlasmaSoftVolumeNodeSocket",
"spawn_empty": True,
}),
])
output_sockets = OrderedDict([
("output", {
"text": "Output Volume",
"type": "PlasmaSoftVolumeNodeSocket"
}),
])
def export(self, exporter, bo, so):
sv = self.get_key(exporter, so).object
for node in self.find_inputs("input"):
sv.addSubVolume(node.get_key(exporter, so))
props = self.find_input("properties")
if props is not None:
props.propagate(sv)
else:
sv.insideStrength = 1.0
sv.outsideStrength = 0.0
class PlasmaSoftVolumeIntersectNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeIntersectNode"
bl_label = "Soft Volume Intersect"
def get_key(self, exporter, so):
## FIXME: SoftVolumeIntersect should not be listed as an interface
return exporter.mgr.find_create_key(plSoftVolumeIntersect, name=self.key_name, so=so)
class PlasmaSoftVolumeUnionNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
bl_category = "SV"
bl_idname = "PlasmaSoftVolumeUnionNode"
bl_label = "Soft Volume Union"
def get_key(self, exporter, so):
## FIXME: SoftVolumeUnion should not be listed as an interface
return exporter.mgr.find_create_key(plSoftVolumeUnion, name=self.key_name, so=so)

View File

@ -17,6 +17,9 @@ import bpy
from bpy.props import *
from PyHSPlasma import *
from ...exporter import ExportError
from ...helpers import TemporaryObject
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from .physics import bounds_types
@ -140,3 +143,91 @@ class PlasmaPanicLinkRegion(PlasmaModifierProperties):
@property
def requires_actor(self):
return True
class PlasmaSoftVolume(PlasmaModifierProperties):
pl_id = "softvolume"
bl_category = "Region"
bl_label = "Soft Volume"
bl_description = "Soft-Boundary Region"
# Advanced
use_nodes = BoolProperty(name="Use Nodes",
description="Make this a node-based Soft Volume",
default=False)
node_tree_name = StringProperty(name="Node Tree",
description="Node Tree detailing soft volume logic")
# Basic
invert = BoolProperty(name="Invert",
description="Invert the soft region")
inside_strength = IntProperty(name="Inside", description="Strength inside the region",
subtype="PERCENTAGE", default=100, min=0, max=100)
outside_strength = IntProperty(name="Outside", description="Strength outside the region",
subtype="PERCENTAGE", default=0, min=0, max=100)
soft_distance = FloatProperty(name="Distance", description="Soft Distance",
default=0.0, min=0.0, max=500.0)
def _apply_settings(self, sv):
sv.insideStrength = self.inside_strength / 100.0
sv.outsideStrength = self.outside_strength / 100.0
def get_key(self, exporter, so=None):
"""Fetches the key appropriate for this Soft Volume"""
if so is None:
so = exporter.mgr.find_create_object(plSceneObject, bl=self.id_data)
if self.use_nodes:
output = self.node_tree.find_output("PlasmaSoftVolumeOutputNode")
if output is None:
raise ExportError("SoftVolume '{}' Node Tree '{}' has no output node!".format(self.key_name, self.node_tree))
return output.get_key(exporter, so)
else:
pClass = plSoftVolumeInvert if self.invert else plSoftVolumeSimple
return exporter.mgr.find_create_key(pClass, bl=self.id_data, so=so)
def export(self, exporter, bo, so):
if self.use_nodes:
self._export_sv_nodes(exporter, bo, so)
else:
self._export_convex_region(exporter, bo, so)
def _export_convex_region(self, exporter, bo, so):
if bo.type != "MESH":
raise ExportError("SoftVolume '{}': Simple SoftVolumes can only be meshes!".format(bo.name))
# Grab the SoftVolume KO
sv = self.get_key(exporter, so).object
self._apply_settings(sv)
# If "invert" was checked, we got a SoftVolumeInvert, but we need to make a Simple for the
# region data to be exported into..
if isinstance(sv, plSoftVolumeInvert):
svSimple = exporter.mgr.find_create_object(plSoftVolumeSimple, bl=bo, so=so)
self._apply_settings(svSimple)
sv.addSubVolume(svSimple.key)
sv = svSimple
sv.softDist = self.soft_distance
# Initialize the plVolumeIsect. Currently, we only support convex isects. If you want parallel
# isects from empties, be my guest...
with TemporaryObject(bo.to_mesh(bpy.context.scene, True, "RENDER", calc_tessface=False), bpy.data.meshes.remove) as mesh:
mesh.transform(bo.matrix_world)
isect = plConvexIsect()
for i in mesh.vertices:
isect.addPlane(hsVector3(*i.normal), hsVector3(*i.co))
sv.volume = isect
def _export_sv_nodes(self, exporter, bo, so):
if self.node_tree_name not in exporter.node_trees_exported:
exporter.node_trees_exported.add(self.node_tree_name)
self.node_tree.export(exporter, bo, so)
@property
def node_tree(self):
tree = bpy.data.node_groups.get(self.node_tree_name, None)
if tree is None:
raise ExportError("SoftVolume '{}': Node Tree '{}' does not exist!".format(self.key_name, self.node_tree_name))
return tree

View File

@ -13,6 +13,8 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
def footstep(modifier, layout, context):
layout.prop(modifier, "bounds")
layout.prop(modifier, "surface")
@ -21,3 +23,19 @@ def paniclink(modifier, layout, context):
phys_mod = context.object.plasma_modifiers.collision
layout.prop(phys_mod, "bounds")
layout.prop(modifier, "play_anim")
def softvolume(modifier, layout, context):
row = layout.row()
row.prop(modifier, "use_nodes", text="", icon="NODETREE")
if modifier.use_nodes:
row.prop_search(modifier, "node_tree_name", bpy.data, "node_groups")
else:
row.label("Simple Soft Volume")
split = layout.split()
col = split.column()
col.prop(modifier, "inside_strength")
col.prop(modifier, "outside_strength")
col = split.column()
col.prop(modifier, "invert")
col.prop(modifier, "soft_distance")