From fedb7c91f56147159710746654eeec5d95ef5e1e Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 28 Sep 2020 20:53:59 -0400 Subject: [PATCH 1/5] Add "Blending" modifier and improve render level export. This matches us up more closely with what PlasmaMAX does and allows us to really fix x-raying effects by properly forcing objects into the correct render pass. --- korman/exporter/mesh.py | 46 +++++++++---- korman/idprops.py | 3 + korman/properties/modifiers/__init__.py | 4 ++ korman/properties/modifiers/base.py | 20 ++++++ korman/properties/modifiers/render.py | 92 +++++++++++++++++++++++++ korman/ui/modifiers/render.py | 30 ++++++++ 6 files changed, 181 insertions(+), 14 deletions(-) diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index cb7d388..5da7a62 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -40,16 +40,12 @@ class _RenderLevel: _MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1) def __init__(self, bo, pass_index, blend_span=False): - self.level = 0 - if pass_index > 0: - self.major = self.MAJOR_FRAMEBUF - self.minor = pass_index * 4 + if blend_span: + self.level = self._determine_level(bo, blend_span) 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... - self.level += pass_index + self.level = 0 + # Gulp... Hope you know what you're doing... + self.minor += pass_index * 4 def __eq__(self, other): return self.level == other.level @@ -60,15 +56,38 @@ class _RenderLevel: def _get_major(self): return self.level >> self._MAJOR_SHIFT def _set_major(self, value): - self.level = ((value << self._MAJOR_SHIFT) & 0xFFFFFFFF) | self.minor + self.level = self._calc_level(value, self.minor) major = property(_get_major, _set_major) def _get_minor(self): return self.level & self._MINOR_MASK def _set_minor(self, value): - self.level = ((self.major << self._MAJOR_SHIFT) & 0xFFFFFFFF) | value + self.level = self._calc_level(self.major, value) minor = property(_get_minor, _set_minor) + def _calc_level(self, major : int, minor : int=0) -> int: + return ((major << self._MAJOR_SHIFT) & 0xFFFFFFFF) | minor + + def _determine_level(self, bo : bpy.types.Object, blend_span : bool) -> int: + mods = bo.plasma_modifiers + if mods.test_property("draw_framebuf"): + return self._calc_level(self.MAJOR_FRAMEBUF) + elif mods.test_property("draw_opaque"): + return self._calc_level(self.MAJOR_OPAQUE) + elif mods.test_property("draw_no_defer"): + blend_span = False + + blend_mod = mods.blend + if blend_mod.enabled and blend_mod.has_dependencies: + level = self._calc_level(self.MAJOR_FRAMEBUF) + for i in blend_mod.iter_dependencies(): + level = max(level, self._determine_level(i, blend_span)) + return level + 4 + elif blend_span: + return self._calc_level(self.MAJOR_BLEND) + else: + return self._calc_level(self.MAJOR_DEFAULT) + class _DrawableCriteria: def __init__(self, bo, geospan, pass_index): @@ -96,12 +115,12 @@ class _DrawableCriteria: 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)) + return not bo.plasma_modifiers.test_property("no_face_sort") 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)) + return not bo.plasma_modifiers.test_property("no_face_sort") @property def span_type(self): @@ -118,7 +137,6 @@ class _GeoData: self.vertices = [] - class _MeshManager: def __init__(self, report=None): if report is not None: diff --git a/korman/idprops.py b/korman/idprops.py index 66328d2..b7c55a6 100644 --- a/korman/idprops.py +++ b/korman/idprops.py @@ -127,6 +127,9 @@ def poll_animated_objects(self, value): def poll_camera_objects(self, value): return value.type == "CAMERA" +def poll_drawable_objects(self, value): + return value.type == "MESH" and any(value.data.materials) + def poll_empty_objects(self, value): return value.type == "EMPTY" diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py index f4905d3..ba179df 100644 --- a/korman/properties/modifiers/__init__.py +++ b/korman/properties/modifiers/__init__.py @@ -66,6 +66,10 @@ class PlasmaModifiers(bpy.types.PropertyGroup): setattr(cls, i.pl_id, bpy.props.PointerProperty(type=i)) bpy.types.Object.plasma_modifiers = bpy.props.PointerProperty(type=cls) + def test_property(self, property : str) -> bool: + """Tests a property on all enabled Plasma modifiers""" + return any((getattr(i, property) for i in self.modifiers)) + class PlasmaModifierSpec(bpy.types.PropertyGroup): pass diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py index 5f4ea34..0f52486 100644 --- a/korman/properties/modifiers/base.py +++ b/korman/properties/modifiers/base.py @@ -30,10 +30,30 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup): def destroyed(self): pass + @property + def draw_opaque(self): + """Render geometry before the avatar""" + return False + + @property + def draw_framebuf(self): + """Render geometry after the avatar but before other blended geometry""" + return False + + @property + def draw_no_defer(self): + """Disallow geometry being sorted into a blending span""" + return False + @property def enabled(self): return self.display_order >= 0 + @property + def face_sort(self): + """Indicates that the geometry's faces should be sorted by the engine""" + return False + def harvest_actors(self): return () diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py index 856840c..f49db3c 100644 --- a/korman/properties/modifiers/render.py +++ b/korman/properties/modifiers/render.py @@ -25,6 +25,98 @@ from ...exporter import utils from ...exporter.explosions import ExportError from ... import idprops +class PlasmaBlendOntoObject(bpy.types.PropertyGroup): + blend_onto = PointerProperty(name="Blend Onto", + description="Object to render first", + options=set(), + type=bpy.types.Object, + poll=idprops.poll_drawable_objects) + enabled = BoolProperty(name="Enabled", + default=True, + options=set()) + + +class PlasmaBlendMod(PlasmaModifierProperties): + pl_id = "blend" + + bl_category = "Render" + bl_label = "Blending" + bl_description = "Advanced Blending Options" + + render_level = EnumProperty(name="Render Pass", + description="Suggested render pass for this object.", + items=[("AUTO", "(Auto)", "Let Korman decide when to render this object."), + ("OPAQUE", "Before Avatar", "Prefer for the object to draw before the avatar."), + ("FRAMEBUF", "Frame Buffer", "Prefer for the object to draw after the avatar but before other blended objects."), + ("BLEND", "Blended", "Prefer for the object to draw after most other geometry in the blended pass.")], + options=set()) + sort_faces = EnumProperty(name="Sort Faces", + description="", + items=[("AUTO", "(Auto)", "Let Korman decide if faces should be sorted."), + ("ALWAYS", "Always", "Force the object's faces to be sorted."), + ("NEVER", "Never", "Force the object's faces to never be sorted.")], + options=set()) + + dependencies = CollectionProperty(type=PlasmaBlendOntoObject) + active_dependency_index = IntProperty(options={"HIDDEN"}) + + def export(self, exporter, bo, so): + # What'er you lookin at? + pass + + @property + def draw_opaque(self): + return self.render_level == "OPAQUE" + + @property + def draw_framebuf(self): + return self.render_level == "FRAMEBUF" + + @property + def draw_no_defer(self): + return self.render_level != "BLEND" + + @property + def face_sort(self): + return self.sort_faces == "ALWAYS" + + @property + def no_face_sort(self): + return self.sort_faces == "NEVER" + + @property + def has_dependencies(self): + return bool(self.dependencies) + + @property + def has_circular_dependency(self): + return self._check_circular_dependency() + + def _check_circular_dependency(self, objects=None): + if objects is None: + objects = set() + elif self.name in objects: + return True + objects.add(self.name) + + for i in self.iter_dependencies(): + # New deep copy of the set for each dependency, so an object can be reused as a + # dependant's dependant. + this_branch = set(objects) + sub_mod = i.plasma_modifiers.blend + if sub_mod.enabled and sub_mod._check_circular_dependency(this_branch): + return True + return False + + def iter_dependencies(self): + for i in (j.blend_onto for j in self.dependencies if j.blend_onto is not None and j.enabled): + yield i + + def sanity_check(self): + if self.has_circular_dependency: + raise ExportError("'{}': Circular Render Dependency detected!".format(self.name)) + + class PlasmaDecalManagerRef(bpy.types.PropertyGroup): enabled = BoolProperty(name="Enabled", default=True, diff --git a/korman/ui/modifiers/render.py b/korman/ui/modifiers/render.py index 64d3c9b..7c6dfa6 100644 --- a/korman/ui/modifiers/render.py +++ b/korman/ui/modifiers/render.py @@ -18,6 +18,36 @@ import bpy from .. import ui_list from ...exporter.mesh import _VERTEX_COLOR_LAYERS +class BlendOntoListUI(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0): + if item.blend_onto is None: + layout.label("[No Object Specified]", icon="ERROR") + else: + layout.label(item.blend_onto.name, icon="OBJECT_DATA") + layout.prop(item, "enabled", text="") + + +def blend(modifier, layout, context): + # Warn if there are render dependencies and a manual render level specification -- this + # could lead to unpredictable results. + layout.alert = modifier.render_level != "AUTO" and bool(modifier.dependencies) + layout.prop(modifier, "render_level") + layout.alert = False + layout.prop(modifier, "sort_faces") + + layout.separator() + layout.label("Render Dependencies:") + ui_list.draw_modifier_list(layout, "BlendOntoListUI", modifier, "dependencies", + "active_dependency_index", rows=2, maxrows=4) + try: + dependency_ref = modifier.dependencies[modifier.active_dependency_index] + except: + pass + else: + layout.alert = dependency_ref.blend_onto is None + layout.prop(dependency_ref, "blend_onto") + + 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: From 94398fd293b84b70f0dc44ddc2cb64e66bc86073 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 30 Sep 2020 13:16:41 -0400 Subject: [PATCH 2/5] Improve support for VtxNonPreshaded and RT Lights. This appropriately marks spans as VtxNonPreshaded if they use vertex alpha or runtime lighting. In those cases, we properly fold the runtime light color into the vertex color as expected by Plasma. --- korman/exporter/mesh.py | 169 +++++++++++++++++++++++++++++----------- 1 file changed, 125 insertions(+), 44 deletions(-) diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 5da7a62..491af68 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -14,6 +14,7 @@ # along with Korman. If not, see . import bpy +import itertools from PyHSPlasma import * from math import fabs import weakref @@ -29,6 +30,29 @@ _WARN_VERTS_PER_SPAN = 0x8000 _VERTEX_COLOR_LAYERS = {"col", "color", "colour"} +class _GeoSpan: + def __init__(self, bo, bm, geospan, pass_index=None): + self.geospan = geospan + self.pass_index = pass_index if pass_index is not None else 0 + self.mult_color = self._determine_mult_color(bo, bm) + + def _determine_mult_color(self, bo, bm): + """Determines the color all vertex colors should be multipled by in this span.""" + if self.geospan.props & plGeometrySpan.kDiffuseFoldedIn: + color = bm.diffuse_color + base_layer = self._find_bottom_of_stack() + return (color.r, color.b, color.g, base_layer.opacity) + if not bo.plasma_modifiers.lighting.preshade: + return (0.0, 0.0, 0.0, 0.0) + return (1.0, 1.0, 1.0, 1.0) + + def _find_bottom_of_stack(self) -> plLayerInterface: + base_layer = self.geospan.material.object.layers[0].object + while base_layer.underLay is not None: + base_layer = base_layer.underLay.object + return base_layer + + class _RenderLevel: MAJOR_OPAQUE = 0 MAJOR_FRAMEBUF = 1 @@ -231,15 +255,48 @@ class MeshConverter(_MeshManager): return (num_user_texs, total_texs, max_user_texs) - def _create_geospan(self, bo, mesh, bm, hsgmatKey): + def _check_vtx_alpha(self, mesh, material_idx): + if material_idx is not None: + polygons = (i for i in mesh.polygons if i.material_index == material_idx) + else: + polygons = mesh.polygons + alpha_layer = self._find_vtx_alpha_layer(mesh.vertex_colors) + if alpha_layer is None: + return False + alpha_loops = (alpha_layer[i.loop_start:i.loop_start+i.loop_total] for i in polygons) + opaque = (sum(i.color) == len(i.color) for i in itertools.chain.from_iterable(alpha_loops)) + has_alpha = not all(opaque) + return has_alpha + + def _check_vtx_nonpreshaded(self, bo, mesh, material_idx, base_layer): + def check_layer_shading_animation(layer): + if isinstance(layer, plLayerAnimationBase): + return layer.opacityCtl is not None or layer.preshadeCtl is not None or layer.runtimeCtl is not None + if layer.underLay is not None: + return check_layer_shading_animation(layer.underLay.object) + return False + + # TODO: if this is an avatar, we can't be non-preshaded. + if check_layer_shading_animation(base_layer): + return False + if base_layer.state.shadeFlags & hsGMatState.kShadeEmissive: + return False + + mods = bo.plasma_modifiers + if mods.lighting.rt_lights: + return True + if mods.lightmap.bake_lightmap: + return True + if self._check_vtx_alpha(mesh, material_idx): + return True + + return False + + def _create_geospan(self, bo, mesh, material_idx, bm, hsgmatKey): """Initializes a plGeometrySpan from a Blender Object and an hsGMaterial""" geospan = plGeometrySpan() geospan.material = hsgmatKey - # Mark us as needing a BlendSpan if the material require blending - if hsgmatKey.object.layers[0].object.state.blendFlags & hsGMatState.kBlendMask: - geospan.props |= plGeometrySpan.kRequiresBlending - # GeometrySpan format # For now, we really only care about the number of UVW Channels user_uvws, total_uvws, max_user_uvws = self._calc_num_uvchans(bo, mesh) @@ -247,10 +304,22 @@ class MeshConverter(_MeshManager): raise explosions.TooManyUVChannelsError(bo, bm, user_uvws, max_user_uvws) geospan.format = total_uvws - # Begin total guesswork WRT flags - mods = bo.plasma_modifiers - if mods.lightmap.enabled: + def is_alpha_blended(layer): + if layer.state.blendFlags & hsGMatState.kBlendMask: + return True + if layer.underLay is not None: + return is_alpha_blended(layer.underLay.object) + return False + + base_layer = hsgmatKey.object.layers[0].object + if is_alpha_blended(base_layer) or self._check_vtx_alpha(mesh, material_idx): + geospan.props |= plGeometrySpan.kRequiresBlending + if self._check_vtx_nonpreshaded(bo, mesh, material_idx, base_layer): geospan.props |= plGeometrySpan.kLiteVtxNonPreshaded + if (geospan.props & plGeometrySpan.kLiteMask) != plGeometrySpan.kLiteMaterial: + geospan.props |= plGeometrySpan.kDiffuseFoldedIn + + mods = bo.plasma_modifiers if mods.lighting.rt_lights: geospan.props |= plGeometrySpan.kPropRunTimeLight if not bm.use_shadows: @@ -292,7 +361,7 @@ class MeshConverter(_MeshManager): dspan.composeGeometry(True, True) inc_progress() - def _export_geometry(self, bo, mesh, materials, geospans): + def _export_geometry(self, bo, mesh, materials, geospans, mat2span_LUT): # Recall that materials is a mapping of exported materials to blender material indices. # Therefore, geodata maps blender material indices to working geometry data. # Maybe the logic is a bit inverted, but it keeps the inner loop simple. @@ -301,16 +370,8 @@ class MeshConverter(_MeshManager): # Locate relevant vertex color layers now... lm = bo.plasma_modifiers.lightmap - has_vtx_alpha = False - color, alpha = None, None - for vcol_layer in mesh.tessface_vertex_colors: - name = vcol_layer.name.lower() - if name in _VERTEX_COLOR_LAYERS: - color = vcol_layer.data - elif name == "autocolor" and color is None and not lm.bake_lightmap: - color = vcol_layer.data - elif name == "alpha": - alpha = vcol_layer.data + color = None if lm.bake_lightmap else self._find_vtx_color_layer(mesh.tessface_vertex_colors) + alpha = self._find_vtx_alpha_layer(mesh.tessface_vertex_colors) # Convert Blender faces into things we can stuff into libHSPlasma for i, tessface in enumerate(mesh.tessfaces): @@ -340,10 +401,8 @@ class MeshConverter(_MeshManager): else: src = alpha[i] # average color becomes the alpha value - tessface_alphas = (((src.color1[0] + src.color1[1] + src.color1[2]) / 3), - ((src.color2[0] + src.color2[1] + src.color2[2]) / 3), - ((src.color3[0] + src.color3[1] + src.color3[2]) / 3), - ((src.color4[0] + src.color4[1] + src.color4[2]) / 3)) + tessface_alphas = ((sum(src.color1) / 3), (sum(src.color2) / 3), + (sum(src.color3) / 3), (sum(src.color4) / 3)) if bumpmap is not None: gradPass = [] @@ -373,10 +432,16 @@ class MeshConverter(_MeshManager): for j, vertex in enumerate(tessface.vertices): uvws = tuple([uvw[j] for uvw in tessface_uvws]) - # Grab VCols - vertex_color = (int(tessface_colors[j][0] * 255), int(tessface_colors[j][1] * 255), - int(tessface_colors[j][2] * 255), int(tessface_alphas[j] * 255)) - has_vtx_alpha |= bool(tessface_alphas[j] < 1.0) + # Calculate vertex colors. + if mat2span_LUT: + mult_color = geospans[mat2span_LUT[tessface.material_index]].mult_color + else: + mult_color = (1.0, 1.0, 1.0, 1.0) + tessface_color, tessface_alpha = tessface_colors[j], tessface_alphas[j] + vertex_color = (int(tessface_color[0] * mult_color[0] * 255), + int(tessface_color[1] * mult_color[1] * 255), + int(tessface_color[2] * mult_color[2] * 255), + int(tessface_alpha * mult_color[0] * 255)) # Now, we'll index into the vertex dict using the per-face elements :( # We're using tuples because lists are not hashable. The many mathutils and PyHSPlasma @@ -433,7 +498,7 @@ class MeshConverter(_MeshManager): # Time to finish it up... for i, data in enumerate(geodata.values()): - geospan = geospans[i][0] + geospan = geospans[i].geospan numVerts = len(data.vertices) numUVs = geospan.format & plGeometrySpan.kUVCountMask @@ -454,10 +519,6 @@ class MeshConverter(_MeshManager): uvMap[numUVs - 1].normalize() vtx.uvs = uvMap - # Mark us for blending if we have a alpha vertex paint layer - if has_vtx_alpha: - geospan.props |= plGeometrySpan.kRequiresBlending - # If we're still here, let's add our data to the GeometrySpan geospan.indices = data.triangles geospan.vertices = data.vertices @@ -540,18 +601,18 @@ class MeshConverter(_MeshManager): return None # Step 1: Export all of the doggone materials. - geospans = self._export_material_spans(bo, mesh, materials) + geospans, mat2span_LUT = self._export_material_spans(bo, mesh, materials) # Step 2: Export Blender mesh data to Plasma GeometrySpans - self._export_geometry(bo, mesh, materials, geospans) + self._export_geometry(bo, mesh, materials, geospans, mat2span_LUT) # Step 3: Add plGeometrySpans to the appropriate DSpan and create indices _diindices = {} - for geospan, pass_index in geospans: - dspan = self._find_create_dspan(bo, geospan, pass_index) + for i in geospans: + dspan = self._find_create_dspan(bo, i.geospan, i.pass_index) self._report.msg("Exported hsGMaterial '{}' geometry into '{}'", - geospan.material.name, dspan.key.name, indent=1) - idx = dspan.addSourceSpan(geospan) + i.geospan.material.name, dspan.key.name, indent=1) + idx = dspan.addSourceSpan(i.geospan) diidx = _diindices.setdefault(dspan, []) diidx.append(idx) @@ -571,20 +632,25 @@ class MeshConverter(_MeshManager): 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][1]) - geospan = self._create_geospan(bo, mesh, materials[0][1], matKey) + blmat = materials[0][1] + matKey = self.material.export_waveset_material(bo, blmat) + geospan = self._create_geospan(bo, mesh, None, blmat, 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)] + return [_GeoSpan(bo, blmat, geospan)], None else: geospans = [None] * len(materials) - for i, (_, blmat) in enumerate(materials): + mat2span_LUT = {} + for i, (blmat_idx, 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 + geospans[i] = _GeoSpan(bo, blmat, + self._create_geospan(bo, mesh, blmat_idx, blmat, matKey), + blmat.pass_index) + mat2span_LUT[blmat_idx] = i + return geospans, mat2span_LUT def _find_create_dspan(self, bo, geospan, pass_index): location = self._mgr.get_location(bo) @@ -620,6 +686,21 @@ class MeshConverter(_MeshManager): else: return self._dspans[location][crit] + def _find_vtx_alpha_layer(self, color_collection): + alpha_layer = next((i for i in color_collection if i.name.lower() == "alpha"), None) + if alpha_layer is not None: + return alpha_layer.data + return None + + def _find_vtx_color_layer(self, color_collection): + manual_layer = next((i for i in color_collection if i.name.lower() in _VERTEX_COLOR_LAYERS), None) + if manual_layer is not None: + return manual_layer.data + baked_layer = color_collection.get("autocolor") + if baked_layer is not None: + return baked_layer.data + return None + @property def _mgr(self): return self._exporter().mgr From a203e109ba5c5d3f72564cbfe0acf4ea3b442570 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 30 Sep 2020 20:39:09 -0400 Subject: [PATCH 3/5] Properly set the layer colors and emissive flag. This fixes the layer preshade color to be black when runtime lighting is requested. Further, both preshade and runtime are properly set to black for emissive layers. --- korman/exporter/material.py | 55 +++++++++++++++++++++++++------------ korman/exporter/mesh.py | 2 +- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 4b973e4..9bdc221 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -147,6 +147,16 @@ class MaterialConverter: "transformCtl": self._export_layer_transform_animation, } + def calc_material_ambient(self, bo, bm) -> hsColorRGBA: + emit_scale = bm.emit * 0.5 + if emit_scale > 0.0: + return hsColorRGBA(bm.diffuse_color.r * emit_scale, + bm.diffuse_color.g * emit_scale, + bm.diffuse_color.b * emit_scale, + 1.0) + else: + return utils.color(bpy.context.scene.world.ambient_color) + def _can_export_texslot(self, slot): if slot is None or not slot.use: return False @@ -187,7 +197,9 @@ class MaterialConverter: self._report.msg("Exporting Material '{}' as single user '{}'", bm.name, mat_name, indent=1) hgmat = None else: - mat_name = bm.name + # Ensure that RT-lit objects don't infect the static-lit objects. + mat_prefix = "RTLit_" if bo.plasma_modifiers.lighting.rt_lights else "" + mat_name = "".join((mat_prefix, bm.name)) self._report.msg("Exporting Material '{}'", mat_name, indent=1) hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo) if hsgmat is not None: @@ -222,7 +234,8 @@ class MaterialConverter: if slot.use_stencil: stencils.append((idx, slot)) else: - tex_layer = self.export_texture_slot(bo, bm, hsgmat, slot, idx) + tex_name = "{}_{}".format(mat_name, slot.name) + tex_layer = self.export_texture_slot(bo, bm, hsgmat, slot, idx, name=tex_name) if restart_pass_next: tex_layer.state.miscFlags |= hsGMatState.kMiscRestartPassHere restart_pass_next = False @@ -249,7 +262,7 @@ class MaterialConverter: # Plasma makes several assumptions that every hsGMaterial has at least one layer. If this # material had no Textures, we will need to initialize a default layer if not hsgmat.layers: - layer = self._mgr.find_create_object(plLayer, name="{}_AutoLayer".format(bm.name), bl=bo) + layer = self._mgr.find_create_object(plLayer, name="{}_AutoLayer".format(mat_name), bl=bo) self._propagate_material_settings(bo, bm, layer) hsgmat.addLayer(layer.key) @@ -349,7 +362,7 @@ class MaterialConverter: return hsgmat.key def export_bumpmap_slot(self, bo, bm, hsgmat, slot, idx): - name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name) + name = "{}_{}".format(hsgmat.key.name, slot.name) self._report.msg("Exporting Plasma Bumpmap Layers for '{}'", name, indent=2) # Okay, now we need to make 3 layers for the Du, Dw, and Dv @@ -1163,6 +1176,20 @@ class MaterialConverter: def get_bump_layer(self, bo): return self._bump_mats.get(bo, None) + def get_material_preshade(self, bo, bm, color=None) -> hsColorRGBA: + if bo.plasma_modifiers.lighting.rt_lights or bm.emit: + return hsColorRGBA.kBlack + if color is None: + color = bm.diffuse_color + return utils.color(color) + + def get_material_runtime(self, bo, bm, color=None) -> hsColorRGBA: + if bm.emit: + return hsColorRGBA.kBlack + if color is None: + color = bm.diffuse_color + return utils.color(color) + def get_texture_animation_key(self, bo, bm, texture): """Finds or creates the appropriate key for sending messages to an animated Texture""" @@ -1204,23 +1231,17 @@ class MaterialConverter: if bm.use_shadeless: state.shadeFlags |= hsGMatState.kShadeWhite + if bm.emit: + state.shadeFlags |= hsGMatState.kShadeEmissive + # Colors - layer.ambient = utils.color(bpy.context.scene.world.ambient_color) - layer.preshade = utils.color(bm.diffuse_color) - layer.runtime = utils.color(bm.diffuse_color) + layer.ambient = self.calc_material_ambient(bo, bm) + layer.preshade = self.get_material_preshade(bo, bm) + layer.runtime = self.get_material_runtime(bo, bm) layer.specular = utils.color(bm.specular_color) layer.specularPower = min(100.0, float(bm.specular_hardness)) - layer.LODBias = -1.0 # Seems to be the Plasma default - - if bm.emit > 0.0: - # Use the diffuse colour as the emit, scaled by the emit amount - # (maximum 2.0, so we'll also scale that by 0.5) - emit_scale = bm.emit * 0.5 - layer.ambient = hsColorRGBA(bm.diffuse_color.r * emit_scale, - bm.diffuse_color.g * emit_scale, - bm.diffuse_color.b * emit_scale, - 1.0) + layer.LODBias = -1.0 def _requires_single_user(self, bo, bm): if bo.data.show_double_sided: diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 491af68..1f1dc7d 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -279,7 +279,7 @@ class MeshConverter(_MeshManager): # TODO: if this is an avatar, we can't be non-preshaded. if check_layer_shading_animation(base_layer): return False - if base_layer.state.shadeFlags & hsGMatState.kShadeEmissive: + if material_idx is not None and mesh.materials[material_idx].emit: return False mods = bo.plasma_modifiers From b0c552ae6a76f556403e2c265529afb873442c52 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 30 Sep 2020 20:40:24 -0400 Subject: [PATCH 4/5] Kickables should be runtime lit. --- korman/properties/modifiers/render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py index f49db3c..3683039 100644 --- a/korman/properties/modifiers/render.py +++ b/korman/properties/modifiers/render.py @@ -531,6 +531,8 @@ class PlasmaLightingMod(PlasmaModifierProperties): return True if self.id_data.plasma_object.has_transform_animation: return True + if mods.collision.enabled and mods.collision.dynamic: + return True return False From 3abb36b4c4629266ef1a935f44bdce406c83d828 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 5 Oct 2020 19:13:06 -0400 Subject: [PATCH 5/5] Partially revert the emissive stuff. --- korman/exporter/material.py | 26 +++++++++++++------------- korman/exporter/mesh.py | 9 +++++++-- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 9bdc221..d2a2ba9 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -147,16 +147,6 @@ class MaterialConverter: "transformCtl": self._export_layer_transform_animation, } - def calc_material_ambient(self, bo, bm) -> hsColorRGBA: - emit_scale = bm.emit * 0.5 - if emit_scale > 0.0: - return hsColorRGBA(bm.diffuse_color.r * emit_scale, - bm.diffuse_color.g * emit_scale, - bm.diffuse_color.b * emit_scale, - 1.0) - else: - return utils.color(bpy.context.scene.world.ambient_color) - def _can_export_texslot(self, slot): if slot is None or not slot.use: return False @@ -1176,15 +1166,25 @@ class MaterialConverter: def get_bump_layer(self, bo): return self._bump_mats.get(bo, None) + def get_material_ambient(self, bo, bm) -> hsColorRGBA: + emit_scale = bm.emit * 0.5 + if emit_scale > 0.0: + return hsColorRGBA(bm.diffuse_color.r * emit_scale, + bm.diffuse_color.g * emit_scale, + bm.diffuse_color.b * emit_scale, + 1.0) + else: + return utils.color(bpy.context.scene.world.ambient_color) + def get_material_preshade(self, bo, bm, color=None) -> hsColorRGBA: - if bo.plasma_modifiers.lighting.rt_lights or bm.emit: + if bo.plasma_modifiers.lighting.rt_lights: return hsColorRGBA.kBlack if color is None: color = bm.diffuse_color return utils.color(color) def get_material_runtime(self, bo, bm, color=None) -> hsColorRGBA: - if bm.emit: + if not bo.plasma_modifiers.lighting.rt_lights: return hsColorRGBA.kBlack if color is None: color = bm.diffuse_color @@ -1235,7 +1235,7 @@ class MaterialConverter: state.shadeFlags |= hsGMatState.kShadeEmissive # Colors - layer.ambient = self.calc_material_ambient(bo, bm) + layer.ambient = self.get_material_ambient(bo, bm) layer.preshade = self.get_material_preshade(bo, bm) layer.runtime = self.get_material_runtime(bo, bm) layer.specular = utils.color(bm.specular_color) diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 1f1dc7d..03625ba 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -279,8 +279,13 @@ class MeshConverter(_MeshManager): # TODO: if this is an avatar, we can't be non-preshaded. if check_layer_shading_animation(base_layer): return False - if material_idx is not None and mesh.materials[material_idx].emit: - return False + + # Reject emissive and shadeless because the kLiteMaterial equation has lots of options + # that are taken away by VtxNonPreshaded that are useful here. + if material_idx is not None: + bm = mesh.materials[material_idx] + if bm.emit or bm.use_shadeless: + return False mods = bo.plasma_modifiers if mods.lighting.rt_lights: