mirror of
https://github.com/H-uru/korman.git
synced 2025-07-14 02:27:36 -04:00
Merge pull request #50 from dpogue/bumpmaps
WIP: Normal Maps/Bumpmapping
This commit is contained in:
@ -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()
|
||||
|
113
korlib/bumpmap.cpp
Normal file
113
korlib/bumpmap.cpp
Normal file
@ -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
Normal file
29
korlib/bumpmap.h
Normal file
@ -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
|
||||
|
@ -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 },
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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...
|
||||
|
@ -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")
|
||||
|
||||
|
Reference in New Issue
Block a user