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