Files
assaultcube-/source/src/texture.cpp
T
ac-stef 8745d002f0 new commands to edit map config items
automapconfig
  Enables the "automatic embedded map config data" feature, which means
  with the next map save the map config file gets renamed and the map
  config data stored inside the map file.

mapsoundslotusage <map sound slot number>
  Returns a list of map entity indices which use a certain map sound slot.
  If the sound is unused, it returns an empty string.

deletemapsoundslot <map sound slot number> <"purge">
  Deletes an unused map sound slot. If "purge" is specified as second
  argument, it also deletes an used slot (including all map entities
  which use that sound). Also enables automapconfig.

editmapsoundslot <map sound slot number> <map sound path> <maxuses>
  Edits path/name and maxuses parameters of a mapsound slot. Only
  non-empty parameters actually change something. The command returns
  the resulting data of the map sound slot. If only the map sound slot
  number is specified, no changes are made, but the current data is
  returned. Therefore the command can be used to get and set parameters.
  If anything is changed, automapconfig is enabled.

mapmodelslotusage  <mapmodel slot number>
  Returns a list of map entity indices which use a certain mapmodel slot.
  If the mapmodel is unused, it returns an empty string.

deletemapmodelslot <mapmodel slot number> <"purge">
  Deletes an unused mapmodel slot. If "purge" is specified as second
  argument, it also deletes an used slot (including all map entities
  which use that mapmodel). Also enables automapconfig.

editmapmodelslot <mapmodel slot #> <radius> <height> <z-offset> 0 <path>
  Edits the parameters of a mapmodel slot. Only non-empty parameters
  actually change something. The command returns the resulting data of
  the mapmodel slot. If only the mapmodel slot number is specified, no
  changes are made, but the current data is returned. Therefore the
  command can be used to get and set parameters.
  examples:
    editmapmodelslot 33 "" "" -5
      sets the z-offset of slot #33 to -5 without affecting other params
    echo (editmapmodelslot 33)
      print the current parameters of mapmodel slot #33
      for example: 0 0 0 0 "makke/lightbulb"
  If anything is changed, automapconfig is enabled.

gettexturelist <includes> <excludes> <extensions>
  Returns a table of all texture files fitting a certain description.

  includes
    Optional list of path prefixes to include in the table.

  excludes
    Optional list of path prefixes to exclude from the table.

  extensions
    Optional list of filename extensions to include in the table.

  All files found under packages/textures are examined. By default, the
  command returns a list with two columns: path and filename. If "excludes"
  is exactly one path prefix to exclude, the table gets a third column
  with path names without that prefix. If "extensions" is exactly one
  extension which does not start with the character '.', the table gets
  an additional (third or fourth) column containing file names without
  that extension.

  examples:

    echo (gettexturelist)
      outputs two columns like "noctua/wall" "wall02.jpg"

    echo (gettexturelist "" "map_editor skymaps")
      same, but omits skymaps and special textures for the editor

    echo (gettexturelist makke)
      outputs only textures by makke and adds a third column
      like "makke/rattrap" "rb_box_03.jpg" "/rattrap"

    echo (gettexturelist "skymaps/" "" "_ft.jpg")
      outputs four columns like
        "skymaps/egypt" "egypt_ft.jpg" "egypt" "egypt"
        "skymaps/humus" "meadow_ft.jpg" "humus" "meadow"

textureslotusage  <texture slot number>
  Returns a list of mapmodels who use that texture as skin. If no mapmodel
  uses the texture, but world geometry does, a string of whitespace is
  returned. If the mapmodel is unused, an empty string is returned.

deletetextureslot <texture slot number> <"purge">
  Deletes an unused texture slot. If "purge" is specified as second
  argument, it also deletes an used slot. All mapmodels that use that
  texture as skin are changed to default skin. All world geometry, which
  uses the texture, is instead set to use slot #255. Slots below #5 always
  require "purge", no matter if they are in use (default slots).
  Also enables automapconfig.

edittextureslot <texture slot #> <scale> <path>
  Edits the parameters of a texture slot. Only non-empty parameters
  actually change something. The command returns the resulting data of
  the texture slot. If only the texture slot number is specified, no
  changes are made, but the current data is returned. Therefore the
  command can be used to get and set parameters.
  examples:
    edittextureslot 33 "" "arcitool/rohbaubims.jpg"
      sets texture name of slot #33
    echo (edittextureslot 33)
      print the current parameters of texture slot #33
      for example: 0 "zastrow/3wood_crate_10.jpg"
  If anything is changed, automapconfig is enabled.

deleteentity <entity index/number>
  Deletes a map entity. (Note: deleting an entity only marks it as unused.
  It will be completely removed after saving and loading the map.)

editentity <entity index/number> <x> <y> <z> <attr1> <attr2> <attr3> <attr4>
  Edits a map entity. Edits only parameters given a non-empty value.
  Returns the values of all attributes (type, x, y, z, attr1-attr4).

  examples:

    echo (editentity 33)
      prints all attributes of map entity number 33
      for example "light 174 172 12  20 255 200 200"

    editentity 33 "" 173
      changes the y-position of entity 33 to 173

rereadtexturelists
  Variable, which is set to "1" when a texture is downloaded. Used to
  trigger menu rebuilds.

rereadsoundlists
  Variable, which is set to "1" when a sound is downloaded. Used to
  trigger menu rebuilds.

