Browse Source

Merge pull request #210 from Hoikas/render

DSpan Export Improvements
pull/218/head
Adam Johnson 4 years ago committed by GitHub
parent
commit
1a31eafe77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 55
      korman/exporter/material.py
  2. 220
      korman/exporter/mesh.py
  3. 3
      korman/idprops.py
  4. 4
      korman/properties/modifiers/__init__.py
  5. 20
      korman/properties/modifiers/base.py
  6. 94
      korman/properties/modifiers/render.py
  7. 30
      korman/ui/modifiers/render.py

55
korman/exporter/material.py

@ -187,7 +187,9 @@ class MaterialConverter:
self._report.msg("Exporting Material '{}' as single user '{}'", bm.name, mat_name, indent=1) self._report.msg("Exporting Material '{}' as single user '{}'", bm.name, mat_name, indent=1)
hgmat = None hgmat = None
else: 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) 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:
@ -222,7 +224,8 @@ class MaterialConverter:
if slot.use_stencil: if slot.use_stencil:
stencils.append((idx, slot)) stencils.append((idx, slot))
else: 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: if restart_pass_next:
tex_layer.state.miscFlags |= hsGMatState.kMiscRestartPassHere tex_layer.state.miscFlags |= hsGMatState.kMiscRestartPassHere
restart_pass_next = False restart_pass_next = False
@ -249,7 +252,7 @@ class MaterialConverter:
# Plasma makes several assumptions that every hsGMaterial has at least one layer. If this # 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 # material had no Textures, we will need to initialize a default layer
if not hsgmat.layers: 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) self._propagate_material_settings(bo, bm, layer)
hsgmat.addLayer(layer.key) hsgmat.addLayer(layer.key)
@ -349,7 +352,7 @@ class MaterialConverter:
return hsgmat.key return hsgmat.key
def export_bumpmap_slot(self, bo, bm, hsgmat, slot, idx): 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) 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 # Okay, now we need to make 3 layers for the Du, Dw, and Dv
@ -1163,6 +1166,30 @@ class MaterialConverter:
def get_bump_layer(self, bo): def get_bump_layer(self, bo):
return self._bump_mats.get(bo, None) 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:
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 not bo.plasma_modifiers.lighting.rt_lights:
return hsColorRGBA.kBlack
if color is None:
color = bm.diffuse_color
return utils.color(color)
def get_texture_animation_key(self, bo, bm, texture): def get_texture_animation_key(self, bo, bm, texture):
"""Finds or creates the appropriate key for sending messages to an animated Texture""" """Finds or creates the appropriate key for sending messages to an animated Texture"""
@ -1204,23 +1231,17 @@ class MaterialConverter:
if bm.use_shadeless: if bm.use_shadeless:
state.shadeFlags |= hsGMatState.kShadeWhite state.shadeFlags |= hsGMatState.kShadeWhite
if bm.emit:
state.shadeFlags |= hsGMatState.kShadeEmissive
# Colors # Colors
layer.ambient = utils.color(bpy.context.scene.world.ambient_color) layer.ambient = self.get_material_ambient(bo, bm)
layer.preshade = utils.color(bm.diffuse_color) layer.preshade = self.get_material_preshade(bo, bm)
layer.runtime = utils.color(bm.diffuse_color) layer.runtime = self.get_material_runtime(bo, bm)
layer.specular = utils.color(bm.specular_color) layer.specular = utils.color(bm.specular_color)
layer.specularPower = min(100.0, float(bm.specular_hardness)) layer.specularPower = min(100.0, float(bm.specular_hardness))
layer.LODBias = -1.0 # Seems to be the Plasma default layer.LODBias = -1.0
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)
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:

220
korman/exporter/mesh.py

