Browse Source

Export lamps as RT lights :D

This includes changes to the light baking code to ensure that we don't
bake runtime lights. This code has several places it could be optimized in
the future when we have larger ages to test against.
pull/6/head
Adam Johnson 10 years ago
parent
commit
fdfd939640
  1. 13
      korman/exporter/convert.py
  2. 5
      korman/exporter/explosions.py
  3. 35
      korman/exporter/manager.py
  4. 11
      korman/exporter/mesh.py
  5. 1
      korman/exporter/physics.py
  6. 200
      korman/exporter/rtlight.py
  7. 61
      korman/operators/op_lightmap.py
  8. 4
      korman/render.py

13
korman/exporter/convert.py

@ -23,6 +23,7 @@ from . import logger
from . import manager
from . import mesh
from . import physics
from . import rtlight
from . import utils
class Exporter:
@ -44,6 +45,7 @@ class Exporter:
self.mesh = mesh.MeshConverter(self)
self.report = logger.ExportAnalysis()
self.physics = physics.PhysicsConverter(self)
self.light = rtlight.LightConverter(self)
# Step 1: Gather a list of objects that we need to export
# We should do this first so we can sanity check
@ -120,10 +122,7 @@ class Exporter:
"""Ensures that the SceneObject has a CoordinateInterface"""
if not so.coord:
print(" Exporting CoordinateInterface")
ci = self.mgr.find_create_key(bo, plCoordinateInterface)
so.coord = ci
ci = ci.object
ci = self.mgr.find_create_key(bo, plCoordinateInterface).object
# Now we have the "fun" work of filling in the CI
ci.localToWorld = utils.matrix44(bo.matrix_basis)
@ -163,8 +162,12 @@ class Exporter:
# or add a silly special case :(
pass
def _export_lamp_blobj(self, so, bo):
# We'll just redirect this to the RT Light converter...
self.light.export_rtlight(so, bo)
def _export_mesh_blobj(self, so, bo):
if bo.data.materials:
so.draw = self.mesh.export_object(bo)
self.mesh.export_object(bo)
else:
print(" No material(s) on the ObData, so no drawables")

5
korman/exporter/explosions.py

@ -18,6 +18,11 @@ class ExportError(Exception):
super(Exception, self).__init__(value)
class BlenderOptionNotSupportedError(ExportError):
def __init__(self, opt):
super(ExportError, self).__init__("Unsupported Blender Option: '{}'".format(opt))
class GLLoadError(ExportError):
def __init__(self, image):
super(ExportError, self).__init__("Failed to load '{}' into OpenGL".format(image.name))

35
korman/exporter/manager.py

@ -91,9 +91,29 @@ class ExportManager:
if isinstance(pl, plObjInterface):
if so is None:
pl.owner = self.find_key(bl, plSceneObject)
key = self.find_key(bl, plSceneObject)
# prevent race conditions
if key is None:
so = self.add_object(plSceneObject, name=name, loc=location)
key = so.key
else:
so = key.object
pl.owner = key
else:
pl.owner = so.key
# The things I do to make life easy...
# This is something of a God function now.
if isinstance(pl, plAudioInterface):
so.audio = pl.key
elif isinstance(pl, plCoordinateInterface):
so.coord = pl.key
elif isinstance(pl, plDrawInterface):
so.draw = pl.key
elif isinstance(pl, plSimulationInterface):
so.sim = pl.key
else:
so.addInterface(pl.key)
elif isinstance(pl, plModifier):
so.addModifier(pl.key)
@ -138,15 +158,18 @@ class ExportManager:
self._nodes[location] = None
return location
def find_create_key(self, bl_obj, pClass):
key = self.find_key(bl_obj, pClass)
def find_create_key(self, bl_obj, pClass, so=None):
key = self.find_key(bl_obj, pClass, so=so)
if key is None:
key = self.add_object(pl=pClass, bl=bl_obj).key
key = self.add_object(pl=pClass, bl=bl_obj, so=so).key
return key
def find_key(self, bl_obj, pClass):
def find_key(self, bl_obj, pClass, so=None):
"""Given a blender Object and a Plasma class, find (or create) an exported plKey"""
location = self._pages[bl_obj.plasma_object.page]
if so is None:
location = self._pages[bl_obj.plasma_object.page]
else:
location = so.key.location
index = plFactory.ClassIndex(pClass.__name__)
for key in self.mgr.getKeys(location, index):

