Newer
Older
Import / applications / HighwayDash / ports / Framework / VoxelModel.cpp
//  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;
}