Browse Source

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.
pull/210/head
Adam Johnson 4 years ago
parent
commit
fedb7c91f5
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 46
      korman/exporter/mesh.py
  2. 3
      korman/idprops.py
  3. 4
      korman/properties/modifiers/__init__.py
  4. 20
      korman/properties/modifiers/base.py
  5. 92
      korman/properties/modifiers/render.py
  6. 30
      korman/ui/modifiers/render.py

46
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:

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

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

20
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 ()

92
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,

30
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:

Loading…
Cancel
Save