text: #include "engine.h"
  
  CVAR1R(ambient, 0x191919);
  FVARR(ambientscale, 0, 1, 16);
  
  CVAR1R(skylight, 0);
  FVARR(skylightscale, 0, 1, 16);
  
  extern void setupsunlight();
  CVAR1FR(sunlight, 0,
  {
      setupsunlight();
      cleardeferredlightshaders();
      clearshadowcache();
  });
  FVARFR(sunlightscale, 0, 1, 16, setupsunlight());
  
  vec sunlightdir(0, 0, 1);
  extern void setsunlightdir();
  FVARFR(sunlightyaw, 0, 0, 360, setsunlightdir());
  FVARFR(sunlightpitch, -90, 90, 90, setsunlightdir());
  
  void setsunlightdir()
  {
      sunlightdir = vec(sunlightyaw*RAD, sunlightpitch*RAD);
      loopk(3) if(fabs(sunlightdir[k]) < 1e-5f) sunlightdir[k] = 0;
      sunlightdir.normalize();
      setupsunlight();
  }
  
  void setupsunlight()
  {
      clearradiancehintscache();
  }
  
  static const surfaceinfo brightsurfaces[6] =
  {
      brightsurface,
      brightsurface,
      brightsurface,
      brightsurface,
      brightsurface,
      brightsurface
  };
  
  void brightencube(cube &c)
  {
      if(!c.ext) newcubeext(c, 0, false);
      memcpy(c.ext->surfaces, brightsurfaces, sizeof(brightsurfaces));
  }
  
  void setsurfaces(cube &c, const surfaceinfo *surfs, const vertinfo *verts, int numverts)
  {
      if(!c.ext || c.ext->maxverts < numverts) newcubeext(c, numverts, false);
      memcpy(c.ext->surfaces, surfs, sizeof(c.ext->surfaces));
      memcpy(c.ext->verts(), verts, numverts*sizeof(vertinfo));
  }
  
  void setsurface(cube &c, int orient, const surfaceinfo &src, const vertinfo *srcverts, int numsrcverts)
  {
      int dstoffset = 0;
      if(!c.ext) newcubeext(c, numsrcverts, true);
      else
      {
          int numbefore = 0, beforeoffset = 0;
          loopi(orient)
          {
              surfaceinfo &surf = c.ext->surfaces[i];
              int numverts = surf.totalverts();
              if(!numverts) continue;
              numbefore += numverts;
              beforeoffset = surf.verts + numverts;
          }
          int numafter = 0, afteroffset = c.ext->maxverts;
          for(int i = 5; i > orient; i--)
          {
              surfaceinfo &surf = c.ext->surfaces[i];
              int numverts = surf.totalverts();
              if(!numverts) continue;
              numafter += numverts;
              afteroffset = surf.verts;
          }
          if(afteroffset - beforeoffset >= numsrcverts) dstoffset = beforeoffset;
          else
          {
              cubeext *ext = c.ext;
              if(numbefore + numsrcverts + numafter > c.ext->maxverts)
              {
                  ext = growcubeext(c.ext, numbefore + numsrcverts + numafter);
                  memcpy(ext->surfaces, c.ext->surfaces, sizeof(ext->surfaces));
              }
              int offset = 0;
              if(numbefore == beforeoffset)
              {
                  if(numbefore && c.ext != ext) memcpy(ext->verts(), c.ext->verts(), numbefore*sizeof(vertinfo));
                  offset = numbefore;
              }
              else loopi(orient)
              {
                  surfaceinfo &surf = ext->surfaces[i];
                  int numverts = surf.totalverts();
                  if(!numverts) continue;
                  memmove(ext->verts() + offset, c.ext->verts() + surf.verts, numverts*sizeof(vertinfo));
                  surf.verts = offset;
                  offset += numverts;
              }
              dstoffset = offset;
              offset += numsrcverts;
              if(numafter && offset > afteroffset)
              {
                  offset += numafter;
                  for(int i = 5; i > orient; i--)
                  {
                      surfaceinfo &surf = ext->surfaces[i];
                      int numverts = surf.totalverts();
                      if(!numverts) continue;
                      offset -= numverts;
                      memmove(ext->verts() + offset, c.ext->verts() + surf.verts, numverts*sizeof(vertinfo));
                      surf.verts = offset;
                  }
              }
              if(c.ext != ext) setcubeext(c, ext);
          }
      }
      surfaceinfo &dst = c.ext->surfaces[orient];
      dst = src;
      dst.verts = dstoffset;
      if(srcverts) memcpy(c.ext->verts() + dstoffset, srcverts, numsrcverts*sizeof(vertinfo));
  }
  
  bool PackNode::insert(ushort &tx, ushort &ty, ushort tw, ushort th)
  {
      if((available < tw && available < th) || w < tw || h < th)
          return false;
      if(child1)
      {
          bool inserted = child1->insert(tx, ty, tw, th) ||
                          child2->insert(tx, ty, tw, th);
          available = max(child1->available, child2->available);
          if(!available) discardchildren();
          return inserted;
      }
      if(w == tw && h == th)
      {
          available = 0;
          tx = x;
          ty = y;
          return true;
      }
  
      if(w - tw > h - th)
      {
          child1 = new PackNode(x, y, tw, h);
          child2 = new PackNode(x + tw, y, w - tw, h);
      }
      else
      {
          child1 = new PackNode(x, y, w, th);
          child2 = new PackNode(x, y + th, w, h - th);
      }
  
      bool inserted = child1->insert(tx, ty, tw, th);
      available = max(child1->available, child2->available);
      return inserted;
  }
  
  void PackNode::reserve(ushort tx, ushort ty, ushort tw, ushort th)
  {
      if(tx + tw <= x || tx >= x + w || ty + th <= y || ty >= y + h) return;
      if(child1)
      {
          child1->reserve(tx, ty, tw, th);
          child2->reserve(tx, ty, tw, th);
          available = max(child1->available, child2->available);
          return;
      }
      int dx1 = tx - x, dx2 = x + w - tx - tw, dx = max(dx1, dx2),
          dy1 = ty - y, dy2 = y + h - ty - th, dy = max(dy1, dy2),
          split;
      if(dx > dy)
      {
          if(dx1 > dx2) split = min(dx1, int(w));
          else split = w - max(dx2, 0);
          if(w - split <= 0)
          {
              w = split;
              available = min(w, h);
              if(dy > 0) reserve(tx, ty, tw, th);
              else if(tx <= x && tx + tw >= x + w) available = 0;
              return;
          }
          if(split <= 0)
          {
              x += split;
              w -= split;
              available = min(w, h);
              if(dy > 0) reserve(tx, ty, tw, th);
              else if(tx <= x && tx + tw >= x + w) available = 0;
              return;
          }
          child1 = new PackNode(x, y, split, h);
          child2 = new PackNode(x + split, y, w - split, h);
      }
      else
      {
          if(dy1 > dy2) split = min(dy1, int(h));
          else split = h - max(dy2, 0);
          if(h - split <= 0)
          {
              h = split;
              available = min(w, h);
              if(dx > 0) reserve(tx, ty, tw, th);
              else if(ty <= y && ty + th >= y + h) available = 0;
              return;
          }
          if(split <= 0)
          {
              y += split;
              h -= split;
              available = min(w, h);
              if(dx > 0) reserve(tx, ty, tw, th);
              else if(ty <= y && ty + th >= y + h) available = 0;
              return;
          }
          child1 = new PackNode(x, y, w, split);
          child2 = new PackNode(x, y + split, w, h - split);
      }
      child1->reserve(tx, ty, tw, th);
      child2->reserve(tx, ty, tw, th);
      available = max(child1->available, child2->available);
  }
  
  static void clearsurfaces(cube *c)
  {
      loopi(8)
      {
          if(c[i].ext)
          {
              loopj(6)
              {
                  surfaceinfo &surf = c[i].ext->surfaces[j];
                  if(!surf.used()) continue;
                  surf.clear();
                  int numverts = surf.numverts&MAXFACEVERTS;
                  if(numverts)
                  {
                      if(!(c[i].merged&(1<<j))) { surf.numverts &= ~MAXFACEVERTS; continue; }
  
                      vertinfo *verts = c[i].ext->verts() + surf.verts;
                      loopk(numverts)
                      {
                          vertinfo &v = verts[k];
                          v.norm = 0;
                      }
                  }
              }
          }
          if(c[i].children) clearsurfaces(c[i].children);
      }
  }
  
  static uint lightprogress = 0;
  
  bool calclight_canceled = false;
  volatile bool check_calclight_progress = false;
  
  void check_calclight_canceled()
  {
      if(interceptkey(SDLK_ESCAPE))
      {
          calclight_canceled = true;
      }
      if(!calclight_canceled) check_calclight_progress = false;
  }
  
  void show_calclight_progress()
  {
      float bar1 = float(lightprogress) / float(allocnodes);
      defformatstring(text1, "%d%%", int(bar1 * 100));
  
      renderprogress(bar1, text1);
  }
  
  static void calcsurfaces(cube &c, const ivec &co, int size, int usefacemask, int preview = 0)
  {
      surfaceinfo surfaces[6];
      vertinfo litverts[6*2*MAXFACEVERTS];
      int numlitverts = 0;
      memset(surfaces, 0, sizeof(surfaces));
      loopi(6)
      {
          int usefaces = usefacemask&0xF;
          usefacemask >>= 4;
          if(!usefaces)
          {
              if(!c.ext) continue;
              surfaceinfo &surf = surfaces[i];
              surf = c.ext->surfaces[i];
              int numverts = surf.totalverts();
              if(numverts)
              {
                  memcpy(&litverts[numlitverts], c.ext->verts() + surf.verts, numverts*sizeof(vertinfo));
                  surf.verts = numlitverts;
                  numlitverts += numverts;
              }
              continue;
          }
  
          VSlot &vslot = lookupvslot(c.texture[i], false),
               *layer = vslot.layer && !(c.material&MAT_ALPHA) ? &lookupvslot(vslot.layer, false) : NULL;
          Shader *shader = vslot.slot->shader;
          int shadertype = shader->type;
          if(layer) shadertype |= layer->slot->shader->type;
  
          surfaceinfo &surf = surfaces[i];
          vertinfo *curlitverts = &litverts[numlitverts];
          int numverts = c.ext ? c.ext->surfaces[i].numverts&MAXFACEVERTS : 0;
          ivec mo(co);
          int msz = size, convex = 0;
          if(numverts)
          {
              vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts;
              loopj(numverts) curlitverts[j].set(verts[j].getxyz());
              if(c.merged&(1<<i))
              {
                  msz = 1<<calcmergedsize(i, mo, size, verts, numverts);
                  mo.mask(~(msz-1));
  
                  if(!(surf.numverts&MAXFACEVERTS))
                  {
                      surf.verts = numlitverts;
                      surf.numverts |= numverts;
                      numlitverts += numverts;
                  }
              }
              else if(!flataxisface(c, i)) convex = faceconvexity(verts, numverts, size);
          }
          else
          {
              ivec v[4];
              genfaceverts(c, i, v);
              if(!flataxisface(c, i)) convex = faceconvexity(v);
              int order = usefaces&4 || convex < 0 ? 1 : 0;
              ivec vo = ivec(co).mask(0xFFF).shl(3);
              curlitverts[numverts++].set(v[order].mul(size).add(vo));
              if(usefaces&1) curlitverts[numverts++].set(v[order+1].mul(size).add(vo));
              curlitverts[numverts++].set(v[order+2].mul(size).add(vo));
              if(usefaces&2) curlitverts[numverts++].set(v[(order+3)&3].mul(size).add(vo));
          }
  
          vec pos[MAXFACEVERTS], n[MAXFACEVERTS], po(ivec(co).mask(~0xFFF));
          loopj(numverts) pos[j] = vec(curlitverts[j].getxyz()).mul(1.0f/8).add(po);
  
          int smooth = vslot.slot->smooth;
          plane planes[2];
          int numplanes = 0;
          planes[numplanes++].toplane(pos[0], pos[1], pos[2]);
          if(numverts < 4 || !convex) loopk(numverts) findnormal(pos[k], smooth, planes[0], n[k]);
          else
          {
              planes[numplanes++].toplane(pos[0], pos[2], pos[3]);
              vec avg = vec(planes[0]).add(planes[1]).normalize();
              findnormal(pos[0], smooth, avg, n[0]);
              findnormal(pos[1], smooth, planes[0], n[1]);
              findnormal(pos[2], smooth, avg, n[2]);
              for(int k = 3; k < numverts; k++) findnormal(pos[k], smooth, planes[1], n[k]);
          }
  
          loopk(numverts) curlitverts[k].norm = encodenormal(n[k]);
          if(!(surf.numverts&MAXFACEVERTS))
          {
              surf.verts = numlitverts;
              surf.numverts |= numverts;
              numlitverts += numverts;
          }
  
          if(preview) { surf.numverts |= preview; continue; }
  
          int surflayer = LAYER_TOP;
          if(vslot.layer)
          {
              int x1 = curlitverts[numverts-1].x, y1 = curlitverts[numverts-1].y, x2 = x1, y2 = y1;
              loopj(numverts-1)
              {
                  const vertinfo &v = curlitverts[j];
                  x1 = min(x1, int(v.x));
                  y1 = min(y1, int(v.y));
                  x2 = max(x2, int(v.x));
                  y2 = max(y2, int(v.y));
              }
              x2 = max(x2, x1+1);
              y2 = max(y2, y1+1);
              x1 = (x1>>3) + (co.x&~0xFFF);
              y1 = (y1>>3) + (co.y&~0xFFF);
              x2 = ((x2+7)>>3) + (co.x&~0xFFF);
              y2 = ((y2+7)>>3) + (co.y&~0xFFF);
              surflayer = calcblendlayer(x1, y1, x2, y2);
          }
          surf.numverts |= surflayer;
      }
      if(preview) setsurfaces(c, surfaces, litverts, numlitverts);
      else loopk(6)
      {
          surfaceinfo &surf = surfaces[k];
          if(surf.used())
          {
              cubeext *ext = c.ext && c.ext->maxverts >= numlitverts ? c.ext : growcubeext(c.ext, numlitverts);
              memcpy(ext->surfaces, surfaces, sizeof(ext->surfaces));
              memcpy(ext->verts(), litverts, numlitverts*sizeof(vertinfo));
              if(c.ext != ext) setcubeext(c, ext);
              break;
          }
      }
  }
  
  static void calcsurfaces(cube *c, const ivec &co, int size)
  {
      CHECK_CALCLIGHT_PROGRESS(return, show_calclight_progress);
  
      lightprogress++;
  
      loopi(8)
      {
          ivec o(i, co, size);
          if(c[i].children)
              calcsurfaces(c[i].children, o, size >> 1);
          else if(!isempty(c[i]))
          {
              if(c[i].ext)
              {
                  loopj(6) c[i].ext->surfaces[j].clear();
              }
              int usefacemask = 0;
              loopj(6) if(c[i].texture[j] != DEFAULT_SKY && (!(c[i].merged&(1<<j)) || (c[i].ext && c[i].ext->surfaces[j].numverts&MAXFACEVERTS)))
              {
                  usefacemask |= visibletris(c[i], j, o, size)<<(4*j);
              }
              if(usefacemask) calcsurfaces(c[i], o, size, usefacemask);
          }
      }
  }
  
  static inline bool previewblends(cube &c, const ivec &o, int size)
  {
      if(isempty(c) || c.material&MAT_ALPHA) return false;
      int usefacemask = 0;
      loopj(6) if(c.texture[j] != DEFAULT_SKY && lookupvslot(c.texture[j], false).layer)
          usefacemask |= visibletris(c, j, o, size)<<(4*j);
      if(!usefacemask) return false;
      int layer = calcblendlayer(o.x, o.y, o.x + size, o.y + size);
      if(!(layer&LAYER_BOTTOM))
      {
          if(!c.ext) return false;
          bool blends = false;
          loopi(6) if(c.ext->surfaces[i].numverts&LAYER_BOTTOM)
          {
              c.ext->surfaces[i].brighten();
              blends = true;
          }
          return blends;
      }
      calcsurfaces(c, o, size, usefacemask, layer);
      return true;
  }
  
  static bool previewblends(cube *c, const ivec &co, int size, const ivec &bo, const ivec &bs)
  {
      bool changed = false;
      loopoctabox(co, size, bo, bs)
      {
          ivec o(i, co, size);
          cubeext *ext = c[i].ext;
          if(ext && ext->va && ext->va->hasmerges)
          {
              changed = true;
              destroyva(ext->va);
              ext->va = NULL;
              invalidatemerges(c[i], co, size, true);
          }
          if(c[i].children ? previewblends(c[i].children, o, size/2, bo, bs) : previewblends(c[i], o, size))
          {
              changed = true;
              ext = c[i].ext;
              if(ext && ext->va)
              {
                  destroyva(ext->va);
                  ext->va = NULL;
              }
          }
      }
      return changed;
  }
  
  void previewblends(const ivec &bo, const ivec &bs)
  {
      updateblendtextures(bo.x, bo.y, bo.x+bs.x, bo.y+bs.y);
      if(previewblends(worldroot, ivec(0, 0, 0), worldsize/2, bo, bs))
          commitchanges(true);
  }
  
  extern int filltjoints;
  
  static Uint32 calclighttimer(Uint32 interval, void *param)
  {
      check_calclight_progress = true;
      return interval;
  }
  
  void calclight()
  {
      renderbackground("computing lighting... (esc to abort)");
      remip();
      optimizeblendmap();
      clearsurfaces(worldroot);
      lightprogress = 0;
      calclight_canceled = false;
      check_calclight_progress = false;
      SDL_TimerID timer = SDL_AddTimer(250, calclighttimer, NULL);
      Uint32 start = SDL_GetTicks();
      calcnormals(filltjoints > 0);
      calcsurfaces(worldroot, ivec(0, 0, 0), worldsize >> 1);
      clearnormals();
      Uint32 end = SDL_GetTicks();
      if(timer) SDL_RemoveTimer(timer);
      renderbackground("lighting done...");
      allchanged();
      if(calclight_canceled)
          conoutf("calclight aborted");
      else
          conoutf("computed lighting (%.1f seconds)",
              (end - start) / 1000.0f);
  }
  
  void mpcalclight(bool local)
  {
      extern selinfo sel;
      if(local) game::edittrigger(sel, EDIT_CALCLIGHT);
      calclight();
  }
  
  ICOMMAND(calclight, "", (), mpcalclight(true));
  
  VAR(fullbright, 0, 0, 1);
  VAR(fullbrightlevel, 0, 160, 255);
  
  void clearlights()
  {
      clearshadowcache();
      cleardeferredlightshaders();
      resetsmoothgroups();
  }
  
  void initlights()
  {
      clearshadowcache();
      loaddeferredlightshaders();
  }
  
  