Browse Source

Fix RT-Lit opacity animations.

This fixes a number of long standing chicken and egg problems that could
prevent opacity animations from working properly on RT-Lit materials.
Also addresses a number of odd situations where the lighting results
might drastically change in unexpected ways.
pull/283/head
Adam Johnson 3 years ago
parent
commit
33a7055a13
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 71
      korman/exporter/material.py
  2. 34
      korman/exporter/mesh.py
  3. 2
      korman/properties/modifiers/render.py

71
korman/exporter/material.py

@ -205,7 +205,8 @@ class MaterialConverter:
mat_prefix = "RTLit_" mat_prefix = "RTLit_"
else: else:
mat_prefix = "" mat_prefix = ""
mat_name = "".join((mat_prefix, bm.name)) mat_prefix2 = "NonVtxP_" if self._exporter().mesh.is_nonpreshaded(bo, bm) else ""
mat_name = "".join((mat_prefix, mat_prefix2, bm.name))
self._report.msg("Exporting Material '{}'", mat_name, indent=1) self._report.msg("Exporting Material '{}'", mat_name, indent=1)
hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo) hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo)
if hsgmat is not None: if hsgmat is not None:
@ -489,8 +490,7 @@ class MaterialConverter:
else: else:
layer_props = texture.plasma_layer layer_props = texture.plasma_layer
layer.opacity = layer_props.opacity / 100 layer.opacity = layer_props.opacity / 100
if layer_props.opacity < 100 and not state.blendFlags & hsGMatState.kBlendMask: self._handle_layer_opacity(layer, layer_props.opacity)
state.blendFlags |= hsGMatState.kBlendAlpha
if layer_props.alpha_halo: if layer_props.alpha_halo:
state.blendFlags |= hsGMatState.kBlendAlphaTestHigh state.blendFlags |= hsGMatState.kBlendAlphaTestHigh
if layer_props.z_bias: if layer_props.z_bias:
@ -602,10 +602,14 @@ class MaterialConverter:
return ctrl return ctrl
def _export_layer_opacity_animation(self, bo, bm, tex_slot, base_layer, fcurves): def _export_layer_opacity_animation(self, bo, bm, tex_slot, base_layer, fcurves):
# Dumb function to intercept the opacity values and properly flag the base layer
def process_opacity(value):
self._handle_layer_opacity(base_layer, value)
return value
for i in fcurves: for i in fcurves:
if i.data_path == "plasma_layer.opacity": if i.data_path == "plasma_layer.opacity":
base_layer.state.blendFlags |= hsGMatState.kBlendAlpha ctrl = self._exporter().animation.make_scalar_leaf_controller(i, process_opacity)
ctrl = self._exporter().animation.make_scalar_leaf_controller(i)
return ctrl return ctrl
return None return None
@ -1255,6 +1259,7 @@ class MaterialConverter:
return self._bump_mats.get(bo, None) return self._bump_mats.get(bo, None)
def get_material_ambient(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA: def get_material_ambient(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
# Although Plasma calls this the ambient color, it is actually always used as the emissive color.
emit_scale = bm.emit * 0.5 emit_scale = bm.emit * 0.5
if emit_scale > 0.0: if emit_scale > 0.0:
if color is None: if color is None:
@ -1267,17 +1272,32 @@ class MaterialConverter:
return utils.color(bpy.context.scene.world.ambient_color) return utils.color(bpy.context.scene.world.ambient_color)
def get_material_preshade(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA: def get_material_preshade(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
if bo.plasma_modifiers.lighting.rt_lights: # This color is always used for shading. In all lighting equations, it represents the world
return hsColorRGBA.kBlack # ambient color. Anyway, if we have a manual (read: animated color), just dump that out.
if color is None: if color is not None:
color = bm.diffuse_color
return utils.color(color) return utils.color(color)
# Runtime lit objects want light from runtime lights, so they have an ambient world color
# of black - and yes, this is an ambient world color. But it gets more fascinating...
# The color has been folded into the vertex colors for nonpreshaded, so for nonpreshaded,
# we'll want black if it's ONLY runtime lighting (and white for lightmaps). Otherwise,
# just use the material color for now.
if self._exporter().mesh.is_nonpreshaded(bo, bm):
if bo.plasma_modifiers.lightmap.bake_lightmap:
return hsColorRGBA.kWhite
elif not bo.plasma_modifiers.lighting.preshade:
return hsColorRGBA.kBlack
# Gulp
return utils.color(bm.diffuse_color)
def get_material_runtime(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA: def get_material_runtime(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
# If we are animated, then we will need to toss out the diffuse color animation here for # The layer runstime color has no effect if the lighting equation is kLiteVtxNonPreshaded,
# non-preshaded objects. # so return black to prevent animations from being exported.
if color is None and not bo.plasma_modifiers.lighting.preshade: if self._exporter().mesh.is_nonpreshaded(bo, bm):
return hsColorRGBA.kBlack return hsColorRGBA.kBlack
# Hmm...
if color is None: if color is None:
color = bm.diffuse_color color = bm.diffuse_color
return utils.color(color) return utils.color(color)
@ -1299,6 +1319,13 @@ class MaterialConverter:
pClass = plLayerSDLAnimation if texture is not None and texture.plasma_layer.anim_sdl_var else plLayerAnimation pClass = plLayerSDLAnimation if texture is not None and texture.plasma_layer.anim_sdl_var else plLayerAnimation
return self._mgr.find_create_key(pClass, bl=bo, name=name) return self._mgr.find_create_key(pClass, bl=bo, name=name)
def _handle_layer_opacity(self, layer: plLayerInterface, value: float):
if value < 100:
base_layer = layer.bottomOfStack.object
state = base_layer.state
if not state.blendFlags & hsGMatState.kBlendMask:
state.blendFlags |= hsGMatState.kBlendAlpha
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr
@ -1339,6 +1366,26 @@ class MaterialConverter:
layer.specularPower = min(100.0, float(bm.specular_hardness)) layer.specularPower = min(100.0, float(bm.specular_hardness))
layer.LODBias = -1.0 layer.LODBias = -1.0
def requires_material_shading(self, bm: bpy.types.Material) -> bool:
"""Determines if this material requires the lighting equation we all know and love
(kLiteMaterial) in order to display opacity and color animations."""
if bm.animation_data is not None and bm.animation_data.action is not None:
if any((i.data_path == "diffuse_color" for i in bm.animation_data.action.fcurves)):
return True
for slot in filter(lambda x: x and x.use and x.texture, bm.texture_slots):
tex = slot.texture
# TODO (someday): I think PlasmaMax will actually bake some opacities into the vertices
# so that kLiteVtxNonPreshaded can be used. Might be a good idea at some point.
if tex.plasma_layer.opacity < 100:
return True
if tex.animation_data is not None and tex.animation_data.action is not None:
if any((i.data_path == "plasma_layer.opacity" for i in tex.animation_data.action.fcurves)):
return True
return False
def _requires_single_user(self, bo, bm): def _requires_single_user(self, bo, bm):
if bo.data.show_double_sided: if bo.data.show_double_sided:
return True return True

34
korman/exporter/mesh.py

@ -18,6 +18,7 @@ from contextlib import ExitStack
import itertools import itertools
from PyHSPlasma import * from PyHSPlasma import *
from math import fabs from math import fabs
from typing import Iterable
import weakref import weakref
from ..exporter.logger import ExportProgressLogger from ..exporter.logger import ExportProgressLogger
@ -242,6 +243,7 @@ class MeshConverter(_MeshManager):
self._dspans = {} self._dspans = {}
self._mesh_geospans = {} self._mesh_geospans = {}
self._non_preshaded = {}
# _report is a property on this subclass # _report is a property on this subclass
super().__init__() super().__init__()
@ -278,23 +280,15 @@ class MeshConverter(_MeshManager):
has_alpha = not all(opaque) has_alpha = not all(opaque)
return has_alpha return has_alpha
def _check_vtx_nonpreshaded(self, bo, mesh, material_idx, base_layer): def _check_vtx_nonpreshaded(self, bo, mesh, material_idx, bm):
def check_layer_shading_animation(layer): def check():
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. # TODO: if this is an avatar, we can't be non-preshaded.
if check_layer_shading_animation(base_layer):
return False
# kShadeWhite (used for shadeless) is not handled for kLiteVtxNonPreshaded # kShadeWhite (used for shadeless) is not handled for kLiteVtxNonPreshaded
if material_idx is not None: if bm is not None:
bm = mesh.materials[material_idx]
if bm.use_shadeless: if bm.use_shadeless:
return False return False
if self.material.requires_material_shading(bm):
return False
mods = bo.plasma_modifiers mods = bo.plasma_modifiers
if mods.lighting.rt_lights: if mods.lighting.rt_lights:
@ -306,6 +300,13 @@ class MeshConverter(_MeshManager):
return False return False
# Safe version for inside the mesh converter.
result = self._non_preshaded.get((bo, bm))
if result is None:
result = check()
self._non_preshaded[(bo, bm)] = result
return result
def _create_geospan(self, bo, mesh, material_idx, bm, hsgmatKey): def _create_geospan(self, bo, mesh, material_idx, bm, hsgmatKey):
"""Initializes a plGeometrySpan from a Blender Object and an hsGMaterial""" """Initializes a plGeometrySpan from a Blender Object and an hsGMaterial"""
geospan = plGeometrySpan() geospan = plGeometrySpan()
@ -328,7 +329,7 @@ class MeshConverter(_MeshManager):
base_layer = hsgmatKey.object.layers[0].object base_layer = hsgmatKey.object.layers[0].object
if is_alpha_blended(base_layer) or self._check_vtx_alpha(mesh, material_idx): if is_alpha_blended(base_layer) or self._check_vtx_alpha(mesh, material_idx):
geospan.props |= plGeometrySpan.kRequiresBlending geospan.props |= plGeometrySpan.kRequiresBlending
if self._check_vtx_nonpreshaded(bo, mesh, material_idx, base_layer): if self._check_vtx_nonpreshaded(bo, mesh, material_idx, bm):
geospan.props |= plGeometrySpan.kLiteVtxNonPreshaded geospan.props |= plGeometrySpan.kLiteVtxNonPreshaded
if (geospan.props & plGeometrySpan.kLiteMask) != plGeometrySpan.kLiteMaterial: if (geospan.props & plGeometrySpan.kLiteMask) != plGeometrySpan.kLiteMaterial:
geospan.props |= plGeometrySpan.kDiffuseFoldedIn geospan.props |= plGeometrySpan.kDiffuseFoldedIn
@ -650,6 +651,7 @@ class MeshConverter(_MeshManager):
msg = "'{}' is a WaveSet -- only one material is supported".format(bo.name) msg = "'{}' is a WaveSet -- only one material is supported".format(bo.name)
self._exporter().report.warn(msg, indent=1) self._exporter().report.warn(msg, indent=1)
blmat = materials[0][1] blmat = materials[0][1]
self._check_vtx_nonpreshaded(bo, mesh, 0, blmat)
matKey = self.material.export_waveset_material(bo, blmat) matKey = self.material.export_waveset_material(bo, blmat)
geospan = self._create_geospan(bo, mesh, None, blmat, matKey) geospan = self._create_geospan(bo, mesh, None, blmat, matKey)
@ -662,6 +664,7 @@ class MeshConverter(_MeshManager):
geospans = [None] * len(materials) geospans = [None] * len(materials)
mat2span_LUT = {} mat2span_LUT = {}
for i, (blmat_idx, blmat) in enumerate(materials): for i, (blmat_idx, blmat) in enumerate(materials):
self._check_vtx_nonpreshaded(bo, mesh, blmat_idx, blmat)
matKey = self.material.export_material(bo, blmat) matKey = self.material.export_material(bo, blmat)
geospans[i] = _GeoSpan(bo, blmat, geospans[i] = _GeoSpan(bo, blmat,
self._create_geospan(bo, mesh, blmat_idx, blmat, matKey), self._create_geospan(bo, mesh, blmat_idx, blmat, matKey),
@ -718,6 +721,9 @@ class MeshConverter(_MeshManager):
return baked_layer.data return baked_layer.data
return None return None
def is_nonpreshaded(self, bo: bpy.types.Object, bm: bpy.types.Material) -> bool:
return self._non_preshaded[(bo, bm)]
@property @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr

2
korman/properties/modifiers/render.py

@ -564,8 +564,6 @@ class PlasmaLightingMod(PlasmaModifierProperties):
mods = self.id_data.plasma_modifiers mods = self.id_data.plasma_modifiers
if mods.water_basic.enabled: if mods.water_basic.enabled:
return False return False
if mods.lightmap.bake_lightmap:
return False
return True return True
def export(self, exporter, bo, so): def export(self, exporter, bo, so):

Loading…
Cancel
Save