@ -14,6 +14,7 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import itertools
from PyHSPlasma import * from PyHSPlasma import *
from math import fabs from math import fabs
import weakref import weakref
@ -29,6 +30,29 @@ _WARN_VERTS_PER_SPAN = 0x8000
_VERTEX_COLOR_LAYERS = {"col", "color", "colour"} _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: class _RenderLevel:
MAJOR_OPAQUE = 0 MAJOR_OPAQUE = 0
MAJOR_FRAMEBUF = 1 MAJOR_FRAMEBUF = 1
@ -40,16 +64,12 @@ class _RenderLevel:
_MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1) _MINOR_MASK = ((1 << _MAJOR_SHIFT) - 1)
def __init__(self, bo, pass_index, blend_span=False): def __init__(self, bo, pass_index, blend_span=False):
self.level = 0 if blend_span:
if pass_index > 0: self.level = self._determine_level(bo, blend_span)
self.major = self.MAJOR_FRAMEBUF
self.minor = pass_index * 4
else: else:
self.major = self.MAJOR_BLEND if blend_span else self.MAJOR_OPAQUE self.level = 0
# Gulp... Hope you know what you're doing...
# We use the blender material's pass index (which we stashed in the hsGMaterial) to increment self.minor += pass_index * 4
# the render pass, just like it says...
self.level += pass_index
def __eq__(self, other): def __eq__(self, other):
return self.level == other.level return self.level == other.level
@ -60,15 +80,38 @@ class _RenderLevel:
def _get_major(self): def _get_major(self):
return self.level >> self._MAJOR_SHIFT return self.level >> self._MAJOR_SHIFT
def _set_major(self, value): 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) major = property(_get_major, _set_major)
def _get_minor(self): def _get_minor(self):
return self.level & self._MINOR_MASK return self.level & self._MINOR_MASK
def _set_minor(self, value): 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) 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: class _DrawableCriteria:
def __init__(self, bo, geospan, pass_index): def __init__(self, bo, geospan, pass_index):
@ -96,12 +139,12 @@ class _DrawableCriteria:
def _face_sort_allowed(self, bo): def _face_sort_allowed(self, bo):
# For now, only test the modifiers # For now, only test the modifiers
# This will need to be tweaked further for GUIs... # 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): def _span_sort_allowed(self, bo):
# For now, only test the modifiers # For now, only test the modifiers
# This will need to be tweaked further for GUIs... # 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 @property
def span_type(self): def span_type(self):
@ -118,7 +161,6 @@ class _GeoData:
self.vertices = [] self.vertices = []
class _MeshManager: class _MeshManager:
def __init__(self, report=None): def __init__(self, report=None):
if report is not None: if report is not None:
@ -214,15 +256,53 @@ class MeshConverter(_MeshManager):
return (num_user_texs, total_texs, max_user_texs) 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
# 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:
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""" """Initializes a plGeometrySpan from a Blender Object and an hsGMaterial"""
geospan = plGeometrySpan() geospan = plGeometrySpan()
geospan.material = hsgmatKey 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 # GeometrySpan format
# For now, we really only care about the number of UVW Channels # 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) user_uvws, total_uvws, max_user_uvws = self._calc_num_uvchans(bo, mesh)
@ -230,10 +310,22 @@ class MeshConverter(_MeshManager):
raise explosions.TooManyUVChannelsError(bo, bm, user_uvws, max_user_uvws) raise explosions.TooManyUVChannelsError(bo, bm, user_uvws, max_user_uvws)
geospan.format = total_uvws geospan.format = total_uvws
# Begin total guesswork WRT flags def is_alpha_blended(layer):
mods = bo.plasma_modifiers if layer.state.blendFlags & hsGMatState.kBlendMask:
if mods.lightmap.enabled: 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 geospan.props |= plGeometrySpan.kLiteVtxNonPreshaded
if (geospan.props & plGeometrySpan.kLiteMask) != plGeometrySpan.kLiteMaterial:
geospan.props |= plGeometrySpan.kDiffuseFoldedIn
mods = bo.plasma_modifiers
if mods.lighting.rt_lights: if mods.lighting.rt_lights:
geospan.props |= plGeometrySpan.kPropRunTimeLight geospan.props |= plGeometrySpan.kPropRunTimeLight
if not bm.use_shadows: if not bm.use_shadows:
@ -275,7 +367,7 @@ class MeshConverter(_MeshManager):
dspan.composeGeometry(True, True) dspan.composeGeometry(True, True)
inc_progress() 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. # Recall that materials is a mapping of exported materials to blender material indices.
# Therefore, geodata maps blender material indices to working geometry data. # Therefore, geodata maps blender material indices to working geometry data.
# Maybe the logic is a bit inverted, but it keeps the inner loop simple. # Maybe the logic is a bit inverted, but it keeps the inner loop simple.
@ -284,16 +376,8 @@ class MeshConverter(_MeshManager):
# Locate relevant vertex color layers now... # Locate relevant vertex color layers now...
lm = bo.plasma_modifiers.lightmap lm = bo.plasma_modifiers.lightmap
has_vtx_alpha = False color = None if lm.bake_lightmap else self._find_vtx_color_layer(mesh.tessface_vertex_colors)
color, alpha = None, None alpha = self._find_vtx_alpha_layer(mesh.tessface_vertex_colors)
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
# Convert Blender faces into things we can stuff into libHSPlasma # Convert Blender faces into things we can stuff into libHSPlasma
for i, tessface in enumerate(mesh.tessfaces): for i, tessface in enumerate(mesh.tessfaces):
@ -323,10 +407,8 @@ class MeshConverter(_MeshManager):
else: else:
src = alpha[i] src = alpha[i]
# average color becomes the alpha value # average color becomes the alpha value
tessface_alphas = (((src.color1[0] + src.color1[1] + src.color1[2]) / 3), tessface_alphas = ((sum(src.color1) / 3), (sum(src.color2) / 3),
((src.color2[0] + src.color2[1] + src.color2[2]) / 3), (sum(src.color3) / 3), (sum(src.color4) / 3))
((src.color3[0] + src.color3[1] + src.color3[2]) / 3),
((src.color4[0] + src.color4[1] + src.color4[2]) / 3))
if bumpmap is not None: if bumpmap is not None:
gradPass = [] gradPass = []
@ -356,10 +438,16 @@ class MeshConverter(_MeshManager):
for j, vertex in enumerate(tessface.vertices): for j, vertex in enumerate(tessface.vertices):
uvws = tuple([uvw[j] for uvw in tessface_uvws]) uvws = tuple([uvw[j] for uvw in tessface_uvws])
# Grab VCols # Calculate vertex colors.
vertex_color = (int(tessface_colors[j][0] * 255), int(tessface_colors[j][1] * 255), if mat2span_LUT:
int(tessface_colors[j][2] * 255), int(tessface_alphas[j] * 255)) mult_color = geospans[mat2span_LUT[tessface.material_index]].mult_color
has_vtx_alpha |= bool(tessface_alphas[j] < 1.0) 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 :( # 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 # We're using tuples because lists are not hashable. The many mathutils and PyHSPlasma
@ -416,7 +504,7 @@ class MeshConverter(_MeshManager):
# Time to finish it up... # Time to finish it up...
for i, data in enumerate(geodata.values()): for i, data in enumerate(geodata.values()):
geospan = geospans[i][0] geospan = geospans[i].geospan
numVerts = len(data.vertices) numVerts = len(data.vertices)
numUVs = geospan.format & plGeometrySpan.kUVCountMask numUVs = geospan.format & plGeometrySpan.kUVCountMask
@ -437,10 +525,6 @@ class MeshConverter(_MeshManager):
uvMap[numUVs - 1].normalize() uvMap[numUVs - 1].normalize()
vtx.uvs = uvMap 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 # If we're still here, let's add our data to the GeometrySpan
geospan.indices = data.triangles geospan.indices = data.triangles
geospan.vertices = data.vertices geospan.vertices = data.vertices
@ -523,18 +607,18 @@ class MeshConverter(_MeshManager):
return None return None
# Step 1: Export all of the doggone materials. # 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 # 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 # Step 3: Add plGeometrySpans to the appropriate DSpan and create indices
_diindices = {} _diindices = {}
for geospan, pass_index in geospans: for i in geospans:
dspan = self._find_create_dspan(bo, geospan, pass_index) dspan = self._find_create_dspan(bo, i.geospan, i.pass_index)
self._report.msg("Exported hsGMaterial '{}' geometry into '{}'", self._report.msg("Exported hsGMaterial '{}' geometry into '{}'",
geospan.material.name, dspan.key.name, indent=1) i.geospan.material.name, dspan.key.name, indent=1)
idx = dspan.addSourceSpan(geospan) idx = dspan.addSourceSpan(i.geospan)
diidx = _diindices.setdefault(dspan, []) diidx = _diindices.setdefault(dspan, [])
diidx.append(idx) diidx.append(idx)
@ -554,20 +638,25 @@ class MeshConverter(_MeshManager):
if len(materials) > 1: if len(materials) > 1:
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)
matKey = self.material.export_waveset_material(bo, materials[0][1]) blmat = materials[0][1]
geospan = self._create_geospan(bo, mesh, materials[0][1], matKey) matKey = self.material.export_waveset_material(bo, blmat)
geospan = self._create_geospan(bo, mesh, None, blmat, matKey)
# FIXME: Can some of this be generalized? # FIXME: Can some of this be generalized?
geospan.props |= (plGeometrySpan.kWaterHeight | plGeometrySpan.kLiteVtxNonPreshaded | geospan.props |= (plGeometrySpan.kWaterHeight | plGeometrySpan.kLiteVtxNonPreshaded |
plGeometrySpan.kPropReverseSort | plGeometrySpan.kPropNoShadow) plGeometrySpan.kPropReverseSort | plGeometrySpan.kPropNoShadow)
geospan.waterHeight = bo.location[2] geospan.waterHeight = bo.location[2]
return [(geospan, 0)] return [_GeoSpan(bo, blmat, geospan)], None
else: else:
geospans = [None] * len(materials) 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) matKey = self.material.export_material(bo, blmat)
geospans[i] = (self._create_geospan(bo, mesh, blmat, matKey), blmat.pass_index) geospans[i] = _GeoSpan(bo, blmat,
return geospans 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): def _find_create_dspan(self, bo, geospan, pass_index):
location = self._mgr.get_location(bo) location = self._mgr.get_location(bo)
@ -603,6 +692,21 @@ class MeshConverter(_MeshManager):
else: else:
return self._dspans[location][crit] 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 @property
def _mgr(self): def _mgr(self):
return self._exporter().mgr return self._exporter().mgr

3
korman/idprops.py

@ -127,6 +127,9 @@ def poll_animated_objects(self, value):
def poll_camera_objects(self, value): def poll_camera_objects(self, value):
return value.type == "CAMERA" 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): def poll_empty_objects(self, value):
return value.type == "EMPTY" return value.type == "EMPTY"

4
korman/properties/modifiers/__init__.py

@ -66,6 +66,10 @@ class PlasmaModifiers(bpy.types.PropertyGroup):
setattr(cls, i.pl_id, bpy.props.PointerProperty(type=i)) setattr(cls, i.pl_id, bpy.props.PointerProperty(type=i))
bpy.types.Object.plasma_modifiers = bpy.props.PointerProperty(type=cls) 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): class PlasmaModifierSpec(bpy.types.PropertyGroup):
pass pass

20
korman/properties/modifiers/base.py

@ -30,10 +30,30 @@ class PlasmaModifierProperties(bpy.types.PropertyGroup):
def destroyed(self): def destroyed(self):
pass 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 @property
def enabled(self): def enabled(self):
return self.display_order >= 0 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): def harvest_actors(self):
return () return ()

94
korman/properties/modifiers/render.py

@ -25,6 +25,98 @@ from ...exporter import utils
from ...exporter.explosions import ExportError from ...exporter.explosions import ExportError
from ... import idprops 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): class PlasmaDecalManagerRef(bpy.types.PropertyGroup):
enabled = BoolProperty(name="Enabled", enabled = BoolProperty(name="Enabled",
default=True, default=True,
@ -439,6 +531,8 @@ class PlasmaLightingMod(PlasmaModifierProperties):
return True return True
if self.id_data.plasma_object.has_transform_animation: if self.id_data.plasma_object.has_transform_animation:
return True return True
if mods.collision.enabled and mods.collision.dynamic:
return True
return False return False

30
korman/ui/modifiers/render.py

@ -18,6 +18,36 @@ import bpy
from .. import ui_list from .. import ui_list
from ...exporter.mesh import _VERTEX_COLOR_LAYERS 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): class DecalMgrListUI(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0): def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
if item.name: if item.name:

Loading…
Cancel
Save