diff --git a/korman/exporter/material.py b/korman/exporter/material.py
index 3775162..d63a797 100644
--- a/korman/exporter/material.py
+++ b/korman/exporter/material.py
@@ -211,6 +211,22 @@ class MaterialConverter:
# Looks like we're done...
return hsgmat.key
+ def export_waveset_material(self, bo, bm):
+ print(" Exporting WaveSet Material '{}'".format(bm.name))
+
+ # WaveSets MUST have their own material
+ unique_name = "{}_WaveSet7".format(bm.name)
+ hsgmat = self._mgr.add_object(hsGMaterial, name=unique_name, bl=bo)
+
+ # Materials MUST have one layer. Wavesets need alpha blending...
+ layer = self._mgr.add_object(plLayer, name=unique_name, bl=bo)
+ self._propagate_material_settings(bm, layer)
+ layer.state.blendFlags |= hsGMatState.kBlendAlpha
+ hsgmat.addLayer(layer.key)
+
+ # Wasn't that easy?
+ return hsgmat.key
+
def _export_texture_slot(self, bo, bm, hsgmat, slots, idx):
slot = slots[idx]
num_exported = 1
diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py
index 0e5e287..faee86b 100644
--- a/korman/exporter/mesh.py
+++ b/korman/exporter/mesh.py
@@ -37,10 +37,9 @@ class _RenderLevel:
_MAJOR_SHIFT = 28
_MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1)
- def __init__(self, hsgmat, pass_index, blendSpan=False):
+ def __init__(self, bo, hsgmat, pass_index, blendSpan=False):
self.level = 0
- # Naive... BlendSpans (any blending on the first layer) are MAJOR_BLEND
if blendSpan:
self.major = self.MAJOR_DEFAULT
@@ -68,15 +67,22 @@ class _RenderLevel:
class _DrawableCriteria:
- def __init__(self, hsgmat, pass_index):
+ def __init__(self, bo, hsgmat, pass_index):
for layer in hsgmat.layers:
if layer.object.state.blendFlags & hsGMatState.kBlendMask:
self.blend_span = True
break
else:
self.blend_span = False
- self.criteria = 0 # TODO
- self.render_level = _RenderLevel(hsgmat, pass_index, self.blend_span)
+
+ 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.sort_spans |= plDrawable.kCritSortSpans
+ self.render_level = _RenderLevel(bo, hsgmat, pass_index, self.blend_span)
def __eq__(self, other):
if not isinstance(other, _DrawableCriteria):
@@ -304,19 +310,34 @@ class MeshConverter:
def _export_material_spans(self, bo, mesh, materials):
"""Exports all Materials and creates plGeometrySpans"""
- geospans = [None] * len(materials)
- for i, blmat in enumerate(materials):
- matKey = self.material.export_material(bo, blmat)
- geospans[i] = (self._create_geospan(bo, mesh, blmat, matKey), blmat.pass_index)
- return geospans
+ waveset_mod = bo.plasma_modifiers.water_basic
+ if waveset_mod.enabled:
+ if len(materials) > 1:
+ msg = "'{}' is a WaveSet -- only one material is supported".format(bo.name)
+ self._exporter().report.warn(msg, indent=1)
+ matKey = self.material.export_waveset_material(bo, materials[0])
+ geospan = self._create_geospan(bo, mesh, materials[0], matKey)
+
+ # FIXME: Can some of this be generalized?
+ geospan.props |= (plGeometrySpan.kWaterHeight | plGeometrySpan.kLiteVtxNonPreshaded |
+ plGeometrySpan.kPropReverseSort | plGeometrySpan.kPropNoShadow)
+ geospan.waterHeight = bo.location[2]
+ return [(geospan, 0)]
+ else:
+ geospans = [None] * len(materials)
+ for i, blmat in enumerate(materials):
+ matKey = self.material.export_material(bo, blmat)
+ geospans[i] = (self._create_geospan(bo, mesh, blmat, matKey), blmat.pass_index)
+ return geospans
def _export_static_lighting(self, bo):
helpers.make_active_selection(bo)
- lm = bo.plasma_modifiers.lightmap
+ mods = bo.plasma_modifiers
+ lm = mods.lightmap
if lm.enabled:
print(" Baking lightmap...")
bpy.ops.object.plasma_lightmap_autobake(light_group=lm.light_group)
- else:
+ elif not mods.water_basic.enabled:
for vcol_layer in bo.data.vertex_colors:
name = vcol_layer.name.lower()
if name in _VERTEX_COLOR_LAYERS:
@@ -325,7 +346,6 @@ class MeshConverter:
print(" Baking crappy vertex color lighting...")
bpy.ops.object.plasma_vertexlight_autobake()
-
def _find_create_dspan(self, bo, hsgmat, pass_index):
location = self._mgr.get_location(bo)
if location not in self._dspans:
@@ -333,10 +353,11 @@ class MeshConverter:
# This is where we figure out which DSpan this goes into. To vaguely summarize the rules...
# BlendSpans: anything with an alpha blended layer
- # [... document me ...]
+ # SortSpans: means we should sort the spans in this DSpan with all other span in this pass
+ # SortFaces: means we should sort the faces in this span only
# We're using pass index to do just what it was designed for. Cyan has a nicer "depends on"
# draw component, but pass index is the Blender way, so that's what we're doing.
- crit = _DrawableCriteria(hsgmat, pass_index)
+ crit = _DrawableCriteria(bo, hsgmat, pass_index)
if crit not in self._dspans[location]:
# AgeName_[District_]_Page_RenderLevel_Crit[Blend]Spans
@@ -345,8 +366,12 @@ class MeshConverter:
name = "{}_{:08X}_{:X}{}".format(node.name, crit.render_level.level, crit.criteria, crit.span_type)
dspan = self._mgr.add_object(pl=plDrawableSpans, name=name, loc=location)
- dspan.criteria = crit.criteria
- # TODO: props
+ criteria = crit.criteria
+ dspan.criteria = criteria
+ if criteria & plDrawable.kCritSortFaces:
+ dspan.props |= plDrawable.kPropSortFaces
+ if criteria & plDrawable.kCritSortSpans:
+ dspan.props |= plDrawable.kPropSortSpans
dspan.renderLevel = crit.render_level.level
dspan.sceneNode = node # AddViaNotify
diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py
index 86b9a5e..6223ac8 100644
--- a/korman/properties/modifiers/__init__.py
+++ b/korman/properties/modifiers/__init__.py
@@ -22,6 +22,7 @@ from .logic import *
from .physics import *
from .region import *
from .render import *
+from .water import *
class PlasmaModifiers(bpy.types.PropertyGroup):
def determine_next_id(self):
diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py
index 3aa81ed..e1a50b6 100644
--- a/korman/properties/modifiers/base.py
+++ b/korman/properties/modifiers/base.py
@@ -37,6 +37,17 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
"""Indicates if this modifier requires the object to be a movable actor"""
return False
+ @property
+ def requires_face_sort(self):
+ """Indicates that the geometry's faces must be sorted by the engine"""
+ 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"""
+ return False
+
# Guess what?
# You can't register properties on a base class--Blender isn't smart enough to do inheritance,
# you see... So, we'll store our definitions in a dict and make those properties on each subclass
diff --git a/korman/properties/modifiers/water.py b/korman/properties/modifiers/water.py
new file mode 100644
index 0000000..04ceb4f
--- /dev/null
+++ b/korman/properties/modifiers/water.py
@@ -0,0 +1,292 @@
+# 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 bpy.props import *
+import math
+from PyHSPlasma import *
+
+from .base import PlasmaModifierProperties
+from ...exporter import ExportError
+
+class PlasmaWaterModifier(PlasmaModifierProperties, bpy.types.PropertyGroup):
+ pl_id = "water_basic"
+
+ bl_category = "Water"
+ bl_label = "Basic Water"
+ bl_description = "Basic water properties"
+
+ wind_object_name = StringProperty(name="Wind Object",
+ description="Object whose Y axis represents the wind direction")
+ wind_speed = FloatProperty(name="Wind Speed",
+ description="Magnitude of the wind",
+ default=1.0)
+
+ specular_tint = FloatVectorProperty(name="Specular Tint",
+ subtype="COLOR",
+ min=0.0, max=1.0,
+ default=(1.0, 1.0, 1.0))
+ specular_alpha = FloatProperty(name="Specular Alpha",
+ min=0.0, max=1.0,
+ default=0.3)
+ noise = IntProperty(name="Noise",
+ subtype="PERCENTAGE",
+ min=0, max=300,
+ default=50)
+ specular_start = FloatProperty(name="Specular Start",
+ min=0.0, max=1000.0,
+ default=50.0)
+ specular_end = FloatProperty(name="Specular End",
+ min=0.0, max=10000.0,
+ default=1000.0)
+ ripple_scale = FloatProperty(name="Ripple Scale",
+ min=5.0, max=1000.0,
+ default=25.0)
+
+ depth_opacity = FloatProperty(name="Opacity End",
+ min=0.5, max=20.0,
+ default=3.0)
+ depth_reflection = FloatProperty(name="Reflection End",
+ min=0.5, max=20.0,
+ default=3.0)
+ depth_wave = FloatProperty(name="Wave End",
+ min=0.5, max=20.0,
+ default=4.0)
+ zero_opacity = FloatProperty(name="Opacity Start",
+ min=-10.0, max=10.0,
+ default=-1.0)
+ zero_reflection = FloatProperty(name="Reflection Start",
+ min=-10.0, max=10.0,
+ default=0.0)
+ zero_wave = FloatProperty(name="Wave Start",
+ min=-10.0, max=10.0,
+ default=0.0)
+
+ def created(self, obj):
+ self.display_name = "{}_WaveSet7".format(obj.name)
+
+ def export(self, exporter, bo, so):
+ waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
+ if self.wind_object_name:
+ wind_obj = bpy.data.objects.get(self.wind_object_name, None)
+ if wind_obj is None:
+ raise ExportError("{}: Wind Object '{}' not found".format(self.display_name, self.wind_object_name))
+ if wind_obj.plasma_object.enabled and wind_obj.plasma_modifiers.animation.enabled:
+ waveset.refObj = exporter.mgr.find_create_key(plSceneObject, bl=wind_obj)
+ waveset.setFlag(plWaveSet7.kHasRefObject, True)
+
+ # This is much like what happened in PyPRP
+ speed = self.wind_speed
+ matrix = wind_obj.maitrx_world
+ wind_dir = hsVector3(matrix[1][0] * speed, matrix[1][1] * speed, matrix[1][2] * speed)
+ else:
+ # Stolen shamelessly from PyPRP
+ wind_dir = hsVector3(0.0871562, 0.996195, 0.0)
+
+ # Stuff we expose
+ state = waveset.state
+ state.rippleScale = self.ripple_scale
+ state.waterHeight = bo.location[2]
+ state.windDir = wind_dir
+ state.specVector = hsVector3(self.noise / 100, self.specular_start, self.specular_end)
+ state.specularTint = hsColorRGBA(*self.specular_tint, alpha=self.specular_alpha)
+ state.waterOffset = hsVector3(self.zero_opacity * -1.0, self.zero_reflection * -1.0, self.zero_wave * -1.0)
+ state.depthFalloff = hsVector3(self.depth_opacity, self.depth_reflection, self.depth_wave)
+
+ # These are either unused, set from somewhere else at runtime, or hardcoded
+ state.waterTint = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
+ state.maxColor = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
+ state.shoreTint = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
+ state.maxAtten = hsVector3(1.0, 1.0, 1.0)
+ state.minAtten = hsVector3(0.0, 0.0, 0.0)
+
+ # Now, we have some related modifiers that may or may not be enabled... If not, we should
+ # make them export their defaults.
+ mods = bo.plasma_modifiers
+ if not mods.water_geostate.enabled:
+ mods.water_geostate.convert_default_wavestate(state.geoState)
+ if not mods.water_texstate.enabled:
+ mods.water_texstate.convert_default_wavestate(state.texState)
+ if not mods.water_shore.enabled:
+ mods.water_shore.convert_default(state)
+
+
+class PlasmaShoreObject(bpy.types.PropertyGroup):
+ display_name = StringProperty(name="Display Name")
+ object_name = StringProperty(name="Shore Object",
+ description="Object that waves crash upon")
+
+
+class PlasmaWaterShoreModifier(PlasmaModifierProperties):
+ pl_depends = {"water_basic"}
+ pl_id = "water_shore"
+
+ bl_category = "Water"
+ bl_label = "Water Shore"
+ bl_description = ""
+
+ # The basic modifier may want to export a default copy of us
+ _shore_tint_default = (0.2, 0.4, 0.4)
+ _shore_opacity_default = 40
+ _wispiness_default = 50
+ _period_default = 100.0
+ _finger_default = 100.0
+ _edge_opacity_default = 100
+ _edge_radius_default = 100.0
+
+ shores = CollectionProperty(type=PlasmaShoreObject)
+ active_shore_index = IntProperty(options={"HIDDEN"})
+
+ shore_tint = FloatVectorProperty(name="Shore Tint",
+ subtype="COLOR",
+ min=0.0, max=1.0,
+ default=_shore_tint_default)
+ shore_opacity = IntProperty(name="Shore Opacity",
+ subtype="PERCENTAGE",
+ min=0, max=100,
+ default=_shore_opacity_default)
+ wispiness = IntProperty(name="Wispiness",
+ subtype="PERCENTAGE",
+ min=0, max=200,
+ default=_wispiness_default)
+
+ period = FloatProperty(name="Period",
+ min=0.0, max=200.0,
+ default=_period_default)
+ finger = FloatProperty(name="Finger",
+ min=50.0, max=300.0,
+ default=_finger_default)
+ edge_opacity = IntProperty(name="Edge Opacity",
+ subtype="PERCENTAGE",
+ min=0, max=100,
+ default=_edge_opacity_default)
+ edge_radius = FloatProperty(name="Edge Radius",
+ subtype="PERCENTAGE",
+ min=50, max=300,
+ default=_edge_radius_default)
+
+ def convert_default(self, wavestate):
+ wavestate.wispiness = self._wispiness_default / 100.0
+ wavestate.minColor = hsColorRGBA(*self._shore_tint_default, alpha=(self._shore_opacity_default / 100.0))
+ wavestate.edgeOpacity = self._edge_opacity_default / 100.0
+ wavestate.edgeRadius = self._edge_radius_default / 100.0
+ wavestate.period = self._period_default / 100.0
+ wavestate.fingerLength = self._finger_default / 100.0
+
+ def created(self, obj):
+ self.display_name = "{}_WaterShore".format(obj.name)
+
+ def export(self, exporter, bo, so):
+ waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
+ wavestate = waveset.state
+
+ for i in self.shores:
+ shore = bpy.data.objects.get(i.object_name, None)
+ if shore is None:
+ raise ExportError("'{}': Shore Object '{}' does not exist".format(self.display_name, i.object_name))
+ waveset.addShore(exporter.mgr.find_create_key(plSceneObject, bl=shore))
+
+ wavestate.wispiness = self.wispiness / 100.0
+ wavestate.minColor = hsColorRGBA(*self.shore_tint, alpha=(self.shore_opacity / 100.0))
+ wavestate.edgeOpacity = self.edge_opacity / 100.0
+ wavestate.edgeRadius = self.edge_radius / 100.0
+ wavestate.period = self.period / 100.0
+ wavestate.fingerLength = self.finger / 100.0
+
+
+class PlasmaWaveState:
+ pl_depends = {"water_basic"}
+
+ def convert_wavestate(self, state):
+ state.minLength = self.min_length
+ state.maxLength = self.max_length
+ state.ampOverLen = self.amplitude / 100.0
+ state.chop = self.chop / 100.0
+ state.angleDev = self.angle_dev
+
+ def convert_default_wavestate(self, state):
+ cls = self.__class__
+ state.minLength = cls._min_length_default
+ state.maxLength = cls._max_length_default
+ state.ampOverLen = cls._amplitude_default / 100.0
+ state.chop = cls._chop_default / 100.0
+ state.angleDev = cls._angle_dev_default
+
+ @classmethod
+ def register(cls):
+ cls.min_length = FloatProperty(name="Min Length",
+ description="Smallest wave length",
+ min=0.1, max=50.0,
+ default=cls._min_length_default)
+ cls.max_length = FloatProperty(name="Max Length",
+ description="Largest wave length",
+ min=0.1, max=50.0,
+ default=cls._max_length_default)
+ cls.amplitude = IntProperty(name="Amplitude",
+ description="Multiplier for wave height",
+ subtype="PERCENTAGE",
+ min=0, max=100,
+ default=cls._amplitude_default)
+ cls.chop = IntProperty(name="Choppiness",
+ description="Sharpness of wave crests",
+ subtype="PERCENTAGE",
+ min=0, max=500,
+ default=cls._chop_default)
+ cls.angle_dev = FloatProperty(name="Wave Spread",
+ subtype="ANGLE",
+ min=math.radians(0.0), max=math.radians(180.0),
+ default=cls._angle_dev_default)
+
+
+class PlasmaWaveGeoState(PlasmaWaveState, PlasmaModifierProperties):
+ pl_id = "water_geostate"
+
+ bl_category = "Water"
+ bl_label = "Geometry Waves"
+ bl_description = "Mesh wave settings"
+
+ _min_length_default = 4.0
+ _max_length_default = 8.0
+ _amplitude_default = 10
+ _chop_default = 50
+ _angle_dev_default = math.radians(20.0)
+
+ def created(self, obj):
+ self.display_name = "{}_WaveGeoState".format(obj.name)
+
+ def export(self, exporter, bo, so):
+ waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
+ self.convert_wavestate(waveset.state.geoState)
+
+
+class PlasmaWaveTexState(PlasmaWaveState, PlasmaModifierProperties):
+ pl_id = "water_texstate"
+
+ bl_category = "Water"
+ bl_label = "Texture Waves"
+ bl_description = "Texture wave settings"
+
+ _min_length_default = 0.1
+ _max_length_default = 4.0
+ _amplitude_default = 10
+ _chop_default = 50
+ _angle_dev_default = math.radians(20.0)
+
+ def created(self, obj):
+ self.display_name = "{}_WaveTexState".format(obj.name)
+
+ def export(self, exporter, bo, so):
+ waveset = exporter.mgr.find_create_object(plWaveSet7, name=bo.name, so=so)
+ self.convert_wavestate(waveset.state.texState)
diff --git a/korman/ui/modifiers/__init__.py b/korman/ui/modifiers/__init__.py
index 0a838df..5cf2fe7 100644
--- a/korman/ui/modifiers/__init__.py
+++ b/korman/ui/modifiers/__init__.py
@@ -19,3 +19,4 @@ from .logic import *
from .physics import *
from .region import *
from .render import *
+from .water import *
diff --git a/korman/ui/modifiers/water.py b/korman/ui/modifiers/water.py
new file mode 100644
index 0000000..918edca
--- /dev/null
+++ b/korman/ui/modifiers/water.py
@@ -0,0 +1,104 @@
+# 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
+
+def water_basic(modifier, layout, context):
+ layout.prop_search(modifier, "wind_object_name", bpy.data, "objects")
+
+ row = layout.row()
+ row.prop(modifier, "wind_speed")
+ layout.separator()
+
+ split = layout.split()
+ col = split.column()
+ col.prop(modifier, "specular_tint")
+ col.prop(modifier, "specular_alpha", text="Alpha")
+
+ col.label("Specular:")
+ col.prop(modifier, "specular_start", text="Start")
+ col.prop(modifier, "specular_end", text="End")
+
+ col.label("Misc:")
+ col.prop(modifier, "noise")
+ col.prop(modifier, "ripple_scale")
+
+ col = split.column()
+ col.label("Opacity:")
+ col.prop(modifier, "zero_opacity", text="Start")
+ col.prop(modifier, "depth_opacity", text="End")
+
+ col.label("Reflection:")
+ col.prop(modifier, "zero_reflection", text="Start")
+ col.prop(modifier, "depth_reflection", text="End")
+
+ col.label("Wave:")
+ col.prop(modifier, "zero_wave", text="Start")
+ col.prop(modifier, "depth_wave", text="End")
+
+def _wavestate(modifier, layout, context):
+ split = layout.split()
+ col = split.column()
+ col.label("Size:")
+ col.prop(modifier, "min_length")
+ col.prop(modifier, "max_length")
+ col.prop(modifier, "amplitude")
+
+ col = split.column()
+ col.label("Behavior:")
+ col.prop(modifier, "chop")
+ col.prop(modifier, "angle_dev")
+
+water_geostate = _wavestate
+water_texstate = _wavestate
+
+class ShoreListUI(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="MOD_WAVE")
+
+
+def water_shore(modifier, layout, context):
+ row = layout.row()
+ row.template_list("ShoreListUI", "shores", modifier, "shores", modifier, "active_shore_index",
+ rows=2, maxrows=3)
+ col = row.column(align=True)
+ op = col.operator("object.plasma_modifier_collection_add", icon="ZOOMIN", text="")
+ op.modifier = modifier.pl_id
+ op.collection = "shores"
+ op.name_prefix = "Shore"
+ op.name_prop = "display_name"
+ op = col.operator("object.plasma_modifier_collection_remove", icon="ZOOMOUT", text="")
+ op.modifier = modifier.pl_id
+ op.collection = "shores"
+ op.index = modifier.active_shore_index
+
+ # Display the active shore
+ if modifier.shores:
+ shore = modifier.shores[modifier.active_shore_index]
+ layout.prop_search(shore, "object_name", bpy.data, "objects", icon="MESH_DATA")
+
+ split = layout.split()
+ col = split.column()
+ col.label("Basic:")
+ col.prop(modifier, "shore_tint")
+ col.prop(modifier, "shore_opacity")
+ col.prop(modifier, "wispiness")
+
+ col = split.column()
+ col.label("Advanced:")
+ col.prop(modifier, "period")
+ col.prop(modifier, "finger")
+ col.prop(modifier, "edge_opacity")
+ col.prop(modifier, "edge_radius")