// BlockyFroggy
// Copyright © 2017 John Ryland.
// All rights reserved.
#include "VoxelModel.h"
#include "Log.h"
#include "Tiles-Car-Test.h"
#include "PngImage.h"
#include "ResourceLoader.h"
#include <cstring>
#include <cassert>
DECLARE_LOG_CONTEXT(VXM)
bool debug = false;
VoxelModels::VoxelModels()
{
decodeData(Tiles_Car_Test_png, Tiles_Car_Test_png_len);
}
VoxelModels::~VoxelModels()
{
}
void VoxelModels::reloadDataFromFile(const char* a_fileName, bool a_onlineAsset)
{
loadFileAsync(a_fileName, a_onlineAsset, [this](Resource* res) {
decodeData(res->data.data(), res->data.size());
m_dataChanged = true;
});
}
void VoxelModels::decodeData(const unsigned char* a_pngData, size_t a_dataLen)
{
unsigned long w, h;
m_decodedData.clear();
int error = ::decodePNG(m_decodedData, w, h, a_pngData, a_dataLen);
if (error != 0)
Log(LL_Error, "error: %d", error);
m_decodedDataWidth = w;
m_decodedDataHeight = h;
m_voxelModelCache.clear();
}
void emitQuad(VoxelTriangleList& a_tris, int axis, int x, int y, int z, int _u1, int _v1, int _u2, int _v2, bool cw, int w=1, int h=1, int d=1)
{
// Turn cube in to triangles
int cubeIdx[4][6] = { { 0, 0, 0, 7, 7, 7 }, { 1, 4, 2, 5, 6, 3 }, { 3, 5, 6, 4, 2, 1 }, { 2, 1, 4, 6, 3, 5 } };
//for (int i = 0; i < 6; i++)
int i = axis + ((cw) ? 0 : 3);
{
/*
if (debug)
Log(LL_Debug, "axis: %i uv: %i %i -> %i %i %i %i %i", axis, _u1, _v1, _u2, _v2, w, h, d);
if (axis == 2 && debug)
Log(LL_Debug, "uv: %i %i -> %i %i %i %i %i", _u1, _v1, _u2, _v2, w, h, d);
*/
int t1[2][3] = { { cubeIdx[0][i], cubeIdx[1][i], cubeIdx[2][i] }, { cubeIdx[2][i], cubeIdx[3][i], cubeIdx[0][i] } };
for (int t = 0; t < 2; t++) {
VoxelTriangle tri;
tri.m_axis = i;
for (int v = 0; v < 3; v++) {
tri.m_verts[v].m_x = x + ((t1[t][v]&2) ? float(w) : 0.0f);
tri.m_verts[v].m_y = y + ((t1[t][v]&1) ? float(h) : 0.0f);
tri.m_verts[v].m_z = z + ((t1[t][v]&4) ? float(d) : 0.0f);
if (axis == 0) {
tri.m_uvs[v].m_u = (t1[t][v]&2) ? float(_u2) : float(_u1)+0.0;
tri.m_uvs[v].m_v = (t1[t][v]&1) ? float(_v2) : float(_v1)+1.0;
} else if (axis == 2) {
tri.m_uvs[v].m_u = (t1[t][v]&2) ? float(_u2) : float(_u1)+0.0;
tri.m_uvs[v].m_v = (t1[t][v]&4) ? float(_v2) : float(_v1)+1.0;
} else {
tri.m_uvs[v].m_u = (t1[t][v]&1) ? float(_u2) : float(_u1)+0.0;
tri.m_uvs[v].m_v = (t1[t][v]&4) ? float(_v2) : float(_v1)+1.0;
}
}
a_tris.push_back(tri);
}
}
}
// Can change just here if test alpha or a particular color value
bool isTransparent(uint32_t val)
{
return (val & 0xff000000) == 0;
}
class Array3D
{
public:
Array3D(int _w, int _h, int _d) : w(_w), h(_h), d(_d) {
m_data = new uint32_t**[w];
for (int i = 0; i < w; i++) {
m_data[i] = new uint32_t*[h];
for (int j = 0; j < h; j++) {
m_data[i][j] = new uint32_t[d];
}
}
}
~Array3D() {
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
delete[] m_data[i][j];
}
delete[] m_data[i];
}
delete[] m_data;
}
void initFromData(const std::vector<unsigned char>& a_data, int xOff, int yOff, size_t stride, size_t imgHeight) {
uint32_t *data = (uint32_t*)&a_data[0];
for (int k = 0; k < d; k++) {
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
size_t x = i+xOff;
size_t y = k*h+j+yOff;
if (y < imgHeight && x < stride) {
m_data[i][j][k] = data[y * stride + x];
}
}
}
}
}
void outputXAxisData(uint32_t* a_data, int xOff, int yOff) {
int columns = w / d;
assert(w % d == 0);
assert(w >= d);
for (int k = 0; k < d; k++) {
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
size_t x = k+xOff + (i%columns)*d;
size_t y = (i/columns)*h + j+yOff;
if (y < 256 && x < 256) {
a_data[y * 256 + x] = m_data[i][j][k];
}
}
}
}
}
void outputZAxisData(uint32_t* a_data, int xOff, int yOff) {
for (int k = 0; k < d; k++) {
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
size_t x = i+xOff;
size_t y = j*d+k+yOff;
if (y < 256 && x < 256) {
a_data[y * 256 + x] = m_data[i][j][k];
}
}
}
}
}
void downScale(Array3D& out) {
assert(out.w == w/2);
assert(out.h == h/2);
assert(out.d == d/2);
for (int k = 0; k < d; k+=2) {
for (int j = 0; j < h; j+=2) {
for (int i = 0; i < w; i+=2) {
uint32_t r = 0;
uint32_t g = 0;
uint32_t b = 0;
int nonTrans = 0;
for (int o = 0; o < 8; o++) {
uint32_t col = m_data[i+((o&1)>>0)][j+((o&2)>>1)][k+((i&4)>>2)];
if (!isTransparent(col)) {
nonTrans++;
r += (col >> 16) & 0xff;
g += (col >> 8) & 0xff;
b += (col >> 0) & 0xff;
}
}
if (nonTrans >= 4) {
r /= nonTrans;
g /= nonTrans;
b /= nonTrans;
out.m_data[i/2][j/2][k/2] = (0xff<<24) | (r<<16) | (g<<8) | b;
} else {
out.m_data[i/2][j/2][k/2] = 0;
}
}
}
}
}
uint32_t*** m_data;
int w, h, d;
};
class Model
{
public:
Model(int w, int h, int d)
: mip8(w,h,d)
, mip4(w/2,h/2,d/2)
, mip2(w/4,h/4,d/4)
, mip1(w/8,h/8,d/8)
, mips{ &mip8, &mip4, &mip2, &mip1 }
{
}
~Model()
{
}
void initFromData(const std::vector<unsigned char>& a_data, int xOff, int yOff, size_t stride, size_t imgHeight) {
mip8.initFromData(a_data, xOff, yOff, stride, imgHeight);
mip8.downScale(mip4);
mip4.downScale(mip2);
mip2.downScale(mip1);
}
Array3D mip8, mip4, mip2, mip1;
Array3D* mips[4]; // 8x8 4x4 2x2 1x1
};
void loadModel(const std::vector<unsigned char>& a_data, CachedVoxelModel& a_model, int xOff, int yOff, int w, int h, int d, bool direction, size_t stride, size_t imgHeight)
{
uint32_t *data = (uint32_t*)&a_data[0];
for (int p = 0; p < d; p++)
{
for (int q = 0; q < h; q++)
{
for (int r = 0; r < w; r++)
{
size_t indx = ((p)*h+q+yOff)*stride +r+xOff;
if (indx < stride*imgHeight)
{
uint32_t pixVal = data[ indx ];
uint32_t alpha = pixVal & 0xff000000;
pixVal = ((pixVal&0xff)<<16)|(pixVal&0xff00)|((pixVal>>16)&0xff);
if (alpha && ((pixVal & 0xffffff) != 0xff00ff))
{
VoxelBox tile;
tile.m_z = (h-1) - q;
tile.m_x = ((direction) ? ((w-1)-r) : r);
tile.m_y = p;
tile.m_w = 1;
tile.m_h = 1;
tile.m_d = 1;
tile.m_color = pixVal;
a_model.m_model.push_back(tile);
}
}
}
}
}
uint32_t *buf = new uint32_t[2*256*256+10000];
memset(buf, 0, sizeof(uint32_t)*2*256*256+10000);
uint32_t *dh = buf + 5000;
uint32_t *wh = buf + 5000;
uint32_t *dw = buf + 5000;
uint32_t *dh2 = buf + 5000 + 256*256;
uint32_t *wh2 = buf + 5000 + 256*256;
uint32_t *dw2 = buf + 5000 + 256*256;
for (int r = 0; r <= w; r++)
{
memset(dh2, 0, sizeof(uint32_t)*d*h);
for (int p = 0; p < d; p++)
{
for (int q = 0; q < h; q++)
{
size_t indx = ((p)*h+q+yOff)*stride +r+xOff;
uint32_t pixVal = (indx < stride*imgHeight && r!=w) ? data[ indx ] : 0;
uint32_t alpha = pixVal & 0xff000000;
pixVal = ((pixVal&0xff)<<16)|(pixVal&0xff00)|((pixVal>>16)&0xff);
if (alpha && ((pixVal & 0xffffff) != 0xff00ff))
{
if (!dh[p*h+q])
dh2[p*h+q] = 1;
dh[p*h+q] = 1;
} else {
if (dh[p*h+q])
dh2[p*h+q] = 2;
dh[p*h+q] = 0;
}
}
}
int x = ((direction) ? ((w-1)-r) : (r));
for (int p = 0; p < d; p++)
for (int q = 0; q < h; q++)
{
int q1 = q;
while ( q < h && dh2[p*h+q1] == dh2[p*h+q] ) {
q++;
}
int q2 = q;
q--;
int p1 = p;
int p2 = p;
while (p2 < d) // p2+1 < d
{
p2++;
bool okay = true;
for (int t = q1; t < q2; t++) {
if (dh2[p*h+q1] != dh2[p2*h+t]) {
okay = false;
break;
}
}
if (!okay) {
break;
}
}
if (dh2[p*h+q1])
{
int y = p;
int z = (h-1) - q;
/*
int x2 = x;
x2 += (direction && (dh2[p*h+q]==2)) ? 1 : 0;
x2 -= (!direction && (dh2[p*h+q]==1)) ? 1 : 0;
*/
bool cw = (dh2[p*h+q]==((direction)?2:1))?true:false;
assert(w % d == 0);
assert(w >= d);
int k = r;
if (cw == direction) k--;
int columns = w / d;
size_t u1 = p1+xOff + (k%columns)*d;
size_t v1 = (k/columns)*h + q2+yOff-1;
size_t u2 = p2+xOff + (k%columns)*d;
size_t v2 = (k/columns)*h + q1+yOff;
emitQuad(a_model.m_modelTriangles, 1, x+(cw?0:-1), y, z, int(u1), int(v1), int(u2), int(v2), (dh2[p*h+q]==1)?!direction:direction, 1, p2-p1, q2-q1);
for (int i = p1; i < p2; i++)
for (int j = q1; j < q2; j++)
dh2[i*h+j] = 0;
}
}
}
for (int p = 0; p <= d; p++)
{
memset(wh2, 0, sizeof(uint32_t)*w*h);
for (int r = 0; r < w; r++)
{
for (int q = 0; q < h; q++)
{
size_t indx = ((p)*h+q+yOff)*stride +r+xOff;
uint32_t pixVal = (indx < stride*imgHeight && p != d) ? data[ indx ] : 0;
uint32_t alpha = pixVal & 0xff000000;
pixVal = ((pixVal&0xff)<<16)|(pixVal&0xff00)|((pixVal>>16)&0xff);
if (alpha && ((pixVal & 0xffffff) != 0xff00ff))
{
if (!wh[r*h+q])
wh2[r*h+q] = 1;
wh[r*h+q] = 1;
} else {
if (wh[r*h+q])
wh2[r*h+q] = 2;
wh[r*h+q] = 0;
}
}
}
int y = p;
for (int r = 0; r < w; r++)
for (int q = 0; q < h; q++)
{
int q1 = q;
while ( q < h && wh2[r*h+q1] == wh2[r*h+q] ) {
q++;
}
int q2 = q-1;
q--;
int r1 = r;
int r2 = r;//+1;
while ((r2+1) < w)
{
r2++;
bool okay = true;
for (int t = q1; t <= q2; t++) {
if (wh2[r*h+q1] != wh2[r2*h+t]) {
okay = false;
break;
}
}
if (!okay) {
r2--;
break;
}
}
if (wh2[r*h+q1])
{
r2++;
int x = ((direction) ? ((w-1)-r2) : (r1));
int z = (h-1) - q2;
bool cw = (wh2[r1*h+q1]==1)?true:false;
int u1 = (direction) ? r2 : r1;
int u2 = (direction) ? r1 : r2;
emitQuad(a_model.m_modelTriangles, 2, x, y+(cw?0:-1), z, u1+xOff, (y+(cw?0:-1))*h+q2+yOff, u2+xOff, (y+(cw?0:-1))*h+q1+yOff, cw, r2-r1, 1, q2-q1+1);
for (int i = r1; i < r2; i++)
for (int j = q1; j <= q2; j++)
wh2[i*h+j] = 0;
}
}
}
for (int q = 0; q < h; q++)
{
memset(dw2, 0, sizeof(uint32_t)*d*w);
for (int p = 0; p < d; p++)
{
for (int r = 0; r < w; r++)
{
size_t indx = ((p)*h+q+yOff)*stride +r+xOff;
if (indx < stride*imgHeight)
{
uint32_t pixVal = data[ indx ];
uint32_t alpha = pixVal & 0xff000000;
pixVal = ((pixVal&0xff)<<16)|(pixVal&0xff00)|((pixVal>>16)&0xff);
if (alpha && ((pixVal & 0xffffff) != 0xff00ff))
{
if (!dw[p*w+r])
dw2[p*w+r] = 2;
dw[p*w+r] = 1;
} else {
if (dw[p*w+r])
dw2[p*w+r] = 1;
dw[p*w+r] = 0;
}
}
}
}
int z = (h-1) - q;
for (int p = 0; p < d; p++)
{
for (int r = 0; r < w; r++)
{
int r1 = r;
while ( r < w && dw2[p*w+r1] == dw2[p*w+r] ) {
r++;
}
int r2 = r;
r--;
int p1 = p;
int p2 = p;//+1;
while (p2 < d)
{
p2++;
bool okay = true;
for (int t = r1; t < r2; t++) {
if (dw2[p*w+r1] != dw2[p2*w+t]) {
okay = false;
break;
}
}
if (!okay)
break;
}
if (dw2[p*w+r1])
{
int x = ((direction) ? ((w-1)-r2) : r1);
int y = p;
bool cw = (dw2[p1*w+r1]==1)?true:false;
int k = (cw) ? q+1 : q;
int u1 = (direction) ? r2 : r1;
int u2 = (direction) ? r1 : r2;
emitQuad(a_model.m_modelTriangles, 0, x, y, z+(cw?1:0), u1+xOff, k*d+p1+yOff-1, u2+xOff, k*d+p2+yOff, cw, r2-r1, p2-p1, 1);
for (int i = r1; i < r2; i++)
for (int j = p1; j < p2; j++)
dw2[j*w+i] = 0;
}
}
}
}
delete[] buf;
}
VoxelBoxList& VoxelModels::getModel(const char* name, int xOff, int yOff, int w, int h, int d, bool direction, uint32_t* x_axis, uint32_t* z_axis)
{
if (m_voxelModelCache.count(name) && !m_voxelModelCache[name].m_dirty)
return m_voxelModelCache[name].m_model;
Model test(w,h,d);
test.initFromData(m_decodedData, xOff, yOff, 256, 256);
test.mip8.outputXAxisData(x_axis, xOff, yOff);
test.mip8.outputZAxisData(z_axis, xOff, yOff);
CachedVoxelModel cachedItem;
loadModel(m_decodedData, cachedItem, xOff, yOff, w, h, d, direction, m_decodedDataWidth, m_decodedDataHeight);
m_voxelModelCache.insert(std::pair<std::string,CachedVoxelModel>(std::string(name), cachedItem));
return m_voxelModelCache[name].m_model;
}
VoxelTriangleList& VoxelModels::getModelTriangles(const char* name, int xOff, int yOff, int w, int h, int d, bool direction, uint32_t* x_axis, uint32_t* z_axis)
{
if (m_voxelModelCache.count(name) && !m_voxelModelCache[name].m_dirty)
return m_voxelModelCache[name].m_modelTriangles;
Log(LL_Debug, "loading model %s", name);
Model test(w,h,d);
test.initFromData(m_decodedData, xOff, yOff, 256, 256);
test.mip8.outputXAxisData(x_axis, xOff, yOff);
test.mip8.outputZAxisData(z_axis, xOff, yOff);
CachedVoxelModel cachedItem;
std::string m = name;
if (m == "trk-left")
debug = true;
loadModel(m_decodedData, cachedItem, xOff, yOff, w, h, d, direction, m_decodedDataWidth, m_decodedDataHeight);
debug = false;
m_voxelModelCache.insert(std::pair<std::string,CachedVoxelModel>(std::string(name), cachedItem));
return m_voxelModelCache[name].m_modelTriangles;
}