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
|
# 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
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 "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 },
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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,25 +307,34 @@ class MaterialConverter:
|
|||||||
texture = slot.texture
|
texture = slot.texture
|
||||||
|
|
||||||
# Apply custom layer properties
|
# Apply custom layer properties
|
||||||
layer_props = texture.plasma_layer
|
if slot.use_map_normal:
|
||||||
layer.opacity = layer_props.opacity / 100
|
state.blendFlags = hsGMatState.kBlendDot3
|
||||||
if layer_props.opacity < 100:
|
state.miscFlags = hsGMatState.kMiscBumpLayer
|
||||||
state.blendFlags |= hsGMatState.kBlendAlpha
|
strength = max(min(1.0, slot.normal_factor), 0.0)
|
||||||
if layer_props.alpha_halo:
|
layer.ambient = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
|
||||||
state.blendFlags |= hsGMatState.kBlendAlphaTestHigh
|
layer.preshade = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
|
||||||
if layer_props.z_bias:
|
layer.runtime = hsColorRGBA(strength, 0.0, 0.0, 1.0)
|
||||||
state.ZFlags |= hsGMatState.kZIncLayer
|
layer.specular = hsColorRGBA(0.0, 0.0, 0.0, 1.0)
|
||||||
if layer_props.skip_depth_test:
|
else:
|
||||||
state.ZFlags |= hsGMatState.kZNoZRead
|
layer_props = texture.plasma_layer
|
||||||
if layer_props.skip_depth_write:
|
layer.opacity = layer_props.opacity / 100
|
||||||
state.ZFlags |= hsGMatState.kZNoZWrite
|
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
|
# Export the specific texture type
|
||||||
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
|
||||||
|
@ -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,10 +285,31 @@ 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(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...
|
# Convert to triangles, if need be...
|
||||||
if len(face_verts) == 3:
|
if len(face_verts) == 3:
|
||||||
@ -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...
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user