11
korman/exporter/mesh.py

@ -109,8 +109,12 @@ class MeshConverter:
raise explosions.TooManyUVChannelsError(bo, bm)
geospan.format = numUVWchans
# TODO: Props
# TODO: RunTime lights (requires libHSPlasma feature)
# Harvest lights
permaLights, permaProjs = self._exporter().light.find_material_light_keys(bo, bm)
for i in permaLights:
geospan.addPermaLight(i)
for i in permaProjs:
geospan.addPermaProjs(i)
# If this object has a CI, we don't need xforms here...
if self._mgr.has_coordiface(bo):
@ -245,7 +249,6 @@ class MeshConverter:
diface = self._mgr.add_object(pl=plDrawInterface, bl=bo)
for dspan_key, idx in drawables:
diface.addDrawable(dspan_key, idx)
return diface.key
def _export_mesh(self, bo):
# Step 0.8: If this mesh wants to be lit, we need to go ahead and generate it.
@ -290,10 +293,10 @@ class MeshConverter:
return geospans
def _export_static_lighting(self, bo):
bpy.context.scene.objects.active = bo
if bo.plasma_modifiers.lightmap.enabled:
print(" Baking lightmap...")
print("====")
bpy.context.scene.objects.active = bo
bpy.ops.object.plasma_lightmap_autobake()
print("====")
else:

1
korman/exporter/physics.py

@ -63,7 +63,6 @@ class PhysicsConverter:
simIface = self._mgr.add_object(pl=plSimulationInterface, bl=bo)
physical = self._mgr.add_object(pl=plGenericPhysical, bl=bo, name=name)
so.sim = simIface.key
simIface.physical = physical.key
physical.object = so.key
physical.sceneNode = self._mgr.get_scene_node(bl=bo)

200
korman/exporter/rtlight.py