DOCREF mapsoundslotusage deletemapsoundslot editmapsoundslot
DOCREF mapmodelslotusage deletemapmodelslot editmapmodelslot
DOCREF textureslotusage deletetextureslot edittextureslot
DOCREF gettexturelist deleteentity editentity
DOCREF automapconfig rereadtexturelists rereadsoundlists
2015-05-18 14:46:03 +02:00

1081 lines
33 KiB
C++

// texture.cpp: texture management
#include "cube.h"
#define FUNCNAME(name) name##1
#define DEFPIXEL uint OP(r, 0);
#define PIXELOP OP(r, 0);
#define BPP 1
#include "scale.h"
#define FUNCNAME(name) name##2
#define DEFPIXEL uint OP(r, 0), OP(g, 1);
#define PIXELOP OP(r, 0); OP(g, 1);
#define BPP 2
#include "scale.h"
#define FUNCNAME(name) name##3
#define DEFPIXEL uint OP(r, 0), OP(g, 1), OP(b, 2);
#define PIXELOP OP(r, 0); OP(g, 1); OP(b, 2);
#define BPP 3
#include "scale.h"
#define FUNCNAME(name) name##4
#define DEFPIXEL uint OP(r, 0), OP(g, 1), OP(b, 2), OP(a, 3);
#define PIXELOP OP(r, 0); OP(g, 1); OP(b, 2); OP(a, 3);
#define BPP 4
#include "scale.h"
void scaletexture(uchar *src, uint sw, uint sh, uint bpp, uchar *dst, uint dw, uint dh)
{
if(sw == dw*2 && sh == dh*2)
{
switch(bpp)
{
case 1: return halvetexture1(src, sw, sh, dst);
case 2: return halvetexture2(src, sw, sh, dst);
case 3: return halvetexture3(src, sw, sh, dst);
case 4: return halvetexture4(src, sw, sh, dst);
}
}
else if(sw < dw || sh < dh || sw&(sw-1) || sh&(sh-1))
{
switch(bpp)
{
case 1: return scaletexture1(src, sw, sh, dst, dw, dh);
case 2: return scaletexture2(src, sw, sh, dst, dw, dh);
case 3: return scaletexture3(src, sw, sh, dst, dw, dh);
case 4: return scaletexture4(src, sw, sh, dst, dw, dh);
}
}
else
{
switch(bpp)
{
case 1: return shifttexture1(src, sw, sh, dst, dw, dh);
case 2: return shifttexture2(src, sw, sh, dst, dw, dh);
case 3: return shifttexture3(src, sw, sh, dst, dw, dh);
case 4: return shifttexture4(src, sw, sh, dst, dw, dh);
}
}
}
Texture *notexture = NULL, *noworldtexture = NULL;
hashtable<char *, Texture> textures;
VAR(hwtexsize, 1, 0, 0);
VAR(hwmaxaniso, 1, 0, 0);
VARFP(maxtexsize, 0, 0, 1<<12, initwarning("texture quality", INIT_LOAD));
VARFP(texreduce, -1, 0, 3, initwarning("texture quality", INIT_LOAD));
VARFP(trilinear, 0, 1, 1, initwarning("texture filtering", INIT_LOAD));
VARFP(bilinear, 0, 1, 1, initwarning("texture filtering", INIT_LOAD));
VARFP(aniso, 0, 0, 16, initwarning("texture filtering", INIT_LOAD));
int formatsize(GLenum format)
{
switch(format)
{
case GL_LUMINANCE:
case GL_ALPHA: return 1;
case GL_LUMINANCE_ALPHA: return 2;
case GL_RGB: return 3;
case GL_RGBA: return 4;
default: return 4;
}
}
void resizetexture(int w, int h, bool mipmap, bool canreduce, GLenum target, int &tw, int &th)
{
int hwlimit = hwtexsize,
sizelimit = mipmap && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit;
if(canreduce && texreduce)
{
if(texreduce==-1)
{
w = 2;
h = 2;
}
else
{
w = max(w>>texreduce, 2); // 1);
h = max(h>>texreduce, 2); // 1);
}
}
w = min(w, sizelimit);
h = min(h, sizelimit);
if(mipmap || w&(w-1) || h&(h-1))
{
tw = th = 1;
while(tw < w) tw *= 2;
while(th < h) th *= 2;
if(w < tw - tw/4) tw /= 2;
if(h < th - th/4) th /= 2;
}
else
{
tw = w;
th = h;
}
}
void uploadtexture(GLenum target, GLenum internal, int tw, int th, GLenum format, GLenum type, void *pixels, int pw, int ph, bool mipmap)
{
int bpp = formatsize(format);
uchar *buf = NULL;
if(pw!=tw || ph!=th)
{
buf = new uchar[tw*th*bpp];
scaletexture((uchar *)pixels, pw, ph, bpp, buf, tw, th);
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for(int level = 0;; level++)
{
uchar *src = buf ? buf : (uchar *)pixels;
glTexImage2D(target, level, internal, tw, th, 0, format, type, src);
if(!mipmap || max(tw, th) <= 1) break;
int srcw = tw, srch = th;
if(tw > 1) tw /= 2;
if(th > 1) th /= 2;
if(!buf) buf = new uchar[tw*th*bpp];
scaletexture(src, srcw, srch, bpp, buf, tw, th);
}
if(buf) delete[] buf;
}
void createtexture(int tnum, int w, int h, void *pixels, int clamp, bool mipmap, bool canreduce, GLenum format)
{
glBindTexture(GL_TEXTURE_2D, tnum);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp&1 ? GL_CLAMP_TO_EDGE : GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp&2 ? GL_CLAMP_TO_EDGE : GL_REPEAT);
if(hasAF && min(aniso, hwmaxaniso) > 0 && mipmap) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(aniso, hwmaxaniso));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, bilinear ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
mipmap ?
(trilinear ?
(bilinear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR) :
(bilinear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST)) :
(bilinear ? GL_LINEAR : GL_NEAREST));
int tw = w, th = h;
if(pixels) resizetexture(w, h, mipmap, canreduce, GL_TEXTURE_2D, tw, th);
uploadtexture(GL_TEXTURE_2D, format, tw, th, format, GL_UNSIGNED_BYTE, pixels, w, h, mipmap);
}
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define RGBAMASKS 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
#define RGBMASKS 0xff0000, 0x00ff00, 0x0000ff, 0
#else
#define RGBAMASKS 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
#define RGBMASKS 0x0000ff, 0x00ff00, 0xff0000, 0
#endif
SDL_Surface *wrapsurface(void *data, int width, int height, int bpp)
{
switch(bpp)
{
case 3: return SDL_CreateRGBSurfaceFrom(data, width, height, 8*bpp, bpp*width, RGBMASKS);
case 4: return SDL_CreateRGBSurfaceFrom(data, width, height, 8*bpp, bpp*width, RGBAMASKS);
}
return NULL;
}
SDL_Surface *creatergbsurface(int width, int height)
{
return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 24, RGBMASKS);
}
SDL_Surface *creatergbasurface(int width, int height)
{
return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, RGBAMASKS);
}
SDL_Surface *forcergbsurface(SDL_Surface *os)
{
SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 24, RGBMASKS);
if(ns) SDL_BlitSurface(os, NULL, ns, NULL);
SDL_FreeSurface(os);
return ns;
}
SDL_Surface *forcergbasurface(SDL_Surface *os)
{
SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 32, RGBAMASKS);
if(ns)
{
SDL_SetAlpha(os, 0, 0);
SDL_BlitSurface(os, NULL, ns, NULL);
}
SDL_FreeSurface(os);
return ns;
}
bool checkgrayscale(SDL_Surface *s)
{
// gray scale images have 256 levels, no colorkey, and the palette is a ramp
if(s->format->palette)
{
if(s->format->palette->ncolors != 256 || s->format->colorkey) return false;
const SDL_Color *colors = s->format->palette->colors;
loopi(256) if(colors[i].r != i || colors[i].g != i || colors[i].b != i) return false;
}
return true;
}
int fixcl(SDL_Surface *s, bool check = true, Uint8 value = 0, Uint8 mlimit = 255)
{
Uint32 pixel = 0;
int bpp = s->format->BytesPerPixel;
int F = 0, N = 0, t = 0;
while ( value > mlimit ) {t++; value >>= 1;}
int tmp = s->w * bpp;
for (int i = 0; i < tmp; i+=bpp)
{
for (int j = 0; j < s->h; j++)
{
Uint8 *p = (Uint8 *)s->pixels + j * s->w * bpp + i;
switch (bpp)
{
case 1:
{
if (check) pixel = *p;
else *p >>= t;
break;
}
case 2:
{
if (check) pixel = *(Uint16 *)p;
else { p[0] >>= t; p[1] >>= t; }
break;
}
case 3:
{
if (check)
{
if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
pixel = p[0] << 16 | p[1] << 8 | p[2];
else
pixel = p[0] | p[1] << 8 | p[2] << 16;
}
else { loopk(3) p[k] >>= t; }
break;
}
case 4:
{
if (check) pixel = *(Uint32 *)p;
else {
Uint8 r = 0, g = 0, b = 0, a = 0;
SDL_GetRGBA(pixel, s->format, &r, &g, &b, &a);
r >>= t; g >>= t; b >>= t;
Uint32 *q = (Uint32 *)p;
*q = SDL_MapRGBA(s->format, r,g,b,a);
}
break;
}
default: break;
}
if (check) {
Uint8 r = 0, g = 0, b = 0, a = 0;
SDL_GetRGBA(pixel, s->format, &r, &g, &b, &a);
F += r > g ? ( r > b ? r : b ) : ( g > b ? g : b ); N++;
}
}
}
if (!N) return 0;
return F/N;
}
SDL_Surface *fixsurfaceformat(SDL_Surface *s)
{
if(!s) return NULL;
if(!s->pixels || min(s->w, s->h) <= 0 || s->format->BytesPerPixel <= 0)
{
SDL_FreeSurface(s);
return NULL;
}
static const uint rgbmasks[] = { RGBMASKS }, rgbamasks[] = { RGBAMASKS };
switch(s->format->BytesPerPixel)
{
case 1:
if(!checkgrayscale(s)) return s->format->colorkey ? forcergbasurface(s) : forcergbsurface(s);
break;
case 3:
if(s->format->Rmask != rgbmasks[0] || s->format->Gmask != rgbmasks[1] || s->format->Bmask != rgbmasks[2])
return forcergbsurface(s);
break;
case 4:
if(s->format->Rmask != rgbamasks[0] || s->format->Gmask != rgbamasks[1] || s->format->Bmask != rgbamasks[2] || s->format->Amask != rgbamasks[3])
return s->format->Amask ? forcergbasurface(s) : forcergbsurface(s);
break;
}
return s;
}
GLenum texformat(int bpp)
{
switch(bpp)
{
case 8: return GL_LUMINANCE;
case 16: return GL_LUMINANCE_ALPHA;
case 24: return GL_RGB;
case 32: return GL_RGBA;
default: return 0;
}
}
SDL_Surface *texdecal(SDL_Surface *s)
{
SDL_Surface *m = SDL_CreateRGBSurface(SDL_SWSURFACE, s->w, s->h, 16, 0, 0, 0, 0);
if(!m) fatal("create surface");
uchar *dst = (uchar *)m->pixels, *src = (uchar *)s->pixels;
loopi(s->h*s->w)
{
*dst++ = *src;
*dst++ = 255 - *src;
src += s->format->BytesPerPixel;
}
SDL_FreeSurface(s);
return m;
}
void scalesurface(SDL_Surface *s, float scale)
{
uint dw = s->w*scale, dh = s->h*scale;
uchar *buf = new uchar[dw*dh*s->format->BytesPerPixel];
scaletexture((uchar *)s->pixels, s->w, s->h, s->format->BytesPerPixel, buf, dw, dh);
delete[] (uchar *)s->pixels;
s->w = dw;
s->h = dh;
s->pixels = buf;
}
bool silent_texture_load = false;
VARFP(hirestextures, 0, 1, 1, initwarning("texture resolution", INIT_LOAD));
bool uniformtexres = !hirestextures;
GLuint loadsurface(const char *texname, int &xs, int &ys, int &bpp, int clamp = 0, bool mipmap = true, bool canreduce = false, float scale = 1.0f, bool trydl = false)
{
const char *file = texname;
if(texname[0]=='<')
{
file = strchr(texname, '>');
if(!file) { if(!silent_texture_load) conoutf("could not load texture %s", texname); return 0; }
file++;
}
SDL_Surface *s = NULL;
stream *z = openzipfile(file, "rb");
if(z)
{
SDL_RWops *rw = z->rwops();
if(rw)
{
s = IMG_Load_RW(rw, 0);
SDL_FreeRW(rw);
}
delete z;
}
if(!s) s = IMG_Load(findfile(file, "rb"));
if(!s)
{
if(trydl)
requirepackage(PCK_TEXTURE, file);
else if(!silent_texture_load) conoutf("couldn't load texture %s", texname);
return 0;
}
s = fixsurfaceformat(s);
Uint8 x = 0;
if(strstr(texname,"playermodel") && (x = fixcl(s)) > 35) { fixcl(s,false,x,35); }
else if(strstr(texname,"skin") && strstr(texname,"weapon") && (x = fixcl(s)) > 40 ) { fixcl(s,false,x,40); }
GLenum format = texformat(s->format->BitsPerPixel);
if(!format)
{
SDL_FreeSurface(s);
conoutf("texture must be 8, 16, 24, or 32 bpp: %s", texname);
return 0;
}
if(max(s->w, s->h) > (1<<12))
{
SDL_FreeSurface(s);
conoutf("texture size exceeded %dx%d pixels: %s", 1<<12, 1<<12, texname);
return 0;
}
if(texname[0]=='<')
{
const char *cmd = &texname[1], *arg1 = strchr(cmd, ':');//, *arg2 = arg1 ? strchr(arg1, ',') : NULL;
if(!arg1) arg1 = strchr(cmd, '>');
if(!strncmp(cmd, "decal", arg1-cmd)) { s = texdecal(s); format = texformat(s->format->BitsPerPixel); }
}
if(uniformtexres && scale > 1.0f) scalesurface(s, 1.0f/scale);
GLuint tnum;
glGenTextures(1, &tnum);
createtexture(tnum, s->w, s->h, s->pixels, clamp, mipmap, canreduce, format);
xs = s->w;
ys = s->h;
bpp = s->format->BitsPerPixel;
SDL_FreeSurface(s);
return tnum;
}
// management of texture slots
// each texture slot can have multiple texture frames, of which currently only the first is used
// additional frames can be used for various shaders
Texture *textureload(const char *name, int clamp, bool mipmap, bool canreduce, float scale, bool trydl)
{
string pname;
copystring(pname, name);
path(pname);
Texture *t = textures.access(pname);
if(t) return t;
int xs, ys, bpp;
GLuint id = loadsurface(pname, xs, ys, bpp, clamp, mipmap, canreduce, scale, trydl);
if(!id) return notexture;
char *key = newstring(pname);
t = &textures[key];
t->name = key;
t->xs = xs;
t->ys = ys;
t->bpp = bpp;
t->clamp = clamp;
t->mipmap = mipmap;
t->canreduce = canreduce;
t->id = id;
t->scale = scale;
return t;
}
Texture *createtexturefromsurface(const char *name, SDL_Surface *s)
{
string pname;
copystring(pname, name);
path(pname);
Texture *t = textures.access(pname);
if(!t)
{
char *key = newstring(pname);
t = &textures[key];
t->name = key;
}
GLuint tnum;
glGenTextures(1, &tnum);
GLenum format = texformat(s->format->BitsPerPixel);
createtexture(tnum, s->w, s->h, s->pixels, 0, true, false, format);
t->xs = s->w;
t->ys = s->h;
t->bpp = s->format->BitsPerPixel;
t->clamp = 0;
t->mipmap = true;
t->canreduce = false;
t->id = tnum;
return t;
}
struct Slot
{
string name;
float scale, orgscale;
Texture *tex;
bool loaded;
};
vector<Slot> slots;
COMMANDF(texturereset, "", ()
{
if(execcontext==IEXC_MAPCFG)
{
slots.setsize(0);
flagmapconfigchange();
}
});
void _texture(Slot &s, float *scale, char *name)
{
if(name)
{
filtertext(s.name, parentdir(name), FTXT__MEDIAFILEPATH); // filter parts separately, because filename may (legally) contain "<decal>"
if(*s.name) concatstring(s.name, "/");
name = (char *)behindpath(name);
if(*name == '<')
{
char *endcmd = strchr(name, '>');
if(endcmd)
{
*endcmd = '\0';
concatstring(s.name, name);
concatstring(s.name, ">");
name = endcmd + 1;
}
}
filtertext(name, name, FTXT__MEDIAFILENAME);
concatstring(s.name, name);
}
s.tex = NULL;
s.loaded = false;
if(scale)
{
s.orgscale = *scale;
s.scale = (*scale > 0 && *scale <= 2.0f) ? *scale : 1.0f;
}
}
COMMANDF(texture, "fs", (float *scale, char *name)
{
intret(slots.length());
Slot &s = slots.add();
_texture(s, scale, name);
flagmapconfigchange();
});
const char *gettextureslot(int i)
{
static string res;
if(slots.inrange(i))
{
Slot &s = slots[i];
defformatstring(d)("%d", int(s.orgscale));
formatstring(res)("texture %s \"%s\"", s.orgscale == int(s.orgscale) ? d : floatstr(s.orgscale), s.name);
return res;
}
else return NULL;
}
Texture *lookuptexture(int tex, Texture *failtex, bool trydl)
{
Texture *t = failtex;
if(slots.inrange(tex))
{
Slot &s = slots[tex];
if(!s.loaded)
{
defformatstring(pname)("packages/textures/%s", path(s.name, true));
s.tex = textureload(pname, 0, true, true, s.scale, trydl);
if(!trydl)
{
if(s.tex==notexture) s.tex = failtex;
s.loaded = true;
}
}
if(s.tex) t = s.tex;
}
return t;
}
void cleanuptextures()
{
enumerate(textures, Texture, t,
if(t.id) { glDeleteTextures(1, &t.id); t.id = 0; }
);
}
bool reloadtexture(Texture &t)
{
if(t.id) return true;
int xs = 1, ys = 1, bpp = 0;
t.id = loadsurface(t.name, xs, ys, bpp, t.clamp, t.mipmap, t.canreduce, t.scale);
t.xs = xs;
t.ys = ys;
t.bpp = bpp;
return t.id!=0;
}
bool reloadtexture(const char *name)
{
Texture *t = textures.access(path(name, true));
if(t) return reloadtexture(*t);
return false;
}
void reloadtextures()
{
enumerate(textures, Texture, t, reloadtexture(t));
}
Texture *sky[6] = {NULL, NULL, NULL, NULL, NULL, NULL};
SVARF(loadsky, "", { loadskymap(false); });
void loadskymap(bool reload)
{
const char *side[] = { "lf", "rt", "ft", "bk", "dn", "up" };
const char *legacyprefix = "textures/skymaps/";
if(!reload)
{
int n = strlen(legacyprefix);
filtertext(loadsky, loadsky, FTXT__MEDIAFILEPATH);
if(!strncmp(loadsky, legacyprefix, n))
{ // remove the prefix
char *t = loadsky;
loadsky = newstring(loadsky + n);
delstring(t);
}
flagmapconfigchange();
}
loopi(6)
{
defformatstring(name)("packages/%s%s_%s.jpg", legacyprefix, loadsky, side[i]);
sky[i] = textureload(name, 3);
if(sky[i] == notexture && !reload && autodownload)
{
defformatstring(dl)("packages/%s%s", legacyprefix, loadsky);
requirepackage(PCK_SKYBOX, dl);
break;
}
}
}
void loadnotexture(char *c)
{
noworldtexture = notexture; // reset to default
*mapconfigdata.notexturename = '\0';
if(c[0])
{
filtertext(mapconfigdata.notexturename, c, FTXT__MEDIAFILEPATH);
defformatstring(p)("packages/textures/%s", mapconfigdata.notexturename);
noworldtexture = textureload(p);
if(noworldtexture==notexture) conoutf("could not load alternative texture '%s'.", p);
}
flagmapconfigchange();
}
COMMAND(loadnotexture, "s");
COMMANDF(getnotexture, "", () { result(mapconfigdata.notexturename); });
void draw_envbox_face(float s0, float t0, float x0, float y0, float z0,
float s1, float t1, float x1, float y1, float z1,
float s2, float t2, float x2, float y2, float z2,
float s3, float t3, float x3, float y3, float z3,
Texture *tex)
{
glBindTexture(GL_TEXTURE_2D, tex->id);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(s3, t3); glVertex3f(x3, y3, z3);
glTexCoord2f(s2, t2); glVertex3f(x2, y2, z2);
glTexCoord2f(s0, t0); glVertex3f(x0, y0, z0);
glTexCoord2f(s1, t1); glVertex3f(x1, y1, z1);
glEnd();
xtraverts += 4;
}
VAR(skyclip, 0, 1, 1);
float skyfloor = 1e16f;
void draw_envbox(int w)
{
extern float skyfloor;
float zclip = skyclip && skyfloor >= camera1->o.z ? 0.5f + float(skyfloor-camera1->o.z)/w : 0.0f,
vclip = 1-zclip,
z = 2*w*(vclip-0.5f);
if(vclip < 0) return;
glDepthMask(GL_FALSE);
draw_envbox_face(0.0f, 0.0f, -w, -w, -w,
1.0f, 0.0f, -w, w, -w,
1.0f, vclip, -w, w, z,
0.0f, vclip, -w, -w, z, sky[0] ? sky[0] : notexture);
draw_envbox_face(1.0f, vclip, +w, -w, z,
0.0f, vclip, +w, w, z,
0.0f, 0.0f, +w, w, -w,
1.0f, 0.0f, +w, -w, -w, sky[1] ? sky[1] : notexture);
draw_envbox_face(1.0f, vclip, -w, -w, z,
0.0f, vclip, w, -w, z,
0.0f, 0.0f, w, -w, -w,
1.0f, 0.0f, -w, -w, -w, sky[2] ? sky[2] : notexture);
draw_envbox_face(1.0f, vclip, +w, w, z,
0.0f, vclip, -w, w, z,
0.0f, 0.0f, -w, w, -w,
1.0f, 0.0f, +w, w, -w, sky[3] ? sky[3] : notexture);
if(!zclip)
draw_envbox_face(0.0f, 1.0f, -w, w, w,
0.0f, 0.0f, +w, w, w,
1.0f, 0.0f, +w, -w, w,
1.0f, 1.0f, -w, -w, w, sky[4] ? sky[4] : notexture);
draw_envbox_face(0.0f, 1.0f, +w, w, -w,
0.0f, 0.0f, -w, w, -w,
1.0f, 0.0f, -w, -w, -w,
1.0f, 1.0f, +w, -w, -w, sky[5] ? sky[5] : notexture);
glDepthMask(GL_TRUE);
skyfloor = 1e16f;
}
struct tmufunc
{
GLenum combine, sources[3], ops[3];
int scale;
};
struct tmu
{
GLenum mode;
GLfloat color[4];
tmufunc rgb, alpha;
};
#define INVALIDTMU \
{ \
0, \
{ -1, -1, -1, -1 }, \
{ 0, { 0, 0, 0 }, { 0, 0, 0 }, 0 }, \
{ 0, { 0, 0, 0 }, { 0, 0, 0 }, 0 } \
}
#define INITTMU \
{ \
GL_MODULATE, \
{ 0, 0, 0, 0 }, \
{ GL_MODULATE, { GL_TEXTURE, GL_PREVIOUS_ARB, GL_CONSTANT_ARB }, { GL_SRC_COLOR, GL_SRC_COLOR, GL_SRC_ALPHA }, 1 }, \
{ GL_MODULATE, { GL_TEXTURE, GL_PREVIOUS_ARB, GL_CONSTANT_ARB }, { GL_SRC_ALPHA, GL_SRC_ALPHA, GL_SRC_ALPHA }, 1 } \
}
#define MAXTMUS 4
tmu tmus[MAXTMUS] =
{
INVALIDTMU,
INVALIDTMU,
INVALIDTMU,
INVALIDTMU
};
VAR(maxtmus, 1, 0, 0);
void parsetmufunc(tmufunc &f, const char *s)
{
int arg = -1;
while(*s) switch(tolower(*s++))
{
case 't': f.sources[++arg] = GL_TEXTURE; f.ops[arg] = GL_SRC_COLOR; break;
case 'p': f.sources[++arg] = GL_PREVIOUS_ARB; f.ops[arg] = GL_SRC_COLOR; break;
case 'k': f.sources[++arg] = GL_CONSTANT_ARB; f.ops[arg] = GL_SRC_COLOR; break;
case 'c': f.sources[++arg] = GL_PRIMARY_COLOR_ARB; f.ops[arg] = GL_SRC_COLOR; break;
case '~': f.ops[arg] = GL_ONE_MINUS_SRC_COLOR; break;
case 'a': f.ops[arg] = f.ops[arg]==GL_ONE_MINUS_SRC_COLOR ? GL_ONE_MINUS_SRC_ALPHA : GL_SRC_ALPHA; break;
case '=': f.combine = GL_REPLACE; break;
case '*': f.combine = GL_MODULATE; break;
case '+': f.combine = GL_ADD; break;
case '-': f.combine = GL_SUBTRACT_ARB; break;
case ',':
case '@': f.combine = GL_INTERPOLATE_ARB; break;
case '.': f.combine = GL_DOT3_RGB_ARB; break;
case 'x': while(!isdigit(*s)) s++; f.scale = *s++-'0'; break;
}
}
void committmufunc(bool rgb, tmufunc &dst, tmufunc &src)
{
if(dst.combine!=src.combine) glTexEnvi(GL_TEXTURE_ENV, rgb ? GL_COMBINE_RGB_ARB : GL_COMBINE_ALPHA_ARB, src.combine);
loopi(3)
{
if(dst.sources[i]!=src.sources[i]) glTexEnvi(GL_TEXTURE_ENV, (rgb ? GL_SOURCE0_RGB_ARB : GL_SOURCE0_ALPHA_ARB)+i, src.sources[i]);
if(dst.ops[i]!=src.ops[i]) glTexEnvi(GL_TEXTURE_ENV, (rgb ? GL_OPERAND0_RGB_ARB : GL_OPERAND0_ALPHA_ARB)+i, src.ops[i]);
}
if(dst.scale!=src.scale) glTexEnvi(GL_TEXTURE_ENV, rgb ? GL_RGB_SCALE_ARB : GL_ALPHA_SCALE, src.scale);
}
void committmu(int n, tmu &f)
{
if(n>=maxtmus) return;
if(tmus[n].mode!=f.mode) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, f.mode);
if(memcmp(tmus[n].color, f.color, sizeof(f.color))) glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, f.color);
committmufunc(true, tmus[n].rgb, f.rgb);
committmufunc(false, tmus[n].alpha, f.alpha);
tmus[n] = f;
}
void resettmu(int n)
{
tmu f = tmus[n];
f.mode = GL_MODULATE;
f.rgb.scale = 1;
f.alpha.scale = 1;
committmu(n, f);
}
void scaletmu(int n, int rgbscale, int alphascale)
{
tmu f = tmus[n];
if(rgbscale) f.rgb.scale = rgbscale;
if(alphascale) f.alpha.scale = alphascale;
committmu(n, f);
}
void colortmu(int n, float r, float g, float b, float a)
{
tmu f = tmus[n];
f.color[0] = r;
f.color[1] = g;
f.color[2] = b;
f.color[3] = a;
committmu(n, f);
}
void setuptmu(int n, const char *rgbfunc, const char *alphafunc)
{
static tmu init = INITTMU;
tmu f = tmus[n];
f.mode = GL_COMBINE_ARB;
if(rgbfunc) parsetmufunc(f.rgb, rgbfunc);
else f.rgb = init.rgb;
if(alphafunc) parsetmufunc(f.alpha, alphafunc);
else f.alpha = init.alpha;
committmu(n, f);
}
void inittmus()
{
GLint val;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &val);
hwtexsize = val;
if(hasTE && !hasMT) maxtmus = 1;
else if(hasTE && hasMT)
{
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &val);
maxtmus = max(1, min(MAXTMUS, int(val)));
loopi(maxtmus)
{
glActiveTexture_(GL_TEXTURE0_ARB+i);
resettmu(i);
}
glActiveTexture_(GL_TEXTURE0_ARB);
}
}
void cleanuptmus()
{
tmu invalidtmu = INVALIDTMU;
loopi(MAXTMUS) tmus[i] = invalidtmu;
}
// only works on 32 bit surfaces with alpha in 4th byte!
void blitsurface(SDL_Surface *dst, SDL_Surface *src, int x, int y)
{
uchar *dstp = (uchar *)dst->pixels + y*dst->pitch + x*4,
*srcp = (uchar *)src->pixels;
int dstpitch = dst->pitch - 4*src->w,
srcpitch = src->pitch - 4*src->w;
loop(dy, src->h)
{
loop(dx, src->w)
{
uint k1 = (255U - srcp[3]) * dstp[3], k2 = srcp[3] * 255U, kmax = max(dstp[3], srcp[3]), kscale = max(kmax * 255U, 1U);
dstp[0] = (dstp[0]*k1 + srcp[0]*k2) / kscale;
dstp[1] = (dstp[1]*k1 + srcp[1]*k2) / kscale;
dstp[2] = (dstp[2]*k1 + srcp[2]*k2) / kscale;
dstp[3] = kmax;
dstp += 4;
srcp += 4;
}
dstp += dstpitch;
srcp += srcpitch;
}
}
Texture *e_wall = NULL, *e_floor = NULL, *e_ceil = NULL;
void guidetoggle()
{
if(player1->state == CS_EDITING)
{
Slot *sw = &slots[DEFAULT_WALL];
Slot *sf = &slots[DEFAULT_FLOOR];
Slot *sc = &slots[DEFAULT_CEIL];
//if textures match original texture
if(e_wall == NULL || e_floor == NULL || e_ceil == NULL)
{
//replace defaults with grid texures
e_wall = sw->tex;
e_floor = sf->tex;
e_ceil = sc->tex;
sw->tex = textureload("packages/textures/map_editor/wall.png");
sf->tex = textureload("packages/textures/map_editor/floor.png");
sc->tex = textureload("packages/textures/map_editor/ceil.png");
conoutf("Guide: \f0on");
}
else
{
// restore textures
if(e_wall) sw->tex = e_wall;
if(e_floor) sf->tex = e_floor;
if(e_ceil) sc->tex = e_ceil;
e_wall = NULL;
e_floor = NULL;
e_ceil = NULL;
conoutf("Guide: \fBoff");
}
}
else
{
conoutf("\fBGuide view is only avaiable when editing.");
}
}
COMMAND(guidetoggle, "");
void gettexturelist(char *_filter, char *_exclude, char *_exts) // create a list of texture paths and filenames
{
vector<char *> filter, exclude, exts, files;
vector<int> filter_n, exclude_n, exts_n;
explodelist(_filter, filter); // path has to start with one of these
explodelist(_exclude, exclude); // path may not start with one of these
explodelist(_exts, exts); // file needs to have one of those extensions (or ".jpg" by default)
if(!exts.length()) exts.add(newstring(".jpg"));
loopv(filter) filter_n.add((int) strlen(filter[i]));
loopv(exclude) exclude_n.add((int) strlen(exclude[i]));
loopv(exts) exts_n.add((int) strlen(exts[i]));
listfilesrecursive("packages/textures", files);
files.sort(stringsort);
loopvrev(files) if(files.inrange(i + 1) && !strcmp(files[i], files[i + 1])) delstring(files.remove(i + 1)); // remove doubles
int pn = strlen("packages/textures/");
loopvrev(files)
{
bool filtered = filter.length() == 0, excluded = false, extmatch = false;
if(!strncmp(files[i], "packages/textures/", pn))
{
const char *s = files[i] + pn;
loopvj(filter) if(!strncmp(s, filter[j], filter_n[j])) filtered = true;
loopvj(exclude) if(!strncmp(s, exclude[j], exclude_n[j])) excluded = true;
int n = strlen(s);
loopvj(exts) if(n > exts_n[j] && !strcmp(s + n - exts_n[j], exts[j])) extmatch = true;
}
if(!extmatch || !filtered || excluded) delstring(files.remove(i));
}
vector<char> res;
bool cutprefix = filter.length() == 1;
bool cutext = exts.length() == 1 && exts[0][0] != '.';
loopv(files)
{
char *f = files[i] += pn;
const char *p = parentdir(f), *b = behindpath(f);
cvecprintf(res, "\"%s\" \"%s\"", p, b); // always add columns for path (without "packages/textures/") and filename
if(cutprefix) cvecprintf(res, " \"%s\"", int(strlen(p)) > filter_n[0] ? p + filter_n[0] : ""); // if there's exactly one filter string, add column with that string omitted
if(cutext)
{
int n = strlen(b);
if(n > exts_n[0]) ((char *)b)[n - exts_n[0]] = '\0';
cvecprintf(res, " \"%s\"", b); // if there's only one allowed extension and it does not start with '.', add column of filenames without extension
}
res.add('\n');
}
if(res.length()) res.last() = '\0';
else res.add('\0');
filter.deletearrays();
exclude.deletearrays();
exts.deletearrays();
result(res.getbuf());
}
COMMAND(gettexturelist, "sss");
bool testworldtexusage(int n)
{
sqr *s = world;
loopirev(cubicsize)
{
if(s->wtex == n || s->ctex == n || s->ftex == n || s->utex == n) return true;
s++;
}
return false;
}
void textureslotusage(int *n) // returns all mapmodel entity indices that use a certain texture to skin the model
// if the texture is used in map geometry, at least a space is returned
// if the texture is unused, an empty string is returned
{
string res = "";
loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr4 == *n) concatformatstring(res, "%s%d", i ? " " : "", i);
if(!*res)
{ // not used to skin a mapmodel: check world usage...
if(testworldtexusage(*n)) copystring(res, " ");
}
result(res);
}
COMMAND(textureslotusage, "i");
void deletetextureslot(int *n, char *opt) // delete texture slot - only if unused or "purge" is specified
{
if(noteditmode("deletetextureslot") || multiplayer(true) || !slots.inrange(*n)) return;
bool purgeall = !strcmp(opt, "purge");
bool mmused = false;
loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr4 == *n) mmused = true;
if(!purgeall && *n <= DEFAULT_CEIL) { conoutf("texture slots below #%d should usually not be deleted", DEFAULT_CEIL + 1); return; }
if(!purgeall && (mmused || testworldtexusage(*n))) { conoutf("texture slot #%d is in use: can't delete", *n); return; }
int deld = 0;
sqr *s = world;
loopirev(cubicsize)
{ // check, if cubes use the texture
if(s->wtex == *n) { s->wtex = 255; deld++; }
else if(s->wtex > *n) s->wtex--;
if(s->ctex == *n) { s->ctex = 255; deld++; }
else if(s->ctex > *n) s->ctex--;
if(s->ftex == *n) { s->ftex = 255; deld++; }
else if(s->ftex > *n) s->ftex--;
if(s->utex == *n) { s->utex = 255; deld++; }
else if(s->utex > *n) s->utex--;
s++;
}
block bb = { mapdims.x1 - 1, mapdims.y1 - 1, mapdims.xspan + 2, mapdims.yspan + 2 };
remip(bb);
loopv(ents) if(ents[i].type == MAPMODEL)
{ // check, if mapmodels use the texture
entity &e = ents[i];
if(e.attr4 == *n)
{ // use default model texture instead
e.attr4 = 0;
deld++;
}
else if(e.attr4 > *n) e.attr4--; // adjust models in higher slots
}
// FIXME: texlists...
slots.remove(*n);
defformatstring(m)(" (%d uses removed)", deld);
conoutf("texture slot #%d deleted%s", *n, deld ? m : "");
unsavededits++;
hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg
}
COMMAND(deletetextureslot, "is");
void edittextureslot(int *n, char *scale, char *name) // edit slot parameters != ""
{
string res = "";
if(!noteditmode("edittextureslot") && !multiplayer(true) && slots.inrange(*n))
{
Slot &s = slots[*n];
if(*scale || *name)
{ // change attributes
float newscale = atof(scale);
_texture(s, *scale ? &newscale : NULL, *name ? name : NULL);
unsavededits++;
hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg
}
defformatstring(d)("%d", int(s.orgscale));
formatstring(res)("%s \"%s\"", s.orgscale == int(s.orgscale) ? d : floatstr(s.orgscale), s.name);
}
result(res);
}
COMMAND(edittextureslot, "iss");