diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py
index 8b4f1be..ffcafe2 100644
--- a/korman/exporter/convert.py
+++ b/korman/exporter/convert.py
@@ -21,6 +21,7 @@ import time
from . import animation
from . import camera
+from . import decal
from . import explosions
from . import etlight
from . import image
@@ -54,6 +55,7 @@ class Exporter:
self.camera = camera.CameraConverter(self)
self.image = image.ImageCache(self)
self.locman = locman.LocalizationConverter(self)
+ self.decal = decal.DecalConverter(self)
# Step 0.8: Init the progress mgr
self.mesh.add_progress_presteps(self.report)
diff --git a/korman/exporter/decal.py b/korman/exporter/decal.py
new file mode 100644
index 0000000..d6120b2
--- /dev/null
+++ b/korman/exporter/decal.py
@@ -0,0 +1,185 @@
+# 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 collections import defaultdict
+import itertools
+from PyHSPlasma import *
+import weakref
+
+from ..exporter.explosions import ExportError
+
+def _get_puddle_class(exporter, name, vs):
+ if vs:
+ # sigh... thou shalt not...
+ exporter.report.warn("'{}': Cannot use 'Water Ripple (Shallow) on a waveset--forcing to 'Water Ripple (Deep)", name)
+ return plDynaRippleVSMgr
+ return plDynaPuddleMgr
+
+def _get_footprint_class(exporter, name, vs):
+ if vs:
+ raise ExportError("'{}': Footprints cannot be attached to wavesets", name)
+ return plDynaFootMgr
+
+class DecalConverter:
+ _decal_lookup = {
+ "footprint_dry": _get_footprint_class,
+ "footprint_wet": _get_footprint_class,
+ "puddle": _get_puddle_class,
+ "ripple": lambda e, name, vs: plDynaRippleVSMgr if vs else plDynaRippleMgr,
+ }
+
+ def __init__(self, exporter):
+ self._decal_managers = defaultdict(list)
+ self._exporter = weakref.ref(exporter)
+ self._notifies = defaultdict(set)
+
+ def add_dynamic_decal_receiver(self, so, decal_name):
+ # One decal manager in Blender can map to many Plasma decal managers.
+ # The case we care about: a single water decal exporting to multiple DynaDecalMgrs
+ # eg two wavesets (two mgrs) and two water planes (one mgr)
+ # We don't care about: DynaDecalMgrs in another page.
+ decal_mgrs, so_key = self._decal_managers.get(decal_name), so.key
+ if decal_mgrs is None:
+ raise ExportError("'{}': Invalid decal manager '{}'", so_key.name, decal_name)
+
+ # If we are waveset water, then we can only have one target...
+ waveset_id = plFactory.ClassIndex("plWaveSet7")
+ waveset = next((i for i in so.modifiers if i.type == waveset_id), None)
+
+ so_loc = so_key.location
+ for key, decal_mgr in ((i, i.object) for i in decal_mgrs):
+ if key.location == so_loc and getattr(decal_mgr, "waveSet", None) == waveset:
+ decal_mgr.addTarget(so_key)
+
+ # HACKAGE: Add the wet/dirty notifes now that we know about all the decal managers.
+ notify_names = self._notifies[decal_name]
+ notify_keys = itertools.chain.from_iterable((self._decal_managers[i] for i in notify_names))
+ for notify_key in notify_keys:
+ for i in (i.object for i in decal_mgrs):
+ i.addNotify(notify_key)
+ # Don't need to do that again.
+ del self._notifies[decal_name]
+
+ def export_active_print_shape(self, print_shape, decal_name):
+ decal_mgrs = self._decal_managers.get(decal_name)
+ if decal_mgrs is None:
+ raise ExportError("'{}': Invalid decal manager '{}'", print_shape.key.name, decal_name)
+ for i in decal_mgrs:
+ print_shape.addDecalMgr(i)
+
+ def export_static_decal(self, bo):
+ mat_mgr = self._exporter().mesh.material
+ mat_keys = mat_mgr.get_materials(bo)
+ if not mat_keys:
+ raise ExportError("'{}': Cannot print decal onto object with no materials", bo.name)
+
+ zFlags = hsGMatState.kZIncLayer | hsGMatState.kZNoZWrite
+ for material in (i.object for i in mat_keys):
+ # Only useful in a debugging context
+ material.compFlags |= hsGMaterial.kCompDecal
+
+ # zFlags should only be applied to the material's base layer
+ # note: changing blend flags is unsafe here -- so don't even think about it!
+ layer = mat_mgr.get_base_layer(material)
+ layer.state.ZFlags |= zFlags
+
+ def generate_dynamic_decal(self, bo, decal_name):
+ decal = next((i for i in bpy.context.scene.plasma_scene.decal_managers if i.name == decal_name), None)
+ if decal is None:
+ raise ExportError("'{}': Invalid decal manager '{}'", bo.name, decal_name)
+
+ exporter = self._exporter()
+ decal_type = decal.decal_type
+ is_waveset = bo.plasma_modifiers.water_basic.enabled
+ pClass = self._decal_lookup[decal_type](exporter, decal_name, is_waveset)
+
+ # DynaDecal Managers generate geometry at runtime, so we need to share them as much as
+ # possible. However, it is best to keep things page local. Furthermore, wavesets cannot
+ # share decal managers due to vertex shaders being used.
+ name = "{}_{}".format(decal_name, bo.name) if is_waveset else decal_name
+ decal_mgr = exporter.mgr.find_object(pClass, bl=bo, name=name)
+ if decal_mgr is None:
+ self._report.msg("Exporing decal manager '{}' to '{}'", decal_name, name, indent=2)
+
+ decal_mgr = exporter.mgr.add_object(pClass, bl=bo, name=name)
+ self._decal_managers[decal_name].append(decal_mgr.key)
+
+ # Certain decals are required to be squares
+ if decal_type in {"footprint_dry", "footprint_wet", "wake"}:
+ length, width = decal.length / 100.0, decal.width / 100.0
+ else:
+ length = max(decal.length, decal.width) / 100.0
+ width = max(decal.length, decal.width) / 100.0
+
+ image = decal.image
+ if image is None:
+ raise ExportError("'{}': decal manager '{}' has no image set", bo.name, decal_name)
+
+ blend = getattr(hsGMatState, decal.blend)
+ mats = exporter.mesh.material.export_print_materials(bo, image, name, blend)
+ decal_mgr.matPreShade, decal_mgr.matRTShade = mats
+
+ # Hardwired values from PlasmaMAX
+ decal_mgr.maxNumVerts = 1000
+ decal_mgr.maxNumIdx = 1000
+ decal_mgr.intensity = decal.intensity / 100.0
+ decal_mgr.gridSizeU = 2.5
+ decal_mgr.gridSizeV = 2.5
+ decal_mgr.scale = hsVector3(length, width, 1.0)
+
+ # Hardwired calculations from PlasmaMAX
+ if decal_type in {"footprint_dry", "footprint_wet", "bullet"}:
+ decal_mgr.rampEnd = 0.1
+ decal_mgr.decayStart = decal.life_span - (decal.life_span * 0.25)
+ decal_mgr.lifeSpan = decal.life_span
+ elif decal_type in {"puddle", "ripple", "torpedo", "wake"}:
+ decal_mgr.rampEnd = 0.25
+ life_span = decal.life_span if decal_type == "torpedo" else length / 2.0
+ decal_mgr.decayStart = life_span * 0.8
+ decal_mgr.lifeSpan = life_span
+ else:
+ raise RuntimeError()
+
+ # While any decal manager can be wet/dry, it really makes the most sense to only
+ # expose wet footprints. In the future, we could expose the plDynaDecalEnableMsg
+ # to nodes for advanced hacking.
+ decal_mgr.waitOnEnable = decal_type == "footprint_wet"
+ if decal_type in {"puddle", "ripple"}:
+ decal_mgr.wetLength = decal.wet_time
+ self._notifies[decal_name].update((i.name for i in decal.wet_managers
+ if i.enabled and i.name != decal_name))
+
+ # UV Animations are hardcoded in PlasmaMAX. Any reason why we should expose this?
+ # I can't think of any presently... Note testing the final instance instead of the
+ # artist setting in case that gets overridden (puddle -> ripple)
+ if isinstance(decal_mgr, plDynaPuddleMgr):
+ decal_mgr.initUVW = hsVector3(5.0, 5.0, 5.0)
+ decal_mgr.finalUVW = hsVector3(1.0, 1.0, 1.0)
+ elif isinstance(decal_mgr, plDynaRippleMgr):
+ # wakes, torpedos, and ripples...
+ decal_mgr.initUVW = hsVector3(3.0, 3.0, 3.0)
+ decal_mgr.finalUVW = hsVector3(1.0, 1.0, 1.0)
+
+ if isinstance(decal_mgr, (plDynaRippleVSMgr, plDynaTorpedoVSMgr)):
+ decal_mgr.waveSet = exporter.mgr.find_create_key(plWaveSet7, bl=bo)
+
+ @property
+ def _mgr(self):
+ return self._exporter().mgr
+
+ @property
+ def _report(self):
+ return self._exporter().report
diff --git a/korman/exporter/material.py b/korman/exporter/material.py
index 7304b4b..b56ea8b 100644
--- a/korman/exporter/material.py
+++ b/korman/exporter/material.py
@@ -90,6 +90,7 @@ class _Texture:
self.ephemeral = kwargs.get("ephemeral", False)
self.image = image
self.tag = kwargs.get("tag", None)
+ self.name = kwargs.get("name", image.name)
def __eq__(self, other):
if not isinstance(other, _Texture):
@@ -106,17 +107,17 @@ class _Texture:
def __str__(self):
if self.extension is None:
- name = self.image.name
+ name = self.name
else:
- name = str(Path(self.image.name).with_suffix(".{}".format(self.extension)))
+ name = str(Path(self.name).with_suffix(".{}".format(self.extension)))
if self.calc_alpha:
- name = "ALPHAGEN_{}".format(name)
+ name = "ALPHAGEN_{}".format(self.name)
if self.is_detail_map:
name = "DETAILGEN_{}-{}-{}-{}-{}_{}".format(self._DETAIL_BLEND[self.detail_blend],
self.detail_fade_start, self.detail_fade_stop,
self.detail_opacity_start, self.detail_opacity_stop,
- name)
+ self.name)
return name
def _update(self, other):
@@ -179,9 +180,9 @@ class MaterialConverter:
"""Exports a Blender Material as an hsGMaterial"""
# Sometimes, a material might need to be single-use. Right now, the most apparent example
- # of that situation is when a lightmap image is baked. Wavesets are in the same boat, but
- # that's a special case as of the writing of this code.
- single_user = self._requires_single_user_material(bo, bm)
+ # of that situation is when a lightmap image is baked. There are others, but as of right now,
+ # it can all be determined by what mods are attached.
+ single_user = any((i.copy_material for i in bo.plasma_modifiers.modifiers))
if single_user:
mat_name = "{}_AutoSingle".format(bm.name) if bo.name == bm.name else "{}_{}".format(bo.name, bm.name)
self._report.msg("Exporting Material '{}' as single user '{}'", bm.name, mat_name, indent=1)
@@ -260,6 +261,78 @@ class MaterialConverter:
# Looks like we're done...
return hsgmat.key
+ def export_print_materials(self, bo, image, name, blend):
+ """Exports dynamic decal print material(s)"""
+
+ def make_print_material(name):
+ layer = self._mgr.add_object(plLayer, bl=bo, name=name)
+ layer.state.blendFlags = blend
+ layer.state.clampFlags = hsGMatState.kClampTexture
+ layer.state.ZFlags = hsGMatState.kZNoZWrite | hsGMatState.kZIncLayer
+ layer.ambient = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+ layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+ layer.runtime = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
+ self.export_prepared_image(name=image_name, image=image, alpha_type=image_alpha,
+ owner=layer, allowed_formats={"DDS"}, indent=4)
+ material = self._mgr.add_object(hsGMaterial, bl=bo, name=name)
+ material.addLayer(layer.key)
+ return material, layer
+
+ want_preshade = blend == hsGMatState.kBlendAlpha
+
+ image_alpha = self._test_image_alpha(image)
+ if image_alpha == TextureAlpha.opaque and want_preshade:
+ self._report.warn("Using an opaque texture with alpha blending -- this may look bad")
+
+ # Non-alpha blendmodes absolutely cannot have an alpha channel. Period. Nada.
+ # You can't even filter it out with blend flags. We'll try to mitigate the damage by
+ # exporting a DXT1 version. As of right now, opaque vs on_off does nothing, so we still
+ # get some turd-alpha data.
+ if image_alpha == TextureAlpha.full and not want_preshade:
+ self._report.warn("Using an alpha texture with a non-alpha blend mode -- this may look bad", indent=3)
+ image_alpha = TextureAlpha.opaque
+ image_name = "DECALPRINT_{}".format(image.name)
+ else:
+ image_name = image.name
+
+ # Check to see if we have already processed this print material...
+ rtname = "DECALPRINT_{}".format(name)
+ rt_key = self._mgr.find_key(hsGMaterial, bl=bo, name=rtname)
+ if want_preshade:
+ prename = "DECALPRINT_{}_AH".format(name)
+ pre_key = self._mgr.find_key(hsGMaterial, bl=bo, name=prename)
+ else:
+ pre_key = None
+ if rt_key or pre_key:
+ return pre_key, rt_key
+
+ self._report.msg("Exporting Print Material '{}'", rtname, indent=3)
+ rt_material, rt_layer = make_print_material(rtname)
+ if blend == hsGMatState.kBlendMult:
+ rt_layer.state.blendFlags |= hsGMatState.kBlendInvertFinalColor
+ rt_key = rt_material.key
+
+ if want_preshade:
+ self._report.msg("Exporting Print Material '{}'", prename, indent=3)
+ pre_material, pre_layer = make_print_material(prename)
+ pre_material.compFlags |= hsGMaterial.kCompNeedsBlendChannel
+ pre_layer.state.miscFlags |= hsGMatState.kMiscBindNext | hsGMatState.kMiscRestartPassHere
+ pre_layer.preshade = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
+
+ blend_layer = self._mgr.add_object(plLayer, bl=bo, name="{}_AlphaBlend".format(rtname))
+ blend_layer.state.blendFlags = hsGMatState.kBlendAlpha | hsGMatState.kBlendNoTexColor | \
+ hsGMatState.kBlendAlphaMult
+ blend_layer.state.clampFlags = hsGMatState.kClampTexture
+ blend_layer.state.ZFlags = hsGMatState.kZNoZWrite
+ blend_layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
+ pre_material.addLayer(blend_layer.key)
+ self.export_alpha_blend("LINEAR", "HORIZONTAL", owner=blend_layer, indent=4)
+
+ pre_key = pre_material.key
+ else:
+ pre_key = None
+ return pre_key, rt_key
+
def export_waveset_material(self, bo, bm):
self._report.msg("Exporting WaveSet Material '{}'", bm.name, indent=1)
@@ -356,8 +429,6 @@ class MaterialConverter:
if canStencil:
hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel
state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
- if slot.texture.type == "BLEND":
- state.clampFlags |= hsGMatState.kClampTexture
state.ZFlags |= hsGMatState.kZNoZWrite
layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
elif blend_flags:
@@ -380,6 +451,8 @@ class MaterialConverter:
layer.specularPower = 1.0
texture = slot.texture
+ if texture.type == "BLEND":
+ hsgmat.compFlags |= hsGMaterial.kCompNeedsBlendChannel
# Apply custom layer properties
wantBumpmap = bm is not None and slot.use_map_normal
@@ -715,6 +788,11 @@ class MaterialConverter:
pass
def _export_texture_type_blend(self, bo, layer, slot):
+ state = layer.state
+ state.blendFlags |= hsGMatState.kBlendAlpha | hsGMatState.kBlendAlphaMult | hsGMatState.kBlendNoTexColor
+ state.clampFlags |= hsGMatState.kClampTexture
+ state.ZFlags |= hsGMatState.kZNoZWrite
+
# This has been separated out because other things may need alpha blend textures.
texture = slot.texture
self.export_alpha_blend(texture.progression, texture.use_flip_axis, layer)
@@ -818,7 +896,7 @@ class MaterialConverter:
image.pixels = pixels
self.export_prepared_image(image=image, owner=owner, allowed_formats={"BMP"},
- alpha_type=TextureAlpha.full, indent=2, ephemeral=True)
+ alpha_type=TextureAlpha.full, indent=indent, ephemeral=True)
def export_prepared_image(self, **kwargs):
"""This exports an externally prepared image and an optional owning layer.
@@ -1073,6 +1151,16 @@ class MaterialConverter:
def get_materials(self, bo):
return self._obj2mat.get(bo, [])
+ def get_base_layer(self, hsgmat):
+ try:
+ layer = hsgmat.layers[0].object
+ except IndexError:
+ return None
+ else:
+ while layer.underLay is not None:
+ layer = layer.underLay.object
+ return layer
+
def get_bump_layer(self, bo):
return self._bump_mats.get(bo, None)
@@ -1132,14 +1220,6 @@ class MaterialConverter:
def _report(self):
return self._exporter().report
- def _requires_single_user_material(self, bo, bm):
- modifiers = bo.plasma_modifiers
- if modifiers.lightmap.bake_lightmap:
- return True
- if modifiers.water_basic.enabled:
- return True
- return False
-
def _test_image_alpha(self, image):
"""Tests to see if this image has any alpha data"""
diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py
index 62e3b24..dd4ad5e 100644
--- a/korman/exporter/mesh.py
+++ b/korman/exporter/mesh.py
@@ -39,11 +39,13 @@ class _RenderLevel:
_MAJOR_SHIFT = 28
_MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1)
- def __init__(self, bo, hsgmat, pass_index, blendSpan=False):
+ def __init__(self, bo, hsgmat, pass_index, blend_span=False):
self.level = 0
-
- if blendSpan:
- self.major = self.MAJOR_DEFAULT
+ if pass_index > 0:
+ self.major = self.MAJOR_FRAMEBUF
+ self.minor = pass_index * 4
+ else:
+ self.major = self.MAJOR_BLEND if blend_span else self.MAJOR_OPAQUE
# We use the blender material's pass index (which we stashed in the hsGMaterial) to increment
# the render pass, just like it says...
@@ -74,11 +76,10 @@ class _DrawableCriteria:
self.criteria = 0
if self.blend_span:
- for mod in bo.plasma_modifiers.modifiers:
- if mod.requires_face_sort:
- self.criteria |= plDrawable.kCritSortFaces
- if mod.requires_span_sort:
- self.criteria |= plDrawable.kCritSortSpans
+ if self._face_sort_allowed(bo):
+ self.criteria |= plDrawable.kCritSortFaces
+ if self._span_sort_allowed(bo):
+ self.criteria |= plDrawable.kCritSortSpans
self.render_level = _RenderLevel(bo, hsgmat, pass_index, self.blend_span)
def __eq__(self, other):
@@ -92,6 +93,16 @@ class _DrawableCriteria:
def __hash__(self):
return hash(self.render_level) ^ hash(self.blend_span) ^ hash(self.criteria)
+ def _face_sort_allowed(self, bo):
+ # For now, only test the modifiers
+ # This will need to be tweaked further for GUIs...
+ return not any((i.no_face_sort for i in bo.plasma_modifiers.modifiers))
+
+ def _span_sort_allowed(self, bo):
+ # For now, only test the modifiers
+ # This will need to be tweaked further for GUIs...
+ return not any((i.no_face_sort for i in bo.plasma_modifiers.modifiers))
+
@property
def span_type(self):
if self.blend_span:
diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py
index 987e1de..0d2049d 100644
--- a/korman/properties/modifiers/base.py
+++ b/korman/properties/modifiers/base.py
@@ -19,6 +19,11 @@ from bpy.props import *
from contextlib import contextmanager
class PlasmaModifierProperties(bpy.types.PropertyGroup):
+ @property
+ def copy_material(self):
+ """Materials MUST be single-user"""
+ return False
+
def created(self):
pass
@@ -37,19 +42,19 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
return self.id_data.name
@property
- def requires_actor(self):
- """Indicates if this modifier requires the object to be a movable actor"""
+ def no_face_sort(self):
+ """Indicates that the geometry's faces should never be sorted by the engine"""
return False
@property
- def requires_face_sort(self):
- """Indicates that the geometry's faces must be sorted by the engine"""
+ def no_span_sort(self):
+ """Indicates that the geometry's Spans should never be sorted with those from other
+ Drawables that will render in the same pass"""
return False
@property
- def requires_span_sort(self):
- """Indicates that the geometry's Spans must be sorted with those from other Drawables that
- will render in the same pass"""
+ def requires_actor(self):
+ """Indicates if this modifier requires the object to be a movable actor"""
return False
# Guess what?
diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py
index befec0d..856840c 100644
--- a/korman/properties/modifiers/render.py
+++ b/korman/properties/modifiers/render.py
@@ -16,6 +16,7 @@
import bpy
from bpy.props import *
+import functools
from PyHSPlasma import *
from .base import PlasmaModifierProperties, PlasmaModifierUpgradable
@@ -24,6 +25,96 @@ from ...exporter import utils
from ...exporter.explosions import ExportError
from ... import idprops
+class PlasmaDecalManagerRef(bpy.types.PropertyGroup):
+ enabled = BoolProperty(name="Enabled",
+ default=True,
+ options=set())
+
+ name = StringProperty(name="Decal Name",
+ options=set())
+
+
+class PlasmaDecalMod:
+ def _iter_decals(self, func):
+ for decal_ref in self.managers:
+ if decal_ref.enabled:
+ func(decal_ref.name)
+
+ @classmethod
+ def register(cls):
+ cls.managers = CollectionProperty(type=PlasmaDecalManagerRef)
+ cls.active_manager_index = IntProperty(options={"HIDDEN"})
+
+
+class PlasmaDecalPrintMod(PlasmaDecalMod, PlasmaModifierProperties):
+ pl_id = "decal_print"
+
+ bl_category = "Render"
+ bl_label = "Print Decal"
+ bl_description = "Prints a decal onto an object"
+
+ decal_type = EnumProperty(name="Decal Type",
+ description="Type of decal to print onto another object",
+ items=[("DYNAMIC", "Dynamic", "This object prints a decal onto dynamic decal surfaces"),
+ ("STATIC", "Static", "This object is a decal itself")],
+ options=set())
+
+ # Dynamic Decals
+ length = FloatProperty(name="Length",
+ min=0.1, soft_max=30.0, precision=2,
+ default=0.45,
+ options=set())
+ width = FloatProperty(name="Width",
+ min=0.1, soft_max=30.0, precision=2,
+ default=0.9,
+ options=set())
+ height = FloatProperty(name="Height",
+ min=0.1, soft_max=30.0, precision=2,
+ default=1.0,
+ options=set())
+
+ @property
+ def copy_material(self):
+ return self.decal_type == "STATIC"
+
+ def get_key(self, exporter, so):
+ if self.decal_type == "DYNAMIC":
+ pClass = plActivePrintShape if any((i.enabled for i in self.managers)) else plPrintShape
+ return exporter.mgr.find_create_key(pClass, so=so)
+
+ def export(self, exporter, bo, so):
+ if self.decal_type == "STATIC":
+ exporter.decal.export_static_decal(bo)
+ elif self.decal_type == "DYNAMIC":
+ print_shape = self.get_key(exporter, so).object
+ print_shape.length = self.length
+ print_shape.width = self.width
+ print_shape.height = self.height
+
+ def post_export(self, exporter, bo, so):
+ if self.decal_type == "DYNAMIC":
+ print_shape = self.get_key(exporter, so).object
+ f = functools.partial(exporter.decal.export_active_print_shape, print_shape)
+ self._iter_decals(f)
+
+class PlasmaDecalReceiveMod(PlasmaDecalMod, PlasmaModifierProperties):
+ pl_id = "decal_receive"
+
+ bl_category = "Render"
+ bl_label = "Receive Decal"
+ bl_description = "Allows this object to receive dynamic decals"
+
+ managers = CollectionProperty(type=PlasmaDecalManagerRef)
+ active_manager_index = IntProperty(options={"HIDDEN"})
+
+ def export(self, exporter, bo, so):
+ f = functools.partial(exporter.decal.generate_dynamic_decal, bo)
+ self._iter_decals(f)
+
+ def post_export(self, exporter, bo, so):
+ f = functools.partial(exporter.decal.add_dynamic_decal_receiver, so)
+ self._iter_decals(f)
+
class PlasmaFadeMod(PlasmaModifierProperties):
pl_id = "fademod"
@@ -81,10 +172,6 @@ class PlasmaFadeMod(PlasmaModifierProperties):
mod.farOpaq = self.far_opaq
mod.farTrans = self.far_trans
- @property
- def requires_span_sort(self):
- return True
-
class PlasmaFollowMod(idprops.IDPropObjectMixin, PlasmaModifierProperties):
pl_id = "followmod"
@@ -202,6 +289,10 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties, PlasmaMod
else:
return self.bake_type == "lightmap"
+ @property
+ def copy_material(self):
+ return self.bake_lightmap
+
def export(self, exporter, bo, so):
# If we're exporting vertex colors, who gives a rat's behind?
if not self.bake_lightmap:
diff --git a/korman/properties/modifiers/water.py b/korman/properties/modifiers/water.py
index 9920e77..e97d554 100644
--- a/korman/properties/modifiers/water.py
+++ b/korman/properties/modifiers/water.py
@@ -238,6 +238,10 @@ class PlasmaWaterModifier(idprops.IDPropMixin, PlasmaModifierProperties, bpy.typ
min=-10.0, max=10.0,
default=0.0)
+ @property
+ def copy_material(self):
+ return True
+
def export(self, exporter, bo, so):
waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
if self.wind_object:
diff --git a/korman/properties/prop_scene.py b/korman/properties/prop_scene.py
index 36d0675..81cb2c5 100644
--- a/korman/properties/prop_scene.py
+++ b/korman/properties/prop_scene.py
@@ -15,6 +15,7 @@
import bpy
from bpy.props import *
+import itertools
from ..exporter.etlight import _NUM_RENDER_LAYERS
@@ -41,10 +42,99 @@ class PlasmaBakePass(bpy.types.PropertyGroup):
default=((True,) * _NUM_RENDER_LAYERS))
+class PlasmaWetDecalRef(bpy.types.PropertyGroup):
+ enabled = BoolProperty(name="Enabled",
+ default=True,
+ options=set())
+
+ name = StringProperty(name="Decal Name",
+ description="Wet decal manager",
+ options=set())
+
+
+class PlasmaDecalManager(bpy.types.PropertyGroup):
+ def _get_display_name(self):
+ return self.name
+ def _set_display_name(self, value):
+ prev_value = self.name
+ for i in bpy.data.objects:
+ decal_receive = i.plasma_modifiers.decal_receive
+ decal_print = i.plasma_modifiers.decal_print
+ for j in itertools.chain(decal_receive.managers, decal_print.managers):
+ if j.name == prev_value:
+ j.name = value
+ for i in bpy.context.scene.plasma_scene.decal_managers:
+ for j in i.wet_managers:
+ if j.name == prev_value:
+ j.name = value
+ self.name = value
+
+ name = StringProperty(name="Decal Name",
+ options=set())
+ display_name = StringProperty(name="Display Name",
+ get=_get_display_name,
+ set=_set_display_name,
+ options=set())
+
+ decal_type = EnumProperty(name="Decal Type",
+ description="",
+ items=[("footprint_dry", "Footprint (Dry)", ""),
+ ("footprint_wet", "Footprint (Wet)", ""),
+ ("puddle", "Water Ripple (Shallow)", ""),
+ ("ripple", "Water Ripple (Deep)", "")],
+ default="footprint_dry",
+ options=set())
+ image = PointerProperty(name="Image",
+ description="",
+ type=bpy.types.Image,
+ options=set())
+ blend = EnumProperty(name="Blend Mode",
+ description="",
+ items=[("kBlendAdd", "Add", ""),
+ ("kBlendAlpha", "Alpha", ""),
+ ("kBlendMADD", "Brighten", ""),
+ ("kBlendMult", "Multiply", "")],
+ default="kBlendAlpha",
+ options=set())
+
+ length = IntProperty(name="Length",
+ description="",
+ subtype="PERCENTAGE",
+ min=0, soft_min=25, soft_max=400, default=100,
+ options=set())
+ width = IntProperty(name="Width",
+ description="",
+ subtype="PERCENTAGE",
+ min=0, soft_min=25, soft_max=400, default=100,
+ options=set())
+ intensity = IntProperty(name="Intensity",
+ description="",
+ subtype="PERCENTAGE",
+ min=0, soft_max=100, default=100,
+ options=set())
+ life_span = FloatProperty(name="Life Span",
+ description="",
+ subtype="TIME", unit="TIME",
+ min=0.0, soft_max=300.0, default=30.0,
+ options=set())
+ wet_time = FloatProperty(name="Wet Time",
+ description="How long the decal print shapes stay wet after losing contact with this surface",
+ subtype="TIME", unit="TIME",
+ min=0.0, soft_max=300.0, default=10.0,
+ options=set())
+
+ # Footprints to wet-ize
+ wet_managers = CollectionProperty(type=PlasmaWetDecalRef)
+ active_wet_index = IntProperty(options={"HIDDEN"})
+
+
class PlasmaScene(bpy.types.PropertyGroup):
bake_passes = CollectionProperty(type=PlasmaBakePass)
active_pass_index = IntProperty(options={"HIDDEN"})
+ decal_managers = CollectionProperty(type=PlasmaDecalManager)
+ active_decal_index = IntProperty(options={"HIDDEN"})
+
modifier_copy_object = PointerProperty(name="INTERNAL: Object to copy modifiers from",
options={"HIDDEN", "SKIP_SAVE"},
type=bpy.types.Object)
diff --git a/korman/ui/__init__.py b/korman/ui/__init__.py
index a292060..5f0ddca 100644
--- a/korman/ui/__init__.py
+++ b/korman/ui/__init__.py
@@ -21,6 +21,7 @@ from .ui_menus import *
from .ui_modifiers import *
from .ui_object import *
from .ui_render_layer import *
+from .ui_scene import *
from .ui_text import *
from .ui_texture import *
from .ui_toolbox import *
diff --git a/korman/ui/modifiers/render.py b/korman/ui/modifiers/render.py
index 482c412..64d3c9b 100644
--- a/korman/ui/modifiers/render.py
+++ b/korman/ui/modifiers/render.py
@@ -18,6 +18,55 @@ import bpy
from .. import ui_list
from ...exporter.mesh import _VERTEX_COLOR_LAYERS
+class DecalMgrListUI(bpy.types.UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ if item.name:
+ layout.label(item.name, icon="BRUSH_DATA")
+ layout.prop(item, "enabled", text="")
+ else:
+ layout.label("[Empty]")
+
+
+def decal_print(modifier, layout, context):
+ layout.prop(modifier, "decal_type")
+
+ layout = layout.column()
+ layout.enabled = modifier.decal_type == "DYNAMIC"
+ layout.label("Dimensions:")
+ row = layout.row(align=True)
+ row.prop(modifier, "length")
+ row.prop(modifier, "width")
+ row.prop(modifier, "height")
+ layout.separator()
+
+ ui_list.draw_modifier_list(layout, "DecalMgrListUI", modifier, "managers",
+ "active_manager_index", rows=2, maxrows=3)
+ try:
+ mgr_ref = modifier.managers[modifier.active_manager_index]
+ except:
+ pass
+ else:
+ scene = context.scene.plasma_scene
+ decal_mgr = next((i for i in scene.decal_managers if i.display_name == mgr_ref), None)
+
+ layout.alert = decal_mgr is None
+ layout.prop_search(mgr_ref, "name", scene, "decal_managers", icon="BRUSH_DATA")
+ layout.alert = False
+
+def decal_receive(modifier, layout, context):
+ ui_list.draw_modifier_list(layout, "DecalMgrListUI", modifier, "managers",
+ "active_manager_index", rows=2, maxrows=3)
+ try:
+ mgr_ref = modifier.managers[modifier.active_manager_index]
+ except:
+ pass
+ else:
+ scene = context.scene.plasma_scene
+ decal_mgr = next((i for i in scene.decal_managers if i.display_name == mgr_ref), None)
+
+ layout.alert = decal_mgr is None
+ layout.prop_search(mgr_ref, "name", scene, "decal_managers", icon="BRUSH_DATA")
+
def fademod(modifier, layout, context):
layout.prop(modifier, "fader_type")
diff --git a/korman/ui/ui_scene.py b/korman/ui/ui_scene.py
new file mode 100644
index 0000000..ec72d82
--- /dev/null
+++ b/korman/ui/ui_scene.py
@@ -0,0 +1,100 @@
+# 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
+import functools
+from . import ui_list
+
+class SceneButtonsPanel:
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.render.engine == "PLASMA_GAME"
+
+
+class DecalManagerListUI(bpy.types.UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ layout.prop(item, "display_name", emboss=False, text="", icon="BRUSH_DATA")
+
+
+class WetManagerListUI(bpy.types.UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
+ if item.name:
+ layout.label(item.name, icon="BRUSH_DATA")
+ layout.prop(item, "enabled", text="")
+ else:
+ layout.label("[Empty]")
+
+
+class PlasmaDecalManagersPanel(SceneButtonsPanel, bpy.types.Panel):
+ bl_label = "Plasma Decal Managers"
+
+ def draw(self, context):
+ layout, scene = self.layout, context.scene.plasma_scene
+
+ ui_list.draw_list(layout, "DecalManagerListUI", "scene", scene, "decal_managers",
+ "active_decal_index", name_prefix="Decal", name_prop="display_name",
+ rows=3)
+
+ try:
+ decal_mgr = scene.decal_managers[scene.active_decal_index]
+ except:
+ pass
+ else:
+ box = layout.box().column()
+
+ box.prop(decal_mgr, "decal_type")
+ box.alert = decal_mgr.image is None
+ box.prop(decal_mgr, "image")
+ box.alert = False
+ box.prop(decal_mgr, "blend")
+ box.separator()
+
+ split = box.split()
+ col = split.column(align=True)
+ col.label("Scale:")
+ col.alert = decal_mgr.decal_type in {"ripple", "puddle", "bullet", "torpedo"} \
+ and decal_mgr.length != decal_mgr.width
+ col.prop(decal_mgr, "length")
+ col.prop(decal_mgr, "width")
+
+ col = split.column()
+ col.label("Draw Settings:")
+ col.prop(decal_mgr, "intensity")
+ sub = col.row()
+ sub.active = decal_mgr.decal_type in {"footprint_dry", "footprint_wet", "bullet", "torpedo"}
+ sub.prop(decal_mgr, "life_span")
+ sub = col.row()
+ sub.active = decal_mgr.decal_type in {"puddle", "ripple"}
+ sub.prop(decal_mgr, "wet_time")
+
+ if decal_mgr.decal_type in {"puddle", "ripple"}:
+ box.separator()
+ box.label("Wet Footprints:")
+ ui_list.draw_list(box, "WetManagerListUI", "scene", decal_mgr, "wet_managers",
+ "active_wet_index", rows=2, maxrows=3)
+ try:
+ wet_ref = decal_mgr.wet_managers[decal_mgr.active_wet_index]
+ except:
+ pass
+ else:
+ wet_mgr = next((i for i in scene.decal_managers if i.name == wet_ref.name), None)
+ box.alert = getattr(wet_mgr, "decal_type", None) == "footprint_wet"
+ box.prop_search(wet_ref, "name", scene, "decal_managers", icon="NONE")
+ if wet_ref.name == decal_mgr.name:
+ box.label(text="Circular reference", icon="ERROR")