@ -0,0 +1,200 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
from PyHSPlasma import *
import weakref
from .explosions import *
from . import utils
_BL2PL = {
"POINT": plOmniLightInfo,
"SPOT": plSpotLightInfo,
"SUN": plDirectionalLightInfo,
}
class LightConverter:
def __init__(self, exporter):
self._exporter = weakref.ref(exporter)
self._converter_funcs = {
"POINT": self._convert_point_lamp,
"SPOT": self._convert_spot_lamp,
"SUN": self._convert_sun_lamp,
}
def _convert_point_lamp(self, bl, pl):
print(" [OmniLightInfo '{}']".format(bl.name))
self._convert_shared_pointspot(bl, pl)
def _convert_spot_lamp(self, bl, pl):
print(" [SpotLightInfo '{}']".format(bl.name))
self._convert_shared_pointspot(bl, pl)
# Spot lights have a few more things...
spot_size = bl.spot_size
pl.spotOuter = spot_size
blend = max(0.001, bl.spot_blend)
pl.spotInner = spot_size - (blend*spot_size)
if bl.use_halo:
pl.falloff = bl.halo_intensity
else:
pl.falloff = 1.0
def _convert_shared_pointspot(self, bl, pl):
# So sue me, this was taken from pyprp2...
dist = bl.distance
if lamp.falloff_type == "LINEAR_QUADRATIC_WEIGHTED":
print(" Attenuation: Linear Quadratic Weighted")
pl.attenQuadratic = lamp.quadratic_attenuation / dist
pl.attenLinear = lamp.linear_attenuation / dist
pl.attenConst = 1.0
elif lamp.falloff_type == "CONSTANT":
print(" Attenuation: Konstant")
pl.attenQuadratic = 0.0
pl.attenLinear = 0.0
pl.attenConst = 1.0
elif lamp.falloff_type == "INVERSE_SQUARE":
print(" Attenuation: Inverse Square")
pl.attenQuadratic = lamp.quadratic_attenuation / dist
pl.attenLinear = 0.0
pl.attenConst = 1.0
elif lamp.falloff_type == "INVERSE_LINEAR":
print(" Attenuation: Inverse Linear")
pl.attenQuadratic = 0.0
pl.attenLinear = lamp.quadratic_attenuation / dist
pl.attenConst = 1.0
else:
raise BlenderOptionNotSupportedError(lamp.falloff_type)
if bl.use_sphere:
print(" Sphere Cutoff: {}".format(dist))
pl.attenCutoff = dist
else:
pl.attenCutoff = dist * 2
def _convert_sun_lamp(self, bl, pl):
print(" [DirectionalLightInfo '{}']".format(bl.name))
def _create_light_key(self, bo, bl_light, so):
try:
xlate = _BL2PL[bl_light.type]
return self.mgr.find_create_key(bo, xlate, so=so)
except LookupError:
raise BlenderOptionNotSupported("Object ('{}') lamp type '{}'".format(bo.name, bl_light.type))
def export_rtlight(self, so, bo):
bl_light = bo.data
# The specifics be here...
pl_light = self._create_light_key(bo, bl_light, so).object
self._converter_funcs[bl_light.type](bl_light, pl_light)
# Light color nonsense
energy = bl_light.energy * 2
if bl_light.use_negative:
color = [(0.0 - i) * energy for i in bl_light.color]
else:
color = [i * energy for i in bl_light.color]
color_str = "({:.4f}, {:.4f}, {:.4f})".format(color[0], color[1], color[2])
color.append(1.0)
# Apply the colors
if bl_light.use_diffuse:
print(" Diffuse: {}".format(color_str))
pl_light.diffuse = hsColorRGBA(*color)
else:
print(" Diffuse: OFF")
pl_light.diffuse = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
if bl_light.use_specular:
print(" Specular: {}".format(color_str))
pl_light.setProperty(plLightInfo.kLPHasSpecular, True)
pl_light.specular = hsColorRGBA(*color)
else:
print(" Specular: OFF")
pl_light.specular = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
# AFAICT ambient lighting is never set in PlasmaMax...
# If you can think of a compelling reason to support it, be my guest.
pl_light.ambient = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
# Now, let's apply the matrices...
# Science indicates that Plasma RT Lights should *always* have mats, even if there is a CI
l2w = utils.matrix44(bo.matrix_local)
pl_light.lightToWorld = l2w
pl_light.worldToLight = l2w.inverse()
# *Sigh*
pl_light.sceneNode = self.mgr.get_scene_node(location=so.key.location)
def find_material_light_keys(self, bo, bm):
"""Given a blender material, we find the keys of all matching Plasma RT Lights.
NOTE: We return a tuple of lists: ([permaLights], [permaProjs])"""
print(" Searching for runtime lights...")
permaLights = []
permaProjs = []
# We're going to inspect the material's light group.
# If there is no light group, we'll say that there is no runtime lighting...
# If there is, we will harvest all Blender lamps in that light group that are Plasma Objects
lg = bm.light_group
if lg is not None:
for obj in lg.objects:
if obj.type != "LAMP":
# moronic...
continue
elif not obj.plasma_object.enabled:
# who cares?
continue
lamp = obj.data
# Check to see if they only want this light to work on its layer...
if lamp.use_own_layer:
# Pairs up elements from both layers sequences such that we can compare
# to see if the lamp and object are in the same layer.
# If you can think of a better way, be my guest.
test = zip(bo.layers, obj.layers)
for i in test:
if i == (True, True):
break
else:
# didn't find a layer where both lamp and object were, skip it.
print(" [{}] '{}': not in same layer, skipping...".format(lamp.type, obj.name))
continue
# This is probably where PermaLight vs PermaProj should be sorted out...
pl_light = self._create_light_key(bo, lamp, None)
if self._is_projection_lamp(lamp):
print(" [{}] PermaProj '{}'".format(lamp.type, obj.name))
permaProj.append(pl_light)
# TODO: run this through the material exporter...
# need to do some work to make the texture slot code not assume it's working with a material
else:
print(" [{}] PermaLight '{}'".format(lamp.type, obj.name))
permaLights.append(pl_light)
return (permaLights, permaProjs)
def _is_projection_lamp(self, bl_light):
for tex in bl_light.texture_slots:
if tex is None or tex.texture is None:
continue
return True
return False
@property
def mgr(self):
return self._exporter().mgr

61
korman/operators/op_lightmap.py

@ -16,24 +16,67 @@
import bpy
from ..helpers import GoodNeighbor
def _fetch_lamp_objects():
for obj in bpy.data.objects:
if obj.type == "LAMP":
yield obj
class _LightingOperator:
def __init__(self):
self._old_lightgroups = {}
@classmethod
def poll(cls, context):
if context.object is not None:
return context.scene.render.engine == "PLASMA_GAME"
def _generate_lightgroups(self, mesh):
"""Makes a new light group for the baking process that excludes all Plasma RT lamps"""
shouldibake = False
for material in mesh.materials:
lg = material.light_group
self._old_lightgroups[material] = lg
# TODO: faux-lightgroup caching for the entire export process. you dig?
if lg is None or len(lg.objects) == 0:
source = _fetch_lamp_objects()
else:
source = lg.objects
dest = bpy.data.groups.new("_LIGHTMAPGEN_{}".format(material.name))
for obj in source:
if obj.plasma_object.enabled:
continue
dest.objects.link(obj)
shouldibake = True
material.light_group = dest
return shouldibake
def _hide_textures(self, mesh, toggle):
for mat in mesh.materials:
for tex in mat.texture_slots:
if tex is not None and tex.use:
toggle.track(tex, "use", False)
def _pop_lightgroups(self):
for material, lg in self._old_lightgroups.items():
_fake = material.light_group
if _fake is not None:
_fake.user_clear()
bpy.data.groups.remove(_fake)
material.light_group = lg
self._old_lightgroups.clear()
class LightmapAutobakeOperator(_LightingOperator, bpy.types.Operator):
bl_idname = "object.plasma_lightmap_autobake"
bl_label = "Bake Lightmap"
bl_options = {"INTERNAL"}
def __init__(self):
super().__init__()
def execute(self, context):
with GoodNeighbor() as toggle:
# We need to ensure that we bake onto the "BlahObject_LIGHTMAPGEN" image
@ -74,6 +117,8 @@ class LightmapAutobakeOperator(_LightingOperator, bpy.types.Operator):
bpy.ops.object.mode_set(mode="OBJECT")
# Associate the image with all the new UVs
# NOTE: no toggle here because it's the artist's problem if they are looking at our
# super swagalicious LIGHTMAPGEN uvtexture...
for i in mesh.uv_textures.active.data:
i.image = im
@ -87,8 +132,9 @@ class LightmapAutobakeOperator(_LightingOperator, bpy.types.Operator):
self._hide_textures(obj.data, toggle)
# Now, we *finally* bake the lightmap...
# FIXME: Don't bake Plasma RT lights
bpy.ops.object.bake_image()
if self._generate_lightgroups(mesh):
bpy.ops.object.bake_image()
self._pop_lightgroups()
# Done!
return {"FINISHED"}
@ -99,6 +145,9 @@ class LightmapAutobakePreviewOperator(_LightingOperator, bpy.types.Operator):
bl_label = "Preview Lightmap"
bl_options = {"INTERNAL"}
def __init__(self):
super().__init__()
def execute(self, context):
bpy.ops.object.plasma_lightmap_autobake()
@ -116,6 +165,9 @@ class VertexColorLightingOperator(_LightingOperator, bpy.types.Operator):
bl_label = "Bake Vertex Color Lighting"
bl_options = {"INTERNAL"}
def __init__(self):
super().__init__()
def execute(self, context):
with GoodNeighbor() as toggle:
mesh = context.active_object.data
@ -129,7 +181,6 @@ class VertexColorLightingOperator(_LightingOperator, bpy.types.Operator):
# Prepare to bake...
self._hide_textures(mesh, toggle)
# TODO: don't bake runtime lights
# Bake settings
render = context.scene.render
@ -137,7 +188,9 @@ class VertexColorLightingOperator(_LightingOperator, bpy.types.Operator):
toggle.track(render, "use_bake_to_vertex_color", True)
# Bake
bpy.ops.object.bake_image()
if self._generate_lightgroups(mesh):
bpy.ops.object.bake_image()
self._pop_lightgroups()
# And done!
return {"FINISHED"}

4
korman/render.py

@ -42,6 +42,10 @@ def _whitelist_all(mod):
if hasattr(attr, "COMPAT_ENGINES"):
getattr(attr, "COMPAT_ENGINES").add("PLASMA_GAME")
from bl_ui import properties_data_lamp
_whitelist_all(properties_data_lamp)
del properties_data_lamp
from bl_ui import properties_render
_whitelist_all(properties_render)
del properties_render

Loading…
Cancel
Save