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:
@ -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...
|
||||
|
@ -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:
|
||||
|
195
korman/nodes/node_softvolume.py
Normal file
195
korman/nodes/node_softvolume.py
Normal 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)
|
@ -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
|
||||
|
@ -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")
|
||||
|
Reference in New Issue
Block a user