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. 79
      korman/exporter/material.py
  2. 56
      korman/exporter/mesh.py
  3. 2
      korman/properties/modifiers/render.py

79
korman/exporter/material.py

@ -205,7 +205,8 @@ class MaterialConverter:
mat_prefix = "RTLit_"
else:
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)
hsgmat = self._mgr.find_key(hsGMaterial, name=mat_name, bl=bo)
if hsgmat is not None:
@ -489,8 +490,7 @@ class MaterialConverter:
else:
layer_props = texture.plasma_layer
layer.opacity = layer_props.opacity / 100
if layer_props.opacity < 100 and not state.blendFlags & hsGMatState.kBlendMask:
state.blendFlags |= hsGMatState.kBlendAlpha
self._handle_layer_opacity(layer, layer_props.opacity)
if layer_props.alpha_halo:
state.blendFlags |= hsGMatState.kBlendAlphaTestHigh
if layer_props.z_bias:
@ -602,10 +602,14 @@ class MaterialConverter:
return ctrl
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:
if i.data_path == "plasma_layer.opacity":
base_layer.state.blendFlags |= hsGMatState.kBlendAlpha
ctrl = self._exporter().animation.make_scalar_leaf_controller(i)
ctrl = self._exporter().animation.make_scalar_leaf_controller(i, process_opacity)
return ctrl
return None
@ -1254,7 +1258,8 @@ class MaterialConverter:
def get_bump_layer(self, bo):
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
if emit_scale > 0.0:
if color is None:
@ -1266,18 +1271,33 @@ class MaterialConverter:
else:
return utils.color(bpy.context.scene.world.ambient_color)
def get_material_preshade(self, bo, bm, color : Union[None, mathutils.Color]=None) -> hsColorRGBA:
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_preshade(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
# This color is always used for shading. In all lighting equations, it represents the world
# ambient color. Anyway, if we have a manual (read: animated color), just dump that out.
if color is not None:
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
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
# non-preshaded objects.
if color is None and not bo.plasma_modifiers.lighting.preshade:
# Gulp
return utils.color(bm.diffuse_color)
def get_material_runtime(self, bo, bm, color: Union[None, mathutils.Color] = None) -> hsColorRGBA:
# The layer runstime color has no effect if the lighting equation is kLiteVtxNonPreshaded,
# so return black to prevent animations from being exported.
if self._exporter().mesh.is_nonpreshaded(bo, bm):
return hsColorRGBA.kBlack
# Hmm...
if color is None:
color = bm.diffuse_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
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
def _mgr(self):
return self._exporter().mgr
@ -1339,6 +1366,26 @@ class MaterialConverter:
layer.specularPower = min(100.0, float(bm.specular_hardness))
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):
if bo.data.show_double_sided:
return True

56
korman/exporter/mesh.py

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

2
korman/properties/modifiers/render.py

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

Loading…
Cancel
Save