Browse Source

Merge pull request #50 from dpogue/bumpmaps

WIP: Normal Maps/Bumpmapping
pull/66/head
Adam Johnson 8 years ago committed by GitHub
parent
commit
f5b5e329ae
  1. 12
      korlib/CMakeLists.txt
  2. 113
      korlib/bumpmap.cpp
  3. 29
      korlib/bumpmap.h
  4. 2
      korlib/module.cpp
  5. 6
      korman/exporter/explosions.py
  6. 12
      korman/exporter/manager.py
  7. 89
      korman/exporter/material.py
  8. 128
      korman/exporter/mesh.py
  9. 36
      korman/korlib/__init__.py

12
korlib/CMakeLists.txt

@ -5,10 +5,13 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Stolen shamelessly from PyHSPlasma # Stolen shamelessly from PyHSPlasma
find_package(PythonLibs REQUIRED) 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 # make sure the versions match
if (NOT "${PYTHONLIBS_VERSION_STRING}" STREQUAL "${PYTHON_VERSION_STRING}") STRING(REGEX REPLACE "([0-9]\\.[0-9])[0-9.+]*" "\\1" PYTHON_VERSION_STRING_FILTERED "${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.") 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() endif()
find_package(HSPlasma REQUIRED) find_package(HSPlasma REQUIRED)
@ -20,6 +23,7 @@ find_package(Vorbis REQUIRED)
# Da files # Da files
set(korlib_HEADERS set(korlib_HEADERS
buffer.h buffer.h
bumpmap.h
korlib.h korlib.h
sound.h sound.h
texture.h texture.h
@ -27,6 +31,7 @@ set(korlib_HEADERS
set(korlib_SOURCES set(korlib_SOURCES
buffer.cpp buffer.cpp
bumpmap.cpp
module.cpp module.cpp
sound.cpp sound.cpp
texture.cpp texture.cpp
@ -40,6 +45,7 @@ include_directories(${STRING_THEORY_INCLUDE_DIRS})
include_directories(${Vorbis_INCLUDE_DIR}) include_directories(${Vorbis_INCLUDE_DIR})
add_library(_korlib SHARED ${korlib_HEADERS} ${korlib_SOURCES}) add_library(_korlib SHARED ${korlib_HEADERS} ${korlib_SOURCES})
set_target_properties(_korlib PROPERTIES PREFIX "")
if(NOT WIN32) if(NOT WIN32)
set_target_properties(_korlib PROPERTIES SUFFIX ".so") set_target_properties(_korlib PROPERTIES SUFFIX ".so")
else() else()

113
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 <http://www.gnu.org/licenses/>.
*/
#include "bumpmap.h"
#include <PRP/Surface/plMipmap.h>
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;
}
};

29
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _KORLIB_BUMPMAP_H
#define _KORLIB_BUMPMAP_H
#include "korlib.h"
extern "C" {
PyObject* create_bump_LUT(PyObject*, PyObject* args);
};
#endif // _KORLIB_BUMPMAP_H

2
korlib/module.cpp

@ -15,12 +15,14 @@
*/ */
#include "buffer.h" #include "buffer.h"
#include "bumpmap.h"
#include "sound.h" #include "sound.h"
#include "texture.h" #include "texture.h"
extern "C" { extern "C" {
static PyMethodDef korlib_Methods[] = { static PyMethodDef korlib_Methods[] = {
{ _pycs("create_bump_LUT"), (PyCFunction)create_bump_LUT, METH_VARARGS, NULL },
{ _pycs("inspect_vorbisfile"), (PyCFunction)inspect_vorbisfile, METH_VARARGS, NULL }, { _pycs("inspect_vorbisfile"), (PyCFunction)inspect_vorbisfile, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }, { NULL, NULL, 0, NULL },

6
korman/exporter/explosions.py

@ -29,9 +29,9 @@ class ExportAssertionError(ExportError):
class TooManyUVChannelsError(ExportError): class TooManyUVChannelsError(ExportError):
def __init__(self, obj, mat): def __init__(self, obj, mat, numUVTexs, maxUVTexCount=8):
msg = "There are too many UV Textures on the material '{}' associated with object '{}'.".format( 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) mat.name, obj.name, maxUVTexCount, numUVTexs)
super(ExportError, self).__init__(msg) super(ExportError, self).__init__(msg)

12
korman/exporter/manager.py

@ -174,14 +174,16 @@ class ExportManager:
key = self.add_object(pl=pClass, name=name, bl=bl, so=so).key key = self.add_object(pl=pClass, name=name, bl=bl, so=so).key
return 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""" """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: if loc is not None:
location = self._pages[bl.plasma_object.page] location = loc
else: elif so is not None:
location = so.key.location location = so.key.location
else:
location = self._pages[bl.plasma_object.page]
if name is None: if name is None:
if bl is not None: if bl is not None:

89
korman/exporter/material.py

@ -109,6 +109,7 @@ class _Texture:
class MaterialConverter: class MaterialConverter:
def __init__(self, exporter): def __init__(self, exporter):
self._obj2mat = {} self._obj2mat = {}
self._bump_mats = {}
self._exporter = weakref.ref(exporter) self._exporter = weakref.ref(exporter)
self._pending = {} self._pending = {}
self._alphatest = {} self._alphatest = {}
@ -140,14 +141,32 @@ class MaterialConverter:
if num_stencils > _MAX_STENCILS: if num_stencils > _MAX_STENCILS:
raise ExportError("Material '{}' uses too many stencils. The maximum is {}".format(bm.name, _MAX_STENCILS)) raise ExportError("Material '{}' uses too many stencils. The maximum is {}".format(bm.name, _MAX_STENCILS))
stencils = [] stencils = []
restart_pass_next = False
# Loop over layers # Loop over layers
for idx, slot in slots: 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: if slot.use_stencil:
stencils.append((idx, slot)) stencils.append((idx, slot))
else: else:
tex_layer = self.export_texture_slot(bo, bm, hsgmat, slot, idx) 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) 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: if stencils:
tex_state = tex_layer.state tex_state = tex_layer.state
if not tex_state.blendFlags & hsGMatState.kBlendMask: if not tex_state.blendFlags & hsGMatState.kBlendMask:
@ -194,12 +213,60 @@ class MaterialConverter:
# Wasn't that easy? # Wasn't that easy?
return hsgmat.key 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): def export_texture_slot(self, bo, bm, hsgmat, slot, idx, name=None, blend_flags=True):
if name is None: if name is None:
name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name) name = "{}_{}".format(bm.name if bm is not None else bo.name, slot.name)
print(" Exporting Plasma Layer '{}'".format(name)) print(" Exporting Plasma Layer '{}'".format(name))
layer = self._mgr.add_object(plLayer, name=name, bl=bo) 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) self._propagate_material_settings(bm, layer)
# UVW Channel # UVW Channel
@ -218,7 +285,7 @@ class MaterialConverter:
xform.setScale(hsVector3(*slot.scale)) xform.setScale(hsVector3(*slot.scale))
layer.transform = xform 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: if wantStencil and not canStencil:
self._exporter().report.warn("{} wants to stencil, but this is not a real Material".format(slot.name)) self._exporter().report.warn("{} wants to stencil, but this is not a real Material".format(slot.name))
@ -240,6 +307,15 @@ class MaterialConverter:
texture = slot.texture texture = slot.texture
# Apply custom layer properties # Apply custom layer properties
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_props = texture.plasma_layer
layer.opacity = layer_props.opacity / 100 layer.opacity = layer_props.opacity / 100
if layer_props.opacity < 100: if layer_props.opacity < 100:
@ -257,8 +333,8 @@ class MaterialConverter:
self._tex_exporters[texture.type](bo, layer, slot) self._tex_exporters[texture.type](bo, layer, slot)
# Export any layer animations # Export any layer animations
# NOTE: animated stencils are nonsense. # NOTE: animated stencils and bumpmaps are nonsense.
if not slot.use_stencil: if not slot.use_stencil and not slot.use_map_normal:
layer = self._export_layer_animations(bo, bm, slot, idx, layer) layer = self._export_layer_animations(bo, bm, slot, idx, layer)
return layer return layer
@ -476,7 +552,7 @@ class MaterialConverter:
# First, let's apply any relevant flags # First, let's apply any relevant flags
state = layer.state state = layer.state
if not slot.use_stencil: if not slot.use_stencil and not slot.use_map_normal:
# mutually exclusive blend flags # mutually exclusive blend flags
if texture.use_alpha and has_alpha: if texture.use_alpha and has_alpha:
if slot.blend_type == "ADD": if slot.blend_type == "ADD":
@ -608,6 +684,9 @@ class MaterialConverter:
def get_materials(self, bo): def get_materials(self, bo):
return self._obj2mat.get(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): 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""" """Finds or creates the appropriate key for sending messages to an animated Texture"""
assert tex_name or tex_slot assert tex_name or tex_slot

128
korman/exporter/mesh.py

@ -15,6 +15,7 @@
import bpy import bpy
from PyHSPlasma import * from PyHSPlasma import *
from math import fabs
import weakref import weakref
from . import explosions from . import explosions
@ -118,6 +119,24 @@ class MeshConverter:
self._dspans = {} self._dspans = {}
self._mesh_geospans = {} 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): def _create_geospan(self, bo, mesh, bm, hsgmatKey):
"""Initializes a plGeometrySpan from a Blender Object and an hsGMaterial""" """Initializes a plGeometrySpan from a Blender Object and an hsGMaterial"""
geospan = plGeometrySpan() geospan = plGeometrySpan()
@ -125,10 +144,10 @@ class MeshConverter:
# GeometrySpan format # GeometrySpan format
# For now, we really only care about the number of UVW Channels # For now, we really only care about the number of UVW Channels
numUVWchans = len(mesh.tessface_uv_textures) user_uvws, total_uvws, max_user_uvws = self._calc_num_uvchans(bo, mesh)
if numUVWchans > plGeometrySpan.kUVCountMask: if total_uvws > plGeometrySpan.kUVCountMask:
raise explosions.TooManyUVChannelsError(bo, bm) raise explosions.TooManyUVChannelsError(bo, bm, user_uvws, max_user_uvws)
geospan.format = numUVWchans geospan.format = total_uvws
# Begin total guesswork WRT flags # Begin total guesswork WRT flags
mods = bo.plasma_modifiers mods = bo.plasma_modifiers
@ -174,6 +193,7 @@ class MeshConverter:
def _export_geometry(self, bo, mesh, materials, geospans): def _export_geometry(self, bo, mesh, materials, geospans):
geodata = [_GeoData(len(mesh.vertices)) for i in materials] geodata = [_GeoData(len(mesh.vertices)) for i in materials]
bumpmap = self.material.get_bump_layer(bo)
# Locate relevant vertex color layers now... # Locate relevant vertex color layers now...
color, alpha = None, None color, alpha = None, None
@ -191,6 +211,8 @@ class MeshConverter:
data = geodata[tessface.material_index] data = geodata[tessface.material_index]
face_verts = [] face_verts = []
use_smooth = tessface.use_smooth 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 # Unpack the UV coordinates from each UV Texture layer
# NOTE: Blender has no third (W) coordinate # NOTE: Blender has no third (W) coordinate
@ -214,6 +236,30 @@ class MeshConverter:
((src.color3[0] + src.color3[1] + src.color3[2]) / 3), ((src.color3[0] + src.color3[1] + src.color3[2]) / 3),
((src.color4[0] + src.color4[1] + src.color4[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 # Convert to per-material indices
for j, vertex in enumerate(tessface.vertices): for j, vertex in enumerate(tessface.vertices):
uvws = tuple([uvw[j] for uvw in tessface_uvws]) uvws = tuple([uvw[j] for uvw in tessface_uvws])
@ -239,9 +285,30 @@ class MeshConverter:
geoVertex.normal = hsVector3(*tessface.normal) geoVertex.normal = hsVector3(*tessface.normal)
geoVertex.color = hsColor32(*vertex_color) geoVertex.color = hsColor32(*vertex_color)
geoVertex.uvs = [hsVector3(uv[0], 1.0 - uv[1], 0.0) for uv in uvws] uvs = [hsVector3(uv[0], 1.0 - uv[1], 0.0) for uv in uvws]
data.blender2gs[vertex][coluv] = len(data.vertices) 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) data.vertices.append(geoVertex)
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]) face_verts.append(data.blender2gs[vertex][coluv])
# Convert to triangles, if need be... # Convert to triangles, if need be...
@ -255,6 +322,7 @@ class MeshConverter:
for i, data in enumerate(geodata): for i, data in enumerate(geodata):
geospan = geospans[i][0] geospan = geospans[i][0]
numVerts = len(data.vertices) 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 # 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 # 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: if numVerts > _WARN_VERTS_PER_SPAN:
raise explosions.TooManyVerticesError(bo.data.name, geospan.material.name, numVerts) 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 # If we're still here, let's add our data to the GeometrySpan
geospan.indices = data.triangles geospan.indices = data.triangles
geospan.vertices = data.vertices 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): 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 # 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... # Otherwise, let's *try* to share meshes as best we can...

36
korman/korlib/__init__.py

@ -18,6 +18,42 @@ try:
except ImportError: except ImportError:
from .texture import * 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): def inspect_voribsfile(stream, header):
raise NotImplementedError("Ogg Vorbis not supported unless _korlib is compiled") raise NotImplementedError("Ogg Vorbis not supported unless _korlib is compiled")

Loading…
Cancel
Save