diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index b66c96c..5a907d0 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -130,7 +130,7 @@ class Exporter: def _export_actor(self, so, bo): """Exports a Coordinate Interface if we need one""" if self.mgr.has_coordiface(bo): - self._export_coordinate_interface(so, bo) + self.export_coordinate_interface(so, bo) # If this object has a parent, then we will need to go upstream and add ourselves to the # parent's CoordinateInterface... Because life just has to be backwards. @@ -148,16 +148,15 @@ class Exporter: The object may not appear in the correct location or animate properly.".format( bo.name, parent.name)) - def _export_coordinate_interface(self, so, bo): + def export_coordinate_interface(self, so, bl, name=None): """Ensures that the SceneObject has a CoordinateInterface""" if not so.coord: - print(" Exporting CoordinateInterface") - ci = self.mgr.find_create_key(plCoordinateInterface, bl=bo, so=so).object + ci = self.mgr.find_create_key(plCoordinateInterface, bl=bl, so=so, name=name).object # Now we have the "fun" work of filling in the CI - ci.localToWorld = utils.matrix44(bo.matrix_basis) + ci.localToWorld = utils.matrix44(bl.matrix_basis) ci.worldToLocal = ci.localToWorld.inverse() - ci.localToParent = utils.matrix44(bo.matrix_local) + ci.localToParent = utils.matrix44(bl.matrix_local) ci.parentToLocal = ci.localToParent.inverse() def _export_scene_objects(self): diff --git a/korman/exporter/manager.py b/korman/exporter/manager.py index dd04f7a..9d6ef91 100644 --- a/korman/exporter/manager.py +++ b/korman/exporter/manager.py @@ -93,7 +93,7 @@ class ExportManager: if isinstance(pl, plObjInterface): if so is None: - key = self.find_key(plSceneObject, bl) + key = self.find_key(plSceneObject, bl, name) # prevent race conditions if key is None: so = self.add_object(plSceneObject, name=name, loc=location) diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 9b6cce4..ef8c2a2 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -21,6 +21,7 @@ from PyHSPlasma import * import weakref from . import explosions +from .. import helpers from . import utils # BGL doesn't know about this as of Blender 2.74 @@ -226,6 +227,103 @@ class MaterialConverter: getattr(self, export_fn)(bo, hsgmat, layer, texture) hsgmat.addLayer(layer.key) + def _export_texture_type_environment_map(self, bo, hsgmat, layer, texture): + """Exports a Blender EnvironmentMapTexture to a plLayer""" + + bl_env = texture.environment_map + if bl_env.source in {"STATIC", "ANIMATED"}: + if bl_env.mapping == "PLANE" and self._mgr.getVer() >= pvMoul: + pl_env = plDynamicCamMap + else: + pl_env = plDynamicEnvMap + pl_env = self._export_dynamic_env(bo, hsgmat, layer, bl_env, pl_env) + else: + # We should really export a CubicEnvMap here, but we have a good setup for DynamicEnvMaps + # that create themselves when the explorer links in, so really... who cares about CEMs? + self._exporter().report.warn("IMAGE EnvironmentMaps are not supported. '{}' will not be exported!".format(layer.key.name)) + pl_env = None + layer.texture = pl_env + + def _export_dynamic_env(self, bo, hsgmat, layer, bl_env, pl_class): + # To protect the user from themselves, let's check to make sure that a DEM/DCM matching this + # viewpoint object has not already been exported... + viewpt = bl_env.viewpoint_object + name = "{}_DynEnvMap".format(viewpt.name) + pl_env = self._mgr.find_key(pl_class, bl=bo, name=name) + if pl_env is not None: + print(" EnvMap for viewpoint {} already exported... NOTE: Your settings here will be overridden by the previous object!".format(viewpt.name)) + pl_env_obj = pl_env.object + if isinstance(pl_env_obj, plDynamicCamMap): + dcm.addTargetNode(self._mgr.find_key(plSceneObject, bl=bo)) + dcm.addMatLayer(layer.key) + return pl_env + + # It matters not whether or not the viewpoint object is a Plasma Object, it is exported as at + # least a SceneObject and CoordInterface so that we can touch it... + root = self._mgr.find_create_key(plSceneObject, bl=bo, name=viewpt.name) + self._exporter().export_coordinate_interface(root.object, bl=bo, name=viewpt.name) + # FIXME: DynamicCamMap Camera + + # Ensure POT + oRes = bl_env.resolution + eRes = helpers.ensure_power_of_two(oRes) + if oRes != eRes: + print(" Overriding EnvMap size to ({}x{}) -- POT".format(eRes, eRes)) + + # And now for the general ho'hum-ness + pl_env = self._mgr.add_object(pl_class, bl=bo, name=name) + pl_env.hither = bl_env.clip_start + pl_env.yon = bl_env.clip_end + pl_env.refreshRate = 0.01 if bl_env.source == "ANIMATED" else 0.0 + pl_env.incCharacters = True + pl_env.rootNode = root # FIXME: DCM camera + + # Perhaps the DEM/DCM fog should be separately configurable at some point? + pl_fog = bpy.context.scene.world.plasma_fni + pl_env.color = utils.color(pl_fog.fog_color) + pl_env.fogStart = pl_fog.fog_start + + if isinstance(pl_env, plDynamicCamMap): + faces = (pl_env,) + + pl_env.addTargetNode(self._mgr.find_key(plSceneObject, bl=bo)) + pl_env.addMatLayer(layer.key) + + # This is really just so we don't raise any eyebrows if anyone is looking at the files. + # If you're disabling DCMs, then you're obviuously trolling! + # Cyan generates a single color image, but we'll just set the layer colors and go away. + fake_layer = self._mgr.add_object(plLayer, bl=bo, name="{}_DisabledDynEnvMap".format(viewpt.name)) + fake_layer.ambient = layer.ambient + fake_layer.preshade = layer.preshade + fake_layer.runtime = layer.runtime + fake_layer.specular = layer.specular + pl_env.disableTexture = fake_layer.key + + if pl_env.camera is None: + layer.UVWSrc = plLayerInterface.kUVWPosition + layer.state.miscFlags |= (hsGMatState.kMiscCam2Screen | hsGMatState.kMiscPerspProjection) + else: + faces = pl_env.faces + (pl_env,) + + layer.UVWSrc = plLayerInterface.kUVWReflect + layer.state.miscFlags |= hsGMatState.kMiscUseRefractionXform + + # Because we might be working with a multi-faced env map. It's even worse than have two faces... + for i in faces: + i.setConfig(plBitmap.kRGB8888) + i.flags |= plBitmap.kIsTexture + i.flags &= ~plBitmap.kAlphaChannelFlag + i.width = eRes + i.height = eRes + i.proportionalViewport = False + i.viewportLeft = 0 + i.viewportTop = 0 + i.viewportRight = eRes + i.viewportBottom = eRes + i.ZDepth = 24 + + return pl_env.key + def _export_texture_type_image(self, bo, hsgmat, layer, texture): """Exports a Blender ImageTexture to a plLayer""" @@ -280,8 +378,8 @@ class MaterialConverter: image = key.image oWidth, oHeight = image.size - eWidth = pow(2, math.floor(math.log(oWidth, 2))) - eHeight = pow(2, math.floor(math.log(oHeight, 2))) + eWidth = helpers.ensure_power_of_two(oWidth) + eHeight = helpers.ensure_power_of_two(oHeight) if (eWidth != oWidth) or (eHeight != oHeight): print(" Image is not a POT ({}x{}) resizing to {}x{}".format(oWidth, oHeight, eWidth, eHeight)) self._resize_image(image, eWidth, eHeight) diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py index 6992cc9..eb041fe 100644 --- a/korman/exporter/mesh.py +++ b/korman/exporter/mesh.py @@ -143,7 +143,7 @@ class MeshConverter: for dspan in loc.values(): print("\n[DrawableSpans '{}']".format(dspan.key.name)) print(" Composing geometry data") - + # This mega-function does a lot: # 1. Converts SourceSpans (geospans) to Icicles and bakes geometry into plGBuffers # 2. Calculates the Icicle bounds diff --git a/korman/helpers.py b/korman/helpers.py index 98ffe63..c87680c 100644 --- a/korman/helpers.py +++ b/korman/helpers.py @@ -14,6 +14,7 @@ # along with Korman. If not, see . import bpy +import math class GoodNeighbor: """Leave Things the Way You Found Them! (TM)""" @@ -31,6 +32,9 @@ class GoodNeighbor: setattr(cls, attr, value) +def ensure_power_of_two(value): + return pow(2, math.floor(math.log(value, 2))) + def make_active_selection(bo): """Selects a single Blender Object and makes it active""" for i in bpy.data.objects: