diff --git a/korlib/CMakeLists.txt b/korlib/CMakeLists.txt
index 56264ca..dce5c59 100644
--- a/korlib/CMakeLists.txt
+++ b/korlib/CMakeLists.txt
@@ -5,10 +5,13 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Stolen shamelessly from PyHSPlasma
find_package(PythonLibs REQUIRED)
-find_package(PythonInterp "${PYTHONLIBS_VERSION_STRING}" REQUIRED)
+# Use only the major.minor version -- no patch, no +, etc
+STRING(REGEX REPLACE "([0-9]\\.[0-9])[0-9.+]*" "\\1" PYTHONLIBS_VERSION_STRING_FILTERED "${PYTHONLIBS_VERSION_STRING}")
+find_package(PythonInterp "${PYTHONLIBS_VERSION_STRING_FILTERED}" REQUIRED)
# make sure the versions match
-if (NOT "${PYTHONLIBS_VERSION_STRING}" STREQUAL "${PYTHON_VERSION_STRING}")
- message(FATAL_ERROR "Versions of Python libraries (${PYTHONLIBS_VERSION_STRING}) and Python interpreter (${PYTHON_VERSION_STRING}) do not match. Please configure the paths manually.")
+STRING(REGEX REPLACE "([0-9]\\.[0-9])[0-9.+]*" "\\1" PYTHON_VERSION_STRING_FILTERED "${PYTHON_VERSION_STRING}")
+if (NOT "${PYTHONLIBS_VERSION_STRING_FILTERED}" STREQUAL "${PYTHON_VERSION_STRING_FILTERED}")
+ message(FATAL_ERROR "Versions of Python libraries (${PYTHONLIBS_VERSION_STRING_FILTERED}) and Python interpreter (${PYTHON_VERSION_STRING_FILTERED}) do not match. Please configure the paths manually.")
endif()
find_package(HSPlasma REQUIRED)
@@ -20,6 +23,7 @@ find_package(Vorbis REQUIRED)
# Da files
set(korlib_HEADERS
buffer.h
+ bumpmap.h
korlib.h
sound.h
texture.h
@@ -27,6 +31,7 @@ set(korlib_HEADERS
set(korlib_SOURCES
buffer.cpp
+ bumpmap.cpp
module.cpp
sound.cpp
texture.cpp
@@ -40,6 +45,7 @@ include_directories(${STRING_THEORY_INCLUDE_DIRS})
include_directories(${Vorbis_INCLUDE_DIR})
add_library(_korlib SHARED ${korlib_HEADERS} ${korlib_SOURCES})
+set_target_properties(_korlib PROPERTIES PREFIX "")
if(NOT WIN32)
set_target_properties(_korlib PROPERTIES SUFFIX ".so")
else()
diff --git a/korlib/bumpmap.cpp b/korlib/bumpmap.cpp
new file mode 100644
index 0000000..00ca20f
--- /dev/null
+++ b/korlib/bumpmap.cpp
@@ -0,0 +1,113 @@
+/* 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 .
+ */
+
+#include "bumpmap.h"
+#include
+
+static uint32_t MakeUInt32Color(float r, float g, float b, float a) {
+ return (uint32_t(a * 255.9f) << 24) |
+ (uint32_t(r * 255.9f) << 16) |
+ (uint32_t(g * 255.9f) << 8) |
+ (uint32_t(b * 255.9f) << 0);
+}
+
+typedef struct {
+ PyObject_HEAD
+ plMipmap* fThis;
+ bool fPyOwned;
+} pyMipmap;
+
+extern "C" {
+
+PyObject* create_bump_LUT(PyObject*, PyObject* args) {
+ const int kLUTHeight = 16;
+ const int kLUTWidth = 16;
+
+ pyMipmap* pymipmap;
+ if (!PyArg_ParseTuple(args, "O", &pymipmap)) {
+ PyErr_SetString(PyExc_TypeError, "create_bump_LUT expects a plMipmap");
+ return NULL;
+ }
+
+ plMipmap* texture = plMipmap::Convert(pymipmap->fThis, false);
+ if (!texture) {
+ PyErr_SetString(PyExc_TypeError, "create_bump_LUT expects a plMipmap");
+ return NULL;
+ }
+
+ texture->Create(kLUTWidth, kLUTHeight, 1, plBitmap::kUncompressed, plBitmap::kRGB8888);
+
+ int delH = (kLUTHeight - 1) / 5;
+ int startH = delH / 2 + 1;
+ int doneH = 0;
+
+ uint8_t* data = new uint8_t[texture->getTotalSize()];
+ uint32_t* pix = (uint32_t*)data;
+ int i;
+
+ // Red ramps, one with G,B = 0,0, one with G,B = 127,127
+ for (i = 0; i < startH; ++i) {
+ for(int j = 0; j < kLUTWidth; ++j) {
+ float x = float(j) / (kLUTWidth - 1);
+ *pix++ = MakeUInt32Color(x, 0.0f, 0.0f, 1.0f);
+ }
+ }
+ doneH = i;
+ for (i = i; i < doneH + delH; ++i) {
+ for (int j = 0; j < kLUTWidth; ++j) {
+ float x = float(j) / (kLUTWidth - 1);
+ *pix++ = MakeUInt32Color(x, 0.5f, 0.5f, 1.0f);
+ }
+ }
+ doneH = i;
+
+ // Green ramps, one with R,B = 0,0, one with R,B = 127,127
+ for (i = i; i < doneH + delH; ++i) {
+ for (int j = 0; j < kLUTWidth; ++j) {
+ float x = float(j) / (kLUTWidth - 1);
+ *pix++ = MakeUInt32Color(0.0f, x, 0.0f, 1.0f);
+ }
+ }
+ doneH = i;
+ for (i = i; i < doneH + delH; ++i) {
+ for (int j = 0; j < kLUTWidth; ++j) {
+ float x = float(j) / (kLUTWidth - 1);
+ *pix++ = MakeUInt32Color(0.5f, x, 0.5f, 1.0f);
+ }
+ }
+ doneH = i;
+
+ // Blue ramps, one with R,G = 0,0, one with R,G = 127,127
+ for (i = i; i < doneH + delH; ++i) {
+ for (int j = 0; j < kLUTWidth; ++j) {
+ float x = float(j) / (kLUTWidth - 1);
+ *pix++ = MakeUInt32Color(0.0f, 0.0f, x, 1.0f);
+ }
+ }
+ doneH = i;
+ for (i = i; i < kLUTHeight; ++i) {
+ for (int j = 0; j < kLUTWidth; ++j) {
+ float x = float(j) / (kLUTWidth - 1);
+ *pix++ = MakeUInt32Color(0.5f, 0.5f, x, 1.0f);
+ }
+ }
+
+ texture->setImageData(data, texture->getTotalSize());
+
+ Py_RETURN_NONE;
+}
+
+};
diff --git a/korlib/bumpmap.h b/korlib/bumpmap.h
new file mode 100644
index 0000000..13a2d9a
--- /dev/null
+++ b/korlib/bumpmap.h
@@ -0,0 +1,29 @@
+/* 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 .
+ */
+
+#ifndef _KORLIB_BUMPMAP_H
+#define _KORLIB_BUMPMAP_H
+
+#include "korlib.h"
+
+extern "C" {
+
+PyObject* create_bump_LUT(PyObject*, PyObject* args);
+
+};
+
+#endif // _KORLIB_BUMPMAP_H
+
diff --git a/korlib/module.cpp b/korlib/module.cpp
index 6ff987c..8850715 100644
--- a/korlib/module.cpp
+++ b/korlib/module.cpp
@@ -15,12 +15,14 @@
*/
#include "buffer.h"
+#include "bumpmap.h"
#include "sound.h"
#include "texture.h"
extern "C" {
static PyMethodDef korlib_Methods[] = {
+ { _pycs("create_bump_LUT"), (PyCFunction)create_bump_LUT, METH_VARARGS, NULL },
{ _pycs("inspect_vorbisfile"), (PyCFunction)inspect_vorbisfile, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL },
diff --git a/korman/exporter/explosions.py b/korman/exporter/explosions.py
index 715680b..331c173 100644
--- a/korman/exporter/explosions.py
+++ b/korman/exporter/explosions.py
@@ -29,9 +29,9 @@ class ExportAssertionError(ExportError):
class TooManyUVChannelsError(ExportError):
- def __init__(self, obj, mat):
- msg = "There are too many UV Textures on the material '{}' associated with object '{}'.".format(
- mat.name, obj.name)
+ def __init__(self, obj, mat, numUVTexs, maxUVTexCount=8):
+ msg = "There are too many UV Textures on the material '{}' associated with object '{}'. You can have at most {} (there are {})".format(
+ mat.name, obj.name, maxUVTexCount, numUVTexs)
super(ExportError, self).__init__(msg)
diff --git a/korman/exporter/manager.py b/korman/exporter/manager.py
index 4541354..e331aa8 100644
--- a/korman/exporter/manager.py
+++ b/korman/exporter/manager.py
@@ -174,14 +174,16 @@ class ExportManager:
key = self.add_object(pl=pClass, name=name, bl=bl, so=so).key
return key
- def find_key(self, pClass, bl=None, name=None, so=None):
+ def find_key(self, pClass, bl=None, name=None, so=None, loc=None):
"""Given a blender Object and a Plasma class, find (or create) an exported plKey"""
- assert bl or so
+ assert loc or bl or so
- if so is None:
- location = self._pages[bl.plasma_object.page]
- else:
+ if loc is not None:
+ location = loc
+ elif so is not None:
location = so.key.location
+ else:
+ location = self._pages[bl.plasma_object.page]
if name is None:
if bl is not None:
diff --git a/korman/exporter/material.py b/korman/exporter/material.py
index 98b830f..edeb1b6 100644
--- a/korman/exporter/material.py
+++ b/korman/exporter/material.py
@@ -109,6 +109,7 @@ class _Texture:
class MaterialConverter:
def __init__(self, exporter):
self._obj2mat = {}
+ self._bump_mats = {}
self._exporter = weakref.ref(exporter)
self._pending = {}
self._alphatest = {}
@@ -140,14 +141,32 @@ class MaterialConverter:
if num_stencils > _MAX_STENCILS:
raise ExportError("Material '{}' uses too many stencils. The maximum is {}".format(bm.name, _MAX_STENCILS))
stencils = []
+ restart_pass_next = False
# Loop over layers
for idx, slot in slots:
+ # Prepend any BumpMapping magic layers
+ if slot.use_map_normal:
+ if bo in self._bump_mats:
+ raise ExportError("Material '{}' has more than one bumpmap layer".format(bm.name))
+ du, dw, dv = self.export_bumpmap_slot(bo, bm, hsgmat, slot, idx)
+ hsgmat.addLayer(du.key) # Du
+ hsgmat.addLayer(dw.key) # Dw
+ hsgmat.addLayer(dv.key) # Dv
+
if slot.use_stencil:
stencils.append((idx, slot))
else:
tex_layer = self.export_texture_slot(bo, bm, hsgmat, slot, idx)
+ if restart_pass_next:
+ tex_layer.state.miscFlags |= hsGMatState.kMiscRestartPassHere
+ restart_pass_next = False
hsgmat.addLayer(tex_layer.key)
+ if slot.use_map_normal:
+ self._bump_mats[bo] = (tex_layer.UVWSrc, tex_layer.transform)
+ # After a bumpmap layer(s), the next layer *must* be in a
+ # new pass, otherwise it gets added in non-intuitive ways
+ restart_pass_next = True
if stencils:
tex_state = tex_layer.state
if not tex_state.blendFlags & hsGMatState.kBlendMask:
@@ -194,12 +213,60 @@ class MaterialConverter:
# Wasn't that easy?
return hsgmat.key
+ def export_bumpmap_slot(self, bo, bm, hsgmat, slot, idx):
+ name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
+ print(" Exporting Plasma Bumpmap Layers for '{}'".format(name))
+
+ # Okay, now we need to make 3 layers for the Du, Dw, and Dv
+ du_layer = self._mgr.add_object(plLayer, name="{}_DU_BumpLut".format(name), bl=bo)
+ dw_layer = self._mgr.add_object(plLayer, name="{}_DW_BumpLut".format(name), bl=bo)
+ dv_layer = self._mgr.add_object(plLayer, name="{}_DV_BumpLut".format(name), bl=bo)
+
+ for layer in (du_layer, dw_layer, dv_layer):
+ layer.ambient = hsColorRGBA(1.0, 1.0, 1.0, 1.0)
+ layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+ layer.runtime = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+ layer.specular = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+
+ state = layer.state
+ state.ZFlags = hsGMatState.kZNoZWrite
+ state.clampFlags = hsGMatState.kClampTexture
+ state.miscFlags = hsGMatState.kMiscBindNext
+ state.blendFlags = hsGMatState.kBlendAdd
+
+ if not slot.use_map_specular:
+ du_layer.state.blendFlags = hsGMatState.kBlendMADD
+
+ du_layer.state.miscFlags |= hsGMatState.kMiscBumpDu | hsGMatState.kMiscRestartPassHere
+ dw_layer.state.miscFlags |= hsGMatState.kMiscBumpDw
+ dv_layer.state.miscFlags |= hsGMatState.kMiscBumpDv
+
+ du_uv = len(bo.data.uv_layers)
+ du_layer.UVWSrc = du_uv
+ dw_layer.UVWSrc = du_uv | plLayerInterface.kUVWNormal
+ dv_layer.UVWSrc = du_uv + 1
+
+ page = self._mgr.get_textures_page(du_layer.key)
+ LUT_key = self._mgr.find_key(plMipmap, loc=page, name="BumpLutTexture")
+
+ if LUT_key is None:
+ bumpLUT = plMipmap("BumpLutTexture", 16, 16, 1, plBitmap.kUncompressed, plBitmap.kRGB8888)
+ create_bump_LUT(bumpLUT)
+ self._mgr.AddObject(page, bumpLUT)
+ LUT_key = bumpLUT.key
+
+ du_layer.texture = LUT_key
+ dw_layer.texture = LUT_key
+ dv_layer.texture = LUT_key
+
+ return (du_layer, dw_layer, dv_layer)
+
def export_texture_slot(self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True):
if name is None:
name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
print(" Exporting Plasma Layer '{}'".format(name))
layer = self._mgr.add_object(plLayer, name=name, bl=bo)
- if bm is not None:
+ if bm is not None and not slot.use_map_normal:
self._propagate_material_settings(bm, layer)
# UVW Channel
@@ -218,7 +285,7 @@ class MaterialConverter:
xform.setScale(hsVector3(*slot.scale))
layer.transform = xform
- wantStencil, canStencil = slot.use_stencil, slot.use_stencil and bm is not None
+ wantStencil, canStencil = slot.use_stencil, slot.use_stencil and bm is not None and not slot.use_map_normal
if wantStencil and not canStencil:
self._exporter().report.warn("{} wants to stencil, but this is not a real Material".format(slot.name))
@@ -240,25 +307,34 @@ class MaterialConverter:
texture = slot.texture
# Apply custom layer properties
- layer_props = texture.plasma_layer
- layer.opacity = layer_props.opacity / 100
- if layer_props.opacity < 100:
- state.blendFlags |= hsGMatState.kBlendAlpha
- if layer_props.alpha_halo:
- state.blendFlags |= hsGMatState.kBlendAlphaTestHigh
- if layer_props.z_bias:
- state.ZFlags |= hsGMatState.kZIncLayer
- if layer_props.skip_depth_test:
- state.ZFlags |= hsGMatState.kZNoZRead
- if layer_props.skip_depth_write:
- state.ZFlags |= hsGMatState.kZNoZWrite
+ if slot.use_map_normal:
+ state.blendFlags = hsGMatState.kBlendDot3
+ state.miscFlags = hsGMatState.kMiscBumpLayer
+ strength = max(min(1.0, slot.normal_factor), 0.0)
+ layer.ambient = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+ layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+ layer.runtime = hsColorRGBA(strength, 0.0, 0.0, 1.0)
+ layer.specular = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
+ else:
+ layer_props = texture.plasma_layer
+ layer.opacity = layer_props.opacity / 100
+ if layer_props.opacity < 100:
+ state.blendFlags |= hsGMatState.kBlendAlpha
+ if layer_props.alpha_halo:
+ state.blendFlags |= hsGMatState.kBlendAlphaTestHigh
+ if layer_props.z_bias:
+ state.ZFlags |= hsGMatState.kZIncLayer
+ if layer_props.skip_depth_test:
+ state.ZFlags |= hsGMatState.kZNoZRead
+ if layer_props.skip_depth_write:
+ state.ZFlags |= hsGMatState.kZNoZWrite
# Export the specific texture type
self._tex_exporters[texture.type](bo, layer, slot)
# Export any layer animations
- # NOTE: animated stencils are nonsense.
- if not slot.use_stencil:
+ # NOTE: animated stencils and bumpmaps are nonsense.
+ if not slot.use_stencil and not slot.use_map_normal:
layer = self._export_layer_animations(bo, bm, slot, idx, layer)
return layer
@@ -476,7 +552,7 @@ class MaterialConverter:
# First, let's apply any relevant flags
state = layer.state
- if not slot.use_stencil:
+ if not slot.use_stencil and not slot.use_map_normal:
# mutually exclusive blend flags
if texture.use_alpha and has_alpha:
if slot.blend_type == "ADD":
@@ -608,6 +684,9 @@ class MaterialConverter:
def get_materials(self, bo):
return self._obj2mat.get(bo, [])
+ def get_bump_layer(self, bo):
+ return self._bump_mats.get(bo, None)
+
def get_texture_animation_key(self, bo, bm, tex_name=None, tex_slot=None):
"""Finds or creates the appropriate key for sending messages to an animated Texture"""
assert tex_name or tex_slot
diff --git a/korman/exporter/mesh.py b/korman/exporter/mesh.py
index 204806d..98fb63c 100644
--- a/korman/exporter/mesh.py
+++ b/korman/exporter/mesh.py
@@ -15,6 +15,7 @@
import bpy
from PyHSPlasma import *
+from math import fabs
import weakref
from . import explosions
@@ -118,6 +119,24 @@ class MeshConverter:
self._dspans = {}
self._mesh_geospans = {}
+ def _calc_num_uvchans(self, bo, mesh):
+ max_user_texs = plGeometrySpan.kUVCountMask
+ num_user_texs = len(mesh.tessface_uv_textures)
+ total_texs = num_user_texs
+
+ # Bump Mapping requires 2 magic channels
+ if self.material.get_bump_layer(bo) is not None:
+ total_texs += 2
+ max_user_texs -= 2
+
+ # Lightmapping requires its own LIGHTMAPGEN channel
+ # NOTE: the LIGHTMAPGEN texture has already been created, so it is in num_user_texs
+ if bo.plasma_modifiers.lightmap.enabled:
+ num_user_texs -= 1
+ max_user_texs -= 1
+
+ return (num_user_texs, total_texs, max_user_texs)
+
def _create_geospan(self, bo, mesh, bm, hsgmatKey):
"""Initializes a plGeometrySpan from a Blender Object and an hsGMaterial"""
geospan = plGeometrySpan()
@@ -125,10 +144,10 @@ class MeshConverter:
# GeometrySpan format
# For now, we really only care about the number of UVW Channels
- numUVWchans = len(mesh.tessface_uv_textures)
- if numUVWchans > plGeometrySpan.kUVCountMask:
- raise explosions.TooManyUVChannelsError(bo, bm)
- geospan.format = numUVWchans
+ user_uvws, total_uvws, max_user_uvws = self._calc_num_uvchans(bo, mesh)
+ if total_uvws > plGeometrySpan.kUVCountMask:
+ raise explosions.TooManyUVChannelsError(bo, bm, user_uvws, max_user_uvws)
+ geospan.format = total_uvws
# Begin total guesswork WRT flags
mods = bo.plasma_modifiers
@@ -174,6 +193,7 @@ class MeshConverter:
def _export_geometry(self, bo, mesh, materials, geospans):
geodata = [_GeoData(len(mesh.vertices)) for i in materials]
+ bumpmap = self.material.get_bump_layer(bo)
# Locate relevant vertex color layers now...
color, alpha = None, None
@@ -191,6 +211,8 @@ class MeshConverter:
data = geodata[tessface.material_index]
face_verts = []
use_smooth = tessface.use_smooth
+ dPosDu = hsVector3(0.0, 0.0, 0.0)
+ dPosDv = hsVector3(0.0, 0.0, 0.0)
# Unpack the UV coordinates from each UV Texture layer
# NOTE: Blender has no third (W) coordinate
@@ -214,6 +236,30 @@ class MeshConverter:
((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:
+ gradPass = []
+ gradUVWs = []
+
+ if len(tessface.vertices) != 3:
+ gradPass.append([tessface.vertices[0], tessface.vertices[1], tessface.vertices[2]])
+ gradPass.append([tessface.vertices[0], tessface.vertices[2], tessface.vertices[3]])
+ gradUVWs.append((tuple((uvw[0] for uvw in tessface_uvws)),
+ tuple((uvw[1] for uvw in tessface_uvws)),
+ tuple((uvw[2] for uvw in tessface_uvws))))
+ gradUVWs.append((tuple((uvw[0] for uvw in tessface_uvws)),
+ tuple((uvw[2] for uvw in tessface_uvws)),
+ tuple((uvw[3] for uvw in tessface_uvws))))
+ else:
+ gradPass.append(tessface.vertices)
+ gradUVWs.append((tuple((uvw[0] for uvw in tessface_uvws)),
+ tuple((uvw[1] for uvw in tessface_uvws)),
+ tuple((uvw[2] for uvw in tessface_uvws))))
+
+ for p, vids in enumerate(gradPass):
+ dPosDu += self._get_bump_gradient(bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 0)
+ dPosDv += self._get_bump_gradient(bumpmap[1], gradUVWs[p], mesh, vids, bumpmap[0], 1)
+ dPosDv = -dPosDv
+
# Convert to per-material indices
for j, vertex in enumerate(tessface.vertices):
uvws = tuple([uvw[j] for uvw in tessface_uvws])
@@ -239,10 +285,31 @@ class MeshConverter:
geoVertex.normal = hsVector3(*tessface.normal)
geoVertex.color = hsColor32(*vertex_color)
- geoVertex.uvs = [hsVector3(uv[0], 1.0 - uv[1], 0.0) for uv in uvws]
- data.blender2gs[vertex][coluv] = len(data.vertices)
+ uvs = [hsVector3(uv[0], 1.0 - uv[1], 0.0) for uv in uvws]
+ if bumpmap is not None:
+ uvs.append(dPosDu)
+ uvs.append(dPosDv)
+ geoVertex.uvs = uvs
+
+ idx = len(data.vertices)
+ data.blender2gs[vertex][coluv] = idx
data.vertices.append(geoVertex)
- face_verts.append(data.blender2gs[vertex][coluv])
+ face_verts.append(idx)
+ else:
+ # If we have a bump mapping layer, then we need to add the bump gradients for
+ # this face to the vertex's magic channels
+ if bumpmap is not None:
+ num_user_uvs = len(uvws)
+ geoVertex = data.vertices[data.blender2gs[vertex][coluv]]
+
+ # Unfortunately, PyHSPlasma returns a copy of everything. Previously, editing
+ # in place would result in silent failures; however, as of python_refactor,
+ # PyHSPlasma now returns tuples to indicate this.
+ geoUVs = list(geoVertex.uvs)
+ geoUVs[num_user_uvs] += dPosDu
+ geoUVs[num_user_uvs+1] += dPosDv
+ geoVertex.uvs = geoUVs
+ face_verts.append(data.blender2gs[vertex][coluv])
# Convert to triangles, if need be...
if len(face_verts) == 3:
@@ -255,6 +322,7 @@ class MeshConverter:
for i, data in enumerate(geodata):
geospan = geospans[i][0]
numVerts = len(data.vertices)
+ numUVs = geospan.format & plGeometrySpan.kUVCountMask
# There is a soft limit of 0x8000 vertices per span in Plasma, but the limit is
# theoretically 0xFFFF because this field is a 16-bit integer. However, bad things
@@ -265,10 +333,58 @@ class MeshConverter:
if numVerts > _WARN_VERTS_PER_SPAN:
raise explosions.TooManyVerticesError(bo.data.name, geospan.material.name, numVerts)
+ # If we're bump mapping, we need to normalize our magic UVW channels
+ if bumpmap is not None:
+ for vtx in data.vertices:
+ uvMap = vtx.uvs
+ uvMap[numUVs - 2].normalize()
+ uvMap[numUVs - 1].normalize()
+ vtx.uvs = uvMap
+
# If we're still here, let's add our data to the GeometrySpan
geospan.indices = data.triangles
geospan.vertices = data.vertices
+
+ def _get_bump_gradient(self, xform, uvws, mesh, vIds, uvIdx, iUV):
+ v0 = hsVector3(*mesh.vertices[vIds[0]].co)
+ v1 = hsVector3(*mesh.vertices[vIds[1]].co)
+ v2 = hsVector3(*mesh.vertices[vIds[2]].co)
+
+ uv0 = (uvws[0][uvIdx][0], uvws[0][uvIdx][1], 0.0)
+ uv1 = (uvws[1][uvIdx][0], uvws[1][uvIdx][1], 0.0)
+ uv2 = (uvws[2][uvIdx][0], uvws[2][uvIdx][1], 0.0)
+
+ notUV = int(not iUV)
+ _REAL_SMALL = 0.000001
+
+ delta = uv0[notUV] - uv1[notUV]
+ if fabs(delta) < _REAL_SMALL:
+ return v1 - v0 if uv0[iUV] - uv1[iUV] < 0 else v0 - v1
+
+ delta = uv2[notUV] - uv1[notUV]
+ if fabs(delta) < _REAL_SMALL:
+ return v1 - v2 if uv2[iUV] - uv1[iUV] < 0 else v2 - v1
+
+ delta = uv2[notUV] - uv0[notUV]
+ if fabs(delta) < _REAL_SMALL:
+ return v0 - v2 if uv2[iUV] - uv0[iUV] < 0 else v2 - v0
+
+ # On to the real fun...
+ delta = uv0[notUV] - uv1[notUV]
+ delta = 1.0 / delta
+ v0Mv1 = v0 - v1
+ v0Mv1 *= delta
+ v0uv = (uv0[iUV] - uv1[iUV]) * delta
+
+ delta = uv2[notUV] - uv1[notUV]
+ delta = 1.0 / delta
+ v2Mv1 = v2 - v1
+ v2Mv1 *= delta
+ v2uv = (uv2[iUV] - uv1[iUV]) * delta
+
+ return v0Mv1 - v2Mv1 if v0uv > v2uv else v2Mv1 - v0Mv1
+
def export_object(self, bo):
# If this object has modifiers, then it's a unique mesh, and we don't need to try caching it
# Otherwise, let's *try* to share meshes as best we can...
diff --git a/korman/korlib/__init__.py b/korman/korlib/__init__.py
index 9a4c22d..77b9457 100644
--- a/korman/korlib/__init__.py
+++ b/korman/korlib/__init__.py
@@ -18,6 +18,42 @@ try:
except ImportError:
from .texture import *
+ def create_bump_LUT(mipmap):
+ kLUTHeight = 16
+ kLUTWidth = 16
+
+ buf = bytearray(kLUTHeight * kLUTWidth * 4)
+
+ denom = kLUTWidth - 1
+ delH = (kLUTHeight - 1) // 5
+ startH = delH // 2 + 1
+ doneH = 0
+
+ doneH = startH * kLUTWidth * 4
+ buf[0:doneH] = [b for x in range(kLUTWidth) for b in (0, 0, int((x / denom) * 255.9), 255)] * startH
+
+ startH = doneH
+ doneH += delH * kLUTWidth * 4
+ buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (127, 127, int((x / denom) * 255.9), 255)] * delH
+
+ startH = doneH
+ doneH += delH * kLUTWidth * 4
+ buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (0, int((x / denom) * 255.9), 0, 255)] * delH
+
+ startH = doneH
+ doneH += delH * kLUTWidth * 4
+ buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (127, int((x / denom) * 255.9), 127, 255)] * delH
+
+ startH = doneH
+ doneH += delH * kLUTWidth * 4
+ buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (int((x / denom) * 255.9), 0, 0, 255)] * delH
+
+ startH = doneH
+ doneH += delH * kLUTWidth * 4
+ buf[startH:doneH] = [b for x in range(kLUTWidth) for b in (int((x / denom) * 255.9), 127, 127, 255)] * startH
+
+ mipmap.setRawImage(bytes(buf))
+
def inspect_voribsfile(stream, header):
raise NotImplementedError("Ogg Vorbis not supported unless _korlib is compiled")