mirror of
https://github.com/H-uru/korman.git
synced 2025-07-14 02:27:36 -04:00
Implement detail fading for Plasma Layers
Yes, this was shamelessly stolen from the max plugin. So shoot me. It's an awesome feature.
This commit is contained in:
@ -23,6 +23,7 @@
|
|||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <gl/gl.h>
|
#include <gl/gl.h>
|
||||||
#include <PRP/Surface/plMipmap.h>
|
#include <PRP/Surface/plMipmap.h>
|
||||||
|
|
||||||
@ -32,11 +33,27 @@
|
|||||||
|
|
||||||
#define TEXTARGET_TEXTURE_2D 0
|
#define TEXTARGET_TEXTURE_2D 0
|
||||||
|
|
||||||
|
static inline bool _get_float(PyObject* source, const char* attr, float& result) {
|
||||||
|
PyObjectRef pyfloat = PyObject_GetAttrString(source, attr);
|
||||||
|
if (pyfloat) {
|
||||||
|
result = (float)PyFloat_AsDouble(pyfloat);
|
||||||
|
return PyErr_Occurred() == NULL;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
enum {
|
||||||
|
TEX_DETAIL_ALPHA = 0,
|
||||||
|
TEX_DETAIL_ADD = 1,
|
||||||
|
TEX_DETAIL_MULTIPLY = 2,
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
PyObject* m_blenderImage;
|
PyObject* m_blenderImage;
|
||||||
|
PyObject* m_textureKey;
|
||||||
bool m_ownIt;
|
bool m_ownIt;
|
||||||
GLint m_prevImage;
|
GLint m_prevImage;
|
||||||
bool m_changedState;
|
bool m_changedState;
|
||||||
@ -50,13 +67,15 @@ typedef struct {
|
|||||||
} pyMipmap;
|
} pyMipmap;
|
||||||
|
|
||||||
static void pyGLTexture_dealloc(pyGLTexture* self) {
|
static void pyGLTexture_dealloc(pyGLTexture* self) {
|
||||||
if (self->m_blenderImage) Py_DECREF(self->m_blenderImage);
|
Py_XDECREF(self->m_textureKey);
|
||||||
|
Py_XDECREF(self->m_blenderImage);
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject* pyGLTexture_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
static PyObject* pyGLTexture_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
||||||
pyGLTexture* self = (pyGLTexture*)type->tp_alloc(type, 0);
|
pyGLTexture* self = (pyGLTexture*)type->tp_alloc(type, 0);
|
||||||
self->m_blenderImage = NULL;
|
self->m_blenderImage = NULL;
|
||||||
|
self->m_textureKey = NULL;
|
||||||
self->m_ownIt = false;
|
self->m_ownIt = false;
|
||||||
self->m_prevImage = 0;
|
self->m_prevImage = 0;
|
||||||
self->m_changedState = false;
|
self->m_changedState = false;
|
||||||
@ -65,15 +84,17 @@ static PyObject* pyGLTexture_new(PyTypeObject* type, PyObject* args, PyObject* k
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int pyGLTexture___init__(pyGLTexture* self, PyObject* args, PyObject* kwds) {
|
static int pyGLTexture___init__(pyGLTexture* self, PyObject* args, PyObject* kwds) {
|
||||||
PyObject* blender_image;
|
if (!PyArg_ParseTuple(args, "O", &self->m_textureKey)) {
|
||||||
if (!PyArg_ParseTuple(args, "O", &blender_image)) {
|
PyErr_SetString(PyExc_TypeError, "expected a korman.exporter.material._Texture");
|
||||||
PyErr_SetString(PyExc_TypeError, "expected a bpy.types.Image");
|
return -1;
|
||||||
|
}
|
||||||
|
self->m_blenderImage = PyObject_GetAttrString(self->m_textureKey, "image");
|
||||||
|
if (!self->m_blenderImage) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Could not fetch Blender Image");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save a reference to the Blender image
|
Py_INCREF(self->m_textureKey);
|
||||||
Py_INCREF(blender_image);
|
|
||||||
self->m_blenderImage = blender_image;
|
|
||||||
|
|
||||||
// Done!
|
// Done!
|
||||||
return 0;
|
return 0;
|
||||||
@ -161,6 +182,90 @@ struct _LevelData
|
|||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline int _get_num_levels(pyGLTexture* self) {
|
||||||
|
PyObjectRef size = PyObject_GetAttrString(self->m_blenderImage, "size");
|
||||||
|
float width = (float)PyFloat_AsDouble(PySequence_GetItem(size, 0));
|
||||||
|
float height = (float)PyFloat_AsDouble(PySequence_GetItem(size, 1));
|
||||||
|
|
||||||
|
int num_levels = (int)std::floor(std::log2(std::max(width, height))) + 1;
|
||||||
|
|
||||||
|
// Major Workaround Ahoy
|
||||||
|
// There is a bug in Cyan's level size algorithm that causes it to not allocate enough memory
|
||||||
|
// for the color block in certain mipmaps. I personally have encountered an access violation on
|
||||||
|
// 1x1 DXT5 mip levels -- the code only allocates an alpha block and not a color block. Paradox
|
||||||
|
// reports that if any dimension is smaller than 4px in a mip level, OpenGL doesn't like Cyan generated
|
||||||
|
// data. So, we're going to lop off the last two mip levels, which should be 1px and 2px as the smallest.
|
||||||
|
// This bug is basically unfixable without crazy hacks because of the way Plasma reads in texture data.
|
||||||
|
// "<Deledrius> I feel like any texture at a 1x1 level is essentially academic. I mean, JPEG/DXT
|
||||||
|
// doesn't even compress that, and what is it? Just the average color of the whole
|
||||||
|
// texture in a single pixel?"
|
||||||
|
// :)
|
||||||
|
return std::max(num_levels - 2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _generate_detail_alpha(pyGLTexture* self, GLint level, float* result) {
|
||||||
|
float dropoff_start, dropoff_stop, detail_max, detail_min;
|
||||||
|
if (!_get_float(self->m_textureKey, "detail_fade_start", dropoff_start))
|
||||||
|
return -1;
|
||||||
|
if (!_get_float(self->m_textureKey, "detail_fade_stop", dropoff_stop))
|
||||||
|
return -1;
|
||||||
|
if (!_get_float(self->m_textureKey, "detail_opacity_start", detail_max))
|
||||||
|
return -1;
|
||||||
|
if (!_get_float(self->m_textureKey, "detail_opacity_stop", detail_min))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
dropoff_start /= 100.f;
|
||||||
|
dropoff_start *= _get_num_levels(self);
|
||||||
|
dropoff_stop /= 100.f;
|
||||||
|
dropoff_stop *= _get_num_levels(self);
|
||||||
|
detail_max /= 100.f;
|
||||||
|
detail_min /= 100.f;
|
||||||
|
|
||||||
|
float alpha = (level - dropoff_start) * (detail_min - detail_max) / (dropoff_stop - dropoff_start) + detail_max;
|
||||||
|
if (detail_min < detail_max)
|
||||||
|
*result = std::min(detail_max, std::max(detail_min, alpha));
|
||||||
|
else
|
||||||
|
*result = std::min(detail_min, std::max(detail_max, alpha));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _generate_detail_map(pyGLTexture* self, uint8_t* buf, size_t bufsz, GLint level) {
|
||||||
|
float alpha;
|
||||||
|
if (_generate_detail_alpha(self, level, &alpha) != 0)
|
||||||
|
return -1;
|
||||||
|
PyObjectRef pydetail_blend = PyObject_GetAttrString(self->m_textureKey, "detail_blend");
|
||||||
|
if (!pydetail_blend)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
size_t detail_blend = PyLong_AsSize_t(pydetail_blend);
|
||||||
|
switch (detail_blend) {
|
||||||
|
case TEX_DETAIL_ALPHA: {
|
||||||
|
for (size_t i = 0; i < bufsz; i += 4) {
|
||||||
|
buf[i+3] = (uint8_t)(((float)buf[i+3]) * alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TEX_DETAIL_ADD: {
|
||||||
|
for (size_t i = 0; i < bufsz; i += 4) {
|
||||||
|
buf[i+0] = (uint8_t)(((float)buf[i+0]) * alpha);
|
||||||
|
buf[i+1] = (uint8_t)(((float)buf[i+1]) * alpha);
|
||||||
|
buf[i+2] = (uint8_t)(((float)buf[i+2]) * alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TEX_DETAIL_MULTIPLY: {
|
||||||
|
float invert_alpha = (1.f - alpha) * 255.f;
|
||||||
|
for (size_t i = 0; i < bufsz; i += 4) {
|
||||||
|
buf[i+3] = (uint8_t)((invert_alpha + (float)buf[i+3]) * alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static _LevelData _get_level_data(pyGLTexture* self, GLint level, bool bgra, bool quiet) {
|
static _LevelData _get_level_data(pyGLTexture* self, GLint level, bool bgra, bool quiet) {
|
||||||
GLint width, height;
|
GLint width, height;
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
|
||||||
@ -206,6 +311,16 @@ static PyObject* pyGLTexture_get_level_data(pyGLTexture* self, PyObject* args, P
|
|||||||
} while ((sptr += row_stride) < (eptr -= row_stride));
|
} while ((sptr += row_stride) < (eptr -= row_stride));
|
||||||
delete[] temp;
|
delete[] temp;
|
||||||
|
|
||||||
|
// Detail blend
|
||||||
|
PyObjectRef is_detail_map = PyObject_GetAttrString(self->m_textureKey, "is_detail_map");
|
||||||
|
if (PyLong_AsLong(is_detail_map) != 0) {
|
||||||
|
if (_generate_detail_map(self, data.m_data, data.m_dataSize, level) != 0) {
|
||||||
|
delete[] data.m_data;
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "error while baking detail map");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (calc_alpha) {
|
if (calc_alpha) {
|
||||||
for (size_t i = 0; i < data.m_dataSize; i += 4)
|
for (size_t i = 0; i < data.m_dataSize; i += 4)
|
||||||
data.m_data[i + 3] = (data.m_data[i + 0] + data.m_data[i + 1] + data.m_data[i + 2]) / 3;
|
data.m_data[i + 3] = (data.m_data[i + 0] + data.m_data[i + 1] + data.m_data[i + 2]) / 3;
|
||||||
@ -268,8 +383,13 @@ static PyObject* pyGLTexture_get_has_alpha(pyGLTexture* self, void*) {
|
|||||||
return PyBool_FromLong(0);
|
return PyBool_FromLong(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject* pyGLTexture_get_num_levels(pyGLTexture* self, void*) {
|
||||||
|
return PyLong_FromLong(_get_num_levels(self));
|
||||||
|
}
|
||||||
|
|
||||||
static PyGetSetDef pyGLTexture_GetSet[] = {
|
static PyGetSetDef pyGLTexture_GetSet[] = {
|
||||||
{ _pycs("has_alpha"), (getter)pyGLTexture_get_has_alpha, NULL, NULL, NULL },
|
{ _pycs("has_alpha"), (getter)pyGLTexture_get_has_alpha, NULL, NULL, NULL },
|
||||||
|
{ _pycs("num_levels"), (getter)pyGLTexture_get_num_levels, NULL, NULL, NULL },
|
||||||
{ NULL, NULL, NULL, NULL, NULL }
|
{ NULL, NULL, NULL, NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,12 +21,19 @@ import weakref
|
|||||||
|
|
||||||
from . import explosions
|
from . import explosions
|
||||||
from .. import helpers
|
from .. import helpers
|
||||||
from .. import korlib
|
from ..korlib import *
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
class _Texture:
|
class _Texture:
|
||||||
def __init__(self, texture=None, image=None, use_alpha=None, force_calc_alpha=False):
|
_DETAIL_BLEND = {
|
||||||
assert (texture or image)
|
TEX_DETAIL_ALPHA: "AL",
|
||||||
|
TEX_DETAIL_ADD: "AD",
|
||||||
|
TEX_DETAIL_MULTIPLY: "ML",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
texture, image = kwargs.get("texture"), kwargs.get("image")
|
||||||
|
assert texture or image
|
||||||
|
|
||||||
if texture is not None:
|
if texture is not None:
|
||||||
if image is None:
|
if image is None:
|
||||||
@ -34,16 +41,29 @@ class _Texture:
|
|||||||
self.calc_alpha = texture.use_calculate_alpha
|
self.calc_alpha = texture.use_calculate_alpha
|
||||||
self.mipmap = texture.use_mipmap
|
self.mipmap = texture.use_mipmap
|
||||||
else:
|
else:
|
||||||
|
self.layer = kwargs.get("layer")
|
||||||
self.calc_alpha = False
|
self.calc_alpha = False
|
||||||
self.mipmap = False
|
self.mipmap = False
|
||||||
|
|
||||||
if force_calc_alpha or self.calc_alpha:
|
if kwargs.get("is_detail_map", False):
|
||||||
self.calc_alpha = True
|
self.is_detail_map = True
|
||||||
self.use_alpha = True
|
self.detail_blend = kwargs["detail_blend"]
|
||||||
elif use_alpha is None:
|
self.detail_fade_start = kwargs["detail_fade_start"]
|
||||||
self.use_alpha = (image.channels == 4 and image.use_alpha)
|
self.detail_fade_stop = kwargs["detail_fade_stop"]
|
||||||
|
self.detail_opacity_start = kwargs["detail_opacity_start"]
|
||||||
|
self.detail_opacity_stop = kwargs["detail_opacity_stop"]
|
||||||
|
self.calc_alpha = False
|
||||||
|
self.use_alpha = True
|
||||||
else:
|
else:
|
||||||
self.use_alpha = use_alpha
|
self.is_detail_map = False
|
||||||
|
use_alpha = kwargs.get("use_alpha")
|
||||||
|
if kwargs.get("force_calc_alpha", False) or self.calc_alpha:
|
||||||
|
self.calc_alpha = True
|
||||||
|
self.use_alpha = True
|
||||||
|
elif use_alpha is None:
|
||||||
|
self.use_alpha = (image.channels == 4 and image.use_alpha)
|
||||||
|
else:
|
||||||
|
self.use_alpha = use_alpha
|
||||||
|
|
||||||
self.image = image
|
self.image = image
|
||||||
|
|
||||||
@ -51,13 +71,14 @@ class _Texture:
|
|||||||
if not isinstance(other, _Texture):
|
if not isinstance(other, _Texture):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.image == other.image:
|
# Yeah, the string name is a unique identifier. So shoot me.
|
||||||
if self.calc_alpha == other.calc_alpha:
|
if str(self) == str(other):
|
||||||
self._update(other)
|
self._update(other)
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.image.name) ^ hash(self.calc_alpha)
|
return hash(str(self))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.mipmap:
|
if self.mipmap:
|
||||||
@ -66,10 +87,17 @@ class _Texture:
|
|||||||
name = str(Path(self.image.name).with_suffix(".bmp"))
|
name = str(Path(self.image.name).with_suffix(".bmp"))
|
||||||
if self.calc_alpha:
|
if self.calc_alpha:
|
||||||
name = "ALPHAGEN_{}".format(name)
|
name = "ALPHAGEN_{}".format(name)
|
||||||
|
|
||||||
|
if self.is_detail_map:
|
||||||
|
name = "DETAILGEN_{}-{}-{}-{}-{}_{}".format(self._DETAIL_BLEND[self.detail_blend],
|
||||||
|
self.detail_fade_start, self.detail_fade_stop,
|
||||||
|
self.detail_opacity_start, self.detail_opacity_stop,
|
||||||
|
name)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def _update(self, other):
|
def _update(self, other):
|
||||||
"""Update myself with any props that might be overridable from another copy of myself"""
|
"""Update myself with any props that might be overridable from another copy of myself"""
|
||||||
|
# NOTE: detail map properties should NEVER be overridden. NEVER. EVER. kthx.
|
||||||
if other.use_alpha:
|
if other.use_alpha:
|
||||||
self.use_alpha = True
|
self.use_alpha = True
|
||||||
if other.mipmap:
|
if other.mipmap:
|
||||||
@ -424,6 +452,7 @@ class MaterialConverter:
|
|||||||
def _export_texture_type_image(self, bo, layer, slot):
|
def _export_texture_type_image(self, bo, layer, slot):
|
||||||
"""Exports a Blender ImageTexture to a plLayer"""
|
"""Exports a Blender ImageTexture to a plLayer"""
|
||||||
texture = slot.texture
|
texture = slot.texture
|
||||||
|
layer_props = texture.plasma_layer
|
||||||
|
|
||||||
# Does the image have any alpha at all?
|
# Does the image have any alpha at all?
|
||||||
if texture.image is not None:
|
if texture.image is not None:
|
||||||
@ -462,7 +491,17 @@ class MaterialConverter:
|
|||||||
dtm.visWidth, dtm.visHeight = 1024, 1024
|
dtm.visWidth, dtm.visHeight = 1024, 1024
|
||||||
layer.texture = dtm.key
|
layer.texture = dtm.key
|
||||||
else:
|
else:
|
||||||
key = _Texture(texture=texture, use_alpha=has_alpha, force_calc_alpha=slot.use_stencil)
|
detail_blend = TEX_DETAIL_ALPHA
|
||||||
|
if layer_props.is_detail_map and texture.use_mipmap:
|
||||||
|
if slot.blend_type == "ADD":
|
||||||
|
detail_blend = TEX_DETAIL_ADD
|
||||||
|
elif slot.blend_type == "MULTIPLY":
|
||||||
|
detail_blend = TEX_DETAIL_MULTIPLY
|
||||||
|
|
||||||
|
key = _Texture(texture=texture, use_alpha=has_alpha, force_calc_alpha=slot.use_stencil,
|
||||||
|
is_detail_map=layer_props.is_detail_map, detail_blend=detail_blend,
|
||||||
|
detail_fade_start=layer_props.detail_fade_start, detail_fade_stop=layer_props.detail_fade_stop,
|
||||||
|
detail_opacity_start=layer_props.detail_opacity_start, detail_opacity_stop=layer_props.detail_opacity_stop)
|
||||||
if key not in self._pending:
|
if key not in self._pending:
|
||||||
print(" Stashing '{}' for conversion as '{}'".format(texture.image.name, str(key)))
|
print(" Stashing '{}' for conversion as '{}'".format(texture.image.name, str(key)))
|
||||||
self._pending[key] = [layer.key,]
|
self._pending[key] = [layer.key,]
|
||||||
@ -481,7 +520,7 @@ class MaterialConverter:
|
|||||||
print(" Stashing '{}' for conversion as '{}'".format(image.name, str(key)))
|
print(" Stashing '{}' for conversion as '{}'".format(image.name, str(key)))
|
||||||
self._pending[key] = [layer.key,]
|
self._pending[key] = [layer.key,]
|
||||||
else:
|
else:
|
||||||
print(" Found another user of '{}'".format(image.name))
|
print(" Found another user of '{}'".format(key))
|
||||||
self._pending[key].append(layer.key)
|
self._pending[key].append(layer.key)
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
@ -498,32 +537,18 @@ class MaterialConverter:
|
|||||||
self._resize_image(image, eWidth, eHeight)
|
self._resize_image(image, eWidth, eHeight)
|
||||||
|
|
||||||
# Some basic mipmap settings.
|
# Some basic mipmap settings.
|
||||||
numLevels = math.floor(math.log(max(eWidth, eHeight), 2)) + 1 if key.mipmap else 1
|
|
||||||
compression = plBitmap.kDirectXCompression if key.mipmap else plBitmap.kUncompressed
|
compression = plBitmap.kDirectXCompression if key.mipmap else plBitmap.kUncompressed
|
||||||
dxt = plBitmap.kDXT5 if key.use_alpha or key.calc_alpha else plBitmap.kDXT1
|
dxt = plBitmap.kDXT5 if key.use_alpha or key.calc_alpha else plBitmap.kDXT1
|
||||||
|
|
||||||
# Major Workaround Ahoy
|
|
||||||
# There is a bug in Cyan's level size algorithm that causes it to not allocate enough memory
|
|
||||||
# for the color block in certain mipmaps. I personally have encountered an access violation on
|
|
||||||
# 1x1 DXT5 mip levels -- the code only allocates an alpha block and not a color block. Paradox
|
|
||||||
# reports that if any dimension is smaller than 4px in a mip level, OpenGL doesn't like Cyan generated
|
|
||||||
# data. So, we're going to lop off the last two mip levels, which should be 1px and 2px as the smallest.
|
|
||||||
# This bug is basically unfixable without crazy hacks because of the way Plasma reads in texture data.
|
|
||||||
# "<Deledrius> I feel like any texture at a 1x1 level is essentially academic. I mean, JPEG/DXT
|
|
||||||
# doesn't even compress that, and what is it? Just the average color of the whole
|
|
||||||
# texture in a single pixel?"
|
|
||||||
# :)
|
|
||||||
if key.mipmap:
|
|
||||||
# If your mipmap only has 2 levels (or less), then you deserve to phail...
|
|
||||||
numLevels = max(numLevels - 2, 2)
|
|
||||||
|
|
||||||
# Grab the image data from OpenGL and stuff it into the plBitmap
|
# Grab the image data from OpenGL and stuff it into the plBitmap
|
||||||
helper = korlib.GLTexture(image)
|
helper = GLTexture(key)
|
||||||
with helper as glimage:
|
with helper as glimage:
|
||||||
if key.mipmap:
|
if key.mipmap:
|
||||||
|
numLevels = glimage.num_levels
|
||||||
print(" Generating mip levels")
|
print(" Generating mip levels")
|
||||||
glimage.generate_mipmap()
|
glimage.generate_mipmap()
|
||||||
else:
|
else:
|
||||||
|
numLevels = 1
|
||||||
print(" Stuffing image data")
|
print(" Stuffing image data")
|
||||||
|
|
||||||
# Uncompressed bitmaps are BGRA
|
# Uncompressed bitmaps are BGRA
|
||||||
@ -606,7 +631,8 @@ class MaterialConverter:
|
|||||||
result = False
|
result = False
|
||||||
else:
|
else:
|
||||||
# Using bpy.types.Image.pixels is VERY VERY VERY slow...
|
# Using bpy.types.Image.pixels is VERY VERY VERY slow...
|
||||||
with korlib.GLTexture(image) as glimage:
|
key = _Texture(image=image)
|
||||||
|
with GLTexture(key) as glimage:
|
||||||
result = glimage.has_alpha
|
result = glimage.has_alpha
|
||||||
|
|
||||||
self._alphatest[image] = result
|
self._alphatest[image] = result
|
||||||
|
@ -34,3 +34,5 @@ except ImportError:
|
|||||||
assert not stream.eof()
|
assert not stream.eof()
|
||||||
size = stream.readInt()
|
size = stream.readInt()
|
||||||
return (header, size)
|
return (header, size)
|
||||||
|
else:
|
||||||
|
from .texture import TEX_DETAIL_ALPHA, TEX_DETAIL_ADD, TEX_DETAIL_MULTIPLY
|
||||||
|
@ -14,16 +14,26 @@
|
|||||||
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bgl
|
import bgl
|
||||||
|
import math
|
||||||
from PyHSPlasma import plBitmap
|
from PyHSPlasma import plBitmap
|
||||||
|
|
||||||
# BGL doesn't know about this as of Blender 2.74
|
# BGL doesn't know about this as of Blender 2.74
|
||||||
bgl.GL_GENERATE_MIPMAP = 0x8191
|
bgl.GL_GENERATE_MIPMAP = 0x8191
|
||||||
bgl.GL_BGRA = 0x80E1
|
bgl.GL_BGRA = 0x80E1
|
||||||
|
|
||||||
|
# Some texture generation flags
|
||||||
|
TEX_DETAIL_ALPHA = 0
|
||||||
|
TEX_DETAIL_ADD = 1
|
||||||
|
TEX_DETAIL_MULTIPLY = 2
|
||||||
|
|
||||||
class GLTexture:
|
class GLTexture:
|
||||||
def __init__(self, blimg):
|
def __init__(self, texkey=None):
|
||||||
self._ownit = (blimg.bindcode[0] == 0)
|
self._texkey = texkey
|
||||||
self._blimg = blimg
|
self._ownit = (self._blimg.bindcode[0] == 0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _blimg(self):
|
||||||
|
return self._texkey.image
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
"""Sets the Blender Image as the active OpenGL texture"""
|
"""Sets the Blender Image as the active OpenGL texture"""
|
||||||
@ -46,6 +56,14 @@ class GLTexture:
|
|||||||
if self._ownit:
|
if self._ownit:
|
||||||
self._blimg.gl_free()
|
self._blimg.gl_free()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _detail_falloff(self):
|
||||||
|
num_levels = self.num_levels
|
||||||
|
return ((self._texkey.detail_fade_start / 100.0) * num_levels,
|
||||||
|
(self._texkey.detail_fade_stop / 100.0) * num_levels,
|
||||||
|
self._texkey.detail_opacity_start / 100.0,
|
||||||
|
self._texkey.detail_opacity_stop / 100.0)
|
||||||
|
|
||||||
def generate_mipmap(self):
|
def generate_mipmap(self):
|
||||||
"""Generates all mip levels for this texture"""
|
"""Generates all mip levels for this texture"""
|
||||||
self._mipmap_state = self._get_tex_param(bgl.GL_GENERATE_MIPMAP)
|
self._mipmap_state = self._get_tex_param(bgl.GL_GENERATE_MIPMAP)
|
||||||
@ -82,12 +100,29 @@ class GLTexture:
|
|||||||
src, dst = i * row_stride, (height - (i+1)) * row_stride
|
src, dst = i * row_stride, (height - (i+1)) * row_stride
|
||||||
finalBuf[dst:dst+row_stride] = buf[src:src+row_stride]
|
finalBuf[dst:dst+row_stride] = buf[src:src+row_stride]
|
||||||
|
|
||||||
|
# If this is a detail map, then we need to bake that per-level here.
|
||||||
|
if self._texkey.is_detail_map:
|
||||||
|
detail_blend = self._texkey.detail_blend
|
||||||
|
if detail_blend == TEX_DETAIL_ALPHA:
|
||||||
|
self._make_detail_map_alpha(finalBuf, level)
|
||||||
|
elif detail_blend == TEX_DETAIL_ADD:
|
||||||
|
self._make_detail_map_alpha(finalBuf, level)
|
||||||
|
elif detail_blend == TEX_DETAIL_MULTIPLY:
|
||||||
|
self._make_detail_map_mult(finalBuf, level)
|
||||||
|
|
||||||
# Do we need to calculate the alpha component?
|
# Do we need to calculate the alpha component?
|
||||||
if calc_alpha:
|
if calc_alpha:
|
||||||
for i in range(0, size, 4):
|
for i in range(0, size, 4):
|
||||||
finalBuf[i+3] = int(sum(finalBuf[i:i+3]) / 3)
|
finalBuf[i+3] = int(sum(finalBuf[i:i+3]) / 3)
|
||||||
return bytes(finalBuf)
|
return bytes(finalBuf)
|
||||||
|
|
||||||
|
def _get_detail_alpha(self, level, dropoff_start, dropoff_stop, detail_max, detail_min):
|
||||||
|
alpha = (level - dropoff_start) * (detail_min - detail_max) / (dropoff_stop - dropoff_start) + detail_max
|
||||||
|
if detail_min < detail_max:
|
||||||
|
return min(detail_max, max(detail_min, alpha))
|
||||||
|
else:
|
||||||
|
return min(detail_min, max(detail_max, alpha))
|
||||||
|
|
||||||
def _get_integer(self, arg):
|
def _get_integer(self, arg):
|
||||||
buf = bgl.Buffer(bgl.GL_INT, 1)
|
buf = bgl.Buffer(bgl.GL_INT, 1)
|
||||||
bgl.glGetIntegerv(arg, buf)
|
bgl.glGetIntegerv(arg, buf)
|
||||||
@ -109,6 +144,44 @@ class GLTexture:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _make_detail_map_add(self, data, level):
|
||||||
|
dropoff_start, dropoff_stop, detail_max, detail_min = self._detail_falloff
|
||||||
|
alpha = self._get_detail_alpha(level, dropoff_start, dropoff_stop, detail_max, detail_min)
|
||||||
|
for i in range(0, len(data), 4):
|
||||||
|
data[i] = int(data[i] * alpha)
|
||||||
|
data[i+1] = int(data[i+1] * alpha)
|
||||||
|
data[i+2] = int(data[i+2] * alpha)
|
||||||
|
|
||||||
|
def _make_detail_map_alpha(self, data, level):
|
||||||
|
dropoff_start, dropoff_end, detail_max, detail_min = self._detail_falloff
|
||||||
|
alpha = self._get_detail_alpha(level, dropoff_start, dropoff_end, detail_max, detail_min)
|
||||||
|
for i in range(0, len(data), 4):
|
||||||
|
data[i+3] = int(data[i+3] * alpha)
|
||||||
|
|
||||||
|
def _make_detail_map_mult(self, data, level):
|
||||||
|
dropoff_start, dropoff_end, detail_max, detail_min = self._detail_falloff
|
||||||
|
alpha = self._get_detail_alpha(level, dropoff_start, dropoff_end, detail_max, detail_min)
|
||||||
|
invert_alpha = (1.0 - alpha) * 255.0
|
||||||
|
for i in range(0, len(data), 4):
|
||||||
|
data[i+3] = int(invert_alpha + data[i+3] * alpha)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def num_levels(self):
|
||||||
|
numLevels = math.floor(math.log(max(self._blimg.size), 2)) + 1
|
||||||
|
|
||||||
|
# Major Workaround Ahoy
|
||||||
|
# There is a bug in Cyan's level size algorithm that causes it to not allocate enough memory
|
||||||
|
# for the color block in certain mipmaps. I personally have encountered an access violation on
|
||||||
|
# 1x1 DXT5 mip levels -- the code only allocates an alpha block and not a color block. Paradox
|
||||||
|
# reports that if any dimension is smaller than 4px in a mip level, OpenGL doesn't like Cyan generated
|
||||||
|
# data. So, we're going to lop off the last two mip levels, which should be 1px and 2px as the smallest.
|
||||||
|
# This bug is basically unfixable without crazy hacks because of the way Plasma reads in texture data.
|
||||||
|
# "<Deledrius> I feel like any texture at a 1x1 level is essentially academic. I mean, JPEG/DXT
|
||||||
|
# doesn't even compress that, and what is it? Just the average color of the whole
|
||||||
|
# texture in a single pixel?"
|
||||||
|
# :)
|
||||||
|
return max(numLevels - 2, 2)
|
||||||
|
|
||||||
def store_in_mipmap(self, mipmap, data, compression):
|
def store_in_mipmap(self, mipmap, data, compression):
|
||||||
func = mipmap.CompressImage if compression == plBitmap.kDirectXCompression else mipmap.setLevel
|
func = mipmap.CompressImage if compression == plBitmap.kDirectXCompression else mipmap.setLevel
|
||||||
for i, level in enumerate(data):
|
for i, level in enumerate(data):
|
||||||
|
@ -52,3 +52,24 @@ class PlasmaLayer(bpy.types.PropertyGroup):
|
|||||||
anim_loop = BoolProperty(name="Loop",
|
anim_loop = BoolProperty(name="Loop",
|
||||||
description="Loop layer animation",
|
description="Loop layer animation",
|
||||||
default=True)
|
default=True)
|
||||||
|
|
||||||
|
is_detail_map = BoolProperty(name="Detail Fade",
|
||||||
|
description="Texture fades out as distance from the camera increases",
|
||||||
|
default=False,
|
||||||
|
options=set())
|
||||||
|
detail_fade_start = IntProperty(name="Falloff Start",
|
||||||
|
description="",
|
||||||
|
min=0, max=100, default=0,
|
||||||
|
options=set(), subtype="PERCENTAGE")
|
||||||
|
detail_fade_stop = IntProperty(name="Falloff Stop",
|
||||||
|
description="",
|
||||||
|
min=0, max=100, default=100,
|
||||||
|
options=set(), subtype="PERCENTAGE")
|
||||||
|
detail_opacity_start = IntProperty(name="Opacity Start",
|
||||||
|
description="",
|
||||||
|
min=0, max=100, default=50,
|
||||||
|
options=set(), subtype="PERCENTAGE")
|
||||||
|
detail_opacity_stop = IntProperty(name="Opacity Stop",
|
||||||
|
description="",
|
||||||
|
min=0, max=100, default=0,
|
||||||
|
options=set(), subtype="PERCENTAGE")
|
||||||
|
@ -66,6 +66,21 @@ class PlasmaLayerPanel(TextureButtonsPanel, bpy.types.Panel):
|
|||||||
layer_props = texture.plasma_layer
|
layer_props = texture.plasma_layer
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
|
col = layout.column()
|
||||||
|
col.active = texture.use_mipmap
|
||||||
|
col.prop(layer_props, "is_detail_map", text="Use Detail Blending")
|
||||||
|
|
||||||
|
split = layout.split()
|
||||||
|
col = split.column(align=True)
|
||||||
|
col.active = texture.use_mipmap and layer_props.is_detail_map
|
||||||
|
col.prop(layer_props, "detail_fade_start")
|
||||||
|
col.prop(layer_props, "detail_fade_stop")
|
||||||
|
col = split.column(align=True)
|
||||||
|
col.active = texture.use_mipmap and layer_props.is_detail_map
|
||||||
|
col.prop(layer_props, "detail_opacity_start")
|
||||||
|
col.prop(layer_props, "detail_opacity_stop")
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
split = layout.split()
|
split = layout.split()
|
||||||
col = split.column()
|
col = split.column()
|
||||||
col.label("Animation:")
|
col.label("Animation:")
|
||||||
|
Reference in New Issue
Block a user