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")