From: xolatile Date: Mon, 4 Aug 2025 20:53:42 +0000 (+0200) Subject: Bunch of small changes... X-Git-Url: https://git.xolatile.top/?a=commitdiff_plain;h=d309df4ce4d8ad0ed995a8e1c4267412a7782021;p=xolatile-badassbug.git Bunch of small changes... --- diff --git a/src/engine/3dgui.cpp b/src/engine/3dgui.cpp index cc9d61b..41a5d47 100644 --- a/src/engine/3dgui.cpp +++ b/src/engine/3dgui.cpp @@ -38,1118 +38,1118 @@ static int lastpreview = 0; static inline bool throttlepreview(bool loaded) { - if(loaded) return true; - if(totalmillis - lastpreview < guipreviewtime) return false; - lastpreview = totalmillis; - return true; + if(loaded) return true; + if(totalmillis - lastpreview < guipreviewtime) return false; + lastpreview = totalmillis; + return true; } struct gui : g3d_gui { - struct list - { - int parent, w, h, springs, curspring, column; - }; - - int firstlist, nextlist; - int columns[MAXCOLUMNS]; - - static vector lists; - static float hitx, hity; - static int curdepth, curlist, xsize, ysize, curx, cury; - static bool shouldmergehits, shouldautotab; - - static void reset() - { - lists.setsize(0); - } - - static int ty, tx, tpos, *tcurrent, tcolor; //tracking tab size and position since uses different layout method... - - bool allowautotab(bool on) - { - bool oldval = shouldautotab; - shouldautotab = on; - return oldval; - } - - void autotab() - { - if(tcurrent) - { - if(layoutpass && !tpos) tcurrent = NULL; //disable tabs because you didn't start with one - if(shouldautotab && !curdepth && (layoutpass ? 0 : cury) + ysize > guiautotab*FONTH) tab(NULL, tcolor); - } - } - - bool shouldtab() - { - if(tcurrent && shouldautotab) - { - if(layoutpass) - { - int space = guiautotab*FONTH - ysize; - if(space < 0) return true; - int l = lists[curlist].parent; - while(l >= 0) - { - space -= lists[l].h; - if(space < 0) return true; - l = lists[l].parent; - } - } - else - { - int space = guiautotab*FONTH - cury; - if(ysize > space) return true; - int l = lists[curlist].parent; - while(l >= 0) - { - if(lists[l].h > space) return true; - l = lists[l].parent; - } - } - } - return false; - } - - bool visible() { return (!tcurrent || tpos==*tcurrent) && !layoutpass; } - - //tab is always at top of page - void tab(const char *name, int color) - { - if(curdepth != 0) return; - if(color) tcolor = color; - tpos++; - if(!name) name = intstr(tpos); - int w = max(text_width(name) - 2*INSERT, 0); - if(layoutpass) - { - ty = max(ty, ysize); - ysize = 0; - } - else - { - cury = -ysize; - int h = FONTH-2*INSERT, - x1 = curx + tx, - x2 = x1 + w + ((skinx[3]-skinx[2]) + (skinx[5]-skinx[4]))*SKIN_SCALE, - y1 = cury - ((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE-h, - y2 = cury; - bool hit = tcurrent && windowhit==this && hitx>=x1 && hity>=y1 && hitx=0) - { - lists[curlist].w = xsize; - lists[curlist].h = ysize; - } - list &l = lists.add(); - l.parent = curlist; - l.springs = 0; - l.column = -1; - curlist = lists.length()-1; - xsize = ysize = 0; - } - else - { - curlist = nextlist++; - if(curlist >= lists.length()) // should never get here unless script code doesn't use same amount of lists in layout and render passes - { - list &l = lists.add(); - l.parent = curlist; - l.springs = 0; - l.column = -1; - l.w = l.h = 0; - } - list &l = lists[curlist]; - l.curspring = 0; - if(l.springs > 0) - { - if(ishorizontal()) xsize = l.w; else ysize = l.h; - } - else - { - xsize = l.w; - ysize = l.h; - } - } - curdepth++; - } - - void poplist() - { - if(!lists.inrange(curlist)) return; - list &l = lists[curlist]; - if(layoutpass) - { - l.w = xsize; - l.h = ysize; - if(l.column >= 0) columns[l.column] = max(columns[l.column], ishorizontal() ? ysize : xsize); - } - curlist = l.parent; - curdepth--; - if(lists.inrange(curlist)) - { - int w = xsize, h = ysize; - if(ishorizontal()) cury -= h; else curx -= w; - list &p = lists[curlist]; - xsize = p.w; - ysize = p.h; - if(!layoutpass && p.springs > 0) - { - list &s = lists[p.parent]; - if(ishorizontal()) xsize = s.w; else ysize = s.h; - } - layout(w, h); - } - } - - int text (const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, false, false); } - int button(const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, true, false); } - int title (const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, false, true); } - - void separator() { autotab(); line_(FONTH/3); } - void progress(float percent) { autotab(); line_((FONTH*4)/5, percent); } - - //use to set min size (useful when you have progress bars) - void strut(float size) { layout(isvertical() ? int(size*FONTW) : 0, isvertical() ? 0 : int(size*FONTH)); } - //add space between list items - void space(float size) { layout(isvertical() ? 0 : int(size*FONTW), isvertical() ? int(size*FONTH) : 0); } - - void spring(int weight) - { - if(curlist < 0) return; - list &l = lists[curlist]; - if(layoutpass) { if(l.parent >= 0) l.springs += weight; return; } - int nextspring = min(l.curspring + weight, l.springs); - if(nextspring <= l.curspring) return; - if(ishorizontal()) - { - int w = xsize - l.w; - layout((w*nextspring)/l.springs - (w*l.curspring)/l.springs, 0); - } - else - { - int h = ysize - l.h; - layout(0, (h*nextspring)/l.springs - (h*l.curspring)/l.springs); - } - l.curspring = nextspring; - } - - void column(int col) - { - if(curlist < 0 || !layoutpass || col < 0 || col >= MAXCOLUMNS) return; - list &l = lists[curlist]; - l.column = col; - } - - int layout(int w, int h) - { - if(layoutpass) - { - if(ishorizontal()) - { - xsize += w; - ysize = max(ysize, h); - } - else - { - xsize = max(xsize, w); - ysize += h; - } - return 0; - } - else - { - bool hit = ishit(w, h); - if(ishorizontal()) curx += w; - else cury += h; - return (hit && visible()) ? mousebuttons|G3D_ROLLOVER : 0; - } - } - - bool mergehits(bool on) - { - bool oldval = shouldmergehits; - shouldmergehits = on; - return oldval; - } - - bool ishit(int w, int h, int x = curx, int y = cury) - { - if(shouldmergehits) return windowhit==this && (ishorizontal() ? hitx>=x && hitx=y && hity=x && hity>=y && hitx=0 && visible()) - { - bool hit = ishit(size+SHADOW, size+SHADOW); - float xs = size, ys = size, xi = curx, yi = cury; - if(overlaid && hit && actionon) - { - hudnotextureshader->set(); - gle::colorf(0, 0, 0, 0.75f); - rect_(xi+SHADOW, yi+SHADOW, xs, ys); - hudshader->set(); - } - int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))), - x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y)))); - glDisable(GL_BLEND); - modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL); - game::renderplayerpreview(model, team, weap); - modelpreview::end(); - hudshader->set(); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - if(overlaid) - { - if(hit) - { - hudnotextureshader->set(); - glBlendFunc(GL_ZERO, GL_SRC_COLOR); - gle::colorf(1, 0.5f, 0.5f); - rect_(xi, yi, xs, ys); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - hudshader->set(); - } - if(overlaid[0]) text_(overlaid, xi + xs/12, yi + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit, hit); - if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); - gle::color(light); - glBindTexture(GL_TEXTURE_2D, overlaytex->id); - rect_(xi, yi, xs, ys, 0); - } - } - return layout(size+SHADOW, size+SHADOW); - } - - int modelpreview(const char *name, int anim, float sizescale, const char *overlaid, bool throttle) - { - autotab(); - if(sizescale==0) sizescale = 1; - int size = (int)(sizescale*2*FONTH)-SHADOW; - if(name[0] && visible() && (!throttle || throttlepreview(modelloaded(name)))) - { - bool hit = ishit(size+SHADOW, size+SHADOW); - float xs = size, ys = size, xi = curx, yi = cury; - if(overlaid && hit && actionon) - { - hudnotextureshader->set(); - gle::colorf(0, 0, 0, 0.75f); - rect_(xi+SHADOW, yi+SHADOW, xs, ys); - hudshader->set(); - } - int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))), - x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y)))); - glDisable(GL_BLEND); - modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL); - model *m = loadmodel(name); - if(m) - { - entitylight light; - light.color = vec(1, 1, 1); - light.dir = vec(0, -1, 2).normalize(); - vec center, radius; - m->boundbox(center, radius); - float yaw; - vec o = calcmodelpreviewpos(radius, yaw).sub(center); - rendermodel(&light, name, anim, o, yaw, 0, 0, NULL, NULL, 0); - } - modelpreview::end(); - hudshader->set(); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - if(overlaid) - { - if(hit) - { - hudnotextureshader->set(); - glBlendFunc(GL_ZERO, GL_SRC_COLOR); - gle::colorf(1, 0.5f, 0.5f); - rect_(xi, yi, xs, ys); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - hudshader->set(); - } - if(overlaid[0]) text_(overlaid, xi + xs/12, yi + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit, hit); - if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); - gle::color(light); - glBindTexture(GL_TEXTURE_2D, overlaytex->id); - rect_(xi, yi, xs, ys, 0); - } - } - return layout(size+SHADOW, size+SHADOW); - } - - int prefabpreview(const char *prefab, const vec &color, float sizescale, const char *overlaid, bool throttle) - { - autotab(); - if(sizescale==0) sizescale = 1; - int size = (int)(sizescale*2*FONTH)-SHADOW; - if(prefab[0] && visible() && (!throttle || throttlepreview(prefabloaded(prefab)))) - { - bool hit = ishit(size+SHADOW, size+SHADOW); - float xs = size, ys = size, xi = curx, yi = cury; - if(overlaid && hit && actionon) - { - hudnotextureshader->set(); - gle::colorf(0, 0, 0, 0.75f); - rect_(xi+SHADOW, yi+SHADOW, xs, ys); - hudshader->set(); - } - int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))), - x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y)))); - glDisable(GL_BLEND); - modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL); - previewprefab(prefab, color); - modelpreview::end(); - hudshader->set(); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - if(overlaid) - { - if(hit) - { - hudnotextureshader->set(); - glBlendFunc(GL_ZERO, GL_SRC_COLOR); - gle::colorf(1, 0.5f, 0.5f); - rect_(xi, yi, xs, ys); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - hudshader->set(); - } - if(overlaid[0]) text_(overlaid, xi + FONTH/2, yi + FONTH/2, hit ? 0xFF0000 : 0xFFFFFF, hit, hit); - if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); - gle::color(light); - glBindTexture(GL_TEXTURE_2D, overlaytex->id); - rect_(xi, yi, xs, ys, 0); - } - } - return layout(size+SHADOW, size+SHADOW); - } - - void slider(int &val, int vmin, int vmax, int color, const char *label) - { - autotab(); - int x = curx; - int y = cury; - line_((FONTH*2)/3); - if(visible()) - { - if(!label) label = intstr(val); - int w = text_width(label); - - bool hit; - int px, py, offset = vmin < vmax ? clamp(val, vmin, vmax) : clamp(val, vmax, vmin); - if(ishorizontal()) - { - hit = ishit(FONTH, ysize, x, y); - px = x + (FONTH-w)/2; - py = y + (ysize-FONTH) - ((ysize-FONTH)*(offset-vmin))/((vmax==vmin) ? 1 : (vmax-vmin)); //vmin at bottom - } - else - { - hit = ishit(xsize, FONTH, x, y); - px = x + FONTH/2 - w/2 + ((xsize-w)*(offset-vmin))/((vmax==vmin) ? 1 : (vmax-vmin)); //vmin at left - py = y; - } - - if(hit) color = 0xFF0000; - text_(label, px, py, color, hit && actionon, hit); - if(hit && actionon) - { - int vnew = (vmin < vmax ? 1 : -1)+vmax-vmin; - if(ishorizontal()) vnew = int((vnew*(y+ysize-FONTH/2-hity))/(ysize-FONTH)); - else vnew = int((vnew*(hitx-x-FONTH/2))/(xsize-w)); - vnew += vmin; - vnew = vmin < vmax ? clamp(vnew, vmin, vmax) : clamp(vnew, vmax, vmin); - if(vnew != val) val = vnew; - } - } - } - - char *field(const char *name, int color, int length, int height, const char *initval, int initmode) - { - return field_(name, color, length, height, initval, initmode, FIELDEDIT); - } - - char *keyfield(const char *name, int color, int length, int height, const char *initval, int initmode) - { - return field_(name, color, length, height, initval, initmode, FIELDKEY); - } - - char *field_(const char *name, int color, int length, int height, const char *initval, int initmode, int fieldtype = FIELDEDIT) - { - editor *e = useeditor(name, initmode, false, initval); // generate a new editor if necessary - if(layoutpass) - { - if(initval && e->mode==EDITORFOCUSED && (e!=currentfocus() || fieldmode == FIELDSHOW)) - { - if(strcmp(e->lines[0].text, initval)) e->clear(initval); - } - e->linewrap = (length<0); - e->maxx = (e->linewrap) ? -1 : length; - e->maxy = (height<=0)?1:-1; - e->pixelwidth = abs(length)*FONTW; - if(e->linewrap && e->maxy==1) - { - int temp; - text_bounds(e->lines[0].text, temp, e->pixelheight, e->pixelwidth); //only single line editors can have variable height - } - else - e->pixelheight = FONTH*max(height, 1); - } - int h = e->pixelheight; - int w = e->pixelwidth + FONTW; - - bool wasvertical = isvertical(); - if(wasvertical && e->maxy != 1) pushlist(); - - char *result = NULL; - if(visible() && !layoutpass) - { - e->rendered = true; - - bool hit = ishit(w, h); - if(hit) - { - if(mousebuttons&G3D_DOWN) //mouse request focus - { - if(fieldtype==FIELDKEY) e->clear(); - useeditor(name, initmode, true); - e->mark(false); - fieldmode = fieldtype; - } - } - bool editing = (fieldmode != FIELDSHOW) && (e==currentfocus()); - if(hit && editing && (mousebuttons&G3D_PRESSED)!=0 && fieldtype==FIELDEDIT) e->hit(int(floor(hitx-(curx+FONTW/2))), int(floor(hity-cury)), (mousebuttons&G3D_DRAGGED)!=0); //mouse request position - if(editing && ((fieldmode==FIELDCOMMIT) || (fieldmode==FIELDABORT) || !hit)) // commit field if user pressed enter or wandered out of focus - { - if(fieldmode==FIELDCOMMIT || (fieldmode!=FIELDABORT && !hit)) result = e->currentline().text; - e->active = (e->mode!=EDITORFOCUSED); - fieldmode = FIELDSHOW; - } - else fieldsactive = true; - - e->draw(curx+FONTW/2, cury, color, hit && editing); - - hudnotextureshader->set(); - glDisable(GL_BLEND); - if(editing) gle::colorf(1, 0, 0); - else gle::colorub(color>>16, (color>>8)&0xFF, color&0xFF); - rect_(curx, cury, w, h, true); - glEnable(GL_BLEND); - hudshader->set(); - } - layout(w, h); - - if(e->maxy != 1) - { - int slines = e->limitscrolly(); - if(slines > 0) - { - int pos = e->scrolly; - slider(e->scrolly, slines, 0, color, NULL); - if(pos != e->scrolly) e->cy = e->scrolly; - } - if(wasvertical) poplist(); - } - - return result; - } - - void rect_(float x, float y, float w, float h, bool lines = false) - { - gle::defvertex(2); - gle::begin(lines ? GL_LINE_LOOP : GL_TRIANGLE_STRIP); - gle::attribf(x, y); - gle::attribf(x + w, y); - if(lines) gle::attribf(x + w, y + h); - gle::attribf(x, y + h); - if(!lines) gle::attribf(x + w, y + h); - xtraverts += gle::end(); - } - - void rect_(float x, float y, float w, float h, int usetc) - { - gle::defvertex(2); - gle::deftexcoord0(); - gle::begin(GL_TRIANGLE_STRIP); - static const vec2 tc[5] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1), vec2(0, 0) }; - gle::attribf(x, y); gle::attrib(tc[usetc]); - gle::attribf(x + w, y); gle::attrib(tc[usetc+1]); - gle::attribf(x, y + h); gle::attrib(tc[usetc+3]); - gle::attribf(x + w, y + h); gle::attrib(tc[usetc+2]); - xtraverts += gle::end(); - } - - void text_(const char *text, int x, int y, int color, bool shadow, bool force = false) - { - if(shadow) draw_text(text, x+SHADOW, y+SHADOW, 0x00, 0x00, 0x00, -0xC0); - draw_text(text, x, y, color>>16, (color>>8)&0xFF, color&0xFF, force ? -0xFF : 0xFF); - } - - void background(int color, int inheritw, int inherith) - { - if(layoutpass) return; - hudnotextureshader->set(); - gle::colorub(color>>16, (color>>8)&0xFF, color&0xFF, 0x80); - int w = xsize, h = ysize; - if(inheritw>0) - { - int parentw = curlist, parentdepth = 0; - for(;parentdepth < inheritw && lists[parentw].parent>=0; parentdepth++) - parentw = lists[parentw].parent; - list &p = lists[parentw]; - w = p.springs > 0 && (curdepth-parentdepth)&1 ? lists[p.parent].w : p.w; - } - if(inherith>0) - { - int parenth = curlist, parentdepth = 0; - for(;parentdepth < inherith && lists[parenth].parent>=0; parentdepth++) - parenth = lists[parenth].parent; - list &p = lists[parenth]; - h = p.springs > 0 && !((curdepth-parentdepth)&1) ? lists[p.parent].h : p.h; - } - rect_(curx, cury, w, h); - hudshader->set(); - } - - void icon_(Texture *t, bool overlaid, int x, int y, int size, bool hit, const char *title = NULL) - { - float scale = float(size)/max(t->xs, t->ys); //scale and preserve aspect ratio - float xs = t->xs*scale, ys = t->ys*scale; - x += int((size-xs)/2); - y += int((size-ys)/2); - const vec &color = hit ? vec(1, 0.5f, 0.5f) : (overlaid ? vec(1, 1, 1) : light); - glBindTexture(GL_TEXTURE_2D, t->id); - if(hit && actionon) - { - gle::colorf(0, 0, 0, 0.75f); - rect_(x+SHADOW, y+SHADOW, xs, ys, 0); - } - gle::color(color); - rect_(x, y, xs, ys, 0); - - if(overlaid) - { - if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); - glBindTexture(GL_TEXTURE_2D, overlaytex->id); - gle::color(light); - rect_(x, y, xs, ys, 0); - if(title) text_(title, x + xs/12, y + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit && actionon, hit); - } - } - - void previewslot(VSlot &vslot, bool overlaid, int x, int y, int size, bool hit) - { - Slot &slot = *vslot.slot; - if(slot.sts.empty()) return; - VSlot *layer = NULL; - Texture *t = NULL, *glowtex = NULL, *layertex = NULL; - if(slot.loaded) - { - t = slot.sts[0].t; - if(t == notexture) return; - Slot &slot = *vslot.slot; - if(slot.texmask&(1<slot->sts.empty()) layertex = layer->slot->sts[0].t; - } - } - else if(slot.thumbnail && slot.thumbnail != notexture) t = slot.thumbnail; - else return; - float xt = min(1.0f, t->xs/(float)t->ys), yt = min(1.0f, t->ys/(float)t->xs), xs = size, ys = size; - if(hit && actionon) - { - hudnotextureshader->set(); - gle::colorf(0, 0, 0, 0.75f); - rect_(x+SHADOW, y+SHADOW, xs, ys); - hudshader->set(); - } - SETSHADER(hudrgb); - gle::defvertex(2); - gle::deftexcoord0(); - const vec &color = hit ? vec(1, 0.5f, 0.5f) : (overlaid ? vec(1, 1, 1) : light); - vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; - float xoff = vslot.offset.x, yoff = vslot.offset.y; - if(vslot.rotation) - { - const texrotation &r = texrotations[vslot.rotation]; - if(r.swapxy) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); } - if(r.flipx) { xoff *= -1; loopk(4) tc[k].x *= -1; } - if(r.flipy) { yoff *= -1; loopk(4) tc[k].y *= -1; } - } - loopk(4) { tc[k].x = tc[k].x/xt - xoff/t->xs; tc[k].y = tc[k].y/yt - yoff/t->ys; } - if(slot.loaded) gle::color(vec(color).mul(vslot.colorscale)); - else gle::color(color); - glBindTexture(GL_TEXTURE_2D, t->id); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x, y); gle::attrib(tc[0]); - gle::attribf(x+xs, y); gle::attrib(tc[1]); - gle::attribf(x, y+ys); gle::attrib(tc[3]); - gle::attribf(x+xs, y+ys); gle::attrib(tc[2]); - gle::end(); - if(glowtex) - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glBindTexture(GL_TEXTURE_2D, glowtex->id); - if(hit || overlaid) gle::color(vec(vslot.glowcolor).mul(color)); - else gle::color(vslot.glowcolor); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x, y); gle::attrib(tc[0]); - gle::attribf(x+xs, y); gle::attrib(tc[1]); - gle::attribf(x, y+ys); gle::attrib(tc[3]); - gle::attribf(x+xs, y+ys); gle::attrib(tc[2]); - gle::end(); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - if(layertex) - { - glBindTexture(GL_TEXTURE_2D, layertex->id); - gle::color(vec(color).mul(layer->colorscale)); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x+xs/2, y+ys/2); gle::attrib(tc[0]); - gle::attribf(x+xs, y+ys/2); gle::attrib(tc[1]); - gle::attribf(x+xs/2, y+ys); gle::attrib(tc[3]); - gle::attribf(x+xs, y+ys); gle::attrib(tc[2]); - gle::end(); - } - - hudshader->set(); - if(overlaid) - { - if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); - glBindTexture(GL_TEXTURE_2D, overlaytex->id); - gle::color(light); - rect_(x, y, xs, ys, 0); - } - } - - void line_(int size, float percent = 1.0f) - { - if(visible()) - { - if(!slidertex) slidertex = textureload("data/guislider.png", 3); - glBindTexture(GL_TEXTURE_2D, slidertex->id); - if(percent < 0.99f) - { - gle::colorf(light.x, light.y, light.z, 0.375f); - if(ishorizontal()) - rect_(curx + FONTH/2 - size/2, cury, size, ysize, 0); - else - rect_(curx, cury + FONTH/2 - size/2, xsize, size, 1); - } - gle::color(light); - if(ishorizontal()) - rect_(curx + FONTH/2 - size/2, cury + ysize*(1-percent), size, ysize*percent, 0); - else - rect_(curx, cury + FONTH/2 - size/2, xsize*percent, size, 1); - } - layout(ishorizontal() ? FONTH : 0, ishorizontal() ? 0 : FONTH); - } - - void textbox(const char *text, int width, int height, int color) - { - width *= FONTW; - height *= FONTH; - int w, h; - text_bounds(text, w, h, width); - if(h > height) height = h; - if(visible()) draw_text(text, curx, cury, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, -1, width); - layout(width, height); - } - - int button_(const char *text, int color, const char *icon, bool clickable, bool center) - { - const int padding = 10; - int w = 0; - if(icon) w += ICON_SIZE; - if(icon && text) w += padding; - if(text) w += text_width(text); - - if(visible()) - { - bool hit = ishit(w, FONTH); - if(hit && clickable) color = 0xFF0000; - int x = curx; - if(isvertical() && center) x += (xsize-w)/2; - - if(icon) - { - if(icon[0] != ' ') - { - const char *ext = strrchr(icon, '.'); - defformatstring(tname, "packages/icons/%s%s", icon, ext ? "" : ".png"); - icon_(textureload(tname, 3), false, x, cury, ICON_SIZE, clickable && hit); - } - x += ICON_SIZE; - } - if(icon && text) x += padding; - if(text) text_(text, x, cury, color, center || (hit && clickable && actionon), hit && clickable); - } - return layout(w, FONTH); - } - - static Texture *skintex, *overlaytex, *slidertex; - static const int skinx[], skiny[]; - static const struct patch { ushort left, right, top, bottom; uchar flags; } patches[]; - - static void drawskin(int x, int y, int gapw, int gaph, int start, int n, int passes = 1, const vec &light = vec(1, 1, 1), float alpha = 0.80f)//int vleft, int vright, int vtop, int vbottom, int start, int n) - { - if(!skintex) skintex = textureload("data/guiskin.png", 3); - glBindTexture(GL_TEXTURE_2D, skintex->id); - int gapx1 = INT_MAX, gapy1 = INT_MAX, gapx2 = INT_MAX, gapy2 = INT_MAX; - float wscale = 1.0f/(SKIN_W*SKIN_SCALE), hscale = 1.0f/(SKIN_H*SKIN_SCALE); - - loopj(passes) - { - bool quads = false; - if(passes>1) glDepthFunc(j ? GL_LEQUAL : GL_GREATER); - gle::color(j ? light : vec(1, 1, 1), passes<=1 || j ? alpha : alpha/2); //ghost when its behind something in depth - loopi(n) - { - const patch &p = patches[start+i]; - int left = skinx[p.left]*SKIN_SCALE, right = skinx[p.right]*SKIN_SCALE, - top = skiny[p.top]*SKIN_SCALE, bottom = skiny[p.bottom]*SKIN_SCALE; - float tleft = left*wscale, tright = right*wscale, - ttop = top*hscale, tbottom = bottom*hscale; - if(p.flags&0x1) - { - gapx1 = left; - gapx2 = right; - } - else if(left >= gapx2) - { - left += gapw - (gapx2-gapx1); - right += gapw - (gapx2-gapx1); - } - if(p.flags&0x10) - { - gapy1 = top; - gapy2 = bottom; - } - else if(top >= gapy2) - { - top += gaph - (gapy2-gapy1); - bottom += gaph - (gapy2-gapy1); - } - - //multiple tiled quads if necessary rather than a single stretched one - int ystep = bottom-top; - int yo = y+top; - while(ystep > 0) - { - if(p.flags&0x10 && yo+ystep-(y+top) > gaph) - { - ystep = gaph+y+top-yo; - tbottom = ttop+ystep*hscale; - } - int xstep = right-left; - int xo = x+left; - float tright2 = tright; - while(xstep > 0) - { - if(p.flags&0x01 && xo+xstep-(x+left) > gapw) - { - xstep = gapw+x+left-xo; - tright = tleft+xstep*wscale; - } - if(!quads) - { - quads = true; - gle::defvertex(2); - gle::deftexcoord0(); - gle::begin(GL_QUADS); - } - gle::attribf(xo, yo); gle::attribf(tleft, ttop); - gle::attribf(xo+xstep, yo); gle::attribf(tright, ttop); - gle::attribf(xo+xstep, yo+ystep); gle::attribf(tright, tbottom); - gle::attribf(xo, yo+ystep); gle::attribf(tleft, tbottom); - if(!(p.flags&0x01)) break; - xo += xstep; - } - tright = tright2; - if(!(p.flags&0x10)) break; - yo += ystep; - } - } - if(quads) xtraverts += gle::end(); - else break; //if it didn't happen on the first pass, it won't happen on the second.. - } - if(passes>1) glDepthFunc(GL_ALWAYS); - } - - vec origin, scale, *savedorigin; - float dist; - g3d_callback *cb; - bool gui2d; - - static float basescale, maxscale; - static bool passthrough; - static float alpha; - static vec light; - - void adjustscale() - { - int w = xsize + (skinx[2]-skinx[1])*SKIN_SCALE + (skinx[10]-skinx[9])*SKIN_SCALE, h = ysize + (skiny[9]-skiny[7])*SKIN_SCALE; - if(tcurrent) h += ((skiny[5]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE + FONTH-2*INSERT; - else h += (skiny[6]-skiny[3])*SKIN_SCALE; - - float aspect = forceaspect ? 1.0f/forceaspect : float(screenh)/float(screenw), fit = 1.0f; - if(w*aspect*basescale>1.0f) fit = 1.0f/(w*aspect*basescale); - if(h*basescale*fit>maxscale) fit *= maxscale/(h*basescale*fit); - origin = vec(0.5f-((w-xsize)/2 - (skinx[2]-skinx[1])*SKIN_SCALE)*aspect*scale.x*fit, 0.5f + (0.5f*h-(skiny[9]-skiny[7])*SKIN_SCALE)*scale.y*fit, 0); - scale = vec(aspect*scale.x*fit, scale.y*fit, 1); - } - - void start(int starttime, float initscale, int *tab, bool allowinput) - { - if(gui2d) - { - initscale *= 0.025f; - if(allowinput) hascursor = true; - } - basescale = initscale; - if(layoutpass) scale.x = scale.y = scale.z = guifadein ? basescale*min((totalmillis-starttime)/300.0f, 1.0f) : basescale; - alpha = allowinput ? 0.80f : 0.60f; - passthrough = scale.xo.y, origin.x-camera1->o.x); - hudmatrix = camprojmatrix; - hudmatrix.translate(origin); - hudmatrix.rotate_around_z(yaw - 90*RAD); - hudmatrix.rotate_around_x(-90*RAD); - hudmatrix.scale(-scale.x, scale.y, scale.z); - - vec dir; - lightreaching(origin, light, dir, false, 0, 0.5f); - float intensity = vec(yaw, 0.0f).dot(dir); - light.mul(1.0f + max(intensity, 0.0f)); - } - - resethudmatrix(); - hudshader->set(); - - drawskin(curx-skinx[2]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, ysize, 0, 9, gui2d ? 1 : 2, light, alpha); - if(!tcurrent) drawskin(curx-skinx[5]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, 0, 9, 1, gui2d ? 1 : 2, light, alpha); - } - } - - void adjusthorizontalcolumn(int col, int i) - { - int h = columns[col], dh = 0; - for(int d = 1; i >= 0; d ^= 1) - { - list &p = lists[i]; - if(d&1) { dh = h - p.h; if(dh <= 0) break; p.h = h; } - else { p.h += dh; h = p.h; } - i = p.parent; - } - ysize += max(dh, 0); - } - - void adjustverticalcolumn(int col, int i) - { - int w = columns[col], dw = 0; - for(int d = 0; i >= 0; d ^= 1) - { - list &p = lists[i]; - if(d&1) { p.w += dw; w = p.w; } - else { dw = w - p.w; if(dw <= 0) break; p.w = w; } - i = p.parent; - } - xsize = max(xsize, w); - } - - void adjustcolumns() - { - if(lists.inrange(curlist)) - { - list &l = lists[curlist]; - if(l.column >= 0) columns[l.column] = max(columns[l.column], ishorizontal() ? ysize : xsize); - } - int parent = -1, depth = 0; - for(int i = firstlist; i < lists.length(); i++) - { - list &l = lists[i]; - if(l.parent > parent) { parent = l.parent; depth++; } - else if(l.parent < parent) - { - while(parent > l.parent && depth > 0) - { - parent = lists[parent].parent; - depth--; - } - } - if(l.column >= 0) - { - if(depth&1) adjusthorizontalcolumn(l.column, i); - else adjustverticalcolumn(l.column, i); - } - } - } - - void end() - { - if(layoutpass) - { - adjustcolumns(); - xsize = max(tx, xsize); - ysize = max(ty, ysize); - ysize = max(ysize, (skiny[7]-skiny[6])*SKIN_SCALE); - if(tcurrent) *tcurrent = max(1, min(*tcurrent, tpos)); - if(gui2d) adjustscale(); - if(!windowhit && !passthrough) - { - float dist = 0; - if(gui2d) - { - hitx = (cursorx - origin.x)/scale.x; - hity = (cursory - origin.y)/scale.y; - } - else - { - plane p; - p.toplane(vec(origin).sub(camera1->o).set(2, 0).normalize(), origin); - if(p.rayintersect(camera1->o, camdir, dist) && dist>=0) - { - vec hitpos(camdir); - hitpos.mul(dist).add(camera1->o).sub(origin); - hitx = vec(-p.y, p.x, 0).dot(hitpos)/scale.x; - hity = -hitpos.z/scale.y; - } - } - if((mousebuttons & G3D_PRESSED) && (fabs(hitx-firstx) > 2 || fabs(hity - firsty) > 2)) mousebuttons |= G3D_DRAGGED; - if(dist>=0 && hitx>=-xsize/2 && hitx<=xsize/2 && hity<=0) - { - if(hity>=-ysize || (tcurrent && hity>=-ysize-(FONTH-2*INSERT)-((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE && hitx<=tx-xsize/2)) - windowhit = this; - } - } - } - else - { - if(tcurrent && txgui(*this, layoutpass); - } + struct list + { + int parent, w, h, springs, curspring, column; + }; + + int firstlist, nextlist; + int columns[MAXCOLUMNS]; + + static vector lists; + static float hitx, hity; + static int curdepth, curlist, xsize, ysize, curx, cury; + static bool shouldmergehits, shouldautotab; + + static void reset() + { + lists.setsize(0); + } + + static int ty, tx, tpos, *tcurrent, tcolor; //tracking tab size and position since uses different layout method... + + bool allowautotab(bool on) + { + bool oldval = shouldautotab; + shouldautotab = on; + return oldval; + } + + void autotab() + { + if(tcurrent) + { + if(layoutpass && !tpos) tcurrent = NULL; //disable tabs because you didn't start with one + if(shouldautotab && !curdepth && (layoutpass ? 0 : cury) + ysize > guiautotab*FONTH) tab(NULL, tcolor); + } + } + + bool shouldtab() + { + if(tcurrent && shouldautotab) + { + if(layoutpass) + { + int space = guiautotab*FONTH - ysize; + if(space < 0) return true; + int l = lists[curlist].parent; + while(l >= 0) + { + space -= lists[l].h; + if(space < 0) return true; + l = lists[l].parent; + } + } + else + { + int space = guiautotab*FONTH - cury; + if(ysize > space) return true; + int l = lists[curlist].parent; + while(l >= 0) + { + if(lists[l].h > space) return true; + l = lists[l].parent; + } + } + } + return false; + } + + bool visible() { return (!tcurrent || tpos==*tcurrent) && !layoutpass; } + + //tab is always at top of page + void tab(const char *name, int color) + { + if(curdepth != 0) return; + if(color) tcolor = color; + tpos++; + if(!name) name = intstr(tpos); + int w = max(text_width(name) - 2*INSERT, 0); + if(layoutpass) + { + ty = max(ty, ysize); + ysize = 0; + } + else + { + cury = -ysize; + int h = FONTH-2*INSERT, + x1 = curx + tx, + x2 = x1 + w + ((skinx[3]-skinx[2]) + (skinx[5]-skinx[4]))*SKIN_SCALE, + y1 = cury - ((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE-h, + y2 = cury; + bool hit = tcurrent && windowhit==this && hitx>=x1 && hity>=y1 && hitx=0) + { + lists[curlist].w = xsize; + lists[curlist].h = ysize; + } + list &l = lists.add(); + l.parent = curlist; + l.springs = 0; + l.column = -1; + curlist = lists.length()-1; + xsize = ysize = 0; + } + else + { + curlist = nextlist++; + if(curlist >= lists.length()) // should never get here unless script code doesn't use same amount of lists in layout and render passes + { + list &l = lists.add(); + l.parent = curlist; + l.springs = 0; + l.column = -1; + l.w = l.h = 0; + } + list &l = lists[curlist]; + l.curspring = 0; + if(l.springs > 0) + { + if(ishorizontal()) xsize = l.w; else ysize = l.h; + } + else + { + xsize = l.w; + ysize = l.h; + } + } + curdepth++; + } + + void poplist() + { + if(!lists.inrange(curlist)) return; + list &l = lists[curlist]; + if(layoutpass) + { + l.w = xsize; + l.h = ysize; + if(l.column >= 0) columns[l.column] = max(columns[l.column], ishorizontal() ? ysize : xsize); + } + curlist = l.parent; + curdepth--; + if(lists.inrange(curlist)) + { + int w = xsize, h = ysize; + if(ishorizontal()) cury -= h; else curx -= w; + list &p = lists[curlist]; + xsize = p.w; + ysize = p.h; + if(!layoutpass && p.springs > 0) + { + list &s = lists[p.parent]; + if(ishorizontal()) xsize = s.w; else ysize = s.h; + } + layout(w, h); + } + } + + int text (const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, false, false); } + int button(const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, true, false); } + int title (const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, false, true); } + + void separator() { autotab(); line_(FONTH/3); } + void progress(float percent) { autotab(); line_((FONTH*4)/5, percent); } + + //use to set min size (useful when you have progress bars) + void strut(float size) { layout(isvertical() ? int(size*FONTW) : 0, isvertical() ? 0 : int(size*FONTH)); } + //add space between list items + void space(float size) { layout(isvertical() ? 0 : int(size*FONTW), isvertical() ? int(size*FONTH) : 0); } + + void spring(int weight) + { + if(curlist < 0) return; + list &l = lists[curlist]; + if(layoutpass) { if(l.parent >= 0) l.springs += weight; return; } + int nextspring = min(l.curspring + weight, l.springs); + if(nextspring <= l.curspring) return; + if(ishorizontal()) + { + int w = xsize - l.w; + layout((w*nextspring)/l.springs - (w*l.curspring)/l.springs, 0); + } + else + { + int h = ysize - l.h; + layout(0, (h*nextspring)/l.springs - (h*l.curspring)/l.springs); + } + l.curspring = nextspring; + } + + void column(int col) + { + if(curlist < 0 || !layoutpass || col < 0 || col >= MAXCOLUMNS) return; + list &l = lists[curlist]; + l.column = col; + } + + int layout(int w, int h) + { + if(layoutpass) + { + if(ishorizontal()) + { + xsize += w; + ysize = max(ysize, h); + } + else + { + xsize = max(xsize, w); + ysize += h; + } + return 0; + } + else + { + bool hit = ishit(w, h); + if(ishorizontal()) curx += w; + else cury += h; + return (hit && visible()) ? mousebuttons|G3D_ROLLOVER : 0; + } + } + + bool mergehits(bool on) + { + bool oldval = shouldmergehits; + shouldmergehits = on; + return oldval; + } + + bool ishit(int w, int h, int x = curx, int y = cury) + { + if(shouldmergehits) return windowhit==this && (ishorizontal() ? hitx>=x && hitx=y && hity=x && hity>=y && hitx=0 && visible()) + { + bool hit = ishit(size+SHADOW, size+SHADOW); + float xs = size, ys = size, xi = curx, yi = cury; + if(overlaid && hit && actionon) + { + hudnotextureshader->set(); + gle::colorf(0, 0, 0, 0.75f); + rect_(xi+SHADOW, yi+SHADOW, xs, ys); + hudshader->set(); + } + int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))), + x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y)))); + glDisable(GL_BLEND); + modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL); + game::renderplayerpreview(model, team, weap); + modelpreview::end(); + hudshader->set(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + if(overlaid) + { + if(hit) + { + hudnotextureshader->set(); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); + gle::colorf(1, 0.5f, 0.5f); + rect_(xi, yi, xs, ys); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + hudshader->set(); + } + if(overlaid[0]) text_(overlaid, xi + xs/12, yi + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit, hit); + if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); + gle::color(light); + glBindTexture(GL_TEXTURE_2D, overlaytex->id); + rect_(xi, yi, xs, ys, 0); + } + } + return layout(size+SHADOW, size+SHADOW); + } + + int modelpreview(const char *name, int anim, float sizescale, const char *overlaid, bool throttle) + { + autotab(); + if(sizescale==0) sizescale = 1; + int size = (int)(sizescale*2*FONTH)-SHADOW; + if(name[0] && visible() && (!throttle || throttlepreview(modelloaded(name)))) + { + bool hit = ishit(size+SHADOW, size+SHADOW); + float xs = size, ys = size, xi = curx, yi = cury; + if(overlaid && hit && actionon) + { + hudnotextureshader->set(); + gle::colorf(0, 0, 0, 0.75f); + rect_(xi+SHADOW, yi+SHADOW, xs, ys); + hudshader->set(); + } + int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))), + x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y)))); + glDisable(GL_BLEND); + modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL); + model *m = loadmodel(name); + if(m) + { + entitylight light; + light.color = vec(1, 1, 1); + light.dir = vec(0, -1, 2).normalize(); + vec center, radius; + m->boundbox(center, radius); + float yaw; + vec o = calcmodelpreviewpos(radius, yaw).sub(center); + rendermodel(&light, name, anim, o, yaw, 0, 0, NULL, NULL, 0); + } + modelpreview::end(); + hudshader->set(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + if(overlaid) + { + if(hit) + { + hudnotextureshader->set(); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); + gle::colorf(1, 0.5f, 0.5f); + rect_(xi, yi, xs, ys); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + hudshader->set(); + } + if(overlaid[0]) text_(overlaid, xi + xs/12, yi + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit, hit); + if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); + gle::color(light); + glBindTexture(GL_TEXTURE_2D, overlaytex->id); + rect_(xi, yi, xs, ys, 0); + } + } + return layout(size+SHADOW, size+SHADOW); + } + + int prefabpreview(const char *prefab, const vec &color, float sizescale, const char *overlaid, bool throttle) + { + autotab(); + if(sizescale==0) sizescale = 1; + int size = (int)(sizescale*2*FONTH)-SHADOW; + if(prefab[0] && visible() && (!throttle || throttlepreview(prefabloaded(prefab)))) + { + bool hit = ishit(size+SHADOW, size+SHADOW); + float xs = size, ys = size, xi = curx, yi = cury; + if(overlaid && hit && actionon) + { + hudnotextureshader->set(); + gle::colorf(0, 0, 0, 0.75f); + rect_(xi+SHADOW, yi+SHADOW, xs, ys); + hudshader->set(); + } + int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))), + x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y)))); + glDisable(GL_BLEND); + modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL); + previewprefab(prefab, color); + modelpreview::end(); + hudshader->set(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + if(overlaid) + { + if(hit) + { + hudnotextureshader->set(); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); + gle::colorf(1, 0.5f, 0.5f); + rect_(xi, yi, xs, ys); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + hudshader->set(); + } + if(overlaid[0]) text_(overlaid, xi + FONTH/2, yi + FONTH/2, hit ? 0xFF0000 : 0xFFFFFF, hit, hit); + if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); + gle::color(light); + glBindTexture(GL_TEXTURE_2D, overlaytex->id); + rect_(xi, yi, xs, ys, 0); + } + } + return layout(size+SHADOW, size+SHADOW); + } + + void slider(int &val, int vmin, int vmax, int color, const char *label) + { + autotab(); + int x = curx; + int y = cury; + line_((FONTH*2)/3); + if(visible()) + { + if(!label) label = intstr(val); + int w = text_width(label); + + bool hit; + int px, py, offset = vmin < vmax ? clamp(val, vmin, vmax) : clamp(val, vmax, vmin); + if(ishorizontal()) + { + hit = ishit(FONTH, ysize, x, y); + px = x + (FONTH-w)/2; + py = y + (ysize-FONTH) - ((ysize-FONTH)*(offset-vmin))/((vmax==vmin) ? 1 : (vmax-vmin)); //vmin at bottom + } + else + { + hit = ishit(xsize, FONTH, x, y); + px = x + FONTH/2 - w/2 + ((xsize-w)*(offset-vmin))/((vmax==vmin) ? 1 : (vmax-vmin)); //vmin at left + py = y; + } + + if(hit) color = 0xFF0000; + text_(label, px, py, color, hit && actionon, hit); + if(hit && actionon) + { + int vnew = (vmin < vmax ? 1 : -1)+vmax-vmin; + if(ishorizontal()) vnew = int((vnew*(y+ysize-FONTH/2-hity))/(ysize-FONTH)); + else vnew = int((vnew*(hitx-x-FONTH/2))/(xsize-w)); + vnew += vmin; + vnew = vmin < vmax ? clamp(vnew, vmin, vmax) : clamp(vnew, vmax, vmin); + if(vnew != val) val = vnew; + } + } + } + + char *field(const char *name, int color, int length, int height, const char *initval, int initmode) + { + return field_(name, color, length, height, initval, initmode, FIELDEDIT); + } + + char *keyfield(const char *name, int color, int length, int height, const char *initval, int initmode) + { + return field_(name, color, length, height, initval, initmode, FIELDKEY); + } + + char *field_(const char *name, int color, int length, int height, const char *initval, int initmode, int fieldtype = FIELDEDIT) + { + editor *e = useeditor(name, initmode, false, initval); // generate a new editor if necessary + if(layoutpass) + { + if(initval && e->mode==EDITORFOCUSED && (e!=currentfocus() || fieldmode == FIELDSHOW)) + { + if(strcmp(e->lines[0].text, initval)) e->clear(initval); + } + e->linewrap = (length<0); + e->maxx = (e->linewrap) ? -1 : length; + e->maxy = (height<=0)?1:-1; + e->pixelwidth = abs(length)*FONTW; + if(e->linewrap && e->maxy==1) + { + int temp; + text_bounds(e->lines[0].text, temp, e->pixelheight, e->pixelwidth); //only single line editors can have variable height + } + else + e->pixelheight = FONTH*max(height, 1); + } + int h = e->pixelheight; + int w = e->pixelwidth + FONTW; + + bool wasvertical = isvertical(); + if(wasvertical && e->maxy != 1) pushlist(); + + char *result = NULL; + if(visible() && !layoutpass) + { + e->rendered = true; + + bool hit = ishit(w, h); + if(hit) + { + if(mousebuttons&G3D_DOWN) //mouse request focus + { + if(fieldtype==FIELDKEY) e->clear(); + useeditor(name, initmode, true); + e->mark(false); + fieldmode = fieldtype; + } + } + bool editing = (fieldmode != FIELDSHOW) && (e==currentfocus()); + if(hit && editing && (mousebuttons&G3D_PRESSED)!=0 && fieldtype==FIELDEDIT) e->hit(int(floor(hitx-(curx+FONTW/2))), int(floor(hity-cury)), (mousebuttons&G3D_DRAGGED)!=0); //mouse request position + if(editing && ((fieldmode==FIELDCOMMIT) || (fieldmode==FIELDABORT) || !hit)) // commit field if user pressed enter or wandered out of focus + { + if(fieldmode==FIELDCOMMIT || (fieldmode!=FIELDABORT && !hit)) result = e->currentline().text; + e->active = (e->mode!=EDITORFOCUSED); + fieldmode = FIELDSHOW; + } + else fieldsactive = true; + + e->draw(curx+FONTW/2, cury, color, hit && editing); + + hudnotextureshader->set(); + glDisable(GL_BLEND); + if(editing) gle::colorf(1, 0, 0); + else gle::colorub(color>>16, (color>>8)&0xFF, color&0xFF); + rect_(curx, cury, w, h, true); + glEnable(GL_BLEND); + hudshader->set(); + } + layout(w, h); + + if(e->maxy != 1) + { + int slines = e->limitscrolly(); + if(slines > 0) + { + int pos = e->scrolly; + slider(e->scrolly, slines, 0, color, NULL); + if(pos != e->scrolly) e->cy = e->scrolly; + } + if(wasvertical) poplist(); + } + + return result; + } + + void rect_(float x, float y, float w, float h, bool lines = false) + { + gle::defvertex(2); + gle::begin(lines ? GL_LINE_LOOP : GL_TRIANGLE_STRIP); + gle::attribf(x, y); + gle::attribf(x + w, y); + if(lines) gle::attribf(x + w, y + h); + gle::attribf(x, y + h); + if(!lines) gle::attribf(x + w, y + h); + xtraverts += gle::end(); + } + + void rect_(float x, float y, float w, float h, int usetc) + { + gle::defvertex(2); + gle::deftexcoord0(); + gle::begin(GL_TRIANGLE_STRIP); + static const vec2 tc[5] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1), vec2(0, 0) }; + gle::attribf(x, y); gle::attrib(tc[usetc]); + gle::attribf(x + w, y); gle::attrib(tc[usetc+1]); + gle::attribf(x, y + h); gle::attrib(tc[usetc+3]); + gle::attribf(x + w, y + h); gle::attrib(tc[usetc+2]); + xtraverts += gle::end(); + } + + void text_(const char *text, int x, int y, int color, bool shadow, bool force = false) + { + if(shadow) draw_text(text, x+SHADOW, y+SHADOW, 0x00, 0x00, 0x00, -0xC0); + draw_text(text, x, y, color>>16, (color>>8)&0xFF, color&0xFF, force ? -0xFF : 0xFF); + } + + void background(int color, int inheritw, int inherith) + { + if(layoutpass) return; + hudnotextureshader->set(); + gle::colorub(color>>16, (color>>8)&0xFF, color&0xFF, 0x80); + int w = xsize, h = ysize; + if(inheritw>0) + { + int parentw = curlist, parentdepth = 0; + for(;parentdepth < inheritw && lists[parentw].parent>=0; parentdepth++) + parentw = lists[parentw].parent; + list &p = lists[parentw]; + w = p.springs > 0 && (curdepth-parentdepth)&1 ? lists[p.parent].w : p.w; + } + if(inherith>0) + { + int parenth = curlist, parentdepth = 0; + for(;parentdepth < inherith && lists[parenth].parent>=0; parentdepth++) + parenth = lists[parenth].parent; + list &p = lists[parenth]; + h = p.springs > 0 && !((curdepth-parentdepth)&1) ? lists[p.parent].h : p.h; + } + rect_(curx, cury, w, h); + hudshader->set(); + } + + void icon_(Texture *t, bool overlaid, int x, int y, int size, bool hit, const char *title = NULL) + { + float scale = float(size)/max(t->xs, t->ys); //scale and preserve aspect ratio + float xs = t->xs*scale, ys = t->ys*scale; + x += int((size-xs)/2); + y += int((size-ys)/2); + const vec &color = hit ? vec(1, 0.5f, 0.5f) : (overlaid ? vec(1, 1, 1) : light); + glBindTexture(GL_TEXTURE_2D, t->id); + if(hit && actionon) + { + gle::colorf(0, 0, 0, 0.75f); + rect_(x+SHADOW, y+SHADOW, xs, ys, 0); + } + gle::color(color); + rect_(x, y, xs, ys, 0); + + if(overlaid) + { + if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); + glBindTexture(GL_TEXTURE_2D, overlaytex->id); + gle::color(light); + rect_(x, y, xs, ys, 0); + if(title) text_(title, x + xs/12, y + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit && actionon, hit); + } + } + + void previewslot(VSlot &vslot, bool overlaid, int x, int y, int size, bool hit) + { + Slot &slot = *vslot.slot; + if(slot.sts.empty()) return; + VSlot *layer = NULL; + Texture *t = NULL, *glowtex = NULL, *layertex = NULL; + if(slot.loaded) + { + t = slot.sts[0].t; + if(t == notexture) return; + Slot &slot = *vslot.slot; + if(slot.texmask&(1<slot->sts.empty()) layertex = layer->slot->sts[0].t; + } + } + else if(slot.thumbnail && slot.thumbnail != notexture) t = slot.thumbnail; + else return; + float xt = min(1.0f, t->xs/(float)t->ys), yt = min(1.0f, t->ys/(float)t->xs), xs = size, ys = size; + if(hit && actionon) + { + hudnotextureshader->set(); + gle::colorf(0, 0, 0, 0.75f); + rect_(x+SHADOW, y+SHADOW, xs, ys); + hudshader->set(); + } + SETSHADER(hudrgb); + gle::defvertex(2); + gle::deftexcoord0(); + const vec &color = hit ? vec(1, 0.5f, 0.5f) : (overlaid ? vec(1, 1, 1) : light); + vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; + float xoff = vslot.offset.x, yoff = vslot.offset.y; + if(vslot.rotation) + { + const texrotation &r = texrotations[vslot.rotation]; + if(r.swapxy) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); } + if(r.flipx) { xoff *= -1; loopk(4) tc[k].x *= -1; } + if(r.flipy) { yoff *= -1; loopk(4) tc[k].y *= -1; } + } + loopk(4) { tc[k].x = tc[k].x/xt - xoff/t->xs; tc[k].y = tc[k].y/yt - yoff/t->ys; } + if(slot.loaded) gle::color(vec(color).mul(vslot.colorscale)); + else gle::color(color); + glBindTexture(GL_TEXTURE_2D, t->id); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x, y); gle::attrib(tc[0]); + gle::attribf(x+xs, y); gle::attrib(tc[1]); + gle::attribf(x, y+ys); gle::attrib(tc[3]); + gle::attribf(x+xs, y+ys); gle::attrib(tc[2]); + gle::end(); + if(glowtex) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBindTexture(GL_TEXTURE_2D, glowtex->id); + if(hit || overlaid) gle::color(vec(vslot.glowcolor).mul(color)); + else gle::color(vslot.glowcolor); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x, y); gle::attrib(tc[0]); + gle::attribf(x+xs, y); gle::attrib(tc[1]); + gle::attribf(x, y+ys); gle::attrib(tc[3]); + gle::attribf(x+xs, y+ys); gle::attrib(tc[2]); + gle::end(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + if(layertex) + { + glBindTexture(GL_TEXTURE_2D, layertex->id); + gle::color(vec(color).mul(layer->colorscale)); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x+xs/2, y+ys/2); gle::attrib(tc[0]); + gle::attribf(x+xs, y+ys/2); gle::attrib(tc[1]); + gle::attribf(x+xs/2, y+ys); gle::attrib(tc[3]); + gle::attribf(x+xs, y+ys); gle::attrib(tc[2]); + gle::end(); + } + + hudshader->set(); + if(overlaid) + { + if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3); + glBindTexture(GL_TEXTURE_2D, overlaytex->id); + gle::color(light); + rect_(x, y, xs, ys, 0); + } + } + + void line_(int size, float percent = 1.0f) + { + if(visible()) + { + if(!slidertex) slidertex = textureload("data/guislider.png", 3); + glBindTexture(GL_TEXTURE_2D, slidertex->id); + if(percent < 0.99f) + { + gle::colorf(light.x, light.y, light.z, 0.375f); + if(ishorizontal()) + rect_(curx + FONTH/2 - size/2, cury, size, ysize, 0); + else + rect_(curx, cury + FONTH/2 - size/2, xsize, size, 1); + } + gle::color(light); + if(ishorizontal()) + rect_(curx + FONTH/2 - size/2, cury + ysize*(1-percent), size, ysize*percent, 0); + else + rect_(curx, cury + FONTH/2 - size/2, xsize*percent, size, 1); + } + layout(ishorizontal() ? FONTH : 0, ishorizontal() ? 0 : FONTH); + } + + void textbox(const char *text, int width, int height, int color) + { + width *= FONTW; + height *= FONTH; + int w, h; + text_bounds(text, w, h, width); + if(h > height) height = h; + if(visible()) draw_text(text, curx, cury, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, -1, width); + layout(width, height); + } + + int button_(const char *text, int color, const char *icon, bool clickable, bool center) + { + const int padding = 10; + int w = 0; + if(icon) w += ICON_SIZE; + if(icon && text) w += padding; + if(text) w += text_width(text); + + if(visible()) + { + bool hit = ishit(w, FONTH); + if(hit && clickable) color = 0xFF0000; + int x = curx; + if(isvertical() && center) x += (xsize-w)/2; + + if(icon) + { + if(icon[0] != ' ') + { + const char *ext = strrchr(icon, '.'); + defformatstring(tname, "packages/icons/%s%s", icon, ext ? "" : ".png"); + icon_(textureload(tname, 3), false, x, cury, ICON_SIZE, clickable && hit); + } + x += ICON_SIZE; + } + if(icon && text) x += padding; + if(text) text_(text, x, cury, color, center || (hit && clickable && actionon), hit && clickable); + } + return layout(w, FONTH); + } + + static Texture *skintex, *overlaytex, *slidertex; + static const int skinx[], skiny[]; + static const struct patch { ushort left, right, top, bottom; uchar flags; } patches[]; + + static void drawskin(int x, int y, int gapw, int gaph, int start, int n, int passes = 1, const vec &light = vec(1, 1, 1), float alpha = 0.80f)//int vleft, int vright, int vtop, int vbottom, int start, int n) + { + if(!skintex) skintex = textureload("data/guiskin.png", 3); + glBindTexture(GL_TEXTURE_2D, skintex->id); + int gapx1 = INT_MAX, gapy1 = INT_MAX, gapx2 = INT_MAX, gapy2 = INT_MAX; + float wscale = 1.0f/(SKIN_W*SKIN_SCALE), hscale = 1.0f/(SKIN_H*SKIN_SCALE); + + loopj(passes) + { + bool quads = false; + if(passes>1) glDepthFunc(j ? GL_LEQUAL : GL_GREATER); + gle::color(j ? light : vec(1, 1, 1), passes<=1 || j ? alpha : alpha/2); //ghost when its behind something in depth + loopi(n) + { + const patch &p = patches[start+i]; + int left = skinx[p.left]*SKIN_SCALE, right = skinx[p.right]*SKIN_SCALE, + top = skiny[p.top]*SKIN_SCALE, bottom = skiny[p.bottom]*SKIN_SCALE; + float tleft = left*wscale, tright = right*wscale, + ttop = top*hscale, tbottom = bottom*hscale; + if(p.flags&0x1) + { + gapx1 = left; + gapx2 = right; + } + else if(left >= gapx2) + { + left += gapw - (gapx2-gapx1); + right += gapw - (gapx2-gapx1); + } + if(p.flags&0x10) + { + gapy1 = top; + gapy2 = bottom; + } + else if(top >= gapy2) + { + top += gaph - (gapy2-gapy1); + bottom += gaph - (gapy2-gapy1); + } + + //multiple tiled quads if necessary rather than a single stretched one + int ystep = bottom-top; + int yo = y+top; + while(ystep > 0) + { + if(p.flags&0x10 && yo+ystep-(y+top) > gaph) + { + ystep = gaph+y+top-yo; + tbottom = ttop+ystep*hscale; + } + int xstep = right-left; + int xo = x+left; + float tright2 = tright; + while(xstep > 0) + { + if(p.flags&0x01 && xo+xstep-(x+left) > gapw) + { + xstep = gapw+x+left-xo; + tright = tleft+xstep*wscale; + } + if(!quads) + { + quads = true; + gle::defvertex(2); + gle::deftexcoord0(); + gle::begin(GL_QUADS); + } + gle::attribf(xo, yo); gle::attribf(tleft, ttop); + gle::attribf(xo+xstep, yo); gle::attribf(tright, ttop); + gle::attribf(xo+xstep, yo+ystep); gle::attribf(tright, tbottom); + gle::attribf(xo, yo+ystep); gle::attribf(tleft, tbottom); + if(!(p.flags&0x01)) break; + xo += xstep; + } + tright = tright2; + if(!(p.flags&0x10)) break; + yo += ystep; + } + } + if(quads) xtraverts += gle::end(); + else break; //if it didn't happen on the first pass, it won't happen on the second.. + } + if(passes>1) glDepthFunc(GL_ALWAYS); + } + + vec origin, scale, *savedorigin; + float dist; + g3d_callback *cb; + bool gui2d; + + static float basescale, maxscale; + static bool passthrough; + static float alpha; + static vec light; + + void adjustscale() + { + int w = xsize + (skinx[2]-skinx[1])*SKIN_SCALE + (skinx[10]-skinx[9])*SKIN_SCALE, h = ysize + (skiny[9]-skiny[7])*SKIN_SCALE; + if(tcurrent) h += ((skiny[5]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE + FONTH-2*INSERT; + else h += (skiny[6]-skiny[3])*SKIN_SCALE; + + float aspect = forceaspect ? 1.0f/forceaspect : float(screenh)/float(screenw), fit = 1.0f; + if(w*aspect*basescale>1.0f) fit = 1.0f/(w*aspect*basescale); + if(h*basescale*fit>maxscale) fit *= maxscale/(h*basescale*fit); + origin = vec(0.5f-((w-xsize)/2 - (skinx[2]-skinx[1])*SKIN_SCALE)*aspect*scale.x*fit, 0.5f + (0.5f*h-(skiny[9]-skiny[7])*SKIN_SCALE)*scale.y*fit, 0); + scale = vec(aspect*scale.x*fit, scale.y*fit, 1); + } + + void start(int starttime, float initscale, int *tab, bool allowinput) + { + if(gui2d) + { + initscale *= 0.025f; + if(allowinput) hascursor = true; + } + basescale = initscale; + if(layoutpass) scale.x = scale.y = scale.z = guifadein ? basescale*min((totalmillis-starttime)/300.0f, 1.0f) : basescale; + alpha = allowinput ? 0.80f : 0.60f; + passthrough = scale.xo.y, origin.x-camera1->o.x); + hudmatrix = camprojmatrix; + hudmatrix.translate(origin); + hudmatrix.rotate_around_z(yaw - 90*RAD); + hudmatrix.rotate_around_x(-90*RAD); + hudmatrix.scale(-scale.x, scale.y, scale.z); + + vec dir; + lightreaching(origin, light, dir, false, 0, 0.5f); + float intensity = vec(yaw, 0.0f).dot(dir); + light.mul(1.0f + max(intensity, 0.0f)); + } + + resethudmatrix(); + hudshader->set(); + + drawskin(curx-skinx[2]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, ysize, 0, 9, gui2d ? 1 : 2, light, alpha); + if(!tcurrent) drawskin(curx-skinx[5]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, 0, 9, 1, gui2d ? 1 : 2, light, alpha); + } + } + + void adjusthorizontalcolumn(int col, int i) + { + int h = columns[col], dh = 0; + for(int d = 1; i >= 0; d ^= 1) + { + list &p = lists[i]; + if(d&1) { dh = h - p.h; if(dh <= 0) break; p.h = h; } + else { p.h += dh; h = p.h; } + i = p.parent; + } + ysize += max(dh, 0); + } + + void adjustverticalcolumn(int col, int i) + { + int w = columns[col], dw = 0; + for(int d = 0; i >= 0; d ^= 1) + { + list &p = lists[i]; + if(d&1) { p.w += dw; w = p.w; } + else { dw = w - p.w; if(dw <= 0) break; p.w = w; } + i = p.parent; + } + xsize = max(xsize, w); + } + + void adjustcolumns() + { + if(lists.inrange(curlist)) + { + list &l = lists[curlist]; + if(l.column >= 0) columns[l.column] = max(columns[l.column], ishorizontal() ? ysize : xsize); + } + int parent = -1, depth = 0; + for(int i = firstlist; i < lists.length(); i++) + { + list &l = lists[i]; + if(l.parent > parent) { parent = l.parent; depth++; } + else if(l.parent < parent) + { + while(parent > l.parent && depth > 0) + { + parent = lists[parent].parent; + depth--; + } + } + if(l.column >= 0) + { + if(depth&1) adjusthorizontalcolumn(l.column, i); + else adjustverticalcolumn(l.column, i); + } + } + } + + void end() + { + if(layoutpass) + { + adjustcolumns(); + xsize = max(tx, xsize); + ysize = max(ty, ysize); + ysize = max(ysize, (skiny[7]-skiny[6])*SKIN_SCALE); + if(tcurrent) *tcurrent = max(1, min(*tcurrent, tpos)); + if(gui2d) adjustscale(); + if(!windowhit && !passthrough) + { + float dist = 0; + if(gui2d) + { + hitx = (cursorx - origin.x)/scale.x; + hity = (cursory - origin.y)/scale.y; + } + else + { + plane p; + p.toplane(vec(origin).sub(camera1->o).set(2, 0).normalize(), origin); + if(p.rayintersect(camera1->o, camdir, dist) && dist>=0) + { + vec hitpos(camdir); + hitpos.mul(dist).add(camera1->o).sub(origin); + hitx = vec(-p.y, p.x, 0).dot(hitpos)/scale.x; + hity = -hitpos.z/scale.y; + } + } + if((mousebuttons & G3D_PRESSED) && (fabs(hitx-firstx) > 2 || fabs(hity - firsty) > 2)) mousebuttons |= G3D_DRAGGED; + if(dist>=0 && hitx>=-xsize/2 && hitx<=xsize/2 && hity<=0) + { + if(hity>=-ysize || (tcurrent && hity>=-ysize-(FONTH-2*INSERT)-((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE && hitx<=tx-xsize/2)) + windowhit = this; + } + } + } + else + { + if(tcurrent && txgui(*this, layoutpass); + } }; Texture *gui::skintex = NULL, *gui::overlaytex = NULL, *gui::slidertex = NULL; //chop skin into a grid const int gui::skiny[] = {0, 7, 21, 34, 43, 48, 56, 104, 111, 117, 128}, - gui::skinx[] = {0, 11, 23, 37, 105, 119, 137, 151, 215, 229, 246, 256}; + gui::skinx[] = {0, 11, 23, 37, 105, 119, 137, 151, 215, 229, 246, 256}; //Note: skinx[3]-skinx[2] = skinx[7]-skinx[6] -// skinx[5]-skinx[4] = skinx[9]-skinx[8] +// skinx[5]-skinx[4] = skinx[9]-skinx[8] const gui::patch gui::patches[] = { //arguably this data can be compressed - it depends on what else needs to be skinned in the future - {1,2,3,6, 0}, // body - {2,9,5,6, 0x01}, - {9,10,3,6, 0}, - - {1,2,6,7, 0x10}, - {2,9,6,7, 0x11}, - {9,10,6,7, 0x10}, - - {1,2,7,9, 0}, - {2,9,7,9, 0x01}, - {9,10,7,9, 0}, - - {5,6,3,5, 0x01}, // top - - {2,3,1,2, 0}, // selected tab - {3,4,1,2, 0x01}, - {4,5,1,2, 0}, - {2,3,2,3, 0x10}, - {3,4,2,3, 0x11}, - {4,5,2,3, 0x10}, - {2,3,3,5, 0}, - {3,4,3,5, 0x01}, - {4,5,3,5, 0}, - - {6,7,1,2, 0}, // deselected tab - {7,8,1,2, 0x01}, - {8,9,1,2, 0}, - {6,7,2,3, 0x10}, - {7,8,2,3, 0x11}, - {8,9,2,3, 0x10}, - {6,7,3,5, 0}, - {7,8,3,5, 0x01}, - {8,9,3,5, 0}, + {1,2,3,6, 0}, // body + {2,9,5,6, 0x01}, + {9,10,3,6, 0}, + + {1,2,6,7, 0x10}, + {2,9,6,7, 0x11}, + {9,10,6,7, 0x10}, + + {1,2,7,9, 0}, + {2,9,7,9, 0x01}, + {9,10,7,9, 0}, + + {5,6,3,5, 0x01}, // top + + {2,3,1,2, 0}, // selected tab + {3,4,1,2, 0x01}, + {4,5,1,2, 0}, + {2,3,2,3, 0x10}, + {3,4,2,3, 0x11}, + {4,5,2,3, 0x10}, + {2,3,3,5, 0}, + {3,4,3,5, 0x01}, + {4,5,3,5, 0}, + + {6,7,1,2, 0}, // deselected tab + {7,8,1,2, 0x01}, + {8,9,1,2, 0}, + {6,7,2,3, 0x10}, + {7,8,2,3, 0x11}, + {8,9,2,3, 0x10}, + {6,7,3,5, 0}, + {7,8,3,5, 0x01}, + {8,9,3,5, 0}, }; vector gui::lists; @@ -1164,117 +1164,117 @@ VARP(guipushdist, 1, 4, 64); bool g3d_input(const char *str, int len) { - editor *e = currentfocus(); - if(fieldmode == FIELDKEY || fieldmode == FIELDSHOW || !e) return false; + editor *e = currentfocus(); + if(fieldmode == FIELDKEY || fieldmode == FIELDSHOW || !e) return false; - e->input(str, len); - return true; + e->input(str, len); + return true; } bool g3d_key(int code, bool isdown) { - editor *e = currentfocus(); - if(fieldmode == FIELDKEY) - { - switch(code) - { - case SDLK_ESCAPE: - if(isdown) fieldmode = FIELDCOMMIT; - return true; - } - const char *keyname = getkeyname(code); - if(keyname && isdown) - { - if(e->lines.length()!=1 || !e->lines[0].empty()) e->insert(" "); - e->insert(keyname); - } - return true; - } - - if(code==-1 && g3d_windowhit(isdown, true)) return true; - else if(code==-3 && g3d_windowhit(isdown, false)) return true; - - if(fieldmode == FIELDSHOW || !e) - { - if(windowhit) switch(code) - { - case -4: // window "management" - if(isdown) - { - if(windowhit->gui2d) - { - vec origin = *guis2d.last().savedorigin; - int i = windowhit - &guis2d[0]; - for(int j = guis2d.length()-1; j > i; j--) *guis2d[j].savedorigin = *guis2d[j-1].savedorigin; - *windowhit->savedorigin = origin; - if(guis2d.length() > 1) - { - if(camera1->o.dist(*windowhit->savedorigin) <= camera1->o.dist(*guis2d.last().savedorigin)) - windowhit->savedorigin->add(camdir); - } - } - else windowhit->savedorigin->add(vec(camdir).mul(guipushdist)); - } - return true; - case -5: - if(isdown) - { - if(windowhit->gui2d) - { - vec origin = *guis2d[0].savedorigin; - loopj(guis2d.length()-1) *guis2d[j].savedorigin = *guis2d[j + 1].savedorigin; - *guis2d.last().savedorigin = origin; - if(guis2d.length() > 1) - { - if(camera1->o.dist(*guis2d.last().savedorigin) >= camera1->o.dist(*guis2d[0].savedorigin)) - guis2d.last().savedorigin->sub(camdir); - } - } - else windowhit->savedorigin->sub(vec(camdir).mul(guipushdist)); - } - return true; - } - - return false; - } - switch(code) - { - case SDLK_ESCAPE: //cancel editing without commit - if(isdown) fieldmode = FIELDABORT; - return true; - case SDLK_RETURN: - [[fallthrough]]; - case SDLK_TAB: - if(e->maxy != 1) break; - [[fallthrough]]; - case SDLK_KP_ENTER: - if(isdown) fieldmode = FIELDCOMMIT; //signal field commit (handled when drawing field) - return true; - } - if(isdown) e->key(code); - return true; + editor *e = currentfocus(); + if(fieldmode == FIELDKEY) + { + switch(code) + { + case SDLK_ESCAPE: + if(isdown) fieldmode = FIELDCOMMIT; + return true; + } + const char *keyname = getkeyname(code); + if(keyname && isdown) + { + if(e->lines.length()!=1 || !e->lines[0].empty()) e->insert(" "); + e->insert(keyname); + } + return true; + } + + if(code==-1 && g3d_windowhit(isdown, true)) return true; + else if(code==-3 && g3d_windowhit(isdown, false)) return true; + + if(fieldmode == FIELDSHOW || !e) + { + if(windowhit) switch(code) + { + case -4: // window "management" + if(isdown) + { + if(windowhit->gui2d) + { + vec origin = *guis2d.last().savedorigin; + int i = windowhit - &guis2d[0]; + for(int j = guis2d.length()-1; j > i; j--) *guis2d[j].savedorigin = *guis2d[j-1].savedorigin; + *windowhit->savedorigin = origin; + if(guis2d.length() > 1) + { + if(camera1->o.dist(*windowhit->savedorigin) <= camera1->o.dist(*guis2d.last().savedorigin)) + windowhit->savedorigin->add(camdir); + } + } + else windowhit->savedorigin->add(vec(camdir).mul(guipushdist)); + } + return true; + case -5: + if(isdown) + { + if(windowhit->gui2d) + { + vec origin = *guis2d[0].savedorigin; + loopj(guis2d.length()-1) *guis2d[j].savedorigin = *guis2d[j + 1].savedorigin; + *guis2d.last().savedorigin = origin; + if(guis2d.length() > 1) + { + if(camera1->o.dist(*guis2d.last().savedorigin) >= camera1->o.dist(*guis2d[0].savedorigin)) + guis2d.last().savedorigin->sub(camdir); + } + } + else windowhit->savedorigin->sub(vec(camdir).mul(guipushdist)); + } + return true; + } + + return false; + } + switch(code) + { + case SDLK_ESCAPE: //cancel editing without commit + if(isdown) fieldmode = FIELDABORT; + return true; + case SDLK_RETURN: + [[fallthrough]]; + case SDLK_TAB: + if(e->maxy != 1) break; + [[fallthrough]]; + case SDLK_KP_ENTER: + if(isdown) fieldmode = FIELDCOMMIT; //signal field commit (handled when drawing field) + return true; + } + if(isdown) e->key(code); + return true; } void g3d_cursorpos(float &x, float &y) { - if(guis2d.length()) { x = cursorx; y = cursory; } - else x = y = 0.5f; + if(guis2d.length()) { x = cursorx; y = cursory; } + else x = y = 0.5f; } void g3d_resetcursor() { - cursorx = cursory = 0.5f; + cursorx = cursory = 0.5f; } FVARP(guisens, 1e-3f, 1, 1e3f); bool g3d_movecursor(int dx, int dy) { - if(!guis2d.length() || !hascursor) return false; - const float CURSORSCALE = 500.0f; - cursorx = max(0.0f, min(1.0f, cursorx+guisens*dx*(screenh/(screenw*CURSORSCALE)))); - cursory = max(0.0f, min(1.0f, cursory+guisens*dy/CURSORSCALE)); - return true; + if(!guis2d.length() || !hascursor) return false; + const float CURSORSCALE = 500.0f; + cursorx = max(0.0f, min(1.0f, cursorx+guisens*dx*(screenh/(screenw*CURSORSCALE)))); + cursory = max(0.0f, min(1.0f, cursory+guisens*dy/CURSORSCALE)); + return true; } VARNP(guifollow, useguifollow, 0, 1, 1); @@ -1282,119 +1282,119 @@ VARNP(gui2d, usegui2d, 0, 1, 1); void g3d_addgui(g3d_callback *cb, vec &origin, int flags) { - bool gui2d = flags&GUI_FORCE_2D || (flags&GUI_2D && usegui2d) || mainmenu; - if(!gui2d && flags&GUI_FOLLOW && useguifollow) origin.z = player->o.z-(player->eyeheight-1); - gui &g = (gui2d ? guis2d : guis3d).add(); - g.cb = cb; - g.origin = origin; - g.savedorigin = &origin; - g.dist = flags&GUI_BOTTOM && gui2d ? 1e16f : camera1->o.dist(g.origin); - g.gui2d = gui2d; + bool gui2d = flags&GUI_FORCE_2D || (flags&GUI_2D && usegui2d) || mainmenu; + if(!gui2d && flags&GUI_FOLLOW && useguifollow) origin.z = player->o.z-(player->eyeheight-1); + gui &g = (gui2d ? guis2d : guis3d).add(); + g.cb = cb; + g.origin = origin; + g.savedorigin = &origin; + g.dist = flags&GUI_BOTTOM && gui2d ? 1e16f : camera1->o.dist(g.origin); + g.gui2d = gui2d; } void g3d_limitscale(float scale) { - gui::maxscale = scale; + gui::maxscale = scale; } static inline bool g3d_sort(const gui &a, const gui &b) { return a.dist < b.dist; } bool g3d_windowhit(bool on, bool act) { - extern int cleargui(int n); - if(act) - { - if(actionon || windowhit) - { - if(on) { firstx = gui::hitx; firsty = gui::hity; } - mousebuttons |= (actionon=on) ? G3D_DOWN : G3D_UP; - } - } else if(!on && windowhit) cleargui(1); - return (guis2d.length() && hascursor) || (windowhit && !windowhit->gui2d); + extern int cleargui(int n); + if(act) + { + if(actionon || windowhit) + { + if(on) { firstx = gui::hitx; firsty = gui::hity; } + mousebuttons |= (actionon=on) ? G3D_DOWN : G3D_UP; + } + } else if(!on && windowhit) cleargui(1); + return (guis2d.length() && hascursor) || (windowhit && !windowhit->gui2d); } void g3d_render() { - windowhit = NULL; - if(actionon) mousebuttons |= G3D_PRESSED; + windowhit = NULL; + if(actionon) mousebuttons |= G3D_PRESSED; - gui::reset(); - guis2d.shrink(0); - guis3d.shrink(0); + gui::reset(); + guis2d.shrink(0); + guis3d.shrink(0); - // call all places in the engine that may want to render a gui from here, they call g3d_addgui() - extern void g3d_texturemenu(); + // call all places in the engine that may want to render a gui from here, they call g3d_addgui() + extern void g3d_texturemenu(); - if(!mainmenu) g3d_texturemenu(); - g3d_mainmenu(); - if(!mainmenu) game::g3d_gamemenus(); + if(!mainmenu) g3d_texturemenu(); + g3d_mainmenu(); + if(!mainmenu) game::g3d_gamemenus(); - guis2d.sort(g3d_sort); - guis3d.sort(g3d_sort); + guis2d.sort(g3d_sort); + guis3d.sort(g3d_sort); - readyeditors(); - fieldsactive = false; + readyeditors(); + fieldsactive = false; - hascursor = false; + hascursor = false; - layoutpass = true; - loopv(guis2d) guis2d[i].draw(); - loopv(guis3d) guis3d[i].draw(); - layoutpass = false; + layoutpass = true; + loopv(guis2d) guis2d[i].draw(); + loopv(guis3d) guis3d[i].draw(); + layoutpass = false; - if(guis3d.length()) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if(guis3d.length()) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_ALWAYS); - glDepthMask(GL_FALSE); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_ALWAYS); + glDepthMask(GL_FALSE); - loopvrev(guis3d) guis3d[i].draw(); + loopvrev(guis3d) guis3d[i].draw(); - glDepthFunc(GL_LESS); - glDepthMask(GL_TRUE); - glDisable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + glDisable(GL_DEPTH_TEST); - glDisable(GL_BLEND); - } + glDisable(GL_BLEND); + } } void g3d_render2d() { - if(guis2d.length()) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if(guis2d.length()) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - loopvrev(guis2d) guis2d[i].draw(); + loopvrev(guis2d) guis2d[i].draw(); - glDisable(GL_BLEND); - } + glDisable(GL_BLEND); + } - flusheditors(); - if(!fieldsactive) fieldmode = FIELDSHOW; //didn't draw any fields, so lose focus - mainly for menu closed - textinput(fieldmode!=FIELDSHOW, TI_GUI); - keyrepeat(fieldmode!=FIELDSHOW, KR_GUI); + flusheditors(); + if(!fieldsactive) fieldmode = FIELDSHOW; //didn't draw any fields, so lose focus - mainly for menu closed + textinput(fieldmode!=FIELDSHOW, TI_GUI); + keyrepeat(fieldmode!=FIELDSHOW, KR_GUI); - mousebuttons = 0; + mousebuttons = 0; } void consolebox(int x1, int y1, int x2, int y2) { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - float bw = x2 - x1, bh = y2 - y1, aspect = bw/bh, sh = bh, sw = sh*aspect; - bw *= float(4*FONTH)/(SKIN_H*SKIN_SCALE); - bh *= float(4*FONTH)/(SKIN_H*SKIN_SCALE); - sw /= bw + (gui::skinx[2]-gui::skinx[1] + gui::skinx[10]-gui::skinx[9])*SKIN_SCALE; - sh /= bh + (gui::skiny[9]-gui::skiny[7] + gui::skiny[6]-gui::skiny[4])*SKIN_SCALE; - pushhudmatrix(); - hudmatrix.translate(x1, y1, 0); - hudmatrix.scale(sw, sh, 1); - flushhudmatrix(); - gui::drawskin(-gui::skinx[1]*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), int(bh), 0, 9, 1, vec(1, 1, 1), 0.60f); - gui::drawskin((-gui::skinx[1] + gui::skinx[2] - gui::skinx[5])*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), 0, 9, 1, 1, vec(1, 1, 1), 0.60f); - pophudmatrix(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + float bw = x2 - x1, bh = y2 - y1, aspect = bw/bh, sh = bh, sw = sh*aspect; + bw *= float(4*FONTH)/(SKIN_H*SKIN_SCALE); + bh *= float(4*FONTH)/(SKIN_H*SKIN_SCALE); + sw /= bw + (gui::skinx[2]-gui::skinx[1] + gui::skinx[10]-gui::skinx[9])*SKIN_SCALE; + sh /= bh + (gui::skiny[9]-gui::skiny[7] + gui::skiny[6]-gui::skiny[4])*SKIN_SCALE; + pushhudmatrix(); + hudmatrix.translate(x1, y1, 0); + hudmatrix.scale(sw, sh, 1); + flushhudmatrix(); + gui::drawskin(-gui::skinx[1]*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), int(bh), 0, 9, 1, vec(1, 1, 1), 0.60f); + gui::drawskin((-gui::skinx[1] + gui::skinx[2] - gui::skinx[5])*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), 0, 9, 1, 1, vec(1, 1, 1), 0.60f); + pophudmatrix(); } diff --git a/src/engine/animmodel.h b/src/engine/animmodel.h index df30231..420d7f2 100644 --- a/src/engine/animmodel.h +++ b/src/engine/animmodel.h @@ -4,1426 +4,1427 @@ VARP(fullbrightmodels, 0, 0, 200); struct animmodel : model { - struct animspec - { - int frame, range; - float speed; - int priority; - }; - - struct animpos - { - int anim, fr1, fr2; - float t; - - void setframes(const animinfo &info) - { - anim = info.anim; - if(info.range<=1) - { - fr1 = 0; - t = 0; - } - else - { - int time = info.anim&ANIM_SETTIME ? info.basetime : lastmillis-info.basetime; - fr1 = (int)(time/info.speed); // round to full frames - t = (time-fr1*info.speed)/info.speed; // progress of the frame, value from 0.0f to 1.0f - } - if(info.anim&ANIM_LOOP) - { - fr1 = fr1%info.range+info.frame; - fr2 = fr1+1; - if(fr2>=info.frame+info.range) fr2 = info.frame; - } - else - { - fr1 = min(fr1, info.range-1)+info.frame; - fr2 = min(fr1+1, info.frame+info.range-1); - } - if(info.anim&ANIM_REVERSE) - { - fr1 = (info.frame+info.range-1)-(fr1-info.frame); - fr2 = (info.frame+info.range-1)-(fr2-info.frame); - } - } - - bool operator==(const animpos &a) const { return fr1==a.fr1 && fr2==a.fr2 && (fr1==fr2 || t==a.t); } - bool operator!=(const animpos &a) const { return fr1!=a.fr1 || fr2!=a.fr2 || (fr1!=fr2 && t!=a.t); } - }; - - struct part; - - struct animstate - { - part *owner; - animpos cur, prev; - float interp; - - bool operator==(const animstate &a) const { return cur==a.cur && (interp<1 ? interp==a.interp && prev==a.prev : a.interp>=1); } - bool operator!=(const animstate &a) const { return cur!=a.cur || (interp<1 ? interp!=a.interp || prev!=a.prev : a.interp<1); } - }; - - struct linkedpart; - struct mesh; - - struct shaderparams - { - float spec, ambient, glow, glowdelta, glowpulse, specglare, glowglare, fullbright, envmapmin, envmapmax, scrollu, scrollv, alphatest; - - shaderparams() : spec(1.0f), ambient(0.3f), glow(3.0f), glowdelta(0), glowpulse(0), specglare(1), glowglare(1), fullbright(0), envmapmin(0), envmapmax(0), scrollu(0), scrollv(0), alphatest(0.9f) {} - }; - - struct shaderparamskey - { - static hashtable keys; - static int firstversion, lastversion; - - int version; - - shaderparamskey() : version(-1) {} - - bool checkversion() - { - if(version >= firstversion) return true; - version = lastversion; - if(++lastversion <= 0) - { - enumerate(keys, shaderparamskey, key, key.version = -1); - firstversion = 0; - lastversion = 1; - version = 0; - } - return false; - } - - static inline void invalidate() - { - firstversion = lastversion; - } - }; - - struct skin : shaderparams - { - part *owner; - Texture *tex, *masks, *envmap, *normalmap; - Shader *shader; - bool alphablend, cullface; - shaderparamskey *key; - - skin() : owner(0), tex(notexture), masks(notexture), envmap(NULL), normalmap(NULL), shader(NULL), alphablend(true), cullface(true), key(NULL) {} - - bool masked() const { return masks != notexture; } - bool envmapped() { return envmapmax>0 && envmapmodels; } - bool bumpmapped() { return normalmap && bumpmodels; } - bool tangents() { return bumpmapped(); } - bool alphatested() const { return alphatest > 0 && tex->type&Texture::ALPHA; } - - void setkey() - { - key = &shaderparamskey::keys[*this]; - } - - void setshaderparams(mesh *m, const animstate *as) - { - if(!Shader::lastshader) return; - - float mincolor = as->cur.anim&ANIM_FULLBRIGHT ? fullbrightmodels/100.0f : 0.0f; - if(fullbright) - { - gle::colorf(fullbright/2, fullbright/2, fullbright/2, transparent); - } - else - { - gle::color(vec(lightcolor).max(mincolor), transparent); - } - - if(key->checkversion() && Shader::lastshader->owner == key) return; - Shader::lastshader->owner = key; - - if(alphatested()) LOCALPARAMF(alphatest, alphatest); - - if(fullbright) - { - LOCALPARAMF(lightscale, 0, 0, 2); - } - else - { - float bias = max(mincolor-1.0f, 0.2f), scale = 0.5f*max(0.8f-bias, 0.0f), - minshade = scale*max(ambient, mincolor); - LOCALPARAMF(lightscale, scale - minshade, scale, minshade + bias); - } - float curglow = glow; - if(glowpulse > 0) - { - float curpulse = lastmillis*glowpulse; - curpulse -= floor(curpulse); - curglow += glowdelta*2*fabs(curpulse - 0.5f); - } - LOCALPARAMF(maskscale, 0.5f*spec, 0.5f*curglow, 16*specglare, 4*glowglare); - LOCALPARAMF(texscroll, scrollu*lastmillis/1000.0f, scrollv*lastmillis/1000.0f); - if(envmapped()) LOCALPARAMF(envmapscale, envmapmin-envmapmax, envmapmax); - } - - Shader *loadshader() - { - #define DOMODELSHADER(name, body) \ - do { \ - static Shader *name##shader = NULL; \ - if(!name##shader) name##shader = useshaderbyname(#name); \ - body; \ - } while(0) - #define LOADMODELSHADER(name) DOMODELSHADER(name, return name##shader) - #define SETMODELSHADER(m, name) DOMODELSHADER(name, (m)->setshader(name##shader)) - if(shader) return shader; - - string opts; - int optslen = 0; - if(alphatested()) opts[optslen++] = 'a'; - if(owner->tangents()) opts[optslen++] = 'q'; - if(bumpmapped()) opts[optslen++] = 'n'; - if(envmapped()) opts[optslen++] = 'e'; - if(masked()) opts[optslen++] = 'm'; - if(!fullbright && (masked() || spec>=0.01f)) opts[optslen++] = 's'; - opts[optslen++] = '\0'; - - defformatstring(name, "model%s", opts); - shader = generateshader(name, "modelshader \"%s\"", opts); - return shader; - } - - void cleanup() - { - if(shader && shader->standard) shader = NULL; - } - - void preloadBIH() - { - if(tex->type&Texture::ALPHA && !tex->alphamask) loadalphamask(tex); - } - - void preloadshader(bool force) - { - if(force) cleanup(); - loadshader(); - } - - void setshader(mesh *m, const animstate *as) - { - m->setshader(loadshader()); - } - - void bind(mesh *b, const animstate *as) - { - if(!cullface && enablecullface) { glDisable(GL_CULL_FACE); enablecullface = false; } - else if(cullface && !enablecullface) { glEnable(GL_CULL_FACE); enablecullface = true; } - - if(as->cur.anim&ANIM_NOSKIN) - { - if(enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; } - if(shadowmapping) SETMODELSHADER(b, shadowmapcaster); - else /*if(as->cur.anim&ANIM_SHADOW)*/ SETMODELSHADER(b, notexturemodel); - return; - } - setshader(b, as); - setshaderparams(b, as); - int activetmu = 0; - if(tex!=lasttex) - { - glBindTexture(GL_TEXTURE_2D, tex->id); - lasttex = tex; - } - if(bumpmapped() && normalmap !=lastnormalmap) - { - glActiveTexture_(GL_TEXTURE3); - activetmu = 3; - glBindTexture(GL_TEXTURE_2D, normalmap->id); - lastnormalmap = normalmap; - } - if(tex->type&Texture::ALPHA) - { - if(alphablend) - { - if(!enablealphablend && !reflecting && !refracting) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - enablealphablend = true; - } - } - else if(enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; } - } - else if(enablealphablend && transparent>=1) { glDisable(GL_BLEND); enablealphablend = false; } - if(masked() && masks!=lastmasks) - { - glActiveTexture_(GL_TEXTURE1); - activetmu = 1; - glBindTexture(GL_TEXTURE_2D, masks->id); - lastmasks = masks; - } - if(envmapped()) - { - GLuint emtex = envmap ? envmap->id : closestenvmaptex; - if(lastenvmaptex!=emtex) - { - glActiveTexture_(GL_TEXTURE2); - activetmu = 2; - glBindTexture(GL_TEXTURE_CUBE_MAP, emtex); - lastenvmaptex = emtex; - } - } - if(activetmu != 0) glActiveTexture_(GL_TEXTURE0); - } - }; - - struct meshgroup; - - struct mesh - { - meshgroup *group; - char *name; - bool noclip; - - mesh() : group(NULL), name(NULL), noclip(false) - { - } - - virtual ~mesh() - { - DELETEA(name); - } - - virtual void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) {} - - virtual void genBIH(BIH::mesh &m) {} - void genBIH(skin &s, vector &bih, const matrix4x3 &t) - { - BIH::mesh &m = bih.add(); - m.xform = t; - m.tex = s.tex; - if(s.tex->type&Texture::ALPHA) m.flags |= BIH::MESH_ALPHA; - if(noclip) m.flags |= BIH::MESH_NOCLIP; - if(s.cullface) m.flags |= BIH::MESH_CULLFACE; - genBIH(m); - while(bih.last().numtris > BIH::mesh::MAXTRIS) - { - BIH::mesh &overflow = bih.dup(); - overflow.tris += BIH::mesh::MAXTRIS; - overflow.numtris -= BIH::mesh::MAXTRIS; - bih[bih.length()-2].numtris = BIH::mesh::MAXTRIS; - } - } - - virtual void setshader(Shader *s) - { - if(glaring) s->setvariant(0, 1); - else s->set(); - } - - template void smoothnorms(V *verts, int numverts, T *tris, int numtris, float limit, bool areaweight) - { - hashtable share; - int *next = new int[numverts]; - for(int i=0;i= 0) - { - float vlimit = limit*v.norm.magnitude(); - for(int j = next[i]; j >= 0; j = next[j]) - { - V &o = verts[j]; - if(v.norm.dot(o.norm) >= vlimit*o.norm.magnitude()) - { - norms[i].add(o.norm); - norms[j].add(v.norm); - } - } - } - } - loopi(numverts) verts[i].norm = norms[i].normalize(); - delete[] next; - delete[] norms; - } - - template void buildnorms(V *verts, int numverts, T *tris, int numtris, bool areaweight) - { - loopi(numverts) verts[i].norm = vec(0, 0, 0); - loopi(numtris) - { - T &t = tris[i]; - V &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]]; - vec norm; - norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos)); - if(!areaweight) norm.normalize(); - v1.norm.add(norm); - v2.norm.add(norm); - v3.norm.add(norm); - } - loopi(numverts) verts[i].norm.normalize(); - } - - template void buildnorms(V *verts, int numverts, T *tris, int numtris, bool areaweight, int numframes) - { - if(!numverts) return; - loopi(numframes) buildnorms(&verts[i*numverts], numverts, tris, numtris, areaweight); - } - - static inline void fixqtangent(quat &q, float bt) - { - static const float bias = -1.5f/65535, biasscale = sqrtf(1 - bias*bias); - if(bt < 0) - { - if(q.w >= 0) q.neg(); - if(q.w > bias) { q.mul3(biasscale); q.w = bias; } - } - else if(q.w < 0) q.neg(); - } - - template static inline void calctangent(V &v, const vec &n, const vec &t, float bt) - { - matrix3 m; - m.c = n; - m.a = t; - m.b.cross(m.c, m.a); - quat q(m); - fixqtangent(q, bt); - v.tangent = q; - } - - template void calctangents(B *bumpverts, V *verts, TC *tcverts, int numverts, T *tris, int numtris, bool areaweight) - { - vec *tangent = new vec[2*numverts], *bitangent = tangent+numverts; - for(int i=0;i<2*numverts;++i)tangent[i]=vec(0,0,0); - //~memclear(tangent, 2*numverts); - loopi(numtris) - { - const T &t = tris[i]; - const vec &e0 = verts[t.vert[0]].pos; - vec e1 = vec(verts[t.vert[1]].pos).sub(e0), e2 = vec(verts[t.vert[2]].pos).sub(e0); - - const vec2 &tc0 = tcverts[t.vert[0]].tc, - &tc1 = tcverts[t.vert[1]].tc, - &tc2 = tcverts[t.vert[2]].tc; - float u1 = tc1.x - tc0.x, v1 = tc1.y - tc0.y, - u2 = tc2.x - tc0.x, v2 = tc2.y - tc0.y; - vec u(e2), v(e2); - u.mul(v1).sub(vec(e1).mul(v2)); - v.mul(u1).sub(vec(e1).mul(u2)); - - if(vec().cross(e2, e1).dot(vec().cross(v, u)) >= 0) - { - u.neg(); - v.neg(); - } - - if(!areaweight) - { - u.normalize(); - v.normalize(); - } - - loopj(3) - { - tangent[t.vert[j]].sub(u); - bitangent[t.vert[j]].add(v); - } - } - loopi(numverts) - { - const vec &n = verts[i].norm, - &t = tangent[i], - &bt = bitangent[i]; - B &bv = bumpverts[i]; - matrix3 m; - m.c = n; - (m.a = t).project(m.c).normalize(); - m.b.cross(m.c, m.a); - quat q(m); - fixqtangent(q, m.b.dot(bt)); - bv.tangent = q; - } - delete[] tangent; - } - - template void calctangents(B *bumpverts, V *verts, TC *tcverts, int numverts, T *tris, int numtris, bool areaweight, int numframes) - { - loopi(numframes) calctangents(&bumpverts[i*numverts], &verts[i*numverts], tcverts, numverts, tris, numtris, areaweight); - } - }; - - struct meshgroup - { - meshgroup *next; - int shared; - char *name; - vector meshes; - - meshgroup() : next(NULL), shared(0), name(NULL) - { - } - - virtual ~meshgroup() - { - DELETEA(name); - meshes.deletecontents(); - DELETEP(next); - } - - virtual int findtag(const char *name) { return -1; } - virtual void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) {} - - void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) - { - loopv(meshes) meshes[i]->calcbb(bbmin, bbmax, m); - } - - void genBIH(vector &skins, vector &bih, const matrix4x3 &t) - { - loopv(meshes) meshes[i]->genBIH(skins[i], bih, t); - } - - virtual void *animkey() { return this; } - virtual int totalframes() const { return 1; } - bool hasframe(int i) const { return i>=0 && i=0 && i+n<=totalframes(); } - int clipframes(int i, int n) const { return min(n, totalframes() - i); } - - virtual void cleanup() {} - virtual void preload(part *p) {} - virtual void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) {} - - void bindpos(GLuint ebuf, GLuint vbuf, void *v, int stride) - { - if(lastebuf!=ebuf) - { - gle::bindebo(ebuf); - lastebuf = ebuf; - } - if(lastvbuf!=vbuf) - { - gle::bindvbo(vbuf); - if(!lastvbuf) gle::enablevertex(); - gle::vertexpointer(stride, v); - lastvbuf = vbuf; - } - } - - void bindtc(void *v, int stride) - { - if(!enabletc) - { - gle::enabletexcoord0(); - enabletc = true; - } - if(lasttcbuf!=lastvbuf) - { - gle::texcoord0pointer(stride, v); - lasttcbuf = lastvbuf; - } - } - - void bindnormals(void *v, int stride) - { - if(!enablenormals) - { - gle::enablenormal(); - enablenormals = true; - } - if(lastnbuf!=lastvbuf) - { - gle::normalpointer(stride, v); - lastnbuf = lastvbuf; - } - } - - void bindtangents(void *v, int stride) - { - if(!enabletangents) - { - gle::enabletangent(); - enabletangents = true; - } - if(lastxbuf!=lastvbuf) - { - gle::tangentpointer(stride, v, GL_SHORT); - lastxbuf = lastvbuf; - } - } - - void bindbones(void *wv, void *bv, int stride) - { - if(!enablebones) - { - gle::enableboneweight(); - gle::enableboneindex(); - enablebones = true; - } - if(lastbbuf!=lastvbuf) - { - gle::boneweightpointer(stride, wv); - gle::boneindexpointer(stride, bv); - lastbbuf = lastvbuf; - } - } - }; - - virtual meshgroup *loadmeshes(const char *name, va_list args) { return NULL; } - - meshgroup *sharemeshes(const char *name, ...) - { - static hashnameset meshgroups; - if(!meshgroups.access(name)) - { - va_list args; - va_start(args, name); - meshgroup *group = loadmeshes(name, args); - va_end(args); - if(!group) return NULL; - meshgroups.add(group); - } - return meshgroups[name]; - } - - struct linkedpart - { - part *p; - int tag, anim, basetime; - vec translate; - vec *pos; - matrix4 matrix; - - linkedpart() : p(NULL), tag(-1), anim(-1), basetime(0), translate(0, 0, 0), pos(NULL) {} - }; - - struct part - { - animmodel *model; - int index; - meshgroup *meshes; - vector links; - vector skins; - vector *anims[MAXANIMPARTS]; - int numanimparts; - float pitchscale, pitchoffset, pitchmin, pitchmax; - vec translate; - - part(animmodel *model, int index = 0) : model(model), index(index), meshes(NULL), numanimparts(1), pitchscale(1), pitchoffset(0), pitchmin(0), pitchmax(0), translate(0, 0, 0) - { - loopk(MAXANIMPARTS) anims[k] = NULL; - } - virtual ~part() - { - loopk(MAXANIMPARTS) DELETEA(anims[k]); - } - - virtual void cleanup() - { - if(meshes) meshes->cleanup(); - loopv(skins) skins[i].cleanup(); - } - - void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) - { - matrix4x3 t = m; - t.scale(model->scale); - t.translate(translate); - meshes->calcbb(bbmin, bbmax, t); - loopv(links) - { - matrix4x3 n; - meshes->concattagtransform(this, links[i].tag, m, n); - n.translate(links[i].translate, model->scale); - links[i].p->calcbb(bbmin, bbmax, n); - } - } - - void genBIH(vector &bih, const matrix4x3 &m) - { - matrix4x3 t = m; - t.scale(model->scale); - t.translate(translate); - meshes->genBIH(skins, bih, t); - loopv(links) - { - matrix4x3 n; - meshes->concattagtransform(this, links[i].tag, m, n); - n.translate(links[i].translate, model->scale); - links[i].p->genBIH(bih, n); - } - } - - bool link(part *p, const char *tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = NULL) - { - int i = meshes ? meshes->findtag(tag) : -1; - if(i<0) - { - loopv(links) if(links[i].p && links[i].p->link(p, tag, translate, anim, basetime, pos)) return true; - return false; - } - linkedpart &l = links.add(); - l.p = p; - l.tag = i; - l.anim = anim; - l.basetime = basetime; - l.translate = translate; - l.pos = pos; - return true; - } - - bool unlink(part *p) - { - loopvrev(links) if(links[i].p==p) { links.remove(i, 1); return true; } - loopv(links) if(links[i].p && links[i].p->unlink(p)) return true; - return false; - } - - void initskins(Texture *tex = notexture, Texture *masks = notexture, int limit = 0) - { - if(!limit) - { - if(!meshes) return; - limit = meshes->meshes.length(); - } - while(skins.length() < limit) - { - skin &s = skins.add(); - s.owner = this; - s.tex = tex; - s.masks = masks; - } - } - - bool envmapped() - { - loopv(skins) if(skins[i].envmapped()) return true; - return false; - } - - bool tangents() - { - loopv(skins) if(skins[i].tangents()) return true; - return false; - } - - void preloadBIH() - { - loopv(skins) skins[i].preloadBIH(); - } - - void preloadshaders(bool force) - { - loopv(skins) skins[i].preloadshader(force); - } - - void preloadmeshes() - { - if(meshes) meshes->preload(this); - } - - virtual void getdefaultanim(animinfo &info, int anim, uint varseed, dynent *d) - { - info.frame = 0; - info.range = 1; - } - - bool calcanim(int animpart, int anim, int basetime, int basetime2, dynent *d, int interp, animinfo &info, int &aitime) - { - uint varseed = uint((size_t)d); - info.anim = anim; - info.basetime = basetime; - info.varseed = varseed; - info.speed = anim&ANIM_SETSPEED ? basetime2 : 100.0f; - if((anim&ANIM_INDEX)==ANIM_ALL) - { - info.frame = 0; - info.range = meshes->totalframes(); - } - else - { - animspec *spec = NULL; - if(anims[animpart]) - { - int primaryidx = anim&ANIM_INDEX; - if(primaryidx < NUMANIMS) - { - vector &primary = anims[animpart][primaryidx]; - if(primary.length()) spec = &primary[uint(varseed + basetime)%primary.length()]; - } - if((anim>>ANIM_SECONDARY)&(ANIM_INDEX|ANIM_DIR)) - { - int secondaryidx = (anim>>ANIM_SECONDARY)&ANIM_INDEX; - if(secondaryidx < NUMANIMS) - { - vector &secondary = anims[animpart][secondaryidx]; - if(secondary.length()) - { - animspec &spec2 = secondary[uint(varseed + basetime2)%secondary.length()]; - if(!spec || spec2.priority > spec->priority) - { - spec = &spec2; - info.anim >>= ANIM_SECONDARY; - info.basetime = basetime2; - } - } - } - } - } - if(spec) - { - info.frame = spec->frame; - info.range = spec->range; - if(spec->speed>0) info.speed = 1000.0f/spec->speed; - } - else getdefaultanim(info, anim, uint(varseed + info.basetime), d); - } - - info.anim &= (1<hasframes(info.frame, info.range)) - { - if(!meshes->hasframe(info.frame)) return false; - info.range = meshes->clipframes(info.frame, info.range); - } - - if(d && interp>=0) - { - animinterpinfo &ai = d->animinterp[interp]; - if((info.anim&ANIM_CLAMP)==ANIM_CLAMP) aitime = min(aitime, int(info.range*info.speed*0.5e-3f)); - void *ak = meshes->animkey(); - if(d->ragdoll && !(anim&ANIM_RAGDOLL)) - { - ai.prev.range = ai.cur.range = 0; - ai.lastswitch = -1; - } - else if(ai.lastmodel!=ak || ai.lastswitch<0 || lastmillis-d->lastrendered>aitime) - { - ai.prev = ai.cur = info; - ai.lastswitch = lastmillis-aitime*2; - } - else if(ai.cur!=info) - { - if(lastmillis-ai.lastswitch>aitime/2) ai.prev = ai.cur; - ai.cur = info; - ai.lastswitch = lastmillis; - } - else if(info.anim&ANIM_SETTIME) ai.cur.basetime = info.basetime; - ai.lastmodel = ak; - } - return true; - } - - void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d) - { - animstate as[MAXANIMPARTS]; - render(anim, basetime, basetime2, pitch, axis, forward, d, as); - } - - void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, animstate *as) - { - if(!(anim&ANIM_REUSE)) loopi(numanimparts) - { - animinfo info; - int interp = d && index+numanimparts<=MAXANIMPARTS ? index+i : -1, aitime = animationinterpolationtime; - if(!calcanim(i, anim, basetime, basetime2, d, interp, info, aitime)) return; - animstate &p = as[i]; - p.owner = this; - p.cur.setframes(info); - p.interp = 1; - if(interp>=0 && d->animinterp[interp].prev.range>0) - { - int diff = lastmillis-d->animinterp[interp].lastswitch; - if(diffaniminterp[interp].prev); - p.interp = diff/float(aitime); - } - } - } - - vec oaxis, oforward; - matrixstack[matrixpos].transposedtransformnormal(axis, oaxis); - float pitchamount = pitchscale*pitch + pitchoffset; - if((pitchmin || pitchmax) && pitchmin <= pitchmax) pitchamount = clamp(pitchamount, pitchmin, pitchmax); - if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH)) - pitchamount *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp); - if(pitchamount) - { - ++matrixpos; - matrixstack[matrixpos] = matrixstack[matrixpos-1]; - matrixstack[matrixpos].rotate(pitchamount*RAD, oaxis); - } - matrixstack[matrixpos].transposedtransformnormal(forward, oforward); - - if(!(anim&ANIM_NORENDER)) - { - matrix4 modelmatrix; - modelmatrix.mul(shadowmapping ? shadowmatrix : camprojmatrix, matrixstack[matrixpos]); - if(model->scale!=1) modelmatrix.scale(model->scale); - if(!translate.iszero()) modelmatrix.translate(translate); - GLOBALPARAM(modelmatrix, modelmatrix); - - if(!(anim&ANIM_NOSKIN)) - { - if(envmapped()) GLOBALPARAM(modelworld, matrix3(matrixstack[matrixpos])); - - vec odir, ocampos; - matrixstack[matrixpos].transposedtransformnormal(lightdir, odir); - GLOBALPARAM(lightdir, odir); - matrixstack[matrixpos].transposedtransform(camera1->o, ocampos); - ocampos.div(model->scale).sub(translate); - GLOBALPARAM(modelcamera, ocampos); - } - } - - meshes->render(as, pitch, oaxis, oforward, d, this); - - if(!(anim&ANIM_REUSE)) - { - loopv(links) - { - linkedpart &link = links[i]; - link.matrix.translate(links[i].translate, model->scale); - - matrixpos++; - matrixstack[matrixpos].mul(matrixstack[matrixpos-1], link.matrix); - - if(link.pos) *link.pos = matrixstack[matrixpos].gettranslation(); - - if(!link.p) - { - matrixpos--; - continue; - } - - int nanim = anim, nbasetime = basetime, nbasetime2 = basetime2; - if(link.anim>=0) - { - nanim = link.anim | (anim&ANIM_FLAGS); - nbasetime = link.basetime; - nbasetime2 = 0; - } - link.p->render(nanim, nbasetime, nbasetime2, pitch, axis, forward, d); - - matrixpos--; - } - } - - if(pitchamount) matrixpos--; - } - - void setanim(int animpart, int num, int frame, int range, float speed, int priority = 0) - { - if(animpart<0 || animpart>=MAXANIMPARTS) return; - if(frame<0 || range<=0 || !meshes || !meshes->hasframes(frame, range)) - { - conoutf(CON_ERROR, "invalid frame %d, range %d in model %s", frame, range, model->name); - return; - } - if(!anims[animpart]) anims[animpart] = new vector[NUMANIMS]; - animspec &spec = anims[animpart][num].add(); - spec.frame = frame; - spec.range = range; - spec.speed = speed; - spec.priority = priority; - } - - virtual void loaded() - { - meshes->shared++; - loopv(skins) skins[i].setkey(); - } - }; - - enum - { - LINK_TAG = 0, - LINK_COOP, - LINK_REUSE - }; - - virtual int linktype(animmodel *m) const { return LINK_TAG; } - - void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, modelattach *a) - { - int numtags = 0; - if(a) - { - int index = parts.last()->index + parts.last()->numanimparts; - for(int i = 0; a[i].tag; i++) - { - numtags++; - - animmodel *m = (animmodel *)a[i].m; - if(!m) - { - if(a[i].pos) link(NULL, a[i].tag, vec(0, 0, 0), 0, 0, a[i].pos); - continue; - } - part *p = m->parts[0]; - switch(linktype(m)) - { - case LINK_TAG: - p->index = link(p, a[i].tag, vec(0, 0, 0), a[i].anim, a[i].basetime, a[i].pos) ? index : -1; - break; - - case LINK_COOP: - p->index = index; - break; - - default: - continue; - } - index += p->numanimparts; - } - } - - animstate as[MAXANIMPARTS]; - parts[0]->render(anim, basetime, basetime2, pitch, axis, forward, d, as); - - if(a) for(int i = numtags-1; i >= 0; i--) - { - animmodel *m = (animmodel *)a[i].m; - if(!m) - { - if(a[i].pos) unlink(NULL); - continue; - } - part *p = m->parts[0]; - switch(linktype(m)) - { - case LINK_TAG: - if(p->index >= 0) unlink(p); - p->index = 0; - break; - - case LINK_COOP: - p->render(anim, basetime, basetime2, pitch, axis, forward, d); - p->index = 0; - break; - - case LINK_REUSE: - p->render(anim | ANIM_REUSE, basetime, basetime2, pitch, axis, forward, d, as); - break; - } - } - } - - void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, dynent *d, modelattach *a, const vec &color, const vec &dir, float trans) - { - yaw += spinyaw*lastmillis/1000.0f; - pitch += offsetpitch + spinpitch*lastmillis/1000.0f; - - vec axis(0, -1, 0), forward(1, 0, 0); - - matrixpos = 0; - matrixstack[0].identity(); - if(!d || !d->ragdoll || anim&ANIM_RAGDOLL) - { - matrixstack[0].settranslation(o); - matrixstack[0].rotate_around_z(yaw*RAD); - matrixstack[0].transformnormal(vec(axis), axis); - matrixstack[0].transformnormal(vec(forward), forward); - if(offsetyaw) matrixstack[0].rotate_around_z(offsetyaw*RAD); - } - else pitch = 0; - - if(anim&ANIM_NORENDER) - { - render(anim, basetime, basetime2, pitch, axis, forward, d, a); - if(d) d->lastrendered = lastmillis; - return; - } - - if(!(anim&ANIM_NOSKIN)) - { - transparent = trans; - lightdir = dir; - lightcolor = color; - - if(envmapped()) - { - setupenvmap: - closestenvmaptex = lookupenvmap(closestenvmap(o)); - GLOBALPARAM(lightdirworld, dir); - } - else if(a) for(int i = 0; a[i].tag; i++) if(a[i].m && a[i].m->envmapped()) goto setupenvmap; - } - - if(depthoffset && !enabledepthoffset) - { - enablepolygonoffset(GL_POLYGON_OFFSET_FILL); - enabledepthoffset = true; - } - - if(transparent<1) - { - if(anim&ANIM_GHOST) - { - glDepthFunc(GL_GREATER); - glDepthMask(GL_FALSE); - } - else if(alphadepth) - { - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - render(anim|ANIM_NOSKIN, basetime, basetime2, pitch, axis, forward, d, a); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE); - - glDepthFunc(GL_LEQUAL); - } - - if(!enablealphablend) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - enablealphablend = true; - } - } - - render(anim, basetime, basetime2, pitch, axis, forward, d, a); - - if(transparent<1 && (alphadepth || anim&ANIM_GHOST)) - { - glDepthFunc(GL_LESS); - if(anim&ANIM_GHOST) glDepthMask(GL_TRUE); - } - - if(d) d->lastrendered = lastmillis; - } - - vector parts; - - animmodel(const char *name) : model(name) - { - } - - ~animmodel() - { - parts.deletecontents(); - } - - void cleanup() - { - loopv(parts) parts[i]->cleanup(); - } - - virtual void flushpart() {} - - part &addpart() - { - flushpart(); - part *p = new part(this, parts.length()); - parts.add(p); - return *p; - } - - void initmatrix(matrix4x3 &m) - { - m.identity(); - if(offsetyaw) m.rotate_around_z(offsetyaw*RAD); - if(offsetpitch) m.rotate_around_y(-offsetpitch*RAD); - } - - void genBIH(vector &bih) - { - if(parts.empty()) return; - matrix4x3 m; - initmatrix(m); - parts[0]->genBIH(bih, m); - } - - void preloadBIH() - { - model::preloadBIH(); - if(bih) loopv(parts) parts[i]->preloadBIH(); - } - - BIH *setBIH() - { - if(bih) return bih; - vector meshes; - genBIH(meshes); - bih = new BIH(meshes); - return bih; - } - - bool link(part *p, const char *tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = NULL) - { - if(parts.empty()) return false; - return parts[0]->link(p, tag, translate, anim, basetime, pos); - } - - bool unlink(part *p) - { - if(parts.empty()) return false; - return parts[0]->unlink(p); - } - - bool envmapped() - { - loopv(parts) if(parts[i]->envmapped()) return true; - return false; - } - - virtual bool flipy() const { return false; } - virtual bool loadconfig() { return false; } - virtual bool loaddefaultparts() { return false; } - virtual void startload() {} - virtual void endload() {} - - bool load() - { - startload(); - bool success = loadconfig() && parts.length(); // configured model, will call the model commands below - if(!success) - success = loaddefaultparts(); // model without configuration, try default tris and skin - flushpart(); - endload(); - if(flipy()) translate.y = -translate.y; - - if(!success) return false; - loopv(parts) if(!parts[i]->meshes) return false; - - loaded(); - return true; - } - - void preloadshaders(bool force) - { - loopv(parts) parts[i]->preloadshaders(force); - } - - void preloadmeshes() - { - loopv(parts) parts[i]->preloadmeshes(); - } - - void setshader(Shader *shader) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].shader = shader; - } - - void setenvmap(float envmapmin, float envmapmax, Texture *envmap) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) - { - skin &s = parts[i]->skins[j]; - if(envmapmax) - { - s.envmapmin = envmapmin; - s.envmapmax = envmapmax; - } - if(envmap) s.envmap = envmap; - } - } - - void setspec(float spec) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].spec = spec; - } - - void setambient(float ambient) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].ambient = ambient; - } - - void setglow(float glow, float delta, float pulse) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) - { - skin &s = parts[i]->skins[j]; - s.glow = glow; - s.glowdelta = delta; - s.glowpulse = pulse; - } - } - - void setglare(float specglare, float glowglare) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) - { - skin &s = parts[i]->skins[j]; - s.specglare = specglare; - s.glowglare = glowglare; - } - } - - void setalphatest(float alphatest) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].alphatest = alphatest; - } - - void setalphablend(bool alphablend) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].alphablend = alphablend; - } - - void setfullbright(float fullbright) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].fullbright = fullbright; - } - - void setcullface(bool cullface) - { - if(parts.empty()) loaddefaultparts(); - loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].cullface = cullface; - } - - void calcbb(vec ¢er, vec &radius) - { - if(parts.empty()) return; - vec bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f); - matrix4x3 m; - initmatrix(m); - parts[0]->calcbb(bbmin, bbmax, m); - radius = bbmax; - radius.sub(bbmin); - radius.mul(0.5f); - center = bbmin; - center.add(radius); - } - - virtual void loaded() - { - scale /= 4; - if(parts.length()) parts[0]->translate = translate; - loopv(parts) parts[i]->loaded(); - } - - static bool enabletc, enablealphablend, enablecullface, enablenormals, enabletangents, enablebones, enabledepthoffset; - static vec lightdir, lightcolor; - static float transparent, lastalphatest; - static GLuint lastvbuf, lasttcbuf, lastnbuf, lastxbuf, lastbbuf, lastebuf, lastenvmaptex, closestenvmaptex; - static Texture *lasttex, *lastmasks, *lastnormalmap; - static int matrixpos; - static matrix4 matrixstack[64]; - - void startrender() - { - enabletc = enablealphablend = enablenormals = enabletangents = enablebones = enabledepthoffset = false; - enablecullface = true; - lastalphatest = -1; - lastvbuf = lasttcbuf = lastxbuf = lastnbuf = lastbbuf = lastebuf = lastenvmaptex = closestenvmaptex = 0; - lasttex = lastmasks = lastnormalmap = NULL; - transparent = 1; - shaderparamskey::invalidate(); - } - - static void disablebones() - { - gle::disableboneweight(); - gle::disableboneindex(); - enablebones = false; - } - - static void disabletangents() - { - gle::disabletangent(); - enabletangents = false; - } - - static void disabletc() - { - gle::disabletexcoord0(); - enabletc = false; - } - - static void disablenormals() - { - gle::disablenormal(); - enablenormals = false; - } - - static void disablevbo() - { - if(lastebuf) gle::clearebo(); - if(lastvbuf) - { - gle::clearvbo(); - gle::disablevertex(); - } - if(enabletc) disabletc(); - if(enablenormals) disablenormals(); - if(enabletangents) disabletangents(); - if(enablebones) disablebones(); - lastvbuf = lasttcbuf = lastxbuf = lastnbuf = lastbbuf = lastebuf = 0; - } - - void endrender() - { - if(lastvbuf || lastebuf) disablevbo(); - if(enablealphablend) glDisable(GL_BLEND); - if(!enablecullface) glEnable(GL_CULL_FACE); - if(enabledepthoffset) disablepolygonoffset(GL_POLYGON_OFFSET_FILL); - } + struct animspec + { + int frame, range; + float speed; + int priority; + }; + + struct animpos + { + int anim, fr1, fr2; + float t; + + void setframes(const animinfo &info) + { + anim = info.anim; + if(info.range<=1) + { + fr1 = 0; + t = 0; + } + else + { + int time = info.anim&ANIM_SETTIME ? info.basetime : lastmillis-info.basetime; + fr1 = (int)(time/info.speed); // round to full frames + t = (time-fr1*info.speed)/info.speed; // progress of the frame, value from 0.0f to 1.0f + } + if(info.anim&ANIM_LOOP) + { + fr1 = fr1%info.range+info.frame; + fr2 = fr1+1; + if(fr2>=info.frame+info.range) fr2 = info.frame; + } + else + { + fr1 = min(fr1, info.range-1)+info.frame; + fr2 = min(fr1+1, info.frame+info.range-1); + } + if(info.anim&ANIM_REVERSE) + { + fr1 = (info.frame+info.range-1)-(fr1-info.frame); + fr2 = (info.frame+info.range-1)-(fr2-info.frame); + } + } + + bool operator==(const animpos &a) const { return fr1==a.fr1 && fr2==a.fr2 && (fr1==fr2 || t==a.t); } + bool operator!=(const animpos &a) const { return fr1!=a.fr1 || fr2!=a.fr2 || (fr1!=fr2 && t!=a.t); } + }; + + struct part; + + struct animstate + { + part *owner; + animpos cur, prev; + float interp; + + bool operator==(const animstate &a) const { return cur==a.cur && (interp<1 ? interp==a.interp && prev==a.prev : a.interp>=1); } + bool operator!=(const animstate &a) const { return cur!=a.cur || (interp<1 ? interp!=a.interp || prev!=a.prev : a.interp<1); } + }; + + struct linkedpart; + struct mesh; + + struct shaderparams + { + float spec, ambient, glow, glowdelta, glowpulse, specglare, glowglare, fullbright, envmapmin, envmapmax, scrollu, scrollv, alphatest; + + shaderparams() : spec(1.0f), ambient(0.3f), glow(3.0f), glowdelta(0), glowpulse(0), specglare(1), glowglare(1), fullbright(0), envmapmin(0), envmapmax(0), scrollu(0), scrollv(0), alphatest(0.9f) {} + }; + + struct shaderparamskey + { + static hashtable keys; + static int firstversion, lastversion; + + int version; + + shaderparamskey() : version(-1) {} + + bool checkversion() + { + if(version >= firstversion) return true; + version = lastversion; + if(++lastversion <= 0) + { + enumerate(keys, shaderparamskey, key, key.version = -1); + firstversion = 0; + lastversion = 1; + version = 0; + } + return false; + } + + static inline void invalidate() + { + firstversion = lastversion; + } + }; + + struct skin : shaderparams + { + part *owner; + Texture *tex, *masks, *envmap, *normalmap; + Shader *shader; + bool alphablend, cullface; + shaderparamskey *key; + + skin() : owner(0), tex(notexture), masks(notexture), envmap(NULL), normalmap(NULL), shader(NULL), alphablend(true), cullface(true), key(NULL) {} + + bool masked() const { return masks != notexture; } + bool envmapped() { return envmapmax>0 && envmapmodels; } + bool bumpmapped() { return normalmap && bumpmodels; } + bool tangents() { return bumpmapped(); } + bool alphatested() const { return alphatest > 0 && tex->type&Texture::ALPHA; } + + void setkey() + { + key = &shaderparamskey::keys[*this]; + } + + void setshaderparams(mesh *m, const animstate *as) + { + if(!Shader::lastshader) return; + + float mincolor = as->cur.anim&ANIM_FULLBRIGHT ? fullbrightmodels/100.0f : 0.0f; + if(fullbright) + { + gle::colorf(fullbright/2, fullbright/2, fullbright/2, transparent); + } + else + { + gle::color(vec(lightcolor).max(mincolor), transparent); + } + + if(key->checkversion() && Shader::lastshader->owner == key) return; + Shader::lastshader->owner = key; + + if(alphatested()) LOCALPARAMF(alphatest, alphatest); + + if(fullbright) + { + LOCALPARAMF(lightscale, 0, 0, 2); + } + else + { + float bias = max(mincolor-1.0f, 0.2f), scale = 0.5f*max(0.8f-bias, 0.0f), + minshade = scale*max(ambient, mincolor); + LOCALPARAMF(lightscale, scale - minshade, scale, minshade + bias); + } + float curglow = glow; + if(glowpulse > 0) + { + float curpulse = lastmillis*glowpulse; + curpulse -= floor(curpulse); + curglow += glowdelta*2*fabs(curpulse - 0.5f); + } + LOCALPARAMF(maskscale, 0.5f*spec, 0.5f*curglow, 16*specglare, 4*glowglare); + LOCALPARAMF(texscroll, scrollu*lastmillis/1000.0f, scrollv*lastmillis/1000.0f); + if(envmapped()) LOCALPARAMF(envmapscale, envmapmin-envmapmax, envmapmax); + } + + Shader *loadshader() + { + #define DOMODELSHADER(name, body) \ + do { \ + static Shader *name##shader = NULL; \ + if(!name##shader) name##shader = useshaderbyname(#name); \ + body; \ + } while(0) + #define LOADMODELSHADER(name) DOMODELSHADER(name, return name##shader) + #define SETMODELSHADER(m, name) DOMODELSHADER(name, (m)->setshader(name##shader)) + if(shader) return shader; + + string opts; + int optslen = 0; + if(alphatested()) opts[optslen++] = 'a'; + if(owner->tangents()) opts[optslen++] = 'q'; + if(bumpmapped()) opts[optslen++] = 'n'; + if(envmapped()) opts[optslen++] = 'e'; + if(masked()) opts[optslen++] = 'm'; + if(!fullbright && (masked() || spec>=0.01f)) opts[optslen++] = 's'; + opts[optslen++] = '\0'; + + defformatstring(name, "model%s", opts); + shader = generateshader(name, "modelshader \"%s\"", opts); + return shader; + } + + void cleanup() + { + if(shader && shader->standard) shader = NULL; + } + + void preloadBIH() + { + if(tex->type&Texture::ALPHA && !tex->alphamask) loadalphamask(tex); + } + + void preloadshader(bool force) + { + if(force) cleanup(); + loadshader(); + } + + void setshader(mesh *m, const animstate *as) + { + m->setshader(loadshader()); + } + + void bind(mesh *b, const animstate *as) + { + if(!cullface && enablecullface) { glDisable(GL_CULL_FACE); enablecullface = false; } + else if(cullface && !enablecullface) { glEnable(GL_CULL_FACE); enablecullface = true; } + + if(as->cur.anim&ANIM_NOSKIN) + { + if(enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; } + if(shadowmapping) SETMODELSHADER(b, shadowmapcaster); + else /*if(as->cur.anim&ANIM_SHADOW)*/ SETMODELSHADER(b, notexturemodel); + return; + } + setshader(b, as); + setshaderparams(b, as); + int activetmu = 0; + if(tex!=lasttex) + { + glBindTexture(GL_TEXTURE_2D, tex->id); + lasttex = tex; + } + if(bumpmapped() && normalmap !=lastnormalmap) + { + glActiveTexture_(GL_TEXTURE3); + activetmu = 3; + glBindTexture(GL_TEXTURE_2D, normalmap->id); + lastnormalmap = normalmap; + } + if(tex->type&Texture::ALPHA) + { + if(alphablend) + { + if(!enablealphablend && !reflecting && !refracting) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + enablealphablend = true; + } + } + else if(enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; } + } + else if(enablealphablend && transparent>=1) { glDisable(GL_BLEND); enablealphablend = false; } + if(masked() && masks!=lastmasks) + { + glActiveTexture_(GL_TEXTURE1); + activetmu = 1; + glBindTexture(GL_TEXTURE_2D, masks->id); + lastmasks = masks; + } + if(envmapped()) + { + GLuint emtex = envmap ? envmap->id : closestenvmaptex; + if(lastenvmaptex!=emtex) + { + glActiveTexture_(GL_TEXTURE2); + activetmu = 2; + glBindTexture(GL_TEXTURE_CUBE_MAP, emtex); + lastenvmaptex = emtex; + } + } + if(activetmu != 0) glActiveTexture_(GL_TEXTURE0); + } + }; + + struct meshgroup; + + struct mesh + { + meshgroup *group; + char *name; + bool noclip; + + mesh() : group(NULL), name(NULL), noclip(false) + { + } + + virtual ~mesh() + { + DELETEA(name); + } + + virtual void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) {} + + virtual void genBIH(BIH::mesh &m) {} + void genBIH(skin &s, vector &bih, const matrix4x3 &t) + { + BIH::mesh &m = bih.add(); + m.xform = t; + m.tex = s.tex; + if(s.tex->type&Texture::ALPHA) m.flags |= BIH::MESH_ALPHA; + if(noclip) m.flags |= BIH::MESH_NOCLIP; + if(s.cullface) m.flags |= BIH::MESH_CULLFACE; + genBIH(m); + while(bih.last().numtris > BIH::mesh::MAXTRIS) + { + BIH::mesh &overflow = bih.dup(); + overflow.tris += BIH::mesh::MAXTRIS; + overflow.numtris -= BIH::mesh::MAXTRIS; + bih[bih.length()-2].numtris = BIH::mesh::MAXTRIS; + } + } + + virtual void setshader(Shader *s) + { + if(glaring) s->setvariant(0, 1); + else s->set(); + } + + template void smoothnorms(V *verts, int numverts, T *tris, int numtris, float limit, bool areaweight) + { + hashtable share; + int *next = new int[numverts]; + for(int i=0;i= 0) + { + float vlimit = limit*v.norm.magnitude(); + for(int j = next[i]; j >= 0; j = next[j]) + { + V &o = verts[j]; + if(v.norm.dot(o.norm) >= vlimit*o.norm.magnitude()) + { + norms[i].add(o.norm); + norms[j].add(v.norm); + } + } + } + } + loopi(numverts) verts[i].norm = norms[i].normalize(); + delete[] next; + delete[] norms; + } + + template void buildnorms(V *verts, int numverts, T *tris, int numtris, bool areaweight) + { + loopi(numverts) verts[i].norm = vec(0, 0, 0); + loopi(numtris) + { + T &t = tris[i]; + V &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]]; + vec norm; + norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos)); + if(!areaweight) norm.normalize(); + v1.norm.add(norm); + v2.norm.add(norm); + v3.norm.add(norm); + } + loopi(numverts) verts[i].norm.normalize(); + } + + template void buildnorms(V *verts, int numverts, T *tris, int numtris, bool areaweight, int numframes) + { + if(!numverts) return; + loopi(numframes) buildnorms(&verts[i*numverts], numverts, tris, numtris, areaweight); + } + + static inline void fixqtangent(quat &q, float bt) + { + static const float bias = -1.5f/65535, biasscale = sqrtf(1 - bias*bias); + if(bt < 0) + { + if(q.w >= 0) q.neg(); + if(q.w > bias) { q.mul3(biasscale); q.w = bias; } + } + else if(q.w < 0) q.neg(); + } + + template static inline void calctangent(V &v, const vec &n, const vec &t, float bt) + { + matrix3 m; + m.c = n; + m.a = t; + m.b.cross(m.c, m.a); + quat q(m); + fixqtangent(q, bt); + v.tangent = q; + } + + template void calctangents(B *bumpverts, V *verts, TC *tcverts, int numverts, T *tris, int numtris, bool areaweight) + { + vec *tangent = new vec[2*numverts], *bitangent = tangent+numverts; + for(int i=0;i<2*numverts;++i)tangent[i]=vec(0,0,0); + //~memclear(tangent, 2*numverts); + loopi(numtris) + { + const T &t = tris[i]; + const vec &e0 = verts[t.vert[0]].pos; + vec e1 = vec(verts[t.vert[1]].pos).sub(e0), e2 = vec(verts[t.vert[2]].pos).sub(e0); + + const vec2 &tc0 = tcverts[t.vert[0]].tc, + &tc1 = tcverts[t.vert[1]].tc, + &tc2 = tcverts[t.vert[2]].tc; + float u1 = tc1.x - tc0.x, v1 = tc1.y - tc0.y, + u2 = tc2.x - tc0.x, v2 = tc2.y - tc0.y; + vec u(e2), v(e2); + u.mul(v1).sub(vec(e1).mul(v2)); + v.mul(u1).sub(vec(e1).mul(u2)); + + if(vec().cross(e2, e1).dot(vec().cross(v, u)) >= 0) + { + u.neg(); + v.neg(); + } + + if(!areaweight) + { + u.normalize(); + v.normalize(); + } + + loopj(3) + { + tangent[t.vert[j]].sub(u); + bitangent[t.vert[j]].add(v); + } + } + loopi(numverts) + { + const vec &n = verts[i].norm, + &t = tangent[i], + &bt = bitangent[i]; + B &bv = bumpverts[i]; + matrix3 m; + m.c = n; + (m.a = t).project(m.c).normalize(); + m.b.cross(m.c, m.a); + quat q(m); + fixqtangent(q, m.b.dot(bt)); + bv.tangent = q; + } + delete[] tangent; + } + + template void calctangents(B *bumpverts, V *verts, TC *tcverts, int numverts, T *tris, int numtris, bool areaweight, int numframes) + { + loopi(numframes) calctangents(&bumpverts[i*numverts], &verts[i*numverts], tcverts, numverts, tris, numtris, areaweight); + } + }; + + struct meshgroup + { + meshgroup *next; + int shared; + char *name; + vector meshes; + + meshgroup() : next(NULL), shared(0), name(NULL) + { + } + + virtual ~meshgroup() + { + DELETEA(name); + meshes.deletecontents(); + DELETEP(next); + } + + virtual int findtag(const char *name) { return -1; } + virtual void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) {} + + void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) + { + loopv(meshes) meshes[i]->calcbb(bbmin, bbmax, m); + } + + void genBIH(vector &skins, vector &bih, const matrix4x3 &t) + { + loopv(meshes) meshes[i]->genBIH(skins[i], bih, t); + } + + virtual void *animkey() { return this; } + virtual int totalframes() const { return 1; } + bool hasframe(int i) const { return i>=0 && i=0 && i+n<=totalframes(); } + int clipframes(int i, int n) const { return min(n, totalframes() - i); } + + virtual void cleanup() {} + virtual void preload(part *p) {} + virtual void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) {} + + void bindpos(GLuint ebuf, GLuint vbuf, void *v, int stride) + { + if(lastebuf!=ebuf) + { + gle::bindebo(ebuf); + lastebuf = ebuf; + } + if(lastvbuf!=vbuf) + { + gle::bindvbo(vbuf); + if(!lastvbuf) gle::enablevertex(); + gle::vertexpointer(stride, v); + lastvbuf = vbuf; + } + } + + void bindtc(void *v, int stride) + { + if(!enabletc) + { + gle::enabletexcoord0(); + enabletc = true; + } + if(lasttcbuf!=lastvbuf) + { + gle::texcoord0pointer(stride, v); + lasttcbuf = lastvbuf; + } + } + + void bindnormals(void *v, int stride) + { + if(!enablenormals) + { + gle::enablenormal(); + enablenormals = true; + } + if(lastnbuf!=lastvbuf) + { + gle::normalpointer(stride, v); + lastnbuf = lastvbuf; + } + } + + void bindtangents(void *v, int stride) + { + if(!enabletangents) + { + gle::enabletangent(); + enabletangents = true; + } + if(lastxbuf!=lastvbuf) + { + gle::tangentpointer(stride, v, GL_SHORT); + lastxbuf = lastvbuf; + } + } + + void bindbones(void *wv, void *bv, int stride) + { + if(!enablebones) + { + gle::enableboneweight(); + gle::enableboneindex(); + enablebones = true; + } + if(lastbbuf!=lastvbuf) + { + gle::boneweightpointer(stride, wv); + gle::boneindexpointer(stride, bv); + lastbbuf = lastvbuf; + } + } + }; + + virtual meshgroup *loadmeshes(const char *name, va_list args) { return NULL; } + + meshgroup *sharemeshes(const char *name, ...) + { + static hashnameset meshgroups; + if(!meshgroups.access(name)) + { + va_list args; + va_start(args, name); + meshgroup *group = loadmeshes(name, args); + va_end(args); + if(!group) return NULL; + meshgroups.add(group); + } + return meshgroups[name]; + } + + struct linkedpart + { + part *p; + int tag, anim, basetime; + vec translate; + vec *pos; + matrix4 matrix; + + linkedpart() : p(NULL), tag(-1), anim(-1), basetime(0), translate(0, 0, 0), pos(NULL) {} + }; + + struct part + { + animmodel *model; + int index; + meshgroup *meshes; + vector links; + vector skins; + vector *anims[MAXANIMPARTS]; + int numanimparts; + float pitchscale, pitchoffset, pitchmin, pitchmax; + vec translate; + + part(animmodel *model, int index = 0) : model(model), index(index), meshes(NULL), numanimparts(1), pitchscale(1), pitchoffset(0), pitchmin(0), pitchmax(0), translate(0, 0, 0) + { + loopk(MAXANIMPARTS) anims[k] = NULL; + } + virtual ~part() + { + loopk(MAXANIMPARTS) DELETEA(anims[k]); + } + + virtual void cleanup() + { + if(meshes) meshes->cleanup(); + loopv(skins) skins[i].cleanup(); + } + + void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) + { + matrix4x3 t = m; + t.scale(model->scale); + t.translate(translate); + meshes->calcbb(bbmin, bbmax, t); + loopv(links) + { + matrix4x3 n; + meshes->concattagtransform(this, links[i].tag, m, n); + n.translate(links[i].translate, model->scale); + links[i].p->calcbb(bbmin, bbmax, n); + } + } + + void genBIH(vector &bih, const matrix4x3 &m) + { + matrix4x3 t = m; + t.scale(model->scale); + t.translate(translate); + meshes->genBIH(skins, bih, t); + loopv(links) + { + matrix4x3 n; + meshes->concattagtransform(this, links[i].tag, m, n); + n.translate(links[i].translate, model->scale); + links[i].p->genBIH(bih, n); + } + } + + bool link(part *p, const char *tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = NULL) + { + int i = meshes ? meshes->findtag(tag) : -1; + if(i<0) + { + loopv(links) if(links[i].p && links[i].p->link(p, tag, translate, anim, basetime, pos)) return true; + return false; + } + linkedpart &l = links.add(); + l.p = p; + l.tag = i; + l.anim = anim; + l.basetime = basetime; + l.translate = translate; + l.pos = pos; + return true; + } + + bool unlink(part *p) + { + loopvrev(links) if(links[i].p==p) { links.remove(i, 1); return true; } + loopv(links) if(links[i].p && links[i].p->unlink(p)) return true; + return false; + } + + void initskins(Texture *tex = notexture, Texture *masks = notexture, int limit = 0) + { + if(!limit) + { + if(!meshes) return; + limit = meshes->meshes.length(); + } + while(skins.length() < limit) + { + skin &s = skins.add(); + s.owner = this; + s.tex = tex; + s.masks = masks; + } + } + + bool envmapped() + { + loopv(skins) if(skins[i].envmapped()) return true; + return false; + } + + bool tangents() + { + loopv(skins) if(skins[i].tangents()) return true; + return false; + } + + void preloadBIH() + { + loopv(skins) skins[i].preloadBIH(); + } + + void preloadshaders(bool force) + { + loopv(skins) skins[i].preloadshader(force); + } + + void preloadmeshes() + { + if(meshes) meshes->preload(this); + } + + virtual void getdefaultanim(animinfo &info, int anim, uint varseed, dynent *d) + { + (void) anim; (void) varseed; (void) d; + info.frame = 0; + info.range = 1; + } + + bool calcanim(int animpart, int anim, int basetime, int basetime2, dynent *d, int interp, animinfo &info, int &aitime) + { + uint varseed = uint((size_t)d); + info.anim = anim; + info.basetime = basetime; + info.varseed = varseed; + info.speed = anim&ANIM_SETSPEED ? basetime2 : 100.0f; + if((anim&ANIM_INDEX)==ANIM_ALL) + { + info.frame = 0; + info.range = meshes->totalframes(); + } + else + { + animspec *spec = NULL; + if(anims[animpart]) + { + int primaryidx = anim&ANIM_INDEX; + if(primaryidx < NUMANIMS) + { + vector &primary = anims[animpart][primaryidx]; + if(primary.length()) spec = &primary[uint(varseed + basetime)%primary.length()]; + } + if((anim>>ANIM_SECONDARY)&(ANIM_INDEX|ANIM_DIR)) + { + int secondaryidx = (anim>>ANIM_SECONDARY)&ANIM_INDEX; + if(secondaryidx < NUMANIMS) + { + vector &secondary = anims[animpart][secondaryidx]; + if(secondary.length()) + { + animspec &spec2 = secondary[uint(varseed + basetime2)%secondary.length()]; + if(!spec || spec2.priority > spec->priority) + { + spec = &spec2; + info.anim >>= ANIM_SECONDARY; + info.basetime = basetime2; + } + } + } + } + } + if(spec) + { + info.frame = spec->frame; + info.range = spec->range; + if(spec->speed>0) info.speed = 1000.0f/spec->speed; + } + else getdefaultanim(info, anim, uint(varseed + info.basetime), d); + } + + info.anim &= (1<hasframes(info.frame, info.range)) + { + if(!meshes->hasframe(info.frame)) return false; + info.range = meshes->clipframes(info.frame, info.range); + } + + if(d && interp>=0) + { + animinterpinfo &ai = d->animinterp[interp]; + if((info.anim&ANIM_CLAMP)==ANIM_CLAMP) aitime = min(aitime, int(info.range*info.speed*0.5e-3f)); + void *ak = meshes->animkey(); + if(d->ragdoll && !(anim&ANIM_RAGDOLL)) + { + ai.prev.range = ai.cur.range = 0; + ai.lastswitch = -1; + } + else if(ai.lastmodel!=ak || ai.lastswitch<0 || lastmillis-d->lastrendered>aitime) + { + ai.prev = ai.cur = info; + ai.lastswitch = lastmillis-aitime*2; + } + else if(ai.cur!=info) + { + if(lastmillis-ai.lastswitch>aitime/2) ai.prev = ai.cur; + ai.cur = info; + ai.lastswitch = lastmillis; + } + else if(info.anim&ANIM_SETTIME) ai.cur.basetime = info.basetime; + ai.lastmodel = ak; + } + return true; + } + + void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d) + { + animstate as[MAXANIMPARTS]; + render(anim, basetime, basetime2, pitch, axis, forward, d, as); + } + + void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, animstate *as) + { + if(!(anim&ANIM_REUSE)) loopi(numanimparts) + { + animinfo info; + int interp = d && index+numanimparts<=MAXANIMPARTS ? index+i : -1, aitime = animationinterpolationtime; + if(!calcanim(i, anim, basetime, basetime2, d, interp, info, aitime)) return; + animstate &p = as[i]; + p.owner = this; + p.cur.setframes(info); + p.interp = 1; + if(interp>=0 && d->animinterp[interp].prev.range>0) + { + int diff = lastmillis-d->animinterp[interp].lastswitch; + if(diffaniminterp[interp].prev); + p.interp = diff/float(aitime); + } + } + } + + vec oaxis, oforward; + matrixstack[matrixpos].transposedtransformnormal(axis, oaxis); + float pitchamount = pitchscale*pitch + pitchoffset; + if((pitchmin || pitchmax) && pitchmin <= pitchmax) pitchamount = clamp(pitchamount, pitchmin, pitchmax); + if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH)) + pitchamount *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp); + if(pitchamount) + { + ++matrixpos; + matrixstack[matrixpos] = matrixstack[matrixpos-1]; + matrixstack[matrixpos].rotate(pitchamount*RAD, oaxis); + } + matrixstack[matrixpos].transposedtransformnormal(forward, oforward); + + if(!(anim&ANIM_NORENDER)) + { + matrix4 modelmatrix; + modelmatrix.mul(shadowmapping ? shadowmatrix : camprojmatrix, matrixstack[matrixpos]); + if(model->scale!=1) modelmatrix.scale(model->scale); + if(!translate.iszero()) modelmatrix.translate(translate); + GLOBALPARAM(modelmatrix, modelmatrix); + + if(!(anim&ANIM_NOSKIN)) + { + if(envmapped()) GLOBALPARAM(modelworld, matrix3(matrixstack[matrixpos])); + + vec odir, ocampos; + matrixstack[matrixpos].transposedtransformnormal(lightdir, odir); + GLOBALPARAM(lightdir, odir); + matrixstack[matrixpos].transposedtransform(camera1->o, ocampos); + ocampos.div(model->scale).sub(translate); + GLOBALPARAM(modelcamera, ocampos); + } + } + + meshes->render(as, pitch, oaxis, oforward, d, this); + + if(!(anim&ANIM_REUSE)) + { + loopv(links) + { + linkedpart &link = links[i]; + link.matrix.translate(links[i].translate, model->scale); + + matrixpos++; + matrixstack[matrixpos].mul(matrixstack[matrixpos-1], link.matrix); + + if(link.pos) *link.pos = matrixstack[matrixpos].gettranslation(); + + if(!link.p) + { + matrixpos--; + continue; + } + + int nanim = anim, nbasetime = basetime, nbasetime2 = basetime2; + if(link.anim>=0) + { + nanim = link.anim | (anim&ANIM_FLAGS); + nbasetime = link.basetime; + nbasetime2 = 0; + } + link.p->render(nanim, nbasetime, nbasetime2, pitch, axis, forward, d); + + matrixpos--; + } + } + + if(pitchamount) matrixpos--; + } + + void setanim(int animpart, int num, int frame, int range, float speed, int priority = 0) + { + if(animpart<0 || animpart>=MAXANIMPARTS) return; + if(frame<0 || range<=0 || !meshes || !meshes->hasframes(frame, range)) + { + conoutf(CON_ERROR, "invalid frame %d, range %d in model %s", frame, range, model->name); + return; + } + if(!anims[animpart]) anims[animpart] = new vector[NUMANIMS]; + animspec &spec = anims[animpart][num].add(); + spec.frame = frame; + spec.range = range; + spec.speed = speed; + spec.priority = priority; + } + + virtual void loaded() + { + meshes->shared++; + loopv(skins) skins[i].setkey(); + } + }; + + enum + { + LINK_TAG = 0, + LINK_COOP, + LINK_REUSE + }; + + virtual int linktype(animmodel *m) const { (void) m; return LINK_TAG; } + + void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, const vec &forward, dynent *d, modelattach *a) + { + int numtags = 0; + if(a) + { + int index = parts.last()->index + parts.last()->numanimparts; + for(int i = 0; a[i].tag; i++) + { + numtags++; + + animmodel *m = (animmodel *)a[i].m; + if(!m) + { + if(a[i].pos) link(NULL, a[i].tag, vec(0, 0, 0), 0, 0, a[i].pos); + continue; + } + part *p = m->parts[0]; + switch(linktype(m)) + { + case LINK_TAG: + p->index = link(p, a[i].tag, vec(0, 0, 0), a[i].anim, a[i].basetime, a[i].pos) ? index : -1; + break; + + case LINK_COOP: + p->index = index; + break; + + default: + continue; + } + index += p->numanimparts; + } + } + + animstate as[MAXANIMPARTS]; + parts[0]->render(anim, basetime, basetime2, pitch, axis, forward, d, as); + + if(a) for(int i = numtags-1; i >= 0; i--) + { + animmodel *m = (animmodel *)a[i].m; + if(!m) + { + if(a[i].pos) unlink(NULL); + continue; + } + part *p = m->parts[0]; + switch(linktype(m)) + { + case LINK_TAG: + if(p->index >= 0) unlink(p); + p->index = 0; + break; + + case LINK_COOP: + p->render(anim, basetime, basetime2, pitch, axis, forward, d); + p->index = 0; + break; + + case LINK_REUSE: + p->render(anim | ANIM_REUSE, basetime, basetime2, pitch, axis, forward, d, as); + break; + } + } + } + + void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, dynent *d, modelattach *a, const vec &color, const vec &dir, float trans) + { + yaw += spinyaw*lastmillis/1000.0f; + pitch += offsetpitch + spinpitch*lastmillis/1000.0f; + + vec axis(0, -1, 0), forward(1, 0, 0); + + matrixpos = 0; + matrixstack[0].identity(); + if(!d || !d->ragdoll || anim&ANIM_RAGDOLL) + { + matrixstack[0].settranslation(o); + matrixstack[0].rotate_around_z(yaw*RAD); + matrixstack[0].transformnormal(vec(axis), axis); + matrixstack[0].transformnormal(vec(forward), forward); + if(offsetyaw) matrixstack[0].rotate_around_z(offsetyaw*RAD); + } + else pitch = 0; + + if(anim&ANIM_NORENDER) + { + render(anim, basetime, basetime2, pitch, axis, forward, d, a); + if(d) d->lastrendered = lastmillis; + return; + } + + if(!(anim&ANIM_NOSKIN)) + { + transparent = trans; + lightdir = dir; + lightcolor = color; + + if(envmapped()) + { + setupenvmap: + closestenvmaptex = lookupenvmap(closestenvmap(o)); + GLOBALPARAM(lightdirworld, dir); + } + else if(a) for(int i = 0; a[i].tag; i++) if(a[i].m && a[i].m->envmapped()) goto setupenvmap; + } + + if(depthoffset && !enabledepthoffset) + { + enablepolygonoffset(GL_POLYGON_OFFSET_FILL); + enabledepthoffset = true; + } + + if(transparent<1) + { + if(anim&ANIM_GHOST) + { + glDepthFunc(GL_GREATER); + glDepthMask(GL_FALSE); + } + else if(alphadepth) + { + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + render(anim|ANIM_NOSKIN, basetime, basetime2, pitch, axis, forward, d, a); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE); + + glDepthFunc(GL_LEQUAL); + } + + if(!enablealphablend) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + enablealphablend = true; + } + } + + render(anim, basetime, basetime2, pitch, axis, forward, d, a); + + if(transparent<1 && (alphadepth || anim&ANIM_GHOST)) + { + glDepthFunc(GL_LESS); + if(anim&ANIM_GHOST) glDepthMask(GL_TRUE); + } + + if(d) d->lastrendered = lastmillis; + } + + vector parts; + + animmodel(const char *name) : model(name) + { + } + + ~animmodel() + { + parts.deletecontents(); + } + + void cleanup() + { + loopv(parts) parts[i]->cleanup(); + } + + virtual void flushpart() {} + + part &addpart() + { + flushpart(); + part *p = new part(this, parts.length()); + parts.add(p); + return *p; + } + + void initmatrix(matrix4x3 &m) + { + m.identity(); + if(offsetyaw) m.rotate_around_z(offsetyaw*RAD); + if(offsetpitch) m.rotate_around_y(-offsetpitch*RAD); + } + + void genBIH(vector &bih) + { + if(parts.empty()) return; + matrix4x3 m; + initmatrix(m); + parts[0]->genBIH(bih, m); + } + + void preloadBIH() + { + model::preloadBIH(); + if(bih) loopv(parts) parts[i]->preloadBIH(); + } + + BIH *setBIH() + { + if(bih) return bih; + vector meshes; + genBIH(meshes); + bih = new BIH(meshes); + return bih; + } + + bool link(part *p, const char *tag, const vec &translate = vec(0, 0, 0), int anim = -1, int basetime = 0, vec *pos = NULL) + { + if(parts.empty()) return false; + return parts[0]->link(p, tag, translate, anim, basetime, pos); + } + + bool unlink(part *p) + { + if(parts.empty()) return false; + return parts[0]->unlink(p); + } + + bool envmapped() + { + loopv(parts) if(parts[i]->envmapped()) return true; + return false; + } + + virtual bool flipy() const { return false; } + virtual bool loadconfig() { return false; } + virtual bool loaddefaultparts() { return false; } + virtual void startload() {} + virtual void endload() {} + + bool load() + { + startload(); + bool success = loadconfig() && parts.length(); // configured model, will call the model commands below + if(!success) + success = loaddefaultparts(); // model without configuration, try default tris and skin + flushpart(); + endload(); + if(flipy()) translate.y = -translate.y; + + if(!success) return false; + loopv(parts) if(!parts[i]->meshes) return false; + + loaded(); + return true; + } + + void preloadshaders(bool force) + { + loopv(parts) parts[i]->preloadshaders(force); + } + + void preloadmeshes() + { + loopv(parts) parts[i]->preloadmeshes(); + } + + void setshader(Shader *shader) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].shader = shader; + } + + void setenvmap(float envmapmin, float envmapmax, Texture *envmap) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) + { + skin &s = parts[i]->skins[j]; + if(envmapmax) + { + s.envmapmin = envmapmin; + s.envmapmax = envmapmax; + } + if(envmap) s.envmap = envmap; + } + } + + void setspec(float spec) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].spec = spec; + } + + void setambient(float ambient) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].ambient = ambient; + } + + void setglow(float glow, float delta, float pulse) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) + { + skin &s = parts[i]->skins[j]; + s.glow = glow; + s.glowdelta = delta; + s.glowpulse = pulse; + } + } + + void setglare(float specglare, float glowglare) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) + { + skin &s = parts[i]->skins[j]; + s.specglare = specglare; + s.glowglare = glowglare; + } + } + + void setalphatest(float alphatest) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].alphatest = alphatest; + } + + void setalphablend(bool alphablend) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].alphablend = alphablend; + } + + void setfullbright(float fullbright) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].fullbright = fullbright; + } + + void setcullface(bool cullface) + { + if(parts.empty()) loaddefaultparts(); + loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].cullface = cullface; + } + + void calcbb(vec ¢er, vec &radius) + { + if(parts.empty()) return; + vec bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f); + matrix4x3 m; + initmatrix(m); + parts[0]->calcbb(bbmin, bbmax, m); + radius = bbmax; + radius.sub(bbmin); + radius.mul(0.5f); + center = bbmin; + center.add(radius); + } + + virtual void loaded() + { + scale /= 4; + if(parts.length()) parts[0]->translate = translate; + loopv(parts) parts[i]->loaded(); + } + + static bool enabletc, enablealphablend, enablecullface, enablenormals, enabletangents, enablebones, enabledepthoffset; + static vec lightdir, lightcolor; + static float transparent, lastalphatest; + static GLuint lastvbuf, lasttcbuf, lastnbuf, lastxbuf, lastbbuf, lastebuf, lastenvmaptex, closestenvmaptex; + static Texture *lasttex, *lastmasks, *lastnormalmap; + static int matrixpos; + static matrix4 matrixstack[64]; + + void startrender() + { + enabletc = enablealphablend = enablenormals = enabletangents = enablebones = enabledepthoffset = false; + enablecullface = true; + lastalphatest = -1; + lastvbuf = lasttcbuf = lastxbuf = lastnbuf = lastbbuf = lastebuf = lastenvmaptex = closestenvmaptex = 0; + lasttex = lastmasks = lastnormalmap = NULL; + transparent = 1; + shaderparamskey::invalidate(); + } + + static void disablebones() + { + gle::disableboneweight(); + gle::disableboneindex(); + enablebones = false; + } + + static void disabletangents() + { + gle::disabletangent(); + enabletangents = false; + } + + static void disabletc() + { + gle::disabletexcoord0(); + enabletc = false; + } + + static void disablenormals() + { + gle::disablenormal(); + enablenormals = false; + } + + static void disablevbo() + { + if(lastebuf) gle::clearebo(); + if(lastvbuf) + { + gle::clearvbo(); + gle::disablevertex(); + } + if(enabletc) disabletc(); + if(enablenormals) disablenormals(); + if(enabletangents) disabletangents(); + if(enablebones) disablebones(); + lastvbuf = lasttcbuf = lastxbuf = lastnbuf = lastbbuf = lastebuf = 0; + } + + void endrender() + { + if(lastvbuf || lastebuf) disablevbo(); + if(enablealphablend) glDisable(GL_BLEND); + if(!enablecullface) glEnable(GL_CULL_FACE); + if(enabledepthoffset) disablepolygonoffset(GL_POLYGON_OFFSET_FILL); + } }; bool animmodel::enabletc = false, animmodel::enablealphablend = false, - animmodel::enablecullface = true, - animmodel::enablenormals = false, animmodel::enabletangents = false, animmodel::enablebones = false, animmodel::enabledepthoffset = false; + animmodel::enablecullface = true, + animmodel::enablenormals = false, animmodel::enabletangents = false, animmodel::enablebones = false, animmodel::enabledepthoffset = false; vec animmodel::lightdir(0, 0, 1), animmodel::lightcolor(1, 1, 1); float animmodel::transparent = 1, animmodel::lastalphatest = -1; GLuint animmodel::lastvbuf = 0, animmodel::lasttcbuf = 0, animmodel::lastnbuf = 0, animmodel::lastxbuf = 0, animmodel::lastbbuf = 0, - animmodel::lastebuf = 0, animmodel::lastenvmaptex = 0, animmodel::closestenvmaptex = 0; + animmodel::lastebuf = 0, animmodel::lastenvmaptex = 0, animmodel::closestenvmaptex = 0; Texture *animmodel::lasttex = NULL, *animmodel::lastmasks = NULL, *animmodel::lastnormalmap = NULL; int animmodel::matrixpos = 0; matrix4 animmodel::matrixstack[64]; static inline uint hthash(const animmodel::shaderparams &k) { - return memhash(&k, sizeof(k)); + return memhash(&k, sizeof(k)); } static inline bool htcmp(const animmodel::shaderparams &x, const animmodel::shaderparams &y) { - return !memcmp(&x, &y, sizeof(animmodel::shaderparams)); + return !memcmp(&x, &y, sizeof(animmodel::shaderparams)); } hashtable animmodel::shaderparamskey::keys; @@ -1431,35 +1432,35 @@ int animmodel::shaderparamskey::firstversion = 0, animmodel::shaderparamskey::la template struct modelloader : BASE { - static MDL *loading; - static string dir; - - modelloader(const char *name) : BASE(name) {} - - static bool animated() { return true; } - static bool multiparted() { return true; } - static bool multimeshed() { return true; } - - void startload() - { - loading = (MDL *)this; - } - - void endload() - { - loading = NULL; - } - - bool loadconfig() - { - formatstring(dir, "packages/models/%s", BASE::name); - defformatstring(cfgname, "packages/models/%s/%s.cfg", BASE::name, MDL::formatname()); - - identflags &= ~IDF_PERSIST; - bool success = execfile(cfgname, false); - identflags |= IDF_PERSIST; - return success; - } + static MDL *loading; + static string dir; + + modelloader(const char *name) : BASE(name) {} + + static bool animated() { return true; } + static bool multiparted() { return true; } + static bool multimeshed() { return true; } + + void startload() + { + loading = (MDL *)this; + } + + void endload() + { + loading = NULL; + } + + bool loadconfig() + { + formatstring(dir, "packages/models/%s", BASE::name); + defformatstring(cfgname, "packages/models/%s/%s.cfg", BASE::name, MDL::formatname()); + + identflags &= ~IDF_PERSIST; + bool success = execfile(cfgname, false); + identflags |= IDF_PERSIST; + return success; + } }; template MDL *modelloader::loading = NULL; @@ -1467,154 +1468,154 @@ template string modelloader::dir = {'\0'}; // template struct modelcommands { - typedef struct MDL::part part; - typedef struct MDL::skin skin; - - static void setdir(char *name) - { - if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - formatstring(MDL::dir, "packages/models/%s", name); - } - - #define loopmeshes(meshname, m, body) \ - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } \ - part &mdl = *MDL::loading->parts.last(); \ - if(!mdl.meshes) return; \ - loopv(mdl.meshes->meshes) \ - { \ - MESH &m = *(MESH *)mdl.meshes->meshes[i]; \ - if(!strcmp(meshname, "*") || (m.name && !strcmp(m.name, meshname))) \ - { \ - body; \ - } \ - } - - #define loopskins(meshname, s, body) loopmeshes(meshname, m, { skin &s = mdl.skins[i]; body; }) - - static void setskin(char *meshname, char *tex, char *masks, float *envmapmax, float *envmapmin) - { - loopskins(meshname, s, - s.tex = textureload(makerelpath(MDL::dir, tex), 0, true, false); - if(*masks) - { - s.masks = textureload(makerelpath(MDL::dir, masks), 0, true, false); - s.envmapmax = *envmapmax; - s.envmapmin = *envmapmin; - } - ); - } - - static void setspec(char *meshname, int *percent) - { - float spec = 1.0f; - if(*percent>0) spec = *percent/100.0f; - else if(*percent<0) spec = 0.0f; - loopskins(meshname, s, s.spec = spec); - } - - static void setambient(char *meshname, int *percent) - { - float ambient = 0.3f; - if(*percent>0) ambient = *percent/100.0f; - else if(*percent<0) ambient = 0.0f; - loopskins(meshname, s, s.ambient = ambient); - } - - static void setglow(char *meshname, int *percent, int *delta, float *pulse) - { - float glow = 3.0f, glowdelta = *delta/100.0f, glowpulse = *pulse > 0 ? *pulse/1000.0f : 0; - if(*percent>0) glow = *percent/100.0f; - else if(*percent<0) glow = 0.0f; - glowdelta -= glow; - loopskins(meshname, s, { s.glow = glow; s.glowdelta = glowdelta; s.glowpulse = glowpulse; }); - } - - static void setglare(char *meshname, float *specglare, float *glowglare) - { - loopskins(meshname, s, { s.specglare = *specglare; s.glowglare = *glowglare; }); - } - - static void setalphatest(char *meshname, float *cutoff) - { - loopskins(meshname, s, s.alphatest = max(0.0f, min(1.0f, *cutoff))); - } - - static void setalphablend(char *meshname, int *blend) - { - loopskins(meshname, s, s.alphablend = *blend!=0); - } - - static void setcullface(char *meshname, int *cullface) - { - loopskins(meshname, s, s.cullface = *cullface!=0); - } - - static void setenvmap(char *meshname, char *envmap) - { - Texture *tex = cubemapload(envmap); - loopskins(meshname, s, s.envmap = tex); - } - - static void setbumpmap(char *meshname, char *normalmapfile) - { - Texture *normalmaptex = textureload(makerelpath(MDL::dir, normalmapfile), 0, true, false); - loopskins(meshname, s, s.normalmap = normalmaptex); - } - - static void setfullbright(char *meshname, float *fullbright) - { - loopskins(meshname, s, s.fullbright = *fullbright); - } - - static void setshader(char *meshname, char *shader) - { - loopskins(meshname, s, s.shader = lookupshaderbyname(shader)); - } - - static void setscroll(char *meshname, float *scrollu, float *scrollv) - { - loopskins(meshname, s, { s.scrollu = *scrollu; s.scrollv = *scrollv; }); - } - - static void setnoclip(char *meshname, int *noclip) - { - loopmeshes(meshname, m, m.noclip = *noclip!=0); - } - - static void setlink(int *parent, int *child, char *tagname, float *x, float *y, float *z) - { - if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - if(!MDL::loading->parts.inrange(*parent) || !MDL::loading->parts.inrange(*child)) { conoutf(CON_ERROR, "no models loaded to link"); return; } - if(!MDL::loading->parts[*parent]->link(MDL::loading->parts[*child], tagname, vec(*x, *y, *z))) conoutf(CON_ERROR, "could not link model %s", MDL::loading->name); - } - - template void modelcommand(F *fun, const char *suffix, const char *args) - { - defformatstring(name, "%s%s", MDL::formatname(), suffix); - addcommand(newstring(name), (void (*)())fun, args); - } - - modelcommands() - { - modelcommand(setdir, "dir", "s"); - if(MDL::multimeshed()) - { - modelcommand(setskin, "skin", "sssff"); - modelcommand(setspec, "spec", "si"); - modelcommand(setambient, "ambient", "si"); - modelcommand(setglow, "glow", "siif"); - modelcommand(setglare, "glare", "sff"); - modelcommand(setalphatest, "alphatest", "sf"); - modelcommand(setalphablend, "alphablend", "si"); - modelcommand(setcullface, "cullface", "si"); - modelcommand(setenvmap, "envmap", "ss"); - modelcommand(setbumpmap, "bumpmap", "ss"); - modelcommand(setfullbright, "fullbright", "sf"); - modelcommand(setshader, "shader", "ss"); - modelcommand(setscroll, "scroll", "sff"); - modelcommand(setnoclip, "noclip", "si"); - } - if(MDL::multiparted()) modelcommand(setlink, "link", "iisfff"); - } + typedef struct MDL::part part; + typedef struct MDL::skin skin; + + static void setdir(char *name) + { + if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + formatstring(MDL::dir, "packages/models/%s", name); + } + + #define loopmeshes(meshname, m, body) \ + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } \ + part &mdl = *MDL::loading->parts.last(); \ + if(!mdl.meshes) return; \ + loopv(mdl.meshes->meshes) \ + { \ + MESH &m = *(MESH *)mdl.meshes->meshes[i]; \ + if(!strcmp(meshname, "*") || (m.name && !strcmp(m.name, meshname))) \ + { \ + body; \ + } \ + } + + #define loopskins(meshname, s, body) loopmeshes(meshname, m, { skin &s = mdl.skins[i]; body; }) + + static void setskin(char *meshname, char *tex, char *masks, float *envmapmax, float *envmapmin) + { + loopskins(meshname, s, + s.tex = textureload(makerelpath(MDL::dir, tex), 0, true, false); + if(*masks) + { + s.masks = textureload(makerelpath(MDL::dir, masks), 0, true, false); + s.envmapmax = *envmapmax; + s.envmapmin = *envmapmin; + } + ); + } + + static void setspec(char *meshname, int *percent) + { + float spec = 1.0f; + if(*percent>0) spec = *percent/100.0f; + else if(*percent<0) spec = 0.0f; + loopskins(meshname, s, s.spec = spec); + } + + static void setambient(char *meshname, int *percent) + { + float ambient = 0.3f; + if(*percent>0) ambient = *percent/100.0f; + else if(*percent<0) ambient = 0.0f; + loopskins(meshname, s, s.ambient = ambient); + } + + static void setglow(char *meshname, int *percent, int *delta, float *pulse) + { + float glow = 3.0f, glowdelta = *delta/100.0f, glowpulse = *pulse > 0 ? *pulse/1000.0f : 0; + if(*percent>0) glow = *percent/100.0f; + else if(*percent<0) glow = 0.0f; + glowdelta -= glow; + loopskins(meshname, s, { s.glow = glow; s.glowdelta = glowdelta; s.glowpulse = glowpulse; }); + } + + static void setglare(char *meshname, float *specglare, float *glowglare) + { + loopskins(meshname, s, { s.specglare = *specglare; s.glowglare = *glowglare; }); + } + + static void setalphatest(char *meshname, float *cutoff) + { + loopskins(meshname, s, s.alphatest = max(0.0f, min(1.0f, *cutoff))); + } + + static void setalphablend(char *meshname, int *blend) + { + loopskins(meshname, s, s.alphablend = *blend!=0); + } + + static void setcullface(char *meshname, int *cullface) + { + loopskins(meshname, s, s.cullface = *cullface!=0); + } + + static void setenvmap(char *meshname, char *envmap) + { + Texture *tex = cubemapload(envmap); + loopskins(meshname, s, s.envmap = tex); + } + + static void setbumpmap(char *meshname, char *normalmapfile) + { + Texture *normalmaptex = textureload(makerelpath(MDL::dir, normalmapfile), 0, true, false); + loopskins(meshname, s, s.normalmap = normalmaptex); + } + + static void setfullbright(char *meshname, float *fullbright) + { + loopskins(meshname, s, s.fullbright = *fullbright); + } + + static void setshader(char *meshname, char *shader) + { + loopskins(meshname, s, s.shader = lookupshaderbyname(shader)); + } + + static void setscroll(char *meshname, float *scrollu, float *scrollv) + { + loopskins(meshname, s, { s.scrollu = *scrollu; s.scrollv = *scrollv; }); + } + + static void setnoclip(char *meshname, int *noclip) + { + loopmeshes(meshname, m, m.noclip = *noclip!=0); + } + + static void setlink(int *parent, int *child, char *tagname, float *x, float *y, float *z) + { + if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + if(!MDL::loading->parts.inrange(*parent) || !MDL::loading->parts.inrange(*child)) { conoutf(CON_ERROR, "no models loaded to link"); return; } + if(!MDL::loading->parts[*parent]->link(MDL::loading->parts[*child], tagname, vec(*x, *y, *z))) conoutf(CON_ERROR, "could not link model %s", MDL::loading->name); + } + + template void modelcommand(F *fun, const char *suffix, const char *args) + { + defformatstring(name, "%s%s", MDL::formatname(), suffix); + addcommand(newstring(name), (void (*)())fun, args); + } + + modelcommands() + { + modelcommand(setdir, "dir", "s"); + if(MDL::multimeshed()) + { + modelcommand(setskin, "skin", "sssff"); + modelcommand(setspec, "spec", "si"); + modelcommand(setambient, "ambient", "si"); + modelcommand(setglow, "glow", "siif"); + modelcommand(setglare, "glare", "sff"); + modelcommand(setalphatest, "alphatest", "sf"); + modelcommand(setalphablend, "alphablend", "si"); + modelcommand(setcullface, "cullface", "si"); + modelcommand(setenvmap, "envmap", "ss"); + modelcommand(setbumpmap, "bumpmap", "ss"); + modelcommand(setfullbright, "fullbright", "sf"); + modelcommand(setshader, "shader", "ss"); + modelcommand(setscroll, "scroll", "sff"); + modelcommand(setnoclip, "noclip", "si"); + } + if(MDL::multiparted()) modelcommand(setlink, "link", "iisfff"); + } }; diff --git a/src/engine/bih.cpp b/src/engine/bih.cpp index e735f38..c983805 100644 --- a/src/engine/bih.cpp +++ b/src/engine/bih.cpp @@ -2,329 +2,329 @@ bool BIH::triintersect(const mesh &m, int tidx, const vec &mo, const vec &mray, float maxdist, float &dist, int mode) { - const tri &t = m.tris[tidx]; - vec a = m.getpos(t.vert[0]), b = m.getpos(t.vert[1]).sub(a), c = m.getpos(t.vert[2]).sub(a), - n = vec().cross(b, c), r = vec(a).sub(mo), e = vec().cross(r, mray); - float det = mray.dot(n), v, w, f; - if(det >= 0) - { - if(!(mode&RAY_SHADOW) && m.flags&MESH_CULLFACE) return false; - v = e.dot(c); - if(v < 0 || v > det) return false; - w = -e.dot(b); - if(w < 0 || v + w > det) return false; - f = r.dot(n)*m.scale; - if(f < 0 || f > maxdist*det || !det) return false; - } - else - { - v = e.dot(c); - if(v > 0 || v < det) return false; - w = -e.dot(b); - if(w > 0 || v + w < det) return false; - f = r.dot(n)*m.scale; - if(f > 0 || f < maxdist*det) return false; - } - float invdet = 1/det; - if(m.flags&MESH_ALPHA && (mode&RAY_ALPHAPOLY)==RAY_ALPHAPOLY && (m.tex->alphamask || (lightmapping <= 1 && loadalphamask(m.tex)))) - { - vec2 at = m.gettc(t.vert[0]), bt = m.gettc(t.vert[1]).sub(at).mul(v*invdet), ct = m.gettc(t.vert[2]).sub(at).mul(w*invdet); - at.add(bt).add(ct); - int si = clamp(int(m.tex->xs * at.x), 0, m.tex->xs-1), - ti = clamp(int(m.tex->ys * at.y), 0, m.tex->ys-1); - if(!(m.tex->alphamask[ti*((m.tex->xs+7)/8) + si/8] & (1<<(si%8)))) return false; - } - dist = f*invdet; - return true; + const tri &t = m.tris[tidx]; + vec a = m.getpos(t.vert[0]), b = m.getpos(t.vert[1]).sub(a), c = m.getpos(t.vert[2]).sub(a), + n = vec().cross(b, c), r = vec(a).sub(mo), e = vec().cross(r, mray); + float det = mray.dot(n), v, w, f; + if(det >= 0) + { + if(!(mode&RAY_SHADOW) && m.flags&MESH_CULLFACE) return false; + v = e.dot(c); + if(v < 0 || v > det) return false; + w = -e.dot(b); + if(w < 0 || v + w > det) return false; + f = r.dot(n)*m.scale; + if(f < 0 || f > maxdist*det || !det) return false; + } + else + { + v = e.dot(c); + if(v > 0 || v < det) return false; + w = -e.dot(b); + if(w > 0 || v + w < det) return false; + f = r.dot(n)*m.scale; + if(f > 0 || f < maxdist*det) return false; + } + float invdet = 1/det; + if(m.flags&MESH_ALPHA && (mode&RAY_ALPHAPOLY)==RAY_ALPHAPOLY && (m.tex->alphamask || (lightmapping <= 1 && loadalphamask(m.tex)))) + { + vec2 at = m.gettc(t.vert[0]), bt = m.gettc(t.vert[1]).sub(at).mul(v*invdet), ct = m.gettc(t.vert[2]).sub(at).mul(w*invdet); + at.add(bt).add(ct); + int si = clamp(int(m.tex->xs * at.x), 0, m.tex->xs-1), + ti = clamp(int(m.tex->ys * at.y), 0, m.tex->ys-1); + if(!(m.tex->alphamask[ti*((m.tex->xs+7)/8) + si/8] & (1<<(si%8)))) return false; + } + dist = f*invdet; + return true; } struct traversestate { - BIH::node *node; - float tmin, tmax; + BIH::node *node; + float tmin, tmax; }; inline bool BIH::traverse(const mesh &m, const vec &o, const vec &ray, const vec &invray, float maxdist, float &dist, int mode, node *curnode, float tmin, float tmax) { - traversestate stack[128]; - int stacksize = 0; - ivec order(ray.x>0 ? 0 : 1, ray.y>0 ? 0 : 1, ray.z>0 ? 0 : 1); - vec mo = m.invxform.transform(o), mray = m.invxformnorm.transform(ray); - for(;;) - { - int axis = curnode->axis(); - int nearidx = order[axis], faridx = nearidx^1; - float nearsplit = (curnode->split[nearidx] - o[axis])*invray[axis], - farsplit = (curnode->split[faridx] - o[axis])*invray[axis]; + traversestate stack[128]; + int stacksize = 0; + ivec order(ray.x>0 ? 0 : 1, ray.y>0 ? 0 : 1, ray.z>0 ? 0 : 1); + vec mo = m.invxform.transform(o), mray = m.invxformnorm.transform(ray); + for(;;) + { + int axis = curnode->axis(); + int nearidx = order[axis], faridx = nearidx^1; + float nearsplit = (curnode->split[nearidx] - o[axis])*invray[axis], + farsplit = (curnode->split[faridx] - o[axis])*invray[axis]; - if(nearsplit <= tmin) - { - if(farsplit < tmax) - { - if(!curnode->isleaf(faridx)) - { - curnode += curnode->childindex(faridx); - tmin = max(tmin, farsplit); - continue; - } - else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true; - } - } - else if(curnode->isleaf(nearidx)) - { - if(triintersect(m, curnode->childindex(nearidx), mo, mray, maxdist, dist, mode)) return true; - if(farsplit < tmax) - { - if(!curnode->isleaf(faridx)) - { - curnode += curnode->childindex(faridx); - tmin = max(tmin, farsplit); - continue; - } - else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true; - } - } - else - { - if(farsplit < tmax) - { - if(!curnode->isleaf(faridx)) - { - if(stacksize < int(sizeof(stack)/sizeof(stack[0]))) - { - traversestate &save = stack[stacksize++]; - save.node = curnode + curnode->childindex(faridx); - save.tmin = max(tmin, farsplit); - save.tmax = tmax; - } - else - { - if(traverse(m, o, ray, invray, maxdist, dist, mode, curnode + curnode->childindex(nearidx), tmin, min(tmax, nearsplit))) return true; - curnode += curnode->childindex(faridx); - tmin = max(tmin, farsplit); - continue; - } - } - else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true; - } - curnode += curnode->childindex(nearidx); - tmax = min(tmax, nearsplit); - continue; - } - if(stacksize <= 0) return false; - traversestate &restore = stack[--stacksize]; - curnode = restore.node; - tmin = restore.tmin; - tmax = restore.tmax; - } + if(nearsplit <= tmin) + { + if(farsplit < tmax) + { + if(!curnode->isleaf(faridx)) + { + curnode += curnode->childindex(faridx); + tmin = max(tmin, farsplit); + continue; + } + else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true; + } + } + else if(curnode->isleaf(nearidx)) + { + if(triintersect(m, curnode->childindex(nearidx), mo, mray, maxdist, dist, mode)) return true; + if(farsplit < tmax) + { + if(!curnode->isleaf(faridx)) + { + curnode += curnode->childindex(faridx); + tmin = max(tmin, farsplit); + continue; + } + else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true; + } + } + else + { + if(farsplit < tmax) + { + if(!curnode->isleaf(faridx)) + { + if(stacksize < int(sizeof(stack)/sizeof(stack[0]))) + { + traversestate &save = stack[stacksize++]; + save.node = curnode + curnode->childindex(faridx); + save.tmin = max(tmin, farsplit); + save.tmax = tmax; + } + else + { + if(traverse(m, o, ray, invray, maxdist, dist, mode, curnode + curnode->childindex(nearidx), tmin, min(tmax, nearsplit))) return true; + curnode += curnode->childindex(faridx); + tmin = max(tmin, farsplit); + continue; + } + } + else if(triintersect(m, curnode->childindex(faridx), mo, mray, maxdist, dist, mode)) return true; + } + curnode += curnode->childindex(nearidx); + tmax = min(tmax, nearsplit); + continue; + } + if(stacksize <= 0) return false; + traversestate &restore = stack[--stacksize]; + curnode = restore.node; + tmin = restore.tmin; + tmax = restore.tmax; + } } inline bool BIH::traverse(const vec &o, const vec &ray, float maxdist, float &dist, int mode) { - vec invray(ray.x ? 1/ray.x : 1e16f, ray.y ? 1/ray.y : 1e16f, ray.z ? 1/ray.z : 1e16f); - loopi(nummeshes) - { - mesh &m = meshes[i]; - if(!(mode&RAY_SHADOW) && m.flags&MESH_NOCLIP) continue; - float t1 = (m.bbmin.x - o.x)*invray.x, - t2 = (m.bbmax.x - o.x)*invray.x, - tmin, tmax; - if(invray.x > 0) { tmin = t1; tmax = t2; } else { tmin = t2; tmax = t1; } - t1 = (m.bbmin.y - o.y)*invray.y; - t2 = (m.bbmax.y - o.y)*invray.y; - if(invray.y > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); } - t1 = (m.bbmin.z - o.z)*invray.z; - t2 = (m.bbmax.z - o.z)*invray.z; - if(invray.z > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); } - tmax = min(tmax, maxdist); - if(tmin < tmax && traverse(m, o, ray, invray, maxdist, dist, mode, m.nodes, tmin, tmax)) return true; - } - return false; + vec invray(ray.x ? 1/ray.x : 1e16f, ray.y ? 1/ray.y : 1e16f, ray.z ? 1/ray.z : 1e16f); + loopi(nummeshes) + { + mesh &m = meshes[i]; + if(!(mode&RAY_SHADOW) && m.flags&MESH_NOCLIP) continue; + float t1 = (m.bbmin.x - o.x)*invray.x, + t2 = (m.bbmax.x - o.x)*invray.x, + tmin, tmax; + if(invray.x > 0) { tmin = t1; tmax = t2; } else { tmin = t2; tmax = t1; } + t1 = (m.bbmin.y - o.y)*invray.y; + t2 = (m.bbmax.y - o.y)*invray.y; + if(invray.y > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); } + t1 = (m.bbmin.z - o.z)*invray.z; + t2 = (m.bbmax.z - o.z)*invray.z; + if(invray.z > 0) { tmin = max(tmin, t1); tmax = min(tmax, t2); } else { tmin = max(tmin, t2); tmax = min(tmax, t1); } + tmax = min(tmax, maxdist); + if(tmin < tmax && traverse(m, o, ray, invray, maxdist, dist, mode, m.nodes, tmin, tmax)) return true; + } + return false; } void BIH::build(mesh &m, ushort *indices, int numindices, const ivec &vmin, const ivec &vmax) { - int axis = 2; - loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k; + int axis = 2; + loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k; - ivec leftmin, leftmax, rightmin, rightmax; - int splitleft, splitright; - int left, right; - loopk(3) - { - leftmin = rightmin = ivec(INT_MAX, INT_MAX, INT_MAX); - leftmax = rightmax = ivec(INT_MIN, INT_MIN, INT_MIN); - int split = (vmax[axis] + vmin[axis])/2; - for(left = 0, right = numindices, splitleft = SHRT_MIN, splitright = SHRT_MAX; left < right;) - { - const tribb &tri = m.tribbs[indices[left]]; - ivec trimin = ivec(tri.center).sub(ivec(tri.radius)), - trimax = ivec(tri.center).add(ivec(tri.radius)); - int amin = trimin[axis], amax = trimax[axis]; - if(max(split - amin, 0) > max(amax - split, 0)) - { - ++left; - splitleft = max(splitleft, amax); - leftmin.min(trimin); - leftmax.max(trimax); - } - else - { - --right; - swap(indices[left], indices[right]); - splitright = min(splitright, amin); - rightmin.min(trimin); - rightmax.max(trimax); - } - } - if(left > 0 && right < numindices) break; - axis = (axis+1)%3; - } + ivec leftmin, leftmax, rightmin, rightmax; + int splitleft, splitright; + int left, right; + loopk(3) + { + leftmin = rightmin = ivec(INT_MAX, INT_MAX, INT_MAX); + leftmax = rightmax = ivec(INT_MIN, INT_MIN, INT_MIN); + int split = (vmax[axis] + vmin[axis])/2; + for(left = 0, right = numindices, splitleft = SHRT_MIN, splitright = SHRT_MAX; left < right;) + { + const tribb &tri = m.tribbs[indices[left]]; + ivec trimin = ivec(tri.center).sub(ivec(tri.radius)), + trimax = ivec(tri.center).add(ivec(tri.radius)); + int amin = trimin[axis], amax = trimax[axis]; + if(max(split - amin, 0) > max(amax - split, 0)) + { + ++left; + splitleft = max(splitleft, amax); + leftmin.min(trimin); + leftmax.max(trimax); + } + else + { + --right; + swap(indices[left], indices[right]); + splitright = min(splitright, amin); + rightmin.min(trimin); + rightmax.max(trimax); + } + } + if(left > 0 && right < numindices) break; + axis = (axis+1)%3; + } - if(!left || right==numindices) - { - leftmin = rightmin = ivec(INT_MAX, INT_MAX, INT_MAX); - leftmax = rightmax = ivec(INT_MIN, INT_MIN, INT_MIN); - left = right = numindices/2; - splitleft = SHRT_MIN; - splitright = SHRT_MAX; - loopi(numindices) - { - const tribb &tri = m.tribbs[indices[i]]; - ivec trimin = ivec(tri.center).sub(ivec(tri.radius)), - trimax = ivec(tri.center).add(ivec(tri.radius)); - if(i < left) - { - splitleft = max(splitleft, trimax[axis]); - leftmin.min(trimin); - leftmax.max(trimax); - } - else - { - splitright = min(splitright, trimin[axis]); - rightmin.min(trimin); - rightmax.max(trimax); - } - } - } + if(!left || right==numindices) + { + leftmin = rightmin = ivec(INT_MAX, INT_MAX, INT_MAX); + leftmax = rightmax = ivec(INT_MIN, INT_MIN, INT_MIN); + left = right = numindices/2; + splitleft = SHRT_MIN; + splitright = SHRT_MAX; + loopi(numindices) + { + const tribb &tri = m.tribbs[indices[i]]; + ivec trimin = ivec(tri.center).sub(ivec(tri.radius)), + trimax = ivec(tri.center).add(ivec(tri.radius)); + if(i < left) + { + splitleft = max(splitleft, trimax[axis]); + leftmin.min(trimin); + leftmax.max(trimax); + } + else + { + splitright = min(splitright, trimin[axis]); + rightmin.min(trimin); + rightmax.max(trimax); + } + } + } - int offset = m.numnodes++; - node &curnode = m.nodes[offset]; - curnode.split[0] = short(splitleft); - curnode.split[1] = short(splitright); + int offset = m.numnodes++; + node &curnode = m.nodes[offset]; + curnode.split[0] = short(splitleft); + curnode.split[1] = short(splitright); - if(left==1) curnode.child[0] = (axis<<14) | indices[0]; - else - { - curnode.child[0] = (axis<<14) | (m.numnodes - offset); - build(m, indices, left, leftmin, leftmax); - } + if(left==1) curnode.child[0] = (axis<<14) | indices[0]; + else + { + curnode.child[0] = (axis<<14) | (m.numnodes - offset); + build(m, indices, left, leftmin, leftmax); + } - if(numindices-right==1) curnode.child[1] = (1<<15) | (left==1 ? 1<<14 : 0) | indices[right]; - else - { - curnode.child[1] = (left==1 ? 1<<14 : 0) | (m.numnodes - offset); - build(m, &indices[right], numindices-right, rightmin, rightmax); - } + if(numindices-right==1) curnode.child[1] = (1<<15) | (left==1 ? 1<<14 : 0) | indices[right]; + else + { + curnode.child[1] = (left==1 ? 1<<14 : 0) | (m.numnodes - offset); + build(m, &indices[right], numindices-right, rightmin, rightmax); + } } BIH::BIH(vector &buildmeshes) : meshes(NULL), nummeshes(0), nodes(NULL), numnodes(0), tribbs(NULL), numtris(0), bbmin(1e16f, 1e16f, 1e16f), bbmax(-1e16f, -1e16f, -1e16f), center(0, 0, 0), radius(0), entradius(0) { - if(buildmeshes.empty()) return; - loopv(buildmeshes) numtris += buildmeshes[i].numtris; - if(!numtris) return; + if(buildmeshes.empty()) return; + loopv(buildmeshes) numtris += buildmeshes[i].numtris; + if(!numtris) return; - nummeshes = buildmeshes.length(); - meshes = new mesh[nummeshes]; - memcpy(meshes, buildmeshes.getbuf(), sizeof(mesh)*buildmeshes.length()); - tribbs = new tribb[numtris]; - tribb *dsttri = tribbs; - loopi(nummeshes) - { - mesh &m = meshes[i]; - m.scale = m.xform.a.magnitude(); - m.invscale = 1/m.scale; - m.xformnorm = matrix3(m.xform); - m.xformnorm.normalize(); - m.invxform.invert(m.xform); - m.invxformnorm = matrix3(m.invxform); - m.invxformnorm.normalize(); - m.tribbs = dsttri; - const tri *srctri = m.tris; - vec mmin(1e16f, 1e16f, 1e16f), mmax(-1e16f, -1e16f, -1e16f); - loopj(m.numtris) - { - vec s0 = m.getpos(srctri->vert[0]), s1 = m.getpos(srctri->vert[1]), s2 = m.getpos(srctri->vert[2]), - v0 = m.xform.transform(s0), v1 = m.xform.transform(s1), v2 = m.xform.transform(s2), - vmin = vec(v0).min(v1).min(v2), - vmax = vec(v0).max(v1).max(v2); - mmin.min(vmin); - mmax.max(vmax); - ivec imin = ivec::floor(vmin), imax = ivec::ceil(vmax); - dsttri->center = svec(ivec(imin).add(imax).div(2)); - dsttri->radius = svec(ivec(imax).sub(imin).add(1).div(2)); - ++srctri; - ++dsttri; - } - loopk(3) if(fabs(mmax[k] - mmin[k]) < 0.125f) - { - float mid = (mmin[k] + mmax[k]) / 2; - mmin[k] = mid - 0.0625f; - mmax[k] = mid + 0.0625f; - } - m.bbmin = mmin; - m.bbmax = mmax; - bbmin.min(mmin); - bbmax.max(mmax); - } + nummeshes = buildmeshes.length(); + meshes = new mesh[nummeshes]; + memcpy(meshes, buildmeshes.getbuf(), sizeof(mesh)*buildmeshes.length()); + tribbs = new tribb[numtris]; + tribb *dsttri = tribbs; + loopi(nummeshes) + { + mesh &m = meshes[i]; + m.scale = m.xform.a.magnitude(); + m.invscale = 1/m.scale; + m.xformnorm = matrix3(m.xform); + m.xformnorm.normalize(); + m.invxform.invert(m.xform); + m.invxformnorm = matrix3(m.invxform); + m.invxformnorm.normalize(); + m.tribbs = dsttri; + const tri *srctri = m.tris; + vec mmin(1e16f, 1e16f, 1e16f), mmax(-1e16f, -1e16f, -1e16f); + loopj(m.numtris) + { + vec s0 = m.getpos(srctri->vert[0]), s1 = m.getpos(srctri->vert[1]), s2 = m.getpos(srctri->vert[2]), + v0 = m.xform.transform(s0), v1 = m.xform.transform(s1), v2 = m.xform.transform(s2), + vmin = vec(v0).min(v1).min(v2), + vmax = vec(v0).max(v1).max(v2); + mmin.min(vmin); + mmax.max(vmax); + ivec imin = ivec::floor(vmin), imax = ivec::ceil(vmax); + dsttri->center = svec(ivec(imin).add(imax).div(2)); + dsttri->radius = svec(ivec(imax).sub(imin).add(1).div(2)); + ++srctri; + ++dsttri; + } + loopk(3) if(fabs(mmax[k] - mmin[k]) < 0.125f) + { + float mid = (mmin[k] + mmax[k]) / 2; + mmin[k] = mid - 0.0625f; + mmax[k] = mid + 0.0625f; + } + m.bbmin = mmin; + m.bbmax = mmax; + bbmin.min(mmin); + bbmax.max(mmax); + } - center = vec(bbmin).add(bbmax).mul(0.5f); - radius = vec(bbmax).sub(bbmin).mul(0.5f).magnitude(); - entradius = max(bbmin.squaredlen(), bbmax.squaredlen()); + center = vec(bbmin).add(bbmax).mul(0.5f); + radius = vec(bbmax).sub(bbmin).mul(0.5f).magnitude(); + entradius = max(bbmin.squaredlen(), bbmax.squaredlen()); - nodes = new node[numtris]; - node *curnode = nodes; - ushort *indices = new ushort[numtris]; - loopi(nummeshes) - { - mesh &m = meshes[i]; - m.nodes = curnode; - loopj(m.numtris) indices[j] = j; - build(m, indices, m.numtris, ivec::floor(m.bbmin), ivec::ceil(m.bbmax)); - curnode += m.numnodes; - } - delete[] indices; - numnodes = int(curnode - nodes); + nodes = new node[numtris]; + node *curnode = nodes; + ushort *indices = new ushort[numtris]; + loopi(nummeshes) + { + mesh &m = meshes[i]; + m.nodes = curnode; + loopj(m.numtris) indices[j] = j; + build(m, indices, m.numtris, ivec::floor(m.bbmin), ivec::ceil(m.bbmax)); + curnode += m.numnodes; + } + delete[] indices; + numnodes = int(curnode - nodes); } BIH::~BIH() { - delete[] meshes; - delete[] nodes; - delete[] tribbs; + delete[] meshes; + delete[] nodes; + delete[] tribbs; } bool mmintersect(const extentity &e, const vec &o, const vec &ray, float maxdist, int mode, float &dist) { - model *m = loadmapmodel(e.attr2); - if(!m) return false; - if(mode&RAY_SHADOW) - { - if(!m->shadow || e.flags&EF_NOSHADOW) return false; - } - else if((mode&RAY_ENTS)!=RAY_ENTS && (!m->collide || e.flags&EF_NOCOLLIDE)) return false; - if(!m->bih && (lightmapping > 1 || !m->setBIH())) return false; - vec mo = vec(o).sub(e.o), mray(ray); - float v = mo.dot(mray), inside = m->bih->entradius - mo.squaredlen(); - if((inside < 0 && v > 0) || inside + v*v < 0) return false; - int yaw = e.attr1; - if(yaw != 0) - { - const vec2 &rot = sincosmod360(-yaw); - mo.rotate_around_z(rot); - mray.rotate_around_z(rot); - } - return m->bih->traverse(mo, mray, maxdist ? maxdist : 1e16f, dist, mode); + model *m = loadmapmodel(e.attr2); + if(!m) return false; + if(mode&RAY_SHADOW) + { + if(!m->shadow || e.flags&EF_NOSHADOW) return false; + } + else if((mode&RAY_ENTS)!=RAY_ENTS && (!m->collide || e.flags&EF_NOCOLLIDE)) return false; + if(!m->bih && (lightmapping > 1 || !m->setBIH())) return false; + vec mo = vec(o).sub(e.o), mray(ray); + float v = mo.dot(mray), inside = m->bih->entradius - mo.squaredlen(); + if((inside < 0 && v > 0) || inside + v*v < 0) return false; + int yaw = e.attr1; + if(yaw != 0) + { + const vec2 &rot = sincosmod360(-yaw); + mo.rotate_around_z(rot); + mray.rotate_around_z(rot); + } + return m->bih->traverse(mo, mray, maxdist ? maxdist : 1e16f, dist, mode); } diff --git a/src/engine/bih.h b/src/engine/bih.h index 971e64b..16da278 100644 --- a/src/engine/bih.h +++ b/src/engine/bih.h @@ -1,78 +1,78 @@ struct BIH { - struct node - { - short split[2]; - ushort child[2]; - - int axis() const { return child[0]>>14; } - int childindex(int which) const { return child[which]&0x3FFF; } - bool isleaf(int which) const { return (child[1]&(1<<(14+which)))!=0; } - }; - - struct tri - { - ushort vert[3]; - }; - - struct tribb - { - svec center, radius; - - bool outside(const ivec &bo, const ivec &br) const - { - return abs(bo.x - center.x) > br.x + radius.x || - abs(bo.y - center.y) > br.y + radius.y || - abs(bo.z - center.z) > br.z + radius.z; - } - }; - - enum { MESH_NOCLIP = 1<<0, MESH_ALPHA = 1<<1, MESH_CULLFACE = 1<<2 }; - - struct mesh - { - enum { MAXTRIS = 1<<14 }; - - matrix4x3 xform, invxform; - matrix3 xformnorm, invxformnorm; - float scale, invscale; - node *nodes; - int numnodes; - const tri *tris; - const tribb *tribbs; - int numtris; - const uchar *pos, *tc; - int posstride, tcstride; - Texture *tex; - int flags; - vec bbmin, bbmax; - - mesh() : numnodes(0), numtris(0), tex(NULL), flags(0) {} - - vec getpos(int i) const { return *(const vec *)(pos + i*posstride); } - vec2 gettc(int i) const { return *(const vec2 *)(tc + i*tcstride); } - }; - - mesh *meshes; - int nummeshes; - node *nodes; - int numnodes; - tribb *tribbs; - int numtris; - vec bbmin, bbmax, center; - float radius, entradius; - - BIH(vector &buildmeshes); - - ~BIH(); - - void build(mesh &m, ushort *indices, int numindices, const ivec &vmin, const ivec &vmax); - - bool traverse(const vec &o, const vec &ray, float maxdist, float &dist, int mode); - bool traverse(const mesh &m, const vec &o, const vec &ray, const vec &invray, float maxdist, float &dist, int mode, node *curnode, float tmin, float tmax); - bool triintersect(const mesh &m, int tidx, const vec &mo, const vec &mray, float maxdist, float &dist, int mode); - - void preload(); + struct node + { + short split[2]; + ushort child[2]; + + int axis() const { return child[0]>>14; } + int childindex(int which) const { return child[which]&0x3FFF; } + bool isleaf(int which) const { return (child[1]&(1<<(14+which)))!=0; } + }; + + struct tri + { + ushort vert[3]; + }; + + struct tribb + { + svec center, radius; + + bool outside(const ivec &bo, const ivec &br) const + { + return abs(bo.x - center.x) > br.x + radius.x || + abs(bo.y - center.y) > br.y + radius.y || + abs(bo.z - center.z) > br.z + radius.z; + } + }; + + enum { MESH_NOCLIP = 1<<0, MESH_ALPHA = 1<<1, MESH_CULLFACE = 1<<2 }; + + struct mesh + { + enum { MAXTRIS = 1<<14 }; + + matrix4x3 xform, invxform; + matrix3 xformnorm, invxformnorm; + float scale, invscale; + node *nodes; + int numnodes; + const tri *tris; + const tribb *tribbs; + int numtris; + const uchar *pos, *tc; + int posstride, tcstride; + Texture *tex; + int flags; + vec bbmin, bbmax; + + mesh() : numnodes(0), numtris(0), tex(NULL), flags(0) {} + + vec getpos(int i) const { return *(const vec *)(pos + i*posstride); } + vec2 gettc(int i) const { return *(const vec2 *)(tc + i*tcstride); } + }; + + mesh *meshes; + int nummeshes; + node *nodes; + int numnodes; + tribb *tribbs; + int numtris; + vec bbmin, bbmax, center; + float radius, entradius; + + BIH(vector &buildmeshes); + + ~BIH(); + + void build(mesh &m, ushort *indices, int numindices, const ivec &vmin, const ivec &vmax); + + bool traverse(const vec &o, const vec &ray, float maxdist, float &dist, int mode); + bool traverse(const mesh &m, const vec &o, const vec &ray, const vec &invray, float maxdist, float &dist, int mode, node *curnode, float tmin, float tmax); + bool triintersect(const mesh &m, int tidx, const vec &mo, const vec &mray, float maxdist, float &dist, int mode); + + void preload(); }; extern bool mmintersect(const extentity &e, const vec &o, const vec &ray, float maxdist, int mode, float &dist); diff --git a/src/engine/blend.cpp b/src/engine/blend.cpp index 16f21c2..4d83ef6 100644 --- a/src/engine/blend.cpp +++ b/src/engine/blend.cpp @@ -2,9 +2,9 @@ enum { - BM_BRANCH = 0, - BM_SOLID, - BM_IMAGE + BM_BRANCH = 0, + BM_SOLID, + BM_IMAGE }; struct BlendMapBranch; @@ -13,35 +13,35 @@ struct BlendMapImage; struct BlendMapNode { - union - { - BlendMapBranch *branch; - BlendMapSolid *solid; - BlendMapImage *image; - }; + union + { + BlendMapBranch *branch; + BlendMapSolid *solid; + BlendMapImage *image; + }; - void cleanup(int type); - void splitsolid(uchar &type, uchar val); + void cleanup(int type); + void splitsolid(uchar &type, uchar val); }; struct BlendMapBranch { - uchar type[4]; - BlendMapNode children[4]; + uchar type[4]; + BlendMapNode children[4]; - ~BlendMapBranch() - { - loopi(4) children[i].cleanup(type[i]); - } + ~BlendMapBranch() + { + loopi(4) children[i].cleanup(type[i]); + } - uchar shrink(BlendMapNode &child, int quadrant); + uchar shrink(BlendMapNode &child, int quadrant); }; struct BlendMapSolid { - uchar val; + uchar val; - BlendMapSolid(uchar val) : val(val) {} + BlendMapSolid(uchar val) : val(val) {} }; #define BM_SCALE 1 @@ -49,76 +49,76 @@ struct BlendMapSolid struct BlendMapImage { - uchar data[BM_IMAGE_SIZE*BM_IMAGE_SIZE]; + uchar data[BM_IMAGE_SIZE*BM_IMAGE_SIZE]; }; void BlendMapNode::cleanup(int type) { - switch(type) - { - case BM_BRANCH: delete branch; break; - case BM_IMAGE: delete image; break; - } + switch(type) + { + case BM_BRANCH: delete branch; break; + case BM_IMAGE: delete image; break; + } } #define DEFBMSOLIDS(n) n, n+1, n+2, n+3, n+4, n+5, n+6, n+7, n+8, n+9, n+10, n+11, n+12, n+13, n+14, n+15 -static BlendMapSolid bmsolids[256] = +static BlendMapSolid bmsolids[256] = { - DEFBMSOLIDS(0x00), DEFBMSOLIDS(0x10), DEFBMSOLIDS(0x20), DEFBMSOLIDS(0x30), - DEFBMSOLIDS(0x40), DEFBMSOLIDS(0x50), DEFBMSOLIDS(0x60), DEFBMSOLIDS(0x70), - DEFBMSOLIDS(0x80), DEFBMSOLIDS(0x90), DEFBMSOLIDS(0xA0), DEFBMSOLIDS(0xB0), - DEFBMSOLIDS(0xC0), DEFBMSOLIDS(0xD0), DEFBMSOLIDS(0xE0), DEFBMSOLIDS(0xF0), + DEFBMSOLIDS(0x00), DEFBMSOLIDS(0x10), DEFBMSOLIDS(0x20), DEFBMSOLIDS(0x30), + DEFBMSOLIDS(0x40), DEFBMSOLIDS(0x50), DEFBMSOLIDS(0x60), DEFBMSOLIDS(0x70), + DEFBMSOLIDS(0x80), DEFBMSOLIDS(0x90), DEFBMSOLIDS(0xA0), DEFBMSOLIDS(0xB0), + DEFBMSOLIDS(0xC0), DEFBMSOLIDS(0xD0), DEFBMSOLIDS(0xE0), DEFBMSOLIDS(0xF0), }; void BlendMapNode::splitsolid(uchar &type, uchar val) { - cleanup(type); - type = BM_BRANCH; - branch = new BlendMapBranch; - loopi(4) - { - branch->type[i] = BM_SOLID; - branch->children[i].solid = &bmsolids[val]; - } + cleanup(type); + type = BM_BRANCH; + branch = new BlendMapBranch; + loopi(4) + { + branch->type[i] = BM_SOLID; + branch->children[i].solid = &bmsolids[val]; + } } uchar BlendMapBranch::shrink(BlendMapNode &child, int quadrant) { - uchar childtype = type[quadrant]; - child = children[quadrant]; - type[quadrant] = BM_SOLID; - children[quadrant].solid = &bmsolids[0]; - return childtype; + uchar childtype = type[quadrant]; + child = children[quadrant]; + type[quadrant] = BM_SOLID; + children[quadrant].solid = &bmsolids[0]; + return childtype; } struct BlendMapRoot : BlendMapNode { - uchar type; + uchar type; - BlendMapRoot() : type(BM_SOLID) { solid = &bmsolids[0xFF]; } - BlendMapRoot(uchar type, const BlendMapNode &node) : BlendMapNode(node), type(type) {} + BlendMapRoot() : type(BM_SOLID) { solid = &bmsolids[0xFF]; } + BlendMapRoot(uchar type, const BlendMapNode &node) : BlendMapNode(node), type(type) {} - void cleanup() { BlendMapNode::cleanup(type); } + void cleanup() { BlendMapNode::cleanup(type); } - void shrink(int quadrant) - { - if(type == BM_BRANCH) - { - BlendMapRoot oldroot = *this; - type = branch->shrink(*this, quadrant); - oldroot.cleanup(); - } - } + void shrink(int quadrant) + { + if(type == BM_BRANCH) + { + BlendMapRoot oldroot = *this; + type = branch->shrink(*this, quadrant); + oldroot.cleanup(); + } + } }; static BlendMapRoot blendmap; struct BlendMapCache { - BlendMapRoot node; - int scale; - ivec2 origin; + BlendMapRoot node; + int scale; + ivec2 origin; }; BlendMapCache *newblendmapcache() { return new BlendMapCache; } @@ -127,430 +127,430 @@ void freeblendmapcache(BlendMapCache *&cache) { delete cache; cache = NULL; } bool setblendmaporigin(BlendMapCache *cache, const ivec &o, int size) { - if(blendmap.type!=BM_BRANCH) - { - cache->node = blendmap; - cache->scale = worldscale-BM_SCALE; - cache->origin = ivec2(0, 0); - return cache->node.solid!=&bmsolids[0xFF]; - } - - BlendMapBranch *bm = blendmap.branch; - int bmscale = worldscale-BM_SCALE, bmsize = 1<>BM_SCALE, y = o.y>>BM_SCALE, - x1 = max(x-1, 0), y1 = max(y-1, 0), - x2 = min(((o.x + size + (1<>BM_SCALE) + 1, bmsize), - y2 = min(((o.y + size + (1<>BM_SCALE) + 1, bmsize), - diff = (x1^x2)|(y1^y2); - if(diff < bmsize) while(!(diff&(1<<(bmscale-1)))) - { - bmscale--; - int n = (((y1>>bmscale)&1)<<1) | ((x1>>bmscale)&1); - if(bm->type[n]!=BM_BRANCH) - { - cache->node = BlendMapRoot(bm->type[n], bm->children[n]); - cache->scale = bmscale; - cache->origin = ivec2(x1&(~0U<node.solid!=&bmsolids[0xFF]; - } - bm = bm->children[n].branch; - } - - cache->node.type = BM_BRANCH; - cache->node.branch = bm; - cache->scale = bmscale; - cache->origin = ivec2(x1&(~0U<node = blendmap; + cache->scale = worldscale-BM_SCALE; + cache->origin = ivec2(0, 0); + return cache->node.solid!=&bmsolids[0xFF]; + } + + BlendMapBranch *bm = blendmap.branch; + int bmscale = worldscale-BM_SCALE, bmsize = 1<>BM_SCALE, y = o.y>>BM_SCALE, + x1 = max(x-1, 0), y1 = max(y-1, 0), + x2 = min(((o.x + size + (1<>BM_SCALE) + 1, bmsize), + y2 = min(((o.y + size + (1<>BM_SCALE) + 1, bmsize), + diff = (x1^x2)|(y1^y2); + if(diff < bmsize) while(!(diff&(1<<(bmscale-1)))) + { + bmscale--; + int n = (((y1>>bmscale)&1)<<1) | ((x1>>bmscale)&1); + if(bm->type[n]!=BM_BRANCH) + { + cache->node = BlendMapRoot(bm->type[n], bm->children[n]); + cache->scale = bmscale; + cache->origin = ivec2(x1&(~0U<node.solid!=&bmsolids[0xFF]; + } + bm = bm->children[n].branch; + } + + cache->node.type = BM_BRANCH; + cache->node.branch = bm; + cache->scale = bmscale; + cache->origin = ivec2(x1&(~0U<node.solid!=&bmsolids[0xFF]; + return cache->node.solid!=&bmsolids[0xFF]; } static uchar lookupblendmap(int x, int y, BlendMapBranch *bm, int bmscale) { - for(;;) - { - bmscale--; - int n = (((y>>bmscale)&1)<<1) | ((x>>bmscale)&1); - switch(bm->type[n]) - { - case BM_SOLID: return bm->children[n].solid->val; - case BM_IMAGE: return bm->children[n].image->data[(y&((1<children[n].branch; - } -} - + for(;;) + { + bmscale--; + int n = (((y>>bmscale)&1)<<1) | ((x>>bmscale)&1); + switch(bm->type[n]) + { + case BM_SOLID: return bm->children[n].solid->val; + case BM_IMAGE: return bm->children[n].image->data[(y&((1<children[n].branch; + } +} + uchar lookupblendmap(BlendMapCache *cache, const vec &pos) { - if(cache->node.type==BM_SOLID) return cache->node.solid->val; - - uchar vals[4], *val = vals; - float bx = pos.x/(1<origin.x, ry = iy-cache->origin.y; - loop(vy, 2) loop(vx, 2) - { - int cx = clamp(rx+vx, 0, (1<scale)-1), cy = clamp(ry+vy, 0, (1<scale)-1); - if(cache->node.type==BM_IMAGE) - *val++ = cache->node.image->data[cy*BM_IMAGE_SIZE + cx]; - else *val++ = lookupblendmap(cx, cy, cache->node.branch, cache->scale); - } - float fx = bx - ix, fy = by - iy; - return uchar((1-fy)*((1-fx)*vals[0] + fx*vals[1]) + - fy*((1-fx)*vals[2] + fx*vals[3])); + if(cache->node.type==BM_SOLID) return cache->node.solid->val; + + uchar vals[4], *val = vals; + float bx = pos.x/(1<origin.x, ry = iy-cache->origin.y; + loop(vy, 2) loop(vx, 2) + { + int cx = clamp(rx+vx, 0, (1<scale)-1), cy = clamp(ry+vy, 0, (1<scale)-1); + if(cache->node.type==BM_IMAGE) + *val++ = cache->node.image->data[cy*BM_IMAGE_SIZE + cx]; + else *val++ = lookupblendmap(cx, cy, cache->node.branch, cache->scale); + } + float fx = bx - ix, fy = by - iy; + return uchar((1-fy)*((1-fx)*vals[0] + fx*vals[1]) + + fy*((1-fx)*vals[2] + fx*vals[3])); } static void fillblendmap(uchar &type, BlendMapNode &node, int size, uchar val, int x1, int y1, int x2, int y2) { - if(max(x1, y1) <= 0 && min(x2, y2) >= size) - { - node.cleanup(type); - type = BM_SOLID; - node.solid = &bmsolids[val]; - return; - } - - if(type==BM_BRANCH) - { - size /= 2; - if(y1 < size) - { - if(x1 < size) fillblendmap(node.branch->type[0], node.branch->children[0], size, val, - x1, y1, min(x2, size), min(y2, size)); - if(x2 > size) fillblendmap(node.branch->type[1], node.branch->children[1], size, val, - max(x1-size, 0), y1, x2-size, min(y2, size)); - } - if(y2 > size) - { - if(x1 < size) fillblendmap(node.branch->type[2], node.branch->children[2], size, val, - x1, max(y1-size, 0), min(x2, size), y2-size); - if(x2 > size) fillblendmap(node.branch->type[3], node.branch->children[3], size, val, - max(x1-size, 0), max(y1-size, 0), x2-size, y2-size); - } - loopi(4) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val!=val) return; - node.cleanup(type); - type = BM_SOLID; - node.solid = &bmsolids[val]; - return; - } - else if(type==BM_SOLID) - { - uchar oldval = node.solid->val; - if(oldval==val) return; - - if(size > BM_IMAGE_SIZE) - { - node.splitsolid(type, oldval); - fillblendmap(type, node, size, val, x1, y1, x2, y2); - return; - } - - type = BM_IMAGE; - node.image = new BlendMapImage; - memset(node.image->data, oldval, sizeof(node.image->data)); - } - - uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1]; - loopi(y2-y1) - { - memset(dst, val, x2-x1); - dst += BM_IMAGE_SIZE; - } + if(max(x1, y1) <= 0 && min(x2, y2) >= size) + { + node.cleanup(type); + type = BM_SOLID; + node.solid = &bmsolids[val]; + return; + } + + if(type==BM_BRANCH) + { + size /= 2; + if(y1 < size) + { + if(x1 < size) fillblendmap(node.branch->type[0], node.branch->children[0], size, val, + x1, y1, min(x2, size), min(y2, size)); + if(x2 > size) fillblendmap(node.branch->type[1], node.branch->children[1], size, val, + max(x1-size, 0), y1, x2-size, min(y2, size)); + } + if(y2 > size) + { + if(x1 < size) fillblendmap(node.branch->type[2], node.branch->children[2], size, val, + x1, max(y1-size, 0), min(x2, size), y2-size); + if(x2 > size) fillblendmap(node.branch->type[3], node.branch->children[3], size, val, + max(x1-size, 0), max(y1-size, 0), x2-size, y2-size); + } + loopi(4) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val!=val) return; + node.cleanup(type); + type = BM_SOLID; + node.solid = &bmsolids[val]; + return; + } + else if(type==BM_SOLID) + { + uchar oldval = node.solid->val; + if(oldval==val) return; + + if(size > BM_IMAGE_SIZE) + { + node.splitsolid(type, oldval); + fillblendmap(type, node, size, val, x1, y1, x2, y2); + return; + } + + type = BM_IMAGE; + node.image = new BlendMapImage; + memset(node.image->data, oldval, sizeof(node.image->data)); + } + + uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1]; + loopi(y2-y1) + { + memset(dst, val, x2-x1); + dst += BM_IMAGE_SIZE; + } } void fillblendmap(int x, int y, int w, int h, uchar val) { - int bmsize = worldsize>>BM_SCALE, - x1 = clamp(x, 0, bmsize), - y1 = clamp(y, 0, bmsize), - x2 = clamp(x+w, 0, bmsize), - y2 = clamp(y+h, 0, bmsize); - if(max(x1, y1) >= bmsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return; - fillblendmap(blendmap.type, blendmap, bmsize, val, x1, y1, x2, y2); + int bmsize = worldsize>>BM_SCALE, + x1 = clamp(x, 0, bmsize), + y1 = clamp(y, 0, bmsize), + x2 = clamp(x+w, 0, bmsize), + y2 = clamp(y+h, 0, bmsize); + if(max(x1, y1) >= bmsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return; + fillblendmap(blendmap.type, blendmap, bmsize, val, x1, y1, x2, y2); } static void invertblendmap(uchar &type, BlendMapNode &node, int size, int x1, int y1, int x2, int y2) { - if(type==BM_BRANCH) - { - size /= 2; - if(y1 < size) - { - if(x1 < size) invertblendmap(node.branch->type[0], node.branch->children[0], size, - x1, y1, min(x2, size), min(y2, size)); - if(x2 > size) invertblendmap(node.branch->type[1], node.branch->children[1], size, - max(x1-size, 0), y1, x2-size, min(y2, size)); - } - if(y2 > size) - { - if(x1 < size) invertblendmap(node.branch->type[2], node.branch->children[2], size, - x1, max(y1-size, 0), min(x2, size), y2-size); - if(x2 > size) invertblendmap(node.branch->type[3], node.branch->children[3], size, - max(x1-size, 0), max(y1-size, 0), x2-size, y2-size); - } - return; - } - else if(type==BM_SOLID) - { - fillblendmap(type, node, size, 255-node.solid->val, x1, y1, x2, y2); - } - else if(type==BM_IMAGE) - { - uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1]; - loopi(y2-y1) - { - loopj(x2-x1) dst[j] = 255-dst[j]; - dst += BM_IMAGE_SIZE; - } - } + if(type==BM_BRANCH) + { + size /= 2; + if(y1 < size) + { + if(x1 < size) invertblendmap(node.branch->type[0], node.branch->children[0], size, + x1, y1, min(x2, size), min(y2, size)); + if(x2 > size) invertblendmap(node.branch->type[1], node.branch->children[1], size, + max(x1-size, 0), y1, x2-size, min(y2, size)); + } + if(y2 > size) + { + if(x1 < size) invertblendmap(node.branch->type[2], node.branch->children[2], size, + x1, max(y1-size, 0), min(x2, size), y2-size); + if(x2 > size) invertblendmap(node.branch->type[3], node.branch->children[3], size, + max(x1-size, 0), max(y1-size, 0), x2-size, y2-size); + } + return; + } + else if(type==BM_SOLID) + { + fillblendmap(type, node, size, 255-node.solid->val, x1, y1, x2, y2); + } + else if(type==BM_IMAGE) + { + uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1]; + loopi(y2-y1) + { + loopj(x2-x1) dst[j] = 255-dst[j]; + dst += BM_IMAGE_SIZE; + } + } } void invertblendmap(int x, int y, int w, int h) { - int bmsize = worldsize>>BM_SCALE, - x1 = clamp(x, 0, bmsize), - y1 = clamp(y, 0, bmsize), - x2 = clamp(x+w, 0, bmsize), - y2 = clamp(y+h, 0, bmsize); - if(max(x1, y1) >= bmsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return; - invertblendmap(blendmap.type, blendmap, bmsize, x1, y1, x2, y2); + int bmsize = worldsize>>BM_SCALE, + x1 = clamp(x, 0, bmsize), + y1 = clamp(y, 0, bmsize), + x2 = clamp(x+w, 0, bmsize), + y2 = clamp(y+h, 0, bmsize); + if(max(x1, y1) >= bmsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return; + invertblendmap(blendmap.type, blendmap, bmsize, x1, y1, x2, y2); } static void optimizeblendmap(uchar &type, BlendMapNode &node) { - switch(type) - { - case BM_IMAGE: - { - uint val = node.image->data[0]; - val |= val<<8; - val |= val<<16; - for(uint *data = (uint *)node.image->data, *end = &data[sizeof(node.image->data)/sizeof(uint)]; data < end; data++) - if(*data != val) return; - node.cleanup(type); - type = BM_SOLID; - node.solid = &bmsolids[val&0xFF]; - break; - } - case BM_BRANCH: - { - loopi(4) optimizeblendmap(node.branch->type[i], node.branch->children[i]); - if(node.branch->type[3]!=BM_SOLID) return; - uint val = node.branch->children[3].solid->val; - loopi(3) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val != val) return; - node.cleanup(type); - type = BM_SOLID; - node.solid = &bmsolids[val]; - break; - } - } + switch(type) + { + case BM_IMAGE: + { + uint val = node.image->data[0]; + val |= val<<8; + val |= val<<16; + for(uint *data = (uint *)node.image->data, *end = &data[sizeof(node.image->data)/sizeof(uint)]; data < end; data++) + if(*data != val) return; + node.cleanup(type); + type = BM_SOLID; + node.solid = &bmsolids[val&0xFF]; + break; + } + case BM_BRANCH: + { + loopi(4) optimizeblendmap(node.branch->type[i], node.branch->children[i]); + if(node.branch->type[3]!=BM_SOLID) return; + uint val = node.branch->children[3].solid->val; + loopi(3) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val != val) return; + node.cleanup(type); + type = BM_SOLID; + node.solid = &bmsolids[val]; + break; + } + } } void optimizeblendmap() { - optimizeblendmap(blendmap.type, blendmap); + optimizeblendmap(blendmap.type, blendmap); } VARF(blendpaintmode, 0, 0, 5, { - if(!blendpaintmode) stoppaintblendmap(); + if(!blendpaintmode) stoppaintblendmap(); }); static void blitblendmap(uchar &type, BlendMapNode &node, int bmx, int bmy, int bmsize, uchar *src, int sx, int sy, int sw, int sh, int smode) { - if(type==BM_BRANCH) - { - bmsize /= 2; - if(sy < bmy + bmsize) - { - if(sx < bmx + bmsize) blitblendmap(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, src, sx, sy, sw, sh, smode); - if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, src, sx, sy, sw, sh, smode); - } - if(sy + sh > bmy + bmsize) - { - if(sx < bmx + bmsize) blitblendmap(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, src, sx, sy, sw, sh, smode); - if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, src, sx, sy, sw, sh, smode); - } - return; - } - if(type==BM_SOLID) - { - uchar val = node.solid->val; - if(bmsize > BM_IMAGE_SIZE) - { - node.splitsolid(type, val); - blitblendmap(type, node, bmx, bmy, bmsize, src, sx, sy, sw, sh, smode); - return; - } - - type = BM_IMAGE; - node.image = new BlendMapImage; - memset(node.image->data, val, sizeof(node.image->data)); - } - - int x1 = clamp(sx - bmx, 0, bmsize), y1 = clamp(sy - bmy, 0, bmsize), - x2 = clamp(sx+sw - bmx, 0, bmsize), y2 = clamp(sy+sh - bmy, 0, bmsize); - uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1]; - src += max(bmy - sy, 0)*sw + max(bmx - sx, 0); - loopi(y2-y1) - { - switch(smode) - { - case 1: - memcpy(dst, src, x2 - x1); - break; - - case 2: - loopi(x2 - x1) dst[i] = min(dst[i], src[i]); - break; - - case 3: - loopi(x2 - x1) dst[i] = max(dst[i], src[i]); - break; - - case 4: - loopi(x2 - x1) dst[i] = min(dst[i], uchar(0xFF - src[i])); - break; - - case 5: - loopi(x2 - x1) dst[i] = max(dst[i], uchar(0xFF - src[i])); - break; - } - dst += BM_IMAGE_SIZE; - src += sw; - } + if(type==BM_BRANCH) + { + bmsize /= 2; + if(sy < bmy + bmsize) + { + if(sx < bmx + bmsize) blitblendmap(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, src, sx, sy, sw, sh, smode); + if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, src, sx, sy, sw, sh, smode); + } + if(sy + sh > bmy + bmsize) + { + if(sx < bmx + bmsize) blitblendmap(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, src, sx, sy, sw, sh, smode); + if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, src, sx, sy, sw, sh, smode); + } + return; + } + if(type==BM_SOLID) + { + uchar val = node.solid->val; + if(bmsize > BM_IMAGE_SIZE) + { + node.splitsolid(type, val); + blitblendmap(type, node, bmx, bmy, bmsize, src, sx, sy, sw, sh, smode); + return; + } + + type = BM_IMAGE; + node.image = new BlendMapImage; + memset(node.image->data, val, sizeof(node.image->data)); + } + + int x1 = clamp(sx - bmx, 0, bmsize), y1 = clamp(sy - bmy, 0, bmsize), + x2 = clamp(sx+sw - bmx, 0, bmsize), y2 = clamp(sy+sh - bmy, 0, bmsize); + uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1]; + src += max(bmy - sy, 0)*sw + max(bmx - sx, 0); + loopi(y2-y1) + { + switch(smode) + { + case 1: + memcpy(dst, src, x2 - x1); + break; + + case 2: + loopi(x2 - x1) dst[i] = min(dst[i], src[i]); + break; + + case 3: + loopi(x2 - x1) dst[i] = max(dst[i], src[i]); + break; + + case 4: + loopi(x2 - x1) dst[i] = min(dst[i], uchar(0xFF - src[i])); + break; + + case 5: + loopi(x2 - x1) dst[i] = max(dst[i], uchar(0xFF - src[i])); + break; + } + dst += BM_IMAGE_SIZE; + src += sw; + } } void blitblendmap(uchar *src, int sx, int sy, int sw, int sh, int smode) { - int bmsize = worldsize>>BM_SCALE; - if(max(sx, sy) >= bmsize || min(sx+sw, sy+sh) <= 0 || min(sw, sh) <= 0) return; - blitblendmap(blendmap.type, blendmap, 0, 0, bmsize, src, sx, sy, sw, sh, smode); + int bmsize = worldsize>>BM_SCALE; + if(max(sx, sy) >= bmsize || min(sx+sw, sy+sh) <= 0 || min(sw, sh) <= 0) return; + blitblendmap(blendmap.type, blendmap, 0, 0, bmsize, src, sx, sy, sw, sh, smode); } - + void resetblendmap() { - blendmap.cleanup(); - blendmap.type = BM_SOLID; - blendmap.solid = &bmsolids[0xFF]; + blendmap.cleanup(); + blendmap.type = BM_SOLID; + blendmap.solid = &bmsolids[0xFF]; } void enlargeblendmap() { - if(blendmap.type == BM_SOLID) return; - BlendMapBranch *branch = new BlendMapBranch; - branch->type[0] = blendmap.type; - branch->children[0] = blendmap; - loopi(3) - { - branch->type[i+1] = BM_SOLID; - branch->children[i+1].solid = &bmsolids[0xFF]; - } - blendmap.type = BM_BRANCH; - blendmap.branch = branch; + if(blendmap.type == BM_SOLID) return; + BlendMapBranch *branch = new BlendMapBranch; + branch->type[0] = blendmap.type; + branch->children[0] = blendmap; + loopi(3) + { + branch->type[i+1] = BM_SOLID; + branch->children[i+1].solid = &bmsolids[0xFF]; + } + blendmap.type = BM_BRANCH; + blendmap.branch = branch; } void shrinkblendmap(int octant) { - blendmap.shrink(octant&3); + blendmap.shrink(octant&3); } void moveblendmap(uchar type, BlendMapNode &node, int size, int x, int y, int dx, int dy) { - if(type == BM_BRANCH) - { - size /= 2; - moveblendmap(node.branch->type[0], node.branch->children[0], size, x, y, dx, dy); - moveblendmap(node.branch->type[1], node.branch->children[1], size, x + size, y, dx, dy); - moveblendmap(node.branch->type[2], node.branch->children[2], size, x, y + size, dx, dy); - moveblendmap(node.branch->type[3], node.branch->children[3], size, x + size, y + size, dx, dy); - return; - } - else if(type == BM_SOLID) - { - fillblendmap(x+dx, y+dy, size, size, node.solid->val); - } - else if(type == BM_IMAGE) - { - blitblendmap(node.image->data, x+dx, y+dy, size, size, 1); - } + if(type == BM_BRANCH) + { + size /= 2; + moveblendmap(node.branch->type[0], node.branch->children[0], size, x, y, dx, dy); + moveblendmap(node.branch->type[1], node.branch->children[1], size, x + size, y, dx, dy); + moveblendmap(node.branch->type[2], node.branch->children[2], size, x, y + size, dx, dy); + moveblendmap(node.branch->type[3], node.branch->children[3], size, x + size, y + size, dx, dy); + return; + } + else if(type == BM_SOLID) + { + fillblendmap(x+dx, y+dy, size, size, node.solid->val); + } + else if(type == BM_IMAGE) + { + blitblendmap(node.image->data, x+dx, y+dy, size, size, 1); + } } void moveblendmap(int dx, int dy) { - BlendMapRoot old = blendmap; - blendmap.type = BM_SOLID; - blendmap.solid = &bmsolids[0xFF]; - moveblendmap(old.type, old, worldsize>>BM_SCALE, 0, 0, dx, dy); - old.cleanup(); + BlendMapRoot old = blendmap; + blendmap.type = BM_SOLID; + blendmap.solid = &bmsolids[0xFF]; + moveblendmap(old.type, old, worldsize>>BM_SCALE, 0, 0, dx, dy); + old.cleanup(); } - + struct BlendBrush { - char *name; - int w, h; - uchar *data; - GLuint tex; - - BlendBrush(const char *name, int w, int h) : - name(newstring(name)), w(w), h(h), data(new uchar[w*h]), tex(0) - {} - - ~BlendBrush() - { - cleanup(); - delete[] name; - if(data) delete[] data; - } - - void cleanup() - { - if(tex) { glDeleteTextures(1, &tex); tex = 0; } - } - - void gentex() - { - if(!tex) glGenTextures(1, &tex); - uchar *buf = new uchar[2*w*h]; - uchar *dst = buf, *src = data; - loopi(h) - { - loopj(w) *dst++ = 255 - *src++; - } - createtexture(tex, w, h, buf, 3, 1, hasTRG ? GL_R8 : GL_LUMINANCE8); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - GLfloat border[4] = { 0, 0, 0, 0 }; - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); - delete[] buf; - } - - void reorient(bool flipx, bool flipy, bool swapxy) - { - uchar *rdata = new uchar[w*h]; - int stridex = 1, stridey = 1; - if(swapxy) stridex *= h; else stridey *= w; - uchar *src = data, *dst = rdata; - if(flipx) { dst += (w-1)*stridex; stridex = -stridex; } - if(flipy) { dst += (h-1)*stridey; stridey = -stridey; } - loopi(h) - { - uchar *curdst = dst; - loopj(w) - { - *curdst = *src++; - curdst += stridex; - } - dst += stridey; - } - if(swapxy) swap(w, h); - delete[] data; - data = rdata; - if(tex) gentex(); - } + char *name; + int w, h; + uchar *data; + GLuint tex; + + BlendBrush(const char *name, int w, int h) : + name(newstring(name)), w(w), h(h), data(new uchar[w*h]), tex(0) + {} + + ~BlendBrush() + { + cleanup(); + delete[] name; + if(data) delete[] data; + } + + void cleanup() + { + if(tex) { glDeleteTextures(1, &tex); tex = 0; } + } + + void gentex() + { + if(!tex) glGenTextures(1, &tex); + uchar *buf = new uchar[2*w*h]; + uchar *dst = buf, *src = data; + loopi(h) + { + loopj(w) *dst++ = 255 - *src++; + } + createtexture(tex, w, h, buf, 3, 1, hasTRG ? GL_R8 : GL_LUMINANCE8); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + GLfloat border[4] = { 0, 0, 0, 0 }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); + delete[] buf; + } + + void reorient(bool flipx, bool flipy, bool swapxy) + { + uchar *rdata = new uchar[w*h]; + int stridex = 1, stridey = 1; + if(swapxy) stridex *= h; else stridey *= w; + uchar *src = data, *dst = rdata; + if(flipx) { dst += (w-1)*stridex; stridex = -stridex; } + if(flipy) { dst += (h-1)*stridey; stridey = -stridey; } + loopi(h) + { + uchar *curdst = dst; + loopj(w) + { + *curdst = *src++; + curdst += stridex; + } + dst += stridey; + } + if(swapxy) swap(w, h); + delete[] data; + data = rdata; + if(tex) gentex(); + } }; static vector brushes; @@ -558,73 +558,73 @@ static int curbrush = -1; void cleanupblendmap() { - loopv(brushes) brushes[i]->cleanup(); + loopv(brushes) brushes[i]->cleanup(); } void clearblendbrushes() { - while(brushes.length()) delete brushes.pop(); - curbrush = -1; + while(brushes.length()) delete brushes.pop(); + curbrush = -1; } void delblendbrush(const char *name) { - loopv(brushes) if(!strcmp(brushes[i]->name, name)) - { - delete brushes[i]; - brushes.remove(i--); - } - curbrush = brushes.empty() ? -1 : clamp(curbrush, 0, brushes.length()-1); + loopv(brushes) if(!strcmp(brushes[i]->name, name)) + { + delete brushes[i]; + brushes.remove(i--); + } + curbrush = brushes.empty() ? -1 : clamp(curbrush, 0, brushes.length()-1); } void addblendbrush(const char *name, const char *imgname) { - delblendbrush(name); + delblendbrush(name); - ImageData s; - if(!loadimage(imgname, s)) { conoutf(CON_ERROR, "could not load blend brush image %s", imgname); return; } - if(max(s.w, s.h) > (1<<12)) - { - conoutf(CON_ERROR, "blend brush image size exceeded %dx%d pixels: %s", 1<<12, 1<<12, imgname); - return; - } - - BlendBrush *brush = new BlendBrush(name, s.w, s.h); + ImageData s; + if(!loadimage(imgname, s)) { conoutf(CON_ERROR, "could not load blend brush image %s", imgname); return; } + if(max(s.w, s.h) > (1<<12)) + { + conoutf(CON_ERROR, "blend brush image size exceeded %dx%d pixels: %s", 1<<12, 1<<12, imgname); + return; + } - uchar *dst = brush->data, *srcrow = s.data; - loopi(s.h) - { - for(uchar *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; src += s.bpp) - *dst++ = src[0]; - srcrow += s.pitch; - } + BlendBrush *brush = new BlendBrush(name, s.w, s.h); - brushes.add(brush); - if(curbrush < 0) curbrush = 0; - else if(curbrush >= brushes.length()) curbrush = brushes.length()-1; + uchar *dst = brush->data, *srcrow = s.data; + loopi(s.h) + { + for(uchar *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; src += s.bpp) + *dst++ = src[0]; + srcrow += s.pitch; + } + + brushes.add(brush); + if(curbrush < 0) curbrush = 0; + else if(curbrush >= brushes.length()) curbrush = brushes.length()-1; } void nextblendbrush(int *dir) { - curbrush += *dir < 0 ? -1 : 1; - if(brushes.empty()) curbrush = -1; - else if(!brushes.inrange(curbrush)) curbrush = *dir < 0 ? brushes.length()-1 : 0; + curbrush += *dir < 0 ? -1 : 1; + if(brushes.empty()) curbrush = -1; + else if(!brushes.inrange(curbrush)) curbrush = *dir < 0 ? brushes.length()-1 : 0; } void setblendbrush(const char *name) { - loopv(brushes) if(!strcmp(brushes[i]->name, name)) { curbrush = i; break; } + loopv(brushes) if(!strcmp(brushes[i]->name, name)) { curbrush = i; break; } } void getblendbrushname(int *n) { - result(brushes.inrange(*n) ? brushes[*n]->name : ""); + result(brushes.inrange(*n) ? brushes[*n]->name : ""); } void curblendbrush() { - intret(curbrush); + intret(curbrush); } COMMAND(clearblendbrushes, ""); @@ -639,40 +639,40 @@ extern int nompedit; bool canpaintblendmap(bool brush = true, bool sel = false, bool msg = true) { - if(noedit(!sel, msg) || (nompedit && multiplayer())) return false; - if(!blendpaintmode) - { - if(msg) conoutf(CON_ERROR, "operation only allowed in blend paint mode"); - return false; - } - if(brush && !brushes.inrange(curbrush)) - { - if(msg) conoutf(CON_ERROR, "no blend brush selected"); - return false; - } - return true; + if(noedit(!sel, msg) || (nompedit && multiplayer())) return false; + if(!blendpaintmode) + { + if(msg) conoutf(CON_ERROR, "operation only allowed in blend paint mode"); + return false; + } + if(brush && !brushes.inrange(curbrush)) + { + if(msg) conoutf(CON_ERROR, "no blend brush selected"); + return false; + } + return true; } void rotateblendbrush(int *val) { - if(!canpaintblendmap()) return; - BlendBrush *brush = brushes[curbrush]; - const texrotation &r = texrotations[*val < 0 ? 3 : clamp(*val, 1, 7)]; - brush->reorient(r.flipx, r.flipy, r.swapxy); + if(!canpaintblendmap()) return; + BlendBrush *brush = brushes[curbrush]; + const texrotation &r = texrotations[*val < 0 ? 3 : clamp(*val, 1, 7)]; + brush->reorient(r.flipx, r.flipy, r.swapxy); } COMMAND(rotateblendbrush, "i"); void paintblendmap(bool msg) { - if(!canpaintblendmap(true, false, msg)) return; + if(!canpaintblendmap(true, false, msg)) return; - BlendBrush *brush = brushes[curbrush]; - int x = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<w), - y = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<h); - blitblendmap(brush->data, x, y, brush->w, brush->h, blendpaintmode); - previewblends(ivec((x-1)<w+1)<h+1)<w), + y = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<h); + blitblendmap(brush->data, x, y, brush->w, brush->h, blendpaintmode); + previewblends(ivec((x-1)<w+1)<h+1)<>BM_SCALE, y1 = sel.o.y>>BM_SCALE, - x2 = (sel.o.x+sel.s.x*sel.grid+(1<>BM_SCALE, - y2 = (sel.o.y+sel.s.y*sel.grid+(1<>BM_SCALE; - fillblendmap(x1, y1, x2-x1, y2-y1, 0xFF); - previewblends(ivec(x1<>BM_SCALE, y1 = sel.o.y>>BM_SCALE, + x2 = (sel.o.x+sel.s.x*sel.grid+(1<>BM_SCALE, + y2 = (sel.o.y+sel.s.y*sel.grid+(1<>BM_SCALE; + fillblendmap(x1, y1, x2-x1, y2-y1, 0xFF); + previewblends(ivec(x1<>BM_SCALE, y1 = sel.o.y>>BM_SCALE, - x2 = (sel.o.x+sel.s.x*sel.grid+(1<>BM_SCALE, - y2 = (sel.o.y+sel.s.y*sel.grid+(1<>BM_SCALE; - invertblendmap(x1, y1, x2-x1, y2-y1); - previewblends(ivec(x1<>BM_SCALE, y1 = sel.o.y>>BM_SCALE, + x2 = (sel.o.x+sel.s.x*sel.grid+(1<>BM_SCALE, + y2 = (sel.o.y+sel.s.y*sel.grid+(1<>BM_SCALE; + invertblendmap(x1, y1, x2-x1, y2-y1); + previewblends(ivec(x1<>BM_SCALE, worldsize>>BM_SCALE); - previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize)); + if(noedit(false) || (nompedit && multiplayer())) return; + invertblendmap(0, 0, worldsize>>BM_SCALE, worldsize>>BM_SCALE); + previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize)); } COMMAND(invertblendmap, ""); void showblendmap() { - if(noedit(true) || (nompedit && multiplayer())) return; - previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize)); + if(noedit(true) || (nompedit && multiplayer())) return; + previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize)); } COMMAND(showblendmap, ""); COMMAND(optimizeblendmap, ""); ICOMMAND(clearblendmap, "", (), { - if(noedit(true) || (nompedit && multiplayer())) return; - resetblendmap(); - showblendmap(); + if(noedit(true) || (nompedit && multiplayer())) return; + resetblendmap(); + showblendmap(); }); ICOMMAND(moveblendmap, "ii", (int *dx, int *dy), { - if(noedit(true) || (nompedit && multiplayer())) return; - if(*dx%(BM_IMAGE_SIZE<= worldsize || *dy <= -worldsize || *dy >= worldsize) - resetblendmap(); - else - moveblendmap(*dx>>BM_SCALE, *dy>>BM_SCALE); - showblendmap(); + if(noedit(true) || (nompedit && multiplayer())) return; + if(*dx%(BM_IMAGE_SIZE<= worldsize || *dy <= -worldsize || *dy >= worldsize) + resetblendmap(); + else + moveblendmap(*dx>>BM_SCALE, *dy>>BM_SCALE); + showblendmap(); }); void renderblendbrush() { - if(!blendpaintmode || !brushes.inrange(curbrush)) return; + if(!blendpaintmode || !brushes.inrange(curbrush)) return; - BlendBrush *brush = brushes[curbrush]; - int x1 = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<w) << BM_SCALE, - y1 = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<h) << BM_SCALE, - x2 = x1 + (brush->w << BM_SCALE), - y2 = y1 + (brush->h << BM_SCALE); + BlendBrush *brush = brushes[curbrush]; + int x1 = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<w) << BM_SCALE, + y1 = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<h) << BM_SCALE, + x2 = x1 + (brush->w << BM_SCALE), + y2 = y1 + (brush->h << BM_SCALE); - if(max(x1, y1) >= worldsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return; + if(max(x1, y1) >= worldsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return; - if(!brush->tex) brush->gentex(); - renderblendbrush(brush->tex, x1, y1, x2 - x1, y2 - y1); + if(!brush->tex) brush->gentex(); + renderblendbrush(brush->tex, x1, y1, x2 - x1, y2 - y1); } bool loadblendmap(stream *f, uchar &type, BlendMapNode &node) { - type = f->getchar(); - switch(type) - { - case BM_SOLID: - { - int val = f->getchar(); - if(val<0 || val>0xFF) return false; - node.solid = &bmsolids[val]; - break; - } - - case BM_IMAGE: - node.image = new BlendMapImage; - if(f->read(node.image->data, sizeof(node.image->data)) != sizeof(node.image->data)) - return false; - break; - - case BM_BRANCH: - node.branch = new BlendMapBranch; - loopi(4) { node.branch->type[i] = BM_SOLID; node.branch->children[i].solid = &bmsolids[0xFF]; } - loopi(4) if(!loadblendmap(f, node.branch->type[i], node.branch->children[i])) - return false; - break; - - default: - type = BM_SOLID; - node.solid = &bmsolids[0xFF]; - return false; - } - return true; + type = f->getchar(); + switch(type) + { + case BM_SOLID: + { + int val = f->getchar(); + if(val<0 || val>0xFF) return false; + node.solid = &bmsolids[val]; + break; + } + + case BM_IMAGE: + node.image = new BlendMapImage; + if(f->read(node.image->data, sizeof(node.image->data)) != sizeof(node.image->data)) + return false; + break; + + case BM_BRANCH: + node.branch = new BlendMapBranch; + loopi(4) { node.branch->type[i] = BM_SOLID; node.branch->children[i].solid = &bmsolids[0xFF]; } + loopi(4) if(!loadblendmap(f, node.branch->type[i], node.branch->children[i])) + return false; + break; + + default: + type = BM_SOLID; + node.solid = &bmsolids[0xFF]; + return false; + } + return true; } bool loadblendmap(stream *f, int info) { - resetblendmap(); - return loadblendmap(f, blendmap.type, blendmap); + resetblendmap(); + return loadblendmap(f, blendmap.type, blendmap); } void saveblendmap(stream *f, uchar type, BlendMapNode &node) { - f->putchar(type); - switch(type) - { - case BM_SOLID: - f->putchar(node.solid->val); - break; - - case BM_IMAGE: - f->write(node.image->data, sizeof(node.image->data)); - break; + f->putchar(type); + switch(type) + { + case BM_SOLID: + f->putchar(node.solid->val); + break; - case BM_BRANCH: - loopi(4) saveblendmap(f, node.branch->type[i], node.branch->children[i]); - break; - } + case BM_IMAGE: + f->write(node.image->data, sizeof(node.image->data)); + break; + + case BM_BRANCH: + loopi(4) saveblendmap(f, node.branch->type[i], node.branch->children[i]); + break; + } } void saveblendmap(stream *f) { - saveblendmap(f, blendmap.type, blendmap); + saveblendmap(f, blendmap.type, blendmap); } uchar shouldsaveblendmap() { - return blendmap.solid!=&bmsolids[0xFF] ? 1 : 0; + return blendmap.solid!=&bmsolids[0xFF] ? 1 : 0; } - + diff --git a/src/engine/client.cpp b/src/engine/client.cpp index 3cfef8e..bd46b24 100644 --- a/src/engine/client.cpp +++ b/src/engine/client.cpp @@ -8,9 +8,9 @@ int connmillis = 0, connattempts = 0, discmillis = 0; bool multiplayer(bool msg) { - bool val = curpeer || hasnonlocalclients(); - if(val && msg) conoutf(CON_ERROR, "operation not available in multiplayer"); - return val; + bool val = curpeer || hasnonlocalclients(); + if(val && msg) conoutf(CON_ERROR, "operation not available in multiplayer"); + return val; } void setrate(int rate) @@ -24,160 +24,160 @@ VARF(rate, 0, 0, 1024, setrate(rate)); void throttle(); VARF(throttle_interval, 0, 5, 30, throttle()); -VARF(throttle_accel, 0, 2, 32, throttle()); -VARF(throttle_decel, 0, 2, 32, throttle()); +VARF(throttle_accel, 0, 2, 32, throttle()); +VARF(throttle_decel, 0, 2, 32, throttle()); void throttle() { - if(!curpeer) return; - ASSERT(ENET_PEER_PACKET_THROTTLE_SCALE==32); - enet_peer_throttle_configure(curpeer, throttle_interval*1000, throttle_accel, throttle_decel); + if(!curpeer) return; + ASSERT(ENET_PEER_PACKET_THROTTLE_SCALE==32); + enet_peer_throttle_configure(curpeer, throttle_interval*1000, throttle_accel, throttle_decel); } bool isconnected(bool attempt, bool local) { - return curpeer || (attempt && connpeer) || (local && haslocalclients()); + return curpeer || (attempt && connpeer) || (local && haslocalclients()); } ICOMMAND(isconnected, "bb", (int *attempt, int *local), intret(isconnected(*attempt > 0, *local != 0) ? 1 : 0)); const ENetAddress *connectedpeer() { - return curpeer ? &curpeer->address : NULL; + return curpeer ? &curpeer->address : NULL; } ICOMMAND(connectedip, "", (), { - const ENetAddress *address = connectedpeer(); - string hostname; - result(address && enet_address_get_host_ip(address, hostname, sizeof(hostname)) >= 0 ? hostname : ""); + const ENetAddress *address = connectedpeer(); + string hostname; + result(address && enet_address_get_host_ip(address, hostname, sizeof(hostname)) >= 0 ? hostname : ""); }); ICOMMAND(connectedport, "", (), { - const ENetAddress *address = connectedpeer(); - intret(address ? address->port : -1); + const ENetAddress *address = connectedpeer(); + intret(address ? address->port : -1); }); void abortconnect() { - if(!connpeer) return; - game::connectfail(); - if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer); - connpeer = NULL; - if(curpeer) return; - enet_host_destroy(clienthost); - clienthost = NULL; + if(!connpeer) return; + game::connectfail(); + if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer); + connpeer = NULL; + if(curpeer) return; + enet_host_destroy(clienthost); + clienthost = NULL; } SVARP(connectname, ""); VARP(connectport, 0, 0, 0xFFFF); void connectserv(const char *servername, int serverport, const char *serverpassword) -{ - if(connpeer) - { - conoutf("aborting connection attempt"); - abortconnect(); - } - - if(serverport <= 0) serverport = server::serverport(); - - ENetAddress address; - address.port = serverport; - - if(servername) - { - if(strcmp(servername, connectname)) setsvar("connectname", servername); - if(serverport != connectport) setvar("connectport", serverport); - addserver(servername, serverport, serverpassword && serverpassword[0] ? serverpassword : NULL); - conoutf("attempting to connect to %s:%d", servername, serverport); - if(!resolverwait(servername, &address)) - { - conoutf(CON_ERROR, "\f3could not resolve server %s", servername); - return; - } - } - else - { - setsvar("connectname", ""); - setvar("connectport", 0); - conoutf("attempting to connect over LAN"); - address.host = ENET_HOST_BROADCAST; - } - - if(!clienthost) - { - clienthost = enet_host_create(NULL, 2, server::numchannels(), rate*1024, rate*1024); - if(!clienthost) - { - conoutf(CON_ERROR, "\f3could not connect to server"); - return; - } - clienthost->duplicatePeers = 0; - } - - connpeer = enet_host_connect(clienthost, &address, server::numchannels(), 0); - enet_host_flush(clienthost); - connmillis = totalmillis; - connattempts = 0; - - game::connectattempt(servername ? servername : "", serverpassword ? serverpassword : "", address); +{ + if(connpeer) + { + conoutf("aborting connection attempt"); + abortconnect(); + } + + if(serverport <= 0) serverport = server::serverport(); + + ENetAddress address; + address.port = serverport; + + if(servername) + { + if(strcmp(servername, connectname)) setsvar("connectname", servername); + if(serverport != connectport) setvar("connectport", serverport); + addserver(servername, serverport, serverpassword && serverpassword[0] ? serverpassword : NULL); + conoutf("attempting to connect to %s:%d", servername, serverport); + if(!resolverwait(servername, &address)) + { + conoutf(CON_ERROR, "\f3could not resolve server %s", servername); + return; + } + } + else + { + setsvar("connectname", ""); + setvar("connectport", 0); + conoutf("attempting to connect over LAN"); + address.host = ENET_HOST_BROADCAST; + } + + if(!clienthost) + { + clienthost = enet_host_create(NULL, 2, server::numchannels(), rate*1024, rate*1024); + if(!clienthost) + { + conoutf(CON_ERROR, "\f3could not connect to server"); + return; + } + clienthost->duplicatePeers = 0; + } + + connpeer = enet_host_connect(clienthost, &address, server::numchannels(), 0); + enet_host_flush(clienthost); + connmillis = totalmillis; + connattempts = 0; + + game::connectattempt(servername ? servername : "", serverpassword ? serverpassword : "", address); } void reconnect(const char *serverpassword) { - if(!connectname[0] || connectport <= 0) - { - conoutf(CON_ERROR, "no previous connection"); - return; - } + if(!connectname[0] || connectport <= 0) + { + conoutf(CON_ERROR, "no previous connection"); + return; + } - connectserv(connectname, connectport, serverpassword); + connectserv(connectname, connectport, serverpassword); } void disconnect(bool async, bool cleanup) { - if(curpeer) - { - if(!discmillis) - { - enet_peer_disconnect(curpeer, DISC_NONE); - enet_host_flush(clienthost); - discmillis = totalmillis; - } - if(curpeer->state!=ENET_PEER_STATE_DISCONNECTED) - { - if(async) return; - enet_peer_reset(curpeer); - } - curpeer = NULL; - discmillis = 0; - conoutf("disconnected"); - game::gamedisconnect(cleanup); - mainmenu = 1; - } - if(!connpeer && clienthost) - { - enet_host_destroy(clienthost); - clienthost = NULL; - } + if(curpeer) + { + if(!discmillis) + { + enet_peer_disconnect(curpeer, DISC_NONE); + enet_host_flush(clienthost); + discmillis = totalmillis; + } + if(curpeer->state!=ENET_PEER_STATE_DISCONNECTED) + { + if(async) return; + enet_peer_reset(curpeer); + } + curpeer = NULL; + discmillis = 0; + conoutf("disconnected"); + game::gamedisconnect(cleanup); + mainmenu = 1; + } + if(!connpeer && clienthost) + { + enet_host_destroy(clienthost); + clienthost = NULL; + } } void trydisconnect(bool local) { - if(connpeer) - { - conoutf("aborting connection attempt"); - abortconnect(); - } - else if(curpeer) - { - conoutf("attempting to disconnect..."); - disconnect(!discmillis); - } - else if(local && haslocalclients()) localdisconnect(); - else conoutf(CON_WARN, "not connected"); + if(connpeer) + { + conoutf("aborting connection attempt"); + abortconnect(); + } + else if(curpeer) + { + conoutf("attempting to disconnect..."); + disconnect(!discmillis); + } + else if(local && haslocalclients()) localdisconnect(); + else conoutf(CON_WARN, "not connected"); } ICOMMAND(connect, "sis", (char *name, int *port, char *pw), connectserv(name, *port, pw)); @@ -189,86 +189,86 @@ ICOMMAND(localdisconnect, "", (), { if(haslocalclients()) localdisconnect(); }); void sendclientpacket(ENetPacket *packet, int chan) { - if(curpeer) enet_peer_send(curpeer, chan, packet); - else localclienttoserver(chan, packet); + if(curpeer) enet_peer_send(curpeer, chan, packet); + else localclienttoserver(chan, packet); } void flushclient() { - if(clienthost) enet_host_flush(clienthost); + if(clienthost) enet_host_flush(clienthost); } void neterr(const char *s, bool disc) { - conoutf(CON_ERROR, "\f3illegal network message (%s)", s); - if(disc) disconnect(); + conoutf(CON_ERROR, "\f3illegal network message (%s)", s); + if(disc) disconnect(); } void localservertoclient(int chan, ENetPacket *packet) // processes any updates from the server { - packetbuf p(packet); - game::parsepacketclient(chan, p); + packetbuf p(packet); + game::parsepacketclient(chan, p); } void clientkeepalive() { if(clienthost) enet_host_service(clienthost, NULL, 0); } -void gets2c() // get updates from the server +void gets2c() // get updates from the server { - ENetEvent event; - if(!clienthost) return; - if(connpeer && totalmillis/3000 > connmillis/3000) - { - conoutf("attempting to connect..."); - connmillis = totalmillis; - ++connattempts; - if(connattempts > 3) - { - conoutf(CON_ERROR, "\f3could not connect to server"); - abortconnect(); - return; - } - } - while(clienthost && enet_host_service(clienthost, &event, 0)>0) - switch(event.type) - { - case ENET_EVENT_TYPE_CONNECT: - disconnect(false, false); - localdisconnect(false); - curpeer = connpeer; - connpeer = NULL; - conoutf("connected to server"); - throttle(); - if(rate) setrate(rate); - game::gameconnect(true); - break; - - case ENET_EVENT_TYPE_RECEIVE: - if(discmillis) conoutf("attempting to disconnect..."); - else localservertoclient(event.channelID, event.packet); - enet_packet_destroy(event.packet); - break; - - case ENET_EVENT_TYPE_DISCONNECT: - if(event.data>=DISC_NUM) event.data = DISC_NONE; - if(event.peer==connpeer) - { - conoutf(CON_ERROR, "\f3could not connect to server"); - abortconnect(); - } - else - { - if(!discmillis || event.data) - { - const char *msg = disconnectreason(event.data); - if(msg) conoutf(CON_ERROR, "\f3server network error, disconnecting (%s) ...", msg); - else conoutf(CON_ERROR, "\f3server network error, disconnecting..."); - } - disconnect(); - } - return; - - default: - break; - } + ENetEvent event; + if(!clienthost) return; + if(connpeer && totalmillis/3000 > connmillis/3000) + { + conoutf("attempting to connect..."); + connmillis = totalmillis; + ++connattempts; + if(connattempts > 3) + { + conoutf(CON_ERROR, "\f3could not connect to server"); + abortconnect(); + return; + } + } + while(clienthost && enet_host_service(clienthost, &event, 0)>0) + switch(event.type) + { + case ENET_EVENT_TYPE_CONNECT: + disconnect(false, false); + localdisconnect(false); + curpeer = connpeer; + connpeer = NULL; + conoutf("connected to server"); + throttle(); + if(rate) setrate(rate); + game::gameconnect(true); + break; + + case ENET_EVENT_TYPE_RECEIVE: + if(discmillis) conoutf("attempting to disconnect..."); + else localservertoclient(event.channelID, event.packet); + enet_packet_destroy(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + if(event.data>=DISC_NUM) event.data = DISC_NONE; + if(event.peer==connpeer) + { + conoutf(CON_ERROR, "\f3could not connect to server"); + abortconnect(); + } + else + { + if(!discmillis || event.data) + { + const char *msg = disconnectreason(event.data); + if(msg) conoutf(CON_ERROR, "\f3server network error, disconnecting (%s) ...", msg); + else conoutf(CON_ERROR, "\f3server network error, disconnecting..."); + } + disconnect(); + } + return; + + default: + break; + } } diff --git a/src/engine/command.cpp b/src/engine/command.cpp index 0029e4d..012b8c1 100644 --- a/src/engine/command.cpp +++ b/src/engine/command.cpp @@ -11,186 +11,186 @@ int identflags = 0; enum { - MAXARGS = 25, - MAXCOMARGS = 12 + MAXARGS = 25, + MAXCOMARGS = 12 }; VARN(numargs, _numargs, MAXARGS, 0, 0); static inline void freearg(tagval &v) { - switch(v.type) - { - case VAL_STR: delete[] v.s; break; - case VAL_CODE: if(v.code[-1] == CODE_START) delete[] (uchar *)&v.code[-1]; break; - } + switch(v.type) + { + case VAL_STR: delete[] v.s; break; + case VAL_CODE: if(v.code[-1] == CODE_START) delete[] (uchar *)&v.code[-1]; break; + } } static inline void forcenull(tagval &v) { - switch(v.type) - { - case VAL_NULL: return; - } - freearg(v); - v.setnull(); + switch(v.type) + { + case VAL_NULL: return; + } + freearg(v); + v.setnull(); } static inline float forcefloat(tagval &v) { - float f = 0.0f; - switch(v.type) - { - case VAL_INT: f = v.i; break; - case VAL_STR: f = parsefloat(v.s); break; - case VAL_MACRO: f = parsefloat(v.s); break; - case VAL_FLOAT: return v.f; - } - freearg(v); - v.setfloat(f); - return f; + float f = 0.0f; + switch(v.type) + { + case VAL_INT: f = v.i; break; + case VAL_STR: f = parsefloat(v.s); break; + case VAL_MACRO: f = parsefloat(v.s); break; + case VAL_FLOAT: return v.f; + } + freearg(v); + v.setfloat(f); + return f; } static inline int forceint(tagval &v) { - int i = 0; - switch(v.type) - { - case VAL_FLOAT: i = v.f; break; - case VAL_STR: i = parseint(v.s); break; - case VAL_MACRO: i = parseint(v.s); break; - case VAL_INT: return v.i; - } - freearg(v); - v.setint(i); - return i; + int i = 0; + switch(v.type) + { + case VAL_FLOAT: i = v.f; break; + case VAL_STR: i = parseint(v.s); break; + case VAL_MACRO: i = parseint(v.s); break; + case VAL_INT: return v.i; + } + freearg(v); + v.setint(i); + return i; } static inline const char *forcestr(tagval &v) { - const char *s = ""; - switch(v.type) - { - case VAL_FLOAT: s = floatstr(v.f); break; - case VAL_INT: s = intstr(v.i); break; - case VAL_STR: - [[fallthrough]]; - case VAL_MACRO: return v.s; - } - freearg(v); - v.setstr(newstring(s)); - return s; + const char *s = ""; + switch(v.type) + { + case VAL_FLOAT: s = floatstr(v.f); break; + case VAL_INT: s = intstr(v.i); break; + case VAL_STR: + [[fallthrough]]; + case VAL_MACRO: return v.s; + } + freearg(v); + v.setstr(newstring(s)); + return s; } static inline void forcearg(tagval &v, int type) { - switch(type) - { - case RET_STR: if(v.type != VAL_STR) forcestr(v); break; - case RET_INT: if(v.type != VAL_INT) forceint(v); break; - case RET_FLOAT: if(v.type != VAL_FLOAT) forcefloat(v); break; - } + switch(type) + { + case RET_STR: if(v.type != VAL_STR) forcestr(v); break; + case RET_INT: if(v.type != VAL_INT) forceint(v); break; + case RET_FLOAT: if(v.type != VAL_FLOAT) forcefloat(v); break; + } } static inline ident *forceident(tagval &v) { - switch(v.type) - { - case VAL_IDENT: return v.id; - case VAL_MACRO: - { - ident *id = newident(v.s, IDF_UNKNOWN); - v.setident(id); - return id; - } - case VAL_STR: - { - ident *id = newident(v.s, IDF_UNKNOWN); - delete[] v.s; - v.setident(id); - return id; - } - } - freearg(v); - v.setident(dummyident); - return dummyident; + switch(v.type) + { + case VAL_IDENT: return v.id; + case VAL_MACRO: + { + ident *id = newident(v.s, IDF_UNKNOWN); + v.setident(id); + return id; + } + case VAL_STR: + { + ident *id = newident(v.s, IDF_UNKNOWN); + delete[] v.s; + v.setident(id); + return id; + } + } + freearg(v); + v.setident(dummyident); + return dummyident; } void tagval::cleanup() { - freearg(*this); + freearg(*this); } static inline void freeargs(tagval *args, int &oldnum, int newnum) { - for(int i = newnum; i < oldnum; i++) freearg(args[i]); - oldnum = newnum; + for(int i = newnum; i < oldnum; i++) freearg(args[i]); + oldnum = newnum; } static inline void cleancode(ident &id) { - if(id.code) - { - id.code[0] -= 0x100; - if(int(id.code[0]) < 0x100) delete[] id.code; - id.code = NULL; - } + if(id.code) + { + id.code[0] -= 0x100; + if(int(id.code[0]) < 0x100) delete[] id.code; + id.code = NULL; + } } struct nullval : tagval { - nullval() { setnull(); } + nullval() { setnull(); } } nullval; tagval noret = nullval, *commandret = &noret; void clear_command() { - enumerate(idents, ident, i, - { - if(i.type==ID_ALIAS) - { - DELETEA(i.name); - i.forcenull(); - DELETEA(i.code); - } - }); + enumerate(idents, ident, i, + { + if(i.type==ID_ALIAS) + { + DELETEA(i.name); + i.forcenull(); + DELETEA(i.code); + } + }); } void clearoverride(ident &i) { - if(!(i.flags&IDF_OVERRIDDEN)) return; - switch(i.type) - { - case ID_ALIAS: - if(i.valtype==VAL_STR) - { - if(!i.val.s[0]) break; - delete[] i.val.s; - } - cleancode(i); - i.valtype = VAL_STR; - i.val.s = newstring(""); - break; - case ID_VAR: - *i.storage.i = i.overrideval.i; - i.changed(); - break; - case ID_FVAR: - *i.storage.f = i.overrideval.f; - i.changed(); - break; - case ID_SVAR: - delete[] *i.storage.s; - *i.storage.s = i.overrideval.s; - i.changed(); - break; - } - i.flags &= ~IDF_OVERRIDDEN; + if(!(i.flags&IDF_OVERRIDDEN)) return; + switch(i.type) + { + case ID_ALIAS: + if(i.valtype==VAL_STR) + { + if(!i.val.s[0]) break; + delete[] i.val.s; + } + cleancode(i); + i.valtype = VAL_STR; + i.val.s = newstring(""); + break; + case ID_VAR: + *i.storage.i = i.overrideval.i; + i.changed(); + break; + case ID_FVAR: + *i.storage.f = i.overrideval.f; + i.changed(); + break; + case ID_SVAR: + delete[] *i.storage.s; + *i.storage.s = i.overrideval.s; + i.changed(); + break; + } + i.flags &= ~IDF_OVERRIDDEN; } void clearoverrides() { - enumerate(idents, ident, i, clearoverride(i)); + enumerate(idents, ident, i, clearoverride(i)); } static bool initedidents = false; @@ -198,32 +198,32 @@ static vector *identinits = NULL; static inline ident *addident(const ident &id) { - if(!initedidents) - { - if(!identinits) identinits = new vector; - identinits->add(id); - return NULL; - } - ident &def = idents.access(id.name, id); - def.index = identmap.length(); - return identmap.add(&def); + if(!initedidents) + { + if(!identinits) identinits = new vector; + identinits->add(id); + return NULL; + } + ident &def = idents.access(id.name, id); + def.index = identmap.length(); + return identmap.add(&def); } static bool initidents() { - initedidents = true; - for(int i = 0; i < MAXARGS; i++) - { - defformatstring(argname, "arg%d", i+1); - newident(argname, IDF_ARG); - } - dummyident = newident("//dummy", IDF_UNKNOWN); - if(identinits) - { - loopv(*identinits) addident((*identinits)[i]); - DELETEP(identinits); - } - return true; + initedidents = true; + for(int i = 0; i < MAXARGS; i++) + { + defformatstring(argname, "arg%d", i+1); + newident(argname, IDF_ARG); + } + dummyident = newident("//dummy", IDF_UNKNOWN); + if(identinits) + { + loopv(*identinits) addident((*identinits)[i]); + DELETEP(identinits); + } + return true; } UNUSED static bool forceinitidents = initidents(); @@ -231,49 +231,49 @@ static const char *sourcefile = NULL, *sourcestr = NULL; static const char *debugline(const char *p, const char *fmt) { - if(!sourcestr) return fmt; - int num = 1; - const char *line = sourcestr; - for(;;) - { - const char *end = strchr(line, '\n'); - if(!end) end = line + strlen(line); - if(p >= line && p <= end) - { - static string buf; - if(sourcefile) formatstring(buf, "%s:%d: %s", sourcefile, num, fmt); - else formatstring(buf, "%d: %s", num, fmt); - return buf; - } - if(!*end) break; - line = end + 1; - num++; - } - return fmt; + if(!sourcestr) return fmt; + int num = 1; + const char *line = sourcestr; + for(;;) + { + const char *end = strchr(line, '\n'); + if(!end) end = line + strlen(line); + if(p >= line && p <= end) + { + static string buf; + if(sourcefile) formatstring(buf, "%s:%d: %s", sourcefile, num, fmt); + else formatstring(buf, "%d: %s", num, fmt); + return buf; + } + if(!*end) break; + line = end + 1; + num++; + } + return fmt; } static struct identlink { - ident *id; - identlink *next; - int usedargs; - identstack *argstack; + ident *id; + identlink *next; + int usedargs; + identstack *argstack; } noalias = { NULL, NULL, (1<next) total++; - for(identlink *l = aliasstack; l != &noalias; l = l->next) - { - ident *id = l->id; - ++depth; - if(depth < dbgalias) conoutf(CON_ERROR, " %d) %s", total-depth+1, id->name); - else if(l->next == &noalias) conoutf(CON_ERROR, depth == dbgalias ? " %d) %s" : " ..%d) %s", total-depth+1, id->name); - } + if(!dbgalias) return; + int total = 0, depth = 0; + for(identlink *l = aliasstack; l != &noalias; l = l->next) total++; + for(identlink *l = aliasstack; l != &noalias; l = l->next) + { + ident *id = l->id; + ++depth; + if(depth < dbgalias) conoutf(CON_ERROR, " %d) %s", total-depth+1, id->name); + else if(l->next == &noalias) conoutf(CON_ERROR, depth == dbgalias ? " %d) %s" : " ..%d) %s", total-depth+1, id->name); + } } static int nodebug = 0; @@ -282,294 +282,294 @@ static void debugcode(const char *fmt, ...) PRINTFARGS(1, 2); static void debugcode(const char *fmt, ...) { - if(nodebug) return; + if(nodebug) return; - va_list args; - va_start(args, fmt); - conoutfv(CON_ERROR, fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + conoutfv(CON_ERROR, fmt, args); + va_end(args); - debugalias(); + debugalias(); } static void debugcodeline(const char *p, const char *fmt, ...) PRINTFARGS(2, 3); static void debugcodeline(const char *p, const char *fmt, ...) { - if(nodebug) return; + if(nodebug) return; - va_list args; - va_start(args, fmt); - conoutfv(CON_ERROR, debugline(p, fmt), args); - va_end(args); + va_list args; + va_start(args, fmt); + conoutfv(CON_ERROR, debugline(p, fmt), args); + va_end(args); - debugalias(); + debugalias(); } ICOMMAND(nodebug, "e", (uint *body), { nodebug++; executeret(body, *commandret); nodebug--; }); void addident(ident *id) { - addident(*id); + addident(*id); } static inline void pusharg(ident &id, const tagval &v, identstack &stack) { - stack.val = id.val; - stack.valtype = id.valtype; - stack.next = id.stack; - id.stack = &stack; - id.setval(v); - cleancode(id); + stack.val = id.val; + stack.valtype = id.valtype; + stack.next = id.stack; + id.stack = &stack; + id.setval(v); + cleancode(id); } static inline void poparg(ident &id) { - if(!id.stack) return; - identstack *stack = id.stack; - if(id.valtype == VAL_STR) delete[] id.val.s; - id.setval(*stack); - cleancode(id); - id.stack = stack->next; + if(!id.stack) return; + identstack *stack = id.stack; + if(id.valtype == VAL_STR) delete[] id.val.s; + id.setval(*stack); + cleancode(id); + id.stack = stack->next; } ICOMMAND(push, "rte", (ident *id, tagval *v, uint *code), { - if(id->type != ID_ALIAS || id->index < MAXARGS) return; - identstack stack; - pusharg(*id, *v, stack); - v->type = VAL_NULL; - id->flags &= ~IDF_UNKNOWN; - executeret(code, *commandret); - poparg(*id); + if(id->type != ID_ALIAS || id->index < MAXARGS) return; + identstack stack; + pusharg(*id, *v, stack); + v->type = VAL_NULL; + id->flags &= ~IDF_UNKNOWN; + executeret(code, *commandret); + poparg(*id); }); static inline void pushalias(ident &id, identstack &stack) { - if(id.type == ID_ALIAS && id.index >= MAXARGS) - { - pusharg(id, nullval, stack); - id.flags &= ~IDF_UNKNOWN; - } + if(id.type == ID_ALIAS && id.index >= MAXARGS) + { + pusharg(id, nullval, stack); + id.flags &= ~IDF_UNKNOWN; + } } static inline void popalias(ident &id) { - if(id.type == ID_ALIAS && id.index >= MAXARGS) poparg(id); + if(id.type == ID_ALIAS && id.index >= MAXARGS) poparg(id); } KEYWORD(local, ID_LOCAL); static inline bool checknumber(const char *s) { - if(isdigit(s[0])) return true; - else switch(s[0]) - { - case '+': case '-': return isdigit(s[1]) || (s[1] == '.' && isdigit(s[2])); - case '.': return isdigit(s[1]) != 0; - default: return false; - } + if(isdigit(s[0])) return true; + else switch(s[0]) + { + case '+': case '-': return isdigit(s[1]) || (s[1] == '.' && isdigit(s[2])); + case '.': return isdigit(s[1]) != 0; + default: return false; + } } ident *newident(const char *name, int flags) { - ident *id = idents.access(name); - if(!id) - { - if(checknumber(name)) - { - debugcode("number %s is not a valid identifier name", name); - return dummyident; - } - id = addident(ident(ID_ALIAS, newstring(name), flags)); - } - return id; + ident *id = idents.access(name); + if(!id) + { + if(checknumber(name)) + { + debugcode("number %s is not a valid identifier name", name); + return dummyident; + } + id = addident(ident(ID_ALIAS, newstring(name), flags)); + } + return id; } ident *writeident(const char *name, int flags) { - ident *id = newident(name, flags); - if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) - { - pusharg(*id, nullval, aliasstack->argstack[id->index]); - aliasstack->usedargs |= 1<index; - } - return id; + ident *id = newident(name, flags); + if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) + { + pusharg(*id, nullval, aliasstack->argstack[id->index]); + aliasstack->usedargs |= 1<index; + } + return id; } ident *readident(const char *name) { - ident *id = idents.access(name); - if(id && id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) - return NULL; - return id; + ident *id = idents.access(name); + if(id && id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) + return NULL; + return id; } void resetvar(char *name) { - ident *id = idents.access(name); - if(!id) return; - if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); - else clearoverride(*id); + ident *id = idents.access(name); + if(!id) return; + if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); + else clearoverride(*id); } COMMAND(resetvar, "s"); static inline void setarg(ident &id, tagval &v) { - if(aliasstack->usedargs&(1<argstack[id.index]); - aliasstack->usedargs |= 1<usedargs&(1<argstack[id.index]); + aliasstack->usedargs |= 1<type == ID_ALIAS) - { - if(id->index < MAXARGS) setarg(*id, v); else setalias(*id, v); - } - else - { - debugcode("cannot redefine builtin %s with an alias", id->name); - freearg(v); - } - } - else if(checknumber(name)) - { - debugcode("cannot alias number %s", name); - freearg(v); - } - else - { - addident(ident(ID_ALIAS, newstring(name), v, identflags)); - } + ident *id = idents.access(name); + if(id) + { + if(id->type == ID_ALIAS) + { + if(id->index < MAXARGS) setarg(*id, v); else setalias(*id, v); + } + else + { + debugcode("cannot redefine builtin %s with an alias", id->name); + freearg(v); + } + } + else if(checknumber(name)) + { + debugcode("cannot alias number %s", name); + freearg(v); + } + else + { + addident(ident(ID_ALIAS, newstring(name), v, identflags)); + } } void alias(const char *name, const char *str) { - tagval v; - v.setstr(newstring(str)); - setalias(name, v); + tagval v; + v.setstr(newstring(str)); + setalias(name, v); } void alias(const char *name, tagval &v) { - setalias(name, v); + setalias(name, v); } ICOMMAND(alias, "st", (const char *name, tagval *v), { - setalias(name, *v); - v->type = VAL_NULL; + setalias(name, *v); + v->type = VAL_NULL; }); // variable's and commands are registered through globals, see cube.h int variable(const char *name, int min, int cur, int max, int *storage, identfun fun, int flags) { - addident(ident(ID_VAR, name, min, max, storage, (void *)fun, flags)); - return cur; + addident(ident(ID_VAR, name, min, max, storage, (void *)fun, flags)); + return cur; } float fvariable(const char *name, float min, float cur, float max, float *storage, identfun fun, int flags) { - addident(ident(ID_FVAR, name, min, max, storage, (void *)fun, flags)); - return cur; + addident(ident(ID_FVAR, name, min, max, storage, (void *)fun, flags)); + return cur; } char *svariable(const char *name, const char *cur, char **storage, identfun fun, int flags) { - addident(ident(ID_SVAR, name, storage, (void *)fun, flags)); - return newstring(cur); + addident(ident(ID_SVAR, name, storage, (void *)fun, flags)); + return newstring(cur); } #define _GETVAR(id, vartype, name, retval) \ - ident *id = idents.access(name); \ - if(!id || id->type!=vartype) return retval; + ident *id = idents.access(name); \ + if(!id || id->type!=vartype) return retval; #define GETVAR(id, name, retval) _GETVAR(id, ID_VAR, name, retval) #define OVERRIDEVAR(errorval, saveval, resetval, clearval) \ - if(identflags&IDF_OVERRIDDEN || id->flags&IDF_OVERRIDE) \ - { \ - if(id->flags&IDF_PERSIST) \ - { \ - debugcode("cannot override persistent variable %s", id->name); \ - errorval; \ - } \ - if(!(id->flags&IDF_OVERRIDDEN)) { saveval; id->flags |= IDF_OVERRIDDEN; } \ - else { clearval; } \ - } \ - else \ - { \ - if(id->flags&IDF_OVERRIDDEN) { resetval; id->flags &= ~IDF_OVERRIDDEN; } \ - clearval; \ - } + if(identflags&IDF_OVERRIDDEN || id->flags&IDF_OVERRIDE) \ + { \ + if(id->flags&IDF_PERSIST) \ + { \ + debugcode("cannot override persistent variable %s", id->name); \ + errorval; \ + } \ + if(!(id->flags&IDF_OVERRIDDEN)) { saveval; id->flags |= IDF_OVERRIDDEN; } \ + else { clearval; } \ + } \ + else \ + { \ + if(id->flags&IDF_OVERRIDDEN) { resetval; id->flags &= ~IDF_OVERRIDDEN; } \ + clearval; \ + } void setvar(const char *name, int i, bool dofunc, bool doclamp) { - GETVAR(id, name, ); - OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , ) - if(doclamp) *id->storage.i = clamp(i, id->minval, id->maxval); - else *id->storage.i = i; - if(dofunc) id->changed(); + GETVAR(id, name, ); + OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , ) + if(doclamp) *id->storage.i = clamp(i, id->minval, id->maxval); + else *id->storage.i = i; + if(dofunc) id->changed(); } void setfvar(const char *name, float f, bool dofunc, bool doclamp) { - _GETVAR(id, ID_FVAR, name, ); - OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , ); - if(doclamp) *id->storage.f = clamp(f, id->minvalf, id->maxvalf); - else *id->storage.f = f; - if(dofunc) id->changed(); + _GETVAR(id, ID_FVAR, name, ); + OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , ); + if(doclamp) *id->storage.f = clamp(f, id->minvalf, id->maxvalf); + else *id->storage.f = f; + if(dofunc) id->changed(); } void setsvar(const char *name, const char *str, bool dofunc) { - _GETVAR(id, ID_SVAR, name, ); - OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s); - *id->storage.s = newstring(str); - if(dofunc) id->changed(); + _GETVAR(id, ID_SVAR, name, ); + OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s); + *id->storage.s = newstring(str); + if(dofunc) id->changed(); } int getvar(const char *name) { - GETVAR(id, name, 0); - return *id->storage.i; + GETVAR(id, name, 0); + return *id->storage.i; } int getvarmin(const char *name) { - GETVAR(id, name, 0); - return id->minval; + GETVAR(id, name, 0); + return id->minval; } int getvarmax(const char *name) { - GETVAR(id, name, 0); - return id->maxval; + GETVAR(id, name, 0); + return id->maxval; } float getfvarmin(const char *name) { - _GETVAR(id, ID_FVAR, name, 0); - return id->minvalf; + _GETVAR(id, ID_FVAR, name, 0); + return id->minvalf; } float getfvarmax(const char *name) { - _GETVAR(id, ID_FVAR, name, 0); - return id->maxvalf; + _GETVAR(id, ID_FVAR, name, 0); + return id->maxvalf; } ICOMMAND(getvarmin, "s", (char *s), intret(getvarmin(s))); @@ -582,433 +582,433 @@ ident *getident(const char *name) { return idents.access(name); } void touchvar(const char *name) { - ident *id = idents.access(name); - if(id) switch(id->type) - { - case ID_VAR: - case ID_FVAR: - case ID_SVAR: - id->changed(); - break; - } + ident *id = idents.access(name); + if(id) switch(id->type) + { + case ID_VAR: + case ID_FVAR: + case ID_SVAR: + id->changed(); + break; + } } const char *getalias(const char *name) { - ident *i = idents.access(name); - return i && i->type==ID_ALIAS && (i->index >= MAXARGS || aliasstack->usedargs&(1<index)) ? i->getstr() : ""; + ident *i = idents.access(name); + return i && i->type==ID_ALIAS && (i->index >= MAXARGS || aliasstack->usedargs&(1<index)) ? i->getstr() : ""; } ICOMMAND(getalias, "s", (char *s), result(getalias(s))); int clampvar(ident *id, int val, int minval, int maxval) { - if(val < minval) val = minval; - else if(val > maxval) val = maxval; - else return val; - debugcode(id->flags&IDF_HEX ? - (minval <= 255 ? "valid range for %s is %d..0x%X" : "valid range for %s is 0x%X..0x%X") : - "valid range for %s is %d..%d", - id->name, minval, maxval); - return val; + if(val < minval) val = minval; + else if(val > maxval) val = maxval; + else return val; + debugcode(id->flags&IDF_HEX ? + (minval <= 255 ? "valid range for %s is %d..0x%X" : "valid range for %s is 0x%X..0x%X") : + "valid range for %s is %d..%d", + id->name, minval, maxval); + return val; } void setvarchecked(ident *id, int val) { - if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); + if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); #ifndef STANDALONE - else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle()) + else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle()) #else - else + else #endif - { - OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , ) - if(val < id->minval || val > id->maxval) val = clampvar(id, val, id->minval, id->maxval); - *id->storage.i = val; - id->changed(); // call trigger function if available + { + OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , ) + if(val < id->minval || val > id->maxval) val = clampvar(id, val, id->minval, id->maxval); + *id->storage.i = val; + id->changed(); // call trigger function if available #ifndef STANDALONE - if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id); + if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id); #endif - } + } } static inline void setvarchecked(ident *id, tagval *args, int numargs) { - int val = forceint(args[0]); - if(id->flags&IDF_HEX && numargs > 1) - { - val = (val << 16) | (forceint(args[1])<<8); - if(numargs > 2) val |= forceint(args[2]); - } - setvarchecked(id, val); + int val = forceint(args[0]); + if(id->flags&IDF_HEX && numargs > 1) + { + val = (val << 16) | (forceint(args[1])<<8); + if(numargs > 2) val |= forceint(args[2]); + } + setvarchecked(id, val); } float clampfvar(ident *id, float val, float minval, float maxval) { - if(val < minval) val = minval; - else if(val > maxval) val = maxval; - else return val; - debugcode("valid range for %s is %s..%s", id->name, floatstr(minval), floatstr(maxval)); - return val; + if(val < minval) val = minval; + else if(val > maxval) val = maxval; + else return val; + debugcode("valid range for %s is %s..%s", id->name, floatstr(minval), floatstr(maxval)); + return val; } void setfvarchecked(ident *id, float val) { - if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); + if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); #ifndef STANDALONE - else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle()) + else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle()) #else - else + else #endif - { - OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , ); - if(val < id->minvalf || val > id->maxvalf) val = clampfvar(id, val, id->minvalf, id->maxvalf); - *id->storage.f = val; - id->changed(); + { + OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , ); + if(val < id->minvalf || val > id->maxvalf) val = clampfvar(id, val, id->minvalf, id->maxvalf); + *id->storage.f = val; + id->changed(); #ifndef STANDALONE - if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id); + if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id); #endif - } + } } void setsvarchecked(ident *id, const char *val) { - if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); + if(id->flags&IDF_READONLY) debugcode("variable %s is read-only", id->name); #ifndef STANDALONE - else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle()) + else if(!(id->flags&IDF_OVERRIDE) || identflags&IDF_OVERRIDDEN || game::allowedittoggle()) #else - else + else #endif - { - OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s); - *id->storage.s = newstring(val); - id->changed(); + { + OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s); + *id->storage.s = newstring(val); + id->changed(); #ifndef STANDALONE - if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id); + if(id->flags&IDF_OVERRIDE && !(identflags&IDF_OVERRIDDEN)) game::vartrigger(id); #endif - } + } } ICOMMAND(set, "rt", (ident *id, tagval *v), { - switch(id->type) - { - case ID_ALIAS: - if(id->index < MAXARGS) setarg(*id, *v); else setalias(*id, *v); - v->type = VAL_NULL; - break; - case ID_VAR: - setvarchecked(id, forceint(*v)); - break; - case ID_FVAR: - setfvarchecked(id, forcefloat(*v)); - break; - case ID_SVAR: - setsvarchecked(id, forcestr(*v)); - break; - case ID_COMMAND: - if(id->flags&IDF_EMUVAR) - { - execute(id, v, 1); - v->type = VAL_NULL; - break; - } - // fall through - default: - debugcode("cannot redefine builtin %s with an alias", id->name); - break; - } + switch(id->type) + { + case ID_ALIAS: + if(id->index < MAXARGS) setarg(*id, *v); else setalias(*id, *v); + v->type = VAL_NULL; + break; + case ID_VAR: + setvarchecked(id, forceint(*v)); + break; + case ID_FVAR: + setfvarchecked(id, forcefloat(*v)); + break; + case ID_SVAR: + setsvarchecked(id, forcestr(*v)); + break; + case ID_COMMAND: + if(id->flags&IDF_EMUVAR) + { + execute(id, v, 1); + v->type = VAL_NULL; + break; + } + // fall through + default: + debugcode("cannot redefine builtin %s with an alias", id->name); + break; + } }); bool addcommand(const char *name, identfun fun, const char *args) { - uint argmask = 0; - int numargs = 0, flags = 0; - bool limit = true; - for(const char *fmt = args; *fmt; fmt++) switch(*fmt) - { - case 'i': case 'b': case 'f': case 't': case 'N': case 'D': if(numargs < MAXARGS) numargs++; break; - case '$': flags |= IDF_EMUVAR; // fall through - case 's': case 'e': case 'r': if(numargs < MAXARGS) { argmask |= 1< MAXCOMARGS) fatal("builtin %s declared with too many args: %d", name, numargs); - addident(ident(ID_COMMAND, name, args, argmask, numargs, (void *)fun, flags)); - return false; + uint argmask = 0; + int numargs = 0, flags = 0; + bool limit = true; + for(const char *fmt = args; *fmt; fmt++) switch(*fmt) + { + case 'i': case 'b': case 'f': case 't': case 'N': case 'D': if(numargs < MAXARGS) numargs++; break; + case '$': flags |= IDF_EMUVAR; // fall through + case 's': case 'e': case 'r': if(numargs < MAXARGS) { argmask |= 1< MAXCOMARGS) fatal("builtin %s declared with too many args: %d", name, numargs); + addident(ident(ID_COMMAND, name, args, argmask, numargs, (void *)fun, flags)); + return false; } bool addkeyword(int type, const char *name) { - addident(ident(type, name, "", 0, 0, NULL)); - return true; + addident(ident(type, name, "", 0, 0, NULL)); + return true; } const char *parsestring(const char *p) { - for(; *p; p++) switch(*p) - { - case '\r': - case '\n': - case '\"': - return p; - case '^': - if(*++p) break; - return p; - } - return p; + for(; *p; p++) switch(*p) + { + case '\r': + case '\n': + case '\"': + return p; + case '^': + if(*++p) break; + return p; + } + return p; } int unescapestring(char *dst, const char *src, const char *end) { - char *start = dst; - while(src < end) - { - int c = *src++; - if(c == '^') - { - if(src >= end) break; - int e = *src++; - switch(e) - { - case 'n': *dst++ = '\n'; break; - case 't': *dst++ = '\t'; break; - case 'f': *dst++ = '\f'; break; - default: *dst++ = e; break; - } - } - else *dst++ = c; - } - return dst - start; + char *start = dst; + while(src < end) + { + int c = *src++; + if(c == '^') + { + if(src >= end) break; + int e = *src++; + switch(e) + { + case 'n': *dst++ = '\n'; break; + case 't': *dst++ = '\t'; break; + case 'f': *dst++ = '\f'; break; + default: *dst++ = e; break; + } + } + else *dst++ = c; + } + return dst - start; } static char *conc(vector &buf, tagval *v, int n, bool space, const char *prefix = NULL, int prefixlen = 0) { - if(prefix) - { - buf.put(prefix, prefixlen); - if(space && n) buf.add(' '); - } - loopi(n) - { - const char *s = ""; - int len = 0; - switch(v[i].type) - { - case VAL_INT: s = intstr(v[i].i); break; - case VAL_FLOAT: s = floatstr(v[i].f); break; - case VAL_STR: s = v[i].s; break; - case VAL_MACRO: s = v[i].s; len = v[i].code[-1]>>8; goto haslen; - } - len = int(strlen(s)); - haslen: - buf.put(s, len); - if(i == n-1) break; - if(space) buf.add(' '); - } - buf.add('\0'); - return buf.getbuf(); + if(prefix) + { + buf.put(prefix, prefixlen); + if(space && n) buf.add(' '); + } + loopi(n) + { + const char *s = ""; + int len = 0; + switch(v[i].type) + { + case VAL_INT: s = intstr(v[i].i); break; + case VAL_FLOAT: s = floatstr(v[i].f); break; + case VAL_STR: s = v[i].s; break; + case VAL_MACRO: s = v[i].s; len = v[i].code[-1]>>8; goto haslen; + } + len = int(strlen(s)); + haslen: + buf.put(s, len); + if(i == n-1) break; + if(space) buf.add(' '); + } + buf.add('\0'); + return buf.getbuf(); } static char *conc(tagval *v, int n, bool space, const char *prefix, int prefixlen) { - static int vlen[MAXARGS]; - static char numbuf[3*MAXSTRLEN]; - int len = prefixlen, numlen = 0, i = 0; - for(; i < n; i++) switch(v[i].type) - { - case VAL_MACRO: len += (vlen[i] = v[i].code[-1]>>8); break; - case VAL_STR: len += (vlen[i] = int(strlen(v[i].s))); break; - case VAL_INT: - if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow; - intformat(&numbuf[numlen], v[i].i); - numlen += (vlen[i] = strlen(&numbuf[numlen])); - break; - case VAL_FLOAT: - if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow; - floatformat(&numbuf[numlen], v[i].f); - numlen += (vlen[i] = strlen(&numbuf[numlen])); - break; - default: vlen[i] = 0; break; - } + static int vlen[MAXARGS]; + static char numbuf[3*MAXSTRLEN]; + int len = prefixlen, numlen = 0, i = 0; + for(; i < n; i++) switch(v[i].type) + { + case VAL_MACRO: len += (vlen[i] = v[i].code[-1]>>8); break; + case VAL_STR: len += (vlen[i] = int(strlen(v[i].s))); break; + case VAL_INT: + if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow; + intformat(&numbuf[numlen], v[i].i); + numlen += (vlen[i] = strlen(&numbuf[numlen])); + break; + case VAL_FLOAT: + if(numlen + MAXSTRLEN > int(sizeof(numbuf))) goto overflow; + floatformat(&numbuf[numlen], v[i].f); + numlen += (vlen[i] = strlen(&numbuf[numlen])); + break; + default: vlen[i] = 0; break; + } overflow: - if(space) len += max(prefix ? i : i-1, 0); - char *buf = newstring(len + numlen); - int offset = 0, numoffset = 0; - if(prefix) - { - memcpy(buf, prefix, prefixlen); - offset += prefixlen; - if(space && i) buf[offset++] = ' '; - } - loopj(i) - { - if(v[j].type == VAL_INT || v[j].type == VAL_FLOAT) - { - memcpy(&buf[offset], &numbuf[numoffset], vlen[j]); - numoffset += vlen[j]; - } - else if(vlen[j]) memcpy(&buf[offset], v[j].s, vlen[j]); - offset += vlen[j]; - if(j==i-1) break; - if(space) buf[offset++] = ' '; - } - buf[offset] = '\0'; - if(i < n) - { - char *morebuf = conc(&v[i], n-i, space, buf, offset); - delete[] buf; - return morebuf; - } - return buf; + if(space) len += max(prefix ? i : i-1, 0); + char *buf = newstring(len + numlen); + int offset = 0, numoffset = 0; + if(prefix) + { + memcpy(buf, prefix, prefixlen); + offset += prefixlen; + if(space && i) buf[offset++] = ' '; + } + loopj(i) + { + if(v[j].type == VAL_INT || v[j].type == VAL_FLOAT) + { + memcpy(&buf[offset], &numbuf[numoffset], vlen[j]); + numoffset += vlen[j]; + } + else if(vlen[j]) memcpy(&buf[offset], v[j].s, vlen[j]); + offset += vlen[j]; + if(j==i-1) break; + if(space) buf[offset++] = ' '; + } + buf[offset] = '\0'; + if(i < n) + { + char *morebuf = conc(&v[i], n-i, space, buf, offset); + delete[] buf; + return morebuf; + } + return buf; } static inline char *conc(tagval *v, int n, bool space) { - return conc(v, n, space, NULL, 0); + return conc(v, n, space, NULL, 0); } static inline char *conc(tagval *v, int n, bool space, const char *prefix) { - return conc(v, n, space, prefix, strlen(prefix)); + return conc(v, n, space, prefix, strlen(prefix)); } static inline void skipcomments(const char *&p) { - for(;;) - { - p += strspn(p, " \t\r"); - if(p[0]!='/' || p[1]!='/') break; - p += strcspn(p, "\n\0"); - } + for(;;) + { + p += strspn(p, " \t\r"); + if(p[0]!='/' || p[1]!='/') break; + p += strcspn(p, "\n\0"); + } } static inline char *cutstring(const char *&p, int &len) { - p++; - const char *end = parsestring(p); - char *s = newstring(end - p); - len = unescapestring(s, p, end); - s[len] = '\0'; - p = end; - if(*p=='\"') p++; - return s; + p++; + const char *end = parsestring(p); + char *s = newstring(end - p); + len = unescapestring(s, p, end); + s[len] = '\0'; + p = end; + if(*p=='\"') p++; + return s; } static inline const char *parseword(const char *p) { - const int maxbrak = 100; - static char brakstack[maxbrak]; - int brakdepth = 0; - for(;; p++) - { - p += strcspn(p, "\"/;()[] \t\r\n\0"); - switch(p[0]) - { - case '"': case ';': case ' ': case '\t': case '\r': case '\n': case '\0': return p; - case '/': if(p[1] == '/') return p; break; - case '[': case '(': if(brakdepth >= maxbrak) return p; brakstack[brakdepth++] = p[0]; break; - case ']': if(brakdepth <= 0 || brakstack[--brakdepth] != '[') return p; break; - case ')': if(brakdepth <= 0 || brakstack[--brakdepth] != '(') return p; break; - } - } - return p; + const int maxbrak = 100; + static char brakstack[maxbrak]; + int brakdepth = 0; + for(;; p++) + { + p += strcspn(p, "\"/;()[] \t\r\n\0"); + switch(p[0]) + { + case '"': case ';': case ' ': case '\t': case '\r': case '\n': case '\0': return p; + case '/': if(p[1] == '/') return p; break; + case '[': case '(': if(brakdepth >= maxbrak) return p; brakstack[brakdepth++] = p[0]; break; + case ']': if(brakdepth <= 0 || brakstack[--brakdepth] != '[') return p; break; + case ')': if(brakdepth <= 0 || brakstack[--brakdepth] != '(') return p; break; + } + } + return p; } static inline char *cutword(const char *&p, int &len) { - const char *word = p; - p = parseword(p); - len = p-word; - if(!len) return NULL; - return newstring(word, len); + const char *word = p; + p = parseword(p); + len = p-word; + if(!len) return NULL; + return newstring(word, len); } static inline void compilestr(vector &code, const char *word, int len, bool macro = false) { - if(len <= 3 && !macro) - { - uint op = CODE_VALI|RET_STR; - for(int i = 0; i < len; i++) op |= uint(uchar(word[i]))<<((i+1)*8); - code.add(op); - return; - } - code.add((macro ? CODE_MACRO : CODE_VAL|RET_STR)|(len<<8)); - code.put((const uint *)word, len/sizeof(uint)); - size_t endlen = len%sizeof(uint); - union - { - char c[sizeof(uint)]; - uint u; - } end; - end.u = 0; - memcpy(end.c, word + len - endlen, endlen); - code.add(end.u); + if(len <= 3 && !macro) + { + uint op = CODE_VALI|RET_STR; + for(int i = 0; i < len; i++) op |= uint(uchar(word[i]))<<((i+1)*8); + code.add(op); + return; + } + code.add((macro ? CODE_MACRO : CODE_VAL|RET_STR)|(len<<8)); + code.put((const uint *)word, len/sizeof(uint)); + size_t endlen = len%sizeof(uint); + union + { + char c[sizeof(uint)]; + uint u; + } end; + end.u = 0; + memcpy(end.c, word + len - endlen, endlen); + code.add(end.u); } static inline void compilestr(vector &code, const char *word = NULL) { - if(!word) { code.add(CODE_VALI|RET_STR); return; } - compilestr(code, word, int(strlen(word))); + if(!word) { code.add(CODE_VALI|RET_STR); return; } + compilestr(code, word, int(strlen(word))); } static inline void compileint(vector &code, int i) { - if(i >= -0x800000 && i <= 0x7FFFFF) - code.add(CODE_VALI|RET_INT|(i<<8)); - else - { - code.add(CODE_VAL|RET_INT); - code.add(i); - } + if(i >= -0x800000 && i <= 0x7FFFFF) + code.add(CODE_VALI|RET_INT|(i<<8)); + else + { + code.add(CODE_VAL|RET_INT); + code.add(i); + } } static inline void compilenull(vector &code) { - code.add(CODE_VALI|RET_NULL); + code.add(CODE_VALI|RET_NULL); } static inline void compileblock(vector &code) { - int start = code.length(); - code.add(CODE_BLOCK); - code.add(CODE_OFFSET|((start+2)<<8)); - code.add(CODE_EXIT); - code[start] |= uint(code.length() - (start + 1))<<8; + int start = code.length(); + code.add(CODE_BLOCK); + code.add(CODE_OFFSET|((start+2)<<8)); + code.add(CODE_EXIT); + code[start] |= uint(code.length() - (start + 1))<<8; } static inline void compileident(vector &code, ident *id) { - code.add((id->index < MAXARGS ? CODE_IDENTARG : CODE_IDENT)|(id->index<<8)); + code.add((id->index < MAXARGS ? CODE_IDENTARG : CODE_IDENT)|(id->index<<8)); } static inline void compileident(vector &code, const char *word = NULL) { - compileident(code, word ? newident(word, IDF_UNKNOWN) : dummyident); + compileident(code, word ? newident(word, IDF_UNKNOWN) : dummyident); } static inline void compileint(vector &code, const char *word = NULL) { - return compileint(code, word ? parseint(word) : 0); + return compileint(code, word ? parseint(word) : 0); } static inline void compilefloat(vector &code, float f) { - if(int(f) == f && f >= -0x800000 && f <= 0x7FFFFF) - code.add(CODE_VALI|RET_FLOAT|(int(f)<<8)); - else - { - union { float f; uint u; } conv; - conv.f = f; - code.add(CODE_VAL|RET_FLOAT); - code.add(conv.u); - } + if(int(f) == f && f >= -0x800000 && f <= 0x7FFFFF) + code.add(CODE_VALI|RET_FLOAT|(int(f)<<8)); + else + { + union { float f; uint u; } conv; + conv.f = f; + code.add(CODE_VAL|RET_FLOAT); + code.add(conv.u); + } } static inline void compilefloat(vector &code, const char *word = NULL) { - return compilefloat(code, word ? parsefloat(word) : 0.0f); + return compilefloat(code, word ? parsefloat(word) : 0.0f); } static bool compilearg(vector &code, const char *&p, int wordtype); @@ -1016,641 +1016,641 @@ static void compilestatements(vector &code, const char *&p, int rettype, i static inline void compileval(vector &code, int wordtype, char *word, int wordlen) { - switch(wordtype) - { - case VAL_STR: compilestr(code, word, wordlen, true); break; - case VAL_ANY: compilestr(code, word, wordlen); break; - case VAL_FLOAT: compilefloat(code, word); break; - case VAL_INT: compileint(code, word); break; - case VAL_CODE: - { - int start = code.length(); - code.add(CODE_BLOCK); - code.add(CODE_OFFSET|((start+2)<<8)); - const char *p = word; - if(p) compilestatements(code, p, VAL_ANY); - code.add(CODE_EXIT|RET_STR); - code[start] |= uint(code.length() - (start + 1))<<8; - break; - } - case VAL_IDENT: compileident(code, word); break; - default: - break; - } + switch(wordtype) + { + case VAL_STR: compilestr(code, word, wordlen, true); break; + case VAL_ANY: compilestr(code, word, wordlen); break; + case VAL_FLOAT: compilefloat(code, word); break; + case VAL_INT: compileint(code, word); break; + case VAL_CODE: + { + int start = code.length(); + code.add(CODE_BLOCK); + code.add(CODE_OFFSET|((start+2)<<8)); + const char *p = word; + if(p) compilestatements(code, p, VAL_ANY); + code.add(CODE_EXIT|RET_STR); + code[start] |= uint(code.length() - (start + 1))<<8; + break; + } + case VAL_IDENT: compileident(code, word); break; + default: + break; + } } static bool compileword(vector &code, const char *&p, int wordtype, char *&word, int &wordlen); static void compilelookup(vector &code, const char *&p, int ltype) { - char *lookup = NULL; - int lookuplen = 0; - switch(*++p) - { - case '(': - case '[': - if(!compileword(code, p, VAL_STR, lookup, lookuplen)) goto invalid; - break; - case '$': - compilelookup(code, p, VAL_STR); - break; - case '\"': - lookup = cutstring(p, lookuplen); - goto lookupid; - default: - { - lookup = cutword(p, lookuplen); - if(!lookup) goto invalid; - lookupid: - ident *id = newident(lookup, IDF_UNKNOWN); - if(id) switch(id->type) - { - case ID_VAR: code.add(CODE_IVAR|((ltype >= VAL_ANY ? VAL_INT : ltype)<index<<8)); goto done; - case ID_FVAR: code.add(CODE_FVAR|((ltype >= VAL_ANY ? VAL_FLOAT : ltype)<index<<8)); goto done; - case ID_SVAR: code.add(CODE_SVAR|((ltype >= VAL_ANY ? VAL_STR : ltype)<index<<8)); goto done; - case ID_ALIAS: code.add((id->index < MAXARGS ? CODE_LOOKUPARG : CODE_LOOKUP)|((ltype >= VAL_ANY ? VAL_STR : ltype)<index<<8)); goto done; - case ID_COMMAND: - { - int comtype = CODE_COM, numargs = 0; - code.add(CODE_ENTER); - for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt) - { - case 's': compilestr(code, NULL, 0, true); numargs++; break; - case 'i': compileint(code); numargs++; break; - case 'b': compileint(code, INT_MIN); numargs++; break; - case 'f': compilefloat(code); numargs++; break; - case 't': compilenull(code); numargs++; break; - case 'e': compileblock(code); numargs++; break; - case 'r': compileident(code); numargs++; break; - case '$': compileident(code, id); numargs++; break; - case 'N': compileint(code, -1); numargs++; break; + char *lookup = NULL; + int lookuplen = 0; + switch(*++p) + { + case '(': + case '[': + if(!compileword(code, p, VAL_STR, lookup, lookuplen)) goto invalid; + break; + case '$': + compilelookup(code, p, VAL_STR); + break; + case '\"': + lookup = cutstring(p, lookuplen); + goto lookupid; + default: + { + lookup = cutword(p, lookuplen); + if(!lookup) goto invalid; + lookupid: + ident *id = newident(lookup, IDF_UNKNOWN); + if(id) switch(id->type) + { + case ID_VAR: code.add(CODE_IVAR|((ltype >= VAL_ANY ? VAL_INT : ltype)<index<<8)); goto done; + case ID_FVAR: code.add(CODE_FVAR|((ltype >= VAL_ANY ? VAL_FLOAT : ltype)<index<<8)); goto done; + case ID_SVAR: code.add(CODE_SVAR|((ltype >= VAL_ANY ? VAL_STR : ltype)<index<<8)); goto done; + case ID_ALIAS: code.add((id->index < MAXARGS ? CODE_LOOKUPARG : CODE_LOOKUP)|((ltype >= VAL_ANY ? VAL_STR : ltype)<index<<8)); goto done; + case ID_COMMAND: + { + int comtype = CODE_COM, numargs = 0; + code.add(CODE_ENTER); + for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt) + { + case 's': compilestr(code, NULL, 0, true); numargs++; break; + case 'i': compileint(code); numargs++; break; + case 'b': compileint(code, INT_MIN); numargs++; break; + case 'f': compilefloat(code); numargs++; break; + case 't': compilenull(code); numargs++; break; + case 'e': compileblock(code); numargs++; break; + case 'r': compileident(code); numargs++; break; + case '$': compileident(code, id); numargs++; break; + case 'N': compileint(code, -1); numargs++; break; #ifndef STANDALONE - case 'D': comtype = CODE_COMD; numargs++; break; + case 'D': comtype = CODE_COMD; numargs++; break; #endif - case 'C': comtype = CODE_COMC; numargs = 1; goto endfmt; - case 'V': comtype = CODE_COMV; numargs = 2; goto endfmt; - case '1': case '2': case '3': case '4': break; - } - endfmt: - code.add(comtype|(ltype < VAL_ANY ? ltype<index<<8)); - code.add(CODE_EXIT|(ltype < VAL_ANY ? ltype<index<<8)); + code.add(CODE_EXIT|(ltype < VAL_ANY ? ltype< &code, const char *str, const char *end, bool macro) { - int start = code.length(); - code.add(macro ? CODE_MACRO : CODE_VAL|RET_STR); - char *buf = (char *)code.reserve((end-str)/sizeof(uint)+1).buf; - int len = 0; - while(str < end) - { - int n = strcspn(str, "\r/\"@]\0"); - memcpy(&buf[len], str, n); - len += n; - str += n; - switch(*str) - { - case '\r': str++; break; - case '\"': - { - const char *start = str; - str = parsestring(str+1); - if(*str=='\"') str++; - memcpy(&buf[len], start, str-start); - len += str-start; - break; - } - case '/': - if(str[1] == '/') - { - size_t comment = strcspn(str, "\n\0"); - if (iscubepunct(str[2])) - { - memcpy(&buf[len], str, comment); - len += comment; - } - str += comment; - } - else buf[len++] = *str++; - break; - case '@': - [[fallthrough]]; - case ']': - if(str < end) { buf[len++] = *str++; break; } - [[fallthrough]]; - case '\0': goto done; - } - } + int start = code.length(); + code.add(macro ? CODE_MACRO : CODE_VAL|RET_STR); + char *buf = (char *)code.reserve((end-str)/sizeof(uint)+1).buf; + int len = 0; + while(str < end) + { + int n = strcspn(str, "\r/\"@]\0"); + memcpy(&buf[len], str, n); + len += n; + str += n; + switch(*str) + { + case '\r': str++; break; + case '\"': + { + const char *start = str; + str = parsestring(str+1); + if(*str=='\"') str++; + memcpy(&buf[len], start, str-start); + len += str-start; + break; + } + case '/': + if(str[1] == '/') + { + size_t comment = strcspn(str, "\n\0"); + if (iscubepunct(str[2])) + { + memcpy(&buf[len], str, comment); + len += comment; + } + str += comment; + } + else buf[len++] = *str++; + break; + case '@': + [[fallthrough]]; + case ']': + if(str < end) { buf[len++] = *str++; break; } + [[fallthrough]]; + case '\0': goto done; + } + } done: - memset(&buf[len], '\0', sizeof(uint)-len%sizeof(uint)); - code.advance(len/sizeof(uint)+1); - code[start] |= len<<8; - return true; + memset(&buf[len], '\0', sizeof(uint)-len%sizeof(uint)); + code.advance(len/sizeof(uint)+1); + code[start] |= len<<8; + return true; } static bool compileblocksub(vector &code, const char *&p) { - char *lookup = NULL; - int lookuplen = 0; - switch(*p) - { - case '(': - if(!compilearg(code, p, VAL_STR)) return false; - break; - case '[': - if(!compilearg(code, p, VAL_STR)) return false; - code.add(CODE_LOOKUPU|RET_STR); - break; - case '\"': - lookup = cutstring(p, lookuplen); - goto lookupid; - default: - { - { - const char *start = p; - while(iscubealnum(*p) || *p=='_') p++; - lookuplen = p-start; - if(!lookuplen) return false; - lookup = newstring(start, lookuplen); - } - lookupid: - ident *id = newident(lookup, IDF_UNKNOWN); - if(id) switch(id->type) - { - case ID_VAR: code.add(CODE_IVAR|RET_STR|(id->index<<8)); goto done; - case ID_FVAR: code.add(CODE_FVAR|RET_STR|(id->index<<8)); goto done; - case ID_SVAR: code.add(CODE_SVAR|RET_STR|(id->index<<8)); goto done; - case ID_ALIAS: code.add((id->index < MAXARGS ? CODE_LOOKUPARG : CODE_LOOKUP)|RET_STR|(id->index<<8)); goto done; - } - compilestr(code, lookup, lookuplen, true); - code.add(CODE_LOOKUPU|RET_STR); - done: - delete[] lookup; - break; - } - } - return true; + char *lookup = NULL; + int lookuplen = 0; + switch(*p) + { + case '(': + if(!compilearg(code, p, VAL_STR)) return false; + break; + case '[': + if(!compilearg(code, p, VAL_STR)) return false; + code.add(CODE_LOOKUPU|RET_STR); + break; + case '\"': + lookup = cutstring(p, lookuplen); + goto lookupid; + default: + { + { + const char *start = p; + while(iscubealnum(*p) || *p=='_') p++; + lookuplen = p-start; + if(!lookuplen) return false; + lookup = newstring(start, lookuplen); + } + lookupid: + ident *id = newident(lookup, IDF_UNKNOWN); + if(id) switch(id->type) + { + case ID_VAR: code.add(CODE_IVAR|RET_STR|(id->index<<8)); goto done; + case ID_FVAR: code.add(CODE_FVAR|RET_STR|(id->index<<8)); goto done; + case ID_SVAR: code.add(CODE_SVAR|RET_STR|(id->index<<8)); goto done; + case ID_ALIAS: code.add((id->index < MAXARGS ? CODE_LOOKUPARG : CODE_LOOKUP)|RET_STR|(id->index<<8)); goto done; + } + compilestr(code, lookup, lookuplen, true); + code.add(CODE_LOOKUPU|RET_STR); + done: + delete[] lookup; + break; + } + } + return true; } static void compileblock(vector &code, const char *&p, int wordtype) { - const char *line = p, *start = p; - int concs = 0; - for(int brak = 1; brak;) - { - p += strcspn(p, "@\"/[]\0"); - int c = *p++; - switch(c) - { - case '\0': - debugcodeline(line, "missing \"]\""); - p--; - goto done; - case '\"': - p = parsestring(p); - if(*p=='\"') p++; - break; - case '/': - if(*p=='/') p += strcspn(p, "\n\0"); - break; - case '[': brak++; break; - case ']': brak--; break; - case '@': - { - const char *esc = p; - while(*p == '@') p++; - int level = p - (esc - 1); - if(brak > level) continue; - else if(brak < level) debugcodeline(line, "too many @s"); - if(!concs) code.add(CODE_ENTER); - if(concs + 2 > MAXARGS) - { - code.add(CODE_CONCW|RET_STR|(concs<<8)); - concs = 1; - } - if(compileblockstr(code, start, esc-1, true)) concs++; - if(compileblocksub(code, p)) concs++; - if(!concs) code.pop(); - else start = p; - break; - } - } - } + const char *line = p, *start = p; + int concs = 0; + for(int brak = 1; brak;) + { + p += strcspn(p, "@\"/[]\0"); + int c = *p++; + switch(c) + { + case '\0': + debugcodeline(line, "missing \"]\""); + p--; + goto done; + case '\"': + p = parsestring(p); + if(*p=='\"') p++; + break; + case '/': + if(*p=='/') p += strcspn(p, "\n\0"); + break; + case '[': brak++; break; + case ']': brak--; break; + case '@': + { + const char *esc = p; + while(*p == '@') p++; + int level = p - (esc - 1); + if(brak > level) continue; + else if(brak < level) debugcodeline(line, "too many @s"); + if(!concs) code.add(CODE_ENTER); + if(concs + 2 > MAXARGS) + { + code.add(CODE_CONCW|RET_STR|(concs<<8)); + concs = 1; + } + if(compileblockstr(code, start, esc-1, true)) concs++; + if(compileblocksub(code, p)) concs++; + if(!concs) code.pop(); + else start = p; + break; + } + } + } done: - if(p-1 > start) - { - if(!concs) switch(wordtype) - { - case VAL_CODE: - { - p = start; - int inst = code.length(); - code.add(CODE_BLOCK); - code.add(CODE_OFFSET|((inst+2)<<8)); - compilestatements(code, p, VAL_ANY, ']'); - code.add(CODE_EXIT); - code[inst] |= uint(code.length() - (inst + 1))<<8; - return; - } - case VAL_IDENT: - { - char *name = newstring(start, p-1-start); - compileident(code, name); - delete[] name; - return; - } - } - compileblockstr(code, start, p-1, concs > 0); - if(concs > 1) concs++; - } - if(concs) - { - code.add(CODE_CONCM|(wordtype < VAL_ANY ? wordtype< start) + { + if(!concs) switch(wordtype) + { + case VAL_CODE: + { + p = start; + int inst = code.length(); + code.add(CODE_BLOCK); + code.add(CODE_OFFSET|((inst+2)<<8)); + compilestatements(code, p, VAL_ANY, ']'); + code.add(CODE_EXIT); + code[inst] |= uint(code.length() - (inst + 1))<<8; + return; + } + case VAL_IDENT: + { + char *name = newstring(start, p-1-start); + compileident(code, name); + delete[] name; + return; + } + } + compileblockstr(code, start, p-1, concs > 0); + if(concs > 1) concs++; + } + if(concs) + { + code.add(CODE_CONCM|(wordtype < VAL_ANY ? wordtype< &code, const char *&p, int wordtype, char *&word, int &wordlen) { - skipcomments(p); - switch(*p) - { - case '\"': word = cutstring(p, wordlen); break; - case '$': compilelookup(code, p, wordtype); return true; - case '(': - p++; - code.add(CODE_ENTER); - compilestatements(code, p, VAL_ANY, ')'); - code.add(CODE_EXIT|(wordtype < VAL_ANY ? wordtype< &code, const char *&p, int wordtype) { - char *word = NULL; - int wordlen = 0; - bool more = compileword(code, p, wordtype, word, wordlen); - if(!more) return false; - if(word) - { - compileval(code, wordtype, word, wordlen); - delete[] word; - } - return true; + char *word = NULL; + int wordlen = 0; + bool more = compileword(code, p, wordtype, word, wordlen); + if(!more) return false; + if(word) + { + compileval(code, wordtype, word, wordlen); + delete[] word; + } + return true; } static void compilestatements(vector &code, const char *&p, int rettype, int brak) { - const char *line = p; - char *idname = NULL; - int idlen = 0; - ident *id = NULL; - int numargs = 0; - for(;;) - { - skipcomments(p); - idname = NULL; - bool more = compileword(code, p, VAL_ANY, idname, idlen); - if(!more) goto endstatement; - skipcomments(p); - if(p[0] == '=') switch(p[1]) - { - case '/': - if(p[2] != '/') break; - [[fallthrough]]; - case ';': - [[fallthrough]]; - case ' ': - [[fallthrough]]; - case '\t': - [[fallthrough]]; - case '\r': - [[fallthrough]]; - case '\n': - [[fallthrough]]; - case '\0': - p++; - if(idname) - { - id = newident(idname, IDF_UNKNOWN); - if(id) switch(id->type) - { - case ID_ALIAS: - if(!(more = compilearg(code, p, VAL_ANY))) compilestr(code); - code.add((id->index < MAXARGS ? CODE_ALIASARG : CODE_ALIAS)|(id->index<<8)); - goto endcommand; - case ID_VAR: - if(!(more = compilearg(code, p, VAL_INT))) compileint(code); - code.add(CODE_IVAR1|(id->index<<8)); - goto endcommand; - case ID_FVAR: - if(!(more = compilearg(code, p, VAL_FLOAT))) compilefloat(code); - code.add(CODE_FVAR1|(id->index<<8)); - goto endcommand; - case ID_SVAR: - if(!(more = compilearg(code, p, VAL_STR))) compilestr(code); - code.add(CODE_SVAR1|(id->index<<8)); - goto endcommand; - case ID_COMMAND: - if(id->flags&IDF_EMUVAR) goto compilecommand; - break; - } - compilestr(code, idname, idlen, true); - delete[] idname; - } - if(!(more = compilearg(code, p, VAL_ANY))) compilestr(code); - code.add(CODE_ALIASU); - goto endstatement; - } - compilecommand: - numargs = 0; - if(!idname) - { - noid: - while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; - code.add(CODE_CALLU); - } - else - { - id = idents.access(idname); - if(!id) - { - if(!checknumber(idname)) { compilestr(code, idname, idlen); delete[] idname; goto noid; } - char *end = idname; - int val = int(strtoul(idname, &end, 0)); - if(*end) compilestr(code, idname, idlen); - else compileint(code, val); - code.add(CODE_RESULT); - } - else switch(id->type) - { - case ID_ALIAS: - while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; - code.add((id->index < MAXARGS ? CODE_CALLARG : CODE_CALL)|(id->index<<8)); - break; - case ID_COMMAND: - { - int comtype = CODE_COM, fakeargs = 0; - bool rep = false; - for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt) - { - case 's': - if(more) more = compilearg(code, p, VAL_STR); - if(!more) - { - if(rep) break; - compilestr(code, NULL, 0, true); - fakeargs++; - } - else if(!fmt[1]) - { - int numconc = 0; - while(numargs + numconc < MAXARGS && (more = compilearg(code, p, VAL_STR))) numconc++; - if(numconc > 0) code.add(CODE_CONC|RET_STR|((numconc+1)<<8)); - } - numargs++; - break; - case 'i': if(more) more = compilearg(code, p, VAL_INT); if(!more) { if(rep) break; compileint(code); fakeargs++; } numargs++; break; - case 'b': if(more) more = compilearg(code, p, VAL_INT); if(!more) { if(rep) break; compileint(code, INT_MIN); fakeargs++; } numargs++; break; - case 'f': if(more) more = compilearg(code, p, VAL_FLOAT); if(!more) { if(rep) break; compilefloat(code); fakeargs++; } numargs++; break; - case 't': if(more) more = compilearg(code, p, VAL_ANY); if(!more) { if(rep) break; compilenull(code); fakeargs++; } numargs++; break; - case 'e': if(more) more = compilearg(code, p, VAL_CODE); if(!more) { if(rep) break; compileblock(code); fakeargs++; } numargs++; break; - case 'r': if(more) more = compilearg(code, p, VAL_IDENT); if(!more) { if(rep) break; compileident(code); fakeargs++; } numargs++; break; - case '$': compileident(code, id); numargs++; break; - case 'N': compileint(code, numargs-fakeargs); numargs++; break; + const char *line = p; + char *idname = NULL; + int idlen = 0; + ident *id = NULL; + int numargs = 0; + for(;;) + { + skipcomments(p); + idname = NULL; + bool more = compileword(code, p, VAL_ANY, idname, idlen); + if(!more) goto endstatement; + skipcomments(p); + if(p[0] == '=') switch(p[1]) + { + case '/': + if(p[2] != '/') break; + [[fallthrough]]; + case ';': + [[fallthrough]]; + case ' ': + [[fallthrough]]; + case '\t': + [[fallthrough]]; + case '\r': + [[fallthrough]]; + case '\n': + [[fallthrough]]; + case '\0': + p++; + if(idname) + { + id = newident(idname, IDF_UNKNOWN); + if(id) switch(id->type) + { + case ID_ALIAS: + if(!(more = compilearg(code, p, VAL_ANY))) compilestr(code); + code.add((id->index < MAXARGS ? CODE_ALIASARG : CODE_ALIAS)|(id->index<<8)); + goto endcommand; + case ID_VAR: + if(!(more = compilearg(code, p, VAL_INT))) compileint(code); + code.add(CODE_IVAR1|(id->index<<8)); + goto endcommand; + case ID_FVAR: + if(!(more = compilearg(code, p, VAL_FLOAT))) compilefloat(code); + code.add(CODE_FVAR1|(id->index<<8)); + goto endcommand; + case ID_SVAR: + if(!(more = compilearg(code, p, VAL_STR))) compilestr(code); + code.add(CODE_SVAR1|(id->index<<8)); + goto endcommand; + case ID_COMMAND: + if(id->flags&IDF_EMUVAR) goto compilecommand; + break; + } + compilestr(code, idname, idlen, true); + delete[] idname; + } + if(!(more = compilearg(code, p, VAL_ANY))) compilestr(code); + code.add(CODE_ALIASU); + goto endstatement; + } + compilecommand: + numargs = 0; + if(!idname) + { + noid: + while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; + code.add(CODE_CALLU); + } + else + { + id = idents.access(idname); + if(!id) + { + if(!checknumber(idname)) { compilestr(code, idname, idlen); delete[] idname; goto noid; } + char *end = idname; + int val = int(strtoul(idname, &end, 0)); + if(*end) compilestr(code, idname, idlen); + else compileint(code, val); + code.add(CODE_RESULT); + } + else switch(id->type) + { + case ID_ALIAS: + while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; + code.add((id->index < MAXARGS ? CODE_CALLARG : CODE_CALL)|(id->index<<8)); + break; + case ID_COMMAND: + { + int comtype = CODE_COM, fakeargs = 0; + bool rep = false; + for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt) + { + case 's': + if(more) more = compilearg(code, p, VAL_STR); + if(!more) + { + if(rep) break; + compilestr(code, NULL, 0, true); + fakeargs++; + } + else if(!fmt[1]) + { + int numconc = 0; + while(numargs + numconc < MAXARGS && (more = compilearg(code, p, VAL_STR))) numconc++; + if(numconc > 0) code.add(CODE_CONC|RET_STR|((numconc+1)<<8)); + } + numargs++; + break; + case 'i': if(more) more = compilearg(code, p, VAL_INT); if(!more) { if(rep) break; compileint(code); fakeargs++; } numargs++; break; + case 'b': if(more) more = compilearg(code, p, VAL_INT); if(!more) { if(rep) break; compileint(code, INT_MIN); fakeargs++; } numargs++; break; + case 'f': if(more) more = compilearg(code, p, VAL_FLOAT); if(!more) { if(rep) break; compilefloat(code); fakeargs++; } numargs++; break; + case 't': if(more) more = compilearg(code, p, VAL_ANY); if(!more) { if(rep) break; compilenull(code); fakeargs++; } numargs++; break; + case 'e': if(more) more = compilearg(code, p, VAL_CODE); if(!more) { if(rep) break; compileblock(code); fakeargs++; } numargs++; break; + case 'r': if(more) more = compilearg(code, p, VAL_IDENT); if(!more) { if(rep) break; compileident(code); fakeargs++; } numargs++; break; + case '$': compileident(code, id); numargs++; break; + case 'N': compileint(code, numargs-fakeargs); numargs++; break; #ifndef STANDALONE - case 'D': comtype = CODE_COMD; numargs++; break; + case 'D': comtype = CODE_COMD; numargs++; break; #endif - case 'C': comtype = CODE_COMC; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; numargs = 1; goto endfmt; - case 'V': comtype = CODE_COMV; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; numargs = 2; goto endfmt; - case '1': case '2': case '3': case '4': - if(more && numargs < MAXARGS) - { - int numrep = *fmt-'0'+1; - fmt -= numrep; - rep = true; - } - else for(; numargs > MAXARGS; numargs--) code.add(CODE_POP); - break; - } - endfmt: - code.add(comtype|(rettype < VAL_ANY ? rettype<index<<8)); - break; - } - case ID_LOCAL: - if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_IDENT))) numargs++; - if(more) while((more = compilearg(code, p, VAL_ANY))) code.add(CODE_POP); - code.add(CODE_LOCAL); - break; - case ID_VAR: - if(!(more = compilearg(code, p, VAL_INT))) code.add(CODE_PRINT|(id->index<<8)); - else if(!(id->flags&IDF_HEX) || !(more = compilearg(code, p, VAL_INT))) code.add(CODE_IVAR1|(id->index<<8)); - else if(!(more = compilearg(code, p, VAL_INT))) code.add(CODE_IVAR2|(id->index<<8)); - else code.add(CODE_IVAR3|(id->index<<8)); - break; - case ID_FVAR: - if(!(more = compilearg(code, p, VAL_FLOAT))) code.add(CODE_PRINT|(id->index<<8)); - else code.add(CODE_FVAR1|(id->index<<8)); - break; - case ID_SVAR: - if(!(more = compilearg(code, p, VAL_STR))) code.add(CODE_PRINT|(id->index<<8)); - else - { - int numconc = 0; - while(numconc+1 < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numconc++; - if(numconc > 0) code.add(CODE_CONC|RET_STR|((numconc+1)<<8)); - code.add(CODE_SVAR1|(id->index<<8)); - } - break; - } - endcommand: - delete[] idname; - } - endstatement: - if(more) while(compilearg(code, p, VAL_ANY)) code.add(CODE_POP); - p += strcspn(p, ")];/\n\0"); - int c = *p++; - switch(c) - { - case '\0': - if(c != brak) debugcodeline(line, "missing \"%c\"", brak); - p--; - return; - - case ')': - case ']': - if(c == brak) return; - debugcodeline(line, "unexpected \"%c\"", c); - break; - - case '/': - if(*p == '/') p += strcspn(p, "\n\0"); - goto endstatement; - } - } + case 'C': comtype = CODE_COMC; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; numargs = 1; goto endfmt; + case 'V': comtype = CODE_COMV; if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numargs++; numargs = 2; goto endfmt; + case '1': case '2': case '3': case '4': + if(more && numargs < MAXARGS) + { + int numrep = *fmt-'0'+1; + fmt -= numrep; + rep = true; + } + else for(; numargs > MAXARGS; numargs--) code.add(CODE_POP); + break; + } + endfmt: + code.add(comtype|(rettype < VAL_ANY ? rettype<index<<8)); + break; + } + case ID_LOCAL: + if(more) while(numargs < MAXARGS && (more = compilearg(code, p, VAL_IDENT))) numargs++; + if(more) while((more = compilearg(code, p, VAL_ANY))) code.add(CODE_POP); + code.add(CODE_LOCAL); + break; + case ID_VAR: + if(!(more = compilearg(code, p, VAL_INT))) code.add(CODE_PRINT|(id->index<<8)); + else if(!(id->flags&IDF_HEX) || !(more = compilearg(code, p, VAL_INT))) code.add(CODE_IVAR1|(id->index<<8)); + else if(!(more = compilearg(code, p, VAL_INT))) code.add(CODE_IVAR2|(id->index<<8)); + else code.add(CODE_IVAR3|(id->index<<8)); + break; + case ID_FVAR: + if(!(more = compilearg(code, p, VAL_FLOAT))) code.add(CODE_PRINT|(id->index<<8)); + else code.add(CODE_FVAR1|(id->index<<8)); + break; + case ID_SVAR: + if(!(more = compilearg(code, p, VAL_STR))) code.add(CODE_PRINT|(id->index<<8)); + else + { + int numconc = 0; + while(numconc+1 < MAXARGS && (more = compilearg(code, p, VAL_ANY))) numconc++; + if(numconc > 0) code.add(CODE_CONC|RET_STR|((numconc+1)<<8)); + code.add(CODE_SVAR1|(id->index<<8)); + } + break; + } + endcommand: + delete[] idname; + } + endstatement: + if(more) while(compilearg(code, p, VAL_ANY)) code.add(CODE_POP); + p += strcspn(p, ")];/\n\0"); + int c = *p++; + switch(c) + { + case '\0': + if(c != brak) debugcodeline(line, "missing \"%c\"", brak); + p--; + return; + + case ')': + case ']': + if(c == brak) return; + debugcodeline(line, "unexpected \"%c\"", c); + break; + + case '/': + if(*p == '/') p += strcspn(p, "\n\0"); + goto endstatement; + } + } } static void compilemain(vector &code, const char *p, int rettype = VAL_ANY) { - code.add(CODE_START); - compilestatements(code, p, VAL_ANY); - code.add(CODE_EXIT|(rettype < VAL_ANY ? rettype< buf; - buf.reserve(64); - compilemain(buf, p); - uint *code = new uint[buf.length()]; - memcpy(code, buf.getbuf(), buf.length()*sizeof(uint)); - code[0] += 0x100; - return code; + vector buf; + buf.reserve(64); + compilemain(buf, p); + uint *code = new uint[buf.length()]; + memcpy(code, buf.getbuf(), buf.length()*sizeof(uint)); + code[0] += 0x100; + return code; } void keepcode(uint *code) { - if(!code) return; - switch(*code&CODE_OP_MASK) - { - case CODE_START: - *code += 0x100; - return; - } - switch(code[-1]&CODE_OP_MASK) - { - case CODE_START: - code[-1] += 0x100; - break; - case CODE_OFFSET: - code -= int(code[-1]>>8); - *code += 0x100; - break; - } + if(!code) return; + switch(*code&CODE_OP_MASK) + { + case CODE_START: + *code += 0x100; + return; + } + switch(code[-1]&CODE_OP_MASK) + { + case CODE_START: + code[-1] += 0x100; + break; + case CODE_OFFSET: + code -= int(code[-1]>>8); + *code += 0x100; + break; + } } void freecode(uint *code) { - if(!code) return; - switch(*code&CODE_OP_MASK) - { - case CODE_START: - *code -= 0x100; - if(int(*code) < 0x100) delete[] code; - return; - } - switch(code[-1]&CODE_OP_MASK) - { - case CODE_START: - code[-1] -= 0x100; - if(int(code[-1]) < 0x100) delete[] &code[-1]; - break; - case CODE_OFFSET: - code -= int(code[-1]>>8); - *code -= 0x100; - if(int(*code) < 0x100) delete[] code; - break; - } + if(!code) return; + switch(*code&CODE_OP_MASK) + { + case CODE_START: + *code -= 0x100; + if(int(*code) < 0x100) delete[] code; + return; + } + switch(code[-1]&CODE_OP_MASK) + { + case CODE_START: + code[-1] -= 0x100; + if(int(code[-1]) < 0x100) delete[] &code[-1]; + break; + case CODE_OFFSET: + code -= int(code[-1]>>8); + *code -= 0x100; + if(int(*code) < 0x100) delete[] code; + break; + } } void printvar(ident *id, int i) { - if(i < 0) conoutf(CON_INFO, id->index, "%s = %d", id->name, i); - else if(id->flags&IDF_HEX && id->maxval==0xFFFFFF) - conoutf(CON_INFO, id->index, "%s = 0x%.6X (%d, %d, %d)", id->name, i, (i>>16)&0xFF, (i>>8)&0xFF, i&0xFF); - else - conoutf(CON_INFO, id->index, id->flags&IDF_HEX ? "%s = 0x%X" : "%s = %d", id->name, i); + if(i < 0) conoutf(CON_INFO, id->index, "%s = %d", id->name, i); + else if(id->flags&IDF_HEX && id->maxval==0xFFFFFF) + conoutf(CON_INFO, id->index, "%s = 0x%.6X (%d, %d, %d)", id->name, i, (i>>16)&0xFF, (i>>8)&0xFF, i&0xFF); + else + conoutf(CON_INFO, id->index, id->flags&IDF_HEX ? "%s = 0x%X" : "%s = %d", id->name, i); } void printfvar(ident *id, float f) { - conoutf(CON_INFO, id->index, "%s = %s", id->name, floatstr(f)); + conoutf(CON_INFO, id->index, "%s = %s", id->name, floatstr(f)); } void printsvar(ident *id, const char *s) { - conoutf(CON_INFO, id->index, strchr(s, '"') ? "%s = [%s]" : "%s = \"%s\"", id->name, s); + conoutf(CON_INFO, id->index, strchr(s, '"') ? "%s = [%s]" : "%s = \"%s\"", id->name, s); } template static void printvar(ident *id, int type, V &val) { - switch(type) - { - case VAL_INT: printvar(id, val.getint()); break; - case VAL_FLOAT: printfvar(id, val.getfloat()); break; - default: printsvar(id, val.getstr()); break; - } + switch(type) + { + case VAL_INT: printvar(id, val.getint()); break; + case VAL_FLOAT: printfvar(id, val.getfloat()); break; + default: printsvar(id, val.getstr()); break; + } } void printvar(ident *id) { - switch(id->type) - { - case ID_VAR: printvar(id, *id->storage.i); break; - case ID_FVAR: printfvar(id, *id->storage.f); break; - case ID_SVAR: printsvar(id, *id->storage.s); break; - case ID_ALIAS: printvar(id, id->valtype, *id); break; - case ID_COMMAND: - if(id->flags&IDF_EMUVAR) - { - tagval result; - executeret(id, NULL, 0, true, result); - printvar(id, result.type, result); - freearg(result); - } - break; - } + switch(id->type) + { + case ID_VAR: printvar(id, *id->storage.i); break; + case ID_FVAR: printfvar(id, *id->storage.f); break; + case ID_SVAR: printsvar(id, *id->storage.s); break; + case ID_ALIAS: printvar(id, id->valtype, *id); break; + case ID_COMMAND: + if(id->flags&IDF_EMUVAR) + { + tagval result; + executeret(id, NULL, 0, true, result); + printvar(id, result.type, result); + freearg(result); + } + break; + } } ICOMMAND(printvar, "r", (ident *id), printvar(id)); @@ -1671,102 +1671,102 @@ typedef void (__cdecl *comfunv)(tagval *, int); static const uint *skipcode(const uint *code, tagval &result) { - int depth = 0; - for(;;) - { - uint op = *code++; - switch(op&0xFF) - { - case CODE_MACRO: - case CODE_VAL|RET_STR: - { - uint len = op>>8; - code += len/sizeof(uint) + 1; - continue; - } - case CODE_BLOCK: - { - uint len = op>>8; - code += len; - continue; - } - case CODE_ENTER: - ++depth; - continue; - case CODE_EXIT|RET_NULL: case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT: - if(depth <= 0) - { - forcearg(result, op&CODE_RET_MASK); - return code; - } - --depth; - continue; - } - } + int depth = 0; + for(;;) + { + uint op = *code++; + switch(op&0xFF) + { + case CODE_MACRO: + case CODE_VAL|RET_STR: + { + uint len = op>>8; + code += len/sizeof(uint) + 1; + continue; + } + case CODE_BLOCK: + { + uint len = op>>8; + code += len; + continue; + } + case CODE_ENTER: + ++depth; + continue; + case CODE_EXIT|RET_NULL: case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT: + if(depth <= 0) + { + forcearg(result, op&CODE_RET_MASK); + return code; + } + --depth; + continue; + } + } } static inline void callcommand(ident *id, tagval *args, int numargs, bool lookup = false) { - int i = -1, fakeargs = 0; - bool rep = false; - for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt) - { - case 'i': if(++i >= numargs) { if(rep) break; args[i].setint(0); fakeargs++; } else forceint(args[i]); break; - case 'b': if(++i >= numargs) { if(rep) break; args[i].setint(INT_MIN); fakeargs++; } else forceint(args[i]); break; - case 'f': if(++i >= numargs) { if(rep) break; args[i].setfloat(0.0f); fakeargs++; } else forcefloat(args[i]); break; - case 's': if(++i >= numargs) { if(rep) break; args[i].setstr(newstring("")); fakeargs++; } else forcestr(args[i]); break; - case 't': if(++i >= numargs) { if(rep) break; args[i].setnull(); fakeargs++; } break; - case 'e': - if(++i >= numargs) - { - if(rep) break; - static uint buf[2] = { CODE_START + 0x100, CODE_EXIT }; - args[i].setcode(buf); - fakeargs++; - } - else - { - vector buf; - buf.reserve(64); - compilemain(buf, numargs <= i ? "" : args[i].getstr()); - freearg(args[i]); - args[i].setcode(buf.getbuf()+1); - buf.disown(); - } - break; - case 'r': if(++i >= numargs) { if(rep) break; args[i].setident(dummyident); fakeargs++; } else forceident(args[i]); break; - case '$': if(++i < numargs) freearg(args[i]); args[i].setident(id); break; - case 'N': if(++i < numargs) freearg(args[i]); args[i].setint(lookup ? -1 : i-fakeargs); break; + int i = -1, fakeargs = 0; + bool rep = false; + for(const char *fmt = id->args; *fmt; fmt++) switch(*fmt) + { + case 'i': if(++i >= numargs) { if(rep) break; args[i].setint(0); fakeargs++; } else forceint(args[i]); break; + case 'b': if(++i >= numargs) { if(rep) break; args[i].setint(INT_MIN); fakeargs++; } else forceint(args[i]); break; + case 'f': if(++i >= numargs) { if(rep) break; args[i].setfloat(0.0f); fakeargs++; } else forcefloat(args[i]); break; + case 's': if(++i >= numargs) { if(rep) break; args[i].setstr(newstring("")); fakeargs++; } else forcestr(args[i]); break; + case 't': if(++i >= numargs) { if(rep) break; args[i].setnull(); fakeargs++; } break; + case 'e': + if(++i >= numargs) + { + if(rep) break; + static uint buf[2] = { CODE_START + 0x100, CODE_EXIT }; + args[i].setcode(buf); + fakeargs++; + } + else + { + vector buf; + buf.reserve(64); + compilemain(buf, numargs <= i ? "" : args[i].getstr()); + freearg(args[i]); + args[i].setcode(buf.getbuf()+1); + buf.disown(); + } + break; + case 'r': if(++i >= numargs) { if(rep) break; args[i].setident(dummyident); fakeargs++; } else forceident(args[i]); break; + case '$': if(++i < numargs) freearg(args[i]); args[i].setident(id); break; + case 'N': if(++i < numargs) freearg(args[i]); args[i].setint(lookup ? -1 : i-fakeargs); break; #ifndef STANDALONE - case 'D': if(++i < numargs) freearg(args[i]); args[i].setint(addreleaseaction(conc(args, i, true, id->name)) ? 1 : 0); fakeargs++; break; + case 'D': if(++i < numargs) freearg(args[i]); args[i].setint(addreleaseaction(conc(args, i, true, id->name)) ? 1 : 0); fakeargs++; break; #endif - case 'C': { i = max(i+1, numargs); vector buf; ((comfun1)id->fun)(conc(buf, args, i, true)); goto cleanup; } - case 'V': i = max(i+1, numargs); ((comfunv)id->fun)(args, i); goto cleanup; - case '1': case '2': case '3': case '4': if(i+1 < numargs) { fmt -= *fmt-'0'+1; rep = true; } break; - } - #define ARG(n) (id->argmask&(1<fun)(); break; \ - case 1: ((comfun1)id->fun)(ARG(0)); break; \ - case 2: ((comfun2)id->fun)(ARG(0), ARG(1)); break; \ - case 3: ((comfun3)id->fun)(ARG(0), ARG(1), ARG(2)); break; \ - case 4: ((comfun4)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3)); break; \ - case 5: ((comfun5)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4)); break; \ - case 6: ((comfun6)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); break; \ - case 7: ((comfun7)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6)); break; \ - case 8: ((comfun8)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7)); break; \ - case 9: ((comfun9)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8)); break; \ - case 10: ((comfun10)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9)); break; \ - case 11: ((comfun11)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10)); break; \ - case 12: ((comfun12)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10), ARG(11)); break; \ - } - ++i; - CALLCOM(i) + case 'C': { i = max(i+1, numargs); vector buf; ((comfun1)id->fun)(conc(buf, args, i, true)); goto cleanup; } + case 'V': i = max(i+1, numargs); ((comfunv)id->fun)(args, i); goto cleanup; + case '1': case '2': case '3': case '4': if(i+1 < numargs) { fmt -= *fmt-'0'+1; rep = true; } break; + } + #define ARG(n) (id->argmask&(1<fun)(); break; \ + case 1: ((comfun1)id->fun)(ARG(0)); break; \ + case 2: ((comfun2)id->fun)(ARG(0), ARG(1)); break; \ + case 3: ((comfun3)id->fun)(ARG(0), ARG(1), ARG(2)); break; \ + case 4: ((comfun4)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3)); break; \ + case 5: ((comfun5)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4)); break; \ + case 6: ((comfun6)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5)); break; \ + case 7: ((comfun7)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6)); break; \ + case 8: ((comfun8)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7)); break; \ + case 9: ((comfun9)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8)); break; \ + case 10: ((comfun10)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9)); break; \ + case 11: ((comfun11)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10)); break; \ + case 12: ((comfun12)id->fun)(ARG(0), ARG(1), ARG(2), ARG(3), ARG(4), ARG(5), ARG(6), ARG(7), ARG(8), ARG(9), ARG(10), ARG(11)); break; \ + } + ++i; + CALLCOM(i) cleanup: - loopk(i) freearg(args[k]); - for(; i < numargs; i++) freearg(args[i]); + loopk(i) freearg(args[k]); + for(; i < numargs; i++) freearg(args[i]); } #define MAXRUNDEPTH 255 @@ -1774,725 +1774,725 @@ static int rundepth = 0; static const uint *runcode(const uint *code, tagval &result) { - result.setnull(); - if(rundepth >= MAXRUNDEPTH) - { - debugcode("exceeded recursion limit"); - return skipcode(code, result); - } - ++rundepth; - ident *id = NULL; - int numargs = 0; - tagval args[MAXARGS+1], *prevret = commandret; - commandret = &result; - for(;;) - { - uint op = *code++; - switch(op&0xFF) - { - case CODE_START: case CODE_OFFSET: continue; - - case CODE_POP: - freearg(args[--numargs]); - continue; - case CODE_ENTER: - code = runcode(code, args[numargs++]); - continue; - case CODE_EXIT|RET_NULL: case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT: - forcearg(result, op&CODE_RET_MASK); - goto exit; - case CODE_PRINT: - printvar(identmap[op>>8]); - continue; - case CODE_LOCAL: - { - identstack locals[MAXARGS]; - freearg(result); - loopi(numargs) pushalias(*args[i].id, locals[i]); - code = runcode(code, result); - loopi(numargs) popalias(*args[i].id); - goto exit; - } - - case CODE_MACRO: - { - uint len = op>>8; - args[numargs++].setmacro(code); - code += len/sizeof(uint) + 1; - continue; - } - - case CODE_VAL|RET_STR: - { - uint len = op>>8; - args[numargs++].setstr(newstring((const char *)code, len)); - code += len/sizeof(uint) + 1; - continue; - } - case CODE_VALI|RET_STR: - { - char s[4] = { char((op>>8)&0xFF), char((op>>16)&0xFF), char((op>>24)&0xFF), '\0' }; - args[numargs++].setstr(newstring(s)); - continue; - } - case CODE_VAL|RET_NULL: - case CODE_VALI|RET_NULL: args[numargs++].setnull(); continue; - case CODE_VAL|RET_INT: args[numargs++].setint(int(*code++)); continue; - case CODE_VALI|RET_INT: args[numargs++].setint(int(op)>>8); continue; - case CODE_VAL|RET_FLOAT: args[numargs++].setfloat(*(const float *)code++); continue; - case CODE_VALI|RET_FLOAT: args[numargs++].setfloat(float(int(op)>>8)); continue; - - case CODE_FORCE|RET_STR: forcestr(args[numargs-1]); continue; - case CODE_FORCE|RET_INT: forceint(args[numargs-1]); continue; - case CODE_FORCE|RET_FLOAT: forcefloat(args[numargs-1]); continue; - - case CODE_RESULT|RET_NULL: case CODE_RESULT|RET_STR: case CODE_RESULT|RET_INT: case CODE_RESULT|RET_FLOAT: - litval: - freearg(result); - result = args[0]; - forcearg(result, op&CODE_RET_MASK); - args[0].setnull(); - freeargs(args, numargs, 0); - continue; - - case CODE_BLOCK: - { - uint len = op>>8; - args[numargs++].setcode(code+1); - code += len; - continue; - } - case CODE_COMPILE: - { - tagval &arg = args[numargs-1]; - vector buf; - switch(arg.type) - { - case VAL_INT: buf.reserve(8); buf.add(CODE_START); compileint(buf, arg.i); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break; - case VAL_FLOAT: buf.reserve(8); buf.add(CODE_START); compilefloat(buf, arg.f); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break; - case VAL_STR: case VAL_MACRO: buf.reserve(64); compilemain(buf, arg.s); freearg(arg); break; - default: buf.reserve(8); buf.add(CODE_START); compilenull(buf); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break; - } - arg.setcode(buf.getbuf()+1); - buf.disown(); - continue; - } - - case CODE_IDENT: - args[numargs++].setident(identmap[op>>8]); - continue; - case CODE_IDENTARG: - { - ident *id = identmap[op>>8]; - if(!(aliasstack->usedargs&(1<index))) - { - pusharg(*id, nullval, aliasstack->argstack[id->index]); - aliasstack->usedargs |= 1<index; - } - args[numargs++].setident(id); - continue; - } - case CODE_IDENTU: - { - tagval &arg = args[numargs-1]; - ident *id = arg.type == VAL_STR || arg.type == VAL_MACRO ? newident(arg.s, IDF_UNKNOWN) : dummyident; - if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) - { - pusharg(*id, nullval, aliasstack->argstack[id->index]); - aliasstack->usedargs |= 1<index; - } - freearg(arg); - arg.setident(id); - continue; - } - - case CODE_LOOKUPU|RET_STR: - #define LOOKUPU(aval, sval, ival, fval, nval) { \ - tagval &arg = args[numargs-1]; \ - if(arg.type != VAL_STR && arg.type != VAL_MACRO) continue; \ - id = idents.access(arg.s); \ - if(id) switch(id->type) \ - { \ - case ID_ALIAS: \ - if(id->flags&IDF_UNKNOWN) break; \ - freearg(arg); \ - if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) { nval; continue; } \ - aval; \ - continue; \ - case ID_SVAR: freearg(arg); sval; continue; \ - case ID_VAR: freearg(arg); ival; continue; \ - case ID_FVAR: freearg(arg); fval; continue; \ - case ID_COMMAND: \ - { \ - freearg(arg); \ - arg.setnull(); \ - commandret = &arg; \ - tagval buf[MAXARGS]; \ - callcommand(id, buf, 0, true); \ - forcearg(arg, op&CODE_RET_MASK); \ - commandret = &result; \ - continue; \ - } \ - default: freearg(arg); nval; continue; \ - } \ - debugcode("unknown alias lookup: %s", arg.s); \ - freearg(arg); \ - nval; \ - continue; \ - } - LOOKUPU(arg.setstr(newstring(id->getstr())), - arg.setstr(newstring(*id->storage.s)), - arg.setstr(newstring(intstr(*id->storage.i))), - arg.setstr(newstring(floatstr(*id->storage.f))), - arg.setstr(newstring(""))); - case CODE_LOOKUP|RET_STR: - #define LOOKUP(aval) { \ - id = identmap[op>>8]; \ - if(id->flags&IDF_UNKNOWN) debugcode("unknown alias lookup: %s", id->name); \ - aval; \ - continue; \ - } - LOOKUP(args[numargs++].setstr(newstring(id->getstr()))); - case CODE_LOOKUPARG|RET_STR: - #define LOOKUPARG(aval, nval) { \ - id = identmap[op>>8]; \ - if(!(aliasstack->usedargs&(1<index))) { nval; continue; } \ - aval; \ - continue; \ - } - LOOKUPARG(args[numargs++].setstr(newstring(id->getstr())), args[numargs++].setstr(newstring(""))); - case CODE_LOOKUPU|RET_INT: - LOOKUPU(arg.setint(id->getint()), - arg.setint(parseint(*id->storage.s)), - arg.setint(*id->storage.i), - arg.setint(int(*id->storage.f)), - arg.setint(0)); - case CODE_LOOKUP|RET_INT: - LOOKUP(args[numargs++].setint(id->getint())); - case CODE_LOOKUPARG|RET_INT: - LOOKUPARG(args[numargs++].setint(id->getint()), args[numargs++].setint(0)); - case CODE_LOOKUPU|RET_FLOAT: - LOOKUPU(arg.setfloat(id->getfloat()), - arg.setfloat(parsefloat(*id->storage.s)), - arg.setfloat(float(*id->storage.i)), - arg.setfloat(*id->storage.f), - arg.setfloat(0.0f)); - case CODE_LOOKUP|RET_FLOAT: - LOOKUP(args[numargs++].setfloat(id->getfloat())); - case CODE_LOOKUPARG|RET_FLOAT: - LOOKUPARG(args[numargs++].setfloat(id->getfloat()), args[numargs++].setfloat(0.0f)); - case CODE_LOOKUPU|RET_NULL: - LOOKUPU(id->getval(arg), - arg.setstr(newstring(*id->storage.s)), - arg.setint(*id->storage.i), - arg.setfloat(*id->storage.f), - arg.setnull()); - case CODE_LOOKUP|RET_NULL: - LOOKUP(id->getval(args[numargs++])); - case CODE_LOOKUPARG|RET_NULL: - LOOKUPARG(id->getval(args[numargs++]), args[numargs++].setnull()); - - case CODE_SVAR|RET_STR: case CODE_SVAR|RET_NULL: args[numargs++].setstr(newstring(*identmap[op>>8]->storage.s)); continue; - case CODE_SVAR|RET_INT: args[numargs++].setint(parseint(*identmap[op>>8]->storage.s)); continue; - case CODE_SVAR|RET_FLOAT: args[numargs++].setfloat(parsefloat(*identmap[op>>8]->storage.s)); continue; - case CODE_SVAR1: setsvarchecked(identmap[op>>8], args[0].s); freeargs(args, numargs, 0); continue; - - case CODE_IVAR|RET_INT: case CODE_IVAR|RET_NULL: args[numargs++].setint(*identmap[op>>8]->storage.i); continue; - case CODE_IVAR|RET_STR: args[numargs++].setstr(newstring(intstr(*identmap[op>>8]->storage.i))); continue; - case CODE_IVAR|RET_FLOAT: args[numargs++].setfloat(float(*identmap[op>>8]->storage.i)); continue; - case CODE_IVAR1: setvarchecked(identmap[op>>8], args[0].i); numargs = 0; continue; - case CODE_IVAR2: setvarchecked(identmap[op>>8], (args[0].i<<16)|(args[1].i<<8)); numargs = 0; continue; - case CODE_IVAR3: setvarchecked(identmap[op>>8], (args[0].i<<16)|(args[1].i<<8)|args[2].i); numargs = 0; continue; - - case CODE_FVAR|RET_FLOAT: case CODE_FVAR|RET_NULL: args[numargs++].setfloat(*identmap[op>>8]->storage.f); continue; - case CODE_FVAR|RET_STR: args[numargs++].setstr(newstring(floatstr(*identmap[op>>8]->storage.f))); continue; - case CODE_FVAR|RET_INT: args[numargs++].setint(int(*identmap[op>>8]->storage.f)); continue; - case CODE_FVAR1: setfvarchecked(identmap[op>>8], args[0].f); numargs = 0; continue; - - case CODE_COM|RET_NULL: case CODE_COM|RET_STR: case CODE_COM|RET_FLOAT: case CODE_COM|RET_INT: - id = identmap[op>>8]; + result.setnull(); + if(rundepth >= MAXRUNDEPTH) + { + debugcode("exceeded recursion limit"); + return skipcode(code, result); + } + ++rundepth; + ident *id = NULL; + int numargs = 0; + tagval args[MAXARGS+1], *prevret = commandret; + commandret = &result; + for(;;) + { + uint op = *code++; + switch(op&0xFF) + { + case CODE_START: case CODE_OFFSET: continue; + + case CODE_POP: + freearg(args[--numargs]); + continue; + case CODE_ENTER: + code = runcode(code, args[numargs++]); + continue; + case CODE_EXIT|RET_NULL: case CODE_EXIT|RET_STR: case CODE_EXIT|RET_INT: case CODE_EXIT|RET_FLOAT: + forcearg(result, op&CODE_RET_MASK); + goto exit; + case CODE_PRINT: + printvar(identmap[op>>8]); + continue; + case CODE_LOCAL: + { + identstack locals[MAXARGS]; + freearg(result); + loopi(numargs) pushalias(*args[i].id, locals[i]); + code = runcode(code, result); + loopi(numargs) popalias(*args[i].id); + goto exit; + } + + case CODE_MACRO: + { + uint len = op>>8; + args[numargs++].setmacro(code); + code += len/sizeof(uint) + 1; + continue; + } + + case CODE_VAL|RET_STR: + { + uint len = op>>8; + args[numargs++].setstr(newstring((const char *)code, len)); + code += len/sizeof(uint) + 1; + continue; + } + case CODE_VALI|RET_STR: + { + char s[4] = { char((op>>8)&0xFF), char((op>>16)&0xFF), char((op>>24)&0xFF), '\0' }; + args[numargs++].setstr(newstring(s)); + continue; + } + case CODE_VAL|RET_NULL: + case CODE_VALI|RET_NULL: args[numargs++].setnull(); continue; + case CODE_VAL|RET_INT: args[numargs++].setint(int(*code++)); continue; + case CODE_VALI|RET_INT: args[numargs++].setint(int(op)>>8); continue; + case CODE_VAL|RET_FLOAT: args[numargs++].setfloat(*(const float *)code++); continue; + case CODE_VALI|RET_FLOAT: args[numargs++].setfloat(float(int(op)>>8)); continue; + + case CODE_FORCE|RET_STR: forcestr(args[numargs-1]); continue; + case CODE_FORCE|RET_INT: forceint(args[numargs-1]); continue; + case CODE_FORCE|RET_FLOAT: forcefloat(args[numargs-1]); continue; + + case CODE_RESULT|RET_NULL: case CODE_RESULT|RET_STR: case CODE_RESULT|RET_INT: case CODE_RESULT|RET_FLOAT: + litval: + freearg(result); + result = args[0]; + forcearg(result, op&CODE_RET_MASK); + args[0].setnull(); + freeargs(args, numargs, 0); + continue; + + case CODE_BLOCK: + { + uint len = op>>8; + args[numargs++].setcode(code+1); + code += len; + continue; + } + case CODE_COMPILE: + { + tagval &arg = args[numargs-1]; + vector buf; + switch(arg.type) + { + case VAL_INT: buf.reserve(8); buf.add(CODE_START); compileint(buf, arg.i); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break; + case VAL_FLOAT: buf.reserve(8); buf.add(CODE_START); compilefloat(buf, arg.f); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break; + case VAL_STR: case VAL_MACRO: buf.reserve(64); compilemain(buf, arg.s); freearg(arg); break; + default: buf.reserve(8); buf.add(CODE_START); compilenull(buf); buf.add(CODE_RESULT); buf.add(CODE_EXIT); break; + } + arg.setcode(buf.getbuf()+1); + buf.disown(); + continue; + } + + case CODE_IDENT: + args[numargs++].setident(identmap[op>>8]); + continue; + case CODE_IDENTARG: + { + ident *id = identmap[op>>8]; + if(!(aliasstack->usedargs&(1<index))) + { + pusharg(*id, nullval, aliasstack->argstack[id->index]); + aliasstack->usedargs |= 1<index; + } + args[numargs++].setident(id); + continue; + } + case CODE_IDENTU: + { + tagval &arg = args[numargs-1]; + ident *id = arg.type == VAL_STR || arg.type == VAL_MACRO ? newident(arg.s, IDF_UNKNOWN) : dummyident; + if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) + { + pusharg(*id, nullval, aliasstack->argstack[id->index]); + aliasstack->usedargs |= 1<index; + } + freearg(arg); + arg.setident(id); + continue; + } + + case CODE_LOOKUPU|RET_STR: + #define LOOKUPU(aval, sval, ival, fval, nval) { \ + tagval &arg = args[numargs-1]; \ + if(arg.type != VAL_STR && arg.type != VAL_MACRO) continue; \ + id = idents.access(arg.s); \ + if(id) switch(id->type) \ + { \ + case ID_ALIAS: \ + if(id->flags&IDF_UNKNOWN) break; \ + freearg(arg); \ + if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) { nval; continue; } \ + aval; \ + continue; \ + case ID_SVAR: freearg(arg); sval; continue; \ + case ID_VAR: freearg(arg); ival; continue; \ + case ID_FVAR: freearg(arg); fval; continue; \ + case ID_COMMAND: \ + { \ + freearg(arg); \ + arg.setnull(); \ + commandret = &arg; \ + tagval buf[MAXARGS]; \ + callcommand(id, buf, 0, true); \ + forcearg(arg, op&CODE_RET_MASK); \ + commandret = &result; \ + continue; \ + } \ + default: freearg(arg); nval; continue; \ + } \ + debugcode("unknown alias lookup: %s", arg.s); \ + freearg(arg); \ + nval; \ + continue; \ + } + LOOKUPU(arg.setstr(newstring(id->getstr())), + arg.setstr(newstring(*id->storage.s)), + arg.setstr(newstring(intstr(*id->storage.i))), + arg.setstr(newstring(floatstr(*id->storage.f))), + arg.setstr(newstring(""))); + case CODE_LOOKUP|RET_STR: + #define LOOKUP(aval) { \ + id = identmap[op>>8]; \ + if(id->flags&IDF_UNKNOWN) debugcode("unknown alias lookup: %s", id->name); \ + aval; \ + continue; \ + } + LOOKUP(args[numargs++].setstr(newstring(id->getstr()))); + case CODE_LOOKUPARG|RET_STR: + #define LOOKUPARG(aval, nval) { \ + id = identmap[op>>8]; \ + if(!(aliasstack->usedargs&(1<index))) { nval; continue; } \ + aval; \ + continue; \ + } + LOOKUPARG(args[numargs++].setstr(newstring(id->getstr())), args[numargs++].setstr(newstring(""))); + case CODE_LOOKUPU|RET_INT: + LOOKUPU(arg.setint(id->getint()), + arg.setint(parseint(*id->storage.s)), + arg.setint(*id->storage.i), + arg.setint(int(*id->storage.f)), + arg.setint(0)); + case CODE_LOOKUP|RET_INT: + LOOKUP(args[numargs++].setint(id->getint())); + case CODE_LOOKUPARG|RET_INT: + LOOKUPARG(args[numargs++].setint(id->getint()), args[numargs++].setint(0)); + case CODE_LOOKUPU|RET_FLOAT: + LOOKUPU(arg.setfloat(id->getfloat()), + arg.setfloat(parsefloat(*id->storage.s)), + arg.setfloat(float(*id->storage.i)), + arg.setfloat(*id->storage.f), + arg.setfloat(0.0f)); + case CODE_LOOKUP|RET_FLOAT: + LOOKUP(args[numargs++].setfloat(id->getfloat())); + case CODE_LOOKUPARG|RET_FLOAT: + LOOKUPARG(args[numargs++].setfloat(id->getfloat()), args[numargs++].setfloat(0.0f)); + case CODE_LOOKUPU|RET_NULL: + LOOKUPU(id->getval(arg), + arg.setstr(newstring(*id->storage.s)), + arg.setint(*id->storage.i), + arg.setfloat(*id->storage.f), + arg.setnull()); + case CODE_LOOKUP|RET_NULL: + LOOKUP(id->getval(args[numargs++])); + case CODE_LOOKUPARG|RET_NULL: + LOOKUPARG(id->getval(args[numargs++]), args[numargs++].setnull()); + + case CODE_SVAR|RET_STR: case CODE_SVAR|RET_NULL: args[numargs++].setstr(newstring(*identmap[op>>8]->storage.s)); continue; + case CODE_SVAR|RET_INT: args[numargs++].setint(parseint(*identmap[op>>8]->storage.s)); continue; + case CODE_SVAR|RET_FLOAT: args[numargs++].setfloat(parsefloat(*identmap[op>>8]->storage.s)); continue; + case CODE_SVAR1: setsvarchecked(identmap[op>>8], args[0].s); freeargs(args, numargs, 0); continue; + + case CODE_IVAR|RET_INT: case CODE_IVAR|RET_NULL: args[numargs++].setint(*identmap[op>>8]->storage.i); continue; + case CODE_IVAR|RET_STR: args[numargs++].setstr(newstring(intstr(*identmap[op>>8]->storage.i))); continue; + case CODE_IVAR|RET_FLOAT: args[numargs++].setfloat(float(*identmap[op>>8]->storage.i)); continue; + case CODE_IVAR1: setvarchecked(identmap[op>>8], args[0].i); numargs = 0; continue; + case CODE_IVAR2: setvarchecked(identmap[op>>8], (args[0].i<<16)|(args[1].i<<8)); numargs = 0; continue; + case CODE_IVAR3: setvarchecked(identmap[op>>8], (args[0].i<<16)|(args[1].i<<8)|args[2].i); numargs = 0; continue; + + case CODE_FVAR|RET_FLOAT: case CODE_FVAR|RET_NULL: args[numargs++].setfloat(*identmap[op>>8]->storage.f); continue; + case CODE_FVAR|RET_STR: args[numargs++].setstr(newstring(floatstr(*identmap[op>>8]->storage.f))); continue; + case CODE_FVAR|RET_INT: args[numargs++].setint(int(*identmap[op>>8]->storage.f)); continue; + case CODE_FVAR1: setfvarchecked(identmap[op>>8], args[0].f); numargs = 0; continue; + + case CODE_COM|RET_NULL: case CODE_COM|RET_STR: case CODE_COM|RET_FLOAT: case CODE_COM|RET_INT: + id = identmap[op>>8]; #ifndef STANDALONE - callcom: + callcom: #endif - forcenull(result); - CALLCOM(numargs) - forceresult: - freeargs(args, numargs, 0); - forcearg(result, op&CODE_RET_MASK); - continue; + forcenull(result); + CALLCOM(numargs) + forceresult: + freeargs(args, numargs, 0); + forcearg(result, op&CODE_RET_MASK); + continue; #ifndef STANDALONE - case CODE_COMD|RET_NULL: case CODE_COMD|RET_STR: case CODE_COMD|RET_FLOAT: case CODE_COMD|RET_INT: - id = identmap[op>>8]; - args[numargs].setint(addreleaseaction(conc(args, numargs, true, id->name)) ? 1 : 0); - numargs++; - goto callcom; + case CODE_COMD|RET_NULL: case CODE_COMD|RET_STR: case CODE_COMD|RET_FLOAT: case CODE_COMD|RET_INT: + id = identmap[op>>8]; + args[numargs].setint(addreleaseaction(conc(args, numargs, true, id->name)) ? 1 : 0); + numargs++; + goto callcom; #endif - case CODE_COMV|RET_NULL: case CODE_COMV|RET_STR: case CODE_COMV|RET_FLOAT: case CODE_COMV|RET_INT: - id = identmap[op>>8]; - forcenull(result); - ((comfunv)id->fun)(args, numargs); - goto forceresult; - case CODE_COMC|RET_NULL: case CODE_COMC|RET_STR: case CODE_COMC|RET_FLOAT: case CODE_COMC|RET_INT: - id = identmap[op>>8]; - forcenull(result); - { - vector buf; - buf.reserve(MAXSTRLEN); - ((comfun1)id->fun)(conc(buf, args, numargs, true)); - } - goto forceresult; - - case CODE_CONC|RET_NULL: case CODE_CONC|RET_STR: case CODE_CONC|RET_FLOAT: case CODE_CONC|RET_INT: - case CODE_CONCW|RET_NULL: case CODE_CONCW|RET_STR: case CODE_CONCW|RET_FLOAT: case CODE_CONCW|RET_INT: - { - int numconc = op>>8; - char *s = conc(&args[numargs-numconc], numconc, (op&CODE_OP_MASK)==CODE_CONC); - freeargs(args, numargs, numargs-numconc); - args[numargs++].setstr(s); - forcearg(args[numargs-1], op&CODE_RET_MASK); - continue; - } - - case CODE_CONCM|RET_NULL: case CODE_CONCM|RET_STR: case CODE_CONCM|RET_FLOAT: case CODE_CONCM|RET_INT: - { - int numconc = op>>8; - char *s = conc(&args[numargs-numconc], numconc, false); - freeargs(args, numargs, numargs-numconc); - result.setstr(s); - forcearg(result, op&CODE_RET_MASK); - continue; - } - - case CODE_ALIAS: - setalias(*identmap[op>>8], args[--numargs]); - freeargs(args, numargs, 0); - continue; - case CODE_ALIASARG: - setarg(*identmap[op>>8], args[--numargs]); - freeargs(args, numargs, 0); - continue; - case CODE_ALIASU: - forcestr(args[0]); - setalias(args[0].s, args[--numargs]); - freeargs(args, numargs, 0); - continue; - - case CODE_CALL|RET_NULL: case CODE_CALL|RET_STR: case CODE_CALL|RET_FLOAT: case CODE_CALL|RET_INT: - #define CALLALIAS(offset) { \ - identstack argstack[MAXARGS]; \ - for(int i = 0; i < numargs-offset; i++) \ - pusharg(*identmap[i], args[i+offset], argstack[i]); \ - int oldargs = _numargs, newargs = numargs-offset; \ - _numargs = newargs; \ - int oldflags = identflags; \ - identflags |= id->flags&IDF_OVERRIDDEN; \ - identlink aliaslink = { id, aliasstack, (1<code) id->code = compilecode(id->getstr()); \ - uint *code = id->code; \ - code[0] += 0x100; \ - runcode(code+1, result); \ - code[0] -= 0x100; \ - if(int(code[0]) < 0x100) delete[] code; \ - aliasstack = aliaslink.next; \ - identflags = oldflags; \ - for(int i = 0; i < newargs; i++) \ - poparg(*identmap[i]); \ - for(int argmask = aliaslink.usedargs&(~0U<>8]; - if(id->flags&IDF_UNKNOWN) - { - debugcode("unknown command: %s", id->name); - goto forceresult; - } - CALLALIAS(0); - continue; - case CODE_CALLARG|RET_NULL: case CODE_CALLARG|RET_STR: case CODE_CALLARG|RET_FLOAT: case CODE_CALLARG|RET_INT: - forcenull(result); - id = identmap[op>>8]; - if(!(aliasstack->usedargs&(1<index))) goto forceresult; - CALLALIAS(0); - continue; - - case CODE_CALLU|RET_NULL: case CODE_CALLU|RET_STR: case CODE_CALLU|RET_FLOAT: case CODE_CALLU|RET_INT: - if(args[0].type != VAL_STR) goto litval; - id = idents.access(args[0].s); - if(!id) - { - noid: - if(checknumber(args[0].s)) goto litval; - debugcode("unknown command: %s", args[0].s); - forcenull(result); - goto forceresult; - } - forcenull(result); - switch(id->type) - { - case ID_COMMAND: - freearg(args[0]); - callcommand(id, args+1, numargs-1); - forcearg(result, op&CODE_RET_MASK); - numargs = 0; - continue; - case ID_LOCAL: - { - identstack locals[MAXARGS]; - freearg(args[0]); - loopj(numargs-1) pushalias(*forceident(args[j+1]), locals[j]); - code = runcode(code, result); - loopj(numargs-1) popalias(*args[j+1].id); - goto exit; - } - case ID_VAR: - if(numargs <= 1) printvar(id); else setvarchecked(id, &args[1], numargs-1); - goto forceresult; - case ID_FVAR: - if(numargs <= 1) printvar(id); else setfvarchecked(id, forcefloat(args[1])); - goto forceresult; - case ID_SVAR: - if(numargs <= 1) printvar(id); else setsvarchecked(id, forcestr(args[1])); - goto forceresult; - case ID_ALIAS: - if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) goto forceresult; - if(id->valtype==VAL_NULL) goto noid; - freearg(args[0]); - CALLALIAS(1); - continue; - default: - goto forceresult; - } - } - } + case CODE_COMV|RET_NULL: case CODE_COMV|RET_STR: case CODE_COMV|RET_FLOAT: case CODE_COMV|RET_INT: + id = identmap[op>>8]; + forcenull(result); + ((comfunv)id->fun)(args, numargs); + goto forceresult; + case CODE_COMC|RET_NULL: case CODE_COMC|RET_STR: case CODE_COMC|RET_FLOAT: case CODE_COMC|RET_INT: + id = identmap[op>>8]; + forcenull(result); + { + vector buf; + buf.reserve(MAXSTRLEN); + ((comfun1)id->fun)(conc(buf, args, numargs, true)); + } + goto forceresult; + + case CODE_CONC|RET_NULL: case CODE_CONC|RET_STR: case CODE_CONC|RET_FLOAT: case CODE_CONC|RET_INT: + case CODE_CONCW|RET_NULL: case CODE_CONCW|RET_STR: case CODE_CONCW|RET_FLOAT: case CODE_CONCW|RET_INT: + { + int numconc = op>>8; + char *s = conc(&args[numargs-numconc], numconc, (op&CODE_OP_MASK)==CODE_CONC); + freeargs(args, numargs, numargs-numconc); + args[numargs++].setstr(s); + forcearg(args[numargs-1], op&CODE_RET_MASK); + continue; + } + + case CODE_CONCM|RET_NULL: case CODE_CONCM|RET_STR: case CODE_CONCM|RET_FLOAT: case CODE_CONCM|RET_INT: + { + int numconc = op>>8; + char *s = conc(&args[numargs-numconc], numconc, false); + freeargs(args, numargs, numargs-numconc); + result.setstr(s); + forcearg(result, op&CODE_RET_MASK); + continue; + } + + case CODE_ALIAS: + setalias(*identmap[op>>8], args[--numargs]); + freeargs(args, numargs, 0); + continue; + case CODE_ALIASARG: + setarg(*identmap[op>>8], args[--numargs]); + freeargs(args, numargs, 0); + continue; + case CODE_ALIASU: + forcestr(args[0]); + setalias(args[0].s, args[--numargs]); + freeargs(args, numargs, 0); + continue; + + case CODE_CALL|RET_NULL: case CODE_CALL|RET_STR: case CODE_CALL|RET_FLOAT: case CODE_CALL|RET_INT: + #define CALLALIAS(offset) { \ + identstack argstack[MAXARGS]; \ + for(int i = 0; i < numargs-offset; i++) \ + pusharg(*identmap[i], args[i+offset], argstack[i]); \ + int oldargs = _numargs, newargs = numargs-offset; \ + _numargs = newargs; \ + int oldflags = identflags; \ + identflags |= id->flags&IDF_OVERRIDDEN; \ + identlink aliaslink = { id, aliasstack, (1<code) id->code = compilecode(id->getstr()); \ + uint *code = id->code; \ + code[0] += 0x100; \ + runcode(code+1, result); \ + code[0] -= 0x100; \ + if(int(code[0]) < 0x100) delete[] code; \ + aliasstack = aliaslink.next; \ + identflags = oldflags; \ + for(int i = 0; i < newargs; i++) \ + poparg(*identmap[i]); \ + for(int argmask = aliaslink.usedargs&(~0U<>8]; + if(id->flags&IDF_UNKNOWN) + { + debugcode("unknown command: %s", id->name); + goto forceresult; + } + CALLALIAS(0); + continue; + case CODE_CALLARG|RET_NULL: case CODE_CALLARG|RET_STR: case CODE_CALLARG|RET_FLOAT: case CODE_CALLARG|RET_INT: + forcenull(result); + id = identmap[op>>8]; + if(!(aliasstack->usedargs&(1<index))) goto forceresult; + CALLALIAS(0); + continue; + + case CODE_CALLU|RET_NULL: case CODE_CALLU|RET_STR: case CODE_CALLU|RET_FLOAT: case CODE_CALLU|RET_INT: + if(args[0].type != VAL_STR) goto litval; + id = idents.access(args[0].s); + if(!id) + { + noid: + if(checknumber(args[0].s)) goto litval; + debugcode("unknown command: %s", args[0].s); + forcenull(result); + goto forceresult; + } + forcenull(result); + switch(id->type) + { + case ID_COMMAND: + freearg(args[0]); + callcommand(id, args+1, numargs-1); + forcearg(result, op&CODE_RET_MASK); + numargs = 0; + continue; + case ID_LOCAL: + { + identstack locals[MAXARGS]; + freearg(args[0]); + loopj(numargs-1) pushalias(*forceident(args[j+1]), locals[j]); + code = runcode(code, result); + loopj(numargs-1) popalias(*args[j+1].id); + goto exit; + } + case ID_VAR: + if(numargs <= 1) printvar(id); else setvarchecked(id, &args[1], numargs-1); + goto forceresult; + case ID_FVAR: + if(numargs <= 1) printvar(id); else setfvarchecked(id, forcefloat(args[1])); + goto forceresult; + case ID_SVAR: + if(numargs <= 1) printvar(id); else setsvarchecked(id, forcestr(args[1])); + goto forceresult; + case ID_ALIAS: + if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) goto forceresult; + if(id->valtype==VAL_NULL) goto noid; + freearg(args[0]); + CALLALIAS(1); + continue; + default: + goto forceresult; + } + } + } exit: - commandret = prevret; - --rundepth; - return code; + commandret = prevret; + --rundepth; + return code; } void executeret(const uint *code, tagval &result) { - runcode(code, result); + runcode(code, result); } void executeret(const char *p, tagval &result) { - vector code; - code.reserve(64); - compilemain(code, p, VAL_ANY); - runcode(code.getbuf()+1, result); - if(int(code[0]) >= 0x100) code.disown(); + vector code; + code.reserve(64); + compilemain(code, p, VAL_ANY); + runcode(code.getbuf()+1, result); + if(int(code[0]) >= 0x100) code.disown(); } void executeret(ident *id, tagval *args, int numargs, bool lookup, tagval &result) { - result.setnull(); - ++rundepth; - tagval *prevret = commandret; - commandret = &result; - if(rundepth > MAXRUNDEPTH) debugcode("exceeded recursion limit"); - else if(id) switch(id->type) - { - default: - if(!id->fun) break; - // fall-through - case ID_COMMAND: - if(numargs < id->numargs) - { - tagval buf[MAXARGS]; - memcpy(buf, args, numargs*sizeof(tagval)); - callcommand(id, buf, numargs, lookup); - } - else callcommand(id, args, numargs, lookup); - numargs = 0; - break; - case ID_VAR: - if(numargs <= 0) printvar(id); else setvarchecked(id, args, numargs); - break; - case ID_FVAR: - if(numargs <= 0) printvar(id); else setfvarchecked(id, forcefloat(args[0])); - break; - case ID_SVAR: - if(numargs <= 0) printvar(id); else setsvarchecked(id, forcestr(args[0])); - break; - case ID_ALIAS: - if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) break; - if(id->valtype==VAL_NULL) break; - #define op RET_NULL - CALLALIAS(0); - #undef op - break; - } - freeargs(args, numargs, 0); - commandret = prevret; - --rundepth; + result.setnull(); + ++rundepth; + tagval *prevret = commandret; + commandret = &result; + if(rundepth > MAXRUNDEPTH) debugcode("exceeded recursion limit"); + else if(id) switch(id->type) + { + default: + if(!id->fun) break; + // fall-through + case ID_COMMAND: + if(numargs < id->numargs) + { + tagval buf[MAXARGS]; + memcpy(buf, args, numargs*sizeof(tagval)); + callcommand(id, buf, numargs, lookup); + } + else callcommand(id, args, numargs, lookup); + numargs = 0; + break; + case ID_VAR: + if(numargs <= 0) printvar(id); else setvarchecked(id, args, numargs); + break; + case ID_FVAR: + if(numargs <= 0) printvar(id); else setfvarchecked(id, forcefloat(args[0])); + break; + case ID_SVAR: + if(numargs <= 0) printvar(id); else setsvarchecked(id, forcestr(args[0])); + break; + case ID_ALIAS: + if(id->index < MAXARGS && !(aliasstack->usedargs&(1<index))) break; + if(id->valtype==VAL_NULL) break; + #define op RET_NULL + CALLALIAS(0); + #undef op + break; + } + freeargs(args, numargs, 0); + commandret = prevret; + --rundepth; } char *executestr(const uint *code) { - tagval result; - runcode(code, result); - if(result.type == VAL_NULL) return NULL; - forcestr(result); - return result.s; + tagval result; + runcode(code, result); + if(result.type == VAL_NULL) return NULL; + forcestr(result); + return result.s; } char *executestr(const char *p) { - tagval result; - executeret(p, result); - if(result.type == VAL_NULL) return NULL; - forcestr(result); - return result.s; + tagval result; + executeret(p, result); + if(result.type == VAL_NULL) return NULL; + forcestr(result); + return result.s; } char *executestr(ident *id, tagval *args, int numargs, bool lookup) { - tagval result; - executeret(id, args, numargs, lookup, result); - if(result.type == VAL_NULL) return NULL; - forcestr(result); - return result.s; + tagval result; + executeret(id, args, numargs, lookup, result); + if(result.type == VAL_NULL) return NULL; + forcestr(result); + return result.s; } char *execidentstr(const char *name, bool lookup) { - ident *id = idents.access(name); - return id ? executestr(id, NULL, 0, lookup) : NULL; + ident *id = idents.access(name); + return id ? executestr(id, NULL, 0, lookup) : NULL; } int execute(const uint *code) { - tagval result; - runcode(code, result); - int i = result.getint(); - freearg(result); - return i; + tagval result; + runcode(code, result); + int i = result.getint(); + freearg(result); + return i; } int execute(const char *p) { - vector code; - code.reserve(64); - compilemain(code, p, VAL_INT); - tagval result; - runcode(code.getbuf()+1, result); - if(int(code[0]) >= 0x100) code.disown(); - int i = result.getint(); - freearg(result); - return i; + vector code; + code.reserve(64); + compilemain(code, p, VAL_INT); + tagval result; + runcode(code.getbuf()+1, result); + if(int(code[0]) >= 0x100) code.disown(); + int i = result.getint(); + freearg(result); + return i; } int execute(ident *id, tagval *args, int numargs, bool lookup) { - tagval result; - executeret(id, args, numargs, lookup, result); - int i = result.getint(); - freearg(result); - return i; + tagval result; + executeret(id, args, numargs, lookup, result); + int i = result.getint(); + freearg(result); + return i; } int execident(const char *name, int noid, bool lookup) { - ident *id = idents.access(name); - return id ? execute(id, NULL, 0, lookup) : noid; + ident *id = idents.access(name); + return id ? execute(id, NULL, 0, lookup) : noid; } static inline bool getbool(const char *s) { - switch(s[0]) - { - case '+': case '-': - switch(s[1]) - { - case '0': break; - case '.': return !isdigit(s[2]) || parsefloat(s) != 0; - default: return true; - } - // fall through - case '0': - { - char *end; - int val = int(strtoul((char *)s, &end, 0)); - if(val) return true; - switch(*end) - { - case 'e': case '.': return parsefloat(s) != 0; - default: return false; - } - } - case '.': return !isdigit(s[1]) || parsefloat(s) != 0; - case '\0': return false; - default: return true; - } + switch(s[0]) + { + case '+': case '-': + switch(s[1]) + { + case '0': break; + case '.': return !isdigit(s[2]) || parsefloat(s) != 0; + default: return true; + } + // fall through + case '0': + { + char *end; + int val = int(strtoul((char *)s, &end, 0)); + if(val) return true; + switch(*end) + { + case 'e': case '.': return parsefloat(s) != 0; + default: return false; + } + } + case '.': return !isdigit(s[1]) || parsefloat(s) != 0; + case '\0': return false; + default: return true; + } } static inline bool getbool(const tagval &v) { - switch(v.type) - { - case VAL_FLOAT: return v.f!=0; - case VAL_INT: return v.i!=0; - case VAL_STR: case VAL_MACRO: return getbool(v.s); - default: return false; - } + switch(v.type) + { + case VAL_FLOAT: return v.f!=0; + case VAL_INT: return v.i!=0; + case VAL_STR: case VAL_MACRO: return getbool(v.s); + default: return false; + } } bool executebool(const uint *code) { - tagval result; - runcode(code, result); - bool b = getbool(result); - freearg(result); - return b; + tagval result; + runcode(code, result); + bool b = getbool(result); + freearg(result); + return b; } bool executebool(const char *p) { - tagval result; - executeret(p, result); - bool b = getbool(result); - freearg(result); - return b; + tagval result; + executeret(p, result); + bool b = getbool(result); + freearg(result); + return b; } bool executebool(ident *id, tagval *args, int numargs, bool lookup) { - tagval result; - executeret(id, args, numargs, lookup, result); - bool b = getbool(result); - freearg(result); - return b; + tagval result; + executeret(id, args, numargs, lookup, result); + bool b = getbool(result); + freearg(result); + return b; } bool execidentbool(const char *name, bool noid, bool lookup) { - ident *id = idents.access(name); - return id ? executebool(id, NULL, 0, lookup) : noid; + ident *id = idents.access(name); + return id ? executebool(id, NULL, 0, lookup) : noid; } bool execfile(const char *cfgfile, bool msg) { - string s; - copystring(s, cfgfile); - char *buf = loadfile(path(s), NULL); - if(!buf) - { - if(msg) conoutf(CON_ERROR, "could not read \"%s\"", cfgfile); - return false; - } - const char *oldsourcefile = sourcefile, *oldsourcestr = sourcestr; - sourcefile = cfgfile; - sourcestr = buf; - execute(buf); - sourcefile = oldsourcefile; - sourcestr = oldsourcestr; - delete[] buf; - return true; + string s; + copystring(s, cfgfile); + char *buf = loadfile(path(s), NULL); + if(!buf) + { + if(msg) conoutf(CON_ERROR, "could not read \"%s\"", cfgfile); + return false; + } + const char *oldsourcefile = sourcefile, *oldsourcestr = sourcestr; + sourcefile = cfgfile; + sourcestr = buf; + execute(buf); + sourcefile = oldsourcefile; + sourcestr = oldsourcestr; + delete[] buf; + return true; } ICOMMAND(exec, "sb", (char *file, int *msg), intret(execfile(file, *msg != 0) ? 1 : 0)); const char *escapestring(const char *s) { - static vector strbuf[3]; - static int stridx = 0; - stridx = (stridx + 1)%3; - vector &buf = strbuf[stridx]; - buf.setsize(0); - buf.add('"'); - for(; *s; s++) switch(*s) - { - case '\n': buf.put("^n", 2); break; - case '\t': buf.put("^t", 2); break; - case '\f': buf.put("^f", 2); break; - case '"': buf.put("^\"", 2); break; - case '^': buf.put("^^", 2); break; - default: buf.add(*s); break; - } - buf.put("\"\0", 2); - return buf.getbuf(); + static vector strbuf[3]; + static int stridx = 0; + stridx = (stridx + 1)%3; + vector &buf = strbuf[stridx]; + buf.setsize(0); + buf.add('"'); + for(; *s; s++) switch(*s) + { + case '\n': buf.put("^n", 2); break; + case '\t': buf.put("^t", 2); break; + case '\f': buf.put("^f", 2); break; + case '"': buf.put("^\"", 2); break; + case '^': buf.put("^^", 2); break; + default: buf.add(*s); break; + } + buf.put("\"\0", 2); + return buf.getbuf(); } ICOMMAND(escape, "s", (char *s), result(escapestring(s))); ICOMMAND(unescape, "s", (char *s), { - int len = strlen(s); - char *d = newstring(len); - d[unescapestring(d, s, &s[len])] = '\0'; - stringret(d); + int len = strlen(s); + char *d = newstring(len); + d[unescapestring(d, s, &s[len])] = '\0'; + stringret(d); }); const char *escapeid(const char *s) { - const char *end = s + strcspn(s, "\"/;()[]@ \f\t\r\n\0"); - return *end ? escapestring(s) : s; + const char *end = s + strcspn(s, "\"/;()[]@ \f\t\r\n\0"); + return *end ? escapestring(s) : s; } bool validateblock(const char *s) { - const int maxbrak = 100; - static char brakstack[maxbrak]; - int brakdepth = 0; - for(; *s; s++) switch(*s) - { - case '[': case '(': if(brakdepth >= maxbrak) return false; brakstack[brakdepth++] = *s; break; - case ']': if(brakdepth <= 0 || brakstack[--brakdepth] != '[') return false; break; - case ')': if(brakdepth <= 0 || brakstack[--brakdepth] != '(') return false; break; - case '"': s = parsestring(s + 1); if(*s != '"') return false; break; - case '/': if(s[1] == '/') return false; break; - case '@': case '\f': return false; - } - return brakdepth == 0; + const int maxbrak = 100; + static char brakstack[maxbrak]; + int brakdepth = 0; + for(; *s; s++) switch(*s) + { + case '[': case '(': if(brakdepth >= maxbrak) return false; brakstack[brakdepth++] = *s; break; + case ']': if(brakdepth <= 0 || brakstack[--brakdepth] != '[') return false; break; + case ')': if(brakdepth <= 0 || brakstack[--brakdepth] != '(') return false; break; + case '"': s = parsestring(s + 1); if(*s != '"') return false; break; + case '/': if(s[1] == '/') return false; break; + case '@': case '\f': return false; + } + return brakdepth == 0; } #ifndef STANDALONE void writecfg(const char *name) { - stream *f = openutf8file(path(name && name[0] ? name : game::savedconfig(), true), "w"); - if(!f) return; - f->printf("// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", game::defaultconfig(), game::autoexec()); - game::writeclientinfo(f); - f->printf("\n"); - writecrosshairs(f); - vector ids; - enumerate(idents, ident, id, ids.add(&id)); - ids.sortname(); - loopv(ids) - { - ident &id = *ids[i]; - if(id.flags&IDF_PERSIST) switch(id.type) - { - case ID_VAR: f->printf("%s %d\n", escapeid(id), *id.storage.i); break; - case ID_FVAR: f->printf("%s %s\n", escapeid(id), floatstr(*id.storage.f)); break; - case ID_SVAR: f->printf("%s %s\n", escapeid(id), escapestring(*id.storage.s)); break; - } - } - f->printf("\n"); - writebinds(f); - f->printf("\n"); - loopv(ids) - { - ident &id = *ids[i]; - if(id.type==ID_ALIAS && id.flags&IDF_PERSIST && !(id.flags&IDF_OVERRIDDEN)) switch(id.valtype) - { - case VAL_STR: - if(!id.val.s[0]) break; - if(!validateblock(id.val.s)) { f->printf("%s = %s\n", escapeid(id), escapestring(id.val.s)); break; } - [[fallthrough]]; - case VAL_FLOAT: - [[fallthrough]]; - case VAL_INT: - f->printf("%s = [%s]\n", escapeid(id), id.getstr()); break; - } - } - f->printf("\n"); - writecompletions(f); - delete f; + stream *f = openutf8file(path(name && name[0] ? name : game::savedconfig(), true), "w"); + if(!f) return; + f->printf("// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", game::defaultconfig(), game::autoexec()); + game::writeclientinfo(f); + f->printf("\n"); + writecrosshairs(f); + vector ids; + enumerate(idents, ident, id, ids.add(&id)); + ids.sortname(); + loopv(ids) + { + ident &id = *ids[i]; + if(id.flags&IDF_PERSIST) switch(id.type) + { + case ID_VAR: f->printf("%s %d\n", escapeid(id), *id.storage.i); break; + case ID_FVAR: f->printf("%s %s\n", escapeid(id), floatstr(*id.storage.f)); break; + case ID_SVAR: f->printf("%s %s\n", escapeid(id), escapestring(*id.storage.s)); break; + } + } + f->printf("\n"); + writebinds(f); + f->printf("\n"); + loopv(ids) + { + ident &id = *ids[i]; + if(id.type==ID_ALIAS && id.flags&IDF_PERSIST && !(id.flags&IDF_OVERRIDDEN)) switch(id.valtype) + { + case VAL_STR: + if(!id.val.s[0]) break; + if(!validateblock(id.val.s)) { f->printf("%s = %s\n", escapeid(id), escapestring(id.val.s)); break; } + [[fallthrough]]; + case VAL_FLOAT: + [[fallthrough]]; + case VAL_INT: + f->printf("%s = [%s]\n", escapeid(id), id.getstr()); break; + } + } + f->printf("\n"); + writecompletions(f); + delete f; } COMMAND(writecfg, "s"); @@ -2500,10 +2500,10 @@ COMMAND(writecfg, "s"); void changedvars() { - vector ids; - enumerate(idents, ident, id, if(id.flags&IDF_OVERRIDDEN) ids.add(&id)); - ids.sortname(); - loopv(ids) printvar(ids[i]); + vector ids; + enumerate(idents, ident, id, if(id.flags&IDF_OVERRIDDEN) ids.add(&id)); + ids.sortname(); + loopv(ids) printvar(ids[i]); } COMMAND(changedvars, ""); @@ -2515,26 +2515,26 @@ static int retidx = 0; const char *intstr(int v) { - retidx = (retidx + 1)%4; - intformat(retbuf[retidx], v); - return retbuf[retidx]; + retidx = (retidx + 1)%4; + intformat(retbuf[retidx], v); + return retbuf[retidx]; } void intret(int v) { - commandret->setint(v); + commandret->setint(v); } const char *floatstr(float v) { - retidx = (retidx + 1)%4; - floatformat(retbuf[retidx], v); - return retbuf[retidx]; + retidx = (retidx + 1)%4; + floatformat(retbuf[retidx], v); + return retbuf[retidx]; } void floatret(float v) { - commandret->setfloat(v); + commandret->setfloat(v); } #undef ICOMMANDNAME @@ -2546,190 +2546,190 @@ ICOMMAND(?, "ttt", (tagval *cond, tagval *t, tagval *f), result(*(getbool(*cond) ICOMMAND(pushif, "rte", (ident *id, tagval *v, uint *code), { - if(id->type != ID_ALIAS || id->index < MAXARGS) return; - if(getbool(*v)) - { - identstack stack; - pusharg(*id, *v, stack); - v->type = VAL_NULL; - id->flags &= ~IDF_UNKNOWN; - executeret(code, *commandret); - poparg(*id); - } + if(id->type != ID_ALIAS || id->index < MAXARGS) return; + if(getbool(*v)) + { + identstack stack; + pusharg(*id, *v, stack); + v->type = VAL_NULL; + id->flags &= ~IDF_UNKNOWN; + executeret(code, *commandret); + poparg(*id); + } }); void loopiter(ident *id, identstack &stack, const tagval &v) { - if(id->stack != &stack) - { - pusharg(*id, v, stack); - id->flags &= ~IDF_UNKNOWN; - } - else - { - if(id->valtype == VAL_STR) delete[] id->val.s; - cleancode(*id); - id->setval(v); - } + if(id->stack != &stack) + { + pusharg(*id, v, stack); + id->flags &= ~IDF_UNKNOWN; + } + else + { + if(id->valtype == VAL_STR) delete[] id->val.s; + cleancode(*id); + id->setval(v); + } } void loopend(ident *id, identstack &stack) { - if(id->stack == &stack) poparg(*id); + if(id->stack == &stack) poparg(*id); } static inline void setiter(ident &id, int i, identstack &stack) { - if(id.stack == &stack) - { - if(id.valtype != VAL_INT) - { - if(id.valtype == VAL_STR) delete[] id.val.s; - cleancode(id); - id.valtype = VAL_INT; - } - id.val.i = i; - } - else - { - tagval t; - t.setint(i); - pusharg(id, t, stack); - id.flags &= ~IDF_UNKNOWN; - } + if(id.stack == &stack) + { + if(id.valtype != VAL_INT) + { + if(id.valtype == VAL_STR) delete[] id.val.s; + cleancode(id); + id.valtype = VAL_INT; + } + id.val.i = i; + } + else + { + tagval t; + t.setint(i); + pusharg(id, t, stack); + id.flags &= ~IDF_UNKNOWN; + } } ICOMMAND(loop, "rie", (ident *id, int *n, uint *body), { - if(*n <= 0 || id->type!=ID_ALIAS) return; - identstack stack; - loopi(*n) - { - setiter(*id, i, stack); - execute(body); - } - poparg(*id); + if(*n <= 0 || id->type!=ID_ALIAS) return; + identstack stack; + loopi(*n) + { + setiter(*id, i, stack); + execute(body); + } + poparg(*id); }); ICOMMAND(loopwhile, "riee", (ident *id, int *n, uint *cond, uint *body), { - if(*n <= 0 || id->type!=ID_ALIAS) return; - identstack stack; - loopi(*n) - { - setiter(*id, i, stack); - if(!executebool(cond)) break; - execute(body); - } - poparg(*id); + if(*n <= 0 || id->type!=ID_ALIAS) return; + identstack stack; + loopi(*n) + { + setiter(*id, i, stack); + if(!executebool(cond)) break; + execute(body); + } + poparg(*id); }); ICOMMAND(while, "ee", (uint *cond, uint *body), while(executebool(cond)) execute(body)); char *loopconc(ident *id, int n, uint *body, bool space) { - identstack stack; - vector s; - loopi(n) - { - setiter(*id, i, stack); - tagval v; - executeret(body, v); - const char *vstr = v.getstr(); - int len = strlen(vstr); - if(space && i) s.add(' '); - s.put(vstr, len); - freearg(v); - } - if(n > 0) poparg(*id); - s.add('\0'); - return newstring(s.getbuf(), s.length()-1); + identstack stack; + vector s; + loopi(n) + { + setiter(*id, i, stack); + tagval v; + executeret(body, v); + const char *vstr = v.getstr(); + int len = strlen(vstr); + if(space && i) s.add(' '); + s.put(vstr, len); + freearg(v); + } + if(n > 0) poparg(*id); + s.add('\0'); + return newstring(s.getbuf(), s.length()-1); } ICOMMAND(loopconcat, "rie", (ident *id, int *n, uint *body), { - if(*n > 0 && id->type==ID_ALIAS) commandret->setstr(loopconc(id, *n, body, true)); + if(*n > 0 && id->type==ID_ALIAS) commandret->setstr(loopconc(id, *n, body, true)); }); ICOMMAND(loopconcatword, "rie", (ident *id, int *n, uint *body), { - if(*n > 0 && id->type==ID_ALIAS) commandret->setstr(loopconc(id, *n, body, false)); + if(*n > 0 && id->type==ID_ALIAS) commandret->setstr(loopconc(id, *n, body, false)); }); void concat(tagval *v, int n) { - commandret->setstr(conc(v, n, true)); + commandret->setstr(conc(v, n, true)); } COMMAND(concat, "V"); void concatword(tagval *v, int n) { - commandret->setstr(conc(v, n, false)); + commandret->setstr(conc(v, n, false)); } COMMAND(concatword, "V"); void append(ident *id, tagval *v, bool space) { - if(id->type != ID_ALIAS || v->type == VAL_NULL) return; - if(id->valtype == VAL_NULL) - { - noprefix: - if(id->index < MAXARGS) setarg(*id, *v); else setalias(*id, *v); - v->type = VAL_NULL; - } - else - { - const char *prefix = id->getstr(); - if(!prefix[0]) goto noprefix; - tagval r; - r.setstr(conc(v, 1, space, prefix)); - if(id->index < MAXARGS) setarg(*id, r); else setalias(*id, r); - } + if(id->type != ID_ALIAS || v->type == VAL_NULL) return; + if(id->valtype == VAL_NULL) + { + noprefix: + if(id->index < MAXARGS) setarg(*id, *v); else setalias(*id, *v); + v->type = VAL_NULL; + } + else + { + const char *prefix = id->getstr(); + if(!prefix[0]) goto noprefix; + tagval r; + r.setstr(conc(v, 1, space, prefix)); + if(id->index < MAXARGS) setarg(*id, r); else setalias(*id, r); + } } ICOMMAND(append, "rt", (ident *id, tagval *v), append(id, v, true)); ICOMMAND(appendword, "rt", (ident *id, tagval *v), append(id, v, false)); void result(tagval &v) { - *commandret = v; - v.type = VAL_NULL; + *commandret = v; + v.type = VAL_NULL; } void stringret(char *s) { - commandret->setstr(s); + commandret->setstr(s); } void result(const char *s) { - commandret->setstr(newstring(s)); + commandret->setstr(newstring(s)); } ICOMMAND(result, "t", (tagval *v), { - *commandret = *v; - v->type = VAL_NULL; + *commandret = *v; + v->type = VAL_NULL; }); void format(tagval *args, int numargs) { - vector s; - const char *f = args[0].getstr(); - while(*f) - { - int c = *f++; - if(c == '%') - { - int i = *f++; - if(i >= '1' && i <= '9') - { - i -= '0'; - const char *sub = i < numargs ? args[i].getstr() : ""; - while(*sub) s.add(*sub++); - } - else s.add(i); - } - else s.add(c); - } - s.add('\0'); - result(s.getbuf()); + vector s; + const char *f = args[0].getstr(); + while(*f) + { + int c = *f++; + if(c == '%') + { + int i = *f++; + if(i >= '1' && i <= '9') + { + i -= '0'; + const char *sub = i < numargs ? args[i].getstr() : ""; + while(*sub) s.add(*sub++); + } + else s.add(i); + } + else s.add(c); + } + s.add('\0'); + result(s.getbuf()); } COMMAND(format, "V"); @@ -2737,448 +2737,448 @@ static const char *liststart = NULL, *listend = NULL, *listquotestart = NULL, *l static inline void skiplist(const char *&p) { - for(;;) - { - p += strspn(p, " \t\r\n"); - if(p[0]!='/' || p[1]!='/') break; - p += strcspn(p, "\n\0"); - } + for(;;) + { + p += strspn(p, " \t\r\n"); + if(p[0]!='/' || p[1]!='/') break; + p += strcspn(p, "\n\0"); + } } static bool parselist(const char *&s, const char *&start = liststart, const char *&end = listend, const char *"estart = listquotestart, const char *"eend = listquoteend) { - skiplist(s); - switch(*s) - { - case '"': quotestart = s++; start = s; s = parsestring(s); end = s; if(*s == '"') s++; quoteend = s; break; - case '(': case '[': - quotestart = s; - start = s+1; - for(int braktype = *s++, brak = 1;;) - { - s += strcspn(s, "\"/;()[]\0"); - int c = *s++; - switch(c) - { - case '\0': s--; quoteend = end = s; return true; - case '"': s = parsestring(s); if(*s == '"') s++; break; - case '/': if(*s == '/') s += strcspn(s, "\n\0"); break; - case '(': case '[': if(c == braktype) brak++; break; - case ')': if(braktype == '(' && --brak <= 0) goto endblock; break; - case ']': if(braktype == '[' && --brak <= 0) goto endblock; break; - } - } - endblock: - end = s-1; - quoteend = s; - break; - case '\0': case ')': case ']': return false; - default: quotestart = start = s; s = parseword(s); quoteend = end = s; break; - } - skiplist(s); - if(*s == ';') s++; - return true; + skiplist(s); + switch(*s) + { + case '"': quotestart = s++; start = s; s = parsestring(s); end = s; if(*s == '"') s++; quoteend = s; break; + case '(': case '[': + quotestart = s; + start = s+1; + for(int braktype = *s++, brak = 1;;) + { + s += strcspn(s, "\"/;()[]\0"); + int c = *s++; + switch(c) + { + case '\0': s--; quoteend = end = s; return true; + case '"': s = parsestring(s); if(*s == '"') s++; break; + case '/': if(*s == '/') s += strcspn(s, "\n\0"); break; + case '(': case '[': if(c == braktype) brak++; break; + case ')': if(braktype == '(' && --brak <= 0) goto endblock; break; + case ']': if(braktype == '[' && --brak <= 0) goto endblock; break; + } + } + endblock: + end = s-1; + quoteend = s; + break; + case '\0': case ')': case ']': return false; + default: quotestart = start = s; s = parseword(s); quoteend = end = s; break; + } + skiplist(s); + if(*s == ';') s++; + return true; } void explodelist(const char *s, vector &elems, int limit) { - const char *start, *end; - while((limit < 0 || elems.length() < limit) && parselist(s, start, end)) - elems.add(newstring(start, end-start)); + const char *start, *end; + while((limit < 0 || elems.length() < limit) && parselist(s, start, end)) + elems.add(newstring(start, end-start)); } char *indexlist(const char *s, int pos) { - loopi(pos) if(!parselist(s)) return newstring(""); - const char *start, *end; - return parselist(s, start, end) ? newstring(start, end-start) : newstring(""); + loopi(pos) if(!parselist(s)) return newstring(""); + const char *start, *end; + return parselist(s, start, end) ? newstring(start, end-start) : newstring(""); } int listlen(const char *s) { - int n = 0; - while(parselist(s)) n++; - return n; + int n = 0; + while(parselist(s)) n++; + return n; } ICOMMAND(listlen, "s", (char *s), intret(listlen(s))); void at(tagval *args, int numargs) { - if(!numargs) return; - const char *start = args[0].getstr(), *end = start + strlen(start); - for(int i = 1; i < numargs; i++) - { - const char *list = start; - int pos = args[i].getint(); - for(; pos > 0; pos--) if(!parselist(list)) break; - if(pos > 0 || !parselist(list, start, end)) start = end = ""; - } - commandret->setstr(newstring(start, end-start)); + if(!numargs) return; + const char *start = args[0].getstr(), *end = start + strlen(start); + for(int i = 1; i < numargs; i++) + { + const char *list = start; + int pos = args[i].getint(); + for(; pos > 0; pos--) if(!parselist(list)) break; + if(pos > 0 || !parselist(list, start, end)) start = end = ""; + } + commandret->setstr(newstring(start, end-start)); } COMMAND(at, "si1V"); void substr(char *s, int *start, int *count, int *numargs) { - int len = strlen(s), offset = clamp(*start, 0, len); - commandret->setstr(newstring(&s[offset], *numargs >= 3 ? clamp(*count, 0, len - offset) : len - offset)); + int len = strlen(s), offset = clamp(*start, 0, len); + commandret->setstr(newstring(&s[offset], *numargs >= 3 ? clamp(*count, 0, len - offset) : len - offset)); } COMMAND(substr, "siiN"); void chopstr(char *s, int *lim, char *ellipsis) { - int len = strlen(s), maxlen = abs(*lim); - if(len > maxlen) - { - int elen = strlen(ellipsis); - maxlen = max(maxlen, elen); - char *chopped = newstring(maxlen); - if(*lim < 0) - { - memcpy(chopped, ellipsis, elen); - memcpy(&chopped[elen], &s[len - (maxlen - elen)], maxlen - elen); - } - else - { - memcpy(chopped, s, maxlen - elen); - memcpy(&chopped[maxlen - elen], ellipsis, elen); - } - chopped[maxlen] = '\0'; - commandret->setstr(chopped); - } - else result(s); + int len = strlen(s), maxlen = abs(*lim); + if(len > maxlen) + { + int elen = strlen(ellipsis); + maxlen = max(maxlen, elen); + char *chopped = newstring(maxlen); + if(*lim < 0) + { + memcpy(chopped, ellipsis, elen); + memcpy(&chopped[elen], &s[len - (maxlen - elen)], maxlen - elen); + } + else + { + memcpy(chopped, s, maxlen - elen); + memcpy(&chopped[maxlen - elen], ellipsis, elen); + } + chopped[maxlen] = '\0'; + commandret->setstr(chopped); + } + else result(s); } COMMAND(chopstr, "sis"); void sublist(const char *s, int *skip, int *count, int *numargs) { - int offset = max(*skip, 0), len = *numargs >= 3 ? max(*count, 0) : -1; - loopi(offset) if(!parselist(s)) break; - if(len < 0) { if(offset > 0) skiplist(s); commandret->setstr(newstring(s)); return; } - const char *list = s, *start, *end, *qstart, *qend = s; - if(len > 0 && parselist(s, start, end, list, qend)) while(--len > 0 && parselist(s, start, end, qstart, qend)); - commandret->setstr(newstring(list, qend - list)); + int offset = max(*skip, 0), len = *numargs >= 3 ? max(*count, 0) : -1; + loopi(offset) if(!parselist(s)) break; + if(len < 0) { if(offset > 0) skiplist(s); commandret->setstr(newstring(s)); return; } + const char *list = s, *start, *end, *qstart, *qend = s; + if(len > 0 && parselist(s, start, end, list, qend)) while(--len > 0 && parselist(s, start, end, qstart, qend)); + commandret->setstr(newstring(list, qend - list)); } COMMAND(sublist, "siiN"); ICOMMAND(stripcolors, "s", (char *s), { - int len = strlen(s); - char *d = newstring(len); - filtertext(d, s, true, false, len); - stringret(d); + int len = strlen(s); + char *d = newstring(len); + filtertext(d, s, true, false, len); + stringret(d); }); static inline void setiter(ident &id, char *val, identstack &stack) { - if(id.stack == &stack) - { - if(id.valtype == VAL_STR) delete[] id.val.s; - else id.valtype = VAL_STR; - cleancode(id); - id.val.s = val; - } - else - { - tagval t; - t.setstr(val); - pusharg(id, t, stack); - id.flags &= ~IDF_UNKNOWN; - } + if(id.stack == &stack) + { + if(id.valtype == VAL_STR) delete[] id.val.s; + else id.valtype = VAL_STR; + cleancode(id); + id.val.s = val; + } + else + { + tagval t; + t.setstr(val); + pusharg(id, t, stack); + id.flags &= ~IDF_UNKNOWN; + } } void listfind(ident *id, const char *list, const uint *body) { - if(id->type!=ID_ALIAS) { intret(-1); return; } - identstack stack; - int n = -1; - for(const char *s = list, *start, *end; parselist(s, start, end);) - { - ++n; - char *val = newstring(start, end-start); - setiter(*id, val, stack); - if(executebool(body)) { intret(n); goto found; } - } - intret(-1); + if(id->type!=ID_ALIAS) { intret(-1); return; } + identstack stack; + int n = -1; + for(const char *s = list, *start, *end; parselist(s, start, end);) + { + ++n; + char *val = newstring(start, end-start); + setiter(*id, val, stack); + if(executebool(body)) { intret(n); goto found; } + } + intret(-1); found: - if(n >= 0) poparg(*id); + if(n >= 0) poparg(*id); } COMMAND(listfind, "rse"); void looplist(ident *id, const char *list, const uint *body) { - if(id->type!=ID_ALIAS) return; - identstack stack; - int n = 0; - for(const char *s = list, *start, *end; parselist(s, start, end); n++) - { - char *val = newstring(start, end-start); - setiter(*id, val, stack); - execute(body); - } - if(n) poparg(*id); + if(id->type!=ID_ALIAS) return; + identstack stack; + int n = 0; + for(const char *s = list, *start, *end; parselist(s, start, end); n++) + { + char *val = newstring(start, end-start); + setiter(*id, val, stack); + execute(body); + } + if(n) poparg(*id); } COMMAND(looplist, "rse"); void loopsublist(ident *id, const char *list, int *skip, int *count, const uint *body) { - if(id->type!=ID_ALIAS) return; - identstack stack; - int n = 0, offset = max(*skip, 0), len = *count < 0 ? INT_MAX : offset + *count; - for(const char *s = list, *start, *end; parselist(s, start, end) && n < len; n++) if(n >= offset) - { - char *val = newstring(start, end-start); - setiter(*id, val, stack); - execute(body); - } - if(n) poparg(*id); + if(id->type!=ID_ALIAS) return; + identstack stack; + int n = 0, offset = max(*skip, 0), len = *count < 0 ? INT_MAX : offset + *count; + for(const char *s = list, *start, *end; parselist(s, start, end) && n < len; n++) if(n >= offset) + { + char *val = newstring(start, end-start); + setiter(*id, val, stack); + execute(body); + } + if(n) poparg(*id); } COMMAND(loopsublist, "rsiie"); void looplistconc(ident *id, const char *list, const uint *body, bool space) { - if(id->type!=ID_ALIAS) return; - identstack stack; - vector r; - int n = 0; - for(const char *s = list, *start, *end; parselist(s, start, end); n++) - { - char *val = newstring(start, end-start); - setiter(*id, val, stack); - - if(n && space) r.add(' '); - - tagval v; - executeret(body, v); - const char *vstr = v.getstr(); - int len = strlen(vstr); - r.put(vstr, len); - freearg(v); - } - if(n) poparg(*id); - r.add('\0'); - commandret->setstr(newstring(r.getbuf(), r.length()-1)); + if(id->type!=ID_ALIAS) return; + identstack stack; + vector r; + int n = 0; + for(const char *s = list, *start, *end; parselist(s, start, end); n++) + { + char *val = newstring(start, end-start); + setiter(*id, val, stack); + + if(n && space) r.add(' '); + + tagval v; + executeret(body, v); + const char *vstr = v.getstr(); + int len = strlen(vstr); + r.put(vstr, len); + freearg(v); + } + if(n) poparg(*id); + r.add('\0'); + commandret->setstr(newstring(r.getbuf(), r.length()-1)); } ICOMMAND(looplistconcat, "rse", (ident *id, char *list, uint *body), looplistconc(id, list, body, true)); ICOMMAND(looplistconcatword, "rse", (ident *id, char *list, uint *body), looplistconc(id, list, body, false)); void listfilter(ident *id, const char *list, const uint *body) { - if(id->type!=ID_ALIAS) return; - identstack stack; - vector r; - int n = 0; - for(const char *s = list, *start, *end, *quotestart, *quoteend; parselist(s, start, end, quotestart, quoteend); n++) - { - char *val = newstring(start, end-start); - setiter(*id, val, stack); - - if(executebool(body)) - { - if(r.length()) r.add(' '); - r.put(quotestart, quoteend-quotestart); - } - } - if(n) poparg(*id); - r.add('\0'); - commandret->setstr(newstring(r.getbuf(), r.length()-1)); + if(id->type!=ID_ALIAS) return; + identstack stack; + vector r; + int n = 0; + for(const char *s = list, *start, *end, *quotestart, *quoteend; parselist(s, start, end, quotestart, quoteend); n++) + { + char *val = newstring(start, end-start); + setiter(*id, val, stack); + + if(executebool(body)) + { + if(r.length()) r.add(' '); + r.put(quotestart, quoteend-quotestart); + } + } + if(n) poparg(*id); + r.add('\0'); + commandret->setstr(newstring(r.getbuf(), r.length()-1)); } COMMAND(listfilter, "rse"); void prettylist(const char *s, const char *conj) { - vector p; - const char *start, *end; - for(int len = listlen(s), n = 0; parselist(s, start, end); n++) - { - p.put(start, end - start); - if(n+1 < len) - { - if(len > 2 || !conj[0]) p.add(','); - if(n+2 == len && conj[0]) - { - p.add(' '); - p.put(conj, strlen(conj)); - } - p.add(' '); - } - } - p.add('\0'); - result(p.getbuf()); + vector p; + const char *start, *end; + for(int len = listlen(s), n = 0; parselist(s, start, end); n++) + { + p.put(start, end - start); + if(n+1 < len) + { + if(len > 2 || !conj[0]) p.add(','); + if(n+2 == len && conj[0]) + { + p.add(' '); + p.put(conj, strlen(conj)); + } + p.add(' '); + } + } + p.add('\0'); + result(p.getbuf()); } COMMAND(prettylist, "ss"); int listincludes(const char *list, const char *needle, int needlelen) { - int offset = 0; - for(const char *s = list, *start, *end; parselist(s, start, end);) - { - int len = end - start; - if(needlelen == len && !strncmp(needle, start, len)) return offset; - offset++; - } - return -1; + int offset = 0; + for(const char *s = list, *start, *end; parselist(s, start, end);) + { + int len = end - start; + if(needlelen == len && !strncmp(needle, start, len)) return offset; + offset++; + } + return -1; } ICOMMAND(indexof, "ss", (char *list, char *elem), intret(listincludes(list, elem, strlen(elem)))); char *listdel(const char *s, const char *del) { - vector p; - for(const char *start, *end, *qstart, *qend; parselist(s, start, end, qstart, qend);) - { - if(listincludes(del, start, end-start) < 0) - { - if(!p.empty()) p.add(' '); - p.put(qstart, qend-qstart); - } - } - p.add('\0'); - return newstring(p.getbuf(), p.length()-1); + vector p; + for(const char *start, *end, *qstart, *qend; parselist(s, start, end, qstart, qend);) + { + if(listincludes(del, start, end-start) < 0) + { + if(!p.empty()) p.add(' '); + p.put(qstart, qend-qstart); + } + } + p.add('\0'); + return newstring(p.getbuf(), p.length()-1); } ICOMMAND(listdel, "ss", (char *list, char *del), commandret->setstr(listdel(list, del))); void listsplice(const char *s, const char *vals, int *skip, int *count) { - int offset = max(*skip, 0), len = max(*count, 0); - const char *list = s, *start, *end, *qstart, *qend = s; - loopi(offset) if(!parselist(s, start, end, qstart, qend)) break; - vector p; - if(qend > list) p.put(list, qend-list); - if(*vals) - { - if(!p.empty()) p.add(' '); - p.put(vals, strlen(vals)); - } - loopi(len) if(!parselist(s)) break; - skiplist(s); - switch(*s) - { - case '\0': case ')': case ']': break; - default: - if(!p.empty()) p.add(' '); - p.put(s, strlen(s)); - break; - } - p.add('\0'); - commandret->setstr(newstring(p.getbuf(), p.length()-1)); + int offset = max(*skip, 0), len = max(*count, 0); + const char *list = s, *start, *end, *qstart, *qend = s; + loopi(offset) if(!parselist(s, start, end, qstart, qend)) break; + vector p; + if(qend > list) p.put(list, qend-list); + if(*vals) + { + if(!p.empty()) p.add(' '); + p.put(vals, strlen(vals)); + } + loopi(len) if(!parselist(s)) break; + skiplist(s); + switch(*s) + { + case '\0': case ')': case ']': break; + default: + if(!p.empty()) p.add(' '); + p.put(s, strlen(s)); + break; + } + p.add('\0'); + commandret->setstr(newstring(p.getbuf(), p.length()-1)); } COMMAND(listsplice, "ssii"); ICOMMAND(loopfiles, "rsse", (ident *id, char *dir, char *ext, uint *body), { - if(id->type!=ID_ALIAS) return; - identstack stack; - vector files; - listfiles(dir, ext[0] ? ext : NULL, files); - loopvrev(files) - { - char *file = files[i]; - bool redundant = false; - loopj(i) if(!strcmp(files[j], file)) { redundant = true; break; } - if(redundant) delete[] files.removeunordered(i); - } - loopv(files) - { - char *file = files[i]; - if(i) - { - if(id->valtype == VAL_STR) delete[] id->val.s; - else id->valtype = VAL_STR; - id->val.s = file; - } - else - { - tagval t; - t.setstr(file); - pusharg(*id, t, stack); - id->flags &= ~IDF_UNKNOWN; - } - execute(body); - } - if(files.length()) poparg(*id); + if(id->type!=ID_ALIAS) return; + identstack stack; + vector files; + listfiles(dir, ext[0] ? ext : NULL, files); + loopvrev(files) + { + char *file = files[i]; + bool redundant = false; + loopj(i) if(!strcmp(files[j], file)) { redundant = true; break; } + if(redundant) delete[] files.removeunordered(i); + } + loopv(files) + { + char *file = files[i]; + if(i) + { + if(id->valtype == VAL_STR) delete[] id->val.s; + else id->valtype = VAL_STR; + id->val.s = file; + } + else + { + tagval t; + t.setstr(file); + pusharg(*id, t, stack); + id->flags &= ~IDF_UNKNOWN; + } + execute(body); + } + if(files.length()) poparg(*id); }); void findfile_(char *name) { - string fname; - copystring(fname, name); - path(fname); - intret( + string fname; + copystring(fname, name); + path(fname); + intret( #ifndef STANDALONE - findzipfile(fname) || + findzipfile(fname) || #endif - fileexists(fname, "e") || findfile(fname, "e") ? 1 : 0 - ); + fileexists(fname, "e") || findfile(fname, "e") ? 1 : 0 + ); } COMMANDN(findfile, findfile_, "s"); struct sortitem { - const char *str, *quotestart, *quoteend; + const char *str, *quotestart, *quoteend; }; struct sortfun { - ident *x, *y; - uint *body; - - bool operator()(const sortitem &xval, const sortitem &yval) - { - if(x->valtype != VAL_MACRO) x->valtype = VAL_MACRO; - cleancode(*x); - x->val.code = (const uint *)xval.str; - if(y->valtype != VAL_MACRO) y->valtype = VAL_MACRO; - cleancode(*y); - y->val.code = (const uint *)yval.str; - return executebool(body); - } + ident *x, *y; + uint *body; + + bool operator()(const sortitem &xval, const sortitem &yval) + { + if(x->valtype != VAL_MACRO) x->valtype = VAL_MACRO; + cleancode(*x); + x->val.code = (const uint *)xval.str; + if(y->valtype != VAL_MACRO) y->valtype = VAL_MACRO; + cleancode(*y); + y->val.code = (const uint *)yval.str; + return executebool(body); + } }; void sortlist(char *list, ident *x, ident *y, uint *body) { - if(x == y || x->type != ID_ALIAS || y->type != ID_ALIAS) return; - - vector items; - int macrolen = strlen(list), total = 0; - char *macros = newstring(list, macrolen); - const char *curlist = list, *start, *end, *quotestart, *quoteend; - while(parselist(curlist, start, end, quotestart, quoteend)) - { - macros[end - list] = '\0'; - sortitem item = { ¯os[start - list], quotestart, quoteend }; - items.add(item); - total += int(quoteend - quotestart); - } - - identstack xstack, ystack; - pusharg(*x, nullval, xstack); x->flags &= ~IDF_UNKNOWN; - pusharg(*y, nullval, ystack); y->flags &= ~IDF_UNKNOWN; - - sortfun f = { x, y, body }; - items.sort(f); - - poparg(*x); - poparg(*y); - - char *sorted = macros; - int sortedlen = total + max(items.length() - 1, 0); - if(macrolen < sortedlen) - { - delete[] macros; - sorted = newstring(sortedlen); - } - - int offset = 0; - loopv(items) - { - sortitem &item = items[i]; - int len = int(item.quoteend - item.quotestart); - if(i) sorted[offset++] = ' '; - memcpy(&sorted[offset], item.quotestart, len); - offset += len; - } - sorted[offset] = '\0'; - - commandret->setstr(sorted); + if(x == y || x->type != ID_ALIAS || y->type != ID_ALIAS) return; + + vector items; + int macrolen = strlen(list), total = 0; + char *macros = newstring(list, macrolen); + const char *curlist = list, *start, *end, *quotestart, *quoteend; + while(parselist(curlist, start, end, quotestart, quoteend)) + { + macros[end - list] = '\0'; + sortitem item = { ¯os[start - list], quotestart, quoteend }; + items.add(item); + total += int(quoteend - quotestart); + } + + identstack xstack, ystack; + pusharg(*x, nullval, xstack); x->flags &= ~IDF_UNKNOWN; + pusharg(*y, nullval, ystack); y->flags &= ~IDF_UNKNOWN; + + sortfun f = { x, y, body }; + items.sort(f); + + poparg(*x); + poparg(*y); + + char *sorted = macros; + int sortedlen = total + max(items.length() - 1, 0); + if(macrolen < sortedlen) + { + delete[] macros; + sorted = newstring(sortedlen); + } + + int offset = 0; + loopv(items) + { + sortitem &item = items[i]; + int len = int(item.quoteend - item.quotestart); + if(i) sorted[offset++] = ' '; + memcpy(&sorted[offset], item.quotestart, len); + offset += len; + } + sorted[offset] = '\0'; + + commandret->setstr(sorted); } COMMAND(sortlist, "srre"); @@ -3212,23 +3212,23 @@ ICOMMAND(<<, "ii", (int *a, int *b), intret(*b < 32 ? *a << max(*b, 0) : 0)); ICOMMAND(>>, "ii", (int *a, int *b), intret(*a >> clamp(*b, 0, 31))); ICOMMAND(&&, "e1V", (tagval *args, int numargs), { - if(!numargs) intret(1); - else loopi(numargs) - { - if(i) freearg(*commandret); - executeret(args[i].code, *commandret); - if(!getbool(*commandret)) break; - } + if(!numargs) intret(1); + else loopi(numargs) + { + if(i) freearg(*commandret); + executeret(args[i].code, *commandret); + if(!getbool(*commandret)) break; + } }); ICOMMAND(||, "e1V", (tagval *args, int numargs), { - if(!numargs) intret(0); - else loopi(numargs) - { - if(i) freearg(*commandret); - executeret(args[i].code, *commandret); - if(getbool(*commandret)) break; - } + if(!numargs) intret(0); + else loopi(numargs) + { + if(i) freearg(*commandret); + executeret(args[i].code, *commandret); + if(getbool(*commandret)) break; + } }); ICOMMAND(div, "ii", (int *a, int *b), intret(*b ? *a / *b : 0)); @@ -3250,27 +3250,27 @@ ICOMMAND(log10, "f", (float *a), floatret(log10(*a))); ICOMMAND(exp, "f", (float *a), floatret(exp(*a))); ICOMMAND(min, "V", (tagval *args, int numargs), { - int val = numargs > 0 ? args[numargs - 1].getint() : 0; - loopi(numargs - 1) val = min(val, args[i].getint()); - intret(val); + int val = numargs > 0 ? args[numargs - 1].getint() : 0; + loopi(numargs - 1) val = min(val, args[i].getint()); + intret(val); }); ICOMMAND(max, "V", (tagval *args, int numargs), { - int val = numargs > 0 ? args[numargs - 1].getint() : 0; - loopi(numargs - 1) val = max(val, args[i].getint()); - intret(val); + int val = numargs > 0 ? args[numargs - 1].getint() : 0; + loopi(numargs - 1) val = max(val, args[i].getint()); + intret(val); }); ICOMMAND(minf, "V", (tagval *args, int numargs), { - float val = numargs > 0 ? args[numargs - 1].getfloat() : 0.0f; - loopi(numargs - 1) val = min(val, args[i].getfloat()); - floatret(val); + float val = numargs > 0 ? args[numargs - 1].getfloat() : 0.0f; + loopi(numargs - 1) val = min(val, args[i].getfloat()); + floatret(val); }); ICOMMAND(maxf, "V", (tagval *args, int numargs), { - float val = numargs > 0 ? args[numargs - 1].getfloat() : 0.0f; - loopi(numargs - 1) val = max(val, args[i].getfloat()); - floatret(val); + float val = numargs > 0 ? args[numargs - 1].getfloat() : 0.0f; + loopi(numargs - 1) val = max(val, args[i].getfloat()); + floatret(val); }); ICOMMAND(abs, "i", (int *n), intret(abs(*n))); ICOMMAND(absf, "f", (float *n), floatret(fabs(*n))); @@ -3279,50 +3279,50 @@ ICOMMAND(floor, "f", (float *n), floatret(floor(*n))); ICOMMAND(ceil, "f", (float *n), floatret(ceil(*n))); ICOMMAND(round, "ff", (float *n, float *k), { - double step = *k; - double r = *n; - if(step > 0) - { - r += step * (r < 0 ? -0.5 : 0.5); - r -= fmod(r, step); - } - else r = r < 0 ? ceil(r - 0.5) : floor(r + 0.5); - floatret(float(r)); + double step = *k; + double r = *n; + if(step > 0) + { + r += step * (r < 0 ? -0.5 : 0.5); + r -= fmod(r, step); + } + else r = r < 0 ? ceil(r - 0.5) : floor(r + 0.5); + floatret(float(r)); }); ICOMMAND(cond, "ee2V", (tagval *args, int numargs), { - for(int i = 0; i < numargs; i += 2) - { - if(i+1 < numargs) - { - if(executebool(args[i].code)) - { - executeret(args[i+1].code, *commandret); - break; - } - } - else - { - executeret(args[i].code, *commandret); - break; - } - } + for(int i = 0; i < numargs; i += 2) + { + if(i+1 < numargs) + { + if(executebool(args[i].code)) + { + executeret(args[i+1].code, *commandret); + break; + } + } + else + { + executeret(args[i].code, *commandret); + break; + } + } }); #define CASECOMMAND(name, fmt, type, acc, compare) \ - ICOMMAND(name, fmt "te2V", (tagval *args, int numargs), \ - { \ - type val = acc; \ - int i; \ - for(i = 1; i+1 < numargs; i += 2) \ - { \ - if(compare) \ - { \ - executeret(args[i+1].code, *commandret); \ - return; \ - } \ - } \ - }) + ICOMMAND(name, fmt "te2V", (tagval *args, int numargs), \ + { \ + type val = acc; \ + int i; \ + for(i = 1; i+1 < numargs; i += 2) \ + { \ + if(compare) \ + { \ + executeret(args[i+1].code, *commandret); \ + return; \ + } \ + } \ + }) CASECOMMAND(case, "i", int, args[0].getint(), args[i].type == VAL_NULL || args[i].getint() == val); CASECOMMAND(casef, "f", float, args[0].getfloat(), args[i].type == VAL_NULL || args[i].getfloat() == val); CASECOMMAND(cases, "s", const char *, args[0].getstr(), args[i].type == VAL_NULL || !strcmp(args[i].getstr(), val)); @@ -3330,19 +3330,19 @@ CASECOMMAND(cases, "s", const char *, args[0].getstr(), args[i].type == VAL_NULL ICOMMAND(rnd, "ii", (int *a, int *b), intret(*a - *b > 0 ? rnd(*a - *b) + *b : *b)); ICOMMAND(rndstr, "i", (int *len), { - int n = clamp(*len, 0, 10000); - char *s = newstring(n); - for(int i = 0; i < n;) - { - uint r = randomMT(); - for(int j = min(i + 4, n); i < j; i++) - { - s[i] = (r%255) + 1; - r /= 255; - } - } - s[n] = '\0'; - stringret(s); + int n = clamp(*len, 0, 10000); + char *s = newstring(n); + for(int i = 0; i < n;) + { + uint r = randomMT(); + for(int j = min(i + 4, n); i < j; i++) + { + s[i] = (r%255) + 1; + r /= 255; + } + } + s[n] = '\0'; + stringret(s); }); ICOMMAND(strcmp, "ss", (char *a, char *b), intret(strcmp(a,b)==0)); @@ -3363,80 +3363,80 @@ ICOMMAND(unistr, "i", (int *i), { char *s = newstring(1); s[0] = uni2cube(*i); s int naturalsort(const char *a, const char *b) { - for(;;) - { - int ac = *a, bc = *b; - if(!ac) return bc ? -1 : 0; - else if(!bc) return 1; - else if(isdigit(ac) && isdigit(bc)) - { - while(*a == '0') a++; - while(*b == '0') b++; - const char *a0 = a, *b0 = b; - while(isdigit(*a)) a++; - while(isdigit(*b)) b++; - int alen = a - a0, blen = b - b0; - if(alen != blen) return alen - blen; - int n = memcmp(a0, b0, alen); - if(n < 0) return -1; - else if(n > 0) return 1; - } - else if(ac != bc) return ac - bc; - else { ++a; ++b; } - } + for(;;) + { + int ac = *a, bc = *b; + if(!ac) return bc ? -1 : 0; + else if(!bc) return 1; + else if(isdigit(ac) && isdigit(bc)) + { + while(*a == '0') a++; + while(*b == '0') b++; + const char *a0 = a, *b0 = b; + while(isdigit(*a)) a++; + while(isdigit(*b)) b++; + int alen = a - a0, blen = b - b0; + if(alen != blen) return alen - blen; + int n = memcmp(a0, b0, alen); + if(n < 0) return -1; + else if(n > 0) return 1; + } + else if(ac != bc) return ac - bc; + else { ++a; ++b; } + } } ICOMMAND(naturalsort, "ss", (char *a, char *b), intret(naturalsort(a,b)<=0)); #define STRMAPCOMMAND(name, map) \ - ICOMMAND(name, "s", (char *s), \ - { \ - int len = strlen(s); \ - char *m = newstring(len); \ - loopi(len) m[i] = map(s[i]); \ - m[len] = '\0'; \ - stringret(m); \ - }) + ICOMMAND(name, "s", (char *s), \ + { \ + int len = strlen(s); \ + char *m = newstring(len); \ + loopi(len) m[i] = map(s[i]); \ + m[len] = '\0'; \ + stringret(m); \ + }) STRMAPCOMMAND(strlower, cubelower); STRMAPCOMMAND(strupper, cubeupper); char *strreplace(const char *s, const char *oldval, const char *newval) { - vector buf; - - int oldlen = strlen(oldval); - if(!oldlen) return newstring(s); - for(;;) - { - const char *found = strstr(s, oldval); - if(found) - { - while(s < found) buf.add(*s++); - for(const char *n = newval; *n; n++) buf.add(*n); - s = found + oldlen; - } - else - { - while(*s) buf.add(*s++); - buf.add('\0'); - return newstring(buf.getbuf(), buf.length()); - } - } + vector buf; + + int oldlen = strlen(oldval); + if(!oldlen) return newstring(s); + for(;;) + { + const char *found = strstr(s, oldval); + if(found) + { + while(s < found) buf.add(*s++); + for(const char *n = newval; *n; n++) buf.add(*n); + s = found + oldlen; + } + else + { + while(*s) buf.add(*s++); + buf.add('\0'); + return newstring(buf.getbuf(), buf.length()); + } + } } ICOMMAND(strreplace, "sss", (char *s, char *o, char *n), commandret->setstr(strreplace(s, o, n))); void strsplice(const char *s, const char *vals, int *skip, int *count) { - int slen = strlen(s), vlen = strlen(vals), - offset = clamp(*skip, 0, slen), - len = clamp(*count, 0, slen - offset); - char *p = newstring(slen - len + vlen); - if(offset) memcpy(p, s, offset); - if(vlen) memcpy(&p[offset], vals, vlen); - if(offset + len < slen) memcpy(&p[offset + vlen], &s[offset + len], slen - (offset + len)); - p[slen - len + vlen] = '\0'; - commandret->setstr(p); + int slen = strlen(s), vlen = strlen(vals), + offset = clamp(*skip, 0, slen), + len = clamp(*count, 0, slen - offset); + char *p = newstring(slen - len + vlen); + if(offset) memcpy(p, s, offset); + if(vlen) memcpy(&p[offset], vals, vlen); + if(offset + len < slen) memcpy(&p[offset + vlen], &s[offset + len], slen - (offset + len)); + p[slen - len + vlen] = '\0'; + commandret->setstr(p); } COMMAND(strsplice, "ssii"); @@ -3445,55 +3445,55 @@ ICOMMAND(getmillis, "i", (int *total), intret(*total ? totalmillis : lastmillis) struct sleepcmd { - int delay, millis, flags; - char *command; + int delay, millis, flags; + char *command; }; vector sleepcmds; void addsleep(int *msec, char *cmd) { - sleepcmd &s = sleepcmds.add(); - s.delay = max(*msec, 1); - s.millis = lastmillis; - s.command = newstring(cmd); - s.flags = identflags; + sleepcmd &s = sleepcmds.add(); + s.delay = max(*msec, 1); + s.millis = lastmillis; + s.command = newstring(cmd); + s.flags = identflags; } COMMANDN(sleep, addsleep, "is"); void checksleep(int millis) { - loopv(sleepcmds) - { - sleepcmd &s = sleepcmds[i]; - if(millis - s.millis >= s.delay) - { - char *cmd = s.command; // execute might create more sleep commands - s.command = NULL; - int oldflags = identflags; - identflags = s.flags; - execute(cmd); - identflags = oldflags; - delete[] cmd; - if(sleepcmds.inrange(i) && !sleepcmds[i].command) sleepcmds.remove(i--); - } - } + loopv(sleepcmds) + { + sleepcmd &s = sleepcmds[i]; + if(millis - s.millis >= s.delay) + { + char *cmd = s.command; // execute might create more sleep commands + s.command = NULL; + int oldflags = identflags; + identflags = s.flags; + execute(cmd); + identflags = oldflags; + delete[] cmd; + if(sleepcmds.inrange(i) && !sleepcmds[i].command) sleepcmds.remove(i--); + } + } } void clearsleep(bool clearoverrides) { - int len = 0; - loopv(sleepcmds) if(sleepcmds[i].command) - { - if(clearoverrides && !(sleepcmds[i].flags&IDF_OVERRIDDEN)) sleepcmds[len++] = sleepcmds[i]; - else delete[] sleepcmds[i].command; - } - sleepcmds.shrink(len); + int len = 0; + loopv(sleepcmds) if(sleepcmds[i].command) + { + if(clearoverrides && !(sleepcmds[i].flags&IDF_OVERRIDDEN)) sleepcmds[len++] = sleepcmds[i]; + else delete[] sleepcmds[i].command; + } + sleepcmds.shrink(len); } void clearsleep_(int *clearoverrides) { - clearsleep(*clearoverrides!=0 || identflags&IDF_OVERRIDDEN); + clearsleep(*clearoverrides!=0 || identflags&IDF_OVERRIDDEN); } COMMANDN(clearsleep, clearsleep_, "i"); diff --git a/src/engine/console.cpp b/src/engine/console.cpp index d6e83a1..c979215 100644 --- a/src/engine/console.cpp +++ b/src/engine/console.cpp @@ -18,33 +18,33 @@ VARFP(maxcon, 10, 200, MAXCONLINES, { while(conlines.length() > maxcon) delete[] VARP(contags, 0, 3, 3); -void conline(int type, const char *sf) // add a line to the console buffer -{ - char *buf = NULL; - if(type&CON_TAG_MASK) for(int i = conlines.length()-1; i >= max(conlines.length()-contags, 0); i--) - { - int prev = conlines.removing(i).type; - if(!(prev&CON_TAG_MASK)) break; - if(type == prev) - { - buf = conlines.remove(i).line; - break; - } - } - if(!buf) buf = conlines.length() >= maxcon ? conlines.remove().line : newstring("", CONSTRLEN-1); - cline &cl = conlines.add(); - cl.line = buf; - cl.type = type; - cl.outtime = totalmillis; // for how long to keep line on screen - copystring(cl.line, sf, CONSTRLEN); +void conline(int type, const char *sf) // add a line to the console buffer +{ + char *buf = NULL; + if(type&CON_TAG_MASK) for(int i = conlines.length()-1; i >= max(conlines.length()-contags, 0); i--) + { + int prev = conlines.removing(i).type; + if(!(prev&CON_TAG_MASK)) break; + if(type == prev) + { + buf = conlines.remove(i).line; + break; + } + } + if(!buf) buf = conlines.length() >= maxcon ? conlines.remove().line : newstring("", CONSTRLEN-1); + cline &cl = conlines.add(); + cl.line = buf; + cl.type = type; + cl.outtime = totalmillis; // for how long to keep line on screen + copystring(cl.line, sf, CONSTRLEN); } void conoutfv(int type, const char *fmt, va_list args) { - static char buf[CONSTRLEN]; - vformatstring(buf, fmt, args, sizeof(buf)); - conline(type, buf); - logoutf("%s", buf); + static char buf[CONSTRLEN]; + vformatstring(buf, fmt, args, sizeof(buf)); + conline(type, buf); + logoutf("%s", buf); } VAR(fullconsole, 0, 0, 1); @@ -52,14 +52,14 @@ ICOMMAND(toggleconsole, "", (), { fullconsole ^= 1; }); int rendercommand(int x, int y, int w) { - if(commandmillis < 0) return 0; + if(commandmillis < 0) return 0; - defformatstring(s, "%s %s", commandprompt ? commandprompt : ">", commandbuf); - int width, height; - text_bounds(s, width, height, w); - y -= height; - draw_text(s, x, y, 0xFF, 0xFF, 0xFF, 0xFF, (commandpos>=0) ? (commandpos+1+(commandprompt?strlen(commandprompt):1)) : strlen(s), w); - return height; + defformatstring(s, "%s %s", commandprompt ? commandprompt : ">", commandbuf); + int width, height; + text_bounds(s, width, height, w); + y -= height; + draw_text(s, x, y, 0xFF, 0xFF, 0xFF, 0xFF, (commandpos>=0) ? (commandpos+1+(commandprompt?strlen(commandprompt):1)) : strlen(s), w); + return height; } VARP(consize, 0, 5, 100); @@ -76,19 +76,19 @@ int conskip = 0, miniconskip = 0; void setconskip(int &skip, int filter, int n) { - filter &= CON_FLAGS; - int offset = abs(n), dir = n < 0 ? -1 : 1; - skip = clamp(skip, 0, conlines.length()-1); - while(offset) - { - skip += dir; - if(!conlines.inrange(skip)) - { - skip = clamp(skip, 0, conlines.length()-1); - return; - } - if(conlines[skip].type&filter) --offset; - } + filter &= CON_FLAGS; + int offset = abs(n), dir = n < 0 ? -1 : 1; + skip = clamp(skip, 0, conlines.length()-1); + while(offset) + { + skip += dir; + if(!conlines.inrange(skip)) + { + skip = clamp(skip, 0, conlines.length()-1); + return; + } + if(conlines[skip].type&filter) --offset; + } } ICOMMAND(conskip, "i", (int *n), setconskip(conskip, fullconsole ? fullconfilter : confilter, *n)); @@ -98,94 +98,94 @@ ICOMMAND(clearconsole, "", (), { while(conlines.length()) delete[] conlines.pop( int drawconlines(int conskip, int confade, int conwidth, int conheight, int conoff, int filter, int y = 0, int dir = 1) { - filter &= CON_FLAGS; - int numl = conlines.length(), offset = min(conskip, numl); - - if(confade) - { - if(!conskip) - { - numl = 0; - loopvrev(conlines) if(totalmillis-conlines[i].outtime < confade*1000) { numl = i+1; break; } - } - else offset--; - } - - int totalheight = 0; - loopi(numl) //determine visible height - { - // shuffle backwards to fill if necessary - int idx = offset+i < numl ? offset+i : --offset; - if(!(conlines[idx].type&filter)) continue; - char *line = conlines[idx].line; - int width, height; - text_bounds(line, width, height, conwidth); - if(totalheight + height > conheight) { numl = i; if(offset == idx) ++offset; break; } - totalheight += height; - } - if(dir > 0) y = conoff; - loopi(numl) - { - int idx = offset + (dir > 0 ? numl-i-1 : i); - if(!(conlines[idx].type&filter)) continue; - char *line = conlines[idx].line; - int width, height; - text_bounds(line, width, height, conwidth); - if(dir <= 0) y -= height; - draw_text(line, conoff, y, 0xFF, 0xFF, 0xFF, 0xFF, -1, conwidth); - if(dir > 0) y += height; - } - return y+conoff; -} - -int renderconsole(int w, int h, int abovehud) // render buffer taking into account time & scrolling -{ - int conpad = fullconsole ? 0 : FONTH/4, - conoff = fullconsole ? FONTH : FONTH/3, - conheight = min(fullconsole ? ((h*fullconsize/100)/FONTH)*FONTH : FONTH*consize, h - 2*(conpad + conoff)), - conwidth = w - 2*(conpad + conoff) - (fullconsole ? 0 : game::clipconsole(w, h)); - - extern void consolebox(int x1, int y1, int x2, int y2); - if(fullconsole) consolebox(conpad, conpad, conwidth+conpad+2*conoff, conheight+conpad+2*conoff); - - int y = drawconlines(conskip, fullconsole ? 0 : confade, conwidth, conheight, conpad+conoff, fullconsole ? fullconfilter : confilter); - if(!fullconsole && (miniconsize && miniconwidth)) - drawconlines(miniconskip, miniconfade, (miniconwidth*(w - 2*(conpad + conoff)))/100, min(FONTH*miniconsize, abovehud - y), conpad+conoff, miniconfilter, abovehud, -1); - return fullconsole ? conheight + 2*(conpad + conoff) : y; + filter &= CON_FLAGS; + int numl = conlines.length(), offset = min(conskip, numl); + + if(confade) + { + if(!conskip) + { + numl = 0; + loopvrev(conlines) if(totalmillis-conlines[i].outtime < confade*1000) { numl = i+1; break; } + } + else offset--; + } + + int totalheight = 0; + loopi(numl) //determine visible height + { + // shuffle backwards to fill if necessary + int idx = offset+i < numl ? offset+i : --offset; + if(!(conlines[idx].type&filter)) continue; + char *line = conlines[idx].line; + int width, height; + text_bounds(line, width, height, conwidth); + if(totalheight + height > conheight) { numl = i; if(offset == idx) ++offset; break; } + totalheight += height; + } + if(dir > 0) y = conoff; + loopi(numl) + { + int idx = offset + (dir > 0 ? numl-i-1 : i); + if(!(conlines[idx].type&filter)) continue; + char *line = conlines[idx].line; + int width, height; + text_bounds(line, width, height, conwidth); + if(dir <= 0) y -= height; + draw_text(line, conoff, y, 0xFF, 0xFF, 0xFF, 0xFF, -1, conwidth); + if(dir > 0) y += height; + } + return y+conoff; +} + +int renderconsole(int w, int h, int abovehud) // render buffer taking into account time & scrolling +{ + int conpad = fullconsole ? 0 : FONTH/4, + conoff = fullconsole ? FONTH : FONTH/3, + conheight = min(fullconsole ? ((h*fullconsize/100)/FONTH)*FONTH : FONTH*consize, h - 2*(conpad + conoff)), + conwidth = w - 2*(conpad + conoff) - (fullconsole ? 0 : game::clipconsole(w, h)); + + extern void consolebox(int x1, int y1, int x2, int y2); + if(fullconsole) consolebox(conpad, conpad, conwidth+conpad+2*conoff, conheight+conpad+2*conoff); + + int y = drawconlines(conskip, fullconsole ? 0 : confade, conwidth, conheight, conpad+conoff, fullconsole ? fullconfilter : confilter); + if(!fullconsole && (miniconsize && miniconwidth)) + drawconlines(miniconskip, miniconfade, (miniconwidth*(w - 2*(conpad + conoff)))/100, min(FONTH*miniconsize, abovehud - y), conpad+conoff, miniconfilter, abovehud, -1); + return fullconsole ? conheight + 2*(conpad + conoff) : y; } // keymap is defined externally in keymap.cfg struct keym { - enum - { - ACTION_DEFAULT = 0, - ACTION_SPECTATOR, - ACTION_EDITING, - NUMACTIONS - }; - - int code; - char *name; - char *actions[NUMACTIONS]; - bool pressed; - - keym() : code(-1), name(NULL), pressed(false) { loopi(NUMACTIONS) actions[i] = newstring(""); } - ~keym() { DELETEA(name); loopi(NUMACTIONS) DELETEA(actions[i]); } + enum + { + ACTION_DEFAULT = 0, + ACTION_SPECTATOR, + ACTION_EDITING, + NUMACTIONS + }; + + int code; + char *name; + char *actions[NUMACTIONS]; + bool pressed; + + keym() : code(-1), name(NULL), pressed(false) { loopi(NUMACTIONS) actions[i] = newstring(""); } + ~keym() { DELETEA(name); loopi(NUMACTIONS) DELETEA(actions[i]); } }; hashtable keyms(128); void keymap(int *code, char *key) { - if(identflags&IDF_OVERRIDDEN) { conoutf(CON_ERROR, "cannot override keymap %d", *code); return; } - keym &km = keyms[*code]; - km.code = *code; - DELETEA(km.name); - km.name = newstring(key); + if(identflags&IDF_OVERRIDDEN) { conoutf(CON_ERROR, "cannot override keymap %d", *code); return; } + keym &km = keyms[*code]; + km.code = *code; + DELETEA(km.name); + km.name = newstring(key); } - + COMMAND(keymap, "is"); keym *keypressed = NULL; @@ -193,83 +193,83 @@ char *keyaction = NULL; const char *getkeyname(int code) { - keym *km = keyms.access(code); - return km ? km->name : NULL; + keym *km = keyms.access(code); + return km ? km->name : NULL; } void searchbinds(char *action, int type) { - vector names; - enumerate(keyms, keym, km, - { - if(!strcmp(km.actions[type], action)) - { - if(names.length()) names.add(' '); - names.put(km.name, strlen(km.name)); - } - }); - names.add('\0'); - result(names.getbuf()); + vector names; + enumerate(keyms, keym, km, + { + if(!strcmp(km.actions[type], action)) + { + if(names.length()) names.add(' '); + names.put(km.name, strlen(km.name)); + } + }); + names.add('\0'); + result(names.getbuf()); } keym *findbind(char *key) { - enumerate(keyms, keym, km, - { - if(!strcasecmp(km.name, key)) return &km; - }); - return NULL; -} - + enumerate(keyms, keym, km, + { + if(!strcasecmp(km.name, key)) return &km; + }); + return NULL; +} + void getbind(char *key, int type) { - keym *km = findbind(key); - result(km ? km->actions[type] : ""); -} + keym *km = findbind(key); + result(km ? km->actions[type] : ""); +} void bindkey(char *key, char *action, int state, const char *cmd) { - if(identflags&IDF_OVERRIDDEN) { conoutf(CON_ERROR, "cannot override %s \"%s\"", cmd, key); return; } - keym *km = findbind(key); - if(!km) { conoutf(CON_ERROR, "unknown key \"%s\"", key); return; } - char *&binding = km->actions[state]; - if(!keypressed || keyaction!=binding) delete[] binding; - // trim white-space to make searchbinds more reliable - while(iscubespace(*action)) action++; - int len = strlen(action); - while(len>0 && iscubespace(action[len-1])) len--; - binding = newstring(action, len); + if(identflags&IDF_OVERRIDDEN) { conoutf(CON_ERROR, "cannot override %s \"%s\"", cmd, key); return; } + keym *km = findbind(key); + if(!km) { conoutf(CON_ERROR, "unknown key \"%s\"", key); return; } + char *&binding = km->actions[state]; + if(!keypressed || keyaction!=binding) delete[] binding; + // trim white-space to make searchbinds more reliable + while(iscubespace(*action)) action++; + int len = strlen(action); + while(len>0 && iscubespace(action[len-1])) len--; + binding = newstring(action, len); } -ICOMMAND(bind, "ss", (char *key, char *action), bindkey(key, action, keym::ACTION_DEFAULT, "bind")); +ICOMMAND(bind, "ss", (char *key, char *action), bindkey(key, action, keym::ACTION_DEFAULT, "bind")); ICOMMAND(specbind, "ss", (char *key, char *action), bindkey(key, action, keym::ACTION_SPECTATOR, "specbind")); ICOMMAND(editbind, "ss", (char *key, char *action), bindkey(key, action, keym::ACTION_EDITING, "editbind")); -ICOMMAND(getbind, "s", (char *key), getbind(key, keym::ACTION_DEFAULT)); +ICOMMAND(getbind, "s", (char *key), getbind(key, keym::ACTION_DEFAULT)); ICOMMAND(getspecbind, "s", (char *key), getbind(key, keym::ACTION_SPECTATOR)); ICOMMAND(geteditbind, "s", (char *key), getbind(key, keym::ACTION_EDITING)); -ICOMMAND(searchbinds, "s", (char *action), searchbinds(action, keym::ACTION_DEFAULT)); +ICOMMAND(searchbinds, "s", (char *action), searchbinds(action, keym::ACTION_DEFAULT)); ICOMMAND(searchspecbinds, "s", (char *action), searchbinds(action, keym::ACTION_SPECTATOR)); ICOMMAND(searcheditbinds, "s", (char *action), searchbinds(action, keym::ACTION_EDITING)); void inputcommand(char *init, char *action = NULL, char *prompt = NULL, char *flags = NULL) // turns input to the command line on or off { - commandmillis = init ? totalmillis : -1; - textinput(commandmillis >= 0, TI_CONSOLE); - keyrepeat(commandmillis >= 0, KR_CONSOLE); - copystring(commandbuf, init ? init : ""); - DELETEA(commandaction); - DELETEA(commandprompt); - commandpos = -1; - if(action && action[0]) commandaction = newstring(action); - if(prompt && prompt[0]) commandprompt = newstring(prompt); - commandflags = 0; - if(flags) while(*flags) switch(*flags++) - { - case 'c': commandflags |= CF_COMPLETE; break; - case 'x': commandflags |= CF_EXECUTE; break; - case 's': commandflags |= CF_COMPLETE|CF_EXECUTE; break; - } - else if(init) commandflags |= CF_COMPLETE|CF_EXECUTE; + commandmillis = init ? totalmillis : -1; + textinput(commandmillis >= 0, TI_CONSOLE); + keyrepeat(commandmillis >= 0, KR_CONSOLE); + copystring(commandbuf, init ? init : ""); + DELETEA(commandaction); + DELETEA(commandprompt); + commandpos = -1; + if(action && action[0]) commandaction = newstring(action); + if(prompt && prompt[0]) commandprompt = newstring(prompt); + commandflags = 0; + if(flags) while(*flags) switch(*flags++) + { + case 'c': commandflags |= CF_COMPLETE; break; + case 'x': commandflags |= CF_EXECUTE; break; + case 's': commandflags |= CF_COMPLETE|CF_EXECUTE; break; + } + else if(init) commandflags |= CF_COMPLETE|CF_EXECUTE; } ICOMMAND(saycommand, "C", (char *init), inputcommand(init)); @@ -277,66 +277,66 @@ COMMAND(inputcommand, "ssss"); void pasteconsole() { - if(!SDL_HasClipboardText()) return; - char *cb = SDL_GetClipboardText(); - if(!cb) return; - size_t cblen = strlen(cb), - commandlen = strlen(commandbuf), - decoded = decodeutf8((uchar *)&commandbuf[commandlen], sizeof(commandbuf)-1-commandlen, (const uchar *)cb, cblen); - commandbuf[commandlen + decoded] = '\0'; - SDL_free(cb); + if(!SDL_HasClipboardText()) return; + char *cb = SDL_GetClipboardText(); + if(!cb) return; + size_t cblen = strlen(cb), + commandlen = strlen(commandbuf), + decoded = decodeutf8((uchar *)&commandbuf[commandlen], sizeof(commandbuf)-1-commandlen, (const uchar *)cb, cblen); + commandbuf[commandlen + decoded] = '\0'; + SDL_free(cb); } struct hline { - char *buf, *action, *prompt; - int flags; - - hline() : buf(NULL), action(NULL), prompt(NULL), flags(0) {} - ~hline() - { - DELETEA(buf); - DELETEA(action); - DELETEA(prompt); - } - - void restore() - { - copystring(commandbuf, buf); - if(commandpos >= (int)strlen(commandbuf)) commandpos = -1; - DELETEA(commandaction); - DELETEA(commandprompt); - if(action) commandaction = newstring(action); - if(prompt) commandprompt = newstring(prompt); - commandflags = flags; - } - - bool shouldsave() - { - return strcmp(commandbuf, buf) || - (commandaction ? !action || strcmp(commandaction, action) : action!=NULL) || - (commandprompt ? !prompt || strcmp(commandprompt, prompt) : prompt!=NULL) || - commandflags != flags; - } - - void save() - { - buf = newstring(commandbuf); - if(commandaction) action = newstring(commandaction); - if(commandprompt) prompt = newstring(commandprompt); - flags = commandflags; - } - - void run() - { - if(flags&CF_EXECUTE && buf[0]=='/') execute(buf+1); - else if(action) - { - alias("commandbuf", buf); - execute(action); - } - else game::toserver(buf); - } + char *buf, *action, *prompt; + int flags; + + hline() : buf(NULL), action(NULL), prompt(NULL), flags(0) {} + ~hline() + { + DELETEA(buf); + DELETEA(action); + DELETEA(prompt); + } + + void restore() + { + copystring(commandbuf, buf); + if(commandpos >= (int)strlen(commandbuf)) commandpos = -1; + DELETEA(commandaction); + DELETEA(commandprompt); + if(action) commandaction = newstring(action); + if(prompt) commandprompt = newstring(prompt); + commandflags = flags; + } + + bool shouldsave() + { + return strcmp(commandbuf, buf) || + (commandaction ? !action || strcmp(commandaction, action) : action!=NULL) || + (commandprompt ? !prompt || strcmp(commandprompt, prompt) : prompt!=NULL) || + commandflags != flags; + } + + void save() + { + buf = newstring(commandbuf); + if(commandaction) action = newstring(commandaction); + if(commandprompt) prompt = newstring(commandprompt); + flags = commandflags; + } + + void run() + { + if(flags&CF_EXECUTE && buf[0]=='/') execute(buf+1); + else if(action) + { + alias("commandbuf", buf); + execute(action); + } + else game::toserver(buf); + } }; vector history; int histpos = 0; @@ -345,250 +345,246 @@ VARP(maxhistory, 0, 1000, 10000); void history_(int *n) { - static bool inhistory = false; - if(!inhistory && history.inrange(*n)) - { - inhistory = true; - history[history.length()-*n-1]->run(); - inhistory = false; - } + static bool inhistory = false; + if(!inhistory && history.inrange(*n)) + { + inhistory = true; + history[history.length()-*n-1]->run(); + inhistory = false; + } } COMMANDN(history, history_, "i"); struct releaseaction { - keym *key; - char *action; + keym *key; + char *action; }; vector releaseactions; const char *addreleaseaction(char *s) { - if(!keypressed) { delete[] s; return NULL; } - releaseaction &ra = releaseactions.add(); - ra.key = keypressed; - ra.action = s; - return keypressed->name; + if(!keypressed) { delete[] s; return NULL; } + releaseaction &ra = releaseactions.add(); + ra.key = keypressed; + ra.action = s; + return keypressed->name; } void onrelease(const char *s) { - addreleaseaction(newstring(s)); + addreleaseaction(newstring(s)); } COMMAND(onrelease, "s"); void execbind(keym &k, bool isdown) { - loopv(releaseactions) - { - releaseaction &ra = releaseactions[i]; - if(ra.key==&k) - { - if(!isdown) execute(ra.action); - delete[] ra.action; - releaseactions.remove(i--); - } - } - if(isdown) - { - int state = keym::ACTION_DEFAULT; - if(!mainmenu) - { - if(editmode) state = keym::ACTION_EDITING; - else if(player->state==CS_SPECTATOR) state = keym::ACTION_SPECTATOR; - } - char *&action = k.actions[state][0] ? k.actions[state] : k.actions[keym::ACTION_DEFAULT]; - keyaction = action; - keypressed = &k; - execute(keyaction); - keypressed = NULL; - if(keyaction!=action) delete[] keyaction; - } - k.pressed = isdown; + loopv(releaseactions) + { + releaseaction &ra = releaseactions[i]; + if(ra.key==&k) + { + if(!isdown) execute(ra.action); + delete[] ra.action; + releaseactions.remove(i--); + } + } + if(isdown) + { + int state = keym::ACTION_DEFAULT; + if(!mainmenu) + { + if(editmode) state = keym::ACTION_EDITING; + else if(player->state==CS_SPECTATOR) state = keym::ACTION_SPECTATOR; + } + char *&action = k.actions[state][0] ? k.actions[state] : k.actions[keym::ACTION_DEFAULT]; + keyaction = action; + keypressed = &k; + execute(keyaction); + keypressed = NULL; + if(keyaction!=action) delete[] keyaction; + } + k.pressed = isdown; } bool consoleinput(const char *str, int len) { - if(commandmillis < 0) return false; - - resetcomplete(); - int cmdlen = (int)strlen(commandbuf), cmdspace = int(sizeof(commandbuf)) - (cmdlen+1); - len = min(len, cmdspace); - if(commandpos<0) - { - memcpy(&commandbuf[cmdlen], str, len); - } - else - { - memmove(&commandbuf[commandpos+len], &commandbuf[commandpos], cmdlen - commandpos); - memcpy(&commandbuf[commandpos], str, len); - commandpos += len; - } - commandbuf[cmdlen + len] = '\0'; - - return true; + if(commandmillis < 0) return false; + + resetcomplete(); + int cmdlen = (int)strlen(commandbuf), cmdspace = int(sizeof(commandbuf)) - (cmdlen+1); + len = min(len, cmdspace); + if(commandpos<0) + { + memcpy(&commandbuf[cmdlen], str, len); + } + else + { + memmove(&commandbuf[commandpos+len], &commandbuf[commandpos], cmdlen - commandpos); + memcpy(&commandbuf[commandpos], str, len); + commandpos += len; + } + commandbuf[cmdlen + len] = '\0'; + + return true; } bool consolekey(int code, bool isdown) { - if(commandmillis < 0) return false; - - #ifdef __APPLE__ - #define MOD_KEYS (KMOD_LGUI|KMOD_RGUI) - #else - #define MOD_KEYS (KMOD_LCTRL|KMOD_RCTRL) - #endif - - if(isdown) - { - switch(code) - { - case SDLK_RETURN: - case SDLK_KP_ENTER: - break; - - case SDLK_HOME: - if(strlen(commandbuf)) commandpos = 0; - break; - - case SDLK_END: - commandpos = -1; - break; - - case SDLK_DELETE: - { - int len = (int)strlen(commandbuf); - if(commandpos<0) break; - memmove(&commandbuf[commandpos], &commandbuf[commandpos+1], len - commandpos); - resetcomplete(); - if(commandpos>=len-1) commandpos = -1; - break; - } - - case SDLK_BACKSPACE: - { - int len = (int)strlen(commandbuf), i = commandpos>=0 ? commandpos : len; - if(i<1) break; - memmove(&commandbuf[i-1], &commandbuf[i], len - i + 1); - resetcomplete(); - if(commandpos>0) commandpos--; - else if(!commandpos && len<=1) commandpos = -1; - break; - } - - case SDLK_LEFT: - if(commandpos>0) commandpos--; - else if(commandpos<0) commandpos = (int)strlen(commandbuf)-1; - break; - - case SDLK_RIGHT: - if(commandpos>=0 && ++commandpos>=(int)strlen(commandbuf)) commandpos = -1; - break; - - case SDLK_UP: - if(histpos > history.length()) histpos = history.length(); - if(histpos > 0) history[--histpos]->restore(); - break; - - case SDLK_DOWN: - if(histpos + 1 < history.length()) history[++histpos]->restore(); - break; - - case SDLK_TAB: - if(commandflags&CF_COMPLETE) - { - complete(commandbuf, sizeof(commandbuf), commandflags&CF_EXECUTE ? "/" : NULL); - if(commandpos>=0 && commandpos>=(int)strlen(commandbuf)) commandpos = -1; - } - break; - - case SDLK_v: - if(SDL_GetModState()&MOD_KEYS) pasteconsole(); - break; - } - } - else - { - if(code==SDLK_RETURN || code==SDLK_KP_ENTER) - { - hline *h = NULL; - if(commandbuf[0]) - { - if(history.empty() || history.last()->shouldsave()) - { - if(maxhistory && history.length() >= maxhistory) - { - loopi(history.length()-maxhistory+1) delete history[i]; - history.remove(0, history.length()-maxhistory+1); - } - history.add(h = new hline)->save(); - } - else h = history.last(); - } - histpos = history.length(); - inputcommand(NULL); - if(h) h->run(); - } - else if(code==SDLK_ESCAPE) - { - histpos = history.length(); - inputcommand(NULL); - } - } - - return true; + if(commandmillis < 0) return false; + +#define MOD_KEYS (KMOD_LCTRL|KMOD_RCTRL) + + if(isdown) + { + switch(code) + { + case SDLK_RETURN: + case SDLK_KP_ENTER: + break; + + case SDLK_HOME: + if(strlen(commandbuf)) commandpos = 0; + break; + + case SDLK_END: + commandpos = -1; + break; + + case SDLK_DELETE: + { + int len = (int)strlen(commandbuf); + if(commandpos<0) break; + memmove(&commandbuf[commandpos], &commandbuf[commandpos+1], len - commandpos); + resetcomplete(); + if(commandpos>=len-1) commandpos = -1; + break; + } + + case SDLK_BACKSPACE: + { + int len = (int)strlen(commandbuf), i = commandpos>=0 ? commandpos : len; + if(i<1) break; + memmove(&commandbuf[i-1], &commandbuf[i], len - i + 1); + resetcomplete(); + if(commandpos>0) commandpos--; + else if(!commandpos && len<=1) commandpos = -1; + break; + } + + case SDLK_LEFT: + if(commandpos>0) commandpos--; + else if(commandpos<0) commandpos = (int)strlen(commandbuf)-1; + break; + + case SDLK_RIGHT: + if(commandpos>=0 && ++commandpos>=(int)strlen(commandbuf)) commandpos = -1; + break; + + case SDLK_UP: + if(histpos > history.length()) histpos = history.length(); + if(histpos > 0) history[--histpos]->restore(); + break; + + case SDLK_DOWN: + if(histpos + 1 < history.length()) history[++histpos]->restore(); + break; + + case SDLK_TAB: + if(commandflags&CF_COMPLETE) + { + complete(commandbuf, sizeof(commandbuf), commandflags&CF_EXECUTE ? "/" : NULL); + if(commandpos>=0 && commandpos>=(int)strlen(commandbuf)) commandpos = -1; + } + break; + + case SDLK_v: + if(SDL_GetModState()&MOD_KEYS) pasteconsole(); + break; + } + } + else + { + if(code==SDLK_RETURN || code==SDLK_KP_ENTER) + { + hline *h = NULL; + if(commandbuf[0]) + { + if(history.empty() || history.last()->shouldsave()) + { + if(maxhistory && history.length() >= maxhistory) + { + loopi(history.length()-maxhistory+1) delete history[i]; + history.remove(0, history.length()-maxhistory+1); + } + history.add(h = new hline)->save(); + } + else h = history.last(); + } + histpos = history.length(); + inputcommand(NULL); + if(h) h->run(); + } + else if(code==SDLK_ESCAPE) + { + histpos = history.length(); + inputcommand(NULL); + } + } + + return true; } void processtextinput(const char *str, int len) { - if(!g3d_input(str, len)) - consoleinput(str, len); + if(!g3d_input(str, len)) + consoleinput(str, len); } void processkey(int code, bool isdown, int modstate) { - switch(code) - { - case SDLK_LGUI: case SDLK_RGUI: - return; - } - keym *haskey = keyms.access(code); - if(haskey && haskey->pressed) execbind(*haskey, isdown); // allow pressed keys to release - else if(!g3d_key(code, isdown)) // 3D GUI mouse button intercept - { - if(!consolekey(code, isdown)) - { - if(modstate&KMOD_GUI) return; - if(haskey) execbind(*haskey, isdown); - } - } + switch(code) + { + case SDLK_LGUI: case SDLK_RGUI: + return; + } + keym *haskey = keyms.access(code); + if(haskey && haskey->pressed) execbind(*haskey, isdown); // allow pressed keys to release + else if(!g3d_key(code, isdown)) // 3D GUI mouse button intercept + { + if(!consolekey(code, isdown)) + { + if(modstate&KMOD_GUI) return; + if(haskey) execbind(*haskey, isdown); + } + } } void clear_console() { - keyms.clear(); + keyms.clear(); } void writebinds(stream *f) { - static const char * const cmds[3] = { "bind", "specbind", "editbind" }; - vector binds; - enumerate(keyms, keym, km, binds.add(&km)); - binds.sortname(); - loopj(3) - { - loopv(binds) - { - keym &km = *binds[i]; - if(*km.actions[j]) - { - if(validateblock(km.actions[j])) f->printf("%s %s [%s]\n", cmds[j], escapestring(km.name), km.actions[j]); - else f->printf("%s %s %s\n", cmds[j], escapestring(km.name), escapestring(km.actions[j])); - } - } - } + static const char * const cmds[3] = { "bind", "specbind", "editbind" }; + vector binds; + enumerate(keyms, keym, km, binds.add(&km)); + binds.sortname(); + loopj(3) + { + loopv(binds) + { + keym &km = *binds[i]; + if(*km.actions[j]) + { + if(validateblock(km.actions[j])) f->printf("%s %s [%s]\n", cmds[j], escapestring(km.name), km.actions[j]); + else f->printf("%s %s %s\n", cmds[j], escapestring(km.name), escapestring(km.actions[j])); + } + } + } } // tab-completion of all idents and base maps @@ -597,62 +593,62 @@ enum { FILES_DIR = 0, FILES_VAR, FILES_LIST }; struct fileskey { - int type; - const char *dir, *ext; + int type; + const char *dir, *ext; - fileskey() {} - fileskey(int type, const char *dir, const char *ext) : type(type), dir(dir), ext(ext) {} + fileskey() {} + fileskey(int type, const char *dir, const char *ext) : type(type), dir(dir), ext(ext) {} }; static void cleanfilesdir(char *dir) { - int dirlen = (int)strlen(dir); - while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\')) - dir[--dirlen] = '\0'; + int dirlen = (int)strlen(dir); + while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\')) + dir[--dirlen] = '\0'; } struct filesval { - int type; - char *dir, *ext; - vector files; - int millis; - - filesval(int type, const char *dir, const char *ext) : type(type), dir(newstring(dir)), ext(ext && ext[0] ? newstring(ext) : NULL), millis(-1) {} - ~filesval() { DELETEA(dir); DELETEA(ext); files.deletearrays(); } - - void update() - { - if((type!=FILES_DIR && type!=FILES_VAR) || millis >= commandmillis) return; - files.deletearrays(); - if(type==FILES_VAR) - { - string buf; - buf[0] = '\0'; - if(ident *id = readident(dir)) switch(id->type) - { - case ID_SVAR: copystring(buf, *id->storage.s); break; - case ID_ALIAS: copystring(buf, id->getstr()); break; - } - if(!buf[0]) copystring(buf, "."); - cleanfilesdir(buf); - listfiles(buf, ext, files); - } - else listfiles(dir, ext, files); - files.sort(); - loopv(files) if(i && !strcmp(files[i], files[i-1])) delete[] files.remove(i--); - millis = totalmillis; - } + int type; + char *dir, *ext; + vector files; + int millis; + + filesval(int type, const char *dir, const char *ext) : type(type), dir(newstring(dir)), ext(ext && ext[0] ? newstring(ext) : NULL), millis(-1) {} + ~filesval() { DELETEA(dir); DELETEA(ext); files.deletearrays(); } + + void update() + { + if((type!=FILES_DIR && type!=FILES_VAR) || millis >= commandmillis) return; + files.deletearrays(); + if(type==FILES_VAR) + { + string buf; + buf[0] = '\0'; + if(ident *id = readident(dir)) switch(id->type) + { + case ID_SVAR: copystring(buf, *id->storage.s); break; + case ID_ALIAS: copystring(buf, id->getstr()); break; + } + if(!buf[0]) copystring(buf, "."); + cleanfilesdir(buf); + listfiles(buf, ext, files); + } + else listfiles(dir, ext, files); + files.sort(); + loopv(files) if(i && !strcmp(files[i], files[i-1])) delete[] files.remove(i--); + millis = totalmillis; + } }; static inline bool htcmp(const fileskey &x, const fileskey &y) { - return x.type==y.type && !strcmp(x.dir, y.dir) && (x.ext == y.ext || (x.ext && y.ext && !strcmp(x.ext, y.ext))); + return x.type==y.type && !strcmp(x.dir, y.dir) && (x.ext == y.ext || (x.ext && y.ext && !strcmp(x.ext, y.ext))); } static inline uint hthash(const fileskey &k) { - return hthash(k.dir); + return hthash(k.dir); } static hashtable completefiles; @@ -665,50 +661,50 @@ void resetcomplete() { completesize = 0; } void addcomplete(char *command, int type, char *dir, char *ext) { - if(identflags&IDF_OVERRIDDEN) - { - conoutf(CON_ERROR, "cannot override complete %s", command); - return; - } - if(!dir[0]) - { - filesval **hasfiles = completions.access(command); - if(hasfiles) *hasfiles = NULL; - return; - } - if(type==FILES_DIR) cleanfilesdir(dir); - if(ext) - { - if(strchr(ext, '*')) ext[0] = '\0'; - if(!ext[0]) ext = NULL; - } - fileskey key(type, dir, ext); - filesval **val = completefiles.access(key); - if(!val) - { - filesval *f = new filesval(type, dir, ext); - if(type==FILES_LIST) explodelist(dir, f->files); - val = &completefiles[fileskey(type, f->dir, f->ext)]; - *val = f; - } - filesval **hasfiles = completions.access(command); - if(hasfiles) *hasfiles = *val; - else completions[newstring(command)] = *val; + if(identflags&IDF_OVERRIDDEN) + { + conoutf(CON_ERROR, "cannot override complete %s", command); + return; + } + if(!dir[0]) + { + filesval **hasfiles = completions.access(command); + if(hasfiles) *hasfiles = NULL; + return; + } + if(type==FILES_DIR) cleanfilesdir(dir); + if(ext) + { + if(strchr(ext, '*')) ext[0] = '\0'; + if(!ext[0]) ext = NULL; + } + fileskey key(type, dir, ext); + filesval **val = completefiles.access(key); + if(!val) + { + filesval *f = new filesval(type, dir, ext); + if(type==FILES_LIST) explodelist(dir, f->files); + val = &completefiles[fileskey(type, f->dir, f->ext)]; + *val = f; + } + filesval **hasfiles = completions.access(command); + if(hasfiles) *hasfiles = *val; + else completions[newstring(command)] = *val; } void addfilecomplete(char *command, char *dir, char *ext) { - addcomplete(command, FILES_DIR, dir, ext); + addcomplete(command, FILES_DIR, dir, ext); } void addvarcomplete(char *command, char *var, char *ext) { - addcomplete(command, FILES_VAR, var, ext); + addcomplete(command, FILES_VAR, var, ext); } void addlistcomplete(char *command, char *list) { - addcomplete(command, FILES_LIST, list, NULL); + addcomplete(command, FILES_LIST, list, NULL); } COMMANDN(complete, addfilecomplete, "sss"); @@ -717,69 +713,69 @@ COMMANDN(listcomplete, addlistcomplete, "ss"); void complete(char *s, int maxlen, const char *cmdprefix) { - int cmdlen = 0; - if(cmdprefix) - { - cmdlen = strlen(cmdprefix); - if(strncmp(s, cmdprefix, cmdlen)) prependstring(s, cmdprefix, maxlen); - } - if(!s[cmdlen]) return; - if(!completesize) { completesize = (int)strlen(&s[cmdlen]); DELETEA(lastcomplete); } - - filesval *f = NULL; - if(completesize) - { - char *end = strchr(&s[cmdlen], ' '); - if(end) f = completions.find(stringslice(&s[cmdlen], end), NULL); - } - - const char *nextcomplete = NULL; - if(f) // complete using filenames - { - int commandsize = strchr(&s[cmdlen], ' ')+1-s; - f->update(); - loopv(f->files) - { - if(strncmp(f->files[i], &s[commandsize], completesize+cmdlen-commandsize)==0 && - (!lastcomplete || strcmp(f->files[i], lastcomplete) > 0) && (!nextcomplete || strcmp(f->files[i], nextcomplete) < 0)) - nextcomplete = f->files[i]; - } - cmdprefix = s; - cmdlen = commandsize; - } - else // complete using command names - { - enumerate(idents, ident, id, - if(strncmp(id.name, &s[cmdlen], completesize)==0 && - (!lastcomplete || strcmp(id.name, lastcomplete) > 0) && (!nextcomplete || strcmp(id.name, nextcomplete) < 0)) - nextcomplete = id.name; - ); - } - DELETEA(lastcomplete); - if(nextcomplete) - { - cmdlen = min(cmdlen, maxlen-1); - if(cmdlen) memmove(s, cmdprefix, cmdlen); - copystring(&s[cmdlen], nextcomplete, maxlen-cmdlen); - lastcomplete = newstring(nextcomplete); - } + int cmdlen = 0; + if(cmdprefix) + { + cmdlen = strlen(cmdprefix); + if(strncmp(s, cmdprefix, cmdlen)) prependstring(s, cmdprefix, maxlen); + } + if(!s[cmdlen]) return; + if(!completesize) { completesize = (int)strlen(&s[cmdlen]); DELETEA(lastcomplete); } + + filesval *f = NULL; + if(completesize) + { + char *end = strchr(&s[cmdlen], ' '); + if(end) f = completions.find(stringslice(&s[cmdlen], end), NULL); + } + + const char *nextcomplete = NULL; + if(f) // complete using filenames + { + int commandsize = strchr(&s[cmdlen], ' ')+1-s; + f->update(); + loopv(f->files) + { + if(strncmp(f->files[i], &s[commandsize], completesize+cmdlen-commandsize)==0 && + (!lastcomplete || strcmp(f->files[i], lastcomplete) > 0) && (!nextcomplete || strcmp(f->files[i], nextcomplete) < 0)) + nextcomplete = f->files[i]; + } + cmdprefix = s; + cmdlen = commandsize; + } + else // complete using command names + { + enumerate(idents, ident, id, + if(strncmp(id.name, &s[cmdlen], completesize)==0 && + (!lastcomplete || strcmp(id.name, lastcomplete) > 0) && (!nextcomplete || strcmp(id.name, nextcomplete) < 0)) + nextcomplete = id.name; + ); + } + DELETEA(lastcomplete); + if(nextcomplete) + { + cmdlen = min(cmdlen, maxlen-1); + if(cmdlen) memmove(s, cmdprefix, cmdlen); + copystring(&s[cmdlen], nextcomplete, maxlen-cmdlen); + lastcomplete = newstring(nextcomplete); + } } void writecompletions(stream *f) { - vector cmds; - enumeratekt(completions, char *, k, filesval *, v, { if(v) cmds.add(k); }); - cmds.sort(); - loopv(cmds) - { - char *k = cmds[i]; - filesval *v = completions[k]; - if(v->type==FILES_LIST) - { - if(validateblock(v->dir)) f->printf("listcomplete %s [%s]\n", escapeid(k), v->dir); - else f->printf("listcomplete %s %s\n", escapeid(k), escapestring(v->dir)); - } - else f->printf("%s %s %s %s\n", v->type==FILES_VAR ? "varcomplete" : "complete", escapeid(k), escapestring(v->dir), escapestring(v->ext ? v->ext : "*")); - } + vector cmds; + enumeratekt(completions, char *, k, filesval *, v, { if(v) cmds.add(k); }); + cmds.sort(); + loopv(cmds) + { + char *k = cmds[i]; + filesval *v = completions[k]; + if(v->type==FILES_LIST) + { + if(validateblock(v->dir)) f->printf("listcomplete %s [%s]\n", escapeid(k), v->dir); + else f->printf("listcomplete %s %s\n", escapeid(k), escapestring(v->dir)); + } + else f->printf("%s %s %s %s\n", v->type==FILES_VAR ? "varcomplete" : "complete", escapeid(k), escapestring(v->dir), escapestring(v->ext ? v->ext : "*")); + } } diff --git a/src/engine/decal.cpp b/src/engine/decal.cpp index fb70ebf..ccee329 100644 --- a/src/engine/decal.cpp +++ b/src/engine/decal.cpp @@ -2,641 +2,635 @@ struct decalvert { - vec pos; - bvec4 color; - vec2 tc; + vec pos; + bvec4 color; + vec2 tc; }; struct decalinfo { - int millis; - bvec color; - ushort startvert, endvert; + int millis; + bvec color; + ushort startvert, endvert; }; enum { - DF_RND4 = 1<<0, - DF_ROTATE = 1<<1, - DF_INVMOD = 1<<2, - DF_OVERBRIGHT = 1<<3, - DF_ADD = 1<<4, - DF_SATURATE = 1<<5 + DF_RND4 = 1<<0, + DF_ROTATE = 1<<1, + DF_INVMOD = 1<<2, + DF_OVERBRIGHT = 1<<3, + DF_ADD = 1<<4, + DF_SATURATE = 1<<5 }; VARFP(maxdecaltris, 1, 1024, 16384, initdecals()); VARP(decalfade, 1000, 10000, 60000); -VAR(dbgdec, 0, 0, 1); struct decalrenderer { - const char *texname; - int flags, fadeintime, fadeouttime, timetolive; - Texture *tex; - decalinfo *decals; - int maxdecals, startdecal, enddecal; - decalvert *verts; - int maxverts, startvert, endvert, lastvert, availverts; - GLuint vbo; - bool dirty; - - decalrenderer(const char *texname, int flags = 0, int fadeintime = 0, int fadeouttime = 1000, int timetolive = -1) - : texname(texname), flags(flags), - fadeintime(fadeintime), fadeouttime(fadeouttime), timetolive(timetolive), - tex(NULL), - decals(NULL), maxdecals(0), startdecal(0), enddecal(0), - verts(NULL), maxverts(0), startvert(0), endvert(0), lastvert(0), availverts(0), - vbo(0), dirty(false), - decalu(0), decalv(0) - { - } - - ~decalrenderer() - { - DELETEA(decals); - DELETEA(verts); - } - - void init(int tris) - { - if(decals) - { - DELETEA(decals); - maxdecals = startdecal = enddecal = 0; - } - if(verts) - { - DELETEA(verts); - maxverts = startvert = endvert = lastvert = availverts = 0; - } - decals = new decalinfo[tris]; - maxdecals = tris; - tex = textureload(texname, 3); - maxverts = tris*3 + 3; - availverts = maxverts - 3; - verts = new decalvert[maxverts]; - } - - int hasdecals() - { - return enddecal < startdecal ? maxdecals - (startdecal - enddecal) : enddecal - startdecal; - } - - void cleanup() - { - if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } - } - - void cleardecals() - { - startdecal = enddecal = 0; - startvert = endvert = lastvert = 0; - availverts = maxverts - 3; - dirty = true; - } - - int freedecal() - { - if(startdecal==enddecal) return 0; - - decalinfo &d = decals[startdecal]; - startdecal++; - if(startdecal >= maxdecals) startdecal = 0; - - int removed = d.endvert < d.startvert ? maxverts - (d.startvert - d.endvert) : d.endvert - d.startvert; - startvert = d.endvert; - if(startvert==endvert) startvert = endvert = lastvert = 0; - availverts += removed; - - return removed; - } - - void fadedecal(decalinfo &d, uchar alpha) - { - bvec rgb; - if(flags&DF_OVERBRIGHT) rgb = bvec(128, 128, 128); - else - { - rgb = d.color; - if(flags&(DF_ADD|DF_INVMOD)) rgb.scale(alpha, 255); - } - bvec4 color(rgb, alpha); - - decalvert *vert = &verts[d.startvert], - *end = &verts[d.endvert < d.startvert ? maxverts : d.endvert]; - while(vert < end) - { - vert->color = color; - vert++; - } - if(d.endvert < d.startvert) - { - vert = verts; - end = &verts[d.endvert]; - while(vert < end) - { - vert->color = color; - vert++; - } - } - dirty = true; - } - - void clearfadeddecals() - { - int threshold = lastmillis - (timetolive>=0 ? timetolive : decalfade) - fadeouttime; - decalinfo *d = &decals[startdecal], - *end = &decals[enddecal < startdecal ? maxdecals : enddecal]; - while(d < end && d->millis <= threshold) d++; - if(d >= end && enddecal < startdecal) - { - d = decals; - end = &decals[enddecal]; - while(d < end && d->millis <= threshold) d++; - } - int prevstart = startdecal; - startdecal = d - decals; - if(prevstart == startdecal) return; - if(startdecal!=enddecal) startvert = decals[startdecal].startvert; - else startvert = endvert = lastvert = 0; - availverts = endvert < startvert ? startvert - endvert - 3 : maxverts - 3 - (endvert - startvert); - dirty = true; - } - - void fadeindecals() - { - if(!fadeintime) return; - decalinfo *d = &decals[enddecal], - *end = &decals[enddecal < startdecal ? 0 : startdecal]; - while(d > end) - { - d--; - int fade = lastmillis - d->millis; - if(fade >= fadeintime) return; - fadedecal(*d, (fade<<8)/fadeintime); - } - if(enddecal < startdecal) - { - d = &decals[maxdecals]; - end = &decals[startdecal]; - while(d > end) - { - d--; - int fade = lastmillis - d->millis; - if(fade >= fadeintime) return; - fadedecal(*d, (fade<<8)/fadeintime); - } - } - } - - void fadeoutdecals() - { - decalinfo *d = &decals[startdecal], - *end = &decals[enddecal < startdecal ? maxdecals : enddecal]; - int offset = (timetolive>=0 ? timetolive : decalfade) + fadeouttime - lastmillis; - while(d < end) - { - int fade = d->millis + offset; - if(fade >= fadeouttime) return; - fadedecal(*d, (fade<<8)/fadeouttime); - d++; - } - if(enddecal < startdecal) - { - d = decals; - end = &decals[enddecal]; - while(d < end) - { - int fade = d->millis + offset; - if(fade >= fadeouttime) return; - fadedecal(*d, (fade<<8)/fadeouttime); - d++; - } - } - } - - static void setuprenderstate() - { - enablepolygonoffset(GL_POLYGON_OFFSET_FILL); - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - - gle::enablevertex(); - gle::enabletexcoord0(); - gle::enablecolor(); - } - - static void cleanuprenderstate() - { - gle::clearvbo(); - - gle::disablevertex(); - gle::disabletexcoord0(); - gle::disablecolor(); - - glDepthMask(GL_TRUE); - glDisable(GL_BLEND); - - disablepolygonoffset(GL_POLYGON_OFFSET_FILL); - } - - void render() - { - if(startvert==endvert) return; - - if(flags&DF_OVERBRIGHT) - { - glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); - SETSHADER(overbrightdecal); - } - else - { - if(flags&DF_INVMOD) { glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); zerofogcolor(); } - else if(flags&DF_ADD) { glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); zerofogcolor(); } - else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - if(flags&DF_SATURATE) SETSHADER(saturatedecal); - else foggedshader->set(); - } - - glBindTexture(GL_TEXTURE_2D, tex->id); - - if(!vbo) { glGenBuffers_(1, &vbo); dirty = true; } - gle::bindvbo(vbo); - - int count = endvert < startvert ? maxverts - startvert : endvert - startvert; - if(dirty) - { - glBufferData_(GL_ARRAY_BUFFER, maxverts*sizeof(decalvert), NULL, GL_STREAM_DRAW); - glBufferSubData_(GL_ARRAY_BUFFER, 0, count*sizeof(decalvert), &verts[startvert]); - if(endvert < startvert) - { - glBufferSubData_(GL_ARRAY_BUFFER, count*sizeof(decalvert), endvert*sizeof(decalvert), verts); - count += endvert; - } - dirty = false; - } - else if(endvert < startvert) count += endvert; - - const decalvert *ptr = 0; - gle::vertexpointer(sizeof(decalvert), ptr->pos.v); - gle::texcoord0pointer(sizeof(decalvert), ptr->tc.v); - gle::colorpointer(sizeof(decalvert), ptr->color.v); - - glDrawArrays(GL_TRIANGLES, 0, count); - xtravertsva += count; - - if(flags&(DF_ADD|DF_INVMOD)) resetfogcolor(); - - extern int intel_vertexarray_bug; - if(intel_vertexarray_bug) glFlush(); - } - - decalinfo &newdecal() - { - decalinfo &d = decals[enddecal]; - int next = enddecal + 1; - if(next>=maxdecals) next = 0; - if(next==startdecal) freedecal(); - enddecal = next; - dirty = true; - return d; - } - - ivec bbmin, bbmax; - vec decalcenter, decalnormal, decaltangent, decalbitangent; - float decalradius, decalu, decalv; - bvec4 decalcolor; - - void adddecal(const vec ¢er, const vec &dir, float radius, const bvec &color, int info) - { - if(dir.iszero()) return; - - int bbradius = int(ceil(radius)); - bbmin = ivec(center).sub(bbradius); - bbmax = ivec(center).add(bbradius); - - decalcolor = bvec4(color, 255); - decalcenter = center; - decalradius = radius; - decalnormal = dir; + const char *texname; + int flags, fadeintime, fadeouttime, timetolive; + Texture *tex; + decalinfo *decals; + int maxdecals, startdecal, enddecal; + decalvert *verts; + int maxverts, startvert, endvert, lastvert, availverts; + GLuint vbo; + bool dirty; + + decalrenderer(const char *texname, int flags = 0, int fadeintime = 0, int fadeouttime = 1000, int timetolive = -1) + : texname(texname), flags(flags), + fadeintime(fadeintime), fadeouttime(fadeouttime), timetolive(timetolive), + tex(NULL), + decals(NULL), maxdecals(0), startdecal(0), enddecal(0), + verts(NULL), maxverts(0), startvert(0), endvert(0), lastvert(0), availverts(0), + vbo(0), dirty(false), + decalu(0), decalv(0) + { + } + + ~decalrenderer() + { + DELETEA(decals); + DELETEA(verts); + } + + void init(int tris) + { + if(decals) + { + DELETEA(decals); + maxdecals = startdecal = enddecal = 0; + } + if(verts) + { + DELETEA(verts); + maxverts = startvert = endvert = lastvert = availverts = 0; + } + decals = new decalinfo[tris]; + maxdecals = tris; + tex = textureload(texname, 3); + maxverts = tris*3 + 3; + availverts = maxverts - 3; + verts = new decalvert[maxverts]; + } + + int hasdecals() + { + return enddecal < startdecal ? maxdecals - (startdecal - enddecal) : enddecal - startdecal; + } + + void cleanup() + { + if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } + } + + void cleardecals() + { + startdecal = enddecal = 0; + startvert = endvert = lastvert = 0; + availverts = maxverts - 3; + dirty = true; + } + + int freedecal() + { + if(startdecal==enddecal) return 0; + + decalinfo &d = decals[startdecal]; + startdecal++; + if(startdecal >= maxdecals) startdecal = 0; + + int removed = d.endvert < d.startvert ? maxverts - (d.startvert - d.endvert) : d.endvert - d.startvert; + startvert = d.endvert; + if(startvert==endvert) startvert = endvert = lastvert = 0; + availverts += removed; + + return removed; + } + + void fadedecal(decalinfo &d, uchar alpha) + { + bvec rgb; + if(flags&DF_OVERBRIGHT) rgb = bvec(128, 128, 128); + else + { + rgb = d.color; + if(flags&(DF_ADD|DF_INVMOD)) rgb.scale(alpha, 255); + } + bvec4 color(rgb, alpha); + + decalvert *vert = &verts[d.startvert], + *end = &verts[d.endvert < d.startvert ? maxverts : d.endvert]; + while(vert < end) + { + vert->color = color; + vert++; + } + if(d.endvert < d.startvert) + { + vert = verts; + end = &verts[d.endvert]; + while(vert < end) + { + vert->color = color; + vert++; + } + } + dirty = true; + } + + void clearfadeddecals() + { + int threshold = lastmillis - (timetolive>=0 ? timetolive : decalfade) - fadeouttime; + decalinfo *d = &decals[startdecal], + *end = &decals[enddecal < startdecal ? maxdecals : enddecal]; + while(d < end && d->millis <= threshold) d++; + if(d >= end && enddecal < startdecal) + { + d = decals; + end = &decals[enddecal]; + while(d < end && d->millis <= threshold) d++; + } + int prevstart = startdecal; + startdecal = d - decals; + if(prevstart == startdecal) return; + if(startdecal!=enddecal) startvert = decals[startdecal].startvert; + else startvert = endvert = lastvert = 0; + availverts = endvert < startvert ? startvert - endvert - 3 : maxverts - 3 - (endvert - startvert); + dirty = true; + } + + void fadeindecals() + { + if(!fadeintime) return; + decalinfo *d = &decals[enddecal], + *end = &decals[enddecal < startdecal ? 0 : startdecal]; + while(d > end) + { + d--; + int fade = lastmillis - d->millis; + if(fade >= fadeintime) return; + fadedecal(*d, (fade<<8)/fadeintime); + } + if(enddecal < startdecal) + { + d = &decals[maxdecals]; + end = &decals[startdecal]; + while(d > end) + { + d--; + int fade = lastmillis - d->millis; + if(fade >= fadeintime) return; + fadedecal(*d, (fade<<8)/fadeintime); + } + } + } + + void fadeoutdecals() + { + decalinfo *d = &decals[startdecal], + *end = &decals[enddecal < startdecal ? maxdecals : enddecal]; + int offset = (timetolive>=0 ? timetolive : decalfade) + fadeouttime - lastmillis; + while(d < end) + { + int fade = d->millis + offset; + if(fade >= fadeouttime) return; + fadedecal(*d, (fade<<8)/fadeouttime); + d++; + } + if(enddecal < startdecal) + { + d = decals; + end = &decals[enddecal]; + while(d < end) + { + int fade = d->millis + offset; + if(fade >= fadeouttime) return; + fadedecal(*d, (fade<<8)/fadeouttime); + d++; + } + } + } + + static void setuprenderstate() + { + enablepolygonoffset(GL_POLYGON_OFFSET_FILL); + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + + gle::enablevertex(); + gle::enabletexcoord0(); + gle::enablecolor(); + } + + static void cleanuprenderstate() + { + gle::clearvbo(); + + gle::disablevertex(); + gle::disabletexcoord0(); + gle::disablecolor(); + + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + + disablepolygonoffset(GL_POLYGON_OFFSET_FILL); + } + + void render() + { + if(startvert==endvert) return; + + if(flags&DF_OVERBRIGHT) + { + glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); + SETSHADER(overbrightdecal); + } + else + { + if(flags&DF_INVMOD) { glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); zerofogcolor(); } + else if(flags&DF_ADD) { glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); zerofogcolor(); } + else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if(flags&DF_SATURATE) SETSHADER(saturatedecal); + else foggedshader->set(); + } + + glBindTexture(GL_TEXTURE_2D, tex->id); + + if(!vbo) { glGenBuffers_(1, &vbo); dirty = true; } + gle::bindvbo(vbo); + + int count = endvert < startvert ? maxverts - startvert : endvert - startvert; + if(dirty) + { + glBufferData_(GL_ARRAY_BUFFER, maxverts*sizeof(decalvert), NULL, GL_STREAM_DRAW); + glBufferSubData_(GL_ARRAY_BUFFER, 0, count*sizeof(decalvert), &verts[startvert]); + if(endvert < startvert) + { + glBufferSubData_(GL_ARRAY_BUFFER, count*sizeof(decalvert), endvert*sizeof(decalvert), verts); + count += endvert; + } + dirty = false; + } + else if(endvert < startvert) count += endvert; + + const decalvert *ptr = 0; + gle::vertexpointer(sizeof(decalvert), ptr->pos.v); + gle::texcoord0pointer(sizeof(decalvert), ptr->tc.v); + gle::colorpointer(sizeof(decalvert), ptr->color.v); + + glDrawArrays(GL_TRIANGLES, 0, count); + xtravertsva += count; + + if(flags&(DF_ADD|DF_INVMOD)) resetfogcolor(); + + extern int intel_vertexarray_bug; + if(intel_vertexarray_bug) glFlush(); + } + + decalinfo &newdecal() + { + decalinfo &d = decals[enddecal]; + int next = enddecal + 1; + if(next>=maxdecals) next = 0; + if(next==startdecal) freedecal(); + enddecal = next; + dirty = true; + return d; + } + + ivec bbmin, bbmax; + vec decalcenter, decalnormal, decaltangent, decalbitangent; + float decalradius, decalu, decalv; + bvec4 decalcolor; + + void adddecal(const vec ¢er, const vec &dir, float radius, const bvec &color, int info) + { + if(dir.iszero()) return; + + int bbradius = int(ceil(radius)); + bbmin = ivec(center).sub(bbradius); + bbmax = ivec(center).add(bbradius); + + decalcolor = bvec4(color, 255); + decalcenter = center; + decalradius = radius; + decalnormal = dir; #if 0 - decaltangent.orthogonal(dir); + decaltangent.orthogonal(dir); #else - decaltangent = vec(dir.z, -dir.x, dir.y); - decaltangent.sub(vec(dir).mul(decaltangent.dot(dir))); + decaltangent = vec(dir.z, -dir.x, dir.y); + decaltangent.sub(vec(dir).mul(decaltangent.dot(dir))); #endif - if(flags&DF_ROTATE) decaltangent.rotate(rnd(360)*RAD, dir); - decaltangent.normalize(); - decalbitangent.cross(decaltangent, dir); - if(flags&DF_RND4) - { - decalu = 0.5f*(info&1); - decalv = 0.5f*((info>>1)&1); - } - - lastvert = endvert; - gentris(worldroot, ivec(0, 0, 0), worldsize>>1); - if(dbgdec) - { - int nverts = endvert < lastvert ? endvert + maxverts - lastvert : endvert - lastvert; - conoutf(CON_DEBUG, "tris = %d, verts = %d, total tris = %d", nverts/3, nverts, (maxverts - 3 - availverts)/3); - } - if(endvert==lastvert) return; - - decalinfo &d = newdecal(); - d.color = color; - d.millis = lastmillis; - d.startvert = lastvert; - d.endvert = endvert; - } - - static int clip(const vec *in, int numin, const vec &dir, float below, float above, vec *out) - { - int numout = 0; - const vec *p = &in[numin-1]; - float pc = dir.dot(*p); - loopi(numin) - { - const vec &v = in[i]; - float c = dir.dot(v); - if(c < below) - { - if(pc > above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); - if(pc > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); - } - else if(c > above) - { - if(pc < below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); - if(pc < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); - } - else - { - if(pc < below) - { - if(c > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); - } - else if(pc > above && c < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); - out[numout++] = v; - } - p = &v; - pc = c; - } - return numout; - } - - void gentris(cube &cu, int orient, const ivec &o, int size, materialsurface *mat = NULL, int vismask = 0) - { - vec pos[MAXFACEVERTS+4] = { vec(0, 0, 0) }; - int numverts = 0, numplanes = 1; - vec planes[2] = { vec(0, 0, 0) }; - if(mat) - { - planes[0] = vec(0, 0, 0); - switch(orient) - { - #define GENFACEORIENT(orient, v0, v1, v2, v3) \ - case orient: \ - planes[0][dimension(orient)] = dimcoord(orient) ? 1 : -1; \ - v0 v1 v2 v3 \ - break; - #define GENFACEVERT(orient, vert, x,y,z, xv,yv,zv) \ - pos[numverts++] = vec(x xv, y yv, z zv); - GENFACEVERTS(o.x, o.x, o.y, o.y, o.z, o.z, , + mat->csize, , + mat->rsize, + 0.1f, - 0.1f); - #undef GENFACEORIENT - #undef GENFACEVERT - } - } - else if(cu.texture[orient] == DEFAULT_SKY) return; - else if(cu.ext && (numverts = cu.ext->surfaces[orient].numverts&MAXFACEVERTS)) - { - vertinfo *verts = cu.ext->verts() + cu.ext->surfaces[orient].verts; - ivec vo = ivec(o).mask(~0xFFF).shl(3); - loopj(numverts) pos[j] = vec(verts[j].getxyz().add(vo)).mul(1/8.0f); - planes[0].cross(pos[0], pos[1], pos[2]).normalize(); - if(numverts >= 4 && !(cu.merged&(1<>1)&1); + } + + lastvert = endvert; + gentris(worldroot, ivec(0, 0, 0), worldsize>>1); + if(endvert==lastvert) return; + + decalinfo &d = newdecal(); + d.color = color; + d.millis = lastmillis; + d.startvert = lastvert; + d.endvert = endvert; + } + + static int clip(const vec *in, int numin, const vec &dir, float below, float above, vec *out) + { + int numout = 0; + const vec *p = &in[numin-1]; + float pc = dir.dot(*p); + loopi(numin) + { + const vec &v = in[i]; + float c = dir.dot(v); + if(c < below) + { + if(pc > above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); + if(pc > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); + } + else if(c > above) + { + if(pc < below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); + if(pc < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); + } + else + { + if(pc < below) + { + if(c > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); + } + else if(pc > above && c < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); + out[numout++] = v; + } + p = &v; + pc = c; + } + return numout; + } + + void gentris(cube &cu, int orient, const ivec &o, int size, materialsurface *mat = NULL, int vismask = 0) + { + vec pos[MAXFACEVERTS+4] = { vec(0, 0, 0) }; + int numverts = 0, numplanes = 1; + vec planes[2] = { vec(0, 0, 0) }; + if(mat) + { + planes[0] = vec(0, 0, 0); + switch(orient) + { + #define GENFACEORIENT(orient, v0, v1, v2, v3) \ + case orient: \ + planes[0][dimension(orient)] = dimcoord(orient) ? 1 : -1; \ + v0 v1 v2 v3 \ + break; + #define GENFACEVERT(orient, vert, x,y,z, xv,yv,zv) \ + pos[numverts++] = vec(x xv, y yv, z zv); + GENFACEVERTS(o.x, o.x, o.y, o.y, o.z, o.z, , + mat->csize, , + mat->rsize, + 0.1f, - 0.1f); + #undef GENFACEORIENT + #undef GENFACEVERT + } + } + else if(cu.texture[orient] == DEFAULT_SKY) return; + else if(cu.ext && (numverts = cu.ext->surfaces[orient].numverts&MAXFACEVERTS)) + { + vertinfo *verts = cu.ext->verts() + cu.ext->surfaces[orient].verts; + ivec vo = ivec(o).mask(~0xFFF).shl(3); + loopj(numverts) pos[j] = vec(verts[j].getxyz().add(vo)).mul(1/8.0f); + planes[0].cross(pos[0], pos[1], pos[2]).normalize(); + if(numverts >= 4 && !(cu.merged&(1< decalradius) continue; - vec pcenter = vec(decalnormal).mul(dist).add(decalcenter); + // intersect ray along decal normal with plane + float dist = n.dot(p) / facing; + if(fabs(dist) > decalradius) continue; + vec pcenter = vec(decalnormal).mul(dist).add(decalcenter); #else - // travel back along plane normal from the decal center - float dist = n.dot(p); - if(fabs(dist) > decalradius) continue; - vec pcenter = vec(n).mul(dist).add(decalcenter); + // travel back along plane normal from the decal center + float dist = n.dot(p); + if(fabs(dist) > decalradius) continue; + vec pcenter = vec(n).mul(dist).add(decalcenter); #endif - vec ft, fb; - ft.orthogonal(n); - ft.normalize(); - fb.cross(ft, n); - vec pt = vec(ft).mul(ft.dot(decaltangent)).add(vec(fb).mul(fb.dot(decaltangent))).normalize(), - pb = vec(ft).mul(ft.dot(decalbitangent)).add(vec(fb).mul(fb.dot(decalbitangent))).normalize(); - // orthonormalize projected bitangent to prevent streaking - pb.sub(vec(pt).mul(pt.dot(pb))).normalize(); - vec v1[MAXFACEVERTS+4], v2[MAXFACEVERTS+4]; - float ptc = pt.dot(pcenter), pbc = pb.dot(pcenter); - int numv; - if(numplanes >= 2) - { - if(l) { pos[1] = pos[2]; pos[2] = pos[3]; } - numv = clip(pos, 3, pt, ptc - decalradius, ptc + decalradius, v1); - if(numv<3) continue; - } - else - { - numv = clip(pos, numverts, pt, ptc - decalradius, ptc + decalradius, v1); - if(numv<3) continue; - } - numv = clip(v1, numv, pb, pbc - decalradius, pbc + decalradius, v2); - if(numv<3) continue; - float tsz = flags&DF_RND4 ? 0.5f : 1.0f, scale = tsz*0.5f/decalradius, - tu = decalu + tsz*0.5f - ptc*scale, tv = decalv + tsz*0.5f - pbc*scale; - pt.mul(scale); pb.mul(scale); - decalvert dv1 = { v2[0], decalcolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) }, - dv2 = { v2[1], decalcolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) }; - int totalverts = 3*(numv-2); - if(totalverts > maxverts-3) return; - while(availverts < totalverts) - { - if(!freedecal()) return; - } - availverts -= totalverts; - loopk(numv-2) - { - verts[endvert++] = dv1; - verts[endvert++] = dv2; - dv2.pos = v2[k+2]; - dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv); - verts[endvert++] = dv2; - if(endvert>=maxverts) endvert = 0; - } - } - } - - void findmaterials(vtxarray *va) - { - materialsurface *matbuf = va->matbuf; - int matsurfs = va->matsurfs; - loopi(matsurfs) - { - materialsurface &m = matbuf[i]; - if(!isclipped(m.material&MATF_VOLUME)) { i += m.skip; continue; } - int dim = dimension(m.orient), dc = dimcoord(m.orient); - if(dc ? decalnormal[dim] <= 0 : decalnormal[dim] >= 0) { i += m.skip; continue; } - int c = C[dim], r = R[dim]; - for(;;) - { - materialsurface &m = matbuf[i]; - if(m.o[dim] >= bbmin[dim] && m.o[dim] <= bbmax[dim] && - m.o[c] + m.csize >= bbmin[c] && m.o[c] <= bbmax[c] && - m.o[r] + m.rsize >= bbmin[r] && m.o[r] <= bbmax[r]) - { - static cube dummy; - gentris(dummy, m.orient, m.o, max(m.csize, m.rsize), &m); - } - if(i+1 >= matsurfs) break; - materialsurface &n = matbuf[i+1]; - if(n.material != m.material || n.orient != m.orient) break; - i++; - } - } - } - - void findescaped(cube *cu, const ivec &o, int size, int escaped) - { - loopi(8) - { - if(escaped&(1<>1, cu[i].escaped); - else - { - int vismask = cu[i].merged; - if(vismask) loopj(6) if(vismask&(1<va && cu[i].ext->va->matsurfs) - findmaterials(cu[i].ext->va); - if(cu[i].children) gentris(cu[i].children, co, size>>1, cu[i].escaped); - else - { - int vismask = cu[i].visible; - if(vismask&0xC0) - { - if(vismask&0x80) loopj(6) gentris(cu[i], j, co, size, NULL, vismask); - else loopj(6) if(vismask&(1<>1, cu[i].escaped); - else - { - int vismask = cu[i].merged; - if(vismask) loopj(6) if(vismask&(1<= 2) + { + if(l) { pos[1] = pos[2]; pos[2] = pos[3]; } + numv = clip(pos, 3, pt, ptc - decalradius, ptc + decalradius, v1); + if(numv<3) continue; + } + else + { + numv = clip(pos, numverts, pt, ptc - decalradius, ptc + decalradius, v1); + if(numv<3) continue; + } + numv = clip(v1, numv, pb, pbc - decalradius, pbc + decalradius, v2); + if(numv<3) continue; + float tsz = flags&DF_RND4 ? 0.5f : 1.0f, scale = tsz*0.5f/decalradius, + tu = decalu + tsz*0.5f - ptc*scale, tv = decalv + tsz*0.5f - pbc*scale; + pt.mul(scale); pb.mul(scale); + decalvert dv1 = { v2[0], decalcolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) }, + dv2 = { v2[1], decalcolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) }; + int totalverts = 3*(numv-2); + if(totalverts > maxverts-3) return; + while(availverts < totalverts) + { + if(!freedecal()) return; + } + availverts -= totalverts; + loopk(numv-2) + { + verts[endvert++] = dv1; + verts[endvert++] = dv2; + dv2.pos = v2[k+2]; + dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv); + verts[endvert++] = dv2; + if(endvert>=maxverts) endvert = 0; + } + } + } + + void findmaterials(vtxarray *va) + { + materialsurface *matbuf = va->matbuf; + int matsurfs = va->matsurfs; + loopi(matsurfs) + { + materialsurface &m = matbuf[i]; + if(!isclipped(m.material&MATF_VOLUME)) { i += m.skip; continue; } + int dim = dimension(m.orient), dc = dimcoord(m.orient); + if(dc ? decalnormal[dim] <= 0 : decalnormal[dim] >= 0) { i += m.skip; continue; } + int c = C[dim], r = R[dim]; + for(;;) + { + materialsurface &m = matbuf[i]; + if(m.o[dim] >= bbmin[dim] && m.o[dim] <= bbmax[dim] && + m.o[c] + m.csize >= bbmin[c] && m.o[c] <= bbmax[c] && + m.o[r] + m.rsize >= bbmin[r] && m.o[r] <= bbmax[r]) + { + static cube dummy; + gentris(dummy, m.orient, m.o, max(m.csize, m.rsize), &m); + } + if(i+1 >= matsurfs) break; + materialsurface &n = matbuf[i+1]; + if(n.material != m.material || n.orient != m.orient) break; + i++; + } + } + } + + void findescaped(cube *cu, const ivec &o, int size, int escaped) + { + loopi(8) + { + if(escaped&(1<>1, cu[i].escaped); + else + { + int vismask = cu[i].merged; + if(vismask) loopj(6) if(vismask&(1<va && cu[i].ext->va->matsurfs) + findmaterials(cu[i].ext->va); + if(cu[i].children) gentris(cu[i].children, co, size>>1, cu[i].escaped); + else + { + int vismask = cu[i].visible; + if(vismask&0xC0) + { + if(vismask&0x80) loopj(6) gentris(cu[i], j, co, size, NULL, vismask); + else loopj(6) if(vismask&(1<>1, cu[i].escaped); + else + { + int vismask = cu[i].merged; + if(vismask) loopj(6) if(vismask&(1<packages/particles/scorch.png", DF_ROTATE, 500), - decalrenderer("packages/particles/blood.png", DF_RND4|DF_ROTATE|DF_INVMOD), - decalrenderer("packages/particles/bullet.png", DF_OVERBRIGHT) + decalrenderer("packages/particles/scorch.png", DF_ROTATE, 500), + decalrenderer("packages/particles/blood.png", DF_RND4|DF_ROTATE|DF_INVMOD), + decalrenderer("packages/particles/bullet.png", DF_OVERBRIGHT) }; void initdecals() { - loopi(sizeof(decals)/sizeof(decals[0])) decals[i].init(maxdecaltris); + loopi(sizeof(decals)/sizeof(decals[0])) decals[i].init(maxdecaltris); } void cleardecals() { - loopi(sizeof(decals)/sizeof(decals[0])) decals[i].cleardecals(); + loopi(sizeof(decals)/sizeof(decals[0])) decals[i].cleardecals(); } void cleanupdecals() { - loopi(sizeof(decals)/sizeof(decals[0])) decals[i].cleanup(); + loopi(sizeof(decals)/sizeof(decals[0])) decals[i].cleanup(); } VARNP(decals, showdecals, 0, 1, 1); void renderdecals(bool mainpass) { - bool rendered = false; - loopi(sizeof(decals)/sizeof(decals[0])) - { - decalrenderer &d = decals[i]; - if(mainpass) - { - d.clearfadeddecals(); - d.fadeindecals(); - d.fadeoutdecals(); - } - if(!showdecals || !d.hasdecals()) continue; - if(!rendered) - { - rendered = true; - decalrenderer::setuprenderstate(); - } - d.render(); - } - if(!rendered) return; - decalrenderer::cleanuprenderstate(); + bool rendered = false; + loopi(sizeof(decals)/sizeof(decals[0])) + { + decalrenderer &d = decals[i]; + if(mainpass) + { + d.clearfadeddecals(); + d.fadeindecals(); + d.fadeoutdecals(); + } + if(!showdecals || !d.hasdecals()) continue; + if(!rendered) + { + rendered = true; + decalrenderer::setuprenderstate(); + } + d.render(); + } + if(!rendered) return; + decalrenderer::cleanuprenderstate(); } VARP(maxdecaldistance, 1, 512, 10000); void adddecal(int type, const vec ¢er, const vec &surface, float radius, const bvec &color, int info) { - if(!showdecals || type<0 || (size_t)type>=sizeof(decals)/sizeof(decals[0]) || center.dist(camera1->o) - radius > maxdecaldistance) return; - decalrenderer &d = decals[type]; - d.adddecal(center, surface, radius, color, info); + if(!showdecals || type<0 || (size_t)type>=sizeof(decals)/sizeof(decals[0]) || center.dist(camera1->o) - radius > maxdecaldistance) return; + decalrenderer &d = decals[type]; + d.adddecal(center, surface, radius, color, info); } diff --git a/src/engine/depthfx.h b/src/engine/depthfx.h index 8332ab5..d313b27 100644 --- a/src/engine/depthfx.h +++ b/src/engine/depthfx.h @@ -25,150 +25,150 @@ vec depthfxmin(1e16f, 1e16f, 1e16f), depthfxmax(1e16f, 1e16f, 1e16f); static struct depthfxtexture : rendertarget { - const GLenum *colorformats() const - { - static const GLenum colorfmts[] = { GL_RG16F, GL_RGB16F, GL_RGBA, GL_RGBA8, GL_RGB, GL_RGB8, GL_FALSE }; - return &colorfmts[fpdepthfx && hasTF ? (hasTRG ? 0 : 1) : 2]; - } - - float eyedepth(const vec &p) const - { - return max(-cammatrix.transform(p).z, 0.0f); - } - - void addscissorvert(const vec &v, float &sx1, float &sy1, float &sx2, float &sy2) - { - vec p = camprojmatrix.perspectivetransform(v); - sx1 = min(sx1, p.x); - sy1 = min(sy1, p.y); - sx2 = max(sx2, p.x); - sy2 = max(sy2, p.y); - } - - bool addscissorbox(const vec ¢er, float size) - { - float sx1, sy1, sx2, sy2; - calcspherescissor(center, size, sx1, sy1, sx2, sy2); - return addblurtiles(sx1, sy1, sx2, sy2); - } - - bool addscissorbox(const vec &bbmin, const vec &bbmax) - { - float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1; - loopi(8) - { - vec v(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, i&4 ? bbmax.z : bbmin.z); - addscissorvert(v, sx1, sy1, sx2, sy2); - } - return addblurtiles(sx1, sy1, sx2, sy2); - } - - bool screenrect() const { return true; } - bool filter() const { return blurdepthfx!=0; } - bool highprecision() const { return colorfmt==GL_RG16F || colorfmt==GL_RGB16F; } - bool emulatehighprecision() const { return depthfxemuprecision && !blurdepthfx; } - - bool shouldrender() - { - extern void finddepthfxranges(); - finddepthfxranges(); - return (numdepthfxranges && scissorx1 < scissorx2 && scissory1 < scissory2); - } - - bool dorender() - { - glClearColor(1, 1, 1, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - depthfxing = true; - refracting = -1; - - extern void renderdepthobstacles(const vec &bbmin, const vec &bbmax, float scale, float *ranges, int numranges); - float scale = depthfxscale; - float *ranges = depthfxranges; - int numranges = numdepthfxranges; - if(highprecision()) - { - scale = depthfxfpscale; - ranges = NULL; - numranges = 0; - } - else if(emulatehighprecision()) - { - scale = depthfxfpscale; - ranges = NULL; - numranges = -3; - } - renderdepthobstacles(depthfxmin, depthfxmax, scale, ranges, numranges); - - refracting = 0; - depthfxing = false; - - return numdepthfxranges > 0; - } + const GLenum *colorformats() const + { + static const GLenum colorfmts[] = { GL_RG16F, GL_RGB16F, GL_RGBA, GL_RGBA8, GL_RGB, GL_RGB8, GL_FALSE }; + return &colorfmts[fpdepthfx && hasTF ? (hasTRG ? 0 : 1) : 2]; + } + + float eyedepth(const vec &p) const + { + return max(-cammatrix.transform(p).z, 0.0f); + } + + void addscissorvert(const vec &v, float &sx1, float &sy1, float &sx2, float &sy2) + { + vec p = camprojmatrix.perspectivetransform(v); + sx1 = min(sx1, p.x); + sy1 = min(sy1, p.y); + sx2 = max(sx2, p.x); + sy2 = max(sy2, p.y); + } + + bool addscissorbox(const vec ¢er, float size) + { + float sx1, sy1, sx2, sy2; + calcspherescissor(center, size, sx1, sy1, sx2, sy2); + return addblurtiles(sx1, sy1, sx2, sy2); + } + + bool addscissorbox(const vec &bbmin, const vec &bbmax) + { + float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1; + loopi(8) + { + vec v(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, i&4 ? bbmax.z : bbmin.z); + addscissorvert(v, sx1, sy1, sx2, sy2); + } + return addblurtiles(sx1, sy1, sx2, sy2); + } + + bool screenrect() const { return true; } + bool filter() const { return blurdepthfx!=0; } + bool highprecision() const { return colorfmt==GL_RG16F || colorfmt==GL_RGB16F; } + bool emulatehighprecision() const { return depthfxemuprecision && !blurdepthfx; } + + bool shouldrender() + { + extern void finddepthfxranges(); + finddepthfxranges(); + return (numdepthfxranges && scissorx1 < scissorx2 && scissory1 < scissory2); + } + + bool dorender() + { + glClearColor(1, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + depthfxing = true; + refracting = -1; + + extern void renderdepthobstacles(const vec &bbmin, const vec &bbmax, float scale, float *ranges, int numranges); + float scale = depthfxscale; + float *ranges = depthfxranges; + int numranges = numdepthfxranges; + if(highprecision()) + { + scale = depthfxfpscale; + ranges = NULL; + numranges = 0; + } + else if(emulatehighprecision()) + { + scale = depthfxfpscale; + ranges = NULL; + numranges = -3; + } + renderdepthobstacles(depthfxmin, depthfxmax, scale, ranges, numranges); + + refracting = 0; + depthfxing = false; + + return numdepthfxranges > 0; + } } depthfxtex; void cleanupdepthfx() { - depthfxtex.cleanup(true); + depthfxtex.cleanup(true); } bool depthfxing = false; bool binddepthfxtex() { - if(!reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0) - { - glActiveTexture_(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, depthfxtex.rendertex); - glActiveTexture_(GL_TEXTURE0); - return true; - } - return false; + if(!reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0) + { + glActiveTexture_(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, depthfxtex.rendertex); + glActiveTexture_(GL_TEXTURE0); + return true; + } + return false; } void binddepthfxparams(float blend, float minblend = 0, bool allow = true, void *owner = NULL) { - if(!reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0) - { - float scale = 0, offset = -1, texscale = 0; - if(!depthfxtex.highprecision()) - { - float select[4] = { 0, 0, 0, 0 }; - if(!depthfxtex.emulatehighprecision()) - { - loopi(numdepthfxranges) if(depthfxowners[i]==owner) - { - select[i] = float(depthfxscale)/blend; - scale = 1.0f/blend; - offset = -float(depthfxranges[i] - depthfxbias)/blend; - break; - } - } - else if(allow) - { - select[0] = float(depthfxfpscale)/blend; - select[1] = select[0]/256; - select[2] = select[1]/256; - scale = 1.0f/blend; - offset = 0; - } - LOCALPARAMF(depthfxselect, select[0], select[1], select[2], select[3]); - } - else if(allow) - { - scale = 1.0f/blend; - offset = 0; - texscale = float(depthfxfpscale)/blend; - } - LOCALPARAMF(depthfxparams, scale, offset, texscale, minblend); - } + if(!reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0) + { + float scale = 0, offset = -1, texscale = 0; + if(!depthfxtex.highprecision()) + { + float select[4] = { 0, 0, 0, 0 }; + if(!depthfxtex.emulatehighprecision()) + { + loopi(numdepthfxranges) if(depthfxowners[i]==owner) + { + select[i] = float(depthfxscale)/blend; + scale = 1.0f/blend; + offset = -float(depthfxranges[i] - depthfxbias)/blend; + break; + } + } + else if(allow) + { + select[0] = float(depthfxfpscale)/blend; + select[1] = select[0]/256; + select[2] = select[1]/256; + scale = 1.0f/blend; + offset = 0; + } + LOCALPARAMF(depthfxselect, select[0], select[1], select[2], select[3]); + } + else if(allow) + { + scale = 1.0f/blend; + offset = 0; + texscale = float(depthfxfpscale)/blend; + } + LOCALPARAMF(depthfxparams, scale, offset, texscale, minblend); + } } void drawdepthfxtex() { - if(!depthfx) return; + if(!depthfx) return; - depthfxtex.render(1< 0) - { - int remaining = expire - lastmillis; - if(flags&DL_EXPAND) - curradius = initradius + (radius - initradius) * (1.0f - remaining/float(fade + peak)); - else if(!(flags&DL_FLASH) && remaining > fade) - curradius = initradius + (radius - initradius) * (1.0f - float(remaining - fade)/peak); - else if(flags&DL_SHRINK) - curradius = (radius*remaining)/fade; - else curradius = radius; - } - else curradius = radius; - } - - void calccolor() - { - if(flags&DL_FLASH || peak <= 0) curcolor = color; - else - { - int peaking = expire - lastmillis - fade; - if(peaking <= 0) curcolor = color; - else curcolor.lerp(initcolor, color, 1.0f - float(peaking)/peak); - } - - float intensity = 1.0f; - if(fade > 0) - { - int fading = expire - lastmillis; - if(fading < fade) intensity = float(fading)/fade; - } - curcolor.mul(intensity); - // KLUGE: this prevents nvidia drivers from trying to recompile dynlight fragment programs - loopk(3) if(fmod(curcolor[k], 1.0f/256) < 0.001f) curcolor[k] += 0.001f; - } + vec o, hud; + float radius, initradius, curradius, dist; + vec color, initcolor, curcolor; + int fade, peak, expire, flags; + physent *owner; + + void calcradius() + { + if(fade + peak > 0) + { + int remaining = expire - lastmillis; + if(flags&DL_EXPAND) + curradius = initradius + (radius - initradius) * (1.0f - remaining/float(fade + peak)); + else if(!(flags&DL_FLASH) && remaining > fade) + curradius = initradius + (radius - initradius) * (1.0f - float(remaining - fade)/peak); + else if(flags&DL_SHRINK) + curradius = (radius*remaining)/fade; + else curradius = radius; + } + else curradius = radius; + } + + void calccolor() + { + if(flags&DL_FLASH || peak <= 0) curcolor = color; + else + { + int peaking = expire - lastmillis - fade; + if(peaking <= 0) curcolor = color; + else curcolor.lerp(initcolor, color, 1.0f - float(peaking)/peak); + } + + float intensity = 1.0f; + if(fade > 0) + { + int fading = expire - lastmillis; + if(fading < fade) intensity = float(fading)/fade; + } + curcolor.mul(intensity); + // KLUGE: this prevents nvidia drivers from trying to recompile dynlight fragment programs + loopk(3) if(fmod(curcolor[k], 1.0f/256) < 0.001f) curcolor[k] += 0.001f; + } }; vector dynlights; @@ -54,174 +54,174 @@ vector closedynlights; void adddynlight(const vec &o, float radius, const vec &color, int fade, int peak, int flags, float initradius, const vec &initcolor, physent *owner) { - if(!maxdynlights) return; - if(o.dist(camera1->o) > dynlightdist || radius <= 0) return; - - int insert = 0, expire = fade + peak + lastmillis; - loopvrev(dynlights) if(expire>=dynlights[i].expire) { insert = i+1; break; } - dynlight d; - d.o = d.hud = o; - d.radius = radius; - d.initradius = initradius; - d.color = color; - d.initcolor = initcolor; - d.fade = fade; - d.peak = peak; - d.expire = expire; - d.flags = flags; - d.owner = owner; - dynlights.insert(insert, d); + if(!maxdynlights) return; + if(o.dist(camera1->o) > dynlightdist || radius <= 0) return; + + int insert = 0, expire = fade + peak + lastmillis; + loopvrev(dynlights) if(expire>=dynlights[i].expire) { insert = i+1; break; } + dynlight d; + d.o = d.hud = o; + d.radius = radius; + d.initradius = initradius; + d.color = color; + d.initcolor = initcolor; + d.fade = fade; + d.peak = peak; + d.expire = expire; + d.flags = flags; + d.owner = owner; + dynlights.insert(insert, d); } void cleardynlights() { - int faded = -1; - loopv(dynlights) if(lastmillis0) dynlights.remove(0, faded); + int faded = -1; + loopv(dynlights) if(lastmillis0) dynlights.remove(0, faded); } void removetrackeddynlights(physent *owner) { - loopvrev(dynlights) if(owner ? dynlights[i].owner == owner : dynlights[i].owner != NULL) dynlights.remove(i); + loopvrev(dynlights) if(owner ? dynlights[i].owner == owner : dynlights[i].owner != NULL) dynlights.remove(i); } void updatedynlights() { - cleardynlights(); - game::adddynlights(); - - loopv(dynlights) - { - dynlight &d = dynlights[i]; - if(d.owner) game::dynlighttrack(d.owner, d.o, d.hud); - d.calcradius(); - d.calccolor(); - } + cleardynlights(); + game::adddynlights(); + + loopv(dynlights) + { + dynlight &d = dynlights[i]; + if(d.owner) game::dynlighttrack(d.owner, d.o, d.hud); + d.calcradius(); + d.calccolor(); + } } int finddynlights() { - closedynlights.setsize(0); - if(!maxdynlights) return 0; - physent e; - e.type = ENT_CAMERA; - loopvj(dynlights) - { - dynlight &d = dynlights[j]; - if(d.curradius <= 0) continue; - d.dist = camera1->o.dist(d.o) - d.curradius; - if(d.dist > dynlightdist || isfoggedsphere(d.curradius, d.o)) - continue; - if(reflecting || refracting > 0) - { - if(d.o.z + d.curradius < reflectz) continue; - } - else if(refracting < 0 && d.o.z - d.curradius > reflectz) continue; - e.o = d.o; - e.radius = e.xradius = e.yradius = e.eyeheight = e.aboveeye = d.curradius; - if(!collide(&e, vec(0, 0, 0), 0, false)) continue; - - int insert = 0; - loopvrev(closedynlights) if(d.dist >= closedynlights[i]->dist) { insert = i+1; break; } - closedynlights.insert(insert, &d); - if(closedynlights.length() >= DYNLIGHTMASK) break; - } - return closedynlights.length(); + closedynlights.setsize(0); + if(!maxdynlights) return 0; + physent e; + e.type = ENT_CAMERA; + loopvj(dynlights) + { + dynlight &d = dynlights[j]; + if(d.curradius <= 0) continue; + d.dist = camera1->o.dist(d.o) - d.curradius; + if(d.dist > dynlightdist || isfoggedsphere(d.curradius, d.o)) + continue; + if(reflecting || refracting > 0) + { + if(d.o.z + d.curradius < reflectz) continue; + } + else if(refracting < 0 && d.o.z - d.curradius > reflectz) continue; + e.o = d.o; + e.radius = e.xradius = e.yradius = e.eyeheight = e.aboveeye = d.curradius; + if(!collide(&e, vec(0, 0, 0), 0, false)) continue; + + int insert = 0; + loopvrev(closedynlights) if(d.dist >= closedynlights[i]->dist) { insert = i+1; break; } + closedynlights.insert(insert, &d); + if(closedynlights.length() >= DYNLIGHTMASK) break; + } + return closedynlights.length(); } bool getdynlight(int n, vec &o, float &radius, vec &color) { - if(!closedynlights.inrange(n)) return false; - dynlight &d = *closedynlights[n]; - o = d.o; - radius = d.curradius; - color = d.curcolor; - return true; + if(!closedynlights.inrange(n)) return false; + dynlight &d = *closedynlights[n]; + o = d.o; + radius = d.curradius; + color = d.curcolor; + return true; } void dynlightreaching(const vec &target, vec &color, vec &dir, bool hud) { - vec dyncolor(0, 0, 0);//, dyndir(0, 0, 0); - loopv(dynlights) - { - dynlight &d = dynlights[i]; - if(d.curradius<=0) continue; - - vec ray(hud ? d.hud : d.o); - ray.sub(target); - float mag = ray.squaredlen(); - if(mag >= d.curradius*d.curradius) continue; - - vec color = d.curcolor; - color.mul(1 - sqrtf(mag)/d.curradius); - dyncolor.add(color); - //dyndir.add(ray.mul(intensity/mag)); - } + vec dyncolor(0, 0, 0);//, dyndir(0, 0, 0); + loopv(dynlights) + { + dynlight &d = dynlights[i]; + if(d.curradius<=0) continue; + + vec ray(hud ? d.hud : d.o); + ray.sub(target); + float mag = ray.squaredlen(); + if(mag >= d.curradius*d.curradius) continue; + + vec color = d.curcolor; + color.mul(1 - sqrtf(mag)/d.curradius); + dyncolor.add(color); + //dyndir.add(ray.mul(intensity/mag)); + } #if 0 - if(!dyndir.iszero()) - { - dyndir.normalize(); - float x = dyncolor.magnitude(), y = color.magnitude(); - if(x+y>0) - { - dir.mul(x); - dyndir.mul(y); - dir.add(dyndir).div(x+y); - if(dir.iszero()) dir = vec(0, 0, 1); - else dir.normalize(); - } - } + if(!dyndir.iszero()) + { + dyndir.normalize(); + float x = dyncolor.magnitude(), y = color.magnitude(); + if(x+y>0) + { + dir.mul(x); + dyndir.mul(y); + dir.add(dyndir).div(x+y); + if(dir.iszero()) dir = vec(0, 0, 1); + else dir.normalize(); + } + } #endif - color.add(dyncolor); + color.add(dyncolor); } void calcdynlightmask(vtxarray *va) { - uint mask = 0; - int offset = 0; - loopv(closedynlights) - { - dynlight &d = *closedynlights[i]; - if(d.o.dist_to_bb(va->geommin, va->geommax) >= d.curradius) continue; - - mask |= (i+1)<= maxdynlights*DYNLIGHTBITS) break; - } - va->dynlightmask = mask; + uint mask = 0; + int offset = 0; + loopv(closedynlights) + { + dynlight &d = *closedynlights[i]; + if(d.o.dist_to_bb(va->geommin, va->geommax) >= d.curradius) continue; + + mask |= (i+1)<= maxdynlights*DYNLIGHTBITS) break; + } + va->dynlightmask = mask; } int setdynlights(vtxarray *va) { - if(closedynlights.empty() || !va->dynlightmask) return 0; + if(closedynlights.empty() || !va->dynlightmask) return 0; - extern bool minimizedynlighttcusage(); + extern bool minimizedynlighttcusage(); - static vec4 posv[MAXDYNLIGHTS]; - static vec colorv[MAXDYNLIGHTS]; + static vec4 posv[MAXDYNLIGHTS]; + static vec colorv[MAXDYNLIGHTS]; - int index = 0; - for(uint mask = va->dynlightmask; mask; mask >>= DYNLIGHTBITS, index++) - { - dynlight &d = *closedynlights[(mask&DYNLIGHTMASK)-1]; + int index = 0; + for(uint mask = va->dynlightmask; mask; mask >>= DYNLIGHTBITS, index++) + { + dynlight &d = *closedynlights[(mask&DYNLIGHTMASK)-1]; - float scale = 1.0f/d.curradius; - vec origin = vec(d.o).mul(-scale); + float scale = 1.0f/d.curradius; + vec origin = vec(d.o).mul(-scale); - if(index>0 && minimizedynlighttcusage()) - { - scale /= posv[0].w; - origin.sub(vec(posv[0]).mul(scale)); - } + if(index>0 && minimizedynlighttcusage()) + { + scale /= posv[0].w; + origin.sub(vec(posv[0]).mul(scale)); + } - posv[index] = vec4(origin, scale); - colorv[index] = d.curcolor; - } + posv[index] = vec4(origin, scale); + colorv[index] = d.curcolor; + } - GLOBALPARAMV(dynlightpos, posv, index); - GLOBALPARAMV(dynlightcolor, colorv, index); + GLOBALPARAMV(dynlightpos, posv, index); + GLOBALPARAMV(dynlightcolor, colorv, index); - return index; + return index; } diff --git a/src/engine/engine.h b/src/engine/engine.h index a9d70ef..de7e525 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -13,7 +13,7 @@ #include "model.h" extern dynent *player; -extern physent *camera1; // special ent that acts as camera, same object as player1 in FPS mode +extern physent *camera1; // special ent that acts as camera, same object as player1 in FPS mode extern int worldscale, worldsize; extern int mapversion; @@ -36,18 +36,18 @@ extern vector entgroup; // rendertext struct font { - struct charinfo - { - short x, y, w, h, offsetx, offsety, advance, tex; - }; - - char *name; - vector texs; - vector chars; - int charoffset, defaultw, defaulth, scale; - - font() : name(NULL) {} - ~font() { DELETEA(name); } + struct charinfo + { + short x, y, w, h, offsetx, offsety, advance, tex; + }; + + char *name; + vector texs; + vector chars; + int charoffset, defaultw, defaulth, scale; + + font() : name(NULL) {} + ~font() { DELETEA(name); } }; #define FONTH (curfont->scale) @@ -131,7 +131,6 @@ extern void gl_init(); extern void gl_resize(); extern void cleanupgl(); extern void rendergame(bool mainpass = false); -extern void invalidatepostfx(); extern void gl_drawhud(); extern void gl_drawframe(); extern void gl_drawmainmenu(); @@ -159,8 +158,8 @@ extern void writecrosshairs(stream *f); namespace modelpreview { - extern void start(int x, int y, int w, int h, bool background = true); - extern void end(); + extern void start(int x, int y, int w, int h, bool background = true); + extern void end(); } // renderextras @@ -217,7 +216,7 @@ extern void mincubeface(const cube &cu, int orient, const ivec &o, int size, con static inline cubeext &ext(cube &c) { - return *(c.ext ? c.ext : newcubeext(c)); + return *(c.ext ? c.ext : newcubeext(c)); } // ents @@ -316,16 +315,16 @@ extern float reflectz; extern int reflectdist, vertwater, waterrefract, waterreflect, waterfade, caustics, waterfallrefract; #define GETMATIDXVAR(name, var, type) \ - type get##name##var(int mat) \ - { \ - switch(mat&MATF_INDEX) \ - { \ - default: case 0: return name##var; \ - case 1: return name##2##var; \ - case 2: return name##3##var; \ - case 3: return name##4##var; \ - } \ - } + type get##name##var(int mat) \ + { \ + switch(mat&MATF_INDEX) \ + { \ + default: case 0: return name##var; \ + case 1: return name##2##var; \ + case 2: return name##3##var; \ + case 3: return name##4##var; \ + } \ + } extern const bvec &getwatercolor(int mat); extern const bvec &getwaterfallcolor(int mat); @@ -408,17 +407,17 @@ extern void writecompletions(stream *f); // main enum { - NOT_INITING = 0, - INIT_GAME, - INIT_LOAD, - INIT_RESET + NOT_INITING = 0, + INIT_GAME, + INIT_LOAD, + INIT_RESET }; extern int initing, numcpus; enum { - CHANGE_GFX = 1<<0, - CHANGE_SOUND = 1<<1 + CHANGE_GFX = 1<<0, + CHANGE_SOUND = 1<<1 }; extern bool initwarning(const char *desc, int level = INIT_RESET, int type = CHANGE_GFX); @@ -485,13 +484,13 @@ extern void preloadusedmapmodels(bool msg = false, bool bih = false); static inline model *loadmapmodel(int n) { - extern vector mapmodels; - if(mapmodels.inrange(n)) - { - model *m = mapmodels[n].m; - return m ? m : loadmodel(NULL, n); - } - return NULL; + extern vector mapmodels; + if(mapmodels.inrange(n)) + { + model *m = mapmodels[n].m; + return m ? m : loadmodel(NULL, n); + } + return NULL; } // renderparticles diff --git a/src/engine/explosion.h b/src/engine/explosion.h index 626d5bf..cda02f4 100644 --- a/src/engine/explosion.h +++ b/src/engine/explosion.h @@ -1,249 +1,249 @@ namespace sphere { - struct vert - { - vec pos; - ushort s, t; - } *verts = NULL; - GLushort *indices = NULL; - int numverts = 0, numindices = 0; - GLuint vbuf = 0, ebuf = 0; - - void init(int slices, int stacks) - { - numverts = (stacks+1)*(slices+1); - verts = new vert[numverts]; - float ds = 1.0f/slices, dt = 1.0f/stacks, t = 1.0f; - loopi(stacks+1) - { - float rho = M_PI*(1-t), s = 0.0f, sinrho = i && i < stacks ? sin(rho) : 0, cosrho = !i ? 1 : (i < stacks ? cos(rho) : -1); - loopj(slices+1) - { - float theta = j==slices ? 0 : 2*M_PI*s; - vert &v = verts[i*(slices+1) + j]; - v.pos = vec(-sin(theta)*sinrho, cos(theta)*sinrho, cosrho); - v.s = ushort(s*0xFFFF); - v.t = ushort(t*0xFFFF); - s += ds; - } - t -= dt; - } - - numindices = (stacks-1)*slices*3*2; - indices = new ushort[numindices]; - GLushort *curindex = indices; - loopi(stacks) - { - loopk(slices) - { - int j = i%2 ? slices-k-1 : k; - if(i) - { - *curindex++ = i*(slices+1)+j; - *curindex++ = (i+1)*(slices+1)+j; - *curindex++ = i*(slices+1)+j+1; - } - if(i+1 < stacks) - { - *curindex++ = i*(slices+1)+j+1; - *curindex++ = (i+1)*(slices+1)+j; - *curindex++ = (i+1)*(slices+1)+j+1; - } - } - } - - if(!vbuf) glGenBuffers_(1, &vbuf); - gle::bindvbo(vbuf); - glBufferData_(GL_ARRAY_BUFFER, numverts*sizeof(vert), verts, GL_STATIC_DRAW); - DELETEA(verts); - - if(!ebuf) glGenBuffers_(1, &ebuf); - gle::bindebo(ebuf); - glBufferData_(GL_ELEMENT_ARRAY_BUFFER, numindices*sizeof(GLushort), indices, GL_STATIC_DRAW); - DELETEA(indices); - } - - void enable() - { - if(!vbuf) init(12, 6); - - gle::bindvbo(vbuf); - gle::bindebo(ebuf); - - gle::vertexpointer(sizeof(vert), &verts->pos); - gle::texcoord0pointer(sizeof(vert), &verts->s, GL_UNSIGNED_SHORT, 2, GL_TRUE); - gle::enablevertex(); - gle::enabletexcoord0(); - } - - void draw() - { - glDrawRangeElements_(GL_TRIANGLES, 0, numverts-1, numindices, GL_UNSIGNED_SHORT, indices); - xtraverts += numindices; - glde++; - } - - void disable() - { - gle::disablevertex(); - gle::disabletexcoord0(); - - gle::clearvbo(); - gle::clearebo(); - } - - void cleanup() - { - if(vbuf) { glDeleteBuffers_(1, &vbuf); vbuf = 0; } - if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } - } + struct vert + { + vec pos; + ushort s, t; + } *verts = NULL; + GLushort *indices = NULL; + int numverts = 0, numindices = 0; + GLuint vbuf = 0, ebuf = 0; + + void init(int slices, int stacks) + { + numverts = (stacks+1)*(slices+1); + verts = new vert[numverts]; + float ds = 1.0f/slices, dt = 1.0f/stacks, t = 1.0f; + loopi(stacks+1) + { + float rho = M_PI*(1-t), s = 0.0f, sinrho = i && i < stacks ? sin(rho) : 0, cosrho = !i ? 1 : (i < stacks ? cos(rho) : -1); + loopj(slices+1) + { + float theta = j==slices ? 0 : 2*M_PI*s; + vert &v = verts[i*(slices+1) + j]; + v.pos = vec(-sin(theta)*sinrho, cos(theta)*sinrho, cosrho); + v.s = ushort(s*0xFFFF); + v.t = ushort(t*0xFFFF); + s += ds; + } + t -= dt; + } + + numindices = (stacks-1)*slices*3*2; + indices = new ushort[numindices]; + GLushort *curindex = indices; + loopi(stacks) + { + loopk(slices) + { + int j = i%2 ? slices-k-1 : k; + if(i) + { + *curindex++ = i*(slices+1)+j; + *curindex++ = (i+1)*(slices+1)+j; + *curindex++ = i*(slices+1)+j+1; + } + if(i+1 < stacks) + { + *curindex++ = i*(slices+1)+j+1; + *curindex++ = (i+1)*(slices+1)+j; + *curindex++ = (i+1)*(slices+1)+j+1; + } + } + } + + if(!vbuf) glGenBuffers_(1, &vbuf); + gle::bindvbo(vbuf); + glBufferData_(GL_ARRAY_BUFFER, numverts*sizeof(vert), verts, GL_STATIC_DRAW); + DELETEA(verts); + + if(!ebuf) glGenBuffers_(1, &ebuf); + gle::bindebo(ebuf); + glBufferData_(GL_ELEMENT_ARRAY_BUFFER, numindices*sizeof(GLushort), indices, GL_STATIC_DRAW); + DELETEA(indices); + } + + void enable() + { + if(!vbuf) init(12, 6); + + gle::bindvbo(vbuf); + gle::bindebo(ebuf); + + gle::vertexpointer(sizeof(vert), &verts->pos); + gle::texcoord0pointer(sizeof(vert), &verts->s, GL_UNSIGNED_SHORT, 2, GL_TRUE); + gle::enablevertex(); + gle::enabletexcoord0(); + } + + void draw() + { + glDrawRangeElements_(GL_TRIANGLES, 0, numverts-1, numindices, GL_UNSIGNED_SHORT, indices); + xtraverts += numindices; + glde++; + } + + void disable() + { + gle::disablevertex(); + gle::disabletexcoord0(); + + gle::clearvbo(); + gle::clearebo(); + } + + void cleanup() + { + if(vbuf) { glDeleteBuffers_(1, &vbuf); vbuf = 0; } + if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } + } } static const float WOBBLE = 1.25f; struct fireballrenderer : listrenderer { - fireballrenderer(const char *texname) - : listrenderer(texname, 0, PT_FIREBALL|PT_GLARE|PT_SHADER) - {} - - void startrender() - { - if(glaring) SETSHADER(explosionglare); - else if(!reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0) - { - if(!depthfxtex.highprecision()) SETSHADER(explosionsoft8); - else SETSHADER(explosionsoft); - } - else SETSHADER(explosion); - - sphere::enable(); - } - - void endrender() - { - sphere::disable(); - } - - void cleanup() - { - sphere::cleanup(); - } - - int finddepthfxranges(void **owners, float *ranges, int numranges, int maxranges, vec &bbmin, vec &bbmax) - { - static struct fireballent : physent - { - fireballent() - { - type = ENT_CAMERA; - } - } e; - - for(listparticle *p = list; p; p = p->next) - { - int ts = p->fade <= 5 ? 1 : lastmillis-p->millis; - float pmax = p->val, - size = p->fade ? float(ts)/p->fade : 1, - psize = (p->size + pmax * size)*WOBBLE; - if(2*(p->size + pmax)*WOBBLE < depthfxblend || - (!depthfxtex.highprecision() && !depthfxtex.emulatehighprecision() && psize > depthfxscale - depthfxbias) || - isfoggedsphere(psize, p->o)) continue; - - e.o = p->o; - e.radius = e.xradius = e.yradius = e.eyeheight = e.aboveeye = psize; - if(!::collide(&e, vec(0, 0, 0), 0, false)) continue; - - if(depthfxscissor==2 && !depthfxtex.addscissorbox(p->o, psize)) continue; - - vec dir = camera1->o; - dir.sub(p->o); - float dist = dir.magnitude(); - dir.mul(psize/dist).add(p->o); - float depth = depthfxtex.eyedepth(dir); - - loopk(3) - { - bbmin[k] = min(bbmin[k], p->o[k] - psize); - bbmax[k] = max(bbmax[k], p->o[k] + psize); - } - - int pos = numranges; - loopi(numranges) if(depth < ranges[i]) { pos = i; break; } - if(pos >= maxranges) continue; - - if(numranges > pos) - { - int moved = min(numranges-pos, maxranges-(pos+1)); - memmove(&ranges[pos+1], &ranges[pos], moved*sizeof(float)); - memmove(&owners[pos+1], &owners[pos], moved*sizeof(void *)); - } - if(numranges < maxranges) numranges++; - - ranges[pos] = depth; - owners[pos] = p; - } - - return numranges; - } - - void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) - { - pe.maxfade = max(pe.maxfade, fade); - pe.extendbb(o, (size+1+pe.ent->attr2)*WOBBLE); - } - - void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) - { - float pmax = p->val, - size = p->fade ? float(ts)/p->fade : 1, - psize = p->size + pmax * size; - - if(isfoggedsphere(psize*WOBBLE, p->o)) return; - - vec dir = vec(o).sub(camera1->o), s, t; - float dist = dir.magnitude(); - bool inside = dist <= psize*WOBBLE; - if(inside) - { - s = camright; - t = camup; - } - else - { - if(reflecting) { dir.z = o.z - reflectz; dist = dir.magnitude(); } - float mag2 = dir.magnitude2(); - dir.x /= mag2; - dir.y /= mag2; - dir.z /= dist; - s = vec(dir.y, -dir.x, 0); - t = vec(dir.x*dir.z, dir.y*dir.z, -mag2/dist); - } - - matrix3 rot(lastmillis/1000.0f*143*RAD, vec(1/SQRT3, 1/SQRT3, 1/SQRT3)); - LOCALPARAM(texgenS, rot.transposedtransform(s)); - LOCALPARAM(texgenT, rot.transposedtransform(t)); - - matrix4 m(rot, o); - m.scale(psize, psize, inside ? -psize : psize); - m.mul(camprojmatrix, m); - LOCALPARAM(explosionmatrix, m); - - LOCALPARAM(center, o); - LOCALPARAMF(millis, lastmillis/1000.0f); - LOCALPARAMF(blendparams, inside ? 0.5f : 4, inside ? 0.25f : 0); - binddepthfxparams(depthfxblend, inside ? blend/(2*255.0f) : 0, 2*(p->size + pmax)*WOBBLE >= depthfxblend, p); - - int passes = !reflecting && !refracting && inside ? 2 : 1; - loopi(passes) - { - gle::color(p->color, i ? blend/2 : blend); - if(i) glDepthFunc(GL_GEQUAL); - sphere::draw(); - if(i) glDepthFunc(GL_LESS); - } - } + fireballrenderer(const char *texname) + : listrenderer(texname, 0, PT_FIREBALL|PT_GLARE|PT_SHADER) + {} + + void startrender() + { + if(glaring) SETSHADER(explosionglare); + else if(!reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0) + { + if(!depthfxtex.highprecision()) SETSHADER(explosionsoft8); + else SETSHADER(explosionsoft); + } + else SETSHADER(explosion); + + sphere::enable(); + } + + void endrender() + { + sphere::disable(); + } + + void cleanup() + { + sphere::cleanup(); + } + + int finddepthfxranges(void **owners, float *ranges, int numranges, int maxranges, vec &bbmin, vec &bbmax) + { + static struct fireballent : physent + { + fireballent() + { + type = ENT_CAMERA; + } + } e; + + for(listparticle *p = list; p; p = p->next) + { + int ts = p->fade <= 5 ? 1 : lastmillis-p->millis; + float pmax = p->val, + size = p->fade ? float(ts)/p->fade : 1, + psize = (p->size + pmax * size)*WOBBLE; + if(2*(p->size + pmax)*WOBBLE < depthfxblend || + (!depthfxtex.highprecision() && !depthfxtex.emulatehighprecision() && psize > depthfxscale - depthfxbias) || + isfoggedsphere(psize, p->o)) continue; + + e.o = p->o; + e.radius = e.xradius = e.yradius = e.eyeheight = e.aboveeye = psize; + if(!::collide(&e, vec(0, 0, 0), 0, false)) continue; + + if(depthfxscissor==2 && !depthfxtex.addscissorbox(p->o, psize)) continue; + + vec dir = camera1->o; + dir.sub(p->o); + float dist = dir.magnitude(); + dir.mul(psize/dist).add(p->o); + float depth = depthfxtex.eyedepth(dir); + + loopk(3) + { + bbmin[k] = min(bbmin[k], p->o[k] - psize); + bbmax[k] = max(bbmax[k], p->o[k] + psize); + } + + int pos = numranges; + loopi(numranges) if(depth < ranges[i]) { pos = i; break; } + if(pos >= maxranges) continue; + + if(numranges > pos) + { + int moved = min(numranges-pos, maxranges-(pos+1)); + memmove(&ranges[pos+1], &ranges[pos], moved*sizeof(float)); + memmove(&owners[pos+1], &owners[pos], moved*sizeof(void *)); + } + if(numranges < maxranges) numranges++; + + ranges[pos] = depth; + owners[pos] = p; + } + + return numranges; + } + + void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) + { + pe.maxfade = max(pe.maxfade, fade); + pe.extendbb(o, (size+1+pe.ent->attr2)*WOBBLE); + } + + void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) + { + float pmax = p->val, + size = p->fade ? float(ts)/p->fade : 1, + psize = p->size + pmax * size; + + if(isfoggedsphere(psize*WOBBLE, p->o)) return; + + vec dir = vec(o).sub(camera1->o), s, t; + float dist = dir.magnitude(); + bool inside = dist <= psize*WOBBLE; + if(inside) + { + s = camright; + t = camup; + } + else + { + if(reflecting) { dir.z = o.z - reflectz; dist = dir.magnitude(); } + float mag2 = dir.magnitude2(); + dir.x /= mag2; + dir.y /= mag2; + dir.z /= dist; + s = vec(dir.y, -dir.x, 0); + t = vec(dir.x*dir.z, dir.y*dir.z, -mag2/dist); + } + + matrix3 rot(lastmillis/1000.0f*143*RAD, vec(1/SQRT3, 1/SQRT3, 1/SQRT3)); + LOCALPARAM(texgenS, rot.transposedtransform(s)); + LOCALPARAM(texgenT, rot.transposedtransform(t)); + + matrix4 m(rot, o); + m.scale(psize, psize, inside ? -psize : psize); + m.mul(camprojmatrix, m); + LOCALPARAM(explosionmatrix, m); + + LOCALPARAM(center, o); + LOCALPARAMF(millis, lastmillis/1000.0f); + LOCALPARAMF(blendparams, inside ? 0.5f : 4, inside ? 0.25f : 0); + binddepthfxparams(depthfxblend, inside ? blend/(2*255.0f) : 0, 2*(p->size + pmax)*WOBBLE >= depthfxblend, p); + + int passes = !reflecting && !refracting && inside ? 2 : 1; + loopi(passes) + { + gle::color(p->color, i ? blend/2 : blend); + if(i) glDepthFunc(GL_GEQUAL); + sphere::draw(); + if(i) glDepthFunc(GL_LESS); + } + } }; static fireballrenderer fireballs("packages/particles/explosion.png"), bluefireballs("packages/particles/plasma.png"); diff --git a/src/engine/glare.cpp b/src/engine/glare.cpp index c674e35..7701430 100644 --- a/src/engine/glare.cpp +++ b/src/engine/glare.cpp @@ -3,17 +3,17 @@ static struct glaretexture : rendertarget { - bool dorender() - { - extern void drawglare(); - drawglare(); - return true; - } + bool dorender() + { + extern void drawglare(); + drawglare(); + return true; + } } glaretex; void cleanupglare() { - glaretex.cleanup(true); + glaretex.cleanup(true); } VARFP(glaresize, 6, 8, 10, cleanupglare()); @@ -26,17 +26,17 @@ bool glaring = false; void drawglaretex() { - if(!glare) return; + if(!glare) return; - int w = 1< (1<<5) && (screenw*h)/w >= (screenh*4)/3) h /= 2; - blury = ((1 + 4*blurglare)*(screenw*h)/w + screenh*2)/(screenh*4); - blury = clamp(blury, 1, MAXBLURRADIUS); - } + int w = 1< (1<<5) && (screenw*h)/w >= (screenh*4)/3) h /= 2; + blury = ((1 + 4*blurglare)*(screenw*h)/w + screenh*2)/(screenh*4); + blury = clamp(blury, 1, MAXBLURRADIUS); + } - glaretex.render(w, h, blurglare, blurglaresigma/100.0f, blury); + glaretex.render(w, h, blurglare, blurglaresigma/100.0f, blury); } FVAR(glaremod, 0.5f, 0.75f, 1); @@ -44,20 +44,20 @@ FVARP(glarescale, 0, 1, 8); void addglare() { - if(!glare) return; + if(!glare) return; - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); - SETSHADER(screenrect); + SETSHADER(screenrect); - glBindTexture(GL_TEXTURE_2D, glaretex.rendertex); + glBindTexture(GL_TEXTURE_2D, glaretex.rendertex); - float g = glarescale*glaremod; - gle::colorf(g, g, g); + float g = glarescale*glaremod; + gle::colorf(g, g, g); - screenquad(1, 1); + screenquad(1, 1); - glDisable(GL_BLEND); + glDisable(GL_BLEND); } diff --git a/src/engine/iqm.h b/src/engine/iqm.h index f426a6b..e36a628 100644 --- a/src/engine/iqm.h +++ b/src/engine/iqm.h @@ -2,394 +2,394 @@ struct iqm; struct iqmheader { - char magic[16]; - uint version; - uint filesize; - uint flags; - uint num_text, ofs_text; - uint num_meshes, ofs_meshes; - uint num_vertexarrays, num_vertexes, ofs_vertexarrays; - uint num_triangles, ofs_triangles, ofs_adjacency; - uint num_joints, ofs_joints; - uint num_poses, ofs_poses; - uint num_anims, ofs_anims; - uint num_frames, num_framechannels, ofs_frames, ofs_bounds; - uint num_comment, ofs_comment; - uint num_extensions, ofs_extensions; + char magic[16]; + uint version; + uint filesize; + uint flags; + uint num_text, ofs_text; + uint num_meshes, ofs_meshes; + uint num_vertexarrays, num_vertexes, ofs_vertexarrays; + uint num_triangles, ofs_triangles, ofs_adjacency; + uint num_joints, ofs_joints; + uint num_poses, ofs_poses; + uint num_anims, ofs_anims; + uint num_frames, num_framechannels, ofs_frames, ofs_bounds; + uint num_comment, ofs_comment; + uint num_extensions, ofs_extensions; }; struct iqmmesh { - uint name; - uint material; - uint first_vertex, num_vertexes; - uint first_triangle, num_triangles; + uint name; + uint material; + uint first_vertex, num_vertexes; + uint first_triangle, num_triangles; }; enum -{ - IQM_POSITION = 0, - IQM_TEXCOORD = 1, - IQM_NORMAL = 2, - IQM_TANGENT = 3, - IQM_BLENDINDEXES = 4, - IQM_BLENDWEIGHTS = 5, - IQM_COLOR = 6, - IQM_CUSTOM = 0x10 -}; +{ + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, + IQM_CUSTOM = 0x10 +}; enum { - IQM_BYTE = 0, - IQM_UBYTE = 1, - IQM_SHORT = 2, - IQM_USHORT = 3, - IQM_INT = 4, - IQM_UINT = 5, - IQM_HALF = 6, - IQM_FLOAT = 7, - IQM_DOUBLE = 8, + IQM_BYTE = 0, + IQM_UBYTE = 1, + IQM_SHORT = 2, + IQM_USHORT = 3, + IQM_INT = 4, + IQM_UINT = 5, + IQM_HALF = 6, + IQM_FLOAT = 7, + IQM_DOUBLE = 8, }; struct iqmtriangle { - uint vertex[3]; + uint vertex[3]; }; struct iqmjoint { - uint name; - int parent; - vec pos; - quat orient; - vec size; + uint name; + int parent; + vec pos; + quat orient; + vec size; }; struct iqmpose { - int parent; - uint mask; - vec offsetpos; - vec4 offsetorient; - vec offsetsize; - vec scalepos; - vec4 scaleorient; - vec scalesize; + int parent; + uint mask; + vec offsetpos; + vec4 offsetorient; + vec offsetsize; + vec scalepos; + vec4 scaleorient; + vec scalesize; }; struct iqmanim { - uint name; - uint first_frame, num_frames; - float framerate; - uint flags; + uint name; + uint first_frame, num_frames; + float framerate; + uint flags; }; struct iqmvertexarray { - uint type; - uint flags; - uint format; - uint size; - uint offset; + uint type; + uint flags; + uint format; + uint size; + uint offset; }; struct iqm : skelloader { - iqm(const char *name) : skelloader(name) {} - - static const char *formatname() { return "iqm"; } - int type() const { return MDL_IQM; } - - struct iqmmeshgroup : skelmeshgroup - { - iqmmeshgroup() - { - } - - bool loadiqmmeshes(const char *filename, const iqmheader &hdr, uchar *buf) - { - lilswap((uint *)&buf[hdr.ofs_vertexarrays], hdr.num_vertexarrays*sizeof(iqmvertexarray)/sizeof(uint)); - lilswap((uint *)&buf[hdr.ofs_triangles], hdr.num_triangles*sizeof(iqmtriangle)/sizeof(uint)); - lilswap((uint *)&buf[hdr.ofs_meshes], hdr.num_meshes*sizeof(iqmmesh)/sizeof(uint)); - lilswap((uint *)&buf[hdr.ofs_joints], hdr.num_joints*sizeof(iqmjoint)/sizeof(uint)); - - const char *str = hdr.ofs_text ? (char *)&buf[hdr.ofs_text] : ""; - float *vpos = NULL, *vnorm = NULL, *vtan = NULL, *vtc = NULL; - uchar *vindex = NULL, *vweight = NULL; - iqmvertexarray *vas = (iqmvertexarray *)&buf[hdr.ofs_vertexarrays]; - loopi(hdr.num_vertexarrays) - { - iqmvertexarray &va = vas[i]; - switch(va.type) - { - case IQM_POSITION: if(va.format != IQM_FLOAT || va.size != 3) return false; vpos = (float *)&buf[va.offset]; lilswap(vpos, 3*hdr.num_vertexes); break; - case IQM_NORMAL: if(va.format != IQM_FLOAT || va.size != 3) return false; vnorm = (float *)&buf[va.offset]; lilswap(vnorm, 3*hdr.num_vertexes); break; - case IQM_TANGENT: if(va.format != IQM_FLOAT || va.size != 4) return false; vtan = (float *)&buf[va.offset]; lilswap(vtan, 4*hdr.num_vertexes); break; - case IQM_TEXCOORD: if(va.format != IQM_FLOAT || va.size != 2) return false; vtc = (float *)&buf[va.offset]; lilswap(vtc, 2*hdr.num_vertexes); break; - case IQM_BLENDINDEXES: if(va.format != IQM_UBYTE || va.size != 4) return false; vindex = (uchar *)&buf[va.offset]; break; - case IQM_BLENDWEIGHTS: if(va.format != IQM_UBYTE || va.size != 4) return false; vweight = (uchar *)&buf[va.offset]; break; - } - } - if(!vpos) return false; - - iqmtriangle *tris = (iqmtriangle *)&buf[hdr.ofs_triangles]; - iqmmesh *imeshes = (iqmmesh *)&buf[hdr.ofs_meshes]; - iqmjoint *joints = (iqmjoint *)&buf[hdr.ofs_joints]; - - if(hdr.num_joints) - { - if(skel->numbones <= 0) - { - skel->numbones = hdr.num_joints; - skel->bones = new boneinfo[skel->numbones]; - loopi(hdr.num_joints) - { - iqmjoint &j = joints[i]; - boneinfo &b = skel->bones[i]; - if(!b.name) b.name = newstring(&str[j.name]); - b.parent = j.parent; - if(skel->shared <= 1) - { - j.pos.y = -j.pos.y; - j.orient.x = -j.orient.x; - j.orient.z = -j.orient.z; - j.orient.normalize(); - b.base = dualquat(j.orient, j.pos); - if(b.parent >= 0) b.base.mul(skel->bones[b.parent].base, dualquat(b.base)); - (b.invbase = b.base).invert(); - } - } - } - - if(skel->shared <= 1) - skel->linkchildren(); - } - - loopi(hdr.num_meshes) - { - iqmmesh &im = imeshes[i]; - skelmesh *m = new skelmesh; - m->group = this; - meshes.add(m); - m->name = newstring(&str[im.name]); - m->numverts = im.num_vertexes; - int noblend = -1; - if(m->numverts) - { - m->verts = new vert[m->numverts]; - if(vtan) m->bumpverts = new bumpvert[m->numverts]; - if(!vindex || !vweight) - { - blendcombo c; - c.finalize(0); - noblend = m->addblendcombo(c); - } - } - int fv = im.first_vertex; - float *mpos = vpos + 3*fv, - *mnorm = vnorm ? vnorm + 3*fv : NULL, - *mtan = vtan ? vtan + 4*fv : NULL, - *mtc = vtc ? vtc + 2*fv : NULL; - uchar *mindex = vindex ? vindex + 4*fv : NULL, *mweight = vweight ? vweight + 4*fv : NULL; - loopj(im.num_vertexes) - { - vert &v = m->verts[j]; - v.pos = vec(mpos[0], -mpos[1], mpos[2]); - mpos += 3; - if(mtc) - { - v.tc = vec2(mtc[0], mtc[1]); - mtc += 2; - } - else v.tc = vec2(0, 0); - if(mnorm) - { - v.norm = vec(mnorm[0], -mnorm[1], mnorm[2]); - mnorm += 3; - if(mtan) - { - m->calctangent(m->bumpverts[j], v.norm, vec(mtan[0], -mtan[1], mtan[2]), mtan[3]); - mtan += 4; - } - } - else v.norm = vec(0, 0, 0); - if(noblend < 0) - { - blendcombo c; - int sorted = 0; - loopk(4) sorted = c.addweight(sorted, mweight[k], mindex[k]); - mweight += 4; - mindex += 4; - c.finalize(sorted); - v.blend = m->addblendcombo(c); - } - else v.blend = noblend; - } - m->numtris = im.num_triangles; - if(m->numtris) m->tris = new tri[m->numtris]; - iqmtriangle *mtris = tris + im.first_triangle; - loopj(im.num_triangles) - { - tri &t = m->tris[j]; - t.vert[0] = mtris->vertex[0] - fv; - t.vert[1] = mtris->vertex[1] - fv; - t.vert[2] = mtris->vertex[2] - fv; - ++mtris; - } - if(!m->numtris || !m->numverts) - { - conoutf(CON_WARN, "empty mesh in %s", filename); - meshes.removeobj(m); - delete m; - } - } - - sortblendcombos(); - - return true; - } - - bool loadiqmanims(const char *filename, const iqmheader &hdr, uchar *buf) - { - lilswap((uint *)&buf[hdr.ofs_poses], hdr.num_poses*sizeof(iqmpose)/sizeof(uint)); - lilswap((uint *)&buf[hdr.ofs_anims], hdr.num_anims*sizeof(iqmanim)/sizeof(uint)); - lilswap((ushort *)&buf[hdr.ofs_frames], hdr.num_frames*hdr.num_framechannels); - - const char *str = hdr.ofs_text ? (char *)&buf[hdr.ofs_text] : ""; - iqmpose *poses = (iqmpose *)&buf[hdr.ofs_poses]; - iqmanim *anims = (iqmanim *)&buf[hdr.ofs_anims]; - ushort *frames = (ushort *)&buf[hdr.ofs_frames]; - loopi(hdr.num_anims) - { - iqmanim &a = anims[i]; - string name; - copystring(name, filename); - concatstring(name, ":"); - concatstring(name, &str[a.name]); - skelanimspec *sa = skel->findskelanim(name); - if(sa) continue; - sa = &skel->addskelanim(name); - sa->frame = skel->numframes; - sa->range = a.num_frames; - dualquat *animbones = new dualquat[(skel->numframes+a.num_frames)*skel->numbones]; - if(skel->bones) - { - memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat)); - delete[] skel->framebones; - } - skel->framebones = animbones; - animbones += skel->numframes*skel->numbones; - skel->numframes += a.num_frames; - ushort *animdata = &frames[a.first_frame*hdr.num_framechannels]; - loopj(a.num_frames) - { - dualquat *frame = &animbones[j*skel->numbones]; - loopk(skel->numbones) - { - iqmpose &p = poses[k]; - vec pos; - quat orient; - pos.x = p.offsetpos.x; if(p.mask&0x01) pos.x += *animdata++ * p.scalepos.x; - pos.y = -p.offsetpos.y; if(p.mask&0x02) pos.y -= *animdata++ * p.scalepos.y; - pos.z = p.offsetpos.z; if(p.mask&0x04) pos.z += *animdata++ * p.scalepos.z; - orient.x = -p.offsetorient.x; if(p.mask&0x08) orient.x -= *animdata++ * p.scaleorient.x; - orient.y = p.offsetorient.y; if(p.mask&0x10) orient.y += *animdata++ * p.scaleorient.y; - orient.z = -p.offsetorient.z; if(p.mask&0x20) orient.z -= *animdata++ * p.scaleorient.z; - orient.w = p.offsetorient.w; if(p.mask&0x40) orient.w += *animdata++ * p.scaleorient.w; - orient.normalize(); - if(p.mask&0x380) - { - if(p.mask&0x80) animdata++; - if(p.mask&0x100) animdata++; - if(p.mask&0x200) animdata++; - } - frame[k] = dualquat(orient, pos); - if(adjustments.inrange(k)) adjustments[k].adjust(frame[k]); - boneinfo &b = skel->bones[k]; - frame[k].mul(b.invbase); - if(b.parent >= 0) frame[k].mul(skel->bones[b.parent].base, dualquat(frame[k])); - frame[k].fixantipodal(skel->framebones[k]); - } - } - } - - return true; - } - - bool loadiqm(const char *filename, bool doloadmesh, bool doloadanim) - { - stream *f = openfile(filename, "rb"); - if(!f) return false; - - uchar *buf = NULL; - iqmheader hdr; - if(f->read(&hdr, sizeof(hdr)) != sizeof(hdr) || memcmp(hdr.magic, "INTERQUAKEMODEL", sizeof(hdr.magic))) goto error; - lilswap(&hdr.version, (sizeof(hdr) - sizeof(hdr.magic))/sizeof(uint)); - if(hdr.version != 2) goto error; - if(hdr.filesize > (16<<20)) goto error; // sanity check... don't load files bigger than 16 MB - buf = new (false) uchar[hdr.filesize]; - if(!buf || f->read(buf + sizeof(hdr), hdr.filesize - sizeof(hdr)) != hdr.filesize - sizeof(hdr)) goto error; - - if(doloadmesh && !loadiqmmeshes(filename, hdr, buf)) goto error; - if(doloadanim && !loadiqmanims(filename, hdr, buf)) goto error; - - delete[] buf; - delete f; - return true; - - error: - if(buf) delete[] buf; - delete f; - return false; - } - - bool loadmesh(const char *filename) - { - name = newstring(filename); - - return loadiqm(filename, true, false); - } - - skelanimspec *loadanim(const char *animname) - { - const char *sep = strchr(animname, ':'); - skelanimspec *sa = skel->findskelanim(animname, sep ? '\0' : ':'); - if(!sa) - { - string filename; - copystring(filename, animname); - if(sep) filename[sep - animname] = '\0'; - if(loadiqm(filename, false, true)) - sa = skel->findskelanim(animname, sep ? '\0' : ':'); - } - return sa; - } - }; - - meshgroup *loadmeshes(const char *name, va_list args) - { - iqmmeshgroup *group = new iqmmeshgroup; - group->shareskeleton(va_arg(args, char *)); - if(!group->loadmesh(name)) { delete group; return NULL; } - return group; - } - - bool loaddefaultparts() - { - skelpart &mdl = addpart(); - mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; - adjustments.setsize(0); - const char *fname = name + strlen(name); - do --fname; while(fname >= name && *fname!='/' && *fname!='\\'); - fname++; - defformatstring(meshname, "packages/models/%s/%s.iqm", name, fname); - mdl.meshes = sharemeshes(path(meshname), NULL); - if(!mdl.meshes) return false; - mdl.initanimparts(); - mdl.initskins(); - return true; - } + iqm(const char *name) : skelloader(name) {} + + static const char *formatname() { return "iqm"; } + int type() const { return MDL_IQM; } + + struct iqmmeshgroup : skelmeshgroup + { + iqmmeshgroup() + { + } + + bool loadiqmmeshes(const char *filename, const iqmheader &hdr, uchar *buf) + { + lilswap((uint *)&buf[hdr.ofs_vertexarrays], hdr.num_vertexarrays*sizeof(iqmvertexarray)/sizeof(uint)); + lilswap((uint *)&buf[hdr.ofs_triangles], hdr.num_triangles*sizeof(iqmtriangle)/sizeof(uint)); + lilswap((uint *)&buf[hdr.ofs_meshes], hdr.num_meshes*sizeof(iqmmesh)/sizeof(uint)); + lilswap((uint *)&buf[hdr.ofs_joints], hdr.num_joints*sizeof(iqmjoint)/sizeof(uint)); + + const char *str = hdr.ofs_text ? (char *)&buf[hdr.ofs_text] : ""; + float *vpos = NULL, *vnorm = NULL, *vtan = NULL, *vtc = NULL; + uchar *vindex = NULL, *vweight = NULL; + iqmvertexarray *vas = (iqmvertexarray *)&buf[hdr.ofs_vertexarrays]; + loopi(hdr.num_vertexarrays) + { + iqmvertexarray &va = vas[i]; + switch(va.type) + { + case IQM_POSITION: if(va.format != IQM_FLOAT || va.size != 3) return false; vpos = (float *)&buf[va.offset]; lilswap(vpos, 3*hdr.num_vertexes); break; + case IQM_NORMAL: if(va.format != IQM_FLOAT || va.size != 3) return false; vnorm = (float *)&buf[va.offset]; lilswap(vnorm, 3*hdr.num_vertexes); break; + case IQM_TANGENT: if(va.format != IQM_FLOAT || va.size != 4) return false; vtan = (float *)&buf[va.offset]; lilswap(vtan, 4*hdr.num_vertexes); break; + case IQM_TEXCOORD: if(va.format != IQM_FLOAT || va.size != 2) return false; vtc = (float *)&buf[va.offset]; lilswap(vtc, 2*hdr.num_vertexes); break; + case IQM_BLENDINDEXES: if(va.format != IQM_UBYTE || va.size != 4) return false; vindex = (uchar *)&buf[va.offset]; break; + case IQM_BLENDWEIGHTS: if(va.format != IQM_UBYTE || va.size != 4) return false; vweight = (uchar *)&buf[va.offset]; break; + } + } + if(!vpos) return false; + + iqmtriangle *tris = (iqmtriangle *)&buf[hdr.ofs_triangles]; + iqmmesh *imeshes = (iqmmesh *)&buf[hdr.ofs_meshes]; + iqmjoint *joints = (iqmjoint *)&buf[hdr.ofs_joints]; + + if(hdr.num_joints) + { + if(skel->numbones <= 0) + { + skel->numbones = hdr.num_joints; + skel->bones = new boneinfo[skel->numbones]; + loopi(hdr.num_joints) + { + iqmjoint &j = joints[i]; + boneinfo &b = skel->bones[i]; + if(!b.name) b.name = newstring(&str[j.name]); + b.parent = j.parent; + if(skel->shared <= 1) + { + j.pos.y = -j.pos.y; + j.orient.x = -j.orient.x; + j.orient.z = -j.orient.z; + j.orient.normalize(); + b.base = dualquat(j.orient, j.pos); + if(b.parent >= 0) b.base.mul(skel->bones[b.parent].base, dualquat(b.base)); + (b.invbase = b.base).invert(); + } + } + } + + if(skel->shared <= 1) + skel->linkchildren(); + } + + loopi(hdr.num_meshes) + { + iqmmesh &im = imeshes[i]; + skelmesh *m = new skelmesh; + m->group = this; + meshes.add(m); + m->name = newstring(&str[im.name]); + m->numverts = im.num_vertexes; + int noblend = -1; + if(m->numverts) + { + m->verts = new vert[m->numverts]; + if(vtan) m->bumpverts = new bumpvert[m->numverts]; + if(!vindex || !vweight) + { + blendcombo c; + c.finalize(0); + noblend = m->addblendcombo(c); + } + } + int fv = im.first_vertex; + float *mpos = vpos + 3*fv, + *mnorm = vnorm ? vnorm + 3*fv : NULL, + *mtan = vtan ? vtan + 4*fv : NULL, + *mtc = vtc ? vtc + 2*fv : NULL; + uchar *mindex = vindex ? vindex + 4*fv : NULL, *mweight = vweight ? vweight + 4*fv : NULL; + loopj(im.num_vertexes) + { + vert &v = m->verts[j]; + v.pos = vec(mpos[0], -mpos[1], mpos[2]); + mpos += 3; + if(mtc) + { + v.tc = vec2(mtc[0], mtc[1]); + mtc += 2; + } + else v.tc = vec2(0, 0); + if(mnorm) + { + v.norm = vec(mnorm[0], -mnorm[1], mnorm[2]); + mnorm += 3; + if(mtan) + { + m->calctangent(m->bumpverts[j], v.norm, vec(mtan[0], -mtan[1], mtan[2]), mtan[3]); + mtan += 4; + } + } + else v.norm = vec(0, 0, 0); + if(noblend < 0) + { + blendcombo c; + int sorted = 0; + loopk(4) sorted = c.addweight(sorted, mweight[k], mindex[k]); + mweight += 4; + mindex += 4; + c.finalize(sorted); + v.blend = m->addblendcombo(c); + } + else v.blend = noblend; + } + m->numtris = im.num_triangles; + if(m->numtris) m->tris = new tri[m->numtris]; + iqmtriangle *mtris = tris + im.first_triangle; + loopj(im.num_triangles) + { + tri &t = m->tris[j]; + t.vert[0] = mtris->vertex[0] - fv; + t.vert[1] = mtris->vertex[1] - fv; + t.vert[2] = mtris->vertex[2] - fv; + ++mtris; + } + if(!m->numtris || !m->numverts) + { + conoutf(CON_WARN, "empty mesh in %s", filename); + meshes.removeobj(m); + delete m; + } + } + + sortblendcombos(); + + return true; + } + + bool loadiqmanims(const char *filename, const iqmheader &hdr, uchar *buf) + { + lilswap((uint *)&buf[hdr.ofs_poses], hdr.num_poses*sizeof(iqmpose)/sizeof(uint)); + lilswap((uint *)&buf[hdr.ofs_anims], hdr.num_anims*sizeof(iqmanim)/sizeof(uint)); + lilswap((ushort *)&buf[hdr.ofs_frames], hdr.num_frames*hdr.num_framechannels); + + const char *str = hdr.ofs_text ? (char *)&buf[hdr.ofs_text] : ""; + iqmpose *poses = (iqmpose *)&buf[hdr.ofs_poses]; + iqmanim *anims = (iqmanim *)&buf[hdr.ofs_anims]; + ushort *frames = (ushort *)&buf[hdr.ofs_frames]; + loopi(hdr.num_anims) + { + iqmanim &a = anims[i]; + string name; + copystring(name, filename); + concatstring(name, ":"); + concatstring(name, &str[a.name]); + skelanimspec *sa = skel->findskelanim(name); + if(sa) continue; + sa = &skel->addskelanim(name); + sa->frame = skel->numframes; + sa->range = a.num_frames; + dualquat *animbones = new dualquat[(skel->numframes+a.num_frames)*skel->numbones]; + if(skel->bones) + { + memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat)); + delete[] skel->framebones; + } + skel->framebones = animbones; + animbones += skel->numframes*skel->numbones; + skel->numframes += a.num_frames; + ushort *animdata = &frames[a.first_frame*hdr.num_framechannels]; + loopj(a.num_frames) + { + dualquat *frame = &animbones[j*skel->numbones]; + loopk(skel->numbones) + { + iqmpose &p = poses[k]; + vec pos; + quat orient; + pos.x = p.offsetpos.x; if(p.mask&0x01) pos.x += *animdata++ * p.scalepos.x; + pos.y = -p.offsetpos.y; if(p.mask&0x02) pos.y -= *animdata++ * p.scalepos.y; + pos.z = p.offsetpos.z; if(p.mask&0x04) pos.z += *animdata++ * p.scalepos.z; + orient.x = -p.offsetorient.x; if(p.mask&0x08) orient.x -= *animdata++ * p.scaleorient.x; + orient.y = p.offsetorient.y; if(p.mask&0x10) orient.y += *animdata++ * p.scaleorient.y; + orient.z = -p.offsetorient.z; if(p.mask&0x20) orient.z -= *animdata++ * p.scaleorient.z; + orient.w = p.offsetorient.w; if(p.mask&0x40) orient.w += *animdata++ * p.scaleorient.w; + orient.normalize(); + if(p.mask&0x380) + { + if(p.mask&0x80) animdata++; + if(p.mask&0x100) animdata++; + if(p.mask&0x200) animdata++; + } + frame[k] = dualquat(orient, pos); + if(adjustments.inrange(k)) adjustments[k].adjust(frame[k]); + boneinfo &b = skel->bones[k]; + frame[k].mul(b.invbase); + if(b.parent >= 0) frame[k].mul(skel->bones[b.parent].base, dualquat(frame[k])); + frame[k].fixantipodal(skel->framebones[k]); + } + } + } + + return true; + } + + bool loadiqm(const char *filename, bool doloadmesh, bool doloadanim) + { + stream *f = openfile(filename, "rb"); + if(!f) return false; + + uchar *buf = NULL; + iqmheader hdr; + if(f->read(&hdr, sizeof(hdr)) != sizeof(hdr) || memcmp(hdr.magic, "INTERQUAKEMODEL", sizeof(hdr.magic))) goto error; + lilswap(&hdr.version, (sizeof(hdr) - sizeof(hdr.magic))/sizeof(uint)); + if(hdr.version != 2) goto error; + if(hdr.filesize > (16<<20)) goto error; // sanity check... don't load files bigger than 16 MB + buf = new (false) uchar[hdr.filesize]; + if(!buf || f->read(buf + sizeof(hdr), hdr.filesize - sizeof(hdr)) != hdr.filesize - sizeof(hdr)) goto error; + + if(doloadmesh && !loadiqmmeshes(filename, hdr, buf)) goto error; + if(doloadanim && !loadiqmanims(filename, hdr, buf)) goto error; + + delete[] buf; + delete f; + return true; + + error: + if(buf) delete[] buf; + delete f; + return false; + } + + bool loadmesh(const char *filename) + { + name = newstring(filename); + + return loadiqm(filename, true, false); + } + + skelanimspec *loadanim(const char *animname) + { + const char *sep = strchr(animname, ':'); + skelanimspec *sa = skel->findskelanim(animname, sep ? '\0' : ':'); + if(!sa) + { + string filename; + copystring(filename, animname); + if(sep) filename[sep - animname] = '\0'; + if(loadiqm(filename, false, true)) + sa = skel->findskelanim(animname, sep ? '\0' : ':'); + } + return sa; + } + }; + + meshgroup *loadmeshes(const char *name, va_list args) + { + iqmmeshgroup *group = new iqmmeshgroup; + group->shareskeleton(va_arg(args, char *)); + if(!group->loadmesh(name)) { delete group; return NULL; } + return group; + } + + bool loaddefaultparts() + { + skelpart &mdl = addpart(); + mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; + adjustments.setsize(0); + const char *fname = name + strlen(name); + do --fname; while(fname >= name && *fname!='/' && *fname!='\\'); + fname++; + defformatstring(meshname, "packages/models/%s/%s.iqm", name, fname); + mdl.meshes = sharemeshes(path(meshname), NULL); + if(!mdl.meshes) return false; + mdl.initanimparts(); + mdl.initskins(); + return true; + } }; skelcommands iqmcommands; diff --git a/src/engine/lightmap.cpp b/src/engine/lightmap.cpp index b4090e2..959782e 100644 --- a/src/engine/lightmap.cpp +++ b/src/engine/lightmap.cpp @@ -8,59 +8,59 @@ struct lightmaptask; struct lightmapworker { - uchar *buf; - int bufstart, bufused; - lightmapinfo *firstlightmap, *lastlightmap, *curlightmaps; - cube *c; - cubeext *ext; - uchar *colorbuf; - bvec *raybuf; - uchar *ambient, *blur; - vec *colordata, *raydata; - int type, bpp, w, h, orient, rotate; - VSlot *vslot; - Slot *slot; - vector lights; - ShadowRayCache *shadowraycache; - BlendMapCache *blendmapcache; - bool needspace, doneworking; - SDL_cond *spacecond; - SDL_Thread *thread; - - lightmapworker(); - ~lightmapworker(); - - void reset(); - bool setupthread(); - void cleanupthread(); - - static int work(void *data); + uchar *buf; + int bufstart, bufused; + lightmapinfo *firstlightmap, *lastlightmap, *curlightmaps; + cube *c; + cubeext *ext; + uchar *colorbuf; + bvec *raybuf; + uchar *ambient, *blur; + vec *colordata, *raydata; + int type, bpp, w, h, orient, rotate; + VSlot *vslot; + Slot *slot; + vector lights; + ShadowRayCache *shadowraycache; + BlendMapCache *blendmapcache; + bool needspace, doneworking; + SDL_cond *spacecond; + SDL_Thread *thread; + + lightmapworker(); + ~lightmapworker(); + + void reset(); + bool setupthread(); + void cleanupthread(); + + static int work(void *data); }; struct lightmapinfo { - lightmapinfo *next; - cube *c; - uchar *colorbuf; - bvec *raybuf; - bool packed; - int type, w, h, bpp, bufsize, surface, layers; + lightmapinfo *next; + cube *c; + uchar *colorbuf; + bvec *raybuf; + bool packed; + int type, w, h, bpp, bufsize, surface, layers; }; struct lightmaptask { - ivec o; - int size, usefaces, progress; - cube *c; - cubeext *ext; - lightmapinfo *lightmaps; - lightmapworker *worker; + ivec o; + int size, usefaces, progress; + cube *c; + cubeext *ext; + lightmapinfo *lightmaps; + lightmapworker *worker; }; struct lightmapext { - cube *c; - cubeext *ext; + cube *c; + cubeext *ext; }; static vector lightmapworkers; @@ -79,24 +79,24 @@ VARR(lighterror, 1, 8, 16); VARR(bumperror, 1, 3, 16); VARR(lightlod, 0, 0, 10); bvec ambientcolor(0x19, 0x19, 0x19), skylightcolor(0, 0, 0); -HVARFR(ambient, 1, 0x191919, 0xFFFFFF, +HVARFR(ambient, 1, 0x191919, 0xFFFFFF, { - if(ambient <= 255) ambient |= (ambient<<8) | (ambient<<16); - ambientcolor = bvec((ambient>>16)&0xFF, (ambient>>8)&0xFF, ambient&0xFF); + if(ambient <= 255) ambient |= (ambient<<8) | (ambient<<16); + ambientcolor = bvec((ambient>>16)&0xFF, (ambient>>8)&0xFF, ambient&0xFF); }); -HVARFR(skylight, 0, 0, 0xFFFFFF, +HVARFR(skylight, 0, 0, 0xFFFFFF, { - if(skylight <= 255) skylight |= (skylight<<8) | (skylight<<16); - skylightcolor = bvec((skylight>>16)&0xFF, (skylight>>8)&0xFF, skylight&0xFF); + if(skylight <= 255) skylight |= (skylight<<8) | (skylight<<16); + skylightcolor = bvec((skylight>>16)&0xFF, (skylight>>8)&0xFF, skylight&0xFF); }); extern void setupsunlight(); bvec sunlightcolor(0, 0, 0); HVARFR(sunlight, 0, 0, 0xFFFFFF, { - if(sunlight <= 255) sunlight |= (sunlight<<8) | (sunlight<<16); - sunlightcolor = bvec((sunlight>>16)&0xFF, (sunlight>>8)&0xFF, sunlight&0xFF); - setupsunlight(); + if(sunlight <= 255) sunlight |= (sunlight<<8) | (sunlight<<16); + sunlightcolor = bvec((sunlight>>16)&0xFF, (sunlight>>8)&0xFF, sunlight&0xFF); + setupsunlight(); }); FVARFR(sunlightscale, 0, 1, 16, setupsunlight()); vec sunlightdir(0, 0, 1); @@ -104,25 +104,25 @@ extern void setsunlightdir(); VARFR(sunlightyaw, 0, 0, 360, setsunlightdir()); VARFR(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 setsunlightdir() +{ + sunlightdir = vec(sunlightyaw*RAD, sunlightpitch*RAD); + loopk(3) if(fabs(sunlightdir[k]) < 1e-5f) sunlightdir[k] = 0; + sunlightdir.normalize(); + setupsunlight(); } entity sunlightent; void setupsunlight() { - memclear(sunlightent); - sunlightent.type = ET_LIGHT; - sunlightent.attr1 = 0; - sunlightent.attr2 = int(sunlightcolor.x*sunlightscale); - sunlightent.attr3 = int(sunlightcolor.y*sunlightscale); - sunlightent.attr4 = int(sunlightcolor.z*sunlightscale); - float dist = min(min(sunlightdir.x ? 1/fabs(sunlightdir.x) : 1e16f, sunlightdir.y ? 1/fabs(sunlightdir.y) : 1e16f), sunlightdir.z ? 1/fabs(sunlightdir.z) : 1e16f); - sunlightent.o = vec(sunlightdir).mul(dist*worldsize).add(vec(worldsize/2, worldsize/2, worldsize/2)); + memclear(sunlightent); + sunlightent.type = ET_LIGHT; + sunlightent.attr1 = 0; + sunlightent.attr2 = int(sunlightcolor.x*sunlightscale); + sunlightent.attr3 = int(sunlightcolor.y*sunlightscale); + sunlightent.attr4 = int(sunlightcolor.z*sunlightscale); + float dist = min(min(sunlightdir.x ? 1/fabs(sunlightdir.x) : 1e16f, sunlightdir.y ? 1/fabs(sunlightdir.y) : 1e16f), sunlightdir.z ? 1/fabs(sunlightdir.z) : 1e16f); + sunlightent.o = vec(sunlightdir).mul(dist*worldsize).add(vec(worldsize/2, worldsize/2, worldsize/2)); } VARR(skytexturelight, 0, 1, 1); @@ -130,97 +130,97 @@ extern int useskytexture; static const surfaceinfo brightsurfaces[6] = { - brightsurface, - brightsurface, - brightsurface, - brightsurface, - brightsurface, - brightsurface + brightsurface, + brightsurface, + brightsurface, + brightsurface, + brightsurface, + brightsurface }; void brightencube(cube &c) { - if(!c.ext) newcubeext(c, 0, false); - memcpy(c.ext->surfaces, brightsurfaces, sizeof(brightsurfaces)); + 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)); + 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)); + 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)); } // quality parameters, set by the calclight arg @@ -238,38 +238,38 @@ volatile bool check_calclight_progress = false; void check_calclight_canceled() { - if(interceptkey(SDLK_ESCAPE)) - { - calclight_canceled = true; - loopv(lightmapworkers) lightmapworkers[i]->doneworking = true; - } - if(!calclight_canceled) check_calclight_progress = false; + if(interceptkey(SDLK_ESCAPE)) + { + calclight_canceled = true; + loopv(lightmapworkers) lightmapworkers[i]->doneworking = true; + } + if(!calclight_canceled) check_calclight_progress = false; } void show_calclight_progress() { - float bar1 = float(progress) / float(allocnodes); - defformatstring(text1, "%d%% using %d textures", int(bar1 * 100), lightmaps.length()); - - if(LM_PACKW <= hwtexsize && !progresstex) - { - glGenTextures(1, &progresstex); - createtexture(progresstex, LM_PACKW, LM_PACKH, NULL, 3, 1, GL_RGB); - } - - // only update once a sec (4 * 250 ms ticks) to not kill performance - if(progresstex && !calclight_canceled && progresslightmap >= 0 && !(progresstexticks++ % 4)) - { - if(tasklock) SDL_LockMutex(tasklock); - LightMap &lm = lightmaps[progresslightmap]; - uchar *data = lm.data; - int bpp = lm.bpp; - if(tasklock) SDL_UnlockMutex(tasklock); - glBindTexture(GL_TEXTURE_2D, progresstex); - glPixelStorei(GL_UNPACK_ALIGNMENT, texalign(data, LM_PACKW, bpp)); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LM_PACKW, LM_PACKH, bpp > 3 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data); - } - renderprogress(bar1, text1, progresstexticks ? progresstex : 0); + float bar1 = float(progress) / float(allocnodes); + defformatstring(text1, "%d%% using %d textures", int(bar1 * 100), lightmaps.length()); + + if(LM_PACKW <= hwtexsize && !progresstex) + { + glGenTextures(1, &progresstex); + createtexture(progresstex, LM_PACKW, LM_PACKH, NULL, 3, 1, GL_RGB); + } + + // only update once a sec (4 * 250 ms ticks) to not kill performance + if(progresstex && !calclight_canceled && progresslightmap >= 0 && !(progresstexticks++ % 4)) + { + if(tasklock) SDL_LockMutex(tasklock); + LightMap &lm = lightmaps[progresslightmap]; + uchar *data = lm.data; + int bpp = lm.bpp; + if(tasklock) SDL_UnlockMutex(tasklock); + glBindTexture(GL_TEXTURE_2D, progresstex); + glPixelStorei(GL_UNPACK_ALIGNMENT, texalign(data, LM_PACKW, bpp)); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LM_PACKW, LM_PACKH, bpp > 3 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data); + } + renderprogress(bar1, text1, progresstexticks ? progresstex : 0); } #define CHECK_PROGRESS_LOCKED(exit, before, after) CHECK_CALCLIGHT_PROGRESS_LOCKED(exit, show_calclight_progress, before, after) @@ -277,398 +277,398 @@ void show_calclight_progress() 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) clear(); - 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; + 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) clear(); + 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; } bool LightMap::insert(ushort &tx, ushort &ty, uchar *src, ushort tw, ushort th) { - if((type&LM_TYPE) != LM_BUMPMAP1 && !packroot.insert(tx, ty, tw, th)) - return false; + if((type&LM_TYPE) != LM_BUMPMAP1 && !packroot.insert(tx, ty, tw, th)) + return false; - copy(tx, ty, src, tw, th); - return true; + copy(tx, ty, src, tw, th); + return true; } void LightMap::copy(ushort tx, ushort ty, uchar *src, ushort tw, ushort th) { - uchar *dst = data + bpp * tx + ty * bpp * LM_PACKW; - loopi(th) - { - memcpy(dst, src, bpp * tw); - dst += bpp * LM_PACKW; - src += bpp * tw; - } - ++lightmaps; - lumels += tw * th; + uchar *dst = data + bpp * tx + ty * bpp * LM_PACKW; + loopi(th) + { + memcpy(dst, src, bpp * tw); + dst += bpp * LM_PACKW; + src += bpp * tw; + } + ++lightmaps; + lumels += tw * th; } static void insertunlit(int i) { - LightMap &l = lightmaps[i]; - if((l.type&LM_TYPE) == LM_BUMPMAP1) - { - l.unlitx = l.unlity = -1; - return; - } - ushort x, y; - uchar unlit[4] = { ambientcolor[0], ambientcolor[1], ambientcolor[2], 255 }; - if(l.insert(x, y, unlit, 1, 1)) - { - if((l.type&LM_TYPE) == LM_BUMPMAP0) - { - bvec front(128, 128, 255); - ASSERT(lightmaps[i+1].insert(x, y, front.v, 1, 1)); - } - l.unlitx = x; - l.unlity = y; - } + LightMap &l = lightmaps[i]; + if((l.type&LM_TYPE) == LM_BUMPMAP1) + { + l.unlitx = l.unlity = -1; + return; + } + ushort x, y; + uchar unlit[4] = { ambientcolor[0], ambientcolor[1], ambientcolor[2], 255 }; + if(l.insert(x, y, unlit, 1, 1)) + { + if((l.type&LM_TYPE) == LM_BUMPMAP0) + { + bvec front(128, 128, 255); + ASSERT(lightmaps[i+1].insert(x, y, front.v, 1, 1)); + } + l.unlitx = x; + l.unlity = y; + } } struct layoutinfo { - ushort x, y, lmid; - uchar w, h; + ushort x, y, lmid; + uchar w, h; }; static void insertlightmap(lightmapinfo &li, layoutinfo &si) { - loopv(lightmaps) - { - if(lightmaps[i].type == li.type && lightmaps[i].insert(si.x, si.y, li.colorbuf, si.w, si.h)) - { - si.lmid = i + LMID_RESERVED; - if((li.type&LM_TYPE) == LM_BUMPMAP0) ASSERT(lightmaps[i+1].insert(si.x, si.y, (uchar *)li.raybuf, si.w, si.h)); - return; - } - } - - progresslightmap = lightmaps.length(); - - si.lmid = lightmaps.length() + LMID_RESERVED; - LightMap &l = lightmaps.add(); - l.type = li.type; - l.bpp = li.bpp; - l.data = new uchar[li.bpp*LM_PACKW*LM_PACKH]; - memset(l.data, 0, li.bpp*LM_PACKW*LM_PACKH); - ASSERT(l.insert(si.x, si.y, li.colorbuf, si.w, si.h)); - if((li.type&LM_TYPE) == LM_BUMPMAP0) - { - LightMap &r = lightmaps.add(); - r.type = LM_BUMPMAP1 | (li.type&~LM_TYPE); - r.bpp = 3; - r.data = new uchar[3*LM_PACKW*LM_PACKH]; - memset(r.data, 0, 3*LM_PACKW*LM_PACKH); - ASSERT(r.insert(si.x, si.y, (uchar *)li.raybuf, si.w, si.h)); - } + loopv(lightmaps) + { + if(lightmaps[i].type == li.type && lightmaps[i].insert(si.x, si.y, li.colorbuf, si.w, si.h)) + { + si.lmid = i + LMID_RESERVED; + if((li.type&LM_TYPE) == LM_BUMPMAP0) ASSERT(lightmaps[i+1].insert(si.x, si.y, (uchar *)li.raybuf, si.w, si.h)); + return; + } + } + + progresslightmap = lightmaps.length(); + + si.lmid = lightmaps.length() + LMID_RESERVED; + LightMap &l = lightmaps.add(); + l.type = li.type; + l.bpp = li.bpp; + l.data = new uchar[li.bpp*LM_PACKW*LM_PACKH]; + memset(l.data, 0, li.bpp*LM_PACKW*LM_PACKH); + ASSERT(l.insert(si.x, si.y, li.colorbuf, si.w, si.h)); + if((li.type&LM_TYPE) == LM_BUMPMAP0) + { + LightMap &r = lightmaps.add(); + r.type = LM_BUMPMAP1 | (li.type&~LM_TYPE); + r.bpp = 3; + r.data = new uchar[3*LM_PACKW*LM_PACKH]; + memset(r.data, 0, 3*LM_PACKW*LM_PACKH); + ASSERT(r.insert(si.x, si.y, (uchar *)li.raybuf, si.w, si.h)); + } } static void copylightmap(lightmapinfo &li, layoutinfo &si) { - lightmaps[si.lmid-LMID_RESERVED].copy(si.x, si.y, li.colorbuf, si.w, si.h); - if((li.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(si.lmid+1-LMID_RESERVED)) - lightmaps[si.lmid+1-LMID_RESERVED].copy(si.x, si.y, (uchar *)li.raybuf, si.w, si.h); + lightmaps[si.lmid-LMID_RESERVED].copy(si.x, si.y, li.colorbuf, si.w, si.h); + if((li.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(si.lmid+1-LMID_RESERVED)) + lightmaps[si.lmid+1-LMID_RESERVED].copy(si.x, si.y, (uchar *)li.raybuf, si.w, si.h); } static inline bool htcmp(const lightmapinfo &k, const layoutinfo &v) { - int kw = k.w, kh = k.h; - if(kw != v.w || kh != v.h) return false; - LightMap &vlm = lightmaps[v.lmid - LMID_RESERVED]; - int ktype = k.type; - if(ktype != vlm.type) return false; - int kbpp = k.bpp; - const uchar *kcolor = k.colorbuf, *vcolor = vlm.data + kbpp*(v.x + v.y*LM_PACKW); - loopi(kh) - { - if(memcmp(kcolor, vcolor, kbpp*kw)) return false; - kcolor += kbpp*kw; - vcolor += kbpp*LM_PACKW; - } - if((ktype&LM_TYPE) != LM_BUMPMAP0) return true; - const bvec *kdir = k.raybuf, *vdir = (const bvec *)lightmaps[v.lmid+1 - LMID_RESERVED].data + (v.x + v.y*LM_PACKW); - loopi(kh) - { - if(memcmp(kdir, vdir, kw*sizeof(bvec))) return false; - kdir += kw; - vdir += LM_PACKW; - } - return true; -} - + int kw = k.w, kh = k.h; + if(kw != v.w || kh != v.h) return false; + LightMap &vlm = lightmaps[v.lmid - LMID_RESERVED]; + int ktype = k.type; + if(ktype != vlm.type) return false; + int kbpp = k.bpp; + const uchar *kcolor = k.colorbuf, *vcolor = vlm.data + kbpp*(v.x + v.y*LM_PACKW); + loopi(kh) + { + if(memcmp(kcolor, vcolor, kbpp*kw)) return false; + kcolor += kbpp*kw; + vcolor += kbpp*LM_PACKW; + } + if((ktype&LM_TYPE) != LM_BUMPMAP0) return true; + const bvec *kdir = k.raybuf, *vdir = (const bvec *)lightmaps[v.lmid+1 - LMID_RESERVED].data + (v.x + v.y*LM_PACKW); + loopi(kh) + { + if(memcmp(kdir, vdir, kw*sizeof(bvec))) return false; + kdir += kw; + vdir += LM_PACKW; + } + return true; +} + static inline uint hthash(const lightmapinfo &k) { - int kw = k.w, kh = k.h, kbpp = k.bpp; - uint hash = kw + (kh<<8); - const uchar *color = k.colorbuf; - loopi(kw*kh) - { - hash ^= color[0] + (color[1] << 4) + (color[2] << 8); - color += kbpp; - } - return hash; + int kw = k.w, kh = k.h, kbpp = k.bpp; + uint hash = kw + (kh<<8); + const uchar *color = k.colorbuf; + loopi(kw*kh) + { + hash ^= color[0] + (color[1] << 4) + (color[2] << 8); + color += kbpp; + } + return hash; } static hashset compressed; VAR(lightcompress, 0, 3, 6); -static bool packlightmap(lightmapinfo &l, layoutinfo &surface) -{ - surface.w = l.w; - surface.h = l.h; - if((int)l.w <= lightcompress && (int)l.h <= lightcompress) - { - layoutinfo *val = compressed.access(l); - if(!val) - { - insertlightmap(l, surface); - compressed[l] = surface; - } - else - { - surface.x = val->x; - surface.y = val->y; - surface.lmid = val->lmid; - return false; - } - } - else insertlightmap(l, surface); - return true; +static bool packlightmap(lightmapinfo &l, layoutinfo &surface) +{ + surface.w = l.w; + surface.h = l.h; + if((int)l.w <= lightcompress && (int)l.h <= lightcompress) + { + layoutinfo *val = compressed.access(l); + if(!val) + { + insertlightmap(l, surface); + compressed[l] = surface; + } + else + { + surface.x = val->x; + surface.y = val->y; + surface.lmid = val->lmid; + return false; + } + } + else insertlightmap(l, surface); + return true; } static void updatelightmap(const layoutinfo &surface) { - if(max(LM_PACKW, LM_PACKH) > hwtexsize || !lightmaps.inrange(surface.lmid-LMID_RESERVED)) return; - - LightMap &lm = lightmaps[surface.lmid-LMID_RESERVED]; - if(lm.tex < 0) - { - lm.offsetx = lm.offsety = 0; - lm.tex = lightmaptexs.length(); - LightMapTexture &tex = lightmaptexs.add(); - tex.type = lm.type; - tex.w = LM_PACKW; - tex.h = LM_PACKH; - tex.unlitx = lm.unlitx; - tex.unlity = lm.unlity; - glGenTextures(1, &tex.id); - createtexture(tex.id, tex.w, tex.h, NULL, 3, 1, tex.type&LM_ALPHA ? GL_RGBA : GL_RGB); - if((lm.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(surface.lmid+1-LMID_RESERVED)) - { - LightMap &lm2 = lightmaps[surface.lmid+1-LMID_RESERVED]; - lm2.offsetx = lm2.offsety = 0; - lm2.tex = lightmaptexs.length(); - LightMapTexture &tex2 = lightmaptexs.add(); - tex2.type = (lm.type&~LM_TYPE) | LM_BUMPMAP0; - tex2.w = LM_PACKW; - tex2.h = LM_PACKH; - tex2.unlitx = lm2.unlitx; - tex2.unlity = lm2.unlity; - glGenTextures(1, &tex2.id); - createtexture(tex2.id, tex2.w, tex2.h, NULL, 3, 1, GL_RGB); - } - } - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ROW_LENGTH, LM_PACKW); - - glBindTexture(GL_TEXTURE_2D, lightmaptexs[lm.tex].id); - glTexSubImage2D(GL_TEXTURE_2D, 0, lm.offsetx + surface.x, lm.offsety + surface.y, surface.w, surface.h, lm.type&LM_ALPHA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, &lm.data[(surface.y*LM_PACKW + surface.x)*lm.bpp]); - if((lm.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(surface.lmid+1-LMID_RESERVED)) - { - LightMap &lm2 = lightmaps[surface.lmid+1-LMID_RESERVED]; - glBindTexture(GL_TEXTURE_2D, lightmaptexs[lm2.tex].id); - glTexSubImage2D(GL_TEXTURE_2D, 0, lm2.offsetx + surface.x, lm2.offsety + surface.y, surface.w, surface.h, GL_RGB, GL_UNSIGNED_BYTE, &lm2.data[(surface.y*LM_PACKW + surface.x)*3]); - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); -} - - + if(max(LM_PACKW, LM_PACKH) > hwtexsize || !lightmaps.inrange(surface.lmid-LMID_RESERVED)) return; + + LightMap &lm = lightmaps[surface.lmid-LMID_RESERVED]; + if(lm.tex < 0) + { + lm.offsetx = lm.offsety = 0; + lm.tex = lightmaptexs.length(); + LightMapTexture &tex = lightmaptexs.add(); + tex.type = lm.type; + tex.w = LM_PACKW; + tex.h = LM_PACKH; + tex.unlitx = lm.unlitx; + tex.unlity = lm.unlity; + glGenTextures(1, &tex.id); + createtexture(tex.id, tex.w, tex.h, NULL, 3, 1, tex.type&LM_ALPHA ? GL_RGBA : GL_RGB); + if((lm.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(surface.lmid+1-LMID_RESERVED)) + { + LightMap &lm2 = lightmaps[surface.lmid+1-LMID_RESERVED]; + lm2.offsetx = lm2.offsety = 0; + lm2.tex = lightmaptexs.length(); + LightMapTexture &tex2 = lightmaptexs.add(); + tex2.type = (lm.type&~LM_TYPE) | LM_BUMPMAP0; + tex2.w = LM_PACKW; + tex2.h = LM_PACKH; + tex2.unlitx = lm2.unlitx; + tex2.unlity = lm2.unlity; + glGenTextures(1, &tex2.id); + createtexture(tex2.id, tex2.w, tex2.h, NULL, 3, 1, GL_RGB); + } + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ROW_LENGTH, LM_PACKW); + + glBindTexture(GL_TEXTURE_2D, lightmaptexs[lm.tex].id); + glTexSubImage2D(GL_TEXTURE_2D, 0, lm.offsetx + surface.x, lm.offsety + surface.y, surface.w, surface.h, lm.type&LM_ALPHA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, &lm.data[(surface.y*LM_PACKW + surface.x)*lm.bpp]); + if((lm.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(surface.lmid+1-LMID_RESERVED)) + { + LightMap &lm2 = lightmaps[surface.lmid+1-LMID_RESERVED]; + glBindTexture(GL_TEXTURE_2D, lightmaptexs[lm2.tex].id); + glTexSubImage2D(GL_TEXTURE_2D, 0, lm2.offsetx + surface.x, lm2.offsety + surface.y, surface.w, surface.h, GL_RGB, GL_UNSIGNED_BYTE, &lm2.data[(surface.y*LM_PACKW + surface.x)*3]); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +} + + static uint generatelumel(lightmapworker *w, const float tolerance, uint lightmask, const vector &lights, const vec &target, const vec &normal, vec &sample, int x, int y) { - vec avgray(0, 0, 0); - float r = 0, g = 0, b = 0; - uint lightused = 0; - loopv(lights) - { - if(lightmask&(1<type==ET_SPOTLIGHT) - { - vec spot = vec(light.attached->o).sub(light.o).normalize(); - float maxatten = sincos360[clamp(int(light.attached->attr1), 1, 89)].x, spotatten = (ray.dot(spot) - maxatten) / (1 - maxatten); - if(spotatten <= 0) continue; - attenuation *= spotatten; - } - if(lmshadows && mag) - { - float dist = shadowray(w->shadowraycache, light.o, ray, mag - tolerance, RAY_SHADOW | (lmshadows > 1 ? RAY_ALPHAPOLY : 0)); - if(dist < mag - tolerance) continue; - } - lightused |= 1<type&LM_TYPE) - { - case LM_BUMPMAP0: - intensity = attenuation; - avgray.add(ray.mul(-attenuation)); - break; - default: - intensity = angle * attenuation; - break; - } - r += intensity * float(light.attr2); - g += intensity * float(light.attr3); - b += intensity * float(light.attr4); - } - if(sunlight) - { - float angle = sunlightdir.dot(normal); - if(angle > 0 && - (!lmshadows || - shadowray(w->shadowraycache, vec(sunlightdir).mul(tolerance).add(target), sunlightdir, 1e16f, RAY_SHADOW | (lmshadows > 1 ? RAY_ALPHAPOLY : 0) | (skytexturelight ? RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0) : 0)) > 1e15f)) - { - float intensity; - switch(w->type&LM_TYPE) - { - case LM_BUMPMAP0: - intensity = 1; - avgray.add(sunlightdir); - break; - default: - intensity = angle; - break; - } - r += intensity * (sunlightcolor.x*sunlightscale); - g += intensity * (sunlightcolor.y*sunlightscale); - b += intensity * (sunlightcolor.z*sunlightscale); - } - } - switch(w->type&LM_TYPE) - { - case LM_BUMPMAP0: - if(avgray.iszero()) break; - // transform to tangent space - extern const vec orientation_tangent[8][3]; - extern const vec orientation_bitangent[8][3]; - vec S(orientation_tangent[w->rotate][dimension(w->orient)]), - T(orientation_bitangent[w->rotate][dimension(w->orient)]); - normal.orthonormalize(S, T); - avgray.normalize(); - w->raydata[y*w->w+x].add(vec(S.dot(avgray)/S.magnitude(), T.dot(avgray)/T.magnitude(), normal.dot(avgray))); - break; - } - sample.x = min(255.0f, max(r, float(ambientcolor[0]))); - sample.y = min(255.0f, max(g, float(ambientcolor[1]))); - sample.z = min(255.0f, max(b, float(ambientcolor[2]))); - return lightused; + vec avgray(0, 0, 0); + float r = 0, g = 0, b = 0; + uint lightused = 0; + loopv(lights) + { + if(lightmask&(1<type==ET_SPOTLIGHT) + { + vec spot = vec(light.attached->o).sub(light.o).normalize(); + float maxatten = sincos360[clamp(int(light.attached->attr1), 1, 89)].x, spotatten = (ray.dot(spot) - maxatten) / (1 - maxatten); + if(spotatten <= 0) continue; + attenuation *= spotatten; + } + if(lmshadows && mag) + { + float dist = shadowray(w->shadowraycache, light.o, ray, mag - tolerance, RAY_SHADOW | (lmshadows > 1 ? RAY_ALPHAPOLY : 0)); + if(dist < mag - tolerance) continue; + } + lightused |= 1<type&LM_TYPE) + { + case LM_BUMPMAP0: + intensity = attenuation; + avgray.add(ray.mul(-attenuation)); + break; + default: + intensity = angle * attenuation; + break; + } + r += intensity * float(light.attr2); + g += intensity * float(light.attr3); + b += intensity * float(light.attr4); + } + if(sunlight) + { + float angle = sunlightdir.dot(normal); + if(angle > 0 && + (!lmshadows || + shadowray(w->shadowraycache, vec(sunlightdir).mul(tolerance).add(target), sunlightdir, 1e16f, RAY_SHADOW | (lmshadows > 1 ? RAY_ALPHAPOLY : 0) | (skytexturelight ? RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0) : 0)) > 1e15f)) + { + float intensity; + switch(w->type&LM_TYPE) + { + case LM_BUMPMAP0: + intensity = 1; + avgray.add(sunlightdir); + break; + default: + intensity = angle; + break; + } + r += intensity * (sunlightcolor.x*sunlightscale); + g += intensity * (sunlightcolor.y*sunlightscale); + b += intensity * (sunlightcolor.z*sunlightscale); + } + } + switch(w->type&LM_TYPE) + { + case LM_BUMPMAP0: + if(avgray.iszero()) break; + // transform to tangent space + extern const vec orientation_tangent[8][3]; + extern const vec orientation_bitangent[8][3]; + vec S(orientation_tangent[w->rotate][dimension(w->orient)]), + T(orientation_bitangent[w->rotate][dimension(w->orient)]); + normal.orthonormalize(S, T); + avgray.normalize(); + w->raydata[y*w->w+x].add(vec(S.dot(avgray)/S.magnitude(), T.dot(avgray)/T.magnitude(), normal.dot(avgray))); + break; + } + sample.x = min(255.0f, max(r, float(ambientcolor[0]))); + sample.y = min(255.0f, max(g, float(ambientcolor[1]))); + sample.z = min(255.0f, max(b, float(ambientcolor[2]))); + return lightused; } static bool lumelsample(const vec &sample, int aasample, int stride) { - if(sample.x >= int(ambientcolor[0])+1 || sample.y >= int(ambientcolor[1])+1 || sample.z >= int(ambientcolor[2])+1) return true; + if(sample.x >= int(ambientcolor[0])+1 || sample.y >= int(ambientcolor[1])+1 || sample.z >= int(ambientcolor[2])+1) return true; #define NCHECK(n) \ - if((n).x >= int(ambientcolor[0])+1 || (n).y >= int(ambientcolor[1])+1 || (n).z >= int(ambientcolor[2])+1) \ - return true; - const vec *n = &sample - stride - aasample; - NCHECK(n[0]); NCHECK(n[aasample]); NCHECK(n[2*aasample]); - n += stride; - NCHECK(n[0]); NCHECK(n[2*aasample]); - n += stride; - NCHECK(n[0]); NCHECK(n[aasample]); NCHECK(n[2*aasample]); - return false; + if((n).x >= int(ambientcolor[0])+1 || (n).y >= int(ambientcolor[1])+1 || (n).z >= int(ambientcolor[2])+1) \ + return true; + const vec *n = &sample - stride - aasample; + NCHECK(n[0]); NCHECK(n[aasample]); NCHECK(n[2*aasample]); + n += stride; + NCHECK(n[0]); NCHECK(n[2*aasample]); + n += stride; + NCHECK(n[0]); NCHECK(n[aasample]); NCHECK(n[2*aasample]); + return false; } static void calcskylight(lightmapworker *w, const vec &o, const vec &normal, float tolerance, uchar *skylight, int flags = RAY_ALPHAPOLY, extentity *t = NULL) { - static const vec rays[17] = - { - vec(cosf(21*RAD)*cosf(50*RAD), sinf(21*RAD)*cosf(50*RAD), sinf(50*RAD)), - vec(cosf(111*RAD)*cosf(50*RAD), sinf(111*RAD)*cosf(50*RAD), sinf(50*RAD)), - vec(cosf(201*RAD)*cosf(50*RAD), sinf(201*RAD)*cosf(50*RAD), sinf(50*RAD)), - vec(cosf(291*RAD)*cosf(50*RAD), sinf(291*RAD)*cosf(50*RAD), sinf(50*RAD)), - - vec(cosf(66*RAD)*cosf(70*RAD), sinf(66*RAD)*cosf(70*RAD), sinf(70*RAD)), - vec(cosf(156*RAD)*cosf(70*RAD), sinf(156*RAD)*cosf(70*RAD), sinf(70*RAD)), - vec(cosf(246*RAD)*cosf(70*RAD), sinf(246*RAD)*cosf(70*RAD), sinf(70*RAD)), - vec(cosf(336*RAD)*cosf(70*RAD), sinf(336*RAD)*cosf(70*RAD), sinf(70*RAD)), - - vec(0, 0, 1), - - vec(cosf(43*RAD)*cosf(60*RAD), sinf(43*RAD)*cosf(60*RAD), sinf(60*RAD)), - vec(cosf(133*RAD)*cosf(60*RAD), sinf(133*RAD)*cosf(60*RAD), sinf(60*RAD)), - vec(cosf(223*RAD)*cosf(60*RAD), sinf(223*RAD)*cosf(60*RAD), sinf(60*RAD)), - vec(cosf(313*RAD)*cosf(60*RAD), sinf(313*RAD)*cosf(60*RAD), sinf(60*RAD)), - - vec(cosf(88*RAD)*cosf(80*RAD), sinf(88*RAD)*cosf(80*RAD), sinf(80*RAD)), - vec(cosf(178*RAD)*cosf(80*RAD), sinf(178*RAD)*cosf(80*RAD), sinf(80*RAD)), - vec(cosf(268*RAD)*cosf(80*RAD), sinf(268*RAD)*cosf(80*RAD), sinf(80*RAD)), - vec(cosf(358*RAD)*cosf(80*RAD), sinf(358*RAD)*cosf(80*RAD), sinf(80*RAD)), - - }; - flags |= RAY_SHADOW; - if(skytexturelight) flags |= RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0); - int hit = 0; - if(w) loopi(17) - { - if(normal.dot(rays[i])>=0 && shadowray(w->shadowraycache, vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, flags, t)>1e15f) hit++; - } - else loopi(17) - { - if(normal.dot(rays[i])>=0 && shadowray(vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, flags, t)>1e15f) hit++; - } - - loopk(3) skylight[k] = uchar(ambientcolor[k] + (max(skylightcolor[k], ambientcolor[k]) - ambientcolor[k])*hit/17.0f); + static const vec rays[17] = + { + vec(cosf(21*RAD)*cosf(50*RAD), sinf(21*RAD)*cosf(50*RAD), sinf(50*RAD)), + vec(cosf(111*RAD)*cosf(50*RAD), sinf(111*RAD)*cosf(50*RAD), sinf(50*RAD)), + vec(cosf(201*RAD)*cosf(50*RAD), sinf(201*RAD)*cosf(50*RAD), sinf(50*RAD)), + vec(cosf(291*RAD)*cosf(50*RAD), sinf(291*RAD)*cosf(50*RAD), sinf(50*RAD)), + + vec(cosf(66*RAD)*cosf(70*RAD), sinf(66*RAD)*cosf(70*RAD), sinf(70*RAD)), + vec(cosf(156*RAD)*cosf(70*RAD), sinf(156*RAD)*cosf(70*RAD), sinf(70*RAD)), + vec(cosf(246*RAD)*cosf(70*RAD), sinf(246*RAD)*cosf(70*RAD), sinf(70*RAD)), + vec(cosf(336*RAD)*cosf(70*RAD), sinf(336*RAD)*cosf(70*RAD), sinf(70*RAD)), + + vec(0, 0, 1), + + vec(cosf(43*RAD)*cosf(60*RAD), sinf(43*RAD)*cosf(60*RAD), sinf(60*RAD)), + vec(cosf(133*RAD)*cosf(60*RAD), sinf(133*RAD)*cosf(60*RAD), sinf(60*RAD)), + vec(cosf(223*RAD)*cosf(60*RAD), sinf(223*RAD)*cosf(60*RAD), sinf(60*RAD)), + vec(cosf(313*RAD)*cosf(60*RAD), sinf(313*RAD)*cosf(60*RAD), sinf(60*RAD)), + + vec(cosf(88*RAD)*cosf(80*RAD), sinf(88*RAD)*cosf(80*RAD), sinf(80*RAD)), + vec(cosf(178*RAD)*cosf(80*RAD), sinf(178*RAD)*cosf(80*RAD), sinf(80*RAD)), + vec(cosf(268*RAD)*cosf(80*RAD), sinf(268*RAD)*cosf(80*RAD), sinf(80*RAD)), + vec(cosf(358*RAD)*cosf(80*RAD), sinf(358*RAD)*cosf(80*RAD), sinf(80*RAD)), + + }; + flags |= RAY_SHADOW; + if(skytexturelight) flags |= RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0); + int hit = 0; + if(w) loopi(17) + { + if(normal.dot(rays[i])>=0 && shadowray(w->shadowraycache, vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, flags, t)>1e15f) hit++; + } + else loopi(17) + { + if(normal.dot(rays[i])>=0 && shadowray(vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, flags, t)>1e15f) hit++; + } + + loopk(3) skylight[k] = uchar(ambientcolor[k] + (max(skylightcolor[k], ambientcolor[k]) - ambientcolor[k])*hit/17.0f); } static inline bool hasskylight() { - return skylightcolor[0]>ambientcolor[0] || skylightcolor[1]>ambientcolor[1] || skylightcolor[2]>ambientcolor[2]; + return skylightcolor[0]>ambientcolor[0] || skylightcolor[1]>ambientcolor[1] || skylightcolor[2]>ambientcolor[2]; } VARR(blurlms, 0, 0, 2); @@ -676,45 +676,45 @@ VARR(blurskylight, 0, 0, 2); static inline void generatealpha(lightmapworker *w, float tolerance, const vec &pos, uchar &alpha) { - alpha = lookupblendmap(w->blendmapcache, pos); - if(w->slot->layermask) - { - static const int sdim[] = { 1, 0, 0 }, tdim[] = { 2, 2, 1 }; - int dim = dimension(w->orient); - float k = 8.0f/w->vslot->scale, - s = (pos[sdim[dim]] * k - w->vslot->offset.y) / w->slot->layermaskscale, - t = (pos[tdim[dim]] * (dim <= 1 ? -k : k) - w->vslot->offset.y) / w->slot->layermaskscale; - const texrotation &r = texrotations[w->rotate]; - if(r.swapxy) swap(s, t); - if(r.flipx) s = -s; - if(r.flipy) t = -t; - const ImageData &mask = *w->slot->layermask; - int mx = int(floor(s))%mask.w, my = int(floor(t))%mask.h; - if(mx < 0) mx += mask.w; - if(my < 0) my += mask.h; - uchar maskval = mask.data[mask.bpp*(mx + 1) - 1 + mask.pitch*my]; - switch(w->slot->layermaskmode) - { - case 2: alpha = min(alpha, maskval); break; - case 3: alpha = max(alpha, maskval); break; - case 4: alpha = min(alpha, uchar(0xFF - maskval)); break; - case 5: alpha = max(alpha, uchar(0xFF - maskval)); break; - default: alpha = maskval; break; - } - } -} - + alpha = lookupblendmap(w->blendmapcache, pos); + if(w->slot->layermask) + { + static const int sdim[] = { 1, 0, 0 }, tdim[] = { 2, 2, 1 }; + int dim = dimension(w->orient); + float k = 8.0f/w->vslot->scale, + s = (pos[sdim[dim]] * k - w->vslot->offset.y) / w->slot->layermaskscale, + t = (pos[tdim[dim]] * (dim <= 1 ? -k : k) - w->vslot->offset.y) / w->slot->layermaskscale; + const texrotation &r = texrotations[w->rotate]; + if(r.swapxy) swap(s, t); + if(r.flipx) s = -s; + if(r.flipy) t = -t; + const ImageData &mask = *w->slot->layermask; + int mx = int(floor(s))%mask.w, my = int(floor(t))%mask.h; + if(mx < 0) mx += mask.w; + if(my < 0) my += mask.h; + uchar maskval = mask.data[mask.bpp*(mx + 1) - 1 + mask.pitch*my]; + switch(w->slot->layermaskmode) + { + case 2: alpha = min(alpha, maskval); break; + case 3: alpha = max(alpha, maskval); break; + case 4: alpha = min(alpha, uchar(0xFF - maskval)); break; + case 5: alpha = max(alpha, uchar(0xFF - maskval)); break; + default: alpha = maskval; break; + } + } +} + VAR(edgetolerance, 1, 4, 64); VAR(adaptivesample, 0, 2, 2); enum { - NO_SURFACE = 0, - SURFACE_AMBIENT_BOTTOM, - SURFACE_AMBIENT_TOP, - SURFACE_LIGHTMAP_BOTTOM, - SURFACE_LIGHTMAP_TOP, - SURFACE_LIGHTMAP_BLEND + NO_SURFACE = 0, + SURFACE_AMBIENT_BOTTOM, + SURFACE_AMBIENT_TOP, + SURFACE_LIGHTMAP_BOTTOM, + SURFACE_LIGHTMAP_TOP, + SURFACE_LIGHTMAP_BLEND }; #define SURFACE_AMBIENT SURFACE_AMBIENT_BOTTOM @@ -722,316 +722,316 @@ enum static bool generatelightmap(lightmapworker *w, float lpu, const lerpvert *lv, int numv, vec origin1, const vec &xstep1, const vec &ystep1, vec origin2, const vec &xstep2, const vec &ystep2, float side0, float sidestep) { - static const float aacoords[8][2] = - { - {0.0f, 0.0f}, - {-0.5f, -0.5f}, - {0.0f, -0.5f}, - {-0.5f, 0.0f}, - - {0.3f, -0.6f}, - {0.6f, 0.3f}, - {-0.3f, 0.6f}, - {-0.6f, -0.3f}, - }; - float tolerance = 0.5 / lpu; - uint lightmask = 0, lightused = 0; - vec offsets1[8], offsets2[8]; - loopi(8) - { - offsets1[i] = vec(xstep1).mul(aacoords[i][0]).add(vec(ystep1).mul(aacoords[i][1])); - offsets2[i] = vec(xstep2).mul(aacoords[i][0]).add(vec(ystep2).mul(aacoords[i][1])); - } - if((w->type&LM_TYPE) == LM_BUMPMAP0) memclear(w->raydata, (LM_MAXW + 4)*(LM_MAXH + 4)); - - origin1.sub(vec(ystep1).add(xstep1).mul(blurlms)); - origin2.sub(vec(ystep2).add(xstep2).mul(blurlms)); - - int aasample = min(1 << lmaa, 4); - int stride = aasample*(w->w+1); - vec *sample = w->colordata; - uchar *skylight = w->ambient; - lerpbounds start, end; - initlerpbounds(-blurlms, -blurlms, lv, numv, start, end); - float sidex = side0 + blurlms*sidestep; - for(int y = 0; y < w->h; ++y, sidex += sidestep) - { - vec normal, nstep; - lerpnormal(-blurlms, y - blurlms, lv, numv, start, end, normal, nstep); - - for(int x = 0; x < w->w; ++x, normal.add(nstep), skylight += w->bpp) - { + static const float aacoords[8][2] = + { + {0.0f, 0.0f}, + {-0.5f, -0.5f}, + {0.0f, -0.5f}, + {-0.5f, 0.0f}, + + {0.3f, -0.6f}, + {0.6f, 0.3f}, + {-0.3f, 0.6f}, + {-0.6f, -0.3f}, + }; + float tolerance = 0.5 / lpu; + uint lightmask = 0, lightused = 0; + vec offsets1[8], offsets2[8]; + loopi(8) + { + offsets1[i] = vec(xstep1).mul(aacoords[i][0]).add(vec(ystep1).mul(aacoords[i][1])); + offsets2[i] = vec(xstep2).mul(aacoords[i][0]).add(vec(ystep2).mul(aacoords[i][1])); + } + if((w->type&LM_TYPE) == LM_BUMPMAP0) memclear(w->raydata, (LM_MAXW + 4)*(LM_MAXH + 4)); + + origin1.sub(vec(ystep1).add(xstep1).mul(blurlms)); + origin2.sub(vec(ystep2).add(xstep2).mul(blurlms)); + + int aasample = min(1 << lmaa, 4); + int stride = aasample*(w->w+1); + vec *sample = w->colordata; + uchar *skylight = w->ambient; + lerpbounds start, end; + initlerpbounds(-blurlms, -blurlms, lv, numv, start, end); + float sidex = side0 + blurlms*sidestep; + for(int y = 0; y < w->h; ++y, sidex += sidestep) + { + vec normal, nstep; + lerpnormal(-blurlms, y - blurlms, lv, numv, start, end, normal, nstep); + + for(int x = 0; x < w->w; ++x, normal.add(nstep), skylight += w->bpp) + { #define EDGE_TOLERANCE(x, y) \ - (x < blurlms \ - || x+1 > w->w - blurlms \ - || y < blurlms \ - || y+1 > w->h - blurlms \ - ? edgetolerance : 1) - float t = EDGE_TOLERANCE(x, y) * tolerance; - vec u = x < sidex ? vec(xstep1).mul(x).add(vec(ystep1).mul(y)).add(origin1) : vec(xstep2).mul(x).add(vec(ystep2).mul(y)).add(origin2); - lightused |= generatelumel(w, t, 0, w->lights, u, vec(normal).normalize(), *sample, x, y); - if(hasskylight()) - { - if((w->type&LM_TYPE)==LM_BUMPMAP0 || !adaptivesample || sample->xyz 1 ? RAY_ALPHAPOLY : 0); - else loopk(3) skylight[k] = max(skylightcolor[k], ambientcolor[k]); - } - else loopk(3) skylight[k] = ambientcolor[k]; - if(w->type&LM_ALPHA) generatealpha(w, t, u, skylight[3]); - sample += aasample; - } - sample += aasample; - } - if(adaptivesample > 1 && min(w->w, w->h) >= 2) lightmask = ~lightused; - sample = w->colordata; - initlerpbounds(-blurlms, -blurlms, lv, numv, start, end); - sidex = side0 + blurlms*sidestep; - for(int y = 0; y < w->h; ++y, sidex += sidestep) - { - vec normal, nstep; - lerpnormal(-blurlms, y - blurlms, lv, numv, start, end, normal, nstep); - - for(int x = 0; x < w->w; ++x, normal.add(nstep)) - { - vec ¢er = *sample++; - if(adaptivesample && x > 0 && x+1 < w->w && y > 0 && y+1 < w->h && !lumelsample(center, aasample, stride)) - loopi(aasample-1) *sample++ = center; - else - { + (x < blurlms \ + || x+1 > w->w - blurlms \ + || y < blurlms \ + || y+1 > w->h - blurlms \ + ? edgetolerance : 1) + float t = EDGE_TOLERANCE(x, y) * tolerance; + vec u = x < sidex ? vec(xstep1).mul(x).add(vec(ystep1).mul(y)).add(origin1) : vec(xstep2).mul(x).add(vec(ystep2).mul(y)).add(origin2); + lightused |= generatelumel(w, t, 0, w->lights, u, vec(normal).normalize(), *sample, x, y); + if(hasskylight()) + { + if((w->type&LM_TYPE)==LM_BUMPMAP0 || !adaptivesample || sample->xyz 1 ? RAY_ALPHAPOLY : 0); + else loopk(3) skylight[k] = max(skylightcolor[k], ambientcolor[k]); + } + else loopk(3) skylight[k] = ambientcolor[k]; + if(w->type&LM_ALPHA) generatealpha(w, t, u, skylight[3]); + sample += aasample; + } + sample += aasample; + } + if(adaptivesample > 1 && min(w->w, w->h) >= 2) lightmask = ~lightused; + sample = w->colordata; + initlerpbounds(-blurlms, -blurlms, lv, numv, start, end); + sidex = side0 + blurlms*sidestep; + for(int y = 0; y < w->h; ++y, sidex += sidestep) + { + vec normal, nstep; + lerpnormal(-blurlms, y - blurlms, lv, numv, start, end, normal, nstep); + + for(int x = 0; x < w->w; ++x, normal.add(nstep)) + { + vec ¢er = *sample++; + if(adaptivesample && x > 0 && x+1 < w->w && y > 0 && y+1 < w->h && !lumelsample(center, aasample, stride)) + loopi(aasample-1) *sample++ = center; + else + { #define AA_EDGE_TOLERANCE(x, y, i) EDGE_TOLERANCE(x + aacoords[i][0], y + aacoords[i][1]) - vec u = x < sidex ? vec(xstep1).mul(x).add(vec(ystep1).mul(y)).add(origin1) : vec(xstep2).mul(x).add(vec(ystep2).mul(y)).add(origin2); - const vec *offsets = x < sidex ? offsets1 : offsets2; - vec n = vec(normal).normalize(); - loopi(aasample-1) - generatelumel(w, AA_EDGE_TOLERANCE(x, y, i+1) * tolerance, lightmask, w->lights, vec(u).add(offsets[i+1]), n, *sample++, x, y); - if(lmaa == 3) - { - loopi(4) - { - vec s; - generatelumel(w, AA_EDGE_TOLERANCE(x, y, i+4) * tolerance, lightmask, w->lights, vec(u).add(offsets[i+4]), n, s, x, y); - center.add(s); - } - center.div(5); - } - } - } - if(aasample > 1) - { - vec u = w->w < sidex ? vec(xstep1).mul(w->w).add(vec(ystep1).mul(y)).add(origin1) : vec(xstep2).mul(w->w).add(vec(ystep2).mul(y)).add(origin2); - const vec *offsets = w->w < sidex ? offsets1 : offsets2; - vec n = vec(normal).normalize(); - generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[1]), n, sample[1], w->w-1, y); - if(aasample > 2) - generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[3]), n, sample[3], w->w-1, y); - } - sample += aasample; - } - - if(aasample > 1) - { - vec normal, nstep; - lerpnormal(-blurlms, w->h - blurlms, lv, numv, start, end, normal, nstep); - - for(int x = 0; x <= w->w; ++x, normal.add(nstep)) - { - vec u = x < sidex ? vec(xstep1).mul(x).add(vec(ystep1).mul(w->h)).add(origin1) : vec(xstep2).mul(x).add(vec(ystep2).mul(w->h)).add(origin2); - const vec *offsets = x < sidex ? offsets1 : offsets2; - vec n = vec(normal).normalize(); - generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[1]), n, sample[1], min(x, w->w-1), w->h-1); - if(aasample > 2) - generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[2]), n, sample[2], min(x, w->w-1), w->h-1); - sample += aasample; - } - } - return true; -} - + vec u = x < sidex ? vec(xstep1).mul(x).add(vec(ystep1).mul(y)).add(origin1) : vec(xstep2).mul(x).add(vec(ystep2).mul(y)).add(origin2); + const vec *offsets = x < sidex ? offsets1 : offsets2; + vec n = vec(normal).normalize(); + loopi(aasample-1) + generatelumel(w, AA_EDGE_TOLERANCE(x, y, i+1) * tolerance, lightmask, w->lights, vec(u).add(offsets[i+1]), n, *sample++, x, y); + if(lmaa == 3) + { + loopi(4) + { + vec s; + generatelumel(w, AA_EDGE_TOLERANCE(x, y, i+4) * tolerance, lightmask, w->lights, vec(u).add(offsets[i+4]), n, s, x, y); + center.add(s); + } + center.div(5); + } + } + } + if(aasample > 1) + { + vec u = w->w < sidex ? vec(xstep1).mul(w->w).add(vec(ystep1).mul(y)).add(origin1) : vec(xstep2).mul(w->w).add(vec(ystep2).mul(y)).add(origin2); + const vec *offsets = w->w < sidex ? offsets1 : offsets2; + vec n = vec(normal).normalize(); + generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[1]), n, sample[1], w->w-1, y); + if(aasample > 2) + generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[3]), n, sample[3], w->w-1, y); + } + sample += aasample; + } + + if(aasample > 1) + { + vec normal, nstep; + lerpnormal(-blurlms, w->h - blurlms, lv, numv, start, end, normal, nstep); + + for(int x = 0; x <= w->w; ++x, normal.add(nstep)) + { + vec u = x < sidex ? vec(xstep1).mul(x).add(vec(ystep1).mul(w->h)).add(origin1) : vec(xstep2).mul(x).add(vec(ystep2).mul(w->h)).add(origin2); + const vec *offsets = x < sidex ? offsets1 : offsets2; + vec n = vec(normal).normalize(); + generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[1]), n, sample[1], min(x, w->w-1), w->h-1); + if(aasample > 2) + generatelumel(w, edgetolerance * tolerance, lightmask, w->lights, vec(u).add(offsets[2]), n, sample[2], min(x, w->w-1), w->h-1); + sample += aasample; + } + } + return true; +} + static int finishlightmap(lightmapworker *w) -{ - if(hasskylight() && blurskylight && (w->w>1 || w->h>1)) - { - blurtexture(blurskylight, w->bpp, w->w, w->h, w->blur, w->ambient); - swap(w->blur, w->ambient); - } - vec *sample = w->colordata; - int aasample = min(1 << lmaa, 4), stride = aasample*(w->w+1); - float weight = 1.0f / (1.0f + 4.0f*lmaa), - cweight = weight * (lmaa == 3 ? 5.0f : 1.0f); - uchar *skylight = w->ambient; - vec *ray = w->raydata; - uchar *dstcolor = blurlms && (w->w > 1 || w->h > 1) ? w->blur : w->colorbuf; - uchar mincolor[4] = { 255, 255, 255, 255 }, maxcolor[4] = { 0, 0, 0, 0 }; - bvec *dstray = blurlms && (w->w > 1 || w->h > 1) ? (bvec *)w->raydata : w->raybuf; - bvec minray(255, 255, 255), maxray(0, 0, 0); - loop(y, w->h) - { - loop(x, w->w) - { - vec l(0, 0, 0); - const vec ¢er = *sample++; - loopi(aasample-1) l.add(*sample++); - if(aasample > 1) - { - l.add(sample[1]); - if(aasample > 2) l.add(sample[3]); - } - vec *next = sample + stride - aasample; - if(aasample > 1) - { - l.add(next[1]); - if(aasample > 2) l.add(next[2]); - l.add(next[aasample+1]); - } - - int r = int(center.x*cweight + l.x*weight), - g = int(center.y*cweight + l.y*weight), - b = int(center.z*cweight + l.z*weight), - ar = skylight[0], ag = skylight[1], ab = skylight[2]; - dstcolor[0] = max(ar, r); - dstcolor[1] = max(ag, g); - dstcolor[2] = max(ab, b); - loopk(3) - { - mincolor[k] = min(mincolor[k], dstcolor[k]); - maxcolor[k] = max(maxcolor[k], dstcolor[k]); - } - if(w->type&LM_ALPHA) - { - dstcolor[3] = skylight[3]; - mincolor[3] = min(mincolor[3], dstcolor[3]); - maxcolor[3] = max(maxcolor[3], dstcolor[3]); - } - if((w->type&LM_TYPE) == LM_BUMPMAP0) - { - if(ray->iszero()) dstray[0] = bvec(128, 128, 255); - else - { - // bias the normals towards the amount of ambient/skylight in the lumel - // this is necessary to prevent the light values in shaders from dropping too far below the skylight (to the ambient) if N.L is small - ray->normalize(); - int l = max(r, max(g, b)), a = max(ar, max(ag, ab)); - ray->mul(max(l-a, 0)); - ray->z += a; - dstray[0] = bvec(ray->normalize()); - } - loopk(3) - { - minray[k] = min(minray[k], dstray[0][k]); - maxray[k] = max(maxray[k], dstray[0][k]); - } - ray++; - dstray++; - } - dstcolor += w->bpp; - skylight += w->bpp; - } - sample += aasample; - } - if(int(maxcolor[0]) - int(mincolor[0]) <= lighterror && - int(maxcolor[1]) - int(mincolor[1]) <= lighterror && - int(maxcolor[2]) - int(mincolor[2]) <= lighterror && - mincolor[3] >= maxcolor[3]) - { - uchar color[3]; - loopk(3) color[k] = (int(maxcolor[k]) + int(mincolor[k])) / 2; - if(color[0] <= int(ambientcolor[0]) + lighterror && - color[1] <= int(ambientcolor[1]) + lighterror && - color[2] <= int(ambientcolor[2]) + lighterror && - (maxcolor[3]==0 || mincolor[3]==255)) - return mincolor[3]==255 ? SURFACE_AMBIENT_TOP : SURFACE_AMBIENT_BOTTOM; - if((w->type&LM_TYPE) != LM_BUMPMAP0 || - (int(maxray.x) - int(minray.x) <= bumperror && - int(maxray.y) - int(minray.z) <= bumperror && - int(maxray.z) - int(minray.z) <= bumperror)) - - { - memcpy(w->colorbuf, color, 3); - if(w->type&LM_ALPHA) w->colorbuf[3] = mincolor[3]; - if((w->type&LM_TYPE) == LM_BUMPMAP0) - { - loopk(3) w->raybuf[0][k] = uchar((int(maxray[k])+int(minray[k]))/2); - } - w->lastlightmap->w = w->w = 1; - w->lastlightmap->h = w->h = 1; - } - } - if(blurlms && (w->w>1 || w->h>1)) - { - blurtexture(blurlms, w->bpp, w->w, w->h, w->colorbuf, w->blur, blurlms); - if((w->type&LM_TYPE) == LM_BUMPMAP0) blurnormals(blurlms, w->w, w->h, w->raybuf, (const bvec *)w->raydata, blurlms); - w->lastlightmap->w = (w->w -= 2*blurlms); - w->lastlightmap->h = (w->h -= 2*blurlms); - } - if(mincolor[3]==255) return SURFACE_LIGHTMAP_TOP; - else if(maxcolor[3]==0) return SURFACE_LIGHTMAP_BOTTOM; - else return SURFACE_LIGHTMAP_BLEND; +{ + if(hasskylight() && blurskylight && (w->w>1 || w->h>1)) + { + blurtexture(blurskylight, w->bpp, w->w, w->h, w->blur, w->ambient); + swap(w->blur, w->ambient); + } + vec *sample = w->colordata; + int aasample = min(1 << lmaa, 4), stride = aasample*(w->w+1); + float weight = 1.0f / (1.0f + 4.0f*lmaa), + cweight = weight * (lmaa == 3 ? 5.0f : 1.0f); + uchar *skylight = w->ambient; + vec *ray = w->raydata; + uchar *dstcolor = blurlms && (w->w > 1 || w->h > 1) ? w->blur : w->colorbuf; + uchar mincolor[4] = { 255, 255, 255, 255 }, maxcolor[4] = { 0, 0, 0, 0 }; + bvec *dstray = blurlms && (w->w > 1 || w->h > 1) ? (bvec *)w->raydata : w->raybuf; + bvec minray(255, 255, 255), maxray(0, 0, 0); + loop(y, w->h) + { + loop(x, w->w) + { + vec l(0, 0, 0); + const vec ¢er = *sample++; + loopi(aasample-1) l.add(*sample++); + if(aasample > 1) + { + l.add(sample[1]); + if(aasample > 2) l.add(sample[3]); + } + vec *next = sample + stride - aasample; + if(aasample > 1) + { + l.add(next[1]); + if(aasample > 2) l.add(next[2]); + l.add(next[aasample+1]); + } + + int r = int(center.x*cweight + l.x*weight), + g = int(center.y*cweight + l.y*weight), + b = int(center.z*cweight + l.z*weight), + ar = skylight[0], ag = skylight[1], ab = skylight[2]; + dstcolor[0] = max(ar, r); + dstcolor[1] = max(ag, g); + dstcolor[2] = max(ab, b); + loopk(3) + { + mincolor[k] = min(mincolor[k], dstcolor[k]); + maxcolor[k] = max(maxcolor[k], dstcolor[k]); + } + if(w->type&LM_ALPHA) + { + dstcolor[3] = skylight[3]; + mincolor[3] = min(mincolor[3], dstcolor[3]); + maxcolor[3] = max(maxcolor[3], dstcolor[3]); + } + if((w->type&LM_TYPE) == LM_BUMPMAP0) + { + if(ray->iszero()) dstray[0] = bvec(128, 128, 255); + else + { + // bias the normals towards the amount of ambient/skylight in the lumel + // this is necessary to prevent the light values in shaders from dropping too far below the skylight (to the ambient) if N.L is small + ray->normalize(); + int l = max(r, max(g, b)), a = max(ar, max(ag, ab)); + ray->mul(max(l-a, 0)); + ray->z += a; + dstray[0] = bvec(ray->normalize()); + } + loopk(3) + { + minray[k] = min(minray[k], dstray[0][k]); + maxray[k] = max(maxray[k], dstray[0][k]); + } + ray++; + dstray++; + } + dstcolor += w->bpp; + skylight += w->bpp; + } + sample += aasample; + } + if(int(maxcolor[0]) - int(mincolor[0]) <= lighterror && + int(maxcolor[1]) - int(mincolor[1]) <= lighterror && + int(maxcolor[2]) - int(mincolor[2]) <= lighterror && + mincolor[3] >= maxcolor[3]) + { + uchar color[3]; + loopk(3) color[k] = (int(maxcolor[k]) + int(mincolor[k])) / 2; + if(color[0] <= int(ambientcolor[0]) + lighterror && + color[1] <= int(ambientcolor[1]) + lighterror && + color[2] <= int(ambientcolor[2]) + lighterror && + (maxcolor[3]==0 || mincolor[3]==255)) + return mincolor[3]==255 ? SURFACE_AMBIENT_TOP : SURFACE_AMBIENT_BOTTOM; + if((w->type&LM_TYPE) != LM_BUMPMAP0 || + (int(maxray.x) - int(minray.x) <= bumperror && + int(maxray.y) - int(minray.z) <= bumperror && + int(maxray.z) - int(minray.z) <= bumperror)) + + { + memcpy(w->colorbuf, color, 3); + if(w->type&LM_ALPHA) w->colorbuf[3] = mincolor[3]; + if((w->type&LM_TYPE) == LM_BUMPMAP0) + { + loopk(3) w->raybuf[0][k] = uchar((int(maxray[k])+int(minray[k]))/2); + } + w->lastlightmap->w = w->w = 1; + w->lastlightmap->h = w->h = 1; + } + } + if(blurlms && (w->w>1 || w->h>1)) + { + blurtexture(blurlms, w->bpp, w->w, w->h, w->colorbuf, w->blur, blurlms); + if((w->type&LM_TYPE) == LM_BUMPMAP0) blurnormals(blurlms, w->w, w->h, w->raybuf, (const bvec *)w->raydata, blurlms); + w->lastlightmap->w = (w->w -= 2*blurlms); + w->lastlightmap->h = (w->h -= 2*blurlms); + } + if(mincolor[3]==255) return SURFACE_LIGHTMAP_TOP; + else if(maxcolor[3]==0) return SURFACE_LIGHTMAP_BOTTOM; + else return SURFACE_LIGHTMAP_BLEND; } static int previewlightmapalpha(lightmapworker *w, float lpu, const vec &origin1, const vec &xstep1, const vec &ystep1, const vec &origin2, const vec &xstep2, const vec &ystep2, float side0, float sidestep) { - extern int fullbrightlevel; - float tolerance = 0.5 / lpu; - uchar *dst = w->colorbuf; - uchar minalpha = 255, maxalpha = 0; - float sidex = side0; - for(int y = 0; y < w->h; ++y, sidex += sidestep) - { - for(int x = 0; x < w->w; ++x, dst += 4) - { - vec u = x < sidex ? - vec(xstep1).mul(x).add(vec(ystep1).mul(y)).add(origin1) : - vec(xstep2).mul(x).add(vec(ystep2).mul(y)).add(origin2); - loopk(3) dst[k] = fullbrightlevel; - generatealpha(w, tolerance, u, dst[3]); - minalpha = min(minalpha, dst[3]); - maxalpha = max(maxalpha, dst[3]); - } - } - if(minalpha==255) return SURFACE_AMBIENT_TOP; - if(maxalpha==0) return SURFACE_AMBIENT_BOTTOM; - if(minalpha==maxalpha) w->w = w->h = 1; - if((w->type&LM_TYPE) == LM_BUMPMAP0) loopi(w->w*w->h) w->raybuf[i] = bvec(128, 128, 255); - return SURFACE_LIGHTMAP_BLEND; -} + extern int fullbrightlevel; + float tolerance = 0.5 / lpu; + uchar *dst = w->colorbuf; + uchar minalpha = 255, maxalpha = 0; + float sidex = side0; + for(int y = 0; y < w->h; ++y, sidex += sidestep) + { + for(int x = 0; x < w->w; ++x, dst += 4) + { + vec u = x < sidex ? + vec(xstep1).mul(x).add(vec(ystep1).mul(y)).add(origin1) : + vec(xstep2).mul(x).add(vec(ystep2).mul(y)).add(origin2); + loopk(3) dst[k] = fullbrightlevel; + generatealpha(w, tolerance, u, dst[3]); + minalpha = min(minalpha, dst[3]); + maxalpha = max(maxalpha, dst[3]); + } + } + if(minalpha==255) return SURFACE_AMBIENT_TOP; + if(maxalpha==0) return SURFACE_AMBIENT_BOTTOM; + if(minalpha==maxalpha) w->w = w->h = 1; + if((w->type&LM_TYPE) == LM_BUMPMAP0) loopi(w->w*w->h) w->raybuf[i] = bvec(128, 128, 255); + return SURFACE_LIGHTMAP_BLEND; +} 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<verts() + surf.verts; - loopk(numverts) - { - vertinfo &v = verts[k]; - v.u = 0; - v.v = 0; - v.norm = 0; - } - } - } - } - if(c[i].children) clearsurfaces(c[i].children); - } + 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<verts() + surf.verts; + loopk(numverts) + { + vertinfo &v = verts[k]; + v.u = 0; + v.v = 0; + v.norm = 0; + } + } + } + } + if(c[i].children) clearsurfaces(c[i].children); + } } #define LIGHTCACHESIZE 1024 static struct lightcacheentry { - int x, y; - vector lights; + int x, y; + vector lights; } lightcache[LIGHTCACHESIZE]; #define LIGHTCACHEHASH(x, y) (((((x)^(y))<<5) + (((x)^(y))>>5)) & (LIGHTCACHESIZE - 1)) @@ -1040,1015 +1040,1015 @@ VARF(lightcachesize, 4, 6, 12, clearlightcache()); void clearlightcache(int id) { - if(id >= 0) - { - const extentity &light = *entities::getents()[id]; - int radius = light.attr1; - if(radius) - { - for(int x = int(max(light.o.x-radius, 0.0f))>>lightcachesize, ex = int(min(light.o.x+radius, worldsize-1.0f))>>lightcachesize; x <= ex; x++) - for(int y = int(max(light.o.y-radius, 0.0f))>>lightcachesize, ey = int(min(light.o.y+radius, worldsize-1.0f))>>lightcachesize; y <= ey; y++) - { - lightcacheentry &lce = lightcache[LIGHTCACHEHASH(x, y)]; - if(lce.x != x || lce.y != y) continue; - lce.x = -1; - lce.lights.setsize(0); - } - return; - } - } - - for(lightcacheentry *lce = lightcache; lce < &lightcache[LIGHTCACHESIZE]; lce++) - { - lce->x = -1; - lce->lights.setsize(0); - } + if(id >= 0) + { + const extentity &light = *entities::getents()[id]; + int radius = light.attr1; + if(radius) + { + for(int x = int(max(light.o.x-radius, 0.0f))>>lightcachesize, ex = int(min(light.o.x+radius, worldsize-1.0f))>>lightcachesize; x <= ex; x++) + for(int y = int(max(light.o.y-radius, 0.0f))>>lightcachesize, ey = int(min(light.o.y+radius, worldsize-1.0f))>>lightcachesize; y <= ey; y++) + { + lightcacheentry &lce = lightcache[LIGHTCACHEHASH(x, y)]; + if(lce.x != x || lce.y != y) continue; + lce.x = -1; + lce.lights.setsize(0); + } + return; + } + } + + for(lightcacheentry *lce = lightcache; lce < &lightcache[LIGHTCACHESIZE]; lce++) + { + lce->x = -1; + lce->lights.setsize(0); + } } const vector &checklightcache(int x, int y) { - x >>= lightcachesize; - y >>= lightcachesize; - lightcacheentry &lce = lightcache[LIGHTCACHEHASH(x, y)]; - if(lce.x == x && lce.y == y) return lce.lights; - - lce.lights.setsize(0); - int csize = 1< &ents = entities::getents(); - loopv(ents) - { - const extentity &light = *ents[i]; - switch(light.type) - { - case ET_LIGHT: - { - int radius = light.attr1; - if(radius > 0) - { - if(light.o.x + radius < cx || light.o.x - radius > cx + csize || - light.o.y + radius < cy || light.o.y - radius > cy + csize) - continue; - } - break; - } - default: continue; - } - lce.lights.add(i); - } - - lce.x = x; - lce.y = y; - return lce.lights; + x >>= lightcachesize; + y >>= lightcachesize; + lightcacheentry &lce = lightcache[LIGHTCACHEHASH(x, y)]; + if(lce.x == x && lce.y == y) return lce.lights; + + lce.lights.setsize(0); + int csize = 1< &ents = entities::getents(); + loopv(ents) + { + const extentity &light = *ents[i]; + switch(light.type) + { + case ET_LIGHT: + { + int radius = light.attr1; + if(radius > 0) + { + if(light.o.x + radius < cx || light.o.x - radius > cx + csize || + light.o.y + radius < cy || light.o.y - radius > cy + csize) + continue; + } + break; + } + default: continue; + } + lce.lights.add(i); + } + + lce.x = x; + lce.y = y; + return lce.lights; } static inline void addlight(lightmapworker *w, const extentity &light, int cx, int cy, int cz, int size, const vec *v, const vec *n, int numv) { - int radius = light.attr1; - if(radius > 0) - { - if(light.o.x + radius < cx || light.o.x - radius > cx + size || - light.o.y + radius < cy || light.o.y - radius > cy + size || - light.o.z + radius < cz || light.o.z - radius > cz + size) - return; - } - - loopi(4) - { - vec p(light.o); - p.sub(v[i]); - float dist = p.dot(n[i]); - if(dist >= 0 && (!radius || dist < radius)) - { - w->lights.add(&light); - break; - } - } -} + int radius = light.attr1; + if(radius > 0) + { + if(light.o.x + radius < cx || light.o.x - radius > cx + size || + light.o.y + radius < cy || light.o.y - radius > cy + size || + light.o.z + radius < cz || light.o.z - radius > cz + size) + return; + } + + loopi(4) + { + vec p(light.o); + p.sub(v[i]); + float dist = p.dot(n[i]); + if(dist >= 0 && (!radius || dist < radius)) + { + w->lights.add(&light); + break; + } + } +} static bool findlights(lightmapworker *w, int cx, int cy, int cz, int size, const vec *v, const vec *n, int numv, const Slot &slot, const VSlot &vslot) { - w->lights.setsize(0); - const vector &ents = entities::getents(); - static volatile bool usinglightcache = false; - if(size <= 1< &lights = checklightcache(cx, cy); - loopv(lights) - { - const extentity &light = *ents[lights[i]]; - switch(light.type) - { - case ET_LIGHT: addlight(w, light, cx, cy, cz, size, v, n, numv); break; - } - } - if(lightlock) { usinglightcache = false; SDL_UnlockMutex(lightlock); } - } - else loopv(ents) - { - const extentity &light = *ents[i]; - switch(light.type) - { - case ET_LIGHT: addlight(w, light, cx, cy, cz, size, v, n, numv); break; - } - } - if(vslot.layer && (setblendmaporigin(w->blendmapcache, ivec(cx, cy, cz), size) || slot.layermask)) return true; - return w->lights.length() || hasskylight() || sunlight; + w->lights.setsize(0); + const vector &ents = entities::getents(); + static volatile bool usinglightcache = false; + if(size <= 1< &lights = checklightcache(cx, cy); + loopv(lights) + { + const extentity &light = *ents[lights[i]]; + switch(light.type) + { + case ET_LIGHT: addlight(w, light, cx, cy, cz, size, v, n, numv); break; + } + } + if(lightlock) { usinglightcache = false; SDL_UnlockMutex(lightlock); } + } + else loopv(ents) + { + const extentity &light = *ents[i]; + switch(light.type) + { + case ET_LIGHT: addlight(w, light, cx, cy, cz, size, v, n, numv); break; + } + } + if(vslot.layer && (setblendmaporigin(w->blendmapcache, ivec(cx, cy, cz), size) || slot.layermask)) return true; + return w->lights.length() || hasskylight() || sunlight; } static int packlightmaps(lightmapworker *w = NULL) { - int numpacked = 0; - for(; packidx < lightmaptasks[0].length(); packidx++, numpacked++) - { - lightmaptask &t = lightmaptasks[0][packidx]; - if(!t.lightmaps) break; - if(t.ext && t.c->ext != t.ext) - { - lightmapext &e = lightmapexts.add(); - e.c = t.c; - e.ext = t.ext; - } - progress = t.progress; - lightmapinfo *l = t.lightmaps; - if(l == (lightmapinfo *)-1) continue; - int space = 0; - for(; l && l->c == t.c; l = l->next) - { - l->packed = true; - space += l->bufsize; - if(l->surface < 0 || !t.ext) continue; - surfaceinfo &surf = t.ext->surfaces[l->surface]; - layoutinfo layout; - packlightmap(*l, layout); - int numverts = surf.numverts&MAXFACEVERTS; - vertinfo *verts = t.ext->verts() + surf.verts; - if(l->layers&LAYER_DUP) - { - if(l->type&LM_ALPHA) surf.lmid[0] = layout.lmid; - else { surf.lmid[1] = layout.lmid; verts += numverts; } - } - else - { - if(l->layers&LAYER_TOP) surf.lmid[0] = layout.lmid; - if(l->layers&LAYER_BOTTOM) surf.lmid[1] = layout.lmid; - } - ushort offsetx = layout.x*((USHRT_MAX+1)/LM_PACKW), offsety = layout.y*((USHRT_MAX+1)/LM_PACKH); - loopk(numverts) - { - vertinfo &v = verts[k]; - v.u += offsetx; - v.v += offsety; - } - } - if(t.worker == w) - { - w->bufused -= space; - w->bufstart = (w->bufstart + space)%LIGHTMAPBUFSIZE; - w->firstlightmap = l; - if(!l) - { - w->lastlightmap = NULL; - w->bufstart = w->bufused = 0; - } - } - if(t.worker->needspace) SDL_CondSignal(t.worker->spacecond); - } - return numpacked; + int numpacked = 0; + for(; packidx < lightmaptasks[0].length(); packidx++, numpacked++) + { + lightmaptask &t = lightmaptasks[0][packidx]; + if(!t.lightmaps) break; + if(t.ext && t.c->ext != t.ext) + { + lightmapext &e = lightmapexts.add(); + e.c = t.c; + e.ext = t.ext; + } + progress = t.progress; + lightmapinfo *l = t.lightmaps; + if(l == (lightmapinfo *)-1) continue; + int space = 0; + for(; l && l->c == t.c; l = l->next) + { + l->packed = true; + space += l->bufsize; + if(l->surface < 0 || !t.ext) continue; + surfaceinfo &surf = t.ext->surfaces[l->surface]; + layoutinfo layout; + packlightmap(*l, layout); + int numverts = surf.numverts&MAXFACEVERTS; + vertinfo *verts = t.ext->verts() + surf.verts; + if(l->layers&LAYER_DUP) + { + if(l->type&LM_ALPHA) surf.lmid[0] = layout.lmid; + else { surf.lmid[1] = layout.lmid; verts += numverts; } + } + else + { + if(l->layers&LAYER_TOP) surf.lmid[0] = layout.lmid; + if(l->layers&LAYER_BOTTOM) surf.lmid[1] = layout.lmid; + } + ushort offsetx = layout.x*((USHRT_MAX+1)/LM_PACKW), offsety = layout.y*((USHRT_MAX+1)/LM_PACKH); + loopk(numverts) + { + vertinfo &v = verts[k]; + v.u += offsetx; + v.v += offsety; + } + } + if(t.worker == w) + { + w->bufused -= space; + w->bufstart = (w->bufstart + space)%LIGHTMAPBUFSIZE; + w->firstlightmap = l; + if(!l) + { + w->lastlightmap = NULL; + w->bufstart = w->bufused = 0; + } + } + if(t.worker->needspace) SDL_CondSignal(t.worker->spacecond); + } + return numpacked; } static lightmapinfo *alloclightmap(lightmapworker *w) { - int needspace1 = sizeof(lightmapinfo) + w->w*w->h*w->bpp, - needspace2 = (w->type&LM_TYPE) == LM_BUMPMAP0 ? w->w*w->h*3 : 0, - needspace = needspace1 + needspace2, - bufend = (w->bufstart + w->bufused)%LIGHTMAPBUFSIZE, - availspace = LIGHTMAPBUFSIZE - w->bufused, - availspace1 = min(availspace, LIGHTMAPBUFSIZE - bufend), - availspace2 = min(availspace, w->bufstart); - if(availspace < needspace || (max(availspace1, availspace2) < needspace && (availspace1 < needspace1 || availspace2 < needspace2))) - { - if(tasklock) SDL_LockMutex(tasklock); - while(!w->doneworking) - { - lightmapinfo *l = w->firstlightmap; - for(; l && l->packed; l = l->next) - { - w->bufused -= l->bufsize; - w->bufstart = (w->bufstart + l->bufsize)%LIGHTMAPBUFSIZE; - } - w->firstlightmap = l; - if(!l) - { - w->lastlightmap = NULL; - w->bufstart = w->bufused = 0; - } - bufend = (w->bufstart + w->bufused)%LIGHTMAPBUFSIZE; - availspace = LIGHTMAPBUFSIZE - w->bufused; - availspace1 = min(availspace, LIGHTMAPBUFSIZE - bufend); - availspace2 = min(availspace, w->bufstart); - if(availspace >= needspace && (max(availspace1, availspace2) >= needspace || (availspace1 >= needspace1 && availspace2 >= needspace2))) break; - if(packlightmaps(w)) continue; - if(!w->spacecond || !tasklock) break; - w->needspace = true; - SDL_CondWait(w->spacecond, tasklock); - w->needspace = false; - } - if(tasklock) SDL_UnlockMutex(tasklock); - } - int usedspace = needspace; - lightmapinfo *l = NULL; - if(availspace1 >= needspace1) - { - l = (lightmapinfo *)&w->buf[bufend]; - w->colorbuf = (uchar *)(l + 1); - if((w->type&LM_TYPE) != LM_BUMPMAP0) w->raybuf = NULL; - else if(availspace1 >= needspace) w->raybuf = (bvec *)&w->buf[bufend + needspace1]; - else - { - w->raybuf = (bvec *)w->buf; - usedspace += availspace1 - needspace1; - } - } - else if(availspace2 >= needspace) - { - usedspace += availspace1; - l = (lightmapinfo *)w->buf; - w->colorbuf = (uchar *)(l + 1); - w->raybuf = (w->type&LM_TYPE) == LM_BUMPMAP0 ? (bvec *)&w->buf[needspace1] : NULL; - } - else return NULL; - w->bufused += usedspace; - l->next = NULL; - l->c = w->c; - l->type = w->type; - l->w = w->w; - l->h = w->h; - l->bpp = w->bpp; - l->colorbuf = w->colorbuf; - l->raybuf = w->raybuf; - l->packed = false; - l->bufsize = usedspace; - l->surface = -1; - l->layers = 0; - if(!w->firstlightmap) w->firstlightmap = l; - if(w->lastlightmap) w->lastlightmap->next = l; - w->lastlightmap = l; - if(!w->curlightmaps) w->curlightmaps = l; - return l; + int needspace1 = sizeof(lightmapinfo) + w->w*w->h*w->bpp, + needspace2 = (w->type&LM_TYPE) == LM_BUMPMAP0 ? w->w*w->h*3 : 0, + needspace = needspace1 + needspace2, + bufend = (w->bufstart + w->bufused)%LIGHTMAPBUFSIZE, + availspace = LIGHTMAPBUFSIZE - w->bufused, + availspace1 = min(availspace, LIGHTMAPBUFSIZE - bufend), + availspace2 = min(availspace, w->bufstart); + if(availspace < needspace || (max(availspace1, availspace2) < needspace && (availspace1 < needspace1 || availspace2 < needspace2))) + { + if(tasklock) SDL_LockMutex(tasklock); + while(!w->doneworking) + { + lightmapinfo *l = w->firstlightmap; + for(; l && l->packed; l = l->next) + { + w->bufused -= l->bufsize; + w->bufstart = (w->bufstart + l->bufsize)%LIGHTMAPBUFSIZE; + } + w->firstlightmap = l; + if(!l) + { + w->lastlightmap = NULL; + w->bufstart = w->bufused = 0; + } + bufend = (w->bufstart + w->bufused)%LIGHTMAPBUFSIZE; + availspace = LIGHTMAPBUFSIZE - w->bufused; + availspace1 = min(availspace, LIGHTMAPBUFSIZE - bufend); + availspace2 = min(availspace, w->bufstart); + if(availspace >= needspace && (max(availspace1, availspace2) >= needspace || (availspace1 >= needspace1 && availspace2 >= needspace2))) break; + if(packlightmaps(w)) continue; + if(!w->spacecond || !tasklock) break; + w->needspace = true; + SDL_CondWait(w->spacecond, tasklock); + w->needspace = false; + } + if(tasklock) SDL_UnlockMutex(tasklock); + } + int usedspace = needspace; + lightmapinfo *l = NULL; + if(availspace1 >= needspace1) + { + l = (lightmapinfo *)&w->buf[bufend]; + w->colorbuf = (uchar *)(l + 1); + if((w->type&LM_TYPE) != LM_BUMPMAP0) w->raybuf = NULL; + else if(availspace1 >= needspace) w->raybuf = (bvec *)&w->buf[bufend + needspace1]; + else + { + w->raybuf = (bvec *)w->buf; + usedspace += availspace1 - needspace1; + } + } + else if(availspace2 >= needspace) + { + usedspace += availspace1; + l = (lightmapinfo *)w->buf; + w->colorbuf = (uchar *)(l + 1); + w->raybuf = (w->type&LM_TYPE) == LM_BUMPMAP0 ? (bvec *)&w->buf[needspace1] : NULL; + } + else return NULL; + w->bufused += usedspace; + l->next = NULL; + l->c = w->c; + l->type = w->type; + l->w = w->w; + l->h = w->h; + l->bpp = w->bpp; + l->colorbuf = w->colorbuf; + l->raybuf = w->raybuf; + l->packed = false; + l->bufsize = usedspace; + l->surface = -1; + l->layers = 0; + if(!w->firstlightmap) w->firstlightmap = l; + if(w->lastlightmap) w->lastlightmap->next = l; + w->lastlightmap = l; + if(!w->curlightmaps) w->curlightmaps = l; + return l; } static void freelightmap(lightmapworker *w) { - lightmapinfo *l = w->lastlightmap; - if(!l || l->surface >= 0) return; - if(w->firstlightmap == w->lastlightmap) - { - w->firstlightmap = w->lastlightmap = w->curlightmaps = NULL; - w->bufstart = w->bufused = 0; - } - else - { - w->bufused -= l->bufsize - sizeof(lightmapinfo); - l->bufsize = sizeof(lightmapinfo); - l->packed = true; - } - if(w->curlightmaps == l) w->curlightmaps = NULL; + lightmapinfo *l = w->lastlightmap; + if(!l || l->surface >= 0) return; + if(w->firstlightmap == w->lastlightmap) + { + w->firstlightmap = w->lastlightmap = w->curlightmaps = NULL; + w->bufstart = w->bufused = 0; + } + else + { + w->bufused -= l->bufsize - sizeof(lightmapinfo); + l->bufsize = sizeof(lightmapinfo); + l->packed = true; + } + if(w->curlightmaps == l) w->curlightmaps = NULL; } static int setupsurface(lightmapworker *w, plane planes[2], int numplanes, const vec *p, const vec *n, int numverts, vertinfo *litverts, bool preview = false) { - vec u, v, t; - vec2 c[MAXFACEVERTS]; - - u = vec(p[2]).sub(p[0]).normalize(); - v.cross(planes[0], u); - c[0] = vec2(0, 0); - if(numplanes >= 2) t.cross(planes[1], u); else t = v; - vec r1 = vec(p[1]).sub(p[0]); - c[1] = vec2(r1.dot(u), min(r1.dot(v), 0.0f)); - c[2] = vec2(vec(p[2]).sub(p[0]).dot(u), 0); - for(int i = 3; i < numverts; i++) - { - vec r = vec(p[i]).sub(p[0]); - c[i] = vec2(r.dot(u), max(r.dot(t), 0.0f)); - } - - float carea = 1e16f; - vec2 cx(0, 0), cy(0, 0), co(0, 0), cmin(0, 0), cmax(0, 0); - loopi(numverts) - { - vec2 px = vec2(c[i+1 < numverts ? i+1 : 0]).sub(c[i]); - float len = px.squaredlen(); - if(!len) continue; - px.mul(1/sqrtf(len)); - vec2 py(-px.y, px.x), pmin(0, 0), pmax(0, 0); - if(numplanes >= 2 && (i == 0 || i >= 3)) px.neg(); - loopj(numverts) - { - vec2 rj = vec2(c[j]).sub(c[i]), pj(rj.dot(px), rj.dot(py)); - pmin.x = min(pmin.x, pj.x); - pmin.y = min(pmin.y, pj.y); - pmax.x = max(pmax.x, pj.x); - pmax.y = max(pmax.y, pj.y); - } - float area = (pmax.x-pmin.x)*(pmax.y-pmin.y); - if(area < carea) { carea = area; cx = px; cy = py; co = c[i]; cmin = pmin; cmax = pmax; } - } - - int scale = int(min(cmax.x - cmin.x, cmax.y - cmin.y)); - float lpu = 16.0f / float(lightlod && scale < (1 << lightlod) ? max(lightprecision / 2, 1) : lightprecision); - int lw = clamp(int(ceil((cmax.x - cmin.x + 1)*lpu)), LM_MINW, LM_MAXW), lh = clamp(int(ceil((cmax.y - cmin.y + 1)*lpu)), LM_MINH, LM_MAXH); - w->w = lw; - w->h = lh; - if(!preview) - { - w->w += 2*blurlms; - w->h += 2*blurlms; - } - if(!alloclightmap(w)) return NO_SURFACE; - - vec2 cscale = vec2(cmax).sub(cmin).div(vec2(lw-1, lh-1)), - comin = vec2(cx).mul(cmin.x).add(vec2(cy).mul(cmin.y)).add(co); - loopi(numverts) - { - vec2 ri = vec2(c[i]).sub(comin); - c[i] = vec2(ri.dot(cx)/cscale.x, ri.dot(cy)/cscale.y); - } - - vec xstep1 = vec(v).mul(cx.y).add(vec(u).mul(cx.x)).mul(cscale.x), - ystep1 = vec(v).mul(cy.y).add(vec(u).mul(cy.x)).mul(cscale.y), - origin1 = vec(v).mul(comin.y).add(vec(u).mul(comin.x)).add(p[0]), - xstep2 = xstep1, ystep2 = ystep1, origin2 = origin1; - float side0 = LM_MAXW + 1, sidestep = 0; - if(numplanes >= 2) - { - xstep2 = vec(t).mul(cx.y).add(vec(u).mul(cx.x)).mul(cscale.x); - ystep2 = vec(t).mul(cy.y).add(vec(u).mul(cy.x)).mul(cscale.y); - origin2 = vec(t).mul(comin.y).add(vec(u).mul(comin.x)).add(p[0]); - if(cx.y) { side0 = comin.y/-(cx.y*cscale.x); sidestep = cy.y*cscale.y/-(cx.y*cscale.x); } - else if(cy.y) { side0 = ceil(comin.y/-(cy.y*cscale.y))*(LM_MAXW + 1); sidestep = -(LM_MAXW + 1); if(cy.y < 0) { side0 = (LM_MAXW + 1) - side0; sidestep = -sidestep; } } - else side0 = comin.y <= 0 ? LM_MAXW + 1 : -1; - } - - int surftype = NO_SURFACE; - if(preview) - { - surftype = previewlightmapalpha(w, lpu, origin1, xstep1, ystep1, origin2, xstep2, ystep2, side0, sidestep); - } - else - { - lerpvert lv[MAXFACEVERTS]; - int numv = numverts; - calclerpverts(c, n, lv, numv); - - if(!generatelightmap(w, lpu, lv, numv, origin1, xstep1, ystep1, origin2, xstep2, ystep2, side0, sidestep)) return NO_SURFACE; - surftype = finishlightmap(w); - } - if(surftypew) texscale.x *= float(w->w - 1) / (lw - 1); - if(lh != w->h) texscale.y *= float(w->h - 1) / (lh - 1); - loopk(numverts) - { - litverts[k].u = ushort(floor(clamp(c[k].x*texscale.x, 0.0f, float(USHRT_MAX)))); - litverts[k].v = ushort(floor(clamp(c[k].y*texscale.y, 0.0f, float(USHRT_MAX)))); - } - return surftype; + vec u, v, t; + vec2 c[MAXFACEVERTS]; + + u = vec(p[2]).sub(p[0]).normalize(); + v.cross(planes[0], u); + c[0] = vec2(0, 0); + if(numplanes >= 2) t.cross(planes[1], u); else t = v; + vec r1 = vec(p[1]).sub(p[0]); + c[1] = vec2(r1.dot(u), min(r1.dot(v), 0.0f)); + c[2] = vec2(vec(p[2]).sub(p[0]).dot(u), 0); + for(int i = 3; i < numverts; i++) + { + vec r = vec(p[i]).sub(p[0]); + c[i] = vec2(r.dot(u), max(r.dot(t), 0.0f)); + } + + float carea = 1e16f; + vec2 cx(0, 0), cy(0, 0), co(0, 0), cmin(0, 0), cmax(0, 0); + loopi(numverts) + { + vec2 px = vec2(c[i+1 < numverts ? i+1 : 0]).sub(c[i]); + float len = px.squaredlen(); + if(!len) continue; + px.mul(1/sqrtf(len)); + vec2 py(-px.y, px.x), pmin(0, 0), pmax(0, 0); + if(numplanes >= 2 && (i == 0 || i >= 3)) px.neg(); + loopj(numverts) + { + vec2 rj = vec2(c[j]).sub(c[i]), pj(rj.dot(px), rj.dot(py)); + pmin.x = min(pmin.x, pj.x); + pmin.y = min(pmin.y, pj.y); + pmax.x = max(pmax.x, pj.x); + pmax.y = max(pmax.y, pj.y); + } + float area = (pmax.x-pmin.x)*(pmax.y-pmin.y); + if(area < carea) { carea = area; cx = px; cy = py; co = c[i]; cmin = pmin; cmax = pmax; } + } + + int scale = int(min(cmax.x - cmin.x, cmax.y - cmin.y)); + float lpu = 16.0f / float(lightlod && scale < (1 << lightlod) ? max(lightprecision / 2, 1) : lightprecision); + int lw = clamp(int(ceil((cmax.x - cmin.x + 1)*lpu)), LM_MINW, LM_MAXW), lh = clamp(int(ceil((cmax.y - cmin.y + 1)*lpu)), LM_MINH, LM_MAXH); + w->w = lw; + w->h = lh; + if(!preview) + { + w->w += 2*blurlms; + w->h += 2*blurlms; + } + if(!alloclightmap(w)) return NO_SURFACE; + + vec2 cscale = vec2(cmax).sub(cmin).div(vec2(lw-1, lh-1)), + comin = vec2(cx).mul(cmin.x).add(vec2(cy).mul(cmin.y)).add(co); + loopi(numverts) + { + vec2 ri = vec2(c[i]).sub(comin); + c[i] = vec2(ri.dot(cx)/cscale.x, ri.dot(cy)/cscale.y); + } + + vec xstep1 = vec(v).mul(cx.y).add(vec(u).mul(cx.x)).mul(cscale.x), + ystep1 = vec(v).mul(cy.y).add(vec(u).mul(cy.x)).mul(cscale.y), + origin1 = vec(v).mul(comin.y).add(vec(u).mul(comin.x)).add(p[0]), + xstep2 = xstep1, ystep2 = ystep1, origin2 = origin1; + float side0 = LM_MAXW + 1, sidestep = 0; + if(numplanes >= 2) + { + xstep2 = vec(t).mul(cx.y).add(vec(u).mul(cx.x)).mul(cscale.x); + ystep2 = vec(t).mul(cy.y).add(vec(u).mul(cy.x)).mul(cscale.y); + origin2 = vec(t).mul(comin.y).add(vec(u).mul(comin.x)).add(p[0]); + if(cx.y) { side0 = comin.y/-(cx.y*cscale.x); sidestep = cy.y*cscale.y/-(cx.y*cscale.x); } + else if(cy.y) { side0 = ceil(comin.y/-(cy.y*cscale.y))*(LM_MAXW + 1); sidestep = -(LM_MAXW + 1); if(cy.y < 0) { side0 = (LM_MAXW + 1) - side0; sidestep = -sidestep; } } + else side0 = comin.y <= 0 ? LM_MAXW + 1 : -1; + } + + int surftype = NO_SURFACE; + if(preview) + { + surftype = previewlightmapalpha(w, lpu, origin1, xstep1, ystep1, origin2, xstep2, ystep2, side0, sidestep); + } + else + { + lerpvert lv[MAXFACEVERTS]; + int numv = numverts; + calclerpverts(c, n, lv, numv); + + if(!generatelightmap(w, lpu, lv, numv, origin1, xstep1, ystep1, origin2, xstep2, ystep2, side0, sidestep)) return NO_SURFACE; + surftype = finishlightmap(w); + } + if(surftypew) texscale.x *= float(w->w - 1) / (lw - 1); + if(lh != w->h) texscale.y *= float(w->h - 1) / (lh - 1); + loopk(numverts) + { + litverts[k].u = ushort(floor(clamp(c[k].x*texscale.x, 0.0f, float(USHRT_MAX)))); + litverts[k].v = ushort(floor(clamp(c[k].y*texscale.y, 0.0f, float(USHRT_MAX)))); + } + return surftype; } static void removelmalpha(lightmapworker *w) { - if(!(w->type&LM_ALPHA)) return; - for(uchar *dst = w->colorbuf, *src = w->colorbuf, *end = &src[w->w*w->h*4]; - src < end; - dst += 3, src += 4) - { - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - } - w->type &= ~LM_ALPHA; - w->bpp = 3; - w->lastlightmap->type = w->type; - w->lastlightmap->bpp = w->bpp; + if(!(w->type&LM_ALPHA)) return; + for(uchar *dst = w->colorbuf, *src = w->colorbuf, *end = &src[w->w*w->h*4]; + src < end; + dst += 3, src += 4) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + w->type &= ~LM_ALPHA; + w->bpp = 3; + w->lastlightmap->type = w->type; + w->lastlightmap->bpp = w->bpp; } static lightmapinfo *setupsurfaces(lightmapworker *w, lightmaptask &task) { - cube &c = *task.c; - const ivec &co = task.o; - int size = task.size, usefacemask = task.usefaces; - - w->curlightmaps = NULL; - w->c = &c; - - surfaceinfo surfaces[6]; - vertinfo litverts[6*2*MAXFACEVERTS]; - int numlitverts = 0; - memclear(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<slot = vslot.slot; - w->vslot = &vslot; - w->type = shader->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE; - if(layer) w->type |= LM_ALPHA; - w->bpp = w->type&LM_ALPHA ? 4 : 3; - w->orient = i; - w->rotate = vslot.rotation; - int surftype = setupsurface(w, planes, numplanes, pos, n, numverts, curlitverts); - switch(surftype) - { - case SURFACE_LIGHTMAP_BOTTOM: - if((shader->type^layer->slot->shader->type)&SHADER_NORMALSLMS || - (shader->type&SHADER_NORMALSLMS && vslot.rotation!=layer->rotation)) - { - freelightmap(w); - break; - } - // fall through - case SURFACE_LIGHTMAP_BLEND: - case SURFACE_LIGHTMAP_TOP: - { - if(!(surf.numverts&MAXFACEVERTS)) - { - surf.verts = numlitverts; - surf.numverts |= numverts; - numlitverts += numverts; - } - - w->lastlightmap->surface = i; - w->lastlightmap->layers = (surftype==SURFACE_LIGHTMAP_BOTTOM ? LAYER_BOTTOM : LAYER_TOP); - if(surftype==SURFACE_LIGHTMAP_BLEND) - { - surf.numverts |= LAYER_BLEND; - w->lastlightmap->layers = LAYER_TOP; - if((shader->type^layer->slot->shader->type)&SHADER_NORMALSLMS || - (shader->type&SHADER_NORMALSLMS && vslot.rotation!=layer->rotation)) - break; - w->lastlightmap->layers |= LAYER_BOTTOM; - } - else - { - if(surftype==SURFACE_LIGHTMAP_BOTTOM) - { - surf.numverts |= LAYER_BOTTOM; - w->lastlightmap->layers = LAYER_BOTTOM; - } - else - { - surf.numverts |= LAYER_TOP; - w->lastlightmap->layers = LAYER_TOP; - } - if(w->type&LM_ALPHA) removelmalpha(w); - } - continue; - } - - case SURFACE_AMBIENT_BOTTOM: - freelightmap(w); - surf.numverts |= layer ? LAYER_BOTTOM : LAYER_TOP; - continue; - - case SURFACE_AMBIENT_TOP: - freelightmap(w); - surf.numverts |= LAYER_TOP; - continue; - - default: - freelightmap(w); - continue; - } - - w->slot = layer->slot; - w->vslot = layer; - w->type = layer->slot->shader->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE; - w->bpp = 3; - w->rotate = layer->rotation; - vertinfo *blendverts = surf.numverts&MAXFACEVERTS ? &curlitverts[numverts] : curlitverts; - switch(setupsurface(w, planes, numplanes, pos, n, numverts, blendverts)) - { - case SURFACE_LIGHTMAP_TOP: - { - if(!(surf.numverts&MAXFACEVERTS)) - { - surf.verts = numlitverts; - surf.numverts |= numverts; - numlitverts += numverts; - } - else if(!(surf.numverts&LAYER_DUP)) - { - surf.numverts |= LAYER_DUP; - w->lastlightmap->layers |= LAYER_DUP; - loopk(numverts) - { - vertinfo &src = curlitverts[k]; - vertinfo &dst = blendverts[k]; - dst.setxyz(src.getxyz()); - dst.norm = src.norm; - } - numlitverts += numverts; - } - surf.numverts |= LAYER_BOTTOM; - w->lastlightmap->layers |= LAYER_BOTTOM; - - w->lastlightmap->surface = i; - break; - } - - case SURFACE_AMBIENT_TOP: - { - freelightmap(w); - surf.numverts |= LAYER_BOTTOM; - break; - } - - default: freelightmap(w); break; - } - } - 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)); - task.ext = ext; - break; - } - } - return w->curlightmaps ? w->curlightmaps : (lightmapinfo *)-1; + cube &c = *task.c; + const ivec &co = task.o; + int size = task.size, usefacemask = task.usefaces; + + w->curlightmaps = NULL; + w->c = &c; + + surfaceinfo surfaces[6]; + vertinfo litverts[6*2*MAXFACEVERTS]; + int numlitverts = 0; + memclear(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<slot = vslot.slot; + w->vslot = &vslot; + w->type = shader->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE; + if(layer) w->type |= LM_ALPHA; + w->bpp = w->type&LM_ALPHA ? 4 : 3; + w->orient = i; + w->rotate = vslot.rotation; + int surftype = setupsurface(w, planes, numplanes, pos, n, numverts, curlitverts); + switch(surftype) + { + case SURFACE_LIGHTMAP_BOTTOM: + if((shader->type^layer->slot->shader->type)&SHADER_NORMALSLMS || + (shader->type&SHADER_NORMALSLMS && vslot.rotation!=layer->rotation)) + { + freelightmap(w); + break; + } + // fall through + case SURFACE_LIGHTMAP_BLEND: + case SURFACE_LIGHTMAP_TOP: + { + if(!(surf.numverts&MAXFACEVERTS)) + { + surf.verts = numlitverts; + surf.numverts |= numverts; + numlitverts += numverts; + } + + w->lastlightmap->surface = i; + w->lastlightmap->layers = (surftype==SURFACE_LIGHTMAP_BOTTOM ? LAYER_BOTTOM : LAYER_TOP); + if(surftype==SURFACE_LIGHTMAP_BLEND) + { + surf.numverts |= LAYER_BLEND; + w->lastlightmap->layers = LAYER_TOP; + if((shader->type^layer->slot->shader->type)&SHADER_NORMALSLMS || + (shader->type&SHADER_NORMALSLMS && vslot.rotation!=layer->rotation)) + break; + w->lastlightmap->layers |= LAYER_BOTTOM; + } + else + { + if(surftype==SURFACE_LIGHTMAP_BOTTOM) + { + surf.numverts |= LAYER_BOTTOM; + w->lastlightmap->layers = LAYER_BOTTOM; + } + else + { + surf.numverts |= LAYER_TOP; + w->lastlightmap->layers = LAYER_TOP; + } + if(w->type&LM_ALPHA) removelmalpha(w); + } + continue; + } + + case SURFACE_AMBIENT_BOTTOM: + freelightmap(w); + surf.numverts |= layer ? LAYER_BOTTOM : LAYER_TOP; + continue; + + case SURFACE_AMBIENT_TOP: + freelightmap(w); + surf.numverts |= LAYER_TOP; + continue; + + default: + freelightmap(w); + continue; + } + + w->slot = layer->slot; + w->vslot = layer; + w->type = layer->slot->shader->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE; + w->bpp = 3; + w->rotate = layer->rotation; + vertinfo *blendverts = surf.numverts&MAXFACEVERTS ? &curlitverts[numverts] : curlitverts; + switch(setupsurface(w, planes, numplanes, pos, n, numverts, blendverts)) + { + case SURFACE_LIGHTMAP_TOP: + { + if(!(surf.numverts&MAXFACEVERTS)) + { + surf.verts = numlitverts; + surf.numverts |= numverts; + numlitverts += numverts; + } + else if(!(surf.numverts&LAYER_DUP)) + { + surf.numverts |= LAYER_DUP; + w->lastlightmap->layers |= LAYER_DUP; + loopk(numverts) + { + vertinfo &src = curlitverts[k]; + vertinfo &dst = blendverts[k]; + dst.setxyz(src.getxyz()); + dst.norm = src.norm; + } + numlitverts += numverts; + } + surf.numverts |= LAYER_BOTTOM; + w->lastlightmap->layers |= LAYER_BOTTOM; + + w->lastlightmap->surface = i; + break; + } + + case SURFACE_AMBIENT_TOP: + { + freelightmap(w); + surf.numverts |= LAYER_BOTTOM; + break; + } + + default: freelightmap(w); break; + } + } + 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)); + task.ext = ext; + break; + } + } + return w->curlightmaps ? w->curlightmaps : (lightmapinfo *)-1; } int lightmapworker::work(void *data) { - lightmapworker *w = (lightmapworker *)data; - SDL_LockMutex(tasklock); - while(!w->doneworking) - { - if(allocidx < lightmaptasks[0].length()) - { - lightmaptask &t = lightmaptasks[0][allocidx++]; - t.worker = w; - SDL_UnlockMutex(tasklock); - lightmapinfo *l = setupsurfaces(w, t); - SDL_LockMutex(tasklock); - t.lightmaps = l; - packlightmaps(w); - } - else - { - if(packidx >= lightmaptasks[0].length()) SDL_CondSignal(emptycond); - SDL_CondWait(fullcond, tasklock); - } - } - SDL_UnlockMutex(tasklock); - return 0; + lightmapworker *w = (lightmapworker *)data; + SDL_LockMutex(tasklock); + while(!w->doneworking) + { + if(allocidx < lightmaptasks[0].length()) + { + lightmaptask &t = lightmaptasks[0][allocidx++]; + t.worker = w; + SDL_UnlockMutex(tasklock); + lightmapinfo *l = setupsurfaces(w, t); + SDL_LockMutex(tasklock); + t.lightmaps = l; + packlightmaps(w); + } + else + { + if(packidx >= lightmaptasks[0].length()) SDL_CondSignal(emptycond); + SDL_CondWait(fullcond, tasklock); + } + } + SDL_UnlockMutex(tasklock); + return 0; } static bool processtasks(bool finish = false) { - if(tasklock) SDL_LockMutex(tasklock); - while(finish || lightmaptasks[1].length()) - { - if(packidx >= lightmaptasks[0].length()) - { - if(lightmaptasks[1].empty()) break; - lightmaptasks[0].setsize(0); - lightmaptasks[0].move(lightmaptasks[1]); - packidx = allocidx = 0; - if(fullcond) SDL_CondBroadcast(fullcond); - } - else if(lightmapping > 1) - { - SDL_CondWaitTimeout(emptycond, tasklock, 250); - CHECK_PROGRESS_LOCKED({ SDL_UnlockMutex(tasklock); return false; }, SDL_UnlockMutex(tasklock), SDL_LockMutex(tasklock)); - } - else - { - while(allocidx < lightmaptasks[0].length()) - { - lightmaptask &t = lightmaptasks[0][allocidx++]; - t.worker = lightmapworkers[0]; - t.lightmaps = setupsurfaces(lightmapworkers[0], t); - packlightmaps(lightmapworkers[0]); - CHECK_PROGRESS(return false); - } - } - } - if(tasklock) SDL_UnlockMutex(tasklock); - return true; + if(tasklock) SDL_LockMutex(tasklock); + while(finish || lightmaptasks[1].length()) + { + if(packidx >= lightmaptasks[0].length()) + { + if(lightmaptasks[1].empty()) break; + lightmaptasks[0].setsize(0); + lightmaptasks[0].move(lightmaptasks[1]); + packidx = allocidx = 0; + if(fullcond) SDL_CondBroadcast(fullcond); + } + else if(lightmapping > 1) + { + SDL_CondWaitTimeout(emptycond, tasklock, 250); + CHECK_PROGRESS_LOCKED({ SDL_UnlockMutex(tasklock); return false; }, SDL_UnlockMutex(tasklock), SDL_LockMutex(tasklock)); + } + else + { + while(allocidx < lightmaptasks[0].length()) + { + lightmaptask &t = lightmaptasks[0][allocidx++]; + t.worker = lightmapworkers[0]; + t.lightmaps = setupsurfaces(lightmapworkers[0], t); + packlightmaps(lightmapworkers[0]); + CHECK_PROGRESS(return false); + } + } + } + if(tasklock) SDL_UnlockMutex(tasklock); + return true; } static void generatelightmaps(cube *c, const ivec &co, int size) { - CHECK_PROGRESS(return); - - taskprogress++; - - loopi(8) - { - ivec o(i, co, size); - if(c[i].children) - generatelightmaps(c[i].children, o, size >> 1); - else if(!isempty(c[i])) - { - if(c[i].ext) - { - loopj(6) - { - surfaceinfo &surf = c[i].ext->surfaces[j]; - if(surf.lmid[0] >= LMID_RESERVED || surf.lmid[1] >= LMID_RESERVED) goto nextcube; - surf.clear(); - } - } - int usefacemask = 0; - loopj(6) if(c[i].texture[j] != DEFAULT_SKY && (!(c[i].merged&(1<surfaces[j].numverts&MAXFACEVERTS))) - { - usefacemask |= visibletris(c[i], j, o, size)<<(4*j); - } - if(usefacemask) - { - lightmaptask &t = lightmaptasks[1].add(); - t.o = o; - t.size = size; - t.usefaces = usefacemask; - t.c = &c[i]; - t.ext = NULL; - t.lightmaps = NULL; - t.progress = taskprogress; - if(lightmaptasks[1].length() >= MAXLIGHTMAPTASKS && !processtasks()) return; - } - } - nextcube:; - } + CHECK_PROGRESS(return); + + taskprogress++; + + loopi(8) + { + ivec o(i, co, size); + if(c[i].children) + generatelightmaps(c[i].children, o, size >> 1); + else if(!isempty(c[i])) + { + if(c[i].ext) + { + loopj(6) + { + surfaceinfo &surf = c[i].ext->surfaces[j]; + if(surf.lmid[0] >= LMID_RESERVED || surf.lmid[1] >= LMID_RESERVED) goto nextcube; + surf.clear(); + } + } + int usefacemask = 0; + loopj(6) if(c[i].texture[j] != DEFAULT_SKY && (!(c[i].merged&(1<surfaces[j].numverts&MAXFACEVERTS))) + { + usefacemask |= visibletris(c[i], j, o, size)<<(4*j); + } + if(usefacemask) + { + lightmaptask &t = lightmaptasks[1].add(); + t.o = o; + t.size = size; + t.usefaces = usefacemask; + t.c = &c[i]; + t.ext = NULL; + t.lightmaps = NULL; + t.progress = taskprogress; + if(lightmaptasks[1].length() >= MAXLIGHTMAPTASKS && !processtasks()) return; + } + } + nextcube:; + } } static bool previewblends(lightmapworker *w, cube &c, const ivec &co, int size) { - if(isempty(c) || c.material&MAT_ALPHA) return false; - - int usefacemask = 0; - loopi(6) if(c.texture[i] != DEFAULT_SKY && lookupvslot(c.texture[i], false).layer) - usefacemask |= visibletris(c, i, co, size)<<(4*i); - if(!usefacemask) return false; - - if(!setblendmaporigin(w->blendmapcache, co, size)) - { - 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; - } - - w->firstlightmap = w->lastlightmap = w->curlightmaps = NULL; - w->bufstart = w->bufused = 0; - w->c = &c; - - surfaceinfo surfaces[6]; - vertinfo litverts[6*2*MAXFACEVERTS]; - int numlitverts = 0; - memcpy(surfaces, c.ext ? c.ext->surfaces : brightsurfaces, sizeof(surfaces)); - loopi(6) - { - int usefaces = usefacemask&0xF; - usefacemask >>= 4; - if(!usefaces) - { - surfaceinfo &surf = 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 = lookupvslot(vslot.layer, false); - Shader *shader = vslot.slot->shader; - int shadertype = shader->type | layer.slot->shader->type; - - vertinfo *curlitverts = &litverts[numlitverts]; - int numverts = 0; - ivec v[4]; - genfaceverts(c, i, v); - int convex = flataxisface(c, i) ? 0 : faceconvexity(v), - 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[4], n[4], po(ivec(co).mask(~0xFFF)); - loopj(numverts) pos[j] = vec(curlitverts[j].getxyz()).mul(1.0f/8).add(po); - - plane planes[2]; - int numplanes = 0; - planes[numplanes++].toplane(pos[0], pos[1], pos[2]); - if(numverts < 4 || !convex) loopk(numverts) n[k] = planes[0]; - else - { - planes[numplanes++].toplane(pos[0], pos[2], pos[3]); - vec avg = vec(planes[0]).add(planes[1]).normalize(); - n[0] = avg; - n[1] = planes[0]; - n[2] = avg; - for(int k = 3; k < numverts; k++) n[k] = planes[1]; - } - - surfaceinfo &surf = surfaces[i]; - w->slot = vslot.slot; - w->vslot = &vslot; - w->type = shadertype&SHADER_NORMALSLMS ? LM_BUMPMAP0|LM_ALPHA : LM_DIFFUSE|LM_ALPHA; - w->bpp = 4; - w->orient = i; - w->rotate = vslot.rotation; - int surftype = setupsurface(w, planes, numplanes, pos, n, numverts, curlitverts, true); - switch(surftype) - { - case SURFACE_AMBIENT_TOP: - surf = brightsurface; - continue; - - case SURFACE_AMBIENT_BOTTOM: - surf = brightbottomsurface; - continue; - - case SURFACE_LIGHTMAP_BLEND: - { - if(surf.numverts == (LAYER_BLEND|numverts) && - surf.lmid[0] == surf.lmid[1] && - (surf.numverts&MAXFACEVERTS) == numverts && - !memcmp(curlitverts, c.ext->verts() + surf.verts, numverts*sizeof(vertinfo)) && - lightmaps.inrange(surf.lmid[0]-LMID_RESERVED) && - lightmaps[surf.lmid[0]-LMID_RESERVED].type==w->type) - { - vertinfo *oldverts = c.ext->verts() + surf.verts; - layoutinfo layout; - layout.w = w->w; - layout.h = w->h; - layout.x = (oldverts[0].x - curlitverts[0].x)/((USHRT_MAX+1)/LM_PACKW); - layout.y = (oldverts[0].y - curlitverts[0].y)/((USHRT_MAX+1)/LM_PACKH); - if(LM_PACKW - layout.x >= w->w && LM_PACKH - layout.y >= w->h) - { - layout.lmid = surf.lmid[0]; - copylightmap(*w->lastlightmap, layout); - updatelightmap(layout); - surf.verts = numlitverts; - numlitverts += numverts; - continue; - } - } - - surf.verts = numlitverts; - surf.numverts = LAYER_BLEND|numverts; - numlitverts += numverts; - layoutinfo layout; - if(packlightmap(*w->lastlightmap, layout)) updatelightmap(layout); - surf.lmid[0] = surf.lmid[1] = layout.lmid; - ushort offsetx = layout.x*((USHRT_MAX+1)/LM_PACKW), offsety = layout.y*((USHRT_MAX+1)/LM_PACKH); - loopk(numverts) - { - vertinfo &v = curlitverts[k]; - v.u += offsetx; - v.v += offsety; - } - continue; - } - } - } - - setsurfaces(c, surfaces, litverts, numlitverts); - return true; + if(isempty(c) || c.material&MAT_ALPHA) return false; + + int usefacemask = 0; + loopi(6) if(c.texture[i] != DEFAULT_SKY && lookupvslot(c.texture[i], false).layer) + usefacemask |= visibletris(c, i, co, size)<<(4*i); + if(!usefacemask) return false; + + if(!setblendmaporigin(w->blendmapcache, co, size)) + { + 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; + } + + w->firstlightmap = w->lastlightmap = w->curlightmaps = NULL; + w->bufstart = w->bufused = 0; + w->c = &c; + + surfaceinfo surfaces[6]; + vertinfo litverts[6*2*MAXFACEVERTS]; + int numlitverts = 0; + memcpy(surfaces, c.ext ? c.ext->surfaces : brightsurfaces, sizeof(surfaces)); + loopi(6) + { + int usefaces = usefacemask&0xF; + usefacemask >>= 4; + if(!usefaces) + { + surfaceinfo &surf = 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 = lookupvslot(vslot.layer, false); + Shader *shader = vslot.slot->shader; + int shadertype = shader->type | layer.slot->shader->type; + + vertinfo *curlitverts = &litverts[numlitverts]; + int numverts = 0; + ivec v[4]; + genfaceverts(c, i, v); + int convex = flataxisface(c, i) ? 0 : faceconvexity(v), + 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[4], n[4], po(ivec(co).mask(~0xFFF)); + loopj(numverts) pos[j] = vec(curlitverts[j].getxyz()).mul(1.0f/8).add(po); + + plane planes[2]; + int numplanes = 0; + planes[numplanes++].toplane(pos[0], pos[1], pos[2]); + if(numverts < 4 || !convex) loopk(numverts) n[k] = planes[0]; + else + { + planes[numplanes++].toplane(pos[0], pos[2], pos[3]); + vec avg = vec(planes[0]).add(planes[1]).normalize(); + n[0] = avg; + n[1] = planes[0]; + n[2] = avg; + for(int k = 3; k < numverts; k++) n[k] = planes[1]; + } + + surfaceinfo &surf = surfaces[i]; + w->slot = vslot.slot; + w->vslot = &vslot; + w->type = shadertype&SHADER_NORMALSLMS ? LM_BUMPMAP0|LM_ALPHA : LM_DIFFUSE|LM_ALPHA; + w->bpp = 4; + w->orient = i; + w->rotate = vslot.rotation; + int surftype = setupsurface(w, planes, numplanes, pos, n, numverts, curlitverts, true); + switch(surftype) + { + case SURFACE_AMBIENT_TOP: + surf = brightsurface; + continue; + + case SURFACE_AMBIENT_BOTTOM: + surf = brightbottomsurface; + continue; + + case SURFACE_LIGHTMAP_BLEND: + { + if(surf.numverts == (LAYER_BLEND|numverts) && + surf.lmid[0] == surf.lmid[1] && + (surf.numverts&MAXFACEVERTS) == numverts && + !memcmp(curlitverts, c.ext->verts() + surf.verts, numverts*sizeof(vertinfo)) && + lightmaps.inrange(surf.lmid[0]-LMID_RESERVED) && + lightmaps[surf.lmid[0]-LMID_RESERVED].type==w->type) + { + vertinfo *oldverts = c.ext->verts() + surf.verts; + layoutinfo layout; + layout.w = w->w; + layout.h = w->h; + layout.x = (oldverts[0].x - curlitverts[0].x)/((USHRT_MAX+1)/LM_PACKW); + layout.y = (oldverts[0].y - curlitverts[0].y)/((USHRT_MAX+1)/LM_PACKH); + if(LM_PACKW - layout.x >= w->w && LM_PACKH - layout.y >= w->h) + { + layout.lmid = surf.lmid[0]; + copylightmap(*w->lastlightmap, layout); + updatelightmap(layout); + surf.verts = numlitverts; + numlitverts += numverts; + continue; + } + } + + surf.verts = numlitverts; + surf.numverts = LAYER_BLEND|numverts; + numlitverts += numverts; + layoutinfo layout; + if(packlightmap(*w->lastlightmap, layout)) updatelightmap(layout); + surf.lmid[0] = surf.lmid[1] = layout.lmid; + ushort offsetx = layout.x*((USHRT_MAX+1)/LM_PACKW), offsety = layout.y*((USHRT_MAX+1)/LM_PACKH); + loopk(numverts) + { + vertinfo &v = curlitverts[k]; + v.u += offsetx; + v.v += offsety; + } + continue; + } + } + } + + setsurfaces(c, surfaces, litverts, numlitverts); + return true; } static bool previewblends(lightmapworker *w, 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(w, c[i].children, o, size/2, bo, bs) : previewblends(w, c[i], o, size)) - { - changed = true; - ext = c[i].ext; - if(ext && ext->va) - { - destroyva(ext->va); - ext->va = NULL; - } - } - } - return changed; + 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(w, c[i].children, o, size/2, bo, bs) : previewblends(w, 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) { - loadlayermasks(); - if(lightmapworkers.empty()) lightmapworkers.add(new lightmapworker); - lightmapworkers[0]->reset(); - if(previewblends(lightmapworkers[0], worldroot, ivec(0, 0, 0), worldsize/2, bo, bs)) - commitchanges(true); + loadlayermasks(); + if(lightmapworkers.empty()) lightmapworkers.add(new lightmapworker); + lightmapworkers[0]->reset(); + if(previewblends(lightmapworkers[0], worldroot, ivec(0, 0, 0), worldsize/2, bo, bs)) + commitchanges(true); } - + void cleanuplightmaps() { - loopv(lightmaps) - { - LightMap &lm = lightmaps[i]; - lm.tex = lm.offsetx = lm.offsety = -1; - } - loopv(lightmaptexs) glDeleteTextures(1, &lightmaptexs[i].id); - lightmaptexs.shrink(0); - if(progresstex) { glDeleteTextures(1, &progresstex); progresstex = 0; } + loopv(lightmaps) + { + LightMap &lm = lightmaps[i]; + lm.tex = lm.offsetx = lm.offsety = -1; + } + loopv(lightmaptexs) glDeleteTextures(1, &lightmaptexs[i].id); + lightmaptexs.shrink(0); + if(progresstex) { glDeleteTextures(1, &progresstex); progresstex = 0; } } void resetlightmaps(bool fullclean) { - cleanuplightmaps(); - lightmaps.shrink(0); - compressed.clear(); - clearlightcache(); - if(fullclean) while(lightmapworkers.length()) delete lightmapworkers.pop(); + cleanuplightmaps(); + lightmaps.shrink(0); + compressed.clear(); + clearlightcache(); + if(fullclean) while(lightmapworkers.length()) delete lightmapworkers.pop(); } lightmapworker::lightmapworker() { - buf = new uchar[LIGHTMAPBUFSIZE]; - bufstart = bufused = 0; - firstlightmap = lastlightmap = curlightmaps = NULL; - ambient = new uchar[4*(LM_MAXW + 4)*(LM_MAXH + 4)]; - blur = new uchar[4*(LM_MAXW + 4)*(LM_MAXH + 4)]; - colordata = new vec[4*(LM_MAXW+1 + 4)*(LM_MAXH+1 + 4)]; - raydata = new vec[(LM_MAXW + 4)*(LM_MAXH + 4)]; - shadowraycache = newshadowraycache(); - blendmapcache = newblendmapcache(); - needspace = doneworking = false; - spacecond = NULL; - thread = NULL; + buf = new uchar[LIGHTMAPBUFSIZE]; + bufstart = bufused = 0; + firstlightmap = lastlightmap = curlightmaps = NULL; + ambient = new uchar[4*(LM_MAXW + 4)*(LM_MAXH + 4)]; + blur = new uchar[4*(LM_MAXW + 4)*(LM_MAXH + 4)]; + colordata = new vec[4*(LM_MAXW+1 + 4)*(LM_MAXH+1 + 4)]; + raydata = new vec[(LM_MAXW + 4)*(LM_MAXH + 4)]; + shadowraycache = newshadowraycache(); + blendmapcache = newblendmapcache(); + needspace = doneworking = false; + spacecond = NULL; + thread = NULL; } lightmapworker::~lightmapworker() { - cleanupthread(); - delete[] buf; - delete[] ambient; - delete[] blur; - delete[] colordata; - delete[] raydata; - freeshadowraycache(shadowraycache); - freeblendmapcache(blendmapcache); + cleanupthread(); + delete[] buf; + delete[] ambient; + delete[] blur; + delete[] colordata; + delete[] raydata; + freeshadowraycache(shadowraycache); + freeblendmapcache(blendmapcache); } void lightmapworker::cleanupthread() { - if(spacecond) { SDL_DestroyCond(spacecond); spacecond = NULL; } - thread = NULL; + if(spacecond) { SDL_DestroyCond(spacecond); spacecond = NULL; } + thread = NULL; } void lightmapworker::reset() { - bufstart = bufused = 0; - firstlightmap = lastlightmap = curlightmaps = NULL; - needspace = doneworking = false; - resetshadowraycache(shadowraycache); + bufstart = bufused = 0; + firstlightmap = lastlightmap = curlightmaps = NULL; + needspace = doneworking = false; + resetshadowraycache(shadowraycache); } bool lightmapworker::setupthread() { - if(!spacecond) spacecond = SDL_CreateCond(); - if(!spacecond) return false; - thread = SDL_CreateThread(work, "lightmap worker", this); - return thread!=NULL; + if(!spacecond) spacecond = SDL_CreateCond(); + if(!spacecond) return false; + thread = SDL_CreateThread(work, "lightmap worker", this); + return thread!=NULL; } static Uint32 calclighttimer(Uint32 interval, void *param) { - check_calclight_progress = true; - return interval; + check_calclight_progress = true; + return interval; } bool setlightmapquality(int quality) { - switch(quality) - { - case 1: lmshadows = 2; lmaa = 3; lerptjoints = 1; break; - case 0: lmshadows = lmshadows_; lmaa = lmaa_; lerptjoints = lerptjoints_; break; - case -1: lmshadows = 1; lmaa = 0; lerptjoints = 0; break; - default: return false; - } - return true; + switch(quality) + { + case 1: lmshadows = 2; lmaa = 3; lerptjoints = 1; break; + case 0: lmshadows = lmshadows_; lmaa = lmaa_; lerptjoints = lerptjoints_; break; + case -1: lmshadows = 1; lmaa = 0; lerptjoints = 0; break; + default: return false; + } + return true; } VARP(lightthreads, 0, 0, 16); @@ -2058,200 +2058,142 @@ VARP(lightthreads, 0, 0, 16); static void cleanuplocks() { - FREELOCK(lightlock, SDL_DestroyMutex); - FREELOCK(tasklock, SDL_DestroyMutex); - FREELOCK(fullcond, SDL_DestroyCond); - FREELOCK(emptycond, SDL_DestroyCond); + FREELOCK(lightlock, SDL_DestroyMutex); + FREELOCK(tasklock, SDL_DestroyMutex); + FREELOCK(fullcond, SDL_DestroyCond); + FREELOCK(emptycond, SDL_DestroyCond); } static void setupthreads(int numthreads) { - loopi(2) lightmaptasks[i].setsize(0); - lightmapexts.setsize(0); - packidx = allocidx = 0; - lightmapping = numthreads; - if(lightmapping > 1) - { - ALLOCLOCK(lightlock, SDL_CreateMutex); - ALLOCLOCK(tasklock, SDL_CreateMutex); - ALLOCLOCK(fullcond, SDL_CreateCond); - ALLOCLOCK(emptycond, SDL_CreateCond); - } - while(lightmapworkers.length() < lightmapping) lightmapworkers.add(new lightmapworker); - loopi(lightmapping) - { - lightmapworker *w = lightmapworkers[i]; - w->reset(); - if(lightmapping <= 1 || w->setupthread()) continue; - w->cleanupthread(); - lightmapping = i >= 1 ? max(i, 2) : 1; - break; - } - if(lightmapping <= 1) cleanuplocks(); + loopi(2) lightmaptasks[i].setsize(0); + lightmapexts.setsize(0); + packidx = allocidx = 0; + lightmapping = numthreads; + if(lightmapping > 1) + { + ALLOCLOCK(lightlock, SDL_CreateMutex); + ALLOCLOCK(tasklock, SDL_CreateMutex); + ALLOCLOCK(fullcond, SDL_CreateCond); + ALLOCLOCK(emptycond, SDL_CreateCond); + } + while(lightmapworkers.length() < lightmapping) lightmapworkers.add(new lightmapworker); + loopi(lightmapping) + { + lightmapworker *w = lightmapworkers[i]; + w->reset(); + if(lightmapping <= 1 || w->setupthread()) continue; + w->cleanupthread(); + lightmapping = i >= 1 ? max(i, 2) : 1; + break; + } + if(lightmapping <= 1) cleanuplocks(); } static void cleanupthreads() { - processtasks(true); - if(lightmapping > 1) - { - SDL_LockMutex(tasklock); - loopv(lightmapworkers) lightmapworkers[i]->doneworking = true; - SDL_CondBroadcast(fullcond); - loopv(lightmapworkers) - { - lightmapworker *w = lightmapworkers[i]; - if(w->needspace && w->spacecond) SDL_CondSignal(w->spacecond); - } - SDL_UnlockMutex(tasklock); - loopv(lightmapworkers) - { - lightmapworker *w = lightmapworkers[i]; - if(w->thread) SDL_WaitThread(w->thread, NULL); - } - } - loopv(lightmapexts) - { - lightmapext &e = lightmapexts[i]; - setcubeext(*e.c, e.ext); - } - loopv(lightmapworkers) lightmapworkers[i]->cleanupthread(); - cleanuplocks(); - lightmapping = 0; + processtasks(true); + if(lightmapping > 1) + { + SDL_LockMutex(tasklock); + loopv(lightmapworkers) lightmapworkers[i]->doneworking = true; + SDL_CondBroadcast(fullcond); + loopv(lightmapworkers) + { + lightmapworker *w = lightmapworkers[i]; + if(w->needspace && w->spacecond) SDL_CondSignal(w->spacecond); + } + SDL_UnlockMutex(tasklock); + loopv(lightmapworkers) + { + lightmapworker *w = lightmapworkers[i]; + if(w->thread) SDL_WaitThread(w->thread, NULL); + } + } + loopv(lightmapexts) + { + lightmapext &e = lightmapexts[i]; + setcubeext(*e.c, e.ext); + } + loopv(lightmapworkers) lightmapworkers[i]->cleanupthread(); + cleanuplocks(); + lightmapping = 0; } void calclight(int *quality) { - if(!setlightmapquality(*quality)) - { - conoutf(CON_ERROR, "valid range for calclight quality is -1..1"); - return; - } - renderbackground("computing lightmaps... (esc to abort)"); - mpremip(true); - optimizeblendmap(); - loadlayermasks(); - int numthreads = lightthreads > 0 ? lightthreads : numcpus; - if(numthreads > 1) preloadusedmapmodels(false, true); - resetlightmaps(false); - clearsurfaces(worldroot); - taskprogress = progress = 0; - progresstexticks = 0; - progresslightmap = -1; - calclight_canceled = false; - check_calclight_progress = false; - SDL_TimerID timer = SDL_AddTimer(250, calclighttimer, NULL); - Uint32 start = SDL_GetTicks(); - calcnormals(lerptjoints > 0); - show_calclight_progress(); - setupthreads(numthreads); - generatelightmaps(worldroot, ivec(0, 0, 0), worldsize >> 1); - cleanupthreads(); - clearnormals(); - Uint32 end = SDL_GetTicks(); - if(timer) SDL_RemoveTimer(timer); - uint total = 0, lumels = 0; - loopv(lightmaps) - { - insertunlit(i); - if(!editmode) lightmaps[i].finalize(); - total += lightmaps[i].lightmaps; - lumels += lightmaps[i].lumels; - } - if(!editmode) compressed.clear(); - initlights(); - renderbackground("lighting done..."); - allchanged(); - if(calclight_canceled) - conoutf("calclight aborted"); - else - conoutf("generated %d lightmaps using %d%% of %d textures (%.1f seconds)", - total, - lightmaps.length() ? lumels * 100 / (lightmaps.length() * LM_PACKW * LM_PACKH) : 0, - lightmaps.length(), - (end - start) / 1000.0f); + if(!setlightmapquality(*quality)) + { + conoutf(CON_ERROR, "valid range for calclight quality is -1..1"); + return; + } + renderbackground("computing lightmaps... (esc to abort)"); + mpremip(true); + optimizeblendmap(); + loadlayermasks(); + int numthreads = lightthreads > 0 ? lightthreads : numcpus; + if(numthreads > 1) preloadusedmapmodels(false, true); + resetlightmaps(false); + clearsurfaces(worldroot); + taskprogress = progress = 0; + progresstexticks = 0; + progresslightmap = -1; + calclight_canceled = false; + check_calclight_progress = false; + SDL_TimerID timer = SDL_AddTimer(250, calclighttimer, NULL); + Uint32 start = SDL_GetTicks(); + calcnormals(lerptjoints > 0); + show_calclight_progress(); + setupthreads(numthreads); + generatelightmaps(worldroot, ivec(0, 0, 0), worldsize >> 1); + cleanupthreads(); + clearnormals(); + Uint32 end = SDL_GetTicks(); + if(timer) SDL_RemoveTimer(timer); + uint total = 0, lumels = 0; + loopv(lightmaps) + { + insertunlit(i); + if(!editmode) lightmaps[i].finalize(); + total += lightmaps[i].lightmaps; + lumels += lightmaps[i].lumels; + } + if(!editmode) compressed.clear(); + initlights(); + renderbackground("lighting done..."); + allchanged(); + if(calclight_canceled) + conoutf("calclight aborted"); + else + conoutf("generated %d lightmaps using %d%% of %d textures (%.1f seconds)", + total, + lightmaps.length() ? lumels * 100 / (lightmaps.length() * LM_PACKW * LM_PACKH) : 0, + lightmaps.length(), + (end - start) / 1000.0f); } COMMAND(calclight, "i"); -VAR(patchnormals, 0, 0, 1); - -void patchlight(int *quality) -{ - if(noedit(true)) return; - if(!setlightmapquality(*quality)) - { - conoutf(CON_ERROR, "valid range for patchlight quality is -1..1"); - return; - } - renderbackground("patching lightmaps... (esc to abort)"); - loadlayermasks(); - int numthreads = lightthreads > 0 ? lightthreads : numcpus; - if(numthreads > 1) preloadusedmapmodels(false, true); - cleanuplightmaps(); - taskprogress = progress = 0; - progresstexticks = 0; - progresslightmap = -1; - int total = 0, lumels = 0; - loopv(lightmaps) - { - if((lightmaps[i].type&LM_TYPE) != LM_BUMPMAP1) progresslightmap = i; - total -= lightmaps[i].lightmaps; - lumels -= lightmaps[i].lumels; - } - calclight_canceled = false; - check_calclight_progress = false; - SDL_TimerID timer = SDL_AddTimer(250, calclighttimer, NULL); - if(patchnormals) renderprogress(0, "computing normals..."); - Uint32 start = SDL_GetTicks(); - if(patchnormals) calcnormals(lerptjoints > 0); - show_calclight_progress(); - setupthreads(numthreads); - generatelightmaps(worldroot, ivec(0, 0, 0), worldsize >> 1); - cleanupthreads(); - if(patchnormals) clearnormals(); - Uint32 end = SDL_GetTicks(); - if(timer) SDL_RemoveTimer(timer); - loopv(lightmaps) - { - total += lightmaps[i].lightmaps; - lumels += lightmaps[i].lumels; - } - initlights(); - renderbackground("lighting done..."); - allchanged(); - if(calclight_canceled) - conoutf("patchlight aborted"); - else - conoutf("patched %d lightmaps using %d%% of %d textures (%.1f seconds)", - total, - lightmaps.length() ? lumels * 100 / (lightmaps.length() * LM_PACKW * LM_PACKH) : 0, - lightmaps.length(), - (end - start) / 1000.0f); -} - -COMMAND(patchlight, "i"); - void clearlightmaps() { - if(noedit(true)) return; - renderprogress(0, "clearing lightmaps..."); - resetlightmaps(false); - clearsurfaces(worldroot); - initlights(); - allchanged(); + if(noedit(true)) return; + renderprogress(0, "clearing lightmaps..."); + resetlightmaps(false); + clearsurfaces(worldroot); + initlights(); + allchanged(); } COMMAND(clearlightmaps, ""); void setfullbrightlevel(int fullbrightlevel) { - if(lightmaptexs.length() > LMID_BRIGHT) - { - uchar bright[3] = { uchar(fullbrightlevel), uchar(fullbrightlevel), uchar(fullbrightlevel) }; - createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, 1); - } - initlights(); + if(lightmaptexs.length() > LMID_BRIGHT) + { + uchar bright[3] = { uchar(fullbrightlevel), uchar(fullbrightlevel), uchar(fullbrightlevel) }; + createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, 1); + } + initlights(); } VARF(fullbright, 0, 0, 1, if(lightmaptexs.length()) { initlights(); lightents(); }); @@ -2259,183 +2201,68 @@ VARF(fullbrightlevel, 0, 128, 255, setfullbrightlevel(fullbrightlevel)); vector lightmaptexs; -static void rotatenormals(LightMap &lmlv, int x, int y, int w, int h, bool flipx, bool flipy, bool swapxy) -{ - uchar *lv = lmlv.data + 3*(y*LM_PACKW + x); - int stride = 3*(LM_PACKW-w); - loopi(h) - { - loopj(w) - { - if(flipx) lv[0] = 255 - lv[0]; - if(flipy) lv[1] = 255 - lv[1]; - if(swapxy) swap(lv[0], lv[1]); - lv += 3; - } - lv += stride; - } -} - -static void rotatenormals(cube *c) -{ - loopi(8) - { - cube &ch = c[i]; - if(ch.children) - { - rotatenormals(ch.children); - continue; - } - else if(!ch.ext) continue; - loopj(6) if(lightmaps.inrange(ch.ext->surfaces[j].lmid[0]+1-LMID_RESERVED)) - { - VSlot &vslot = lookupvslot(ch.texture[j], false); - if(!vslot.rotation) continue; - surfaceinfo &surface = ch.ext->surfaces[j]; - int numverts = surface.numverts&MAXFACEVERTS; - if(!numverts) continue; - LightMap &lmlv = lightmaps[surface.lmid[0]+1-LMID_RESERVED]; - if((lmlv.type&LM_TYPE)!=LM_BUMPMAP1) continue; - ushort x1 = USHRT_MAX, y1 = USHRT_MAX, x2 = 0, y2 = 0; - vertinfo *verts = ch.ext->verts() + surface.verts; - loopk(numverts) - { - vertinfo &v = verts[k]; - x1 = min(x1, v.u); - y1 = min(y1, v.u); - x2 = max(x2, v.u); - y2 = max(y2, v.v); - } - if(x1 > x2 || y1 > y2) continue; - x1 /= (USHRT_MAX+1)/LM_PACKW; - y1 /= (USHRT_MAX+1)/LM_PACKH; - x2 /= (USHRT_MAX+1)/LM_PACKW; - y2 /= (USHRT_MAX+1)/LM_PACKH; - const texrotation &r = texrotations[vslot.rotation < 4 ? 4-vslot.rotation : vslot.rotation]; - rotatenormals(lmlv, x1, y1, x2-x1, y1-y1, r.flipx, r.flipy, r.swapxy); - } - } -} - -void fixlightmapnormals() -{ - rotatenormals(worldroot); -} - -void fixrotatedlightmaps(cube &c, const ivec &co, int size) -{ - if(c.children) - { - loopi(8) fixrotatedlightmaps(c.children[i], ivec(i, co, size>>1), size>>1); - return; - } - if(!c.ext) return; - loopi(6) - { - if(c.merged&(1<surfaces[i]; - int numverts = surf.numverts&MAXFACEVERTS; - if(numverts!=4 || (surf.lmid[0] < LMID_RESERVED && surf.lmid[1] < LMID_RESERVED)) continue; - vertinfo *verts = c.ext->verts() + surf.verts; - int vis = visibletris(c, i, co, size); - if(!vis || vis==3) continue; - if((verts[0].u != verts[1].u || verts[0].v != verts[1].v) && - (verts[0].u != verts[3].u || verts[0].v != verts[3].v) && - (verts[2].u != verts[1].u || verts[2].v != verts[1].v) && - (verts[2].u != verts[3].u || verts[2].v != verts[3].v)) - continue; - if(vis&4) - { - vertinfo tmp = verts[0]; - verts[0].x = verts[1].x; verts[0].y = verts[1].y; verts[0].z = verts[1].z; - verts[1].x = verts[2].x; verts[1].y = verts[2].y; verts[1].z = verts[2].z; - verts[2].x = verts[3].x; verts[2].y = verts[3].y; verts[2].z = verts[3].z; - verts[3].x = tmp.x; verts[3].y = tmp.y; verts[3].z = tmp.z; - if(surf.numverts&LAYER_DUP) loopk(4) - { - vertinfo &v = verts[k], &b = verts[k+4]; - b.x = v.x; - b.y = v.y; - b.z = v.z; - } - } - surf.numverts = (surf.numverts & ~MAXFACEVERTS) | 3; - if(vis&2) - { - verts[1] = verts[2]; verts[2] = verts[3]; - if(surf.numverts&LAYER_DUP) { verts[3] = verts[4]; verts[4] = verts[6]; verts[5] = verts[7]; } - } - else if(surf.numverts&LAYER_DUP) { verts[3] = verts[4]; verts[4] = verts[5]; verts[5] = verts[6]; } - } -} - -void fixrotatedlightmaps() -{ - loopi(8) fixrotatedlightmaps(worldroot[i], ivec(i, ivec(0, 0, 0), worldsize>>1), worldsize>>1); -} - static void copylightmap(LightMap &lm, uchar *dst, size_t stride) { - const uchar *c = lm.data; - loopi(LM_PACKH) - { - memcpy(dst, c, lm.bpp*LM_PACKW); - c += lm.bpp*LM_PACKW; - dst += stride; - } + const uchar *c = lm.data; + loopi(LM_PACKH) + { + memcpy(dst, c, lm.bpp*LM_PACKW); + c += lm.bpp*LM_PACKW; + dst += stride; + } } void genreservedlightmaptexs() { - while(lightmaptexs.length() < LMID_RESERVED) - { - LightMapTexture &tex = lightmaptexs.add(); - tex.type = lightmaptexs.length()&1 ? LM_DIFFUSE : LM_BUMPMAP1; - glGenTextures(1, &tex.id); - } - uchar unlit[3] = { ambientcolor[0], ambientcolor[1], ambientcolor[2] }; - createtexture(lightmaptexs[LMID_AMBIENT].id, 1, 1, unlit, 0, 1); - bvec front(128, 128, 255); - createtexture(lightmaptexs[LMID_AMBIENT1].id, 1, 1, &front, 0, 1); - uchar bright[3] = { uchar(fullbrightlevel), uchar(fullbrightlevel), uchar(fullbrightlevel) }; - createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, 1); - createtexture(lightmaptexs[LMID_BRIGHT1].id, 1, 1, &front, 0, 1); - uchar dark[3] = { 0, 0, 0 }; - createtexture(lightmaptexs[LMID_DARK].id, 1, 1, dark, 0, 1); - createtexture(lightmaptexs[LMID_DARK1].id, 1, 1, &front, 0, 1); + while(lightmaptexs.length() < LMID_RESERVED) + { + LightMapTexture &tex = lightmaptexs.add(); + tex.type = lightmaptexs.length()&1 ? LM_DIFFUSE : LM_BUMPMAP1; + glGenTextures(1, &tex.id); + } + uchar unlit[3] = { ambientcolor[0], ambientcolor[1], ambientcolor[2] }; + createtexture(lightmaptexs[LMID_AMBIENT].id, 1, 1, unlit, 0, 1); + bvec front(128, 128, 255); + createtexture(lightmaptexs[LMID_AMBIENT1].id, 1, 1, &front, 0, 1); + uchar bright[3] = { uchar(fullbrightlevel), uchar(fullbrightlevel), uchar(fullbrightlevel) }; + createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, 1); + createtexture(lightmaptexs[LMID_BRIGHT1].id, 1, 1, &front, 0, 1); + uchar dark[3] = { 0, 0, 0 }; + createtexture(lightmaptexs[LMID_DARK].id, 1, 1, dark, 0, 1); + createtexture(lightmaptexs[LMID_DARK1].id, 1, 1, &front, 0, 1); } static void findunlit(int i) { - LightMap &lm = lightmaps[i]; - if(lm.unlitx>=0) return; - else if((lm.type&LM_TYPE)==LM_BUMPMAP0) - { - if(i+1>=lightmaps.length() || (lightmaps[i+1].type&LM_TYPE)!=LM_BUMPMAP1) return; - } - else if((lm.type&LM_TYPE)!=LM_DIFFUSE) return; - uchar *data = lm.data; - loop(y, 2) loop(x, LM_PACKW) - { - if(!data[0] && !data[1] && !data[2]) - { - memcpy(data, ambientcolor.v, 3); - if((lm.type&LM_TYPE)==LM_BUMPMAP0) ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] = bvec(128, 128, 255); - lm.unlitx = x; - lm.unlity = y; - return; - } - if(data[0]==ambientcolor[0] && data[1]==ambientcolor[1] && data[2]==ambientcolor[2]) - { - if((lm.type&LM_TYPE)!=LM_BUMPMAP0 || ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] == bvec(128, 128, 255)) - { - lm.unlitx = x; - lm.unlity = y; - return; - } - } - data += lm.bpp; - } + LightMap &lm = lightmaps[i]; + if(lm.unlitx>=0) return; + else if((lm.type&LM_TYPE)==LM_BUMPMAP0) + { + if(i+1>=lightmaps.length() || (lightmaps[i+1].type&LM_TYPE)!=LM_BUMPMAP1) return; + } + else if((lm.type&LM_TYPE)!=LM_DIFFUSE) return; + uchar *data = lm.data; + loop(y, 2) loop(x, LM_PACKW) + { + if(!data[0] && !data[1] && !data[2]) + { + memcpy(data, ambientcolor.v, 3); + if((lm.type&LM_TYPE)==LM_BUMPMAP0) ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] = bvec(128, 128, 255); + lm.unlitx = x; + lm.unlity = y; + return; + } + if(data[0]==ambientcolor[0] && data[1]==ambientcolor[1] && data[2]==ambientcolor[2]) + { + if((lm.type&LM_TYPE)!=LM_BUMPMAP0 || ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] == bvec(128, 128, 255)) + { + lm.unlitx = x; + lm.unlity = y; + return; + } + } + data += lm.bpp; + } } VARF(roundlightmaptex, 0, 4, 16, { cleanuplightmaps(); initlights(); allchanged(); }); @@ -2443,286 +2270,286 @@ VARF(batchlightmaps, 0, 4, 256, { cleanuplightmaps(); initlights(); allchanged() void genlightmaptexs(int flagmask, int flagval) { - if(lightmaptexs.length() < LMID_RESERVED) genreservedlightmaptexs(); - - int remaining[LM_TYPE+1] = { 0 }, total = 0; - loopv(lightmaps) - { - LightMap &lm = lightmaps[i]; - if(lm.tex >= 0 || (lm.type&flagmask)!=flagval) continue; - int type = lm.type&LM_TYPE; - remaining[type]++; - total++; - if(lm.unlitx < 0) findunlit(i); - } - - int sizelimit = (maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize)/max(LM_PACKW, LM_PACKH); - sizelimit = min(batchlightmaps, sizelimit*sizelimit); - while(total) - { - int type = LM_DIFFUSE; - LightMap *firstlm = NULL; - loopv(lightmaps) - { - LightMap &lm = lightmaps[i]; - if(lm.tex >= 0 || (lm.type&flagmask) != flagval) continue; - type = lm.type&LM_TYPE; - firstlm = &lm; - break; - } - if(!firstlm) break; - int used = 0, uselimit = min(remaining[type], sizelimit); - do used++; while((1<type; - tex.w = LM_PACKW<<((used+1)/2); - tex.h = LM_PACKH<<(used/2); - int bpp = firstlm->bpp; - uchar *data = used ? new uchar[bpp*tex.w*tex.h] : NULL; - int offsetx = 0, offsety = 0; - loopv(lightmaps) - { - LightMap &lm = lightmaps[i]; - if(lm.tex >= 0 || (lm.type&flagmask) != flagval || (lm.type&LM_TYPE) != type) continue; - - lm.tex = lightmaptexs.length()-1; - lm.offsetx = offsetx; - lm.offsety = offsety; - if(tex.unlitx < 0 && lm.unlitx >= 0) - { - tex.unlitx = offsetx + lm.unlitx; - tex.unlity = offsety + lm.unlity; - } - - if(data) copylightmap(lm, &data[bpp*(offsety*tex.w + offsetx)], bpp*tex.w); - - offsetx += LM_PACKW; - if(offsetx >= tex.w) { offsetx = 0; offsety += LM_PACKH; } - if(offsety >= tex.h) break; - } - - glGenTextures(1, &tex.id); - createtexture(tex.id, tex.w, tex.h, data ? data : firstlm->data, 3, 1, bpp==4 ? GL_RGBA : GL_RGB); - if(data) delete[] data; - } + if(lightmaptexs.length() < LMID_RESERVED) genreservedlightmaptexs(); + + int remaining[LM_TYPE+1] = { 0 }, total = 0; + loopv(lightmaps) + { + LightMap &lm = lightmaps[i]; + if(lm.tex >= 0 || (lm.type&flagmask)!=flagval) continue; + int type = lm.type&LM_TYPE; + remaining[type]++; + total++; + if(lm.unlitx < 0) findunlit(i); + } + + int sizelimit = (maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize)/max(LM_PACKW, LM_PACKH); + sizelimit = min(batchlightmaps, sizelimit*sizelimit); + while(total) + { + int type = LM_DIFFUSE; + LightMap *firstlm = NULL; + loopv(lightmaps) + { + LightMap &lm = lightmaps[i]; + if(lm.tex >= 0 || (lm.type&flagmask) != flagval) continue; + type = lm.type&LM_TYPE; + firstlm = &lm; + break; + } + if(!firstlm) break; + int used = 0, uselimit = min(remaining[type], sizelimit); + do used++; while((1<type; + tex.w = LM_PACKW<<((used+1)/2); + tex.h = LM_PACKH<<(used/2); + int bpp = firstlm->bpp; + uchar *data = used ? new uchar[bpp*tex.w*tex.h] : NULL; + int offsetx = 0, offsety = 0; + loopv(lightmaps) + { + LightMap &lm = lightmaps[i]; + if(lm.tex >= 0 || (lm.type&flagmask) != flagval || (lm.type&LM_TYPE) != type) continue; + + lm.tex = lightmaptexs.length()-1; + lm.offsetx = offsetx; + lm.offsety = offsety; + if(tex.unlitx < 0 && lm.unlitx >= 0) + { + tex.unlitx = offsetx + lm.unlitx; + tex.unlity = offsety + lm.unlity; + } + + if(data) copylightmap(lm, &data[bpp*(offsety*tex.w + offsetx)], bpp*tex.w); + + offsetx += LM_PACKW; + if(offsetx >= tex.w) { offsetx = 0; offsety += LM_PACKH; } + if(offsety >= tex.h) break; + } + + glGenTextures(1, &tex.id); + createtexture(tex.id, tex.w, tex.h, data ? data : firstlm->data, 3, 1, bpp==4 ? GL_RGBA : GL_RGB); + if(data) delete[] data; + } } bool brightengeom = false, shouldlightents = false; void clearlights() { - clearlightcache(); - const vector &ents = entities::getents(); - loopv(ents) - { - extentity &e = *ents[i]; - e.light.color = vec(1, 1, 1); - e.light.dir = vec(0, 0, 1); - } - shouldlightents = false; + clearlightcache(); + const vector &ents = entities::getents(); + loopv(ents) + { + extentity &e = *ents[i]; + e.light.color = vec(1, 1, 1); + e.light.dir = vec(0, 0, 1); + } + shouldlightents = false; - genlightmaptexs(LM_ALPHA, 0); - genlightmaptexs(LM_ALPHA, LM_ALPHA); - brightengeom = true; + genlightmaptexs(LM_ALPHA, 0); + genlightmaptexs(LM_ALPHA, LM_ALPHA); + brightengeom = true; } void lightent(extentity &e, float height) { - if(e.type==ET_LIGHT) return; - float ambient = 0.0f; - if(e.type==ET_MAPMODEL) - { - model *m = loadmodel(NULL, e.attr2); - if(m) height = m->above()*0.75f; - } - else if(e.type>=ET_GAMESPECIFIC) ambient = 0.4f; - vec target(e.o.x, e.o.y, e.o.z + height); - lightreaching(target, e.light.color, e.light.dir, false, &e, ambient); + if(e.type==ET_LIGHT) return; + float ambient = 0.0f; + if(e.type==ET_MAPMODEL) + { + model *m = loadmodel(NULL, e.attr2); + if(m) height = m->above()*0.75f; + } + else if(e.type>=ET_GAMESPECIFIC) ambient = 0.4f; + vec target(e.o.x, e.o.y, e.o.z + height); + lightreaching(target, e.light.color, e.light.dir, false, &e, ambient); } void lightents(bool force) { - if(!force && !shouldlightents) return; + if(!force && !shouldlightents) return; - const vector &ents = entities::getents(); - loopv(ents) lightent(*ents[i]); + const vector &ents = entities::getents(); + loopv(ents) lightent(*ents[i]); - shouldlightents = false; + shouldlightents = false; } void initlights() { - if((fullbright && editmode) || lightmaps.empty()) - { - clearlights(); - return; - } + if((fullbright && editmode) || lightmaps.empty()) + { + clearlights(); + return; + } - clearlightcache(); - genlightmaptexs(LM_ALPHA, 0); - genlightmaptexs(LM_ALPHA, LM_ALPHA); - brightengeom = false; - shouldlightents = true; + clearlightcache(); + genlightmaptexs(LM_ALPHA, 0); + genlightmaptexs(LM_ALPHA, LM_ALPHA); + brightengeom = false; + shouldlightents = true; } static inline void fastskylight(const vec &o, float tolerance, uchar *skylight, int flags = RAY_ALPHAPOLY, extentity *t = NULL, bool fast = false) { - flags |= RAY_SHADOW; - if(skytexturelight) flags |= RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0); - if(fast) - { - static const vec ray(0, 0, 1); - if(shadowray(vec(ray).mul(tolerance).add(o), ray, 1e16f, flags, t)>1e15f) - memcpy(skylight, skylightcolor.v, 3); - else memcpy(skylight, ambientcolor.v, 3); - } - else - { - static const vec rays[5] = - { - vec(cosf(66*RAD)*cosf(65*RAD), sinf(66*RAD)*cosf(65*RAD), sinf(65*RAD)), - vec(cosf(156*RAD)*cosf(65*RAD), sinf(156*RAD)*cosf(65*RAD), sinf(65*RAD)), - vec(cosf(246*RAD)*cosf(65*RAD), sinf(246*RAD)*cosf(65*RAD), sinf(65*RAD)), - vec(cosf(336*RAD)*cosf(65*RAD), sinf(336*RAD)*cosf(65*RAD), sinf(65*RAD)), - vec(0, 0, 1), - }; - int hit = 0; - loopi(5) if(shadowray(vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, flags, t)>1e15f) hit++; - loopk(3) skylight[k] = uchar(ambientcolor[k] + (max(skylightcolor[k], ambientcolor[k]) - ambientcolor[k])*hit/5.0f); - } + flags |= RAY_SHADOW; + if(skytexturelight) flags |= RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0); + if(fast) + { + static const vec ray(0, 0, 1); + if(shadowray(vec(ray).mul(tolerance).add(o), ray, 1e16f, flags, t)>1e15f) + memcpy(skylight, skylightcolor.v, 3); + else memcpy(skylight, ambientcolor.v, 3); + } + else + { + static const vec rays[5] = + { + vec(cosf(66*RAD)*cosf(65*RAD), sinf(66*RAD)*cosf(65*RAD), sinf(65*RAD)), + vec(cosf(156*RAD)*cosf(65*RAD), sinf(156*RAD)*cosf(65*RAD), sinf(65*RAD)), + vec(cosf(246*RAD)*cosf(65*RAD), sinf(246*RAD)*cosf(65*RAD), sinf(65*RAD)), + vec(cosf(336*RAD)*cosf(65*RAD), sinf(336*RAD)*cosf(65*RAD), sinf(65*RAD)), + vec(0, 0, 1), + }; + int hit = 0; + loopi(5) if(shadowray(vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, flags, t)>1e15f) hit++; + loopk(3) skylight[k] = uchar(ambientcolor[k] + (max(skylightcolor[k], ambientcolor[k]) - ambientcolor[k])*hit/5.0f); + } } void lightreaching(const vec &target, vec &color, vec &dir, bool fast, extentity *t, float ambient) { - if((fullbright && editmode) || lightmaps.empty()) - { - color = vec(1, 1, 1); - dir = vec(0, 0, 1); - return; - } - - color = dir = vec(0, 0, 0); - const vector &ents = entities::getents(); - const vector &lights = checklightcache(int(target.x), int(target.y)); - loopv(lights) - { - extentity &e = *ents[lights[i]]; - if(e.type != ET_LIGHT) - continue; - - vec ray(target); - ray.sub(e.o); - float mag = ray.magnitude(); - if(e.attr1 && mag >= float(e.attr1)) - continue; - - if(mag < 1e-4f) ray = vec(0, 0, -1); - else - { - ray.div(mag); - if(shadowray(e.o, ray, mag, RAY_SHADOW | RAY_POLY, t) < mag) - continue; - } - - float intensity = 1; - if(e.attr1) - intensity -= mag / float(e.attr1); - if(e.attached && e.attached->type==ET_SPOTLIGHT) - { - vec spot = vec(e.attached->o).sub(e.o).normalize(); - float maxatten = sincos360[clamp(int(e.attached->attr1), 1, 89)].x, spotatten = (ray.dot(spot) - maxatten) / (1 - maxatten); - if(spotatten <= 0) continue; - intensity *= spotatten; - } - - //if(target==player->o) - //{ - // conoutf(CON_DEBUG, "%d - %f %f", i, intensity, mag); - //} - - vec lightcol = vec(e.attr2, e.attr3, e.attr4).mul(1.0f/255); - color.add(vec(lightcol).mul(intensity)); - dir.add(vec(ray).mul(-intensity*lightcol.x*lightcol.y*lightcol.z)); - } - if(sunlight && shadowray(target, sunlightdir, 1e16f, RAY_SHADOW | RAY_POLY | (skytexturelight ? RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0) : 0), t) > 1e15f) - { - vec lightcol = vec(sunlightcolor.x, sunlightcolor.y, sunlightcolor.z).mul(sunlightscale/255); - color.add(lightcol); - dir.add(vec(sunlightdir).mul(lightcol.x*lightcol.y*lightcol.z)); - } - if(hasskylight()) - { - uchar skylight[3]; - if(t) calcskylight(NULL, target, vec(0, 0, 0), 0.5f, skylight, RAY_POLY, t); - else fastskylight(target, 0.5f, skylight, RAY_POLY, t, fast); - loopk(3) color[k] = min(1.5f, max(max(skylight[k]/255.0f, ambient), color[k])); - } - else loopk(3) color[k] = min(1.5f, max(max(ambientcolor[k]/255.0f, ambient), color[k])); - if(dir.iszero()) dir = vec(0, 0, 1); - else dir.normalize(); + if((fullbright && editmode) || lightmaps.empty()) + { + color = vec(1, 1, 1); + dir = vec(0, 0, 1); + return; + } + + color = dir = vec(0, 0, 0); + const vector &ents = entities::getents(); + const vector &lights = checklightcache(int(target.x), int(target.y)); + loopv(lights) + { + extentity &e = *ents[lights[i]]; + if(e.type != ET_LIGHT) + continue; + + vec ray(target); + ray.sub(e.o); + float mag = ray.magnitude(); + if(e.attr1 && mag >= float(e.attr1)) + continue; + + if(mag < 1e-4f) ray = vec(0, 0, -1); + else + { + ray.div(mag); + if(shadowray(e.o, ray, mag, RAY_SHADOW | RAY_POLY, t) < mag) + continue; + } + + float intensity = 1; + if(e.attr1) + intensity -= mag / float(e.attr1); + if(e.attached && e.attached->type==ET_SPOTLIGHT) + { + vec spot = vec(e.attached->o).sub(e.o).normalize(); + float maxatten = sincos360[clamp(int(e.attached->attr1), 1, 89)].x, spotatten = (ray.dot(spot) - maxatten) / (1 - maxatten); + if(spotatten <= 0) continue; + intensity *= spotatten; + } + + //if(target==player->o) + //{ + // conoutf(CON_DEBUG, "%d - %f %f", i, intensity, mag); + //} + + vec lightcol = vec(e.attr2, e.attr3, e.attr4).mul(1.0f/255); + color.add(vec(lightcol).mul(intensity)); + dir.add(vec(ray).mul(-intensity*lightcol.x*lightcol.y*lightcol.z)); + } + if(sunlight && shadowray(target, sunlightdir, 1e16f, RAY_SHADOW | RAY_POLY | (skytexturelight ? RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0) : 0), t) > 1e15f) + { + vec lightcol = vec(sunlightcolor.x, sunlightcolor.y, sunlightcolor.z).mul(sunlightscale/255); + color.add(lightcol); + dir.add(vec(sunlightdir).mul(lightcol.x*lightcol.y*lightcol.z)); + } + if(hasskylight()) + { + uchar skylight[3]; + if(t) calcskylight(NULL, target, vec(0, 0, 0), 0.5f, skylight, RAY_POLY, t); + else fastskylight(target, 0.5f, skylight, RAY_POLY, t, fast); + loopk(3) color[k] = min(1.5f, max(max(skylight[k]/255.0f, ambient), color[k])); + } + else loopk(3) color[k] = min(1.5f, max(max(ambientcolor[k]/255.0f, ambient), color[k])); + if(dir.iszero()) dir = vec(0, 0, 1); + else dir.normalize(); } entity *brightestlight(const vec &target, const vec &dir) { - if(sunlight && sunlightdir.dot(dir) > 0 && shadowray(target, sunlightdir, 1e16f, RAY_SHADOW | RAY_POLY | (skytexturelight ? RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0) : 0)) > 1e15f) - return &sunlightent; - const vector &ents = entities::getents(); - const vector &lights = checklightcache(int(target.x), int(target.y)); - extentity *brightest = NULL; - float bintensity = 0; - loopv(lights) - { - extentity &e = *ents[lights[i]]; - if(e.type != ET_LIGHT || vec(e.o).sub(target).dot(dir)<0) - continue; - - vec ray(target); - ray.sub(e.o); - float mag = ray.magnitude(); - if(e.attr1 && mag >= float(e.attr1)) - continue; - - ray.div(mag); - if(shadowray(e.o, ray, mag, RAY_SHADOW | RAY_POLY) < mag) - continue; - float intensity = 1; - if(e.attr1) - intensity -= mag / float(e.attr1); - if(e.attached && e.attached->type==ET_SPOTLIGHT) - { - vec spot = vec(e.attached->o).sub(e.o).normalize(); - float maxatten = sincos360[clamp(int(e.attached->attr1), 1, 89)].x, spotatten = (ray.dot(spot) - maxatten) / (1 - maxatten); - if(spotatten <= 0) continue; - intensity *= spotatten; - } - - if(!brightest || intensity > bintensity) - { - brightest = &e; - bintensity = intensity; - } - } - return brightest; + if(sunlight && sunlightdir.dot(dir) > 0 && shadowray(target, sunlightdir, 1e16f, RAY_SHADOW | RAY_POLY | (skytexturelight ? RAY_SKIPSKY | (useskytexture ? RAY_SKYTEX : 0) : 0)) > 1e15f) + return &sunlightent; + const vector &ents = entities::getents(); + const vector &lights = checklightcache(int(target.x), int(target.y)); + extentity *brightest = NULL; + float bintensity = 0; + loopv(lights) + { + extentity &e = *ents[lights[i]]; + if(e.type != ET_LIGHT || vec(e.o).sub(target).dot(dir)<0) + continue; + + vec ray(target); + ray.sub(e.o); + float mag = ray.magnitude(); + if(e.attr1 && mag >= float(e.attr1)) + continue; + + ray.div(mag); + if(shadowray(e.o, ray, mag, RAY_SHADOW | RAY_POLY) < mag) + continue; + float intensity = 1; + if(e.attr1) + intensity -= mag / float(e.attr1); + if(e.attached && e.attached->type==ET_SPOTLIGHT) + { + vec spot = vec(e.attached->o).sub(e.o).normalize(); + float maxatten = sincos360[clamp(int(e.attached->attr1), 1, 89)].x, spotatten = (ray.dot(spot) - maxatten) / (1 - maxatten); + if(spotatten <= 0) continue; + intensity *= spotatten; + } + + if(!brightest || intensity > bintensity) + { + brightest = &e; + bintensity = intensity; + } + } + return brightest; } void dumplms() { - loopv(lightmaps) - { - ImageData temp(LM_PACKW, LM_PACKH, lightmaps[i].bpp, lightmaps[i].data); - const char *map = game::getclientmap(), *name = strrchr(map, '/'); - defformatstring(buf, "lightmap_%s_%d.png", name ? name+1 : map, i); - savepng(buf, temp, true); - } + loopv(lightmaps) + { + ImageData temp(LM_PACKW, LM_PACKH, lightmaps[i].bpp, lightmaps[i].data); + const char *map = game::getclientmap(), *name = strrchr(map, '/'); + defformatstring(buf, "lightmap_%s_%d.png", name ? name+1 : map, i); + savepng(buf, temp, true); + } } COMMAND(dumplms, ""); diff --git a/src/engine/lightmap.h b/src/engine/lightmap.h index 005f1fe..2c09d0d 100644 --- a/src/engine/lightmap.h +++ b/src/engine/lightmap.h @@ -7,79 +7,79 @@ struct PackNode { - PackNode *child1, *child2; - ushort x, y, w, h; - int available; + PackNode *child1, *child2; + ushort x, y, w, h; + int available; - PackNode() : child1(0), child2(0), x(0), y(0), w(LM_PACKW), h(LM_PACKH), available(min(LM_PACKW, LM_PACKH)) {} - PackNode(ushort x, ushort y, ushort w, ushort h) : child1(0), child2(0), x(x), y(y), w(w), h(h), available(min(w, h)) {} + PackNode() : child1(0), child2(0), x(0), y(0), w(LM_PACKW), h(LM_PACKH), available(min(LM_PACKW, LM_PACKH)) {} + PackNode(ushort x, ushort y, ushort w, ushort h) : child1(0), child2(0), x(x), y(y), w(w), h(h), available(min(w, h)) {} - void clear() - { - DELETEP(child1); - DELETEP(child2); - } + void clear() + { + DELETEP(child1); + DELETEP(child2); + } - ~PackNode() - { - clear(); - } + ~PackNode() + { + clear(); + } - bool insert(ushort &tx, ushort &ty, ushort tw, ushort th); + bool insert(ushort &tx, ushort &ty, ushort tw, ushort th); }; enum { - LM_DIFFUSE = 0, - LM_BUMPMAP0, - LM_BUMPMAP1, - LM_TYPE = 0x0F, + LM_DIFFUSE = 0, + LM_BUMPMAP0, + LM_BUMPMAP1, + LM_TYPE = 0x0F, - LM_ALPHA = 1<<4, - LM_FLAGS = 0xF0 + LM_ALPHA = 1<<4, + LM_FLAGS = 0xF0 }; struct LightMap { - int type, bpp, tex, offsetx, offsety; - PackNode packroot; - uint lightmaps, lumels; - int unlitx, unlity; - uchar *data; - - LightMap() - : type(LM_DIFFUSE), bpp(3), tex(-1), offsetx(-1), offsety(-1), - lightmaps(0), lumels(0), unlitx(-1), unlity(-1), - data(NULL) - { - } - - ~LightMap() - { - if(data) delete[] data; - } - - void finalize() - { - packroot.clear(); - packroot.available = 0; - } - - void copy(ushort tx, ushort ty, uchar *src, ushort tw, ushort th); - bool insert(ushort &tx, ushort &ty, uchar *src, ushort tw, ushort th); + int type, bpp, tex, offsetx, offsety; + PackNode packroot; + uint lightmaps, lumels; + int unlitx, unlity; + uchar *data; + + LightMap() + : type(LM_DIFFUSE), bpp(3), tex(-1), offsetx(-1), offsety(-1), + lightmaps(0), lumels(0), unlitx(-1), unlity(-1), + data(NULL) + { + } + + ~LightMap() + { + if(data) delete[] data; + } + + void finalize() + { + packroot.clear(); + packroot.available = 0; + } + + void copy(ushort tx, ushort ty, uchar *src, ushort tw, ushort th); + bool insert(ushort &tx, ushort &ty, uchar *src, ushort tw, ushort th); }; extern vector lightmaps; struct LightMapTexture { - int w, h, type; - GLuint id; - int unlitx, unlity; + int w, h, type; + GLuint id; + int unlitx, unlity; - LightMapTexture() - : w(0), h(0), type(LM_DIFFUSE), id(0), unlitx(-1), unlity(-1) - {} + LightMapTexture() + : w(0), h(0), type(LM_DIFFUSE), id(0), unlitx(-1), unlity(-1) + {} }; extern vector lightmaptexs; @@ -100,20 +100,20 @@ extern void previewblends(const ivec &bo, const ivec &bs); struct lerpvert { - vec normal; - vec2 tc; + vec normal; + vec2 tc; - bool operator==(const lerpvert &l) const { return tc == l.tc;; } - bool operator!=(const lerpvert &l) const { return tc != l.tc; } + bool operator==(const lerpvert &l) const { return tc == l.tc;; } + bool operator!=(const lerpvert &l) const { return tc != l.tc; } }; struct lerpbounds { - const lerpvert *min; - const lerpvert *max; - float u, ustep; - vec normal, nstep; - int winding; + const lerpvert *min; + const lerpvert *max; + float u, ustep; + vec normal, nstep; + int winding; }; extern void calcnormals(bool lerptjoints = false); @@ -124,17 +124,17 @@ extern void initlerpbounds(float u, float v, const lerpvert *lv, int numv, lerpb extern void lerpnormal(float u, float v, const lerpvert *lv, int numv, lerpbounds &start, lerpbounds &end, vec &normal, vec &nstep); #define CHECK_CALCLIGHT_PROGRESS_LOCKED(exit, show_calclight_progress, before, after) \ - if(check_calclight_progress) \ - { \ - if(!calclight_canceled) \ - { \ - before; \ - show_calclight_progress(); \ - check_calclight_canceled(); \ - after; \ - } \ - if(calclight_canceled) { exit; } \ - } + if(check_calclight_progress) \ + { \ + if(!calclight_canceled) \ + { \ + before; \ + show_calclight_progress(); \ + check_calclight_canceled(); \ + after; \ + } \ + if(calclight_canceled) { exit; } \ + } #define CHECK_CALCLIGHT_PROGRESS(exit, show_calclight_progress) CHECK_CALCLIGHT_PROGRESS_LOCKED(exit, show_calclight_progress, , ) extern bool calclight_canceled; diff --git a/src/engine/lightning.h b/src/engine/lightning.h index 16b146d..cec993a 100644 --- a/src/engine/lightning.h +++ b/src/engine/lightning.h @@ -12,112 +12,112 @@ FVAR(lnblendpower, 0, 0.25f, 1000); static void calclightningjitter(int frame) { - loopi(MAXLIGHTNINGSTEPS) - { - lnjitterx[lnjitterframe][i] = -lnjitterradius + rnd(2*lnjitterradius + 1); - lnjittery[lnjitterframe][i] = -lnjitterradius + rnd(2*lnjitterradius + 1); - } + loopi(MAXLIGHTNINGSTEPS) + { + lnjitterx[lnjitterframe][i] = -lnjitterradius + rnd(2*lnjitterradius + 1); + lnjittery[lnjitterframe][i] = -lnjitterradius + rnd(2*lnjitterradius + 1); + } } static void setuplightning() { - if(!lastlnjitter || lastmillis-lastlnjitter > lnjittermillis) - { - if(!lastlnjitter) calclightningjitter(lnjitterframe); - lastlnjitter = lastmillis - (lastmillis%lnjittermillis); - calclightningjitter(lnjitterframe ^= 1); - } + if(!lastlnjitter || lastmillis-lastlnjitter > lnjittermillis) + { + if(!lastlnjitter) calclightningjitter(lnjitterframe); + lastlnjitter = lastmillis - (lastmillis%lnjittermillis); + calclightningjitter(lnjitterframe ^= 1); + } } static void renderlightning(Texture *tex, const vec &o, const vec &d, float sz) { - vec step(d); - step.sub(o); - float len = step.magnitude(); - int numsteps = clamp(int(ceil(len/LIGHTNINGSTEP)), 2, MAXLIGHTNINGSTEPS); - step.div(numsteps+1); - int jitteroffset = detrnd(int(d.x+d.y+d.z), MAXLIGHTNINGSTEPS); - vec cur(o), up, right; - up.orthogonal(step); - up.normalize(); - right.cross(up, step); - right.normalize(); - float scroll = -float(lastmillis%lnscrollmillis)/lnscrollmillis, - scrollscale = lnscrollscale*(LIGHTNINGSTEP*tex->ys)/(sz*tex->xs), - blend = pow(clamp(float(lastmillis - lastlnjitter)/lnjittermillis, 0.0f, 1.0f), lnblendpower), - jitter0 = (1-blend)*lnjitterscale*sz/lnjitterradius, jitter1 = blend*lnjitterscale*sz/lnjitterradius; - gle::begin(GL_TRIANGLE_STRIP); - loopj(numsteps) - { - vec next(cur); - next.add(step); - if(j+1==numsteps) next = d; - else - { - int lj = (j+jitteroffset)%MAXLIGHTNINGSTEPS; - next.add(vec(right).mul((jitter1*lnjitterx[lnjitterframe][lj] + jitter0*lnjitterx[lnjitterframe^1][lj]))); - next.add(vec(up).mul((jitter1*lnjittery[lnjitterframe][lj] + jitter0*lnjittery[lnjitterframe^1][lj]))); - } - vec dir1 = next, dir2 = next, across; - dir1.sub(cur); - dir2.sub(camera1->o); - across.cross(dir2, dir1).normalize().mul(sz); - gle::attribf(cur.x-across.x, cur.y-across.y, cur.z-across.z); - gle::attribf(scroll, 1); - gle::attribf(cur.x+across.x, cur.y+across.y, cur.z+across.z); - gle::attribf(scroll, 0); - scroll += scrollscale; - if(j+1==numsteps) - { - gle::attribf(next.x-across.x, next.y-across.y, next.z-across.z); - gle::attribf(scroll, 1); - gle::attribf(next.x+across.x, next.y+across.y, next.z+across.z); - gle::attribf(scroll, 0); - } - cur = next; - } - gle::end(); + vec step(d); + step.sub(o); + float len = step.magnitude(); + int numsteps = clamp(int(ceil(len/LIGHTNINGSTEP)), 2, MAXLIGHTNINGSTEPS); + step.div(numsteps+1); + int jitteroffset = detrnd(int(d.x+d.y+d.z), MAXLIGHTNINGSTEPS); + vec cur(o), up, right; + up.orthogonal(step); + up.normalize(); + right.cross(up, step); + right.normalize(); + float scroll = -float(lastmillis%lnscrollmillis)/lnscrollmillis, + scrollscale = lnscrollscale*(LIGHTNINGSTEP*tex->ys)/(sz*tex->xs), + blend = pow(clamp(float(lastmillis - lastlnjitter)/lnjittermillis, 0.0f, 1.0f), lnblendpower), + jitter0 = (1-blend)*lnjitterscale*sz/lnjitterradius, jitter1 = blend*lnjitterscale*sz/lnjitterradius; + gle::begin(GL_TRIANGLE_STRIP); + loopj(numsteps) + { + vec next(cur); + next.add(step); + if(j+1==numsteps) next = d; + else + { + int lj = (j+jitteroffset)%MAXLIGHTNINGSTEPS; + next.add(vec(right).mul((jitter1*lnjitterx[lnjitterframe][lj] + jitter0*lnjitterx[lnjitterframe^1][lj]))); + next.add(vec(up).mul((jitter1*lnjittery[lnjitterframe][lj] + jitter0*lnjittery[lnjitterframe^1][lj]))); + } + vec dir1 = next, dir2 = next, across; + dir1.sub(cur); + dir2.sub(camera1->o); + across.cross(dir2, dir1).normalize().mul(sz); + gle::attribf(cur.x-across.x, cur.y-across.y, cur.z-across.z); + gle::attribf(scroll, 1); + gle::attribf(cur.x+across.x, cur.y+across.y, cur.z+across.z); + gle::attribf(scroll, 0); + scroll += scrollscale; + if(j+1==numsteps) + { + gle::attribf(next.x-across.x, next.y-across.y, next.z-across.z); + gle::attribf(scroll, 1); + gle::attribf(next.x+across.x, next.y+across.y, next.z+across.z); + gle::attribf(scroll, 0); + } + cur = next; + } + gle::end(); } struct lightningrenderer : listrenderer { - lightningrenderer() - : listrenderer("packages/particles/lightning.png", 2, PT_LIGHTNING|PT_TRACK|PT_GLARE) - {} + lightningrenderer() + : listrenderer("packages/particles/lightning.png", 2, PT_LIGHTNING|PT_TRACK|PT_GLARE) + {} - void startrender() - { - glDisable(GL_CULL_FACE); - gle::defattrib(gle::ATTRIB_VERTEX, 3, GL_FLOAT); - gle::defattrib(gle::ATTRIB_TEXCOORD0, 2, GL_FLOAT); - } + void startrender() + { + glDisable(GL_CULL_FACE); + gle::defattrib(gle::ATTRIB_VERTEX, 3, GL_FLOAT); + gle::defattrib(gle::ATTRIB_TEXCOORD0, 2, GL_FLOAT); + } - void endrender() - { - glEnable(GL_CULL_FACE); - } + void endrender() + { + glEnable(GL_CULL_FACE); + } - void update() - { - setuplightning(); - } + void update() + { + setuplightning(); + } - void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) - { - pe.maxfade = max(pe.maxfade, fade); - pe.extendbb(o, size); - pe.extendbb(d, size); - } + void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) + { + pe.maxfade = max(pe.maxfade, fade); + pe.extendbb(o, size); + pe.extendbb(d, size); + } - void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) - { - blend = min(blend<<2, 255); - if(type&PT_MOD) //multiply alpha into color - gle::colorub((p->color.r*blend)>>8, (p->color.g*blend)>>8, (p->color.b*blend)>>8); - else - gle::color(p->color, blend); - renderlightning(tex, o, d, p->size); - } + void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) + { + blend = min(blend<<2, 255); + if(type&PT_MOD) //multiply alpha into color + gle::colorub((p->color.r*blend)>>8, (p->color.g*blend)>>8, (p->color.b*blend)>>8); + else + gle::color(p->color, blend); + renderlightning(tex, o, d, p->size); + } }; static lightningrenderer lightnings; diff --git a/src/engine/main.cpp b/src/engine/main.cpp index 499002f..fb32dfd 100644 --- a/src/engine/main.cpp +++ b/src/engine/main.cpp @@ -12,65 +12,59 @@ extern void cleargamma(); void cleanup() { - cleanupserver(); - SDL_ShowCursor(SDL_TRUE); - SDL_SetRelativeMouseMode(SDL_FALSE); - if(screen) SDL_SetWindowGrab(screen, SDL_FALSE); - cleargamma(); - freeocta(worldroot); - extern void clear_command(); clear_command(); - extern void clear_console(); clear_console(); - extern void clear_mdls(); clear_mdls(); - extern void clear_sound(); clear_sound(); - closelogfile(); - #ifdef __APPLE__ - if(screen) SDL_SetWindowFullscreen(screen, 0); - #endif - SDL_Quit(); + cleanupserver(); + SDL_ShowCursor(SDL_TRUE); + SDL_SetRelativeMouseMode(SDL_FALSE); + if(screen) SDL_SetWindowGrab(screen, SDL_FALSE); + cleargamma(); + freeocta(worldroot); + extern void clear_command(); clear_command(); + extern void clear_console(); clear_console(); + extern void clear_mdls(); clear_mdls(); + extern void clear_sound(); clear_sound(); + closelogfile(); + SDL_Quit(); } extern void writeinitcfg(); -void quit() // normal exit +void quit() // normal exit { - writeinitcfg(); - writeservercfg(); - abortconnect(); - disconnect(); - localdisconnect(); - writecfg(); - cleanup(); - exit(EXIT_SUCCESS); + writeinitcfg(); + writeservercfg(); + abortconnect(); + disconnect(); + localdisconnect(); + writecfg(); + cleanup(); + exit(EXIT_SUCCESS); } -void fatal(const char *s, ...) // failure exit +void fatal(const char *s, ...) // failure exit { - static int errors = 0; - errors++; - - if(errors <= 2) // print up to one extra recursive error - { - defvformatstring(msg,s,s); - logoutf("%s", msg); - - if(errors <= 1) // avoid recursion - { - if(SDL_WasInit(SDL_INIT_VIDEO)) - { - SDL_ShowCursor(SDL_TRUE); - SDL_SetRelativeMouseMode(SDL_FALSE); - if(screen) SDL_SetWindowGrab(screen, SDL_FALSE); - cleargamma(); - #ifdef __APPLE__ - if(screen) SDL_SetWindowFullscreen(screen, 0); - #endif - } - SDL_Quit(); - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Cube 2: Sauerbraten fatal error", msg, NULL); - } - } - - exit(EXIT_FAILURE); + static int errors = 0; + errors++; + + if(errors <= 2) // print up to one extra recursive error + { + defvformatstring(msg,s,s); + logoutf("%s", msg); + + if(errors <= 1) // avoid recursion + { + if(SDL_WasInit(SDL_INIT_VIDEO)) + { + SDL_ShowCursor(SDL_TRUE); + SDL_SetRelativeMouseMode(SDL_FALSE); + if(screen) SDL_SetWindowGrab(screen, SDL_FALSE); + cleargamma(); + } + SDL_Quit(); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Cube 2: Sauerbraten fatal error", msg, NULL); + } + } + + exit(EXIT_FAILURE); } int curtime = 0, lastmillis = 1, elapsedtime = 0, totalmillis = 1; @@ -81,12 +75,12 @@ int initing = NOT_INITING; bool initwarning(const char *desc, int level, int type) { - if(initing < level) - { - addchange(desc, type); - return true; - } - return false; + if(initing < level) + { + addchange(desc, type); + return true; + } + return false; } VAR(desktopw, 1, 0, 0); @@ -108,36 +102,36 @@ VARF(fsaa, -1, -1, 16, initwarning("anti-aliasing")); void writeinitcfg() { - stream *f = openutf8file("init.cfg", "w"); - if(!f) return; - f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n"); - extern int fullscreen, fullscreendesktop; - f->printf("fullscreen %d\n", fullscreen); - f->printf("fullscreendesktop %d\n", fullscreendesktop); - f->printf("scr_w %d\n", scr_w); - f->printf("scr_h %d\n", scr_h); - f->printf("depthbits %d\n", depthbits); - f->printf("fsaa %d\n", fsaa); - extern int usesound, soundchans, soundfreq, soundbufferlen; - extern char *audiodriver; - f->printf("usesound %d\n", usesound); - f->printf("soundchans %d\n", soundchans); - f->printf("soundfreq %d\n", soundfreq); - f->printf("soundbufferlen %d\n", soundbufferlen); - if(audiodriver[0]) f->printf("audiodriver %s\n", escapestring(audiodriver)); - delete f; + stream *f = openutf8file("init.cfg", "w"); + if(!f) return; + f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n"); + extern int fullscreen, fullscreendesktop; + f->printf("fullscreen %d\n", fullscreen); + f->printf("fullscreendesktop %d\n", fullscreendesktop); + f->printf("scr_w %d\n", scr_w); + f->printf("scr_h %d\n", scr_h); + f->printf("depthbits %d\n", depthbits); + f->printf("fsaa %d\n", fsaa); + extern int usesound, soundchans, soundfreq, soundbufferlen; + extern char *audiodriver; + f->printf("usesound %d\n", usesound); + f->printf("soundchans %d\n", soundchans); + f->printf("soundfreq %d\n", soundfreq); + f->printf("soundbufferlen %d\n", soundbufferlen); + if(audiodriver[0]) f->printf("audiodriver %s\n", escapestring(audiodriver)); + delete f; } COMMAND(quit, ""); static void getbackgroundres(int &w, int &h) { - float wk = 1, hk = 1; - if(w < 1024) wk = 1024.0f/w; - if(h < 768) hk = 768.0f/h; - wk = hk = max(wk, hk); - w = int(ceil(w*wk)); - h = int(ceil(h*hk)); + float wk = 1, hk = 1; + if(w < 1024) wk = 1024.0f/w; + if(h < 768) hk = 768.0f/h; + wk = hk = max(wk, hk); + w = int(ceil(w*wk)); + h = int(ceil(h*hk)); } string backgroundcaption = ""; @@ -147,156 +141,156 @@ char *backgroundmapinfo = NULL; void setbackgroundinfo(const char *caption = NULL, Texture *mapshot = NULL, const char *mapname = NULL, const char *mapinfo = NULL) { - renderedframe = false; - copystring(backgroundcaption, caption ? caption : ""); - backgroundmapshot = mapshot; - copystring(backgroundmapname, mapname ? mapname : ""); - if(mapinfo != backgroundmapinfo) - { - DELETEA(backgroundmapinfo); - if(mapinfo) backgroundmapinfo = newstring(mapinfo); - } + renderedframe = false; + copystring(backgroundcaption, caption ? caption : ""); + backgroundmapshot = mapshot; + copystring(backgroundmapname, mapname ? mapname : ""); + if(mapinfo != backgroundmapinfo) + { + DELETEA(backgroundmapinfo); + if(mapinfo) backgroundmapinfo = newstring(mapinfo); + } } void restorebackground(bool force = false) { - if(renderedframe) - { - if(!force) return; - setbackgroundinfo(); - } - renderbackground(backgroundcaption[0] ? backgroundcaption : NULL, backgroundmapshot, backgroundmapname[0] ? backgroundmapname : NULL, backgroundmapinfo, true); + if(renderedframe) + { + if(!force) return; + setbackgroundinfo(); + } + renderbackground(backgroundcaption[0] ? backgroundcaption : NULL, backgroundmapshot, backgroundmapname[0] ? backgroundmapname : NULL, backgroundmapinfo, true); } void bgquad(float x, float y, float w, float h, float tx = 0, float ty = 0, float tw = 1, float th = 1) { - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x, y); gle::attribf(tx, ty); - gle::attribf(x+w, y); gle::attribf(tx + tw, ty); - gle::attribf(x, y+h); gle::attribf(tx, ty + th); - gle::attribf(x+w, y+h); gle::attribf(tx + tw, ty + th); - gle::end(); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x, y); gle::attribf(tx, ty); + gle::attribf(x+w, y); gle::attribf(tx + tw, ty); + gle::attribf(x, y+h); gle::attribf(tx, ty + th); + gle::attribf(x+w, y+h); gle::attribf(tx + tw, ty + th); + gle::end(); } void renderbackground(const char *caption, Texture *mapshot, const char *mapname, const char *mapinfo, bool restore, bool force) { - if(!inbetweenframes && !force) return; - - if(!restore || force) stopsounds(); // stop sounds while loading - - int w = screenw, h = screenh; - if(forceaspect) w = int(ceil(h*forceaspect)); - getbackgroundres(w, h); - gettextres(w, h); - - static int lastupdate = -1, lastw = -1, lasth = -1; - static int numdecals = 0; - static struct decal { float x, y, size; int side; } decals[12]; - if((renderedframe && !mainmenu && lastupdate != lastmillis) || lastw != w || lasth != h) - { - lastupdate = lastmillis; - lastw = w; - lasth = h; - - numdecals = sizeof(decals)/sizeof(decals[0]); - numdecals = numdecals/3 + rnd((numdecals*2)/3 + 1); - float maxsize = min(w, h)/16.0f; - loopi(numdecals) - { - decal d = { rndscale(w), rndscale(h), maxsize/2 + rndscale(maxsize/2), rnd(2) }; - decals[i] = d; - } - } - else if(lastupdate != lastmillis) lastupdate = lastmillis; - - loopi(restore ? 1 : 3) - { - hudmatrix.ortho(0, w, h, 0, -1, 1); - resethudmatrix(); - - hudshader->set(); - gle::colorf(1, 1, 1); - - gle::defvertex(2); - gle::deftexcoord0(); - - settexture("background/daemex.png", 0); - bgquad(0, 0, screenw, screenh, 0, 0, 1, 1); - glEnable(GL_BLEND); - float lh = 0.5f*min(w, h), lw = lh*2, - lx = 0.5f*(w - lw), ly = 0.5f*(h*0.5f - lh); - settexture((maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize) >= 1024 && (screenw > 1280 || screenh > 800) ? "data/logo_1024.png" : "data/logo.png", 3); - bgquad(lx, ly, lw, lh); - if(caption) - { - int tw = text_width(caption); - float tsz = 0.04f*min(w, h)/FONTH, - tx = 0.5f*(w - tw*tsz), ty = h - 0.075f*1.5f*min(w, h) - 1.25f*FONTH*tsz; - pushhudmatrix(); - hudmatrix.translate(tx, ty, 0); - hudmatrix.scale(tsz, tsz, 1); - flushhudmatrix(); - draw_text(caption, 0, 0); - pophudmatrix(); - } - if(mapshot || mapname) - { - int infowidth = 12*FONTH; - float sz = 0.35f*min(w, h), msz = (0.75f*min(w, h) - sz)/(infowidth + FONTH), x = 0.5f*(w-sz), y = ly+lh - sz/15; - if(mapinfo) - { - int mw, mh; - text_bounds(mapinfo, mw, mh, infowidth); - x -= 0.5f*(mw*msz + FONTH*msz); - } - if(mapshot && mapshot!=notexture) - { - glBindTexture(GL_TEXTURE_2D, mapshot->id); - bgquad(x, y, sz, sz); - } - else - { - int qw, qh; - text_bounds("?", qw, qh); - float qsz = sz*0.5f/max(qw, qh); - pushhudmatrix(); - hudmatrix.translate(x + 0.5f*(sz - qw*qsz), y + 0.5f*(sz - qh*qsz), 0); - hudmatrix.scale(qsz, qsz, 1); - flushhudmatrix(); - draw_text("?", 0, 0); - pophudmatrix(); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - settexture("data/mapshot_frame.png", 3); - bgquad(x, y, sz, sz); - if(mapname) - { - int tw = text_width(mapname); - float tsz = sz/(8*FONTH), - tx = 0.9f*sz - tw*tsz, ty = 0.9f*sz - FONTH*tsz; - if(tx < 0.1f*sz) { tsz = 0.1f*sz/tw; tx = 0.1f; } - pushhudmatrix(); - hudmatrix.translate(x+tx, y+ty, 0); - hudmatrix.scale(tsz, tsz, 1); - flushhudmatrix(); - draw_text(mapname, 0, 0); - pophudmatrix(); - } - if(mapinfo) - { - pushhudmatrix(); - hudmatrix.translate(x+sz+FONTH*msz, y, 0); - hudmatrix.scale(msz, msz, 1); - flushhudmatrix(); - draw_text(mapinfo, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, -1, infowidth); - pophudmatrix(); - } - } - glDisable(GL_BLEND); - if(!restore) swapbuffers(false); - } - - if(!restore) setbackgroundinfo(caption, mapshot, mapname, mapinfo); + if(!inbetweenframes && !force) return; + + if(!restore || force) stopsounds(); // stop sounds while loading + + int w = screenw, h = screenh; + if(forceaspect) w = int(ceil(h*forceaspect)); + getbackgroundres(w, h); + gettextres(w, h); + + static int lastupdate = -1, lastw = -1, lasth = -1; + static int numdecals = 0; + static struct decal { float x, y, size; int side; } decals[12]; + if((renderedframe && !mainmenu && lastupdate != lastmillis) || lastw != w || lasth != h) + { + lastupdate = lastmillis; + lastw = w; + lasth = h; + + numdecals = sizeof(decals)/sizeof(decals[0]); + numdecals = numdecals/3 + rnd((numdecals*2)/3 + 1); + float maxsize = min(w, h)/16.0f; + loopi(numdecals) + { + decal d = { rndscale(w), rndscale(h), maxsize/2 + rndscale(maxsize/2), rnd(2) }; + decals[i] = d; + } + } + else if(lastupdate != lastmillis) lastupdate = lastmillis; + + loopi(restore ? 1 : 3) + { + hudmatrix.ortho(0, w, h, 0, -1, 1); + resethudmatrix(); + + hudshader->set(); + gle::colorf(1, 1, 1); + + gle::defvertex(2); + gle::deftexcoord0(); + + settexture("background/daemex.png", 0); + bgquad(0, 0, screenw, screenh, 0, 0, 1, 1); + glEnable(GL_BLEND); + float lh = 0.5f*min(w, h), lw = lh*2, + lx = 0.5f*(w - lw), ly = 0.5f*(h*0.5f - lh); + settexture((maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize) >= 1024 && (screenw > 1280 || screenh > 800) ? "data/logo_1024.png" : "data/logo.png", 3); + bgquad(lx, ly, lw, lh); + if(caption) + { + int tw = text_width(caption); + float tsz = 0.04f*min(w, h)/FONTH, + tx = 0.5f*(w - tw*tsz), ty = h - 0.075f*1.5f*min(w, h) - 1.25f*FONTH*tsz; + pushhudmatrix(); + hudmatrix.translate(tx, ty, 0); + hudmatrix.scale(tsz, tsz, 1); + flushhudmatrix(); + draw_text(caption, 0, 0); + pophudmatrix(); + } + if(mapshot || mapname) + { + int infowidth = 12*FONTH; + float sz = 0.35f*min(w, h), msz = (0.75f*min(w, h) - sz)/(infowidth + FONTH), x = 0.5f*(w-sz), y = ly+lh - sz/15; + if(mapinfo) + { + int mw, mh; + text_bounds(mapinfo, mw, mh, infowidth); + x -= 0.5f*(mw*msz + FONTH*msz); + } + if(mapshot && mapshot!=notexture) + { + glBindTexture(GL_TEXTURE_2D, mapshot->id); + bgquad(x, y, sz, sz); + } + else + { + int qw, qh; + text_bounds("?", qw, qh); + float qsz = sz*0.5f/max(qw, qh); + pushhudmatrix(); + hudmatrix.translate(x + 0.5f*(sz - qw*qsz), y + 0.5f*(sz - qh*qsz), 0); + hudmatrix.scale(qsz, qsz, 1); + flushhudmatrix(); + draw_text("?", 0, 0); + pophudmatrix(); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + settexture("data/mapshot_frame.png", 3); + bgquad(x, y, sz, sz); + if(mapname) + { + int tw = text_width(mapname); + float tsz = sz/(8*FONTH), + tx = 0.9f*sz - tw*tsz, ty = 0.9f*sz - FONTH*tsz; + if(tx < 0.1f*sz) { tsz = 0.1f*sz/tw; tx = 0.1f; } + pushhudmatrix(); + hudmatrix.translate(x+tx, y+ty, 0); + hudmatrix.scale(tsz, tsz, 1); + flushhudmatrix(); + draw_text(mapname, 0, 0); + pophudmatrix(); + } + if(mapinfo) + { + pushhudmatrix(); + hudmatrix.translate(x+sz+FONTH*msz, y, 0); + hudmatrix.scale(msz, msz, 1); + flushhudmatrix(); + draw_text(mapinfo, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, -1, infowidth); + pophudmatrix(); + } + } + glDisable(GL_BLEND); + if(!restore) swapbuffers(false); + } + + if(!restore) setbackgroundinfo(caption, mapshot, mapname, mapinfo); } VAR(progressbackground, 0, 0, 1); @@ -305,108 +299,108 @@ float loadprogress = 0; void renderprogress(float bar, const char *text, GLuint tex, bool background) // also used during loading { - if(!inbetweenframes || drawtex) return; - - extern int menufps, maxfps; - int fps = menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps; - if(fps) - { - static int lastprogress = 0; - int ticks = SDL_GetTicks(), diff = ticks - lastprogress; - if(bar > 0 && diff >= 0 && diff < (1000 + fps-1)/fps) return; - lastprogress = ticks; - } - - clientkeepalive(); // make sure our connection doesn't time out while loading maps etc. - - SDL_PumpEvents(); // keep the event queue awake to avoid 'beachball' cursor - - extern int mesa_swap_bug, curvsync; - bool forcebackground = progressbackground || (mesa_swap_bug && (curvsync || totalmillis==1)); - if(background || forcebackground) restorebackground(forcebackground); - - int w = screenw, h = screenh; - if(forceaspect) w = int(ceil(h*forceaspect)); - getbackgroundres(w, h); - gettextres(w, h); - - hudmatrix.ortho(0, w, h, 0, -1, 1); - resethudmatrix(); - - hudshader->set(); - gle::colorf(1, 1, 1); - - gle::defvertex(2); - gle::deftexcoord0(); - - float fh = 0.075f*min(w, h), fw = fh*10, - fx = renderedframe ? w - fw - fh/4 : 0.5f*(w - fw), - fy = renderedframe ? fh/4 : h - fh*1.5f, - fu1 = 0/512.0f, fu2 = 511/512.0f, - fv1 = 0/64.0f, fv2 = 52/64.0f; - settexture("data/loading_frame.png", 3); - bgquad(fx, fy, fw, fh, fu1, fv1, fu2-fu1, fv2-fv1); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - float bw = fw*(511 - 2*17)/511.0f, bh = fh*20/52.0f, - bx = fx + fw*17/511.0f, by = fy + fh*16/52.0f, - bv1 = 0/32.0f, bv2 = 20/32.0f, - su1 = 0/32.0f, su2 = 7/32.0f, sw = fw*7/511.0f, - eu1 = 23/32.0f, eu2 = 30/32.0f, ew = fw*7/511.0f, - mw = bw - sw - ew, - ex = bx+sw + max(mw*bar, fw*7/511.0f); - if(bar > 0) - { - settexture("data/loading_bar.png", 3); - gle::begin(GL_QUADS); - gle::attribf(bx, by); gle::attribf(su1, bv1); - gle::attribf(bx+sw, by); gle::attribf(su2, bv1); - gle::attribf(bx+sw, by+bh); gle::attribf(su2, bv2); - gle::attribf(bx, by+bh); gle::attribf(su1, bv2); - - gle::attribf(bx+sw, by); gle::attribf(su2, bv1); - gle::attribf(ex, by); gle::attribf(eu1, bv1); - gle::attribf(ex, by+bh); gle::attribf(eu1, bv2); - gle::attribf(bx+sw, by+bh); gle::attribf(su2, bv2); - - gle::attribf(ex, by); gle::attribf(eu1, bv1); - gle::attribf(ex+ew, by); gle::attribf(eu2, bv1); - gle::attribf(ex+ew, by+bh); gle::attribf(eu2, bv2); - gle::attribf(ex, by+bh); gle::attribf(eu1, bv2); - gle::end(); - } - - if(text) - { - int tw = text_width(text); - float tsz = bh*0.8f/FONTH; - if(tw*tsz > mw) tsz = mw/tw; - pushhudmatrix(); - hudmatrix.translate(bx+sw, by + (bh - FONTH*tsz)/2, 0); - hudmatrix.scale(tsz, tsz, 1); - flushhudmatrix(); - draw_text(text, 0, 0); - pophudmatrix(); - } - - glDisable(GL_BLEND); - - if(tex) - { - glBindTexture(GL_TEXTURE_2D, tex); - float sz = 0.35f*min(w, h), x = 0.5f*(w-sz), y = 0.5f*min(w, h) - sz/15; - bgquad(x, y, sz, sz); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - settexture("data/mapshot_frame.png", 3); - bgquad(x, y, sz, sz); - glDisable(GL_BLEND); - } - - swapbuffers(false); + if(!inbetweenframes || drawtex) return; + + extern int menufps, maxfps; + int fps = menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps; + if(fps) + { + static int lastprogress = 0; + int ticks = SDL_GetTicks(), diff = ticks - lastprogress; + if(bar > 0 && diff >= 0 && diff < (1000 + fps-1)/fps) return; + lastprogress = ticks; + } + + clientkeepalive(); // make sure our connection doesn't time out while loading maps etc. + + SDL_PumpEvents(); // keep the event queue awake to avoid 'beachball' cursor + + extern int mesa_swap_bug, curvsync; + bool forcebackground = progressbackground || (mesa_swap_bug && (curvsync || totalmillis==1)); + if(background || forcebackground) restorebackground(forcebackground); + + int w = screenw, h = screenh; + if(forceaspect) w = int(ceil(h*forceaspect)); + getbackgroundres(w, h); + gettextres(w, h); + + hudmatrix.ortho(0, w, h, 0, -1, 1); + resethudmatrix(); + + hudshader->set(); + gle::colorf(1, 1, 1); + + gle::defvertex(2); + gle::deftexcoord0(); + + float fh = 0.075f*min(w, h), fw = fh*10, + fx = renderedframe ? w - fw - fh/4 : 0.5f*(w - fw), + fy = renderedframe ? fh/4 : h - fh*1.5f, + fu1 = 0/512.0f, fu2 = 511/512.0f, + fv1 = 0/64.0f, fv2 = 52/64.0f; + settexture("data/loading_frame.png", 3); + bgquad(fx, fy, fw, fh, fu1, fv1, fu2-fu1, fv2-fv1); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + float bw = fw*(511 - 2*17)/511.0f, bh = fh*20/52.0f, + bx = fx + fw*17/511.0f, by = fy + fh*16/52.0f, + bv1 = 0/32.0f, bv2 = 20/32.0f, + su1 = 0/32.0f, su2 = 7/32.0f, sw = fw*7/511.0f, + eu1 = 23/32.0f, eu2 = 30/32.0f, ew = fw*7/511.0f, + mw = bw - sw - ew, + ex = bx+sw + max(mw*bar, fw*7/511.0f); + if(bar > 0) + { + settexture("data/loading_bar.png", 3); + gle::begin(GL_QUADS); + gle::attribf(bx, by); gle::attribf(su1, bv1); + gle::attribf(bx+sw, by); gle::attribf(su2, bv1); + gle::attribf(bx+sw, by+bh); gle::attribf(su2, bv2); + gle::attribf(bx, by+bh); gle::attribf(su1, bv2); + + gle::attribf(bx+sw, by); gle::attribf(su2, bv1); + gle::attribf(ex, by); gle::attribf(eu1, bv1); + gle::attribf(ex, by+bh); gle::attribf(eu1, bv2); + gle::attribf(bx+sw, by+bh); gle::attribf(su2, bv2); + + gle::attribf(ex, by); gle::attribf(eu1, bv1); + gle::attribf(ex+ew, by); gle::attribf(eu2, bv1); + gle::attribf(ex+ew, by+bh); gle::attribf(eu2, bv2); + gle::attribf(ex, by+bh); gle::attribf(eu1, bv2); + gle::end(); + } + + if(text) + { + int tw = text_width(text); + float tsz = bh*0.8f/FONTH; + if(tw*tsz > mw) tsz = mw/tw; + pushhudmatrix(); + hudmatrix.translate(bx+sw, by + (bh - FONTH*tsz)/2, 0); + hudmatrix.scale(tsz, tsz, 1); + flushhudmatrix(); + draw_text(text, 0, 0); + pophudmatrix(); + } + + glDisable(GL_BLEND); + + if(tex) + { + glBindTexture(GL_TEXTURE_2D, tex); + float sz = 0.35f*min(w, h), x = 0.5f*(w-sz), y = 0.5f*min(w, h) - sz/15; + bgquad(x, y, sz, sz); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + settexture("data/mapshot_frame.png", 3); + bgquad(x, y, sz, sz); + glDisable(GL_BLEND); + } + + swapbuffers(false); } int keyrepeatmask = 0, textinputmask = 0; @@ -415,34 +409,29 @@ VAR(textinputfilter, 0, 5, 1000); void keyrepeat(bool on, int mask) { - if(on) keyrepeatmask |= mask; - else keyrepeatmask &= ~mask; + if(on) keyrepeatmask |= mask; + else keyrepeatmask &= ~mask; } void textinput(bool on, int mask) { - if(on) - { - if(!textinputmask) - { - SDL_StartTextInput(); - textinputtime = SDL_GetTicks(); - } - textinputmask |= mask; - } - else if(textinputmask) - { - textinputmask &= ~mask; - if(!textinputmask) SDL_StopTextInput(); - } + if(on) + { + if(!textinputmask) + { + SDL_StartTextInput(); + textinputtime = SDL_GetTicks(); + } + textinputmask |= mask; + } + else if(textinputmask) + { + textinputmask &= ~mask; + if(!textinputmask) SDL_StopTextInput(); + } } -#ifdef WIN32 -// SDL_WarpMouseInWindow behaves erratically on Windows, so force relative mouse instead. -VARN(relativemouse, userelativemouse, 1, 1, 0); -#else VARNP(relativemouse, userelativemouse, 0, 1, 1); -#endif bool shouldgrab = false, grabinput = false, minimized = false, canrelativemouse = true, relativemouse = false; @@ -453,54 +442,54 @@ VAR(sdl_xgrab_bug, 0, 0, 1); void inputgrab(bool on, bool delay = false) { #ifdef SDL_VIDEO_DRIVER_X11 - bool wasrelativemouse = relativemouse; + bool wasrelativemouse = relativemouse; #endif - if(on) - { - SDL_ShowCursor(SDL_FALSE); - if(canrelativemouse && userelativemouse) - { - if(SDL_SetRelativeMouseMode(SDL_TRUE) >= 0) - { - SDL_SetWindowGrab(screen, SDL_TRUE); - relativemouse = true; - } - else - { - SDL_SetWindowGrab(screen, SDL_FALSE); - canrelativemouse = false; - relativemouse = false; - } - } - } - else - { - SDL_ShowCursor(SDL_TRUE); - if(relativemouse) - { - SDL_SetWindowGrab(screen, SDL_FALSE); - SDL_SetRelativeMouseMode(SDL_FALSE); - relativemouse = false; - } - } - shouldgrab = delay; + if(on) + { + SDL_ShowCursor(SDL_FALSE); + if(canrelativemouse && userelativemouse) + { + if(SDL_SetRelativeMouseMode(SDL_TRUE) >= 0) + { + SDL_SetWindowGrab(screen, SDL_TRUE); + relativemouse = true; + } + else + { + SDL_SetWindowGrab(screen, SDL_FALSE); + canrelativemouse = false; + relativemouse = false; + } + } + } + else + { + SDL_ShowCursor(SDL_TRUE); + if(relativemouse) + { + SDL_SetWindowGrab(screen, SDL_FALSE); + SDL_SetRelativeMouseMode(SDL_FALSE); + relativemouse = false; + } + } + shouldgrab = delay; #ifdef SDL_VIDEO_DRIVER_X11 - if((relativemouse || wasrelativemouse) && sdl_xgrab_bug) - { - // Workaround for buggy SDL X11 pointer grabbing - union { SDL_SysWMinfo info; uchar buf[sizeof(SDL_SysWMinfo) + 128]; }; - SDL_GetVersion(&info.version); - if(SDL_GetWindowWMInfo(screen, &info) && info.subsystem == SDL_SYSWM_X11) - { - if(relativemouse) - { - uint mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; - XGrabPointer(info.info.x11.display, info.info.x11.window, True, mask, GrabModeAsync, GrabModeAsync, info.info.x11.window, None, CurrentTime); - } - else XUngrabPointer(info.info.x11.display, CurrentTime); - } - } + if((relativemouse || wasrelativemouse) && sdl_xgrab_bug) + { + // Workaround for buggy SDL X11 pointer grabbing + union { SDL_SysWMinfo info; uchar buf[sizeof(SDL_SysWMinfo) + 128]; }; + SDL_GetVersion(&info.version); + if(SDL_GetWindowWMInfo(screen, &info) && info.subsystem == SDL_SYSWM_X11) + { + if(relativemouse) + { + uint mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; + XGrabPointer(info.info.x11.display, info.info.x11.window, True, mask, GrabModeAsync, GrabModeAsync, info.info.x11.window, None, CurrentTime); + } + else XUngrabPointer(info.info.x11.display, CurrentTime); + } + } #endif } @@ -508,20 +497,20 @@ bool initwindowpos = false; void setfullscreen(bool enable) { - if(!screen) return; - //initwarning(enable ? "fullscreen" : "windowed"); - extern int fullscreendesktop; - SDL_SetWindowFullscreen(screen, enable ? (fullscreendesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) : 0); - if(!enable) - { - SDL_SetWindowSize(screen, scr_w, scr_h); - if(initwindowpos) - { - int winx = SDL_WINDOWPOS_CENTERED, winy = SDL_WINDOWPOS_CENTERED; - SDL_SetWindowPosition(screen, winx, winy); - initwindowpos = false; - } - } + if(!screen) return; + //initwarning(enable ? "fullscreen" : "windowed"); + extern int fullscreendesktop; + SDL_SetWindowFullscreen(screen, enable ? (fullscreendesktop ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN) : 0); + if(!enable) + { + SDL_SetWindowSize(screen, scr_w, scr_h); + if(initwindowpos) + { + int winx = SDL_WINDOWPOS_CENTERED, winy = SDL_WINDOWPOS_CENTERED; + SDL_SetWindowPosition(screen, winx, winy); + initwindowpos = false; + } + } } #ifdef _DEBUG @@ -532,76 +521,76 @@ VARF(fullscreen, 0, 1, 1, setfullscreen(fullscreen!=0)); void resetfullscreen() { - setfullscreen(false); - setfullscreen(true); + setfullscreen(false); + setfullscreen(true); } VARF(fullscreendesktop, 0, 0, 1, if(fullscreen) resetfullscreen()); void screenres(int w, int h) { - scr_w = clamp(w, SCR_MINW, SCR_MAXW); - scr_h = clamp(h, SCR_MINH, SCR_MAXH); - if(screen) - { - if(fullscreendesktop) - { - scr_w = min(scr_w, desktopw); - scr_h = min(scr_h, desktoph); - } - if(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN) - { - if(fullscreendesktop) gl_resize(); - else resetfullscreen(); - initwindowpos = true; - } - else - { - SDL_SetWindowSize(screen, scr_w, scr_h); - SDL_SetWindowPosition(screen, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); - initwindowpos = false; - } - } - else - { - initwarning("screen resolution"); - } + scr_w = clamp(w, SCR_MINW, SCR_MAXW); + scr_h = clamp(h, SCR_MINH, SCR_MAXH); + if(screen) + { + if(fullscreendesktop) + { + scr_w = min(scr_w, desktopw); + scr_h = min(scr_h, desktoph); + } + if(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN) + { + if(fullscreendesktop) gl_resize(); + else resetfullscreen(); + initwindowpos = true; + } + else + { + SDL_SetWindowSize(screen, scr_w, scr_h); + SDL_SetWindowPosition(screen, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + initwindowpos = false; + } + } + else + { + initwarning("screen resolution"); + } } ICOMMAND(screenres, "ii", (int *w, int *h), screenres(*w, *h)); static void setgamma(int val) { - if(screen && SDL_SetWindowBrightness(screen, val/100.0f) < 0) conoutf(CON_ERROR, "Could not set gamma: %s", SDL_GetError()); + if(screen && SDL_SetWindowBrightness(screen, val/100.0f) < 0) conoutf(CON_ERROR, "Could not set gamma: %s", SDL_GetError()); } static int curgamma = 100; VARFNP(gamma, reqgamma, 30, 100, 300, { - if(initing || reqgamma == curgamma) return; - curgamma = reqgamma; - setgamma(curgamma); + if(initing || reqgamma == curgamma) return; + curgamma = reqgamma; + setgamma(curgamma); }); void restoregamma() { - if(initing || reqgamma == 100) return; - curgamma = reqgamma; - setgamma(curgamma); + if(initing || reqgamma == 100) return; + curgamma = reqgamma; + setgamma(curgamma); } void cleargamma() { - if(curgamma != 100 && screen) SDL_SetWindowBrightness(screen, 1.0f); + if(curgamma != 100 && screen) SDL_SetWindowBrightness(screen, 1.0f); } int curvsync = -1; void restorevsync() { - if(initing || !glcontext) return; - extern int vsync, vsynctear; - if(!SDL_GL_SetSwapInterval(vsync ? (vsynctear ? -1 : 1) : 0)) - curvsync = vsync; + if(initing || !glcontext) return; + extern int vsync, vsynctear; + if(!SDL_GL_SetSwapInterval(vsync ? (vsynctear ? -1 : 1) : 0)) + curvsync = vsync; } VARFP(vsync, 0, 0, 1, restorevsync()); @@ -609,168 +598,162 @@ VARFP(vsynctear, 0, 0, 1, { if(vsync) restorevsync(); }); void setupscreen() { - if(glcontext) - { - SDL_GL_DeleteContext(glcontext); - glcontext = NULL; - } - if(screen) - { - SDL_DestroyWindow(screen); - screen = NULL; - } - curvsync = -1; - - SDL_Rect desktop; - if(SDL_GetDisplayBounds(0, &desktop) < 0) fatal("failed querying desktop bounds: %s", SDL_GetError()); - desktopw = desktop.w; - desktoph = desktop.h; - - if(scr_h < 0) scr_h = fullscreen ? desktoph : SCR_DEFAULTH; - if(scr_w < 0) scr_w = (scr_h*desktopw)/desktoph; - scr_w = clamp(scr_w, SCR_MINW, SCR_MAXW); - scr_h = clamp(scr_h, SCR_MINH, SCR_MAXH); - if(fullscreendesktop) - { - scr_w = min(scr_w, desktopw); - scr_h = min(scr_h, desktoph); - } - - int winx = SDL_WINDOWPOS_UNDEFINED, winy = SDL_WINDOWPOS_UNDEFINED, winw = scr_w, winh = scr_h, flags = SDL_WINDOW_RESIZABLE; - if(fullscreen) - { - if(fullscreendesktop) - { - winw = desktopw; - winh = desktoph; - flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; - } - else flags |= SDL_WINDOW_FULLSCREEN; - initwindowpos = true; - } - - SDL_GL_ResetAttributes(); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - #if !defined(WIN32) && !defined(__APPLE__) - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); - #endif - static const int configs[] = - { - 0x3, /* try everything */ - 0x2, 0x1, /* try disabling one at a time */ - 0 /* try disabling everything */ - }; - int config = 0; - if(!depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - if(!fsaa) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - } - loopi(sizeof(configs)/sizeof(configs[0])) - { - config = configs[i]; - if(!depthbits && config&1) continue; - if(fsaa<=0 && config&2) continue; - if(depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config&1 ? depthbits : 24); - if(fsaa>0) - { - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, config&2 ? 1 : 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config&2 ? fsaa : 0); - } - screen = SDL_CreateWindow("Cube 2: Sauerbraten", winx, winy, winw, winh, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags); - if(!screen) continue; - - #ifdef __APPLE__ - static const int glversions[] = { 32, 20 }; - #else - static const int glversions[] = { 33, 32, 31, 30, 20 }; - #endif - loopj(sizeof(glversions)/sizeof(glversions[0])) - { - glcompat = glversions[j] <= 30 ? 1 : 0; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, glversions[j] / 10); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, glversions[j] % 10); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, glversions[j] >= 32 ? SDL_GL_CONTEXT_PROFILE_CORE : 0); - glcontext = SDL_GL_CreateContext(screen); - if(glcontext) break; - } - if(glcontext) break; - } - if(!screen) fatal("failed to create OpenGL window: %s", SDL_GetError()); - else if(!glcontext) fatal("failed to create OpenGL context: %s", SDL_GetError()); - else - { - if(depthbits && (config&1)==0) conoutf(CON_WARN, "%d bit z-buffer not supported - disabling", depthbits); - if(fsaa>0 && (config&2)==0) conoutf(CON_WARN, "%dx anti-aliasing not supported - disabling", fsaa); - } - - SDL_SetWindowMinimumSize(screen, SCR_MINW, SCR_MINH); - SDL_SetWindowMaximumSize(screen, SCR_MAXW, SCR_MAXH); - - SDL_GetWindowSize(screen, &screenw, &screenh); + if(glcontext) + { + SDL_GL_DeleteContext(glcontext); + glcontext = NULL; + } + if(screen) + { + SDL_DestroyWindow(screen); + screen = NULL; + } + curvsync = -1; + + SDL_Rect desktop; + if(SDL_GetDisplayBounds(0, &desktop) < 0) fatal("failed querying desktop bounds: %s", SDL_GetError()); + desktopw = desktop.w; + desktoph = desktop.h; + + if(scr_h < 0) scr_h = fullscreen ? desktoph : SCR_DEFAULTH; + if(scr_w < 0) scr_w = (scr_h*desktopw)/desktoph; + scr_w = clamp(scr_w, SCR_MINW, SCR_MAXW); + scr_h = clamp(scr_h, SCR_MINH, SCR_MAXH); + if(fullscreendesktop) + { + scr_w = min(scr_w, desktopw); + scr_h = min(scr_h, desktoph); + } + + int winx = SDL_WINDOWPOS_UNDEFINED, winy = SDL_WINDOWPOS_UNDEFINED, winw = scr_w, winh = scr_h, flags = SDL_WINDOW_RESIZABLE; + if(fullscreen) + { + if(fullscreendesktop) + { + winw = desktopw; + winh = desktoph; + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + else flags |= SDL_WINDOW_FULLSCREEN; + initwindowpos = true; + } + + SDL_GL_ResetAttributes(); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + static const int configs[] = + { + 0x3, /* try everything */ + 0x2, 0x1, /* try disabling one at a time */ + 0 /* try disabling everything */ + }; + int config = 0; + if(!depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + if(!fsaa) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + } + loopi(sizeof(configs)/sizeof(configs[0])) + { + config = configs[i]; + if(!depthbits && config&1) continue; + if(fsaa<=0 && config&2) continue; + if(depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config&1 ? depthbits : 24); + if(fsaa>0) + { + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, config&2 ? 1 : 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, config&2 ? fsaa : 0); + } + screen = SDL_CreateWindow("Cube 2: Sauerbraten", winx, winy, winw, winh, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS | flags); + if(!screen) continue; + + static const int glversions[] = { 33, 32, 31, 30, 20 }; + loopj(sizeof(glversions)/sizeof(glversions[0])) + { + glcompat = glversions[j] <= 30 ? 1 : 0; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, glversions[j] / 10); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, glversions[j] % 10); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, glversions[j] >= 32 ? SDL_GL_CONTEXT_PROFILE_CORE : 0); + glcontext = SDL_GL_CreateContext(screen); + if(glcontext) break; + } + if(glcontext) break; + } + if(!screen) fatal("failed to create OpenGL window: %s", SDL_GetError()); + else if(!glcontext) fatal("failed to create OpenGL context: %s", SDL_GetError()); + else + { + if(depthbits && (config&1)==0) conoutf(CON_WARN, "%d bit z-buffer not supported - disabling", depthbits); + if(fsaa>0 && (config&2)==0) conoutf(CON_WARN, "%dx anti-aliasing not supported - disabling", fsaa); + } + + SDL_SetWindowMinimumSize(screen, SCR_MINW, SCR_MINH); + SDL_SetWindowMaximumSize(screen, SCR_MAXW, SCR_MAXH); + + SDL_GetWindowSize(screen, &screenw, &screenh); } void resetgl() { - clearchanges(CHANGE_GFX); - - renderbackground("resetting OpenGL"); - - extern void cleanupva(); - extern void cleanupparticles(); - extern void cleanupdecals(); - extern void cleanupsky(); - extern void cleanupmodels(); - extern void cleanupprefabs(); - extern void cleanuplightmaps(); - extern void cleanupblendmap(); - extern void cleanshadowmap(); - extern void cleanreflections(); - extern void cleanupglare(); - extern void cleanupdepthfx(); - cleanupva(); - cleanupparticles(); - cleanupdecals(); - cleanupsky(); - cleanupmodels(); - cleanupprefabs(); - cleanuptextures(); - cleanuplightmaps(); - cleanupblendmap(); - cleanshadowmap(); - cleanreflections(); - cleanupglare(); - cleanupdepthfx(); - cleanupshaders(); - cleanupgl(); - - setupscreen(); - inputgrab(grabinput); - gl_init(); - - inbetweenframes = false; - if(!reloadtexture(*notexture) || - !reloadtexture("data/logo.png") || - !reloadtexture("data/logo_1024.png") || - !reloadtexture("data/background.png") || - !reloadtexture("data/background_detail.png") || - !reloadtexture("data/background_decal.png") || - !reloadtexture("data/mapshot_frame.png") || - !reloadtexture("data/loading_frame.png") || - !reloadtexture("data/loading_bar.png")) - fatal("failed to reload core texture"); - reloadfonts(); - inbetweenframes = true; - renderbackground("initializing..."); - restoregamma(); - restorevsync(); - reloadshaders(); - reloadtextures(); - initlights(); - allchanged(true); + clearchanges(CHANGE_GFX); + + renderbackground("resetting OpenGL"); + + extern void cleanupva(); + extern void cleanupparticles(); + extern void cleanupdecals(); + extern void cleanupsky(); + extern void cleanupmodels(); + extern void cleanupprefabs(); + extern void cleanuplightmaps(); + extern void cleanupblendmap(); + extern void cleanshadowmap(); + extern void cleanreflections(); + extern void cleanupglare(); + extern void cleanupdepthfx(); + cleanupva(); + cleanupparticles(); + cleanupdecals(); + cleanupsky(); + cleanupmodels(); + cleanupprefabs(); + cleanuptextures(); + cleanuplightmaps(); + cleanupblendmap(); + cleanshadowmap(); + cleanreflections(); + cleanupglare(); + cleanupdepthfx(); + cleanupshaders(); + cleanupgl(); + + setupscreen(); + inputgrab(grabinput); + gl_init(); + + inbetweenframes = false; + if(!reloadtexture(*notexture) || + !reloadtexture("data/logo.png") || + !reloadtexture("data/logo_1024.png") || + !reloadtexture("data/background.png") || + !reloadtexture("data/background_detail.png") || + !reloadtexture("data/background_decal.png") || + !reloadtexture("data/mapshot_frame.png") || + !reloadtexture("data/loading_frame.png") || + !reloadtexture("data/loading_bar.png")) + fatal("failed to reload core texture"); + reloadfonts(); + inbetweenframes = true; + renderbackground("initializing..."); + restoregamma(); + restorevsync(); + reloadshaders(); + reloadtextures(); + initlights(); + allchanged(true); } COMMAND(resetgl, ""); @@ -779,233 +762,229 @@ static queue events; static inline bool filterevent(const SDL_Event &event) { - switch(event.type) - { - case SDL_MOUSEMOTION: - if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) - { - if(event.motion.x == screenw / 2 && event.motion.y == screenh / 2) - return false; // ignore any motion events generated by SDL_WarpMouse - #ifdef __APPLE__ - if(event.motion.y == 0) - return false; // let mac users drag windows via the title bar - #endif - } - break; - } - return true; + switch(event.type) + { + case SDL_MOUSEMOTION: + if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) + { + if(event.motion.x == screenw / 2 && event.motion.y == screenh / 2) + return false; // ignore any motion events generated by SDL_WarpMouse + } + break; + } + return true; } template static inline bool pumpevents(queue &events) { - while(events.empty()) - { - SDL_PumpEvents(); - databuf buf = events.reserve(events.capacity()); - int n = SDL_PeepEvents(buf.getbuf(), buf.remaining(), SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); - if(n <= 0) return false; - loopi(n) if(filterevent(buf.buf[i])) buf.put(buf.buf[i]); - events.addbuf(buf); - } - return true; + while(events.empty()) + { + SDL_PumpEvents(); + databuf buf = events.reserve(events.capacity()); + int n = SDL_PeepEvents(buf.getbuf(), buf.remaining(), SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT); + if(n <= 0) return false; + loopi(n) if(filterevent(buf.buf[i])) buf.put(buf.buf[i]); + events.addbuf(buf); + } + return true; } static int interceptkeysym = 0; static int interceptevents(void *data, SDL_Event *event) { - switch(event->type) - { - case SDL_MOUSEMOTION: return 0; - case SDL_KEYDOWN: - if(event->key.keysym.sym == interceptkeysym) - { - interceptkeysym = -interceptkeysym; - return 0; - } - break; - } - return 1; + switch(event->type) + { + case SDL_MOUSEMOTION: return 0; + case SDL_KEYDOWN: + if(event->key.keysym.sym == interceptkeysym) + { + interceptkeysym = -interceptkeysym; + return 0; + } + break; + } + return 1; } static void clearinterceptkey() { - SDL_DelEventWatch(interceptevents, NULL); - interceptkeysym = 0; + SDL_DelEventWatch(interceptevents, NULL); + interceptkeysym = 0; } bool interceptkey(int sym) { - if(!interceptkeysym) - { - interceptkeysym = sym; - SDL_FilterEvents(interceptevents, NULL); - if(interceptkeysym < 0) - { - interceptkeysym = 0; - return true; - } - SDL_AddEventWatch(interceptevents, NULL); - } - else if(abs(interceptkeysym) != sym) interceptkeysym = sym; - SDL_PumpEvents(); - if(interceptkeysym < 0) - { - clearinterceptkey(); - interceptkeysym = sym; - SDL_FilterEvents(interceptevents, NULL); - interceptkeysym = 0; - return true; - } - return false; + if(!interceptkeysym) + { + interceptkeysym = sym; + SDL_FilterEvents(interceptevents, NULL); + if(interceptkeysym < 0) + { + interceptkeysym = 0; + return true; + } + SDL_AddEventWatch(interceptevents, NULL); + } + else if(abs(interceptkeysym) != sym) interceptkeysym = sym; + SDL_PumpEvents(); + if(interceptkeysym < 0) + { + clearinterceptkey(); + interceptkeysym = sym; + SDL_FilterEvents(interceptevents, NULL); + interceptkeysym = 0; + return true; + } + return false; } static void ignoremousemotion() { - SDL_PumpEvents(); - SDL_FlushEvent(SDL_MOUSEMOTION); + SDL_PumpEvents(); + SDL_FlushEvent(SDL_MOUSEMOTION); } static void resetmousemotion() { - if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) - { - SDL_WarpMouseInWindow(screen, screenw / 2, screenh / 2); - } + if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) + { + SDL_WarpMouseInWindow(screen, screenw / 2, screenh / 2); + } } static void checkmousemotion(int &dx, int &dy) { - while(pumpevents(events)) - { - SDL_Event &event = events.removing(); - if(event.type != SDL_MOUSEMOTION) return; - dx += event.motion.xrel; - dy += event.motion.yrel; - events.remove(); - } + while(pumpevents(events)) + { + SDL_Event &event = events.removing(); + if(event.type != SDL_MOUSEMOTION) return; + dx += event.motion.xrel; + dy += event.motion.yrel; + events.remove(); + } } void checkinput() { - if(interceptkeysym) clearinterceptkey(); - //int lasttype = 0, lastbut = 0; - bool mousemoved = false; - int focused = 0; - while(pumpevents(events)) - { - SDL_Event &event = events.remove(); - - if(focused && event.type!=SDL_WINDOWEVENT) { if(grabinput != (focused>0)) inputgrab(grabinput = focused>0, shouldgrab); focused = 0; } - - switch(event.type) - { - case SDL_QUIT: - quit(); - return; - - case SDL_TEXTINPUT: - if(textinputmask && int(event.text.timestamp-textinputtime) >= textinputfilter) - { - uchar buf[SDL_TEXTINPUTEVENT_TEXT_SIZE+1]; - size_t len = decodeutf8(buf, sizeof(buf)-1, (const uchar *)event.text.text, strlen(event.text.text)); - if(len > 0) { buf[len] = '\0'; processtextinput((const char *)buf, len); } - } - break; - - case SDL_KEYDOWN: - case SDL_KEYUP: - if(keyrepeatmask || !event.key.repeat) - processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED, event.key.keysym.mod | SDL_GetModState()); - break; - - case SDL_WINDOWEVENT: - switch(event.window.event) - { - case SDL_WINDOWEVENT_CLOSE: - quit(); - break; - - case SDL_WINDOWEVENT_FOCUS_GAINED: - shouldgrab = true; - break; - case SDL_WINDOWEVENT_ENTER: - shouldgrab = false; - focused = 1; - break; - - case SDL_WINDOWEVENT_LEAVE: - case SDL_WINDOWEVENT_FOCUS_LOST: - shouldgrab = false; - focused = -1; - break; - - case SDL_WINDOWEVENT_MINIMIZED: - minimized = true; - break; - - case SDL_WINDOWEVENT_MAXIMIZED: - case SDL_WINDOWEVENT_RESTORED: - minimized = false; - break; - - case SDL_WINDOWEVENT_RESIZED: - break; - - case SDL_WINDOWEVENT_SIZE_CHANGED: - { - SDL_GetWindowSize(screen, &screenw, &screenh); - if(!fullscreendesktop || !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) - { - scr_w = clamp(screenw, SCR_MINW, SCR_MAXW); - scr_h = clamp(screenh, SCR_MINH, SCR_MAXH); - } - gl_resize(); - break; - } - } - break; - - case SDL_MOUSEMOTION: - if(grabinput) - { - int dx = event.motion.xrel, dy = event.motion.yrel; - checkmousemotion(dx, dy); - if(!g3d_movecursor(dx, dy)) mousemove(dx, dy); - mousemoved = true; - } - else if(shouldgrab) inputgrab(grabinput = true); - break; - - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - //if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it - switch(event.button.button) - { - case SDL_BUTTON_LEFT: processkey(-1, event.button.state==SDL_PRESSED); break; - case SDL_BUTTON_MIDDLE: processkey(-2, event.button.state==SDL_PRESSED); break; - case SDL_BUTTON_RIGHT: processkey(-3, event.button.state==SDL_PRESSED); break; - case SDL_BUTTON_X1: processkey(-6, event.button.state==SDL_PRESSED); break; - case SDL_BUTTON_X2: processkey(-7, event.button.state==SDL_PRESSED); break; - } - //lasttype = event.type; - //lastbut = event.button.button; - break; - - case SDL_MOUSEWHEEL: - if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); } - else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); } - break; - } - } - if(focused) { if(grabinput != (focused>0)) inputgrab(grabinput = focused>0, shouldgrab); focused = 0; } - if(mousemoved) resetmousemotion(); + if(interceptkeysym) clearinterceptkey(); + //int lasttype = 0, lastbut = 0; + bool mousemoved = false; + int focused = 0; + while(pumpevents(events)) + { + SDL_Event &event = events.remove(); + + if(focused && event.type!=SDL_WINDOWEVENT) { if(grabinput != (focused>0)) inputgrab(grabinput = focused>0, shouldgrab); focused = 0; } + + switch(event.type) + { + case SDL_QUIT: + quit(); + return; + + case SDL_TEXTINPUT: + if(textinputmask && int(event.text.timestamp-textinputtime) >= textinputfilter) + { + uchar buf[SDL_TEXTINPUTEVENT_TEXT_SIZE+1]; + size_t len = decodeutf8(buf, sizeof(buf)-1, (const uchar *)event.text.text, strlen(event.text.text)); + if(len > 0) { buf[len] = '\0'; processtextinput((const char *)buf, len); } + } + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + if(keyrepeatmask || !event.key.repeat) + processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED, event.key.keysym.mod | SDL_GetModState()); + break; + + case SDL_WINDOWEVENT: + switch(event.window.event) + { + case SDL_WINDOWEVENT_CLOSE: + quit(); + break; + + case SDL_WINDOWEVENT_FOCUS_GAINED: + shouldgrab = true; + break; + case SDL_WINDOWEVENT_ENTER: + shouldgrab = false; + focused = 1; + break; + + case SDL_WINDOWEVENT_LEAVE: + case SDL_WINDOWEVENT_FOCUS_LOST: + shouldgrab = false; + focused = -1; + break; + + case SDL_WINDOWEVENT_MINIMIZED: + minimized = true; + break; + + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + minimized = false; + break; + + case SDL_WINDOWEVENT_RESIZED: + break; + + case SDL_WINDOWEVENT_SIZE_CHANGED: + { + SDL_GetWindowSize(screen, &screenw, &screenh); + if(!fullscreendesktop || !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN)) + { + scr_w = clamp(screenw, SCR_MINW, SCR_MAXW); + scr_h = clamp(screenh, SCR_MINH, SCR_MAXH); + } + gl_resize(); + break; + } + } + break; + + case SDL_MOUSEMOTION: + if(grabinput) + { + int dx = event.motion.xrel, dy = event.motion.yrel; + checkmousemotion(dx, dy); + if(!g3d_movecursor(dx, dy)) mousemove(dx, dy); + mousemoved = true; + } + else if(shouldgrab) inputgrab(grabinput = true); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + //if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it + switch(event.button.button) + { + case SDL_BUTTON_LEFT: processkey(-1, event.button.state==SDL_PRESSED); break; + case SDL_BUTTON_MIDDLE: processkey(-2, event.button.state==SDL_PRESSED); break; + case SDL_BUTTON_RIGHT: processkey(-3, event.button.state==SDL_PRESSED); break; + case SDL_BUTTON_X1: processkey(-6, event.button.state==SDL_PRESSED); break; + case SDL_BUTTON_X2: processkey(-7, event.button.state==SDL_PRESSED); break; + } + //lasttype = event.type; + //lastbut = event.button.button; + break; + + case SDL_MOUSEWHEEL: + if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); } + else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); } + break; + } + } + if(focused) { if(grabinput != (focused>0)) inputgrab(grabinput = focused>0, shouldgrab); focused = 0; } + if(mousemoved) resetmousemotion(); } void swapbuffers(bool overlay) { - gle::disable(); - SDL_GL_SwapWindow(screen); + gle::disable(); + SDL_GL_SwapWindow(screen); } VAR(menufps, 0, 60, 1000); @@ -1013,68 +992,26 @@ VARP(maxfps, 0, 200, 1000); void limitfps(int &millis, int curmillis) { - int limit = (mainmenu || minimized) && menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps; - if(!limit) return; - static int fpserror = 0; - int delay = 1000/limit - (millis-curmillis); - if(delay < 0) fpserror = 0; - else - { - fpserror += 1000%limit; - if(fpserror >= limit) - { - ++delay; - fpserror -= limit; - } - if(delay > 0) - { - SDL_Delay(delay); - millis += delay; - } - } -} - -#if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) -void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep) -{ - if(!ep) fatal("unknown type"); - EXCEPTION_RECORD *er = ep->ExceptionRecord; - CONTEXT *context = ep->ContextRecord; - char out[512]; - formatstring(out, "Cube 2: Sauerbraten Win32 Exception: 0x%x [0x%x]\n\n", er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1); - SymInitialize(GetCurrentProcess(), NULL, TRUE); -#ifdef _AMD64_ - STACKFRAME64 sf = {{context->Rip, 0, AddrModeFlat}, {}, {context->Rbp, 0, AddrModeFlat}, {context->Rsp, 0, AddrModeFlat}, 0}; - while(::StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL)) + int limit = (mainmenu || minimized) && menufps ? (maxfps ? min(maxfps, menufps) : menufps) : maxfps; + if(!limit) return; + static int fpserror = 0; + int delay = 1000/limit - (millis-curmillis); + if(delay < 0) fpserror = 0; + else { - union { IMAGEHLP_SYMBOL64 sym; char symext[sizeof(IMAGEHLP_SYMBOL64) + sizeof(string)]; }; - sym.SizeOfStruct = sizeof(sym); - sym.MaxNameLength = sizeof(symext) - sizeof(sym); - IMAGEHLP_LINE64 line; - line.SizeOfStruct = sizeof(line); - DWORD64 symoff; - DWORD lineoff; - if(SymGetSymFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr64(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line)) -#else - STACKFRAME sf = {{context->Eip, 0, AddrModeFlat}, {}, {context->Ebp, 0, AddrModeFlat}, {context->Esp, 0, AddrModeFlat}, 0}; - while(::StackWalk(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(), &sf, context, NULL, ::SymFunctionTableAccess, ::SymGetModuleBase, NULL)) - { - union { IMAGEHLP_SYMBOL sym; char symext[sizeof(IMAGEHLP_SYMBOL) + sizeof(string)]; }; - sym.SizeOfStruct = sizeof(sym); - sym.MaxNameLength = sizeof(symext) - sizeof(sym); - IMAGEHLP_LINE line; - line.SizeOfStruct = sizeof(line); - DWORD symoff, lineoff; - if(SymGetSymFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &symoff, &sym) && SymGetLineFromAddr(GetCurrentProcess(), sf.AddrPC.Offset, &lineoff, &line)) -#endif - { - char *del = strrchr(line.FileName, '\\'); - concformatstring(out, "%s - %s [%d]\n", sym.Name, del ? del + 1 : line.FileName, line.LineNumber); - } - } - fatal(out); + fpserror += 1000%limit; + if(fpserror >= limit) + { + ++delay; + fpserror -= limit; + } + if(delay > 0) + { + SDL_Delay(delay); + millis += delay; + } + } } -#endif #define MAXFPSHISTORY 60 @@ -1082,38 +1019,38 @@ int fpspos = 0, fpshistory[MAXFPSHISTORY]; void resetfpshistory() { - loopi(MAXFPSHISTORY) fpshistory[i] = 1; - fpspos = 0; + loopi(MAXFPSHISTORY) fpshistory[i] = 1; + fpspos = 0; } void updatefpshistory(int millis) { - fpshistory[fpspos++] = max(1, min(1000, millis)); - if(fpspos>=MAXFPSHISTORY) fpspos = 0; + fpshistory[fpspos++] = max(1, min(1000, millis)); + if(fpspos>=MAXFPSHISTORY) fpspos = 0; } void getfps(int &fps, int &bestdiff, int &worstdiff) { - int total = fpshistory[MAXFPSHISTORY-1], best = total, worst = total; - loopi(MAXFPSHISTORY-1) - { - int millis = fpshistory[i]; - total += millis; - if(millis < best) best = millis; - if(millis > worst) worst = millis; - } - - fps = (1000*MAXFPSHISTORY)/total; - bestdiff = 1000/best-fps; - worstdiff = fps-1000/worst; + int total = fpshistory[MAXFPSHISTORY-1], best = total, worst = total; + loopi(MAXFPSHISTORY-1) + { + int millis = fpshistory[i]; + total += millis; + if(millis < best) best = millis; + if(millis > worst) worst = millis; + } + + fps = (1000*MAXFPSHISTORY)/total; + bestdiff = 1000/best-fps; + worstdiff = fps-1000/worst; } void getfps_(int *raw) { - int fps, bestdiff, worstdiff; - if(*raw) fps = 1000/fpshistory[(fpspos+MAXFPSHISTORY-1)%MAXFPSHISTORY]; - else getfps(fps, bestdiff, worstdiff); - intret(fps); + int fps, bestdiff, worstdiff; + if(*raw) fps = 1000/fpshistory[(fpspos+MAXFPSHISTORY-1)%MAXFPSHISTORY]; + else getfps(fps, bestdiff, worstdiff); + intret(fps); } COMMANDN(getfps, getfps_, "i"); @@ -1122,8 +1059,8 @@ bool inbetweenframes = false, renderedframe = true; static bool findarg(int argc, char **argv, const char *str) { - for(int i = 1; i0, dedicated>1); // never returns if dedicated - ASSERT(dedicated <= 1); - game::initclient(); - - logoutf("init: video"); - SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0"); - #if !defined(WIN32) && !defined(__APPLE__) - SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); - #endif - setupscreen(); - SDL_ShowCursor(SDL_FALSE); - SDL_StopTextInput(); // workaround for spurious text-input events getting sent on first text input toggle? - - logoutf("init: gl"); - gl_checkextensions(); - gl_init(); - notexture = textureload("packages/textures/texture_error.png"); - if(!notexture) fatal("could not find core textures"); - - logoutf("init: console"); - if(!execfile("data/stdlib.cfg", false)) fatal("cannot find data files (you are running from the wrong folder, try .bat file in the main folder)"); // this is the first file we load. - if(!execfile("packages/fonts/default.cfg", false)) fatal("cannot find font definitions"); - if(!setfont("default")) fatal("no default font specified"); - - inbetweenframes = true; - renderbackground("initializing..."); - - logoutf("init: world"); - camera1 = player = game::iterdynents(0); - emptymap(0, true, NULL, false); - - logoutf("init: sound"); - initsound(); - - logoutf("init: cfg"); - initing = INIT_LOAD; - execfile("data/keymap.cfg"); - execfile("data/stdedit.cfg"); - execfile("data/sounds.cfg"); - execfile("data/menus.cfg"); - execfile("data/heightmap.cfg"); - execfile("data/brushes.cfg"); - execfile("data/game.cfg"); - execfile("data/custom_maps_menu.cfg"); - if(game::savedservers()) execfile(game::savedservers(), false); - - identflags |= IDF_PERSIST; - - if(!execfile(game::savedconfig(), false)) - { - execfile(game::defaultconfig()); - writecfg(game::restoreconfig()); - } - execfile(game::autoexec(), false); - - identflags &= ~IDF_PERSIST; - - initing = INIT_GAME; - game::loadconfigs(); - - initing = NOT_INITING; - - logoutf("init: render"); - restoregamma(); - restorevsync(); - loadshaders(); - initparticles(); - initdecals(); - - identflags |= IDF_PERSIST; - - logoutf("init: mainloop"); - - if(execfile("once.cfg", false)) remove(findfile("once.cfg", "rb")); - - if(load) - { - logoutf("init: localconnect"); - //localconnect(); - game::changemap(load); - } - - if(initscript) execute(initscript); - - resetfpshistory(); - - inputgrab(grabinput = true); - ignoremousemotion(); - - for(;;) - { - static int frames = 0; - int millis = getclockmillis(); - limitfps(millis, totalmillis); - elapsedtime = millis - totalmillis; - static int timeerr = 0; - int scaledtime = game::scaletime(elapsedtime) + timeerr; - curtime = scaledtime/100; - timeerr = scaledtime%100; - if(!multiplayer(false) && curtime>200) curtime = 200; - if(game::ispaused()) curtime = 0; - lastmillis += curtime; - totalmillis = millis; - updatetime(); + } + + logoutf("init: net"); + if(enet_initialize()<0) fatal("Unable to initialise network module"); + atexit(enet_deinitialize); + enet_time_set(0); + + logoutf("init: game"); + game::parseoptions(gameargs); + initserver(dedicated>0, dedicated>1); // never returns if dedicated + ASSERT(dedicated <= 1); + game::initclient(); + + logoutf("init: video"); + SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "0"); + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); + setupscreen(); + SDL_ShowCursor(SDL_FALSE); + SDL_StopTextInput(); // workaround for spurious text-input events getting sent on first text input toggle? + + logoutf("init: gl"); + gl_checkextensions(); + gl_init(); + notexture = textureload("packages/textures/texture_error.png"); + if(!notexture) fatal("could not find core textures"); + + logoutf("init: console"); + if(!execfile("data/stdlib.cfg", false)) fatal("cannot find data files (you are running from the wrong folder, try .bat file in the main folder)"); // this is the first file we load. + if(!execfile("packages/fonts/default.cfg", false)) fatal("cannot find font definitions"); + if(!setfont("default")) fatal("no default font specified"); + + inbetweenframes = true; + renderbackground("initializing..."); + + logoutf("init: world"); + camera1 = player = game::iterdynents(0); + emptymap(0, true, NULL, false); + + logoutf("init: sound"); + initsound(); + + logoutf("init: cfg"); + initing = INIT_LOAD; + execfile("data/keymap.cfg"); + execfile("data/stdedit.cfg"); + execfile("data/sounds.cfg"); + execfile("data/menus.cfg"); + execfile("data/heightmap.cfg"); + execfile("data/brushes.cfg"); + execfile("data/game.cfg"); + execfile("data/custom_maps_menu.cfg"); + if(game::savedservers()) execfile(game::savedservers(), false); + + identflags |= IDF_PERSIST; + + if(!execfile(game::savedconfig(), false)) + { + execfile(game::defaultconfig()); + writecfg(game::restoreconfig()); + } + execfile(game::autoexec(), false); + + identflags &= ~IDF_PERSIST; + + initing = INIT_GAME; + game::loadconfigs(); + + initing = NOT_INITING; + + logoutf("init: render"); + restoregamma(); + restorevsync(); + loadshaders(); + initparticles(); + initdecals(); + + identflags |= IDF_PERSIST; - checkinput(); - menuprocess(); - tryedit(); + logoutf("init: mainloop"); + + if(execfile("once.cfg", false)) remove(findfile("once.cfg", "rb")); + + if(load) + { + logoutf("init: localconnect"); + //localconnect(); + game::changemap(load); + } + + if(initscript) execute(initscript); + + resetfpshistory(); + + inputgrab(grabinput = true); + ignoremousemotion(); + + for(;;) + { + static int frames = 0; + int millis = getclockmillis(); + limitfps(millis, totalmillis); + elapsedtime = millis - totalmillis; + static int timeerr = 0; + int scaledtime = game::scaletime(elapsedtime) + timeerr; + curtime = scaledtime/100; + timeerr = scaledtime%100; + if(!multiplayer(false) && curtime>200) curtime = 200; + if(game::ispaused()) curtime = 0; + lastmillis += curtime; + totalmillis = millis; + updatetime(); - if(lastmillis) game::updateworld(); + checkinput(); + menuprocess(); + tryedit(); - checksleep(lastmillis); + if(lastmillis) game::updateworld(); - serverslice(false, 0); + checksleep(lastmillis); - if(frames) updatefpshistory(elapsedtime); - frames++; + serverslice(false, 0); - // miscellaneous general game effects - recomputecamera(); - updateparticles(); - updatesounds(); + if(frames) updatefpshistory(elapsedtime); + frames++; - if(minimized) continue; + // miscellaneous general game effects + recomputecamera(); + updateparticles(); + updatesounds(); - inbetweenframes = false; - if(mainmenu) gl_drawmainmenu(); - else gl_drawframe(); - swapbuffers(); - renderedframe = inbetweenframes = true; - } + if(minimized) continue; - ASSERT(0); - return EXIT_FAILURE; + inbetweenframes = false; + if(mainmenu) gl_drawmainmenu(); + else gl_drawframe(); + swapbuffers(); + renderedframe = inbetweenframes = true; + } - #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) - } __except(stackdumper(0, GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) { return 0; } - #endif + ASSERT(0); + return EXIT_FAILURE; } diff --git a/src/engine/master.cpp b/src/engine/master.cpp index 6eb6fe6..2bf1dba 100644 --- a/src/engine/master.cpp +++ b/src/engine/master.cpp @@ -24,24 +24,24 @@ FILE *logfile = NULL; struct userinfo { - char *name; - void *pubkey; + char *name; + void *pubkey; }; hashnameset users; void adduser(char *name, char *pubkey) { - name = newstring(name); - userinfo &u = users[name]; - u.name = name; - u.pubkey = parsepubkey(pubkey); + name = newstring(name); + userinfo &u = users[name]; + u.name = name; + u.pubkey = parsepubkey(pubkey); } COMMAND(adduser, "ss"); void clearusers() { - enumerate(users, userinfo, u, { delete[] u.name; freepubkey(u.pubkey); }); - users.clear(); + enumerate(users, userinfo, u, { delete[] u.name; freepubkey(u.pubkey); }); + users.clear(); } COMMAND(clearusers, ""); @@ -49,17 +49,17 @@ vector bans, servbans, gbans; void clearbans() { - bans.shrink(0); - servbans.shrink(0); - gbans.shrink(0); + bans.shrink(0); + servbans.shrink(0); + gbans.shrink(0); } COMMAND(clearbans, ""); void addban(vector &bans, const char *name) { - ipmask ban; - ban.parse(name); - bans.add(ban); + ipmask ban; + ban.parse(name); + bans.add(ban); } ICOMMAND(ban, "s", (char *name), addban(bans, name)); ICOMMAND(servban, "s", (char *name), addban(servbans, name)); @@ -67,73 +67,73 @@ ICOMMAND(gban, "s", (char *name), addban(gbans, name)); bool checkban(vector &bans, enet_uint32 host) { - loopv(bans) if(bans[i].check(host)) return true; - return false; + loopv(bans) if(bans[i].check(host)) return true; + return false; } struct authreq { - enet_uint32 reqtime; - uint id; - void *answer; + enet_uint32 reqtime; + uint id; + void *answer; }; struct gameserver { - ENetAddress address; - string ip; - int port, numpings; - enet_uint32 lastping, lastpong; + ENetAddress address; + string ip; + int port, numpings; + enet_uint32 lastping, lastpong; }; vector gameservers; struct messagebuf { - vector &owner; - vector buf; - int refs; + vector &owner; + vector buf; + int refs; - messagebuf(vector &owner) : owner(owner), refs(0) {} + messagebuf(vector &owner) : owner(owner), refs(0) {} - const char *getbuf() { return buf.getbuf(); } - int length() { return buf.length(); } - void purge(); + const char *getbuf() { return buf.getbuf(); } + int length() { return buf.length(); } + void purge(); - bool equals(const messagebuf &m) const - { - return buf.length() == m.buf.length() && !memcmp(buf.getbuf(), m.buf.getbuf(), buf.length()); - } + bool equals(const messagebuf &m) const + { + return buf.length() == m.buf.length() && !memcmp(buf.getbuf(), m.buf.getbuf(), buf.length()); + } - bool endswith(const messagebuf &m) const - { - return buf.length() >= m.buf.length() && !memcmp(&buf[buf.length() - m.buf.length()], m.buf.getbuf(), m.buf.length()); - } + bool endswith(const messagebuf &m) const + { + return buf.length() >= m.buf.length() && !memcmp(&buf[buf.length() - m.buf.length()], m.buf.getbuf(), m.buf.length()); + } - void concat(const messagebuf &m) - { - if(buf.length() && buf.last() == '\0') buf.pop(); - buf.put(m.buf.getbuf(), m.buf.length()); - } + void concat(const messagebuf &m) + { + if(buf.length() && buf.last() == '\0') buf.pop(); + buf.put(m.buf.getbuf(), m.buf.length()); + } }; vector gameserverlists, gbanlists; bool updateserverlist = true; struct client { - ENetAddress address; - ENetSocket socket; - char input[INPUT_LIMIT]; - messagebuf *message; - vector output; - int inputpos, outputpos; - enet_uint32 connecttime, lastinput; - int servport; - enet_uint32 lastauth; - vector authreqs; - bool shouldpurge; - bool registeredserver; - - client() : message(NULL), inputpos(0), outputpos(0), servport(-1), lastauth(0), shouldpurge(false), registeredserver(false) {} + ENetAddress address; + ENetSocket socket; + char input[INPUT_LIMIT]; + messagebuf *message; + vector output; + int inputpos, outputpos; + enet_uint32 connecttime, lastinput; + int servport; + enet_uint32 lastauth; + vector authreqs; + bool shouldpurge; + bool registeredserver; + + client() : message(NULL), inputpos(0), outputpos(0), servport(-1), lastauth(0), shouldpurge(false), registeredserver(false) {} }; vector clients; @@ -144,567 +144,567 @@ enet_uint32 servtime = 0; void fatal(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - vfprintf(logfile, fmt, args); - fputc('\n', logfile); - va_end(args); - exit(EXIT_FAILURE); + va_list args; + va_start(args, fmt); + vfprintf(logfile, fmt, args); + fputc('\n', logfile); + va_end(args); + exit(EXIT_FAILURE); } void conoutfv(int type, const char *fmt, va_list args) { - vfprintf(logfile, fmt, args); - fputc('\n', logfile); + vfprintf(logfile, fmt, args); + fputc('\n', logfile); } void purgeclient(int n) { - client &c = *clients[n]; - if(c.message) c.message->purge(); - enet_socket_destroy(c.socket); - delete clients[n]; - clients.remove(n); + client &c = *clients[n]; + if(c.message) c.message->purge(); + enet_socket_destroy(c.socket); + delete clients[n]; + clients.remove(n); } void output(client &c, const char *msg, int len = 0) { - if(!len) len = strlen(msg); - c.output.put(msg, len); + if(!len) len = strlen(msg); + c.output.put(msg, len); } void outputf(client &c, const char *fmt, ...) { - string msg; - va_list args; - va_start(args, fmt); - vformatstring(msg, fmt, args); - va_end(args); + string msg; + va_list args; + va_start(args, fmt); + vformatstring(msg, fmt, args); + va_end(args); - output(c, msg); + output(c, msg); } ENetSocket pingsocket = ENET_SOCKET_NULL; bool setuppingsocket(ENetAddress *address) { - if(pingsocket != ENET_SOCKET_NULL) return true; - pingsocket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); - if(pingsocket == ENET_SOCKET_NULL) return false; - if(address && enet_socket_bind(pingsocket, address) < 0) return false; - enet_socket_set_option(pingsocket, ENET_SOCKOPT_NONBLOCK, 1); - return true; + if(pingsocket != ENET_SOCKET_NULL) return true; + pingsocket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + if(pingsocket == ENET_SOCKET_NULL) return false; + if(address && enet_socket_bind(pingsocket, address) < 0) return false; + enet_socket_set_option(pingsocket, ENET_SOCKOPT_NONBLOCK, 1); + return true; } void setupserver(int port, const char *ip = NULL) { - ENetAddress address; - address.host = ENET_HOST_ANY; - address.port = port; - - if(ip) - { - if(enet_address_set_host(&address, ip)<0) - fatal("failed to resolve server address: %s", ip); - } - serversocket = enet_socket_create(ENET_SOCKET_TYPE_STREAM); - if(serversocket==ENET_SOCKET_NULL || - enet_socket_set_option(serversocket, ENET_SOCKOPT_REUSEADDR, 1) < 0 || - enet_socket_bind(serversocket, &address) < 0 || - enet_socket_listen(serversocket, -1) < 0) - fatal("failed to create server socket"); - if(enet_socket_set_option(serversocket, ENET_SOCKOPT_NONBLOCK, 1)<0) - fatal("failed to make server socket non-blocking"); - if(!setuppingsocket(&address)) - fatal("failed to create ping socket"); - - enet_time_set(0); - - starttime = time(NULL); - char *ct = ctime(&starttime); - if(strchr(ct, '\n')) *strchr(ct, '\n') = '\0'; - conoutf("*** Starting master server on %s %d at %s ***", ip ? ip : "localhost", port, ct); + ENetAddress address; + address.host = ENET_HOST_ANY; + address.port = port; + + if(ip) + { + if(enet_address_set_host(&address, ip)<0) + fatal("failed to resolve server address: %s", ip); + } + serversocket = enet_socket_create(ENET_SOCKET_TYPE_STREAM); + if(serversocket==ENET_SOCKET_NULL || + enet_socket_set_option(serversocket, ENET_SOCKOPT_REUSEADDR, 1) < 0 || + enet_socket_bind(serversocket, &address) < 0 || + enet_socket_listen(serversocket, -1) < 0) + fatal("failed to create server socket"); + if(enet_socket_set_option(serversocket, ENET_SOCKOPT_NONBLOCK, 1)<0) + fatal("failed to make server socket non-blocking"); + if(!setuppingsocket(&address)) + fatal("failed to create ping socket"); + + enet_time_set(0); + + starttime = time(NULL); + char *ct = ctime(&starttime); + if(strchr(ct, '\n')) *strchr(ct, '\n') = '\0'; + conoutf("*** Starting master server on %s %d at %s ***", ip ? ip : "localhost", port, ct); } void genserverlist() { - if(!updateserverlist) return; - while(gameserverlists.length() && gameserverlists.last()->refs<=0) - delete gameserverlists.pop(); - messagebuf *l = new messagebuf(gameserverlists); - loopv(gameservers) - { - gameserver &s = *gameservers[i]; - if(!s.lastpong) continue; - defformatstring(cmd, "addserver %s %d\n", s.ip, s.port); - l->buf.put(cmd, strlen(cmd)); - } - l->buf.add('\0'); - gameserverlists.add(l); - updateserverlist = false; + if(!updateserverlist) return; + while(gameserverlists.length() && gameserverlists.last()->refs<=0) + delete gameserverlists.pop(); + messagebuf *l = new messagebuf(gameserverlists); + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(!s.lastpong) continue; + defformatstring(cmd, "addserver %s %d\n", s.ip, s.port); + l->buf.put(cmd, strlen(cmd)); + } + l->buf.add('\0'); + gameserverlists.add(l); + updateserverlist = false; } void gengbanlist() { - messagebuf *l = new messagebuf(gbanlists); - const char *header = "cleargbans\n"; - l->buf.put(header, strlen(header)); - string cmd = "addgban "; - int cmdlen = strlen(cmd); - loopv(gbans) - { - ipmask &b = gbans[i]; - l->buf.put(cmd, cmdlen + b.print(&cmd[cmdlen])); - l->buf.add('\n'); - } - if(gbanlists.length() && gbanlists.last()->equals(*l)) - { - delete l; - return; - } - while(gbanlists.length() && gbanlists.last()->refs<=0) - delete gbanlists.pop(); - loopv(gbanlists) - { - messagebuf *m = gbanlists[i]; - if(m->refs > 0 && !m->endswith(*l)) m->concat(*l); - } - gbanlists.add(l); - loopv(clients) - { - client &c = *clients[i]; - if(c.servport >= 0 && !c.message) - { - c.message = l; - c.message->refs++; - } - } + messagebuf *l = new messagebuf(gbanlists); + const char *header = "cleargbans\n"; + l->buf.put(header, strlen(header)); + string cmd = "addgban "; + int cmdlen = strlen(cmd); + loopv(gbans) + { + ipmask &b = gbans[i]; + l->buf.put(cmd, cmdlen + b.print(&cmd[cmdlen])); + l->buf.add('\n'); + } + if(gbanlists.length() && gbanlists.last()->equals(*l)) + { + delete l; + return; + } + while(gbanlists.length() && gbanlists.last()->refs<=0) + delete gbanlists.pop(); + loopv(gbanlists) + { + messagebuf *m = gbanlists[i]; + if(m->refs > 0 && !m->endswith(*l)) m->concat(*l); + } + gbanlists.add(l); + loopv(clients) + { + client &c = *clients[i]; + if(c.servport >= 0 && !c.message) + { + c.message = l; + c.message->refs++; + } + } } void addgameserver(client &c) { - if(gameservers.length() >= SERVER_LIMIT) return; - int dups = 0; - loopv(gameservers) - { - gameserver &s = *gameservers[i]; - if(s.address.host != c.address.host) continue; - ++dups; - if(s.port == c.servport) - { - s.lastping = 0; - s.numpings = 0; - return; - } - } - if(dups >= SERVER_DUP_LIMIT) - { - outputf(c, "failreg too many servers on ip\n"); - return; - } - string hostname; - if(enet_address_get_host_ip(&c.address, hostname, sizeof(hostname)) < 0) - { - outputf(c, "failreg failed resolving ip\n"); - return; - } - gameserver &s = *gameservers.add(new gameserver); - s.address.host = c.address.host; - s.address.port = c.servport+1; - copystring(s.ip, hostname); - s.port = c.servport; - s.numpings = 0; - s.lastping = s.lastpong = 0; + if(gameservers.length() >= SERVER_LIMIT) return; + int dups = 0; + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(s.address.host != c.address.host) continue; + ++dups; + if(s.port == c.servport) + { + s.lastping = 0; + s.numpings = 0; + return; + } + } + if(dups >= SERVER_DUP_LIMIT) + { + outputf(c, "failreg too many servers on ip\n"); + return; + } + string hostname; + if(enet_address_get_host_ip(&c.address, hostname, sizeof(hostname)) < 0) + { + outputf(c, "failreg failed resolving ip\n"); + return; + } + gameserver &s = *gameservers.add(new gameserver); + s.address.host = c.address.host; + s.address.port = c.servport+1; + copystring(s.ip, hostname); + s.port = c.servport; + s.numpings = 0; + s.lastping = s.lastpong = 0; } client *findclient(gameserver &s) { - loopv(clients) - { - client &c = *clients[i]; - if(s.address.host == c.address.host && s.port == c.servport) - return &c; - } - return NULL; + loopv(clients) + { + client &c = *clients[i]; + if(s.address.host == c.address.host && s.port == c.servport) + return &c; + } + return NULL; } void servermessage(gameserver &s, const char *msg) { - client *c = findclient(s); - if(c) outputf(*c, msg); + client *c = findclient(s); + if(c) outputf(*c, msg); } void checkserverpongs() { - ENetBuffer buf; - ENetAddress addr; - static uchar pong[MAXTRANS]; - for(;;) - { - buf.data = pong; - buf.dataLength = sizeof(pong); - int len = enet_socket_receive(pingsocket, &addr, &buf, 1); - if(len <= 0) break; - loopv(gameservers) - { - gameserver &s = *gameservers[i]; - if(s.address.host == addr.host && s.address.port == addr.port) - { - if(s.lastping && (!s.lastpong || ENET_TIME_GREATER(s.lastping, s.lastpong))) - { - client *c = findclient(s); - if(c) - { - c->registeredserver = true; - outputf(*c, "succreg\n"); - if(!c->message && gbanlists.length()) - { - c->message = gbanlists.last(); - c->message->refs++; - } - } - } - if(!s.lastpong) updateserverlist = true; - s.lastpong = servtime ? servtime : 1; - break; - } - } - } + ENetBuffer buf; + ENetAddress addr; + static uchar pong[MAXTRANS]; + for(;;) + { + buf.data = pong; + buf.dataLength = sizeof(pong); + int len = enet_socket_receive(pingsocket, &addr, &buf, 1); + if(len <= 0) break; + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(s.address.host == addr.host && s.address.port == addr.port) + { + if(s.lastping && (!s.lastpong || ENET_TIME_GREATER(s.lastping, s.lastpong))) + { + client *c = findclient(s); + if(c) + { + c->registeredserver = true; + outputf(*c, "succreg\n"); + if(!c->message && gbanlists.length()) + { + c->message = gbanlists.last(); + c->message->refs++; + } + } + } + if(!s.lastpong) updateserverlist = true; + s.lastpong = servtime ? servtime : 1; + break; + } + } + } } void bangameservers() { - loopvrev(gameservers) if(checkban(servbans, gameservers[i]->address.host)) - { - delete gameservers.remove(i); - updateserverlist = true; - } + loopvrev(gameservers) if(checkban(servbans, gameservers[i]->address.host)) + { + delete gameservers.remove(i); + updateserverlist = true; + } } void checkgameservers() { - ENetBuffer buf; - loopv(gameservers) - { - gameserver &s = *gameservers[i]; - if(s.lastping && s.lastpong && ENET_TIME_LESS_EQUAL(s.lastping, s.lastpong)) - { - if(ENET_TIME_DIFFERENCE(servtime, s.lastpong) > KEEPALIVE_TIME) - { - delete gameservers.remove(i--); - updateserverlist = true; - } - } - else if(!s.lastping || ENET_TIME_DIFFERENCE(servtime, s.lastping) > PING_TIME) - { - if(s.numpings >= PING_RETRY) - { - servermessage(s, "failreg failed pinging server\n"); - delete gameservers.remove(i--); - updateserverlist = true; - } - else - { - static const uchar ping[] = { 1 }; - buf.data = (void *)ping; - buf.dataLength = sizeof(ping); - s.numpings++; - s.lastping = servtime ? servtime : 1; - enet_socket_send(pingsocket, &s.address, &buf, 1); - } - } - } + ENetBuffer buf; + loopv(gameservers) + { + gameserver &s = *gameservers[i]; + if(s.lastping && s.lastpong && ENET_TIME_LESS_EQUAL(s.lastping, s.lastpong)) + { + if(ENET_TIME_DIFFERENCE(servtime, s.lastpong) > KEEPALIVE_TIME) + { + delete gameservers.remove(i--); + updateserverlist = true; + } + } + else if(!s.lastping || ENET_TIME_DIFFERENCE(servtime, s.lastping) > PING_TIME) + { + if(s.numpings >= PING_RETRY) + { + servermessage(s, "failreg failed pinging server\n"); + delete gameservers.remove(i--); + updateserverlist = true; + } + else + { + static const uchar ping[] = { 1 }; + buf.data = (void *)ping; + buf.dataLength = sizeof(ping); + s.numpings++; + s.lastping = servtime ? servtime : 1; + enet_socket_send(pingsocket, &s.address, &buf, 1); + } + } + } } void messagebuf::purge() { - refs = max(refs - 1, 0); - if(refs<=0 && owner.last()!=this) - { - owner.removeobj(this); - delete this; - } + refs = max(refs - 1, 0); + if(refs<=0 && owner.last()!=this) + { + owner.removeobj(this); + delete this; + } } void purgeauths(client &c) { - int expired = 0; - loopv(c.authreqs) - { - if(ENET_TIME_DIFFERENCE(servtime, c.authreqs[i].reqtime) >= AUTH_TIME) - { - outputf(c, "failauth %u\n", c.authreqs[i].id); - freechallenge(c.authreqs[i].answer); - expired = i + 1; - } - else break; - } - if(expired > 0) c.authreqs.remove(0, expired); + int expired = 0; + loopv(c.authreqs) + { + if(ENET_TIME_DIFFERENCE(servtime, c.authreqs[i].reqtime) >= AUTH_TIME) + { + outputf(c, "failauth %u\n", c.authreqs[i].id); + freechallenge(c.authreqs[i].answer); + expired = i + 1; + } + else break; + } + if(expired > 0) c.authreqs.remove(0, expired); } void reqauth(client &c, uint id, char *name) { - if(ENET_TIME_DIFFERENCE(servtime, c.lastauth) < AUTH_THROTTLE) - return; - - c.lastauth = servtime; - - purgeauths(c); - - time_t t = time(NULL); - char *ct = ctime(&t); - if(ct) - { - char *newline = strchr(ct, '\n'); - if(newline) *newline = '\0'; - } - string ip; - if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-"); - conoutf("%s: attempting \"%s\" as %u from %s", ct ? ct : "-", name, id, ip); - - userinfo *u = users.access(name); - if(!u) - { - outputf(c, "failauth %u\n", id); - return; - } - - if(c.authreqs.length() >= AUTH_LIMIT) - { - outputf(c, "failauth %u\n", c.authreqs[0].id); - freechallenge(c.authreqs[0].answer); - c.authreqs.remove(0); - } - - authreq &a = c.authreqs.add(); - a.reqtime = servtime; - a.id = id; - uint seed[3] = { uint(starttime), servtime, randomMT() }; - static vector buf; - buf.setsize(0); - a.answer = genchallenge(u->pubkey, seed, sizeof(seed), buf); - - outputf(c, "chalauth %u %s\n", id, buf.getbuf()); + if(ENET_TIME_DIFFERENCE(servtime, c.lastauth) < AUTH_THROTTLE) + return; + + c.lastauth = servtime; + + purgeauths(c); + + time_t t = time(NULL); + char *ct = ctime(&t); + if(ct) + { + char *newline = strchr(ct, '\n'); + if(newline) *newline = '\0'; + } + string ip; + if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-"); + conoutf("%s: attempting \"%s\" as %u from %s", ct ? ct : "-", name, id, ip); + + userinfo *u = users.access(name); + if(!u) + { + outputf(c, "failauth %u\n", id); + return; + } + + if(c.authreqs.length() >= AUTH_LIMIT) + { + outputf(c, "failauth %u\n", c.authreqs[0].id); + freechallenge(c.authreqs[0].answer); + c.authreqs.remove(0); + } + + authreq &a = c.authreqs.add(); + a.reqtime = servtime; + a.id = id; + uint seed[3] = { uint(starttime), servtime, randomMT() }; + static vector buf; + buf.setsize(0); + a.answer = genchallenge(u->pubkey, seed, sizeof(seed), buf); + + outputf(c, "chalauth %u %s\n", id, buf.getbuf()); } void confauth(client &c, uint id, const char *val) { - purgeauths(c); - - loopv(c.authreqs) if(c.authreqs[i].id == id) - { - string ip; - if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-"); - if(checkchallenge(val, c.authreqs[i].answer)) - { - outputf(c, "succauth %u\n", id); - conoutf("succeeded %u from %s", id, ip); - } - else - { - outputf(c, "failauth %u\n", id); - conoutf("failed %u from %s", id, ip); - } - freechallenge(c.authreqs[i].answer); - c.authreqs.remove(i--); - return; - } - outputf(c, "failauth %u\n", id); + purgeauths(c); + + loopv(c.authreqs) if(c.authreqs[i].id == id) + { + string ip; + if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-"); + if(checkchallenge(val, c.authreqs[i].answer)) + { + outputf(c, "succauth %u\n", id); + conoutf("succeeded %u from %s", id, ip); + } + else + { + outputf(c, "failauth %u\n", id); + conoutf("failed %u from %s", id, ip); + } + freechallenge(c.authreqs[i].answer); + c.authreqs.remove(i--); + return; + } + outputf(c, "failauth %u\n", id); } bool checkclientinput(client &c) { - if(c.inputpos<0) return true; - char *end = (char *)memchr(c.input, '\n', c.inputpos); - while(end) - { - *end++ = '\0'; - c.lastinput = servtime; - - int port; - uint id; - string user, val; - if(!strncmp(c.input, "list", 4) && (!c.input[4] || c.input[4] == '\n' || c.input[4] == '\r')) - { - genserverlist(); - if(gameserverlists.empty() || c.message) return false; - c.message = gameserverlists.last(); - c.message->refs++; - c.output.setsize(0); - c.outputpos = 0; - c.shouldpurge = true; - return true; - } - else if(sscanf(c.input, "regserv %d", &port) == 1) - { - if(checkban(servbans, c.address.host)) return false; - if(port < 0 || port > 0xFFFF-1 || (c.servport >= 0 && port != c.servport)) outputf(c, "failreg invalid port\n"); - else - { - c.servport = port; - addgameserver(c); - } - } - else if(sscanf(c.input, "reqauth %u %100s", &id, user) == 2) - { - reqauth(c, id, user); - } - else if(sscanf(c.input, "confauth %u %100s", &id, val) == 2) - { - confauth(c, id, val); - } - c.inputpos = &c.input[c.inputpos] - end; - memmove(c.input, end, c.inputpos); - - end = (char *)memchr(c.input, '\n', c.inputpos); - } - return c.inputpos<(int)sizeof(c.input); + if(c.inputpos<0) return true; + char *end = (char *)memchr(c.input, '\n', c.inputpos); + while(end) + { + *end++ = '\0'; + c.lastinput = servtime; + + int port; + uint id; + string user, val; + if(!strncmp(c.input, "list", 4) && (!c.input[4] || c.input[4] == '\n' || c.input[4] == '\r')) + { + genserverlist(); + if(gameserverlists.empty() || c.message) return false; + c.message = gameserverlists.last(); + c.message->refs++; + c.output.setsize(0); + c.outputpos = 0; + c.shouldpurge = true; + return true; + } + else if(sscanf(c.input, "regserv %d", &port) == 1) + { + if(checkban(servbans, c.address.host)) return false; + if(port < 0 || port > 0xFFFF-1 || (c.servport >= 0 && port != c.servport)) outputf(c, "failreg invalid port\n"); + else + { + c.servport = port; + addgameserver(c); + } + } + else if(sscanf(c.input, "reqauth %u %100s", &id, user) == 2) + { + reqauth(c, id, user); + } + else if(sscanf(c.input, "confauth %u %100s", &id, val) == 2) + { + confauth(c, id, val); + } + c.inputpos = &c.input[c.inputpos] - end; + memmove(c.input, end, c.inputpos); + + end = (char *)memchr(c.input, '\n', c.inputpos); + } + return c.inputpos<(int)sizeof(c.input); } ENetSocketSet readset, writeset; void checkclients() { - ENetSocketSet readset, writeset; - ENetSocket maxsock = max(serversocket, pingsocket); - ENET_SOCKETSET_EMPTY(readset); - ENET_SOCKETSET_EMPTY(writeset); - ENET_SOCKETSET_ADD(readset, serversocket); - ENET_SOCKETSET_ADD(readset, pingsocket); - loopv(clients) - { - client &c = *clients[i]; - if(c.authreqs.length()) purgeauths(c); - if(c.message || c.output.length()) ENET_SOCKETSET_ADD(writeset, c.socket); - else ENET_SOCKETSET_ADD(readset, c.socket); - maxsock = max(maxsock, c.socket); - } - if(enet_socketset_select(maxsock, &readset, &writeset, 1000)<=0) return; - - if(ENET_SOCKETSET_CHECK(readset, pingsocket)) checkserverpongs(); - if(ENET_SOCKETSET_CHECK(readset, serversocket)) - { - ENetAddress address; - ENetSocket clientsocket = enet_socket_accept(serversocket, &address); - if(clients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket); - else if(clientsocket!=ENET_SOCKET_NULL) - { - int dups = 0, oldest = -1; - loopv(clients) if(clients[i]->address.host == address.host) - { - dups++; - if(oldest<0 || clients[i]->connecttime < clients[oldest]->connecttime) oldest = i; - } - if(dups >= DUP_LIMIT) purgeclient(oldest); - - client *c = new client; - c->address = address; - c->socket = clientsocket; - c->connecttime = servtime; - c->lastinput = servtime; - clients.add(c); - } - } - - loopv(clients) - { - client &c = *clients[i]; - if((c.message || c.output.length()) && ENET_SOCKETSET_CHECK(writeset, c.socket)) - { - const char *data = c.output.length() ? c.output.getbuf() : c.message->getbuf(); - int len = c.output.length() ? c.output.length() : c.message->length(); - ENetBuffer buf; - buf.data = (void *)&data[c.outputpos]; - buf.dataLength = len-c.outputpos; - int res = enet_socket_send(c.socket, NULL, &buf, 1); - if(res>=0) - { - c.outputpos += res; - if(c.outputpos>=len) - { - if(c.output.length()) c.output.setsize(0); - else - { - c.message->purge(); - c.message = NULL; - } - c.outputpos = 0; - if(!c.message && c.output.empty() && c.shouldpurge) - { - purgeclient(i--); - continue; - } - } - } - else { purgeclient(i--); continue; } - } - if(ENET_SOCKETSET_CHECK(readset, c.socket)) - { - ENetBuffer buf; - buf.data = &c.input[c.inputpos]; - buf.dataLength = sizeof(c.input) - c.inputpos; - int res = enet_socket_receive(c.socket, NULL, &buf, 1); - if(res>0) - { - c.inputpos += res; - c.input[min(c.inputpos, (int)sizeof(c.input)-1)] = '\0'; - if(!checkclientinput(c)) { purgeclient(i--); continue; } - } - else { purgeclient(i--); continue; } - } - if(c.output.length() > OUTPUT_LIMIT) { purgeclient(i--); continue; } - if(ENET_TIME_DIFFERENCE(servtime, c.lastinput) >= (c.registeredserver ? KEEPALIVE_TIME : CLIENT_TIME)) { purgeclient(i--); continue; } - } + ENetSocketSet readset, writeset; + ENetSocket maxsock = max(serversocket, pingsocket); + ENET_SOCKETSET_EMPTY(readset); + ENET_SOCKETSET_EMPTY(writeset); + ENET_SOCKETSET_ADD(readset, serversocket); + ENET_SOCKETSET_ADD(readset, pingsocket); + loopv(clients) + { + client &c = *clients[i]; + if(c.authreqs.length()) purgeauths(c); + if(c.message || c.output.length()) ENET_SOCKETSET_ADD(writeset, c.socket); + else ENET_SOCKETSET_ADD(readset, c.socket); + maxsock = max(maxsock, c.socket); + } + if(enet_socketset_select(maxsock, &readset, &writeset, 1000)<=0) return; + + if(ENET_SOCKETSET_CHECK(readset, pingsocket)) checkserverpongs(); + if(ENET_SOCKETSET_CHECK(readset, serversocket)) + { + ENetAddress address; + ENetSocket clientsocket = enet_socket_accept(serversocket, &address); + if(clients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket); + else if(clientsocket!=ENET_SOCKET_NULL) + { + int dups = 0, oldest = -1; + loopv(clients) if(clients[i]->address.host == address.host) + { + dups++; + if(oldest<0 || clients[i]->connecttime < clients[oldest]->connecttime) oldest = i; + } + if(dups >= DUP_LIMIT) purgeclient(oldest); + + client *c = new client; + c->address = address; + c->socket = clientsocket; + c->connecttime = servtime; + c->lastinput = servtime; + clients.add(c); + } + } + + loopv(clients) + { + client &c = *clients[i]; + if((c.message || c.output.length()) && ENET_SOCKETSET_CHECK(writeset, c.socket)) + { + const char *data = c.output.length() ? c.output.getbuf() : c.message->getbuf(); + int len = c.output.length() ? c.output.length() : c.message->length(); + ENetBuffer buf; + buf.data = (void *)&data[c.outputpos]; + buf.dataLength = len-c.outputpos; + int res = enet_socket_send(c.socket, NULL, &buf, 1); + if(res>=0) + { + c.outputpos += res; + if(c.outputpos>=len) + { + if(c.output.length()) c.output.setsize(0); + else + { + c.message->purge(); + c.message = NULL; + } + c.outputpos = 0; + if(!c.message && c.output.empty() && c.shouldpurge) + { + purgeclient(i--); + continue; + } + } + } + else { purgeclient(i--); continue; } + } + if(ENET_SOCKETSET_CHECK(readset, c.socket)) + { + ENetBuffer buf; + buf.data = &c.input[c.inputpos]; + buf.dataLength = sizeof(c.input) - c.inputpos; + int res = enet_socket_receive(c.socket, NULL, &buf, 1); + if(res>0) + { + c.inputpos += res; + c.input[min(c.inputpos, (int)sizeof(c.input)-1)] = '\0'; + if(!checkclientinput(c)) { purgeclient(i--); continue; } + } + else { purgeclient(i--); continue; } + } + if(c.output.length() > OUTPUT_LIMIT) { purgeclient(i--); continue; } + if(ENET_TIME_DIFFERENCE(servtime, c.lastinput) >= (c.registeredserver ? KEEPALIVE_TIME : CLIENT_TIME)) { purgeclient(i--); continue; } + } } void banclients() { - loopvrev(clients) if(checkban(bans, clients[i]->address.host)) purgeclient(i); + loopvrev(clients) if(checkban(bans, clients[i]->address.host)) purgeclient(i); } volatile int reloadcfg = 1; void reloadsignal(int signum) { - reloadcfg = 1; + reloadcfg = 1; } int main(int argc, char **argv) { - if(enet_initialize()<0) fatal("Unable to initialise network module"); - atexit(enet_deinitialize); - - const char *dir = "", *ip = NULL; - int port = 28787; - if(argc>=2) dir = argv[1]; - if(argc>=3) port = atoi(argv[2]); - if(argc>=4) ip = argv[3]; - defformatstring(logname, "%smaster.log", dir); - defformatstring(cfgname, "%smaster.cfg", dir); - path(logname); - path(cfgname); - logfile = fopen(logname, "a"); - if(!logfile) logfile = stdout; - setvbuf(logfile, NULL, _IOLBF, BUFSIZ); - signal(SIGUSR1, reloadsignal); - setupserver(port, ip); - for(;;) - { - if(reloadcfg) - { - conoutf("reloading master.cfg"); - execfile(cfgname); - bangameservers(); - banclients(); - gengbanlist(); - reloadcfg = 0; - } - - servtime = enet_time_get(); - checkclients(); - checkgameservers(); - } - - return EXIT_SUCCESS; + if(enet_initialize()<0) fatal("Unable to initialise network module"); + atexit(enet_deinitialize); + + const char *dir = "", *ip = NULL; + int port = 28787; + if(argc>=2) dir = argv[1]; + if(argc>=3) port = atoi(argv[2]); + if(argc>=4) ip = argv[3]; + defformatstring(logname, "%smaster.log", dir); + defformatstring(cfgname, "%smaster.cfg", dir); + path(logname); + path(cfgname); + logfile = fopen(logname, "a"); + if(!logfile) logfile = stdout; + setvbuf(logfile, NULL, _IOLBF, BUFSIZ); + signal(SIGUSR1, reloadsignal); + setupserver(port, ip); + for(;;) + { + if(reloadcfg) + { + conoutf("reloading master.cfg"); + execfile(cfgname); + bangameservers(); + banclients(); + gengbanlist(); + reloadcfg = 0; + } + + servtime = enet_time_get(); + checkclients(); + checkgameservers(); + } + + return EXIT_SUCCESS; } diff --git a/src/engine/material.cpp b/src/engine/material.cpp index d56c7e2..54bc4ae 100644 --- a/src/engine/material.cpp +++ b/src/engine/material.cpp @@ -1,450 +1,450 @@ #include "engine.h" struct QuadNode { - int x, y, size; - uint filled; - QuadNode *child[4]; - - QuadNode(int x, int y, int size) : x(x), y(y), size(size), filled(0) { loopi(4) child[i] = 0; } - - void clear() { loopi(4) DELETEP(child[i]); } - - ~QuadNode() { clear(); } - - void insert(int mx, int my, int msize) { - if(size == msize) { - filled = 0xF; - return; - } - int csize = size>>1, i = 0; - if(mx >= x+csize) i |= 1; - if(my >= y+csize) i |= 2; - if(csize == msize) { - filled |= (1 << i); - return; - } - if(!child[i]) child[i] = new QuadNode(i&1 ? x+csize : x, i&2 ? y+csize : y, csize); - child[i]->insert(mx, my, msize); - loopj(4) if(child[j]) - { - if(child[j]->filled == 0xF) { - DELETEP(child[j]); - filled |= (1 << j); - } - } - } - - void genmatsurf(ushort mat, uchar orient, uchar visible, int x, int y, int z, int size, materialsurface *&matbuf) { - materialsurface &m = *matbuf++; - m.material = mat; - m.orient = orient; - m.visible = visible; - m.csize = size; - m.rsize = size; - int dim = dimension(orient); - m.o[C[dim]] = x; - m.o[R[dim]] = y; - m.o[dim] = z; - } - - void genmatsurfs(ushort mat, uchar orient, uchar flags, int z, materialsurface *&matbuf) { - if(filled == 0xF) genmatsurf(mat, orient, flags, x, y, z, size, matbuf); - else if(filled) - { - int csize = size>>1; - loopi(4) if(filled & (1 << i)) - genmatsurf(mat, orient, flags, i&1 ? x+csize : x, i&2 ? y+csize : y, z, csize, matbuf); - } - loopi(4) if(child[i]) child[i]->genmatsurfs(mat, orient, flags, z, matbuf); - } + int x, y, size; + uint filled; + QuadNode *child[4]; + + QuadNode(int x, int y, int size) : x(x), y(y), size(size), filled(0) { loopi(4) child[i] = 0; } + + void clear() { loopi(4) DELETEP(child[i]); } + + ~QuadNode() { clear(); } + + void insert(int mx, int my, int msize) { + if(size == msize) { + filled = 0xF; + return; + } + int csize = size>>1, i = 0; + if(mx >= x+csize) i |= 1; + if(my >= y+csize) i |= 2; + if(csize == msize) { + filled |= (1 << i); + return; + } + if(!child[i]) child[i] = new QuadNode(i&1 ? x+csize : x, i&2 ? y+csize : y, csize); + child[i]->insert(mx, my, msize); + loopj(4) if(child[j]) + { + if(child[j]->filled == 0xF) { + DELETEP(child[j]); + filled |= (1 << j); + } + } + } + + void genmatsurf(ushort mat, uchar orient, uchar visible, int x, int y, int z, int size, materialsurface *&matbuf) { + materialsurface &m = *matbuf++; + m.material = mat; + m.orient = orient; + m.visible = visible; + m.csize = size; + m.rsize = size; + int dim = dimension(orient); + m.o[C[dim]] = x; + m.o[R[dim]] = y; + m.o[dim] = z; + } + + void genmatsurfs(ushort mat, uchar orient, uchar flags, int z, materialsurface *&matbuf) { + if(filled == 0xF) genmatsurf(mat, orient, flags, x, y, z, size, matbuf); + else if(filled) + { + int csize = size>>1; + loopi(4) if(filled & (1 << i)) + genmatsurf(mat, orient, flags, i&1 ? x+csize : x, i&2 ? y+csize : y, z, csize, matbuf); + } + loopi(4) if(child[i]) child[i]->genmatsurfs(mat, orient, flags, z, matbuf); + } }; static float wfwave; static const bvec4 matnormals[6] = { - bvec4(0x80, 0, 0), - bvec4(0x7F, 0, 0), - bvec4(0, 0x80, 0), - bvec4(0, 0x7F, 0), - bvec4(0, 0, 0x80), - bvec4(0, 0, 0x7F) + bvec4(0x80, 0, 0), + bvec4(0x7F, 0, 0), + bvec4(0, 0x80, 0), + bvec4(0, 0x7F, 0), + bvec4(0, 0, 0x80), + bvec4(0, 0, 0x7F) }; static void renderwaterfall(const materialsurface &m, float offset) { - if(gle::attribbuf.empty()) - { - gle::defvertex(); - gle::defnormal(4, GL_BYTE); - gle::begin(GL_QUADS); - } - float x = m.o.x, y = m.o.y, zmin = m.o.z, zmax = zmin; - if(m.ends&1) zmin += -WATER_OFFSET-WATER_AMPLITUDE; - if(m.ends&2) zmax += wfwave; - int csize = m.csize, rsize = m.rsize; - switch(m.orient) - { - #define GENFACEORIENT(orient, v0, v1, v2, v3) \ - case orient: v0 v1 v2 v3 break; - #define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \ - { \ - gle::attribf(mx sx, my sy, mz sz); \ - gle::attrib(matnormals[orient]); \ - } - GENFACEVERTSXY(x, x, y, y, zmin, zmax, /**/, + csize, /**/, + rsize, + offset, - offset) - #undef GENFACEORIENT - #undef GENFACEVERT - } + if(gle::attribbuf.empty()) + { + gle::defvertex(); + gle::defnormal(4, GL_BYTE); + gle::begin(GL_QUADS); + } + float x = m.o.x, y = m.o.y, zmin = m.o.z, zmax = zmin; + if(m.ends&1) zmin += -WATER_OFFSET-WATER_AMPLITUDE; + if(m.ends&2) zmax += wfwave; + int csize = m.csize, rsize = m.rsize; + switch(m.orient) + { + #define GENFACEORIENT(orient, v0, v1, v2, v3) \ + case orient: v0 v1 v2 v3 break; + #define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \ + { \ + gle::attribf(mx sx, my sy, mz sz); \ + gle::attrib(matnormals[orient]); \ + } + GENFACEVERTSXY(x, x, y, y, zmin, zmax, /**/, + csize, /**/, + rsize, + offset, - offset) + #undef GENFACEORIENT + #undef GENFACEVERT + } } static void drawmaterial(const materialsurface &m, float offset, const bvec4 &color) { - if(gle::attribbuf.empty()) - { - gle::defvertex(); - gle::defcolor(4, GL_UNSIGNED_BYTE); - gle::begin(GL_QUADS); - } - float x = m.o.x, y = m.o.y, z = m.o.z, csize = m.csize, rsize = m.rsize; - switch(m.orient) - { - #define GENFACEORIENT(orient, v0, v1, v2, v3) \ - case orient: v0 v1 v2 v3 break; - #define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \ - { \ - gle::attribf(mx sx, my sy, mz sz); \ - gle::attrib(color); \ - } - GENFACEVERTS(x, x, y, y, z, z, /**/, + csize, /**/, + rsize, + offset, - offset) - #undef GENFACEORIENT - #undef GENFACEVERT - } + if(gle::attribbuf.empty()) + { + gle::defvertex(); + gle::defcolor(4, GL_UNSIGNED_BYTE); + gle::begin(GL_QUADS); + } + float x = m.o.x, y = m.o.y, z = m.o.z, csize = m.csize, rsize = m.rsize; + switch(m.orient) + { + #define GENFACEORIENT(orient, v0, v1, v2, v3) \ + case orient: v0 v1 v2 v3 break; + #define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \ + { \ + gle::attribf(mx sx, my sy, mz sz); \ + gle::attrib(color); \ + } + GENFACEVERTS(x, x, y, y, z, z, /**/, + csize, /**/, + rsize, + offset, - offset) + #undef GENFACEORIENT + #undef GENFACEVERT + } } const struct material { - const char *name; - ushort id; + const char *name; + ushort id; } materials[] = { - {"air", MAT_AIR}, - {"water", MAT_WATER}, {"water1", MAT_WATER}, {"water2", MAT_WATER+1}, {"water3", MAT_WATER+2}, {"water4", MAT_WATER+3}, - {"glass", MAT_GLASS}, {"glass1", MAT_GLASS}, {"glass2", MAT_GLASS+1}, {"glass3", MAT_GLASS+2}, {"glass4", MAT_GLASS+3}, - {"lava", MAT_LAVA}, {"lava1", MAT_LAVA}, {"lava2", MAT_LAVA+1}, {"lava3", MAT_LAVA+2}, {"lava4", MAT_LAVA+3}, - {"clip", MAT_CLIP}, - {"noclip", MAT_NOCLIP}, - {"gameclip", MAT_GAMECLIP}, - {"death", MAT_DEATH}, - {"alpha", MAT_ALPHA} + {"air", MAT_AIR}, + {"water", MAT_WATER}, {"water1", MAT_WATER}, {"water2", MAT_WATER+1}, {"water3", MAT_WATER+2}, {"water4", MAT_WATER+3}, + {"glass", MAT_GLASS}, {"glass1", MAT_GLASS}, {"glass2", MAT_GLASS+1}, {"glass3", MAT_GLASS+2}, {"glass4", MAT_GLASS+3}, + {"lava", MAT_LAVA}, {"lava1", MAT_LAVA}, {"lava2", MAT_LAVA+1}, {"lava3", MAT_LAVA+2}, {"lava4", MAT_LAVA+3}, + {"clip", MAT_CLIP}, + {"noclip", MAT_NOCLIP}, + {"gameclip", MAT_GAMECLIP}, + {"death", MAT_DEATH}, + {"alpha", MAT_ALPHA} }; int findmaterial(const char *name) { - loopi(sizeof(materials)/sizeof(material)) - { - if(!strcmp(materials[i].name, name)) return materials[i].id; - } - return -1; + loopi(sizeof(materials)/sizeof(material)) + { + if(!strcmp(materials[i].name, name)) return materials[i].id; + } + return -1; } const char *findmaterialname(int mat) { - loopi(sizeof(materials)/sizeof(materials[0])) if(materials[i].id == mat) return materials[i].name; - return NULL; + loopi(sizeof(materials)/sizeof(materials[0])) if(materials[i].id == mat) return materials[i].name; + return NULL; } const char *getmaterialdesc(int mat, const char *prefix) { - static const ushort matmasks[] = { MATF_VOLUME|MATF_INDEX, MATF_CLIP, MAT_DEATH, MAT_ALPHA }; - static string desc; - desc[0] = '\0'; - loopi(sizeof(matmasks)/sizeof(matmasks[0])) if(mat&matmasks[i]) - { - const char *matname = findmaterialname(mat&matmasks[i]); - if(matname) - { - concatstring(desc, desc[0] ? ", " : prefix); - concatstring(desc, matname); - } - } - return desc; + static const ushort matmasks[] = { MATF_VOLUME|MATF_INDEX, MATF_CLIP, MAT_DEATH, MAT_ALPHA }; + static string desc; + desc[0] = '\0'; + loopi(sizeof(matmasks)/sizeof(matmasks[0])) if(mat&matmasks[i]) + { + const char *matname = findmaterialname(mat&matmasks[i]); + if(matname) + { + concatstring(desc, desc[0] ? ", " : prefix); + concatstring(desc, matname); + } + } + return desc; } int visiblematerial(const cube &c, int orient, const ivec &co, int size, ushort matmask) { - ushort mat = c.material&matmask; - switch(mat) - { - case MAT_AIR: - break; - - case MAT_LAVA: - case MAT_WATER: - if(visibleface(c, orient, co, size, mat, MAT_AIR, matmask)) - return (orient != O_BOTTOM ? MATSURF_VISIBLE : MATSURF_EDIT_ONLY); - break; - - case MAT_GLASS: - if(visibleface(c, orient, co, size, MAT_GLASS, MAT_AIR, matmask)) - return MATSURF_VISIBLE; - break; - - default: - if(visibleface(c, orient, co, size, mat, MAT_AIR, matmask)) - return MATSURF_EDIT_ONLY; - break; - } - return MATSURF_NOT_VISIBLE; + ushort mat = c.material&matmask; + switch(mat) + { + case MAT_AIR: + break; + + case MAT_LAVA: + case MAT_WATER: + if(visibleface(c, orient, co, size, mat, MAT_AIR, matmask)) + return (orient != O_BOTTOM ? MATSURF_VISIBLE : MATSURF_EDIT_ONLY); + break; + + case MAT_GLASS: + if(visibleface(c, orient, co, size, MAT_GLASS, MAT_AIR, matmask)) + return MATSURF_VISIBLE; + break; + + default: + if(visibleface(c, orient, co, size, mat, MAT_AIR, matmask)) + return MATSURF_EDIT_ONLY; + break; + } + return MATSURF_NOT_VISIBLE; } void genmatsurfs(const cube &c, const ivec &co, int size, vector &matsurfs) { - loopi(6) - { - static const ushort matmasks[] = { MATF_VOLUME|MATF_INDEX, MATF_CLIP, MAT_DEATH, MAT_ALPHA }; - loopj(sizeof(matmasks)/sizeof(matmasks[0])) - { - int matmask = matmasks[j]; - int vis = visiblematerial(c, i, co, size, matmask&~MATF_INDEX); - if(vis != MATSURF_NOT_VISIBLE) - { - materialsurface m; - m.material = c.material&matmask; - m.orient = i; - m.visible = vis; - m.o = co; - m.csize = m.rsize = size; - if(dimcoord(i)) m.o[dimension(i)] += size; - matsurfs.add(m); - break; - } - } - } + loopi(6) + { + static const ushort matmasks[] = { MATF_VOLUME|MATF_INDEX, MATF_CLIP, MAT_DEATH, MAT_ALPHA }; + loopj(sizeof(matmasks)/sizeof(matmasks[0])) + { + int matmask = matmasks[j]; + int vis = visiblematerial(c, i, co, size, matmask&~MATF_INDEX); + if(vis != MATSURF_NOT_VISIBLE) + { + materialsurface m; + m.material = c.material&matmask; + m.orient = i; + m.visible = vis; + m.o = co; + m.csize = m.rsize = size; + if(dimcoord(i)) m.o[dimension(i)] += size; + matsurfs.add(m); + break; + } + } + } } static inline bool mergematcmp(const materialsurface &x, const materialsurface &y) { - int dim = dimension(x.orient), c = C[dim], r = R[dim]; - if(x.o[r] + x.rsize < y.o[r] + y.rsize) return true; - if(x.o[r] + x.rsize > y.o[r] + y.rsize) return false; - return x.o[c] < y.o[c]; + int dim = dimension(x.orient), c = C[dim], r = R[dim]; + if(x.o[r] + x.rsize < y.o[r] + y.rsize) return true; + if(x.o[r] + x.rsize > y.o[r] + y.rsize) return false; + return x.o[c] < y.o[c]; } static int mergematr(materialsurface *m, int sz, materialsurface &n) { - int dim = dimension(n.orient), c = C[dim], r = R[dim]; - for(int i = sz-1; i >= 0; --i) - { - if(m[i].o[r] + m[i].rsize < n.o[r]) break; - if(m[i].o[r] + m[i].rsize == n.o[r] && m[i].o[c] == n.o[c] && m[i].csize == n.csize) - { - n.o[r] = m[i].o[r]; - n.rsize += m[i].rsize; - memmove(&m[i], &m[i+1], (sz - (i+1)) * sizeof(materialsurface)); - return 1; - } - } - return 0; + int dim = dimension(n.orient), c = C[dim], r = R[dim]; + for(int i = sz-1; i >= 0; --i) + { + if(m[i].o[r] + m[i].rsize < n.o[r]) break; + if(m[i].o[r] + m[i].rsize == n.o[r] && m[i].o[c] == n.o[c] && m[i].csize == n.csize) + { + n.o[r] = m[i].o[r]; + n.rsize += m[i].rsize; + memmove(&m[i], &m[i+1], (sz - (i+1)) * sizeof(materialsurface)); + return 1; + } + } + return 0; } static int mergematc(materialsurface &m, materialsurface &n) { - int dim = dimension(n.orient), c = C[dim], r = R[dim]; - if(m.o[r] == n.o[r] && m.rsize == n.rsize && m.o[c] + m.csize == n.o[c]) - { - n.o[c] = m.o[c]; - n.csize += m.csize; - return 1; - } - return 0; + int dim = dimension(n.orient), c = C[dim], r = R[dim]; + if(m.o[r] == n.o[r] && m.rsize == n.rsize && m.o[c] + m.csize == n.o[c]) + { + n.o[c] = m.o[c]; + n.csize += m.csize; + return 1; + } + return 0; } static int mergemat(materialsurface *m, int sz, materialsurface &n) { - for(bool merged = false; sz; merged = true) - { - int rmerged = mergematr(m, sz, n); - sz -= rmerged; - if(!rmerged && merged) break; - if(!sz) break; - int cmerged = mergematc(m[sz-1], n); - sz -= cmerged; - if(!cmerged) break; - } - m[sz++] = n; - return sz; + for(bool merged = false; sz; merged = true) + { + int rmerged = mergematr(m, sz, n); + sz -= rmerged; + if(!rmerged && merged) break; + if(!sz) break; + int cmerged = mergematc(m[sz-1], n); + sz -= cmerged; + if(!cmerged) break; + } + m[sz++] = n; + return sz; } static int mergemats(materialsurface *m, int sz) { - quicksort(m, sz, mergematcmp); + quicksort(m, sz, mergematcmp); - int nsz = 0; - loopi(sz) nsz = mergemat(m, nsz, m[i]); - return nsz; + int nsz = 0; + loopi(sz) nsz = mergemat(m, nsz, m[i]); + return nsz; } static inline bool optmatcmp(const materialsurface &x, const materialsurface &y) { - if(x.material < y.material) return true; - if(x.material > y.material) return false; - if(x.orient > y.orient) return true; - if(x.orient < y.orient) return false; - int dim = dimension(x.orient); - return x.o[dim] < y.o[dim]; + if(x.material < y.material) return true; + if(x.material > y.material) return false; + if(x.orient > y.orient) return true; + if(x.orient < y.orient) return false; + int dim = dimension(x.orient); + return x.o[dim] < y.o[dim]; } VARF(optmats, 0, 1, 1, allchanged()); int optimizematsurfs(materialsurface *matbuf, int matsurfs) { - quicksort(matbuf, matsurfs, optmatcmp); - if(!optmats) return matsurfs; - materialsurface *cur = matbuf, *end = matbuf+matsurfs; - while(cur < end) - { - materialsurface *start = cur++; - int dim = dimension(start->orient); - while(cur < end && - cur->material == start->material && - cur->orient == start->orient && - cur->visible == start->visible && - cur->o[dim] == start->o[dim]) - ++cur; - if(!isliquid(start->material&MATF_VOLUME) || start->orient != O_TOP || !vertwater) - { - if(start!=matbuf) memmove(matbuf, start, (cur-start)*sizeof(materialsurface)); - matbuf += mergemats(matbuf, cur-start); - } - else if(cur-start>=4) - { - QuadNode vmats(0, 0, worldsize); - loopi(cur-start) vmats.insert(start[i].o[C[dim]], start[i].o[R[dim]], start[i].csize); - vmats.genmatsurfs(start->material, start->orient, start->visible, start->o[dim], matbuf); - } - else - { - if(start!=matbuf) memmove(matbuf, start, (cur-start)*sizeof(materialsurface)); - matbuf += cur-start; - } - } - return matsurfs - (end-matbuf); + quicksort(matbuf, matsurfs, optmatcmp); + if(!optmats) return matsurfs; + materialsurface *cur = matbuf, *end = matbuf+matsurfs; + while(cur < end) + { + materialsurface *start = cur++; + int dim = dimension(start->orient); + while(cur < end && + cur->material == start->material && + cur->orient == start->orient && + cur->visible == start->visible && + cur->o[dim] == start->o[dim]) + ++cur; + if(!isliquid(start->material&MATF_VOLUME) || start->orient != O_TOP || !vertwater) + { + if(start!=matbuf) memmove(matbuf, start, (cur-start)*sizeof(materialsurface)); + matbuf += mergemats(matbuf, cur-start); + } + else if(cur-start>=4) + { + QuadNode vmats(0, 0, worldsize); + loopi(cur-start) vmats.insert(start[i].o[C[dim]], start[i].o[R[dim]], start[i].csize); + vmats.genmatsurfs(start->material, start->orient, start->visible, start->o[dim], matbuf); + } + else + { + if(start!=matbuf) memmove(matbuf, start, (cur-start)*sizeof(materialsurface)); + matbuf += cur-start; + } + } + return matsurfs - (end-matbuf); } struct waterinfo { - materialsurface *m; - double depth, area; + materialsurface *m; + double depth, area; }; void setupmaterials(int start, int len) { - int hasmat = 0; - vector water; - unionfind uf; - if(!len) len = valist.length(); - for(int i = start; i < len; i++) - { - vtxarray *va = valist[i]; - materialsurface *skip = NULL; - loopj(va->matsurfs) - { - materialsurface &m = va->matbuf[j]; - int matvol = m.material&MATF_VOLUME; - if(matvol==MAT_WATER && m.orient==O_TOP) - { - m.index = water.length(); - loopvk(water) - { - materialsurface &n = *water[k].m; - if(m.material!=n.material || m.o.z!=n.o.z) continue; - if(n.o.x+n.rsize==m.o.x || m.o.x+m.rsize==n.o.x) - { - if(n.o.y+n.csize>m.o.y && n.o.ym.o.x && n.o.xmaterial && m.orient == skip->orient && skip->skip < 0xFFFF) - skip->skip++; - else - skip = &m; - } - } - loopv(water) - { - int root = uf.find(i); - if(i==root) continue; - materialsurface &m = *water[i].m, &n = *water[root].m; - if(m.light && (!m.light->attr1 || !n.light || (n.light->attr1 && m.light->attr1 > n.light->attr1))) n.light = m.light; - water[root].depth += water[i].depth; - water[root].area += water[i].area; - } - loopv(water) - { - int root = uf.find(i); - water[i].m->light = water[root].m->light; - water[i].m->depth = (short)(water[root].depth/water[root].area); - } - if(hasmat&(0xF< water; + unionfind uf; + if(!len) len = valist.length(); + for(int i = start; i < len; i++) + { + vtxarray *va = valist[i]; + materialsurface *skip = NULL; + loopj(va->matsurfs) + { + materialsurface &m = va->matbuf[j]; + int matvol = m.material&MATF_VOLUME; + if(matvol==MAT_WATER && m.orient==O_TOP) + { + m.index = water.length(); + loopvk(water) + { + materialsurface &n = *water[k].m; + if(m.material!=n.material || m.o.z!=n.o.z) continue; + if(n.o.x+n.rsize==m.o.x || m.o.x+m.rsize==n.o.x) + { + if(n.o.y+n.csize>m.o.y && n.o.ym.o.x && n.o.xmaterial && m.orient == skip->orient && skip->skip < 0xFFFF) + skip->skip++; + else + skip = &m; + } + } + loopv(water) + { + int root = uf.find(i); + if(i==root) continue; + materialsurface &m = *water[i].m, &n = *water[root].m; + if(m.light && (!m.light->attr1 || !n.light || (n.light->attr1 && m.light->attr1 > n.light->attr1))) n.light = m.light; + water[root].depth += water[i].depth; + water[root].area += water[i].area; + } + loopv(water) + { + int root = uf.find(i); + water[i].m->light = water[root].m->light; + water[i].m->depth = (short)(water[root].depth/water[root].area); + } + if(hasmat&(0xF< ymin && ymax > xmin) continue; - int c = sortorigin[dim]; - if(c > xmin && c < xmax) return sortedit; - if(c > ymin && c < ymax) return !sortedit; - xmin = abs(xmin - c); - xmax = abs(xmax - c); - ymin = abs(ymin - c); - ymax = abs(ymax - c); - if(max(xmin, xmax) <= min(ymin, ymax)) return sortedit; - else if(max(ymin, ymax) <= min(xmin, xmax)) return !sortedit; - } - if(x.material < y.material) return sortedit; - if(x.material > y.material) return !sortedit; - return false; + const materialsurface &x = *xm, &y = *ym; + if(!sortedit) + { + if((x.material&MATF_VOLUME) == MAT_LAVA) { if((y.material&MATF_VOLUME) != MAT_LAVA) return true; } + else if((y.material&MATF_VOLUME) == MAT_LAVA) return false; + } + int xdim = dimension(x.orient), ydim = dimension(y.orient); + loopi(3) + { + int dim = sortdim[i], xmin, xmax, ymin, ymax; + xmin = xmax = x.o[dim]; + if(dim==C[xdim]) xmax += x.csize; + else if(dim==R[xdim]) xmax += x.rsize; + ymin = ymax = y.o[dim]; + if(dim==C[ydim]) ymax += y.csize; + else if(dim==R[ydim]) ymax += y.rsize; + if(xmax > ymin && ymax > xmin) continue; + int c = sortorigin[dim]; + if(c > xmin && c < xmax) return sortedit; + if(c > ymin && c < ymax) return !sortedit; + xmin = abs(xmin - c); + xmax = abs(xmax - c); + ymin = abs(ymin - c); + ymax = abs(ymax - c); + if(max(xmin, xmax) <= min(ymin, ymax)) return sortedit; + else if(max(ymin, ymax) <= min(xmin, xmax)) return !sortedit; + } + if(x.material < y.material) return sortedit; + if(x.material > y.material) return !sortedit; + return false; } void sortmaterials(vector &vismats) { - sortorigin = ivec(camera1->o); - if(reflecting) sortorigin.z = int(reflectz - (camera1->o.z - reflectz)); - vec dir; - vecfromyawpitch(camera1->yaw, reflecting ? -camera1->pitch : camera1->pitch, 1, 0, dir); - loopi(3) { dir[i] = fabs(dir[i]); sortdim[i] = i; } - if(dir[sortdim[2]] > dir[sortdim[1]]) swap(sortdim[2], sortdim[1]); - if(dir[sortdim[1]] > dir[sortdim[0]]) swap(sortdim[1], sortdim[0]); - if(dir[sortdim[2]] > dir[sortdim[1]]) swap(sortdim[2], sortdim[1]); - - for(vtxarray *va = reflecting ? reflectedva : visibleva; va; va = reflecting ? va->rnext : va->next) - { - if(!va->matsurfs || va->occluded >= OCCLUDE_BB) continue; - if(reflecting || refracting>0 ? va->o.z+va->size <= reflectz : va->o.z >= reflectz) continue; - loopi(va->matsurfs) - { - materialsurface &m = va->matbuf[i]; - if(!editmode || !showmat || drawtex) - { - int matvol = m.material&MATF_VOLUME; - if(matvol==MAT_WATER && (m.orient==O_TOP || (refracting<0 && reflectz>worldsize))) { i += m.skip; continue; } - if(m.visible == MATSURF_EDIT_ONLY) { i += m.skip; continue; } - if(glaring && matvol!=MAT_LAVA) { i += m.skip; continue; } - } - else if(glaring) continue; - vismats.add(&m); - } - } - sortedit = editmode && showmat && !drawtex; - vismats.sort(vismatcmp); + sortorigin = ivec(camera1->o); + if(reflecting) sortorigin.z = int(reflectz - (camera1->o.z - reflectz)); + vec dir; + vecfromyawpitch(camera1->yaw, reflecting ? -camera1->pitch : camera1->pitch, 1, 0, dir); + loopi(3) { dir[i] = fabs(dir[i]); sortdim[i] = i; } + if(dir[sortdim[2]] > dir[sortdim[1]]) swap(sortdim[2], sortdim[1]); + if(dir[sortdim[1]] > dir[sortdim[0]]) swap(sortdim[1], sortdim[0]); + if(dir[sortdim[2]] > dir[sortdim[1]]) swap(sortdim[2], sortdim[1]); + + for(vtxarray *va = reflecting ? reflectedva : visibleva; va; va = reflecting ? va->rnext : va->next) + { + if(!va->matsurfs || va->occluded >= OCCLUDE_BB) continue; + if(reflecting || refracting>0 ? va->o.z+va->size <= reflectz : va->o.z >= reflectz) continue; + loopi(va->matsurfs) + { + materialsurface &m = va->matbuf[i]; + if(!editmode || !showmat || drawtex) + { + int matvol = m.material&MATF_VOLUME; + if(matvol==MAT_WATER && (m.orient==O_TOP || (refracting<0 && reflectz>worldsize))) { i += m.skip; continue; } + if(m.visible == MATSURF_EDIT_ONLY) { i += m.skip; continue; } + if(glaring && matvol!=MAT_LAVA) { i += m.skip; continue; } + } + else if(glaring) continue; + vismats.add(&m); + } + } + sortedit = editmode && showmat && !drawtex; + vismats.sort(vismatcmp); } void rendermatgrid(vector &vismats) { - enablepolygonoffset(GL_POLYGON_OFFSET_LINE); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - int lastmat = -1; - bvec4 color(0, 0, 0, 0); - loopvrev(vismats) - { - materialsurface &m = *vismats[i]; - if(m.material != lastmat) - { - switch(m.material&~MATF_INDEX) - { - case MAT_WATER: color = bvec4( 0, 0, 85, 255); break; // blue - case MAT_CLIP: color = bvec4(85, 0, 0, 255); break; // red - case MAT_GLASS: color = bvec4( 0, 85, 85, 255); break; // cyan - case MAT_NOCLIP: color = bvec4( 0, 85, 0, 255); break; // green - case MAT_LAVA: color = bvec4(85, 40, 0, 255); break; // orange - case MAT_GAMECLIP: color = bvec4(85, 85, 0, 255); break; // yellow - case MAT_DEATH: color = bvec4(40, 40, 40, 255); break; // black - case MAT_ALPHA: color = bvec4(85, 0, 85, 255); break; // pink - default: continue; - } - lastmat = m.material; - } - drawmaterial(m, -0.1f, color); - } - xtraverts += gle::end(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - disablepolygonoffset(GL_POLYGON_OFFSET_LINE); + enablepolygonoffset(GL_POLYGON_OFFSET_LINE); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + int lastmat = -1; + bvec4 color(0, 0, 0, 0); + loopvrev(vismats) + { + materialsurface &m = *vismats[i]; + if(m.material != lastmat) + { + switch(m.material&~MATF_INDEX) + { + case MAT_WATER: color = bvec4( 0, 0, 85, 255); break; // blue + case MAT_CLIP: color = bvec4(85, 0, 0, 255); break; // red + case MAT_GLASS: color = bvec4( 0, 85, 85, 255); break; // cyan + case MAT_NOCLIP: color = bvec4( 0, 85, 0, 255); break; // green + case MAT_LAVA: color = bvec4(85, 40, 0, 255); break; // orange + case MAT_GAMECLIP: color = bvec4(85, 85, 0, 255); break; // yellow + case MAT_DEATH: color = bvec4(40, 40, 40, 255); break; // black + case MAT_ALPHA: color = bvec4(85, 0, 85, 255); break; // pink + default: continue; + } + lastmat = m.material; + } + drawmaterial(m, -0.1f, color); + } + xtraverts += gle::end(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + disablepolygonoffset(GL_POLYGON_OFFSET_LINE); } #define GLASSVARS(name) \ - bvec name##color(0x20, 0x80, 0xC0); \ - HVARFR(name##colour, 0, 0x2080C0, 0xFFFFFF, \ - { \ - if(!name##colour) name##colour = 0x2080C0; \ - name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \ - }); + bvec name##color(0x20, 0x80, 0xC0); \ + HVARFR(name##colour, 0, 0x2080C0, 0xFFFFFF, \ + { \ + if(!name##colour) name##colour = 0x2080C0; \ + name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \ + }); GLASSVARS(glass) GLASSVARS(glass2) @@ -572,301 +572,300 @@ VARP(glassenv, 0, 1, 1); static void drawglass(const materialsurface &m, float offset) { - if(gle::attribbuf.empty()) - { - gle::defvertex(); - gle::defnormal(4, GL_BYTE); - gle::begin(GL_QUADS); - } - float x = m.o.x, y = m.o.y, z = m.o.z, csize = m.csize, rsize = m.rsize; - switch(m.orient) - { - #define GENFACEORIENT(orient, v0, v1, v2, v3) \ - case orient: v0 v1 v2 v3 break; - #define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \ - { \ - gle::attribf(mx sx, my sy, mz sz); \ - gle::attrib(matnormals[orient]); \ - } - GENFACEVERTS(x, x, y, y, z, z, /**/, + csize, /**/, + rsize, + offset, - offset) - #undef GENFACEORIENT - #undef GENFACEVERT - } + if(gle::attribbuf.empty()) + { + gle::defvertex(); + gle::defnormal(4, GL_BYTE); + gle::begin(GL_QUADS); + } + float x = m.o.x, y = m.o.y, z = m.o.z, csize = m.csize, rsize = m.rsize; + switch(m.orient) + { + #define GENFACEORIENT(orient, v0, v1, v2, v3) \ + case orient: v0 v1 v2 v3 break; + #define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \ + { \ + gle::attribf(mx sx, my sy, mz sz); \ + gle::attrib(matnormals[orient]); \ + } + GENFACEVERTS(x, x, y, y, z, z, /**/, + csize, /**/, + rsize, + offset, - offset) + #undef GENFACEORIENT + #undef GENFACEVERT + } } VARFP(waterfallenv, 0, 1, 1, preloadwatershaders()); static inline void changematerial(int mat, int orient) { - switch(mat&~MATF_INDEX) - { - case MAT_LAVA: - if(orient==O_TOP) flushlava(); - else xtraverts += gle::end(); - break; - default: - xtraverts += gle::end(); - break; - } + switch(mat&~MATF_INDEX) + { + case MAT_LAVA: + if(orient==O_TOP) flushlava(); + else xtraverts += gle::end(); + break; + default: + xtraverts += gle::end(); + break; + } } void rendermaterials() { - vector vismats; - sortmaterials(vismats); - if(vismats.empty()) return; - - glDisable(GL_CULL_FACE); - - MSlot *mslot = NULL; - int lastorient = -1, lastmat = -1, usedwaterfall = -1; - bool depth = true, blended = false; - ushort envmapped = EMID_NONE; - - GLOBALPARAM(camera, camera1->o); - - int lastfogtype = 1; - if(editmode && showmat && !drawtex) - { - glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); - glEnable(GL_BLEND); blended = true; - foggednotextureshader->set(); - zerofogcolor(); lastfogtype = 0; - bvec4 color(0, 0, 0, 0); - loopv(vismats) - { - const materialsurface &m = *vismats[i]; - if(lastmat!=m.material) - { - switch(m.material&~MATF_INDEX) - { - case MAT_WATER: color = bvec4(255, 128, 0, 255); break; // blue - case MAT_CLIP: color = bvec4( 0, 255, 255, 255); break; // red - case MAT_GLASS: color = bvec4(255, 0, 0, 255); break; // cyan - case MAT_NOCLIP: color = bvec4(255, 0, 255, 255); break; // green - case MAT_LAVA: color = bvec4( 0, 128, 255, 255); break; // orange - case MAT_GAMECLIP: color = bvec4( 0, 0, 255, 255); break; // yellow - case MAT_DEATH: color = bvec4(192, 192, 192, 255); break; // black - case MAT_ALPHA: color = bvec4( 0, 255, 0, 255); break; // pink - default: continue; - } - lastmat = m.material; - } - drawmaterial(m, -0.1f, color); - } - xtraverts += gle::end(); - } - else loopv(vismats) - { - const materialsurface &m = *vismats[i]; - int matvol = m.material&~MATF_INDEX; - if(lastmat!=m.material || lastorient!=m.orient || (matvol==MAT_GLASS && envmapped && m.envmap != envmapped)) - { - int fogtype = lastfogtype; - switch(matvol) - { - case MAT_WATER: - if(m.orient == O_TOP) continue; - if(lastmat == m.material) break; - mslot = &lookupmaterialslot(m.material, false); - if(!mslot->loaded || !mslot->sts.inrange(1)) continue; - else - { - changematerial(lastmat, lastorient); - glBindTexture(GL_TEXTURE_2D, mslot->sts[1].t->id); - - bvec wfcol = getwaterfallcolor(m.material); - if(wfcol.iszero()) wfcol = getwatercolor(m.material); - gle::color(wfcol, 192); - - int wfog = getwaterfog(m.material); - if(!wfog && !waterfallenv) - { - foggednotextureshader->set(); - fogtype = 1; - if(blended) { glDisable(GL_BLEND); blended = false; } - if(!depth) { glDepthMask(GL_TRUE); depth = true; } - } - else if((!waterfallrefract || reflecting || refracting) && !waterfallenv) - { - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); - SETSHADER(waterfall); - fogtype = 0; - if(!blended) { glEnable(GL_BLEND); blended = true; } - if(depth) { glDepthMask(GL_FALSE); depth = false; } - } - else - { - fogtype = 1; - - if(waterfallrefract && wfog && !reflecting && !refracting) - { - if(waterfallenv) SETSHADER(waterfallenvrefract); - else SETSHADER(waterfallrefract); - if(blended) { glDisable(GL_BLEND); blended = false; } - if(!depth) { glDepthMask(GL_TRUE); depth = true; } - } - else - { - SETSHADER(waterfallenv); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - if(wfog) - { - if(!blended) { glEnable(GL_BLEND); blended = true; } - if(depth) { glDepthMask(GL_FALSE); depth = false; } - } - else - { - if(blended) { glDisable(GL_BLEND); blended = false; } - if(!depth) { glDepthMask(GL_TRUE); depth = true; } - } - } - - if(usedwaterfall != m.material) - { - Texture *dudv = mslot->sts.inrange(5) ? mslot->sts[5].t : notexture; - float scale = TEX_SCALE/(dudv->ys*mslot->scale); - LOCALPARAMF(dudvoffset, 0, scale*16*lastmillis/1000.0f); - - glActiveTexture_(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, mslot->sts.inrange(4) ? mslot->sts[4].t->id : notexture->id); - glActiveTexture_(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, mslot->sts.inrange(5) ? mslot->sts[5].t->id : notexture->id); - if(waterfallenv) - { - glActiveTexture_(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_CUBE_MAP, lookupenvmap(*mslot)); - } - if(waterfallrefract && (!reflecting || !refracting) && usedwaterfall < 0) - { - glActiveTexture_(GL_TEXTURE4); - extern void setupwaterfallrefract(); - setupwaterfallrefract(); - } - glActiveTexture_(GL_TEXTURE0); - - usedwaterfall = m.material; - } - } - float angle = fmod(float(lastmillis/600.0f/(2*M_PI)), 1.0f), - s = angle - int(angle) - 0.5f; - s *= 8 - fabs(s)*16; - wfwave = vertwater ? WATER_AMPLITUDE*s-WATER_OFFSET : -WATER_OFFSET; - float scroll = 16.0f*lastmillis/1000.0f; - float xscale = TEX_SCALE/(mslot->sts[1].t->xs*mslot->scale); - float yscale = -TEX_SCALE/(mslot->sts[1].t->ys*mslot->scale); - LOCALPARAMF(waterfalltexgen, xscale, yscale, 0.0f, scroll); - } - break; - - case MAT_LAVA: - if(lastmat==m.material && lastorient!=O_TOP && m.orient!=O_TOP) break; - mslot = &lookupmaterialslot(m.material, false); - if(!mslot->loaded) continue; - else - { - int subslot = m.orient==O_TOP ? 0 : 1; - if(!mslot->sts.inrange(subslot)) continue; - changematerial(lastmat, lastorient); - glBindTexture(GL_TEXTURE_2D, mslot->sts[subslot].t->id); - } - if(lastmat!=m.material) - { - if(!depth) { glDepthMask(GL_TRUE); depth = true; } - if(blended) { glDisable(GL_BLEND); blended = false; } - float t = lastmillis/2000.0f; - t -= floor(t); - t = 1.0f - 2*fabs(t-0.5f); - extern int glare; - if(glare) t = 0.625f + 0.075f*t; - else t = 0.5f + 0.5f*t; - gle::colorf(t, t, t); - if(glaring) SETSHADER(lavaglare); else SETSHADER(lava); - fogtype = 1; - } - if(m.orient!=O_TOP) - { - float angle = fmod(float(lastmillis/2000.0f/(2*M_PI)), 1.0f), - s = angle - int(angle) - 0.5f; - s *= 8 - fabs(s)*16; - wfwave = vertwater ? WATER_AMPLITUDE*s-WATER_OFFSET : -WATER_OFFSET; - float scroll = 16.0f*lastmillis/3000.0f; - float xscale = TEX_SCALE/(mslot->sts[1].t->xs*mslot->scale); - float yscale = -TEX_SCALE/(mslot->sts[1].t->ys*mslot->scale); - LOCALPARAMF(lavatexgen, xscale, yscale, 0.0f, scroll); - } - else setuplava(mslot->sts[0].t, mslot->scale); - break; - - case MAT_GLASS: - if((m.envmap==EMID_NONE || !glassenv || envmapped==m.envmap) && lastmat==m.material) break; - changematerial(lastmat, lastorient); - if(m.envmap!=EMID_NONE && glassenv && envmapped!=m.envmap) - { - glBindTexture(GL_TEXTURE_CUBE_MAP, lookupenvmap(m.envmap)); - envmapped = m.envmap; - } - if(lastmat!=m.material) - { - if(!blended) { glEnable(GL_BLEND); blended = true; } - if(depth) { glDepthMask(GL_FALSE); depth = false; } - const bvec &gcol = getglasscolor(m.material); - if(m.envmap!=EMID_NONE && glassenv) - { - glBlendFunc(GL_ONE, GL_SRC_ALPHA); - gle::color(gcol); - SETSHADER(glass); - } - else - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gle::color(gcol, 40); - foggednotextureshader->set(); - fogtype = 1; - } - } - break; - - default: continue; - } - lastmat = m.material; - lastorient = m.orient; - if(fogtype!=lastfogtype) - { - if(fogtype) resetfogcolor(); - else zerofogcolor(); - lastfogtype = fogtype; - } - } - switch(matvol) - { - case MAT_WATER: - renderwaterfall(m, 0.1f); - break; - - case MAT_LAVA: - if(m.orient==O_TOP) renderlava(m); - else renderwaterfall(m, 0.1f); - break; - - case MAT_GLASS: - drawglass(m, 0.1f); - break; - } - } - - if(lastorient >= 0) changematerial(lastmat, lastorient); - - if(!depth) glDepthMask(GL_TRUE); - if(blended) glDisable(GL_BLEND); - if(!lastfogtype) resetfogcolor(); - extern int wireframe; - if(editmode && showmat && !drawtex && !wireframe) - { - foggednotextureshader->set(); - rendermatgrid(vismats); - } - - glEnable(GL_CULL_FACE); + vector vismats; + sortmaterials(vismats); + if(vismats.empty()) return; + + glDisable(GL_CULL_FACE); + + MSlot *mslot = NULL; + int lastorient = -1, lastmat = -1, usedwaterfall = -1; + bool depth = true, blended = false; + ushort envmapped = EMID_NONE; + + GLOBALPARAM(camera, camera1->o); + + int lastfogtype = 1; + if(editmode && showmat && !drawtex) + { + glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + glEnable(GL_BLEND); blended = true; + foggednotextureshader->set(); + zerofogcolor(); lastfogtype = 0; + bvec4 color(0, 0, 0, 0); + loopv(vismats) + { + const materialsurface &m = *vismats[i]; + if(lastmat!=m.material) + { + switch(m.material&~MATF_INDEX) + { + case MAT_WATER: color = bvec4(255, 128, 0, 255); break; // blue + case MAT_CLIP: color = bvec4( 0, 255, 255, 255); break; // red + case MAT_GLASS: color = bvec4(255, 0, 0, 255); break; // cyan + case MAT_NOCLIP: color = bvec4(255, 0, 255, 255); break; // green + case MAT_LAVA: color = bvec4( 0, 128, 255, 255); break; // orange + case MAT_GAMECLIP: color = bvec4( 0, 0, 255, 255); break; // yellow + case MAT_DEATH: color = bvec4(192, 192, 192, 255); break; // black + case MAT_ALPHA: color = bvec4( 0, 255, 0, 255); break; // pink + default: continue; + } + lastmat = m.material; + } + drawmaterial(m, -0.1f, color); + } + xtraverts += gle::end(); + } + else loopv(vismats) + { + const materialsurface &m = *vismats[i]; + int matvol = m.material&~MATF_INDEX; + if(lastmat!=m.material || lastorient!=m.orient || (matvol==MAT_GLASS && envmapped && m.envmap != envmapped)) + { + int fogtype = lastfogtype; + switch(matvol) + { + case MAT_WATER: + if(m.orient == O_TOP) continue; + if(lastmat == m.material) break; + mslot = &lookupmaterialslot(m.material, false); + if(!mslot->loaded || !mslot->sts.inrange(1)) continue; + else + { + changematerial(lastmat, lastorient); + glBindTexture(GL_TEXTURE_2D, mslot->sts[1].t->id); + + bvec wfcol = getwaterfallcolor(m.material); + if(wfcol.iszero()) wfcol = getwatercolor(m.material); + gle::color(wfcol, 192); + + int wfog = getwaterfog(m.material); + if(!wfog && !waterfallenv) + { + foggednotextureshader->set(); + fogtype = 1; + if(blended) { glDisable(GL_BLEND); blended = false; } + if(!depth) { glDepthMask(GL_TRUE); depth = true; } + } + else if((!waterfallrefract || reflecting || refracting) && !waterfallenv) + { + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); + SETSHADER(waterfall); + fogtype = 0; + if(!blended) { glEnable(GL_BLEND); blended = true; } + if(depth) { glDepthMask(GL_FALSE); depth = false; } + } + else + { + fogtype = 1; + + if(waterfallrefract && wfog && !reflecting && !refracting) + { + if(waterfallenv) SETSHADER(waterfallenvrefract); + else SETSHADER(waterfallrefract); + if(blended) { glDisable(GL_BLEND); blended = false; } + if(!depth) { glDepthMask(GL_TRUE); depth = true; } + } + else + { + SETSHADER(waterfallenv); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if(wfog) + { + if(!blended) { glEnable(GL_BLEND); blended = true; } + if(depth) { glDepthMask(GL_FALSE); depth = false; } + } + else + { + if(blended) { glDisable(GL_BLEND); blended = false; } + if(!depth) { glDepthMask(GL_TRUE); depth = true; } + } + } + + if(usedwaterfall != m.material) + { + Texture *dudv = mslot->sts.inrange(5) ? mslot->sts[5].t : notexture; + float scale = TEX_SCALE/(dudv->ys*mslot->scale); + LOCALPARAMF(dudvoffset, 0, scale*16*lastmillis/1000.0f); + + glActiveTexture_(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, mslot->sts.inrange(4) ? mslot->sts[4].t->id : notexture->id); + glActiveTexture_(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, mslot->sts.inrange(5) ? mslot->sts[5].t->id : notexture->id); + if(waterfallenv) + { + glActiveTexture_(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_CUBE_MAP, lookupenvmap(*mslot)); + } + if(waterfallrefract && (!reflecting || !refracting) && usedwaterfall < 0) + { + glActiveTexture_(GL_TEXTURE4); + extern void setupwaterfallrefract(); + setupwaterfallrefract(); + } + glActiveTexture_(GL_TEXTURE0); + + usedwaterfall = m.material; + } + } + float angle = fmod(float(lastmillis/600.0f/(2*M_PI)), 1.0f), + s = angle - int(angle) - 0.5f; + s *= 8 - fabs(s)*16; + wfwave = vertwater ? WATER_AMPLITUDE*s-WATER_OFFSET : -WATER_OFFSET; + float scroll = 16.0f*lastmillis/1000.0f; + float xscale = TEX_SCALE/(mslot->sts[1].t->xs*mslot->scale); + float yscale = -TEX_SCALE/(mslot->sts[1].t->ys*mslot->scale); + LOCALPARAMF(waterfalltexgen, xscale, yscale, 0.0f, scroll); + } + break; + + case MAT_LAVA: + if(lastmat==m.material && lastorient!=O_TOP && m.orient!=O_TOP) break; + mslot = &lookupmaterialslot(m.material, false); + if(!mslot->loaded) continue; + else + { + int subslot = m.orient==O_TOP ? 0 : 1; + if(!mslot->sts.inrange(subslot)) continue; + changematerial(lastmat, lastorient); + glBindTexture(GL_TEXTURE_2D, mslot->sts[subslot].t->id); + } + if(lastmat!=m.material) + { + if(!depth) { glDepthMask(GL_TRUE); depth = true; } + if(blended) { glDisable(GL_BLEND); blended = false; } + float t = lastmillis/2000.0f; + t -= floor(t); + t = 1.0f - 2*fabs(t-0.5f); + extern int glare; + if(glare) t = 0.625f + 0.075f*t; + else t = 0.5f + 0.5f*t; + gle::colorf(t, t, t); + if(glaring) SETSHADER(lavaglare); else SETSHADER(lava); + fogtype = 1; + } + if(m.orient!=O_TOP) + { + float angle = fmod(float(lastmillis/2000.0f/(2*M_PI)), 1.0f), + s = angle - int(angle) - 0.5f; + s *= 8 - fabs(s)*16; + wfwave = vertwater ? WATER_AMPLITUDE*s-WATER_OFFSET : -WATER_OFFSET; + float scroll = 16.0f*lastmillis/3000.0f; + float xscale = TEX_SCALE/(mslot->sts[1].t->xs*mslot->scale); + float yscale = -TEX_SCALE/(mslot->sts[1].t->ys*mslot->scale); + LOCALPARAMF(lavatexgen, xscale, yscale, 0.0f, scroll); + } + else setuplava(mslot->sts[0].t, mslot->scale); + break; + + case MAT_GLASS: + if((m.envmap==EMID_NONE || !glassenv || envmapped==m.envmap) && lastmat==m.material) break; + changematerial(lastmat, lastorient); + if(m.envmap!=EMID_NONE && glassenv && envmapped!=m.envmap) + { + glBindTexture(GL_TEXTURE_CUBE_MAP, lookupenvmap(m.envmap)); + envmapped = m.envmap; + } + if(lastmat!=m.material) + { + if(!blended) { glEnable(GL_BLEND); blended = true; } + if(depth) { glDepthMask(GL_FALSE); depth = false; } + const bvec &gcol = getglasscolor(m.material); + if(m.envmap!=EMID_NONE && glassenv) + { + glBlendFunc(GL_ONE, GL_SRC_ALPHA); + gle::color(gcol); + SETSHADER(glass); + } + else + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gle::color(gcol, 40); + foggednotextureshader->set(); + fogtype = 1; + } + } + break; + + default: continue; + } + lastmat = m.material; + lastorient = m.orient; + if(fogtype!=lastfogtype) + { + if(fogtype) resetfogcolor(); + else zerofogcolor(); + lastfogtype = fogtype; + } + } + switch(matvol) + { + case MAT_WATER: + renderwaterfall(m, 0.1f); + break; + + case MAT_LAVA: + if(m.orient==O_TOP) renderlava(m); + else renderwaterfall(m, 0.1f); + break; + + case MAT_GLASS: + drawglass(m, 0.1f); + break; + } + } + + if(lastorient >= 0) changematerial(lastmat, lastorient); + + if(!depth) glDepthMask(GL_TRUE); + if(blended) glDisable(GL_BLEND); + if(!lastfogtype) resetfogcolor(); + extern int wireframe; + if(editmode && showmat && !drawtex && !wireframe) + { + foggednotextureshader->set(); + rendermatgrid(vismats); + } + + glEnable(GL_CULL_FACE); } - diff --git a/src/engine/md3.h b/src/engine/md3.h index 2ae5986..7296055 100644 --- a/src/engine/md3.h +++ b/src/engine/md3.h @@ -2,181 +2,181 @@ struct md3; struct md3frame { - vec bbmin, bbmax, origin; - float radius; - uchar name[16]; + vec bbmin, bbmax, origin; + float radius; + uchar name[16]; }; struct md3tag { - char name[64]; - float translation[3]; - float rotation[3][3]; + char name[64]; + float translation[3]; + float rotation[3][3]; }; struct md3vertex { - short vertex[3]; - short normal; + short vertex[3]; + short normal; }; struct md3triangle { - int vertexindices[3]; + int vertexindices[3]; }; struct md3header { - char id[4]; - int version; - char name[64]; - int flags; - int numframes, numtags, nummeshes, numskins; - int ofs_frames, ofs_tags, ofs_meshes, ofs_eof; // offsets + char id[4]; + int version; + char name[64]; + int flags; + int numframes, numtags, nummeshes, numskins; + int ofs_frames, ofs_tags, ofs_meshes, ofs_eof; // offsets }; struct md3meshheader { - char id[4]; - char name[64]; - int flags; - int numframes, numshaders, numvertices, numtriangles; - int ofs_triangles, ofs_shaders, ofs_uv, ofs_vertices, meshsize; // offsets + char id[4]; + char name[64]; + int flags; + int numframes, numshaders, numvertices, numtriangles; + int ofs_triangles, ofs_shaders, ofs_uv, ofs_vertices, meshsize; // offsets }; struct md3 : vertloader { - md3(const char *name) : vertloader(name) {} - - static const char *formatname() { return "md3"; } - bool flipy() const { return true; } - int type() const { return MDL_MD3; } - - struct md3meshgroup : vertmeshgroup - { - bool load(const char *path) - { - stream *f = openfile(path, "rb"); - if(!f) return false; - md3header header; - f->read(&header, sizeof(md3header)); - lilswap(&header.version, 1); - lilswap(&header.flags, 9); - if(strncmp(header.id, "IDP3", 4) != 0 || header.version != 15) // header check - { - delete f; - conoutf(CON_ERROR, "md3: corrupted header"); - return false; - } - - name = newstring(path); - - numframes = header.numframes; - - int mesh_offset = header.ofs_meshes; - loopi(header.nummeshes) - { - vertmesh &m = *new vertmesh; - m.group = this; - meshes.add(&m); - - md3meshheader mheader; - f->seek(mesh_offset, SEEK_SET); - f->read(&mheader, sizeof(md3meshheader)); - lilswap(&mheader.flags, 10); - - m.name = newstring(mheader.name); - - m.numtris = mheader.numtriangles; - m.tris = new tri[m.numtris]; - f->seek(mesh_offset + mheader.ofs_triangles, SEEK_SET); - loopj(m.numtris) - { - md3triangle tri = { 0 }; - f->read(&tri, sizeof(md3triangle)); // read the triangles - lilswap(tri.vertexindices, 3); - loopk(3) m.tris[j].vert[k] = (ushort)tri.vertexindices[k]; - } - - m.numverts = mheader.numvertices; - m.tcverts = new tcvert[m.numverts]; - f->seek(mesh_offset + mheader.ofs_uv , SEEK_SET); - f->read(m.tcverts, m.numverts*2*sizeof(float)); // read the UV data - lilswap(&m.tcverts[0].tc.x, 2*m.numverts); - - m.verts = new vert[numframes*m.numverts]; - f->seek(mesh_offset + mheader.ofs_vertices, SEEK_SET); - loopj(numframes*m.numverts) - { - md3vertex v = { {0}, 0 }; - f->read(&v, sizeof(md3vertex)); // read the vertices - lilswap(v.vertex, 4); - - m.verts[j].pos = vec(v.vertex[0]/64.0f, -v.vertex[1]/64.0f, v.vertex[2]/64.0f); - - float lng = (v.normal&0xFF)*2*M_PI/255.0f; // decode vertex normals - float lat = ((v.normal>>8)&0xFF)*2*M_PI/255.0f; - m.verts[j].norm = vec(cosf(lat)*sinf(lng), -sinf(lat)*sinf(lng), cosf(lng)); - } - - mesh_offset += mheader.meshsize; - } - - numtags = header.numtags; - if(numtags) - { - tags = new tag[numframes*numtags]; - f->seek(header.ofs_tags, SEEK_SET); - md3tag tag; - - loopi(header.numframes*header.numtags) - { - f->read(&tag, sizeof(md3tag)); - lilswap(tag.translation, 12); - if(tag.name[0] && iload(name)) { delete group; return NULL; } - return group; - } - - bool loaddefaultparts() - { - const char *pname = parentdir(name); - part &mdl = addpart(); - defformatstring(name1, "packages/models/%s/tris.md3", name); - mdl.meshes = sharemeshes(path(name1)); - if(!mdl.meshes) - { - defformatstring(name2, "packages/models/%s/tris.md3", pname); // try md3 in parent folder (vert sharing) - mdl.meshes = sharemeshes(path(name2)); - if(!mdl.meshes) return false; - } - Texture *tex, *masks; - loadskin(name, pname, tex, masks); - mdl.initskins(tex, masks); - if(tex==notexture) conoutf(CON_ERROR, "could not load model skin for %s", name1); - return true; - } + md3(const char *name) : vertloader(name) {} + + static const char *formatname() { return "md3"; } + bool flipy() const { return true; } + int type() const { return MDL_MD3; } + + struct md3meshgroup : vertmeshgroup + { + bool load(const char *path) + { + stream *f = openfile(path, "rb"); + if(!f) return false; + md3header header; + f->read(&header, sizeof(md3header)); + lilswap(&header.version, 1); + lilswap(&header.flags, 9); + if(strncmp(header.id, "IDP3", 4) != 0 || header.version != 15) // header check + { + delete f; + conoutf(CON_ERROR, "md3: corrupted header"); + return false; + } + + name = newstring(path); + + numframes = header.numframes; + + int mesh_offset = header.ofs_meshes; + loopi(header.nummeshes) + { + vertmesh &m = *new vertmesh; + m.group = this; + meshes.add(&m); + + md3meshheader mheader; + f->seek(mesh_offset, SEEK_SET); + f->read(&mheader, sizeof(md3meshheader)); + lilswap(&mheader.flags, 10); + + m.name = newstring(mheader.name); + + m.numtris = mheader.numtriangles; + m.tris = new tri[m.numtris]; + f->seek(mesh_offset + mheader.ofs_triangles, SEEK_SET); + loopj(m.numtris) + { + md3triangle tri = { 0 }; + f->read(&tri, sizeof(md3triangle)); // read the triangles + lilswap(tri.vertexindices, 3); + loopk(3) m.tris[j].vert[k] = (ushort)tri.vertexindices[k]; + } + + m.numverts = mheader.numvertices; + m.tcverts = new tcvert[m.numverts]; + f->seek(mesh_offset + mheader.ofs_uv , SEEK_SET); + f->read(m.tcverts, m.numverts*2*sizeof(float)); // read the UV data + lilswap(&m.tcverts[0].tc.x, 2*m.numverts); + + m.verts = new vert[numframes*m.numverts]; + f->seek(mesh_offset + mheader.ofs_vertices, SEEK_SET); + loopj(numframes*m.numverts) + { + md3vertex v = { {0}, 0 }; + f->read(&v, sizeof(md3vertex)); // read the vertices + lilswap(v.vertex, 4); + + m.verts[j].pos = vec(v.vertex[0]/64.0f, -v.vertex[1]/64.0f, v.vertex[2]/64.0f); + + float lng = (v.normal&0xFF)*2*M_PI/255.0f; // decode vertex normals + float lat = ((v.normal>>8)&0xFF)*2*M_PI/255.0f; + m.verts[j].norm = vec(cosf(lat)*sinf(lng), -sinf(lat)*sinf(lng), cosf(lng)); + } + + mesh_offset += mheader.meshsize; + } + + numtags = header.numtags; + if(numtags) + { + tags = new tag[numframes*numtags]; + f->seek(header.ofs_tags, SEEK_SET); + md3tag tag; + + loopi(header.numframes*header.numtags) + { + f->read(&tag, sizeof(md3tag)); + lilswap(tag.translation, 12); + if(tag.name[0] && iload(name)) { delete group; return NULL; } + return group; + } + + bool loaddefaultparts() + { + const char *pname = parentdir(name); + part &mdl = addpart(); + defformatstring(name1, "packages/models/%s/tris.md3", name); + mdl.meshes = sharemeshes(path(name1)); + if(!mdl.meshes) + { + defformatstring(name2, "packages/models/%s/tris.md3", pname); // try md3 in parent folder (vert sharing) + mdl.meshes = sharemeshes(path(name2)); + if(!mdl.meshes) return false; + } + Texture *tex, *masks; + loadskin(name, pname, tex, masks); + mdl.initskins(tex, masks); + if(tex==notexture) conoutf(CON_ERROR, "could not load model skin for %s", name1); + return true; + } }; vertcommands md3commands; diff --git a/src/engine/md5.h b/src/engine/md5.h index 0d59586..d948ccf 100644 --- a/src/engine/md5.h +++ b/src/engine/md5.h @@ -2,417 +2,417 @@ struct md5; struct md5joint { - vec pos; - quat orient; + vec pos; + quat orient; }; struct md5weight { - int joint; - float bias; - vec pos; -}; + int joint; + float bias; + vec pos; +}; struct md5vert { - vec2 tc; - ushort start, count; + vec2 tc; + ushort start, count; }; struct md5hierarchy { - string name; - int parent, flags, start; + string name; + int parent, flags, start; }; struct md5 : skelloader { - md5(const char *name) : skelloader(name) {} - - static const char *formatname() { return "md5"; } - int type() const { return MDL_MD5; } - - struct md5mesh : skelmesh - { - md5weight *weightinfo; - int numweights; - md5vert *vertinfo; - - md5mesh() : weightinfo(NULL), numweights(0), vertinfo(NULL) - { - } - - ~md5mesh() - { - cleanup(); - } - - void cleanup() - { - DELETEA(weightinfo); - DELETEA(vertinfo); - } - - void buildverts(vector &joints) - { - loopi(numverts) - { - md5vert &v = vertinfo[i]; - vec pos(0, 0, 0); - loopk(v.count) - { - md5weight &w = weightinfo[v.start+k]; - md5joint &j = joints[w.joint]; - vec wpos = j.orient.rotate(w.pos); - wpos.add(j.pos); - wpos.mul(w.bias); - pos.add(wpos); - } - vert &vv = verts[i]; - vv.pos = pos; - vv.tc = v.tc; - - blendcombo c; - int sorted = 0; - loopj(v.count) - { - md5weight &w = weightinfo[v.start+j]; - sorted = c.addweight(sorted, w.bias, w.joint); - } - c.finalize(sorted); - vv.blend = addblendcombo(c); - } - } - - void load(stream *f, char *buf, size_t bufsize) - { - md5weight w; - md5vert v; - tri t; - int index; - - while(f->getline(buf, bufsize) && buf[0]!='}') - { - if(strstr(buf, "// meshes:")) - { - char *start = strchr(buf, ':')+1; - if(*start==' ') start++; - char *end = start + strlen(start)-1; - while(end >= start && isspace(*end)) end--; - name = newstring(start, end+1-start); - } - else if(strstr(buf, "shader")) - { - char *start = strchr(buf, '"'), *end = start ? strchr(start+1, '"') : NULL; - if(start && end) - { - char *texname = newstring(start+1, end-(start+1)); - part *p = loading->parts.last(); - p->initskins(notexture, notexture, group->meshes.length()); - skin &s = p->skins.last(); - s.tex = textureload(makerelpath(dir, texname), 0, true, false); - delete[] texname; - } - } - else if(sscanf(buf, " numverts %d", &numverts)==1) - { - numverts = max(numverts, 0); - if(numverts) - { - vertinfo = new md5vert[numverts]; - verts = new vert[numverts]; - } - } - else if(sscanf(buf, " numtris %d", &numtris)==1) - { - numtris = max(numtris, 0); - if(numtris) tris = new tri[numtris]; - } - else if(sscanf(buf, " numweights %d", &numweights)==1) - { - numweights = max(numweights, 0); - if(numweights) weightinfo = new md5weight[numweights]; - } - else if(sscanf(buf, " vert %d ( %f %f ) %hu %hu", &index, &v.tc.x, &v.tc.y, &v.start, &v.count)==5) - { - if(index>=0 && index=0 && index=0 && index basejoints; - while(f->getline(buf, sizeof(buf))) - { - int tmp; - if(sscanf(buf, " MD5Version %d", &tmp)==1) - { - if(tmp!=10) { delete f; return false; } - } - else if(sscanf(buf, " numJoints %d", &tmp)==1) - { - if(tmp<1) { delete f; return false; } - if(skel->numbones>0) continue; - skel->numbones = tmp; - skel->bones = new boneinfo[skel->numbones]; - } - else if(sscanf(buf, " numMeshes %d", &tmp)==1) - { - if(tmp<1) { delete f; return false; } - } - else if(strstr(buf, "joints {")) - { - string name; - int parent; - md5joint j; - while(f->getline(buf, sizeof(buf)) && buf[0]!='}') - { - char *curbuf = buf, *curname = name; - bool allowspace = false; - while(*curbuf && isspace(*curbuf)) curbuf++; - if(*curbuf == '"') { curbuf++; allowspace = true; } - while(*curbuf && curname < &name[sizeof(name)-1]) - { - char c = *curbuf++; - if(c == '"') break; - if(isspace(c) && !allowspace) break; - *curname++ = c; - } - *curname = '\0'; - if(sscanf(curbuf, " %d ( %f %f %f ) ( %f %f %f )", - &parent, &j.pos.x, &j.pos.y, &j.pos.z, - &j.orient.x, &j.orient.y, &j.orient.z)==7) - { - j.pos.y = -j.pos.y; - j.orient.x = -j.orient.x; - j.orient.z = -j.orient.z; - if(basejoints.length()numbones) - { - if(!skel->bones[basejoints.length()].name) - skel->bones[basejoints.length()].name = newstring(name); - skel->bones[basejoints.length()].parent = parent; - } - j.orient.restorew(); - basejoints.add(j); - } - } - if(basejoints.length()!=skel->numbones) { delete f; return false; } - } - else if(strstr(buf, "mesh {")) - { - md5mesh *m = new md5mesh; - m->group = this; - meshes.add(m); - m->load(f, buf, sizeof(buf)); - if(!m->numtris || !m->numverts) - { - conoutf(CON_WARN, "empty mesh in %s", filename); - meshes.removeobj(m); - delete m; - } - } - } - - if(skel->shared <= 1) - { - skel->linkchildren(); - loopv(basejoints) - { - boneinfo &b = skel->bones[i]; - b.base = dualquat(basejoints[i].orient, basejoints[i].pos); - (b.invbase = b.base).invert(); - } - } - - loopv(meshes) - { - md5mesh &m = *(md5mesh *)meshes[i]; - m.buildverts(basejoints); - if(smooth <= 1) m.smoothnorms(smooth); - else m.buildnorms(); - m.cleanup(); - } - - sortblendcombos(); - - delete f; - return true; - } - - skelanimspec *loadanim(const char *filename) - { - skelanimspec *sa = skel->findskelanim(filename); - if(sa) return sa; - - stream *f = openfile(filename, "r"); - if(!f) return NULL; - - vector hierarchy; - vector basejoints; - int animdatalen = 0, animframes = 0; - float *animdata = NULL; - dualquat *animbones = NULL; - char buf[512]; - while(f->getline(buf, sizeof(buf))) - { - int tmp; - if(sscanf(buf, " MD5Version %d", &tmp)==1) - { - if(tmp!=10) { delete f; return NULL; } - } - else if(sscanf(buf, " numJoints %d", &tmp)==1) - { - if(tmp!=skel->numbones) { delete f; return NULL; } - } - else if(sscanf(buf, " numFrames %d", &animframes)==1) - { - if(animframes<1) { delete f; return NULL; } - } - else if(sscanf(buf, " frameRate %d", &tmp)==1); - else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1) - { - if(animdatalen>0) animdata = new float[animdatalen]; - } - else if(strstr(buf, "bounds {")) - { - while(f->getline(buf, sizeof(buf)) && buf[0]!='}'); - } - else if(strstr(buf, "hierarchy {")) - { - while(f->getline(buf, sizeof(buf)) && buf[0]!='}') - { - md5hierarchy h; - if(sscanf(buf, " %100s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4) - hierarchy.add(h); - } - } - else if(strstr(buf, "baseframe {")) - { - while(f->getline(buf, sizeof(buf)) && buf[0]!='}') - { - md5joint j; - if(sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6) - { - j.pos.y = -j.pos.y; - j.orient.x = -j.orient.x; - j.orient.z = -j.orient.z; - j.orient.restorew(); - basejoints.add(j); - } - } - if(basejoints.length()!=skel->numbones) { delete f; if(animdata) delete[] animdata; return NULL; } - animbones = new dualquat[(skel->numframes+animframes)*skel->numbones]; - if(skel->framebones) - { - memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat)); - delete[] skel->framebones; - } - skel->framebones = animbones; - animbones += skel->numframes*skel->numbones; - - sa = &skel->addskelanim(filename); - sa->frame = skel->numframes; - sa->range = animframes; - - skel->numframes += animframes; - } - else if(sscanf(buf, " frame %d", &tmp)==1) - { - for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';) - { - for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next) - { - animdata[numdata] = strtod(src, &next); - if(next <= src) break; - } - } - dualquat *frame = &animbones[tmp*skel->numbones]; - loopv(basejoints) - { - md5hierarchy &h = hierarchy[i]; - md5joint j = basejoints[i]; - if(h.start < animdatalen && h.flags) - { - float *jdata = &animdata[h.start]; - if(h.flags&1) j.pos.x = *jdata++; - if(h.flags&2) j.pos.y = -*jdata++; - if(h.flags&4) j.pos.z = *jdata++; - if(h.flags&8) j.orient.x = -*jdata++; - if(h.flags&16) j.orient.y = *jdata++; - if(h.flags&32) j.orient.z = -*jdata++; - j.orient.restorew(); - } - frame[i] = dualquat(j.orient, j.pos); - if(adjustments.inrange(i)) adjustments[i].adjust(frame[i]); - frame[i].mul(skel->bones[i].invbase); - if(h.parent >= 0) frame[i].mul(skel->bones[h.parent].base, dualquat(frame[i])); - frame[i].fixantipodal(skel->framebones[i]); - } - } - } - - if(animdata) delete[] animdata; - delete f; - - return sa; - } - - bool load(const char *meshfile, float smooth) - { - name = newstring(meshfile); - - if(!loadmesh(meshfile, smooth)) return false; - - return true; - } - }; - - meshgroup *loadmeshes(const char *name, va_list args) - { - md5meshgroup *group = new md5meshgroup; - group->shareskeleton(va_arg(args, char *)); - if(!group->load(name, va_arg(args, double))) { delete group; return NULL; } - return group; - } - - bool loaddefaultparts() - { - skelpart &mdl = addpart(); - mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; - adjustments.setsize(0); - const char *fname = name + strlen(name); - do --fname; while(fname >= name && *fname!='/' && *fname!='\\'); - fname++; - defformatstring(meshname, "packages/models/%s/%s.md5mesh", name, fname); - mdl.meshes = sharemeshes(path(meshname), NULL, 2.0); - if(!mdl.meshes) return false; - mdl.initanimparts(); - mdl.initskins(); - defformatstring(animname, "packages/models/%s/%s.md5anim", name, fname); - ((md5meshgroup *)mdl.meshes)->loadanim(path(animname)); - return true; - } + md5(const char *name) : skelloader(name) {} + + static const char *formatname() { return "md5"; } + int type() const { return MDL_MD5; } + + struct md5mesh : skelmesh + { + md5weight *weightinfo; + int numweights; + md5vert *vertinfo; + + md5mesh() : weightinfo(NULL), numweights(0), vertinfo(NULL) + { + } + + ~md5mesh() + { + cleanup(); + } + + void cleanup() + { + DELETEA(weightinfo); + DELETEA(vertinfo); + } + + void buildverts(vector &joints) + { + loopi(numverts) + { + md5vert &v = vertinfo[i]; + vec pos(0, 0, 0); + loopk(v.count) + { + md5weight &w = weightinfo[v.start+k]; + md5joint &j = joints[w.joint]; + vec wpos = j.orient.rotate(w.pos); + wpos.add(j.pos); + wpos.mul(w.bias); + pos.add(wpos); + } + vert &vv = verts[i]; + vv.pos = pos; + vv.tc = v.tc; + + blendcombo c; + int sorted = 0; + loopj(v.count) + { + md5weight &w = weightinfo[v.start+j]; + sorted = c.addweight(sorted, w.bias, w.joint); + } + c.finalize(sorted); + vv.blend = addblendcombo(c); + } + } + + void load(stream *f, char *buf, size_t bufsize) + { + md5weight w; + md5vert v; + tri t; + int index; + + while(f->getline(buf, bufsize) && buf[0]!='}') + { + if(strstr(buf, "// meshes:")) + { + char *start = strchr(buf, ':')+1; + if(*start==' ') start++; + char *end = start + strlen(start)-1; + while(end >= start && isspace(*end)) end--; + name = newstring(start, end+1-start); + } + else if(strstr(buf, "shader")) + { + char *start = strchr(buf, '"'), *end = start ? strchr(start+1, '"') : NULL; + if(start && end) + { + char *texname = newstring(start+1, end-(start+1)); + part *p = loading->parts.last(); + p->initskins(notexture, notexture, group->meshes.length()); + skin &s = p->skins.last(); + s.tex = textureload(makerelpath(dir, texname), 0, true, false); + delete[] texname; + } + } + else if(sscanf(buf, " numverts %d", &numverts)==1) + { + numverts = max(numverts, 0); + if(numverts) + { + vertinfo = new md5vert[numverts]; + verts = new vert[numverts]; + } + } + else if(sscanf(buf, " numtris %d", &numtris)==1) + { + numtris = max(numtris, 0); + if(numtris) tris = new tri[numtris]; + } + else if(sscanf(buf, " numweights %d", &numweights)==1) + { + numweights = max(numweights, 0); + if(numweights) weightinfo = new md5weight[numweights]; + } + else if(sscanf(buf, " vert %d ( %f %f ) %hu %hu", &index, &v.tc.x, &v.tc.y, &v.start, &v.count)==5) + { + if(index>=0 && index=0 && index=0 && index basejoints; + while(f->getline(buf, sizeof(buf))) + { + int tmp; + if(sscanf(buf, " MD5Version %d", &tmp)==1) + { + if(tmp!=10) { delete f; return false; } + } + else if(sscanf(buf, " numJoints %d", &tmp)==1) + { + if(tmp<1) { delete f; return false; } + if(skel->numbones>0) continue; + skel->numbones = tmp; + skel->bones = new boneinfo[skel->numbones]; + } + else if(sscanf(buf, " numMeshes %d", &tmp)==1) + { + if(tmp<1) { delete f; return false; } + } + else if(strstr(buf, "joints {")) + { + string name; + int parent; + md5joint j; + while(f->getline(buf, sizeof(buf)) && buf[0]!='}') + { + char *curbuf = buf, *curname = name; + bool allowspace = false; + while(*curbuf && isspace(*curbuf)) curbuf++; + if(*curbuf == '"') { curbuf++; allowspace = true; } + while(*curbuf && curname < &name[sizeof(name)-1]) + { + char c = *curbuf++; + if(c == '"') break; + if(isspace(c) && !allowspace) break; + *curname++ = c; + } + *curname = '\0'; + if(sscanf(curbuf, " %d ( %f %f %f ) ( %f %f %f )", + &parent, &j.pos.x, &j.pos.y, &j.pos.z, + &j.orient.x, &j.orient.y, &j.orient.z)==7) + { + j.pos.y = -j.pos.y; + j.orient.x = -j.orient.x; + j.orient.z = -j.orient.z; + if(basejoints.length()numbones) + { + if(!skel->bones[basejoints.length()].name) + skel->bones[basejoints.length()].name = newstring(name); + skel->bones[basejoints.length()].parent = parent; + } + j.orient.restorew(); + basejoints.add(j); + } + } + if(basejoints.length()!=skel->numbones) { delete f; return false; } + } + else if(strstr(buf, "mesh {")) + { + md5mesh *m = new md5mesh; + m->group = this; + meshes.add(m); + m->load(f, buf, sizeof(buf)); + if(!m->numtris || !m->numverts) + { + conoutf(CON_WARN, "empty mesh in %s", filename); + meshes.removeobj(m); + delete m; + } + } + } + + if(skel->shared <= 1) + { + skel->linkchildren(); + loopv(basejoints) + { + boneinfo &b = skel->bones[i]; + b.base = dualquat(basejoints[i].orient, basejoints[i].pos); + (b.invbase = b.base).invert(); + } + } + + loopv(meshes) + { + md5mesh &m = *(md5mesh *)meshes[i]; + m.buildverts(basejoints); + if(smooth <= 1) m.smoothnorms(smooth); + else m.buildnorms(); + m.cleanup(); + } + + sortblendcombos(); + + delete f; + return true; + } + + skelanimspec *loadanim(const char *filename) + { + skelanimspec *sa = skel->findskelanim(filename); + if(sa) return sa; + + stream *f = openfile(filename, "r"); + if(!f) return NULL; + + vector hierarchy; + vector basejoints; + int animdatalen = 0, animframes = 0; + float *animdata = NULL; + dualquat *animbones = NULL; + char buf[512]; + while(f->getline(buf, sizeof(buf))) + { + int tmp; + if(sscanf(buf, " MD5Version %d", &tmp)==1) + { + if(tmp!=10) { delete f; return NULL; } + } + else if(sscanf(buf, " numJoints %d", &tmp)==1) + { + if(tmp!=skel->numbones) { delete f; return NULL; } + } + else if(sscanf(buf, " numFrames %d", &animframes)==1) + { + if(animframes<1) { delete f; return NULL; } + } + else if(sscanf(buf, " frameRate %d", &tmp)==1); + else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1) + { + if(animdatalen>0) animdata = new float[animdatalen]; + } + else if(strstr(buf, "bounds {")) + { + while(f->getline(buf, sizeof(buf)) && buf[0]!='}'); + } + else if(strstr(buf, "hierarchy {")) + { + while(f->getline(buf, sizeof(buf)) && buf[0]!='}') + { + md5hierarchy h; + if(sscanf(buf, " %100s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4) + hierarchy.add(h); + } + } + else if(strstr(buf, "baseframe {")) + { + while(f->getline(buf, sizeof(buf)) && buf[0]!='}') + { + md5joint j; + if(sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6) + { + j.pos.y = -j.pos.y; + j.orient.x = -j.orient.x; + j.orient.z = -j.orient.z; + j.orient.restorew(); + basejoints.add(j); + } + } + if(basejoints.length()!=skel->numbones) { delete f; if(animdata) delete[] animdata; return NULL; } + animbones = new dualquat[(skel->numframes+animframes)*skel->numbones]; + if(skel->framebones) + { + memcpy(animbones, skel->framebones, skel->numframes*skel->numbones*sizeof(dualquat)); + delete[] skel->framebones; + } + skel->framebones = animbones; + animbones += skel->numframes*skel->numbones; + + sa = &skel->addskelanim(filename); + sa->frame = skel->numframes; + sa->range = animframes; + + skel->numframes += animframes; + } + else if(sscanf(buf, " frame %d", &tmp)==1) + { + for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';) + { + for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next) + { + animdata[numdata] = strtod(src, &next); + if(next <= src) break; + } + } + dualquat *frame = &animbones[tmp*skel->numbones]; + loopv(basejoints) + { + md5hierarchy &h = hierarchy[i]; + md5joint j = basejoints[i]; + if(h.start < animdatalen && h.flags) + { + float *jdata = &animdata[h.start]; + if(h.flags&1) j.pos.x = *jdata++; + if(h.flags&2) j.pos.y = -*jdata++; + if(h.flags&4) j.pos.z = *jdata++; + if(h.flags&8) j.orient.x = -*jdata++; + if(h.flags&16) j.orient.y = *jdata++; + if(h.flags&32) j.orient.z = -*jdata++; + j.orient.restorew(); + } + frame[i] = dualquat(j.orient, j.pos); + if(adjustments.inrange(i)) adjustments[i].adjust(frame[i]); + frame[i].mul(skel->bones[i].invbase); + if(h.parent >= 0) frame[i].mul(skel->bones[h.parent].base, dualquat(frame[i])); + frame[i].fixantipodal(skel->framebones[i]); + } + } + } + + if(animdata) delete[] animdata; + delete f; + + return sa; + } + + bool load(const char *meshfile, float smooth) + { + name = newstring(meshfile); + + if(!loadmesh(meshfile, smooth)) return false; + + return true; + } + }; + + meshgroup *loadmeshes(const char *name, va_list args) + { + md5meshgroup *group = new md5meshgroup; + group->shareskeleton(va_arg(args, char *)); + if(!group->load(name, va_arg(args, double))) { delete group; return NULL; } + return group; + } + + bool loaddefaultparts() + { + skelpart &mdl = addpart(); + mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; + adjustments.setsize(0); + const char *fname = name + strlen(name); + do --fname; while(fname >= name && *fname!='/' && *fname!='\\'); + fname++; + defformatstring(meshname, "packages/models/%s/%s.md5mesh", name, fname); + mdl.meshes = sharemeshes(path(meshname), NULL, 2.0); + if(!mdl.meshes) return false; + mdl.initanimparts(); + mdl.initskins(); + defformatstring(animname, "packages/models/%s/%s.md5anim", name, fname); + ((md5meshgroup *)mdl.meshes)->loadanim(path(animname)); + return true; + } }; skelcommands md5commands; diff --git a/src/engine/menus.cpp b/src/engine/menus.cpp index f2d8e3b..805a79b 100644 --- a/src/engine/menus.cpp +++ b/src/engine/menus.cpp @@ -14,101 +14,101 @@ VAR(guitabnum, 1, 0, 0); struct menu : g3d_callback { - char *name, *header; - uint *contents, *init, *onclear; - bool showtab, keeptab; - int menutab; - - menu() : name(NULL), header(NULL), contents(NULL), init(NULL), onclear(NULL), showtab(true), keeptab(false), menutab(1) {} - - void gui(g3d_gui &g, bool firstpass) - { - cgui = &g; - guitabnum = menutab; - cgui->start(menustart, 0.03f, showtab ? &menutab : NULL); - if(showtab) cgui->tab(header ? header : name, GUI_TITLE_COLOR); - execute(contents); - cgui->end(); - cgui = NULL; - guitabnum = 0; - } - - virtual void clear() - { - if(onclear) { freecode(onclear); onclear = NULL; } - } + char *name, *header; + uint *contents, *init, *onclear; + bool showtab, keeptab; + int menutab; + + menu() : name(NULL), header(NULL), contents(NULL), init(NULL), onclear(NULL), showtab(true), keeptab(false), menutab(1) {} + + void gui(g3d_gui &g, bool firstpass) + { + cgui = &g; + guitabnum = menutab; + cgui->start(menustart, 0.03f, showtab ? &menutab : NULL); + if(showtab) cgui->tab(header ? header : name, GUI_TITLE_COLOR); + execute(contents); + cgui->end(); + cgui = NULL; + guitabnum = 0; + } + + virtual void clear() + { + if(onclear) { freecode(onclear); onclear = NULL; } + } }; struct delayedupdate { - enum - { - INT, - FLOAT, - STRING, - ACTION - } type; - ident *id; - union - { - int i; - float f; - char *s; - } val; - delayedupdate() : type(ACTION), id(NULL) { val.s = NULL; } - ~delayedupdate() { if(type == STRING || type == ACTION) DELETEA(val.s); } - - void schedule(const char *s) { type = ACTION; val.s = newstring(s); } - void schedule(ident *var, int i) { type = INT; id = var; val.i = i; } - void schedule(ident *var, float f) { type = FLOAT; id = var; val.f = f; } - void schedule(ident *var, char *s) { type = STRING; id = var; val.s = newstring(s); } - - int getint() const - { - switch(type) - { - case INT: return val.i; - case FLOAT: return int(val.f); - case STRING: return int(strtol(val.s, NULL, 0)); - default: return 0; - } - } - - float getfloat() const - { - switch(type) - { - case INT: return float(val.i); - case FLOAT: return val.f; - case STRING: return float(parsefloat(val.s)); - default: return 0; - } - } - - const char *getstring() const - { - switch(type) - { - case INT: return intstr(val.i); - case FLOAT: return intstr(int(floor(val.f))); - case STRING: return val.s; - default: return ""; - } - } - - void run() - { - if(type == ACTION) { if(val.s) execute(val.s); } - else if(id) switch(id->type) - { - case ID_VAR: setvarchecked(id, getint()); break; - case ID_FVAR: setfvarchecked(id, getfloat()); break; - case ID_SVAR: setsvarchecked(id, getstring()); break; - case ID_ALIAS: alias(id->name, getstring()); break; - } - } + enum + { + INT, + FLOAT, + STRING, + ACTION + } type; + ident *id; + union + { + int i; + float f; + char *s; + } val; + delayedupdate() : type(ACTION), id(NULL) { val.s = NULL; } + ~delayedupdate() { if(type == STRING || type == ACTION) DELETEA(val.s); } + + void schedule(const char *s) { type = ACTION; val.s = newstring(s); } + void schedule(ident *var, int i) { type = INT; id = var; val.i = i; } + void schedule(ident *var, float f) { type = FLOAT; id = var; val.f = f; } + void schedule(ident *var, char *s) { type = STRING; id = var; val.s = newstring(s); } + + int getint() const + { + switch(type) + { + case INT: return val.i; + case FLOAT: return int(val.f); + case STRING: return int(strtol(val.s, NULL, 0)); + default: return 0; + } + } + + float getfloat() const + { + switch(type) + { + case INT: return float(val.i); + case FLOAT: return val.f; + case STRING: return float(parsefloat(val.s)); + default: return 0; + } + } + + const char *getstring() const + { + switch(type) + { + case INT: return intstr(val.i); + case FLOAT: return intstr(int(floor(val.f))); + case STRING: return val.s; + default: return ""; + } + } + + void run() + { + if(type == ACTION) { if(val.s) execute(val.s); } + else if(id) switch(id->type) + { + case ID_VAR: setvarchecked(id, getint()); break; + case ID_FVAR: setfvarchecked(id, getfloat()); break; + case ID_SVAR: setsvarchecked(id, getstring()); break; + case ID_ALIAS: alias(id->name, getstring()); break; + } + } }; - + static hashnameset guis; static vector guistack; static vector updatelater; @@ -118,375 +118,375 @@ VARP(menudistance, 16, 40, 256); VARP(menuautoclose, 32, 120, 4096); vec menuinfrontofplayer() -{ - vec dir; - vecfromyawpitch(camera1->yaw, 0, 1, 0, dir); - dir.mul(menudistance).add(camera1->o); - dir.z -= player->eyeheight-1; - return dir; +{ + vec dir; + vecfromyawpitch(camera1->yaw, 0, 1, 0, dir); + dir.mul(menudistance).add(camera1->o); + dir.z -= player->eyeheight-1; + return dir; } void popgui() { - menu *m = guistack.pop(); - m->clear(); + menu *m = guistack.pop(); + m->clear(); } void removegui(menu *m) { - loopv(guistack) if(guistack[i]==m) - { - guistack.remove(i); - m->clear(); - return; - } -} + loopv(guistack) if(guistack[i]==m) + { + guistack.remove(i); + m->clear(); + return; + } +} void pushgui(menu *m, int pos = -1) { - if(guistack.empty()) - { - menupos = menuinfrontofplayer(); - g3d_resetcursor(); - } - if(pos < 0) guistack.add(m); - else guistack.insert(pos, m); - if(pos < 0 || pos==guistack.length()-1) - { - if(!m->keeptab) m->menutab = 1; - menustart = totalmillis; - } - if(m->init) execute(m->init); + if(guistack.empty()) + { + menupos = menuinfrontofplayer(); + g3d_resetcursor(); + } + if(pos < 0) guistack.add(m); + else guistack.insert(pos, m); + if(pos < 0 || pos==guistack.length()-1) + { + if(!m->keeptab) m->menutab = 1; + menustart = totalmillis; + } + if(m->init) execute(m->init); } void restoregui(int pos) { - int clear = guistack.length()-pos-1; - loopi(clear) popgui(); - menustart = totalmillis; + int clear = guistack.length()-pos-1; + loopi(clear) popgui(); + menustart = totalmillis; } void showgui(const char *name) { - menu *m = guis.access(name); - if(!m) return; - int pos = guistack.find(m); - if(pos<0) pushgui(m); - else restoregui(pos); + menu *m = guis.access(name); + if(!m) return; + int pos = guistack.find(m); + if(pos<0) pushgui(m); + else restoregui(pos); } void hidegui(const char *name) { - menu *m = guis.access(name); - if(m) removegui(m); + menu *m = guis.access(name); + if(m) removegui(m); } - + int cleargui(int n) { - int clear = guistack.length(); - if(mainmenu && !isconnected(true) && clear > 0 && guistack[0]->name && !strcmp(guistack[0]->name, "main")) - { - clear--; - if(!clear) return 1; - } - if(n>0) clear = min(clear, n); - loopi(clear) popgui(); - if(!guistack.empty()) restoregui(guistack.length()-1); - return clear; + int clear = guistack.length(); + if(mainmenu && !isconnected(true) && clear > 0 && guistack[0]->name && !strcmp(guistack[0]->name, "main")) + { + clear--; + if(!clear) return 1; + } + if(n>0) clear = min(clear, n); + loopi(clear) popgui(); + if(!guistack.empty()) restoregui(guistack.length()-1); + return clear; } void clearguis(int level = -1) { - if(level < 0) level = guistack.length(); - loopvrev(guistack) - { - menu *m = guistack[i]; - if(m->onclear) - { - uint *action = m->onclear; - m->onclear = NULL; - execute(action); - freecode(action); - } - } - cleargui(level); + if(level < 0) level = guistack.length(); + loopvrev(guistack) + { + menu *m = guistack[i]; + if(m->onclear) + { + uint *action = m->onclear; + m->onclear = NULL; + execute(action); + freecode(action); + } + } + cleargui(level); } void guionclear(char *action) { - if(guistack.empty()) return; - menu *m = guistack.last(); - if(m->onclear) { freecode(m->onclear); m->onclear = NULL; } - if(action[0]) m->onclear = compilecode(action); + if(guistack.empty()) return; + menu *m = guistack.last(); + if(m->onclear) { freecode(m->onclear); m->onclear = NULL; } + if(action[0]) m->onclear = compilecode(action); } void guistayopen(uint *contents) { - bool oldclearmenu = shouldclearmenu; - shouldclearmenu = false; - execute(contents); - shouldclearmenu = oldclearmenu; + bool oldclearmenu = shouldclearmenu; + shouldclearmenu = false; + execute(contents); + shouldclearmenu = oldclearmenu; } void guinoautotab(uint *contents) { - if(!cgui) return; - bool oldval = cgui->allowautotab(false); - execute(contents); - cgui->allowautotab(oldval); + if(!cgui) return; + bool oldval = cgui->allowautotab(false); + execute(contents); + cgui->allowautotab(oldval); } void guimerge(uint *contents) { - if(!cgui) return; - bool oldval = cgui->mergehits(true); - execute(contents); - cgui->mergehits(oldval); + if(!cgui) return; + bool oldval = cgui->mergehits(true); + execute(contents); + cgui->mergehits(oldval); } //@DOC name and icon are optional void guibutton(char *name, char *action, char *icon) { - if(!cgui) return; - bool hideicon = !strcmp(icon, "0"); - int ret = cgui->button(name, GUI_BUTTON_COLOR, hideicon ? NULL : (icon[0] ? icon : (strstr(action, "showgui") ? "menu" : "action"))); - if(ret&G3D_UP) - { - updatelater.add().schedule(action[0] ? action : name); - if(shouldclearmenu) clearlater = true; - } - else if(ret&G3D_ROLLOVER) - { - alias("guirollovername", name); - alias("guirolloveraction", action); - } + if(!cgui) return; + bool hideicon = !strcmp(icon, "0"); + int ret = cgui->button(name, GUI_BUTTON_COLOR, hideicon ? NULL : (icon[0] ? icon : (strstr(action, "showgui") ? "menu" : "action"))); + if(ret&G3D_UP) + { + updatelater.add().schedule(action[0] ? action : name); + if(shouldclearmenu) clearlater = true; + } + else if(ret&G3D_ROLLOVER) + { + alias("guirollovername", name); + alias("guirolloveraction", action); + } } void guiimage(char *path, char *action, float *scale, int *overlaid, char *alt, char *title) { - if(!cgui) return; - Texture *t = textureload(path, 0, true, false); - if(t==notexture) - { - if(alt[0]) t = textureload(alt, 0, true, false); - if(t==notexture) return; - } - int ret = cgui->image(t, *scale, *overlaid!=0 ? title : NULL); - if(ret&G3D_UP) - { - if(*action) - { - updatelater.add().schedule(action); - if(shouldclearmenu) clearlater = true; - } - } - else if(ret&G3D_ROLLOVER) - { - alias("guirolloverimgpath", path); - alias("guirolloverimgaction", action); - } + if(!cgui) return; + Texture *t = textureload(path, 0, true, false); + if(t==notexture) + { + if(alt[0]) t = textureload(alt, 0, true, false); + if(t==notexture) return; + } + int ret = cgui->image(t, *scale, *overlaid!=0 ? title : NULL); + if(ret&G3D_UP) + { + if(*action) + { + updatelater.add().schedule(action); + if(shouldclearmenu) clearlater = true; + } + } + else if(ret&G3D_ROLLOVER) + { + alias("guirolloverimgpath", path); + alias("guirolloverimgaction", action); + } } void guicolor(int *color) { - if(cgui) - { - defformatstring(desc, "0x%06X", *color); - cgui->text(desc, *color, NULL); - } + if(cgui) + { + defformatstring(desc, "0x%06X", *color); + cgui->text(desc, *color, NULL); + } } void guitextbox(char *text, int *width, int *height, int *color) { - if(cgui && text[0]) cgui->textbox(text, *width ? *width : 12, *height ? *height : 1, *color ? *color : 0xFFFFFF); + if(cgui && text[0]) cgui->textbox(text, *width ? *width : 12, *height ? *height : 1, *color ? *color : 0xFFFFFF); } void guitext(char *name, char *icon) { - bool hideicon = !strcmp(icon, "0"); - if(cgui) cgui->text(name, !hideicon && icon[0] ? GUI_BUTTON_COLOR : GUI_TEXT_COLOR, hideicon ? NULL : (icon[0] ? icon : "info")); + bool hideicon = !strcmp(icon, "0"); + if(cgui) cgui->text(name, !hideicon && icon[0] ? GUI_BUTTON_COLOR : GUI_TEXT_COLOR, hideicon ? NULL : (icon[0] ? icon : "info")); } void guititle(char *name) { - if(cgui) cgui->title(name, GUI_TITLE_COLOR); + if(cgui) cgui->title(name, GUI_TITLE_COLOR); } void guitab(char *name) { - if(cgui) cgui->tab(name, GUI_TITLE_COLOR); + if(cgui) cgui->tab(name, GUI_TITLE_COLOR); } void guibar() { - if(cgui) cgui->separator(); + if(cgui) cgui->separator(); } void guistrut(float *strut, int *alt) { - if(cgui) - { - if(*alt) cgui->strut(*strut); else cgui->space(*strut); - } + if(cgui) + { + if(*alt) cgui->strut(*strut); else cgui->space(*strut); + } } void guispring(int *weight) { - if(cgui) cgui->spring(max(*weight, 1)); + if(cgui) cgui->spring(max(*weight, 1)); } void guicolumn(int *col) { - if(cgui) cgui->column(*col); + if(cgui) cgui->column(*col); } template static void updateval(char *var, T val, char *onchange) { - ident *id = writeident(var); - updatelater.add().schedule(id, val); - if(onchange[0]) updatelater.add().schedule(onchange); + ident *id = writeident(var); + updatelater.add().schedule(id, val); + if(onchange[0]) updatelater.add().schedule(onchange); } static int getval(char *var) { - ident *id = readident(var); - if(!id) return 0; - switch(id->type) - { - case ID_VAR: return *id->storage.i; - case ID_FVAR: return int(*id->storage.f); - case ID_SVAR: return parseint(*id->storage.s); - case ID_ALIAS: return id->getint(); - default: return 0; - } + ident *id = readident(var); + if(!id) return 0; + switch(id->type) + { + case ID_VAR: return *id->storage.i; + case ID_FVAR: return int(*id->storage.f); + case ID_SVAR: return parseint(*id->storage.s); + case ID_ALIAS: return id->getint(); + default: return 0; + } } static float getfval(char *var) { - ident *id = readident(var); - if(!id) return 0; - switch(id->type) - { - case ID_VAR: return *id->storage.i; - case ID_FVAR: return *id->storage.f; - case ID_SVAR: return parsefloat(*id->storage.s); - case ID_ALIAS: return id->getfloat(); - default: return 0; - } + ident *id = readident(var); + if(!id) return 0; + switch(id->type) + { + case ID_VAR: return *id->storage.i; + case ID_FVAR: return *id->storage.f; + case ID_SVAR: return parsefloat(*id->storage.s); + case ID_ALIAS: return id->getfloat(); + default: return 0; + } } static const char *getsval(char *var) { - ident *id = readident(var); - if(!id) return ""; - switch(id->type) - { - case ID_VAR: return intstr(*id->storage.i); - case ID_FVAR: return floatstr(*id->storage.f); - case ID_SVAR: return *id->storage.s; - case ID_ALIAS: return id->getstr(); - default: return ""; - } + ident *id = readident(var); + if(!id) return ""; + switch(id->type) + { + case ID_VAR: return intstr(*id->storage.i); + case ID_FVAR: return floatstr(*id->storage.f); + case ID_SVAR: return *id->storage.s; + case ID_ALIAS: return id->getstr(); + default: return ""; + } } void guislider(char *var, int *min, int *max, char *onchange) { if(!cgui) return; - int oldval = getval(var), val = oldval, vmin = *max > INT_MIN ? *min : getvarmin(var), vmax = *max > INT_MIN ? *max : getvarmax(var); - cgui->slider(val, vmin, vmax, GUI_TITLE_COLOR); - if(val != oldval) updateval(var, val, onchange); + int oldval = getval(var), val = oldval, vmin = *max > INT_MIN ? *min : getvarmin(var), vmax = *max > INT_MIN ? *max : getvarmax(var); + cgui->slider(val, vmin, vmax, GUI_TITLE_COLOR); + if(val != oldval) updateval(var, val, onchange); } void guilistslider(char *var, char *list, char *onchange) { - if(!cgui) return; - vector vals; - list += strspn(list, "\n\t "); - while(*list) - { - vals.add(parseint(list)); - list += strcspn(list, "\n\t \0"); - list += strspn(list, "\n\t "); - } - if(vals.empty()) return; - int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset; - loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; } - cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, intstr(val)); - if(offset != oldoffset) updateval(var, vals[offset], onchange); + if(!cgui) return; + vector vals; + list += strspn(list, "\n\t "); + while(*list) + { + vals.add(parseint(list)); + list += strcspn(list, "\n\t \0"); + list += strspn(list, "\n\t "); + } + if(vals.empty()) return; + int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset; + loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; } + cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, intstr(val)); + if(offset != oldoffset) updateval(var, vals[offset], onchange); } void guinameslider(char *var, char *names, char *list, char *onchange) { - if(!cgui) return; - vector vals; - list += strspn(list, "\n\t "); - while(*list) - { - vals.add(parseint(list)); - list += strcspn(list, "\n\t \0"); - list += strspn(list, "\n\t "); - } - if(vals.empty()) return; - int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset; - loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; } - char *label = indexlist(names, offset); - cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, label); - if(offset != oldoffset) updateval(var, vals[offset], onchange); - delete[] label; + if(!cgui) return; + vector vals; + list += strspn(list, "\n\t "); + while(*list) + { + vals.add(parseint(list)); + list += strcspn(list, "\n\t \0"); + list += strspn(list, "\n\t "); + } + if(vals.empty()) return; + int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset; + loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; } + char *label = indexlist(names, offset); + cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, label); + if(offset != oldoffset) updateval(var, vals[offset], onchange); + delete[] label; } void guicheckbox(char *name, char *var, float *on, float *off, char *onchange) { - bool enabled = getfval(var)!=*off; - if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP) - { - updateval(var, enabled ? *off : (*on || *off ? *on : 1.0f), onchange); - } + bool enabled = getfval(var)!=*off; + if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP) + { + updateval(var, enabled ? *off : (*on || *off ? *on : 1.0f), onchange); + } } void guiradio(char *name, char *var, float *n, char *onchange) { - bool enabled = getfval(var)==*n; - if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "radio_on" : "radio_off")&G3D_UP) - { - if(!enabled) updateval(var, *n, onchange); - } + bool enabled = getfval(var)==*n; + if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "radio_on" : "radio_off")&G3D_UP) + { + if(!enabled) updateval(var, *n, onchange); + } } void guibitfield(char *name, char *var, int *mask, char *onchange) { - int val = getval(var); - bool enabled = (val & *mask) != 0; - if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP) - { - updateval(var, enabled ? val & ~*mask : val | *mask, onchange); - } + int val = getval(var); + bool enabled = (val & *mask) != 0; + if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP) + { + updateval(var, enabled ? val & ~*mask : val | *mask, onchange); + } } //-ve length indicates a wrapped text field of any (approx 260 chars) length, |length| is the field width void guifield(char *var, int *maxlength, char *onchange) -{ - if(!cgui) return; - const char *initval = getsval(var); +{ + if(!cgui) return; + const char *initval = getsval(var); char *result = cgui->field(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : 12, 0, initval); - if(result) updateval(var, result, onchange); + if(result) updateval(var, result, onchange); } //-ve maxlength indicates a wrapped text field of any (approx 260 chars) length, |maxlength| is the field width void guieditor(char *name, int *maxlength, int *height, int *mode) { - if(!cgui) return; - cgui->field(name, GUI_BUTTON_COLOR, *maxlength ? *maxlength : 12, *height, NULL, *mode<=0 ? EDITORFOREVER : *mode); - //returns a non-NULL pointer (the currentline) when the user commits, could then manipulate via text* commands + if(!cgui) return; + cgui->field(name, GUI_BUTTON_COLOR, *maxlength ? *maxlength : 12, *height, NULL, *mode<=0 ? EDITORFOREVER : *mode); + //returns a non-NULL pointer (the currentline) when the user commits, could then manipulate via text* commands } //-ve length indicates a wrapped text field of any (approx 260 chars) length, |length| is the field width void guikeyfield(char *var, int *maxlength, char *onchange) { - if(!cgui) return; - const char *initval = getsval(var); - char *result = cgui->keyfield(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : -8, 0, initval); - if(result) updateval(var, result, onchange); + if(!cgui) return; + const char *initval = getsval(var); + char *result = cgui->keyfield(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : -8, 0, initval); + if(result) updateval(var, result, onchange); } //use text to do more... @@ -494,87 +494,87 @@ void guikeyfield(char *var, int *maxlength, char *onchange) void guilist(uint *contents) { - if(!cgui) return; - cgui->pushlist(); - execute(contents); - cgui->poplist(); + if(!cgui) return; + cgui->pushlist(); + execute(contents); + cgui->poplist(); } void guialign(int *align, uint *contents) { - if(!cgui) return; - cgui->pushlist(); - if(*align >= 0) cgui->spring(); - execute(contents); - if(*align == 0) cgui->spring(); - cgui->poplist(); + if(!cgui) return; + cgui->pushlist(); + if(*align >= 0) cgui->spring(); + execute(contents); + if(*align == 0) cgui->spring(); + cgui->poplist(); } void newgui(char *name, char *contents, char *header, char *init) { - menu *m = guis.access(name); - if(!m) - { - name = newstring(name); - m = &guis[name]; - m->name = name; - } - else - { - DELETEA(m->header); - freecode(m->contents); - freecode(m->init); - } - if(header && header[0]) - { - char *end = NULL; - int val = strtol(header, &end, 0); - if(end && !*end) - { - m->header = NULL; - m->showtab = val != 0; - } - else - { - m->header = newstring(header); - m->showtab = true; - } - } - else - { - m->header = NULL; - m->showtab = true; - } - m->contents = compilecode(contents); - m->init = init && init[0] ? compilecode(init) : NULL; + menu *m = guis.access(name); + if(!m) + { + name = newstring(name); + m = &guis[name]; + m->name = name; + } + else + { + DELETEA(m->header); + freecode(m->contents); + freecode(m->init); + } + if(header && header[0]) + { + char *end = NULL; + int val = strtol(header, &end, 0); + if(end && !*end) + { + m->header = NULL; + m->showtab = val != 0; + } + else + { + m->header = newstring(header); + m->showtab = true; + } + } + else + { + m->header = NULL; + m->showtab = true; + } + m->contents = compilecode(contents); + m->init = init && init[0] ? compilecode(init) : NULL; } menu *guiserversmenu = NULL; void guiservers(uint *header, int *pagemin, int *pagemax) { - extern const char *showservers(g3d_gui *cgui, uint *header, int pagemin, int pagemax); - if(cgui) - { - const char *command = showservers(cgui, header, *pagemin, *pagemax > 0 ? *pagemax : INT_MAX); - if(command) - { - updatelater.add().schedule(command); - if(shouldclearmenu) clearlater = true; - guiserversmenu = clearlater || guistack.empty() ? NULL : guistack.last(); - } - } + extern const char *showservers(g3d_gui *cgui, uint *header, int pagemin, int pagemax); + if(cgui) + { + const char *command = showservers(cgui, header, *pagemin, *pagemax > 0 ? *pagemax : INT_MAX); + if(command) + { + updatelater.add().schedule(command); + if(shouldclearmenu) clearlater = true; + guiserversmenu = clearlater || guistack.empty() ? NULL : guistack.last(); + } + } } void notifywelcome() { - if(guiserversmenu) - { - if(guistack.length() && guistack.last() == guiserversmenu) clearguis(); - guiserversmenu = NULL; - } + if(guiserversmenu) + { + if(guistack.length() && guistack.last() == guiserversmenu) clearguis(); + guiserversmenu = NULL; + } } - + COMMAND(newgui, "ssss"); COMMAND(guibutton, "sss"); COMMAND(guitext, "ss"); @@ -610,113 +610,113 @@ COMMAND(guitextbox, "siii"); void guiplayerpreview(int *model, int *team, int *weap, char *action, float *scale, int *overlaid, char *title) { - if(!cgui) return; - int ret = cgui->playerpreview(*model, *team, *weap, *scale, *overlaid!=0 ? title : NULL); - if(ret&G3D_UP) - { - if(*action) - { - updatelater.add().schedule(action); - if(shouldclearmenu) clearlater = true; - } - } + if(!cgui) return; + int ret = cgui->playerpreview(*model, *team, *weap, *scale, *overlaid!=0 ? title : NULL); + if(ret&G3D_UP) + { + if(*action) + { + updatelater.add().schedule(action); + if(shouldclearmenu) clearlater = true; + } + } } COMMAND(guiplayerpreview, "iiisfis"); void guimodelpreview(char *model, char *animspec, char *action, float *scale, int *overlaid, char *title, int *throttle) { - if(!cgui) return; - int anim = ANIM_ALL; - if(animspec[0]) - { - if(isdigit(animspec[0])) - { - anim = parseint(animspec); - if(anim >= 0) anim %= ANIM_INDEX; - else anim = ANIM_ALL; - } - else - { - vector anims; - findanims(animspec, anims); - if(anims.length()) anim = anims[0]; - } - } - int ret = cgui->modelpreview(model, anim|ANIM_LOOP, *scale, *overlaid!=0 ? title : NULL, *throttle!=0); - if(ret&G3D_UP) - { - if(*action) - { - updatelater.add().schedule(action); - if(shouldclearmenu) clearlater = true; - } - } - else if(ret&G3D_ROLLOVER) - { - alias("guirolloverpreviewname", model); - alias("guirolloverpreviewaction", action); - } + if(!cgui) return; + int anim = ANIM_ALL; + if(animspec[0]) + { + if(isdigit(animspec[0])) + { + anim = parseint(animspec); + if(anim >= 0) anim %= ANIM_INDEX; + else anim = ANIM_ALL; + } + else + { + vector anims; + findanims(animspec, anims); + if(anims.length()) anim = anims[0]; + } + } + int ret = cgui->modelpreview(model, anim|ANIM_LOOP, *scale, *overlaid!=0 ? title : NULL, *throttle!=0); + if(ret&G3D_UP) + { + if(*action) + { + updatelater.add().schedule(action); + if(shouldclearmenu) clearlater = true; + } + } + else if(ret&G3D_ROLLOVER) + { + alias("guirolloverpreviewname", model); + alias("guirolloverpreviewaction", action); + } } COMMAND(guimodelpreview, "sssfisi"); void guiprefabpreview(char *prefab, int *color, char *action, float *scale, int *overlaid, char *title, int *throttle) { - if(!cgui) return; - int ret = cgui->prefabpreview(prefab, vec::hexcolor(*color), *scale, *overlaid!=0 ? title : NULL, *throttle!=0); - if(ret&G3D_UP) - { - if(*action) - { - updatelater.add().schedule(action); - if(shouldclearmenu) clearlater = true; - } - } - else if(ret&G3D_ROLLOVER) - { - alias("guirolloverpreviewname", prefab); - alias("guirolloverpreviewaction", action); - } + if(!cgui) return; + int ret = cgui->prefabpreview(prefab, vec::hexcolor(*color), *scale, *overlaid!=0 ? title : NULL, *throttle!=0); + if(ret&G3D_UP) + { + if(*action) + { + updatelater.add().schedule(action); + if(shouldclearmenu) clearlater = true; + } + } + else if(ret&G3D_ROLLOVER) + { + alias("guirolloverpreviewname", prefab); + alias("guirolloverpreviewaction", action); + } } COMMAND(guiprefabpreview, "sisfisi"); struct change { - int type; - const char *desc; + int type; + const char *desc; - change() {} - change(int type, const char *desc) : type(type), desc(desc) {} + change() {} + change(int type, const char *desc) : type(type), desc(desc) {} }; static vector needsapply; static struct applymenu : menu { - void gui(g3d_gui &g, bool firstpass) - { - if(guistack.empty()) return; - g.start(menustart, 0.03f); - g.text("the following settings have changed:", GUI_TEXT_COLOR, "info"); - loopv(needsapply) g.text(needsapply[i].desc, GUI_TEXT_COLOR, "info"); - g.separator(); - g.text("apply changes now?", GUI_TEXT_COLOR, "info"); - if(g.button("yes", GUI_BUTTON_COLOR, "action")&G3D_UP) - { - int changetypes = 0; - loopv(needsapply) changetypes |= needsapply[i].type; - if(changetypes&CHANGE_GFX) updatelater.add().schedule("resetgl"); - if(changetypes&CHANGE_SOUND) updatelater.add().schedule("resetsound"); - clearlater = true; - } - if(g.button("no", GUI_BUTTON_COLOR, "action")&G3D_UP) - clearlater = true; - g.end(); - } - - void clear() - { - menu::clear(); - needsapply.shrink(0); - } + void gui(g3d_gui &g, bool firstpass) + { + if(guistack.empty()) return; + g.start(menustart, 0.03f); + g.text("the following settings have changed:", GUI_TEXT_COLOR, "info"); + loopv(needsapply) g.text(needsapply[i].desc, GUI_TEXT_COLOR, "info"); + g.separator(); + g.text("apply changes now?", GUI_TEXT_COLOR, "info"); + if(g.button("yes", GUI_BUTTON_COLOR, "action")&G3D_UP) + { + int changetypes = 0; + loopv(needsapply) changetypes |= needsapply[i].type; + if(changetypes&CHANGE_GFX) updatelater.add().schedule("resetgl"); + if(changetypes&CHANGE_SOUND) updatelater.add().schedule("resetsound"); + clearlater = true; + } + if(g.button("no", GUI_BUTTON_COLOR, "action")&G3D_UP) + clearlater = true; + g.end(); + } + + void clear() + { + menu::clear(); + needsapply.shrink(0); + } } applymenu; VARP(applydialog, 0, 1, 1); @@ -725,59 +725,59 @@ static bool processingmenu = false; void addchange(const char *desc, int type) { - if(!applydialog) return; - loopv(needsapply) if(!strcmp(needsapply[i].desc, desc)) return; - needsapply.add(change(type, desc)); - if(needsapply.length() && guistack.find(&applymenu) < 0) - pushgui(&applymenu, processingmenu ? max(guistack.length()-1, 0) : -1); + if(!applydialog) return; + loopv(needsapply) if(!strcmp(needsapply[i].desc, desc)) return; + needsapply.add(change(type, desc)); + if(needsapply.length() && guistack.find(&applymenu) < 0) + pushgui(&applymenu, processingmenu ? max(guistack.length()-1, 0) : -1); } void clearchanges(int type) { - loopv(needsapply) - { - if(needsapply[i].type&type) - { - needsapply[i].type &= ~type; - if(!needsapply[i].type) needsapply.remove(i--); - } - } - if(needsapply.empty()) removegui(&applymenu); + loopv(needsapply) + { + if(needsapply[i].type&type) + { + needsapply[i].type &= ~type; + if(!needsapply[i].type) needsapply.remove(i--); + } + } + if(needsapply.empty()) removegui(&applymenu); } void menuprocess() { - processingmenu = true; - int wasmain = mainmenu, level = guistack.length(); - loopv(updatelater) updatelater[i].run(); - updatelater.shrink(0); - if(wasmain > mainmenu || clearlater) - { - if(wasmain > mainmenu || level==guistack.length()) clearguis(level); - clearlater = false; - } - if(mainmenu && !isconnected(true) && guistack.empty()) showgui("main"); - processingmenu = false; + processingmenu = true; + int wasmain = mainmenu, level = guistack.length(); + loopv(updatelater) updatelater[i].run(); + updatelater.shrink(0); + if(wasmain > mainmenu || clearlater) + { + if(wasmain > mainmenu || level==guistack.length()) clearguis(level); + clearlater = false; + } + if(mainmenu && !isconnected(true) && guistack.empty()) showgui("main"); + processingmenu = false; } VAR(mainmenu, 1, 1, 0); void clearmainmenu() { - if(mainmenu && isconnected()) - { - mainmenu = 0; - if(!processingmenu) cleargui(); - } + if(mainmenu && isconnected()) + { + mainmenu = 0; + if(!processingmenu) cleargui(); + } } void g3d_mainmenu() { - if(!guistack.empty()) - { - extern int usegui2d; - if(!mainmenu && !usegui2d && camera1->o.dist(menupos) > menuautoclose) cleargui(); - else g3d_addgui(guistack.last(), menupos, GUI_2D | GUI_FOLLOW); - } + if(!guistack.empty()) + { + extern int usegui2d; + if(!mainmenu && !usegui2d && camera1->o.dist(menupos) > menuautoclose) cleargui(); + else g3d_addgui(guistack.last(), menupos, GUI_2D | GUI_FOLLOW); + } } diff --git a/src/engine/model.h b/src/engine/model.h index d314d4b..de850ef 100644 --- a/src/engine/model.h +++ b/src/engine/model.h @@ -2,89 +2,89 @@ enum { MDL_MD3, MDL_MD5, MDL_IQM, NUMMODELTYPES }; struct model { - char *name; - float spinyaw, spinpitch, offsetyaw, offsetpitch; - bool collide, ellipsecollide, shadow, alphadepth, depthoffset; - float scale; - vec translate; - BIH *bih; - vec bbcenter, bbradius, bbextend, collidecenter, collideradius; - float rejectradius, eyeheight, collidexyradius, collideheight; - int batch; + char *name; + float spinyaw, spinpitch, offsetyaw, offsetpitch; + bool collide, ellipsecollide, shadow, alphadepth, depthoffset; + float scale; + vec translate; + BIH *bih; + vec bbcenter, bbradius, bbextend, collidecenter, collideradius; + float rejectradius, eyeheight, collidexyradius, collideheight; + int batch; - model(const char *name) : name(name ? newstring(name) : NULL), spinyaw(0), spinpitch(0), offsetyaw(0), offsetpitch(0), collide(true), ellipsecollide(false), shadow(true), alphadepth(true), depthoffset(false), scale(1.0f), translate(0, 0, 0), bih(0), bbcenter(0, 0, 0), bbradius(-1, -1, -1), bbextend(0, 0, 0), collidecenter(0, 0, 0), collideradius(-1, -1, -1), rejectradius(-1), eyeheight(0.9f), collidexyradius(0), collideheight(0), batch(-1) {} - virtual ~model() { DELETEA(name); DELETEP(bih); } - virtual void calcbb(vec ¢er, vec &radius) = 0; - virtual void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, dynent *d, modelattach *a = NULL, const vec &color = vec(0, 0, 0), const vec &dir = vec(0, 0, 0), float transparent = 1) = 0; - virtual bool load() = 0; - virtual int type() const = 0; - virtual BIH *setBIH() { return 0; } - virtual bool envmapped() { return false; } - virtual bool skeletal() const { return false; } + model(const char *name) : name(name ? newstring(name) : NULL), spinyaw(0), spinpitch(0), offsetyaw(0), offsetpitch(0), collide(true), ellipsecollide(false), shadow(true), alphadepth(true), depthoffset(false), scale(1.0f), translate(0, 0, 0), bih(0), bbcenter(0, 0, 0), bbradius(-1, -1, -1), bbextend(0, 0, 0), collidecenter(0, 0, 0), collideradius(-1, -1, -1), rejectradius(-1), eyeheight(0.9f), collidexyradius(0), collideheight(0), batch(-1) {} + virtual ~model() { DELETEA(name); DELETEP(bih); } + virtual void calcbb(vec ¢er, vec &radius) = 0; + virtual void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, dynent *d, modelattach *a = NULL, const vec &color = vec(0, 0, 0), const vec &dir = vec(0, 0, 0), float transparent = 1) = 0; + virtual bool load() = 0; + virtual int type() const = 0; + virtual BIH *setBIH() { return 0; } + virtual bool envmapped() { return false; } + virtual bool skeletal() const { return false; } - virtual void setshader(Shader *shader) {} - virtual void setenvmap(float envmapmin, float envmapmax, Texture *envmap) {} - virtual void setspec(float spec) {} - virtual void setambient(float ambient) {} - virtual void setglow(float glow, float glowdelta, float glowpulse) {} - virtual void setglare(float specglare, float glowglare) {} - virtual void setalphatest(float alpha) {} - virtual void setalphablend(bool blend) {} - virtual void setfullbright(float fullbright) {} - virtual void setcullface(bool cullface) {} + virtual void setshader(Shader *shader) {} + virtual void setenvmap(float envmapmin, float envmapmax, Texture *envmap) {} + virtual void setspec(float spec) {} + virtual void setambient(float ambient) {} + virtual void setglow(float glow, float glowdelta, float glowpulse) {} + virtual void setglare(float specglare, float glowglare) {} + virtual void setalphatest(float alpha) {} + virtual void setalphablend(bool blend) {} + virtual void setfullbright(float fullbright) {} + virtual void setcullface(bool cullface) {} - virtual void preloadBIH() { if(!bih) setBIH(); } - virtual void preloadshaders(bool force = false) {} - virtual void preloadmeshes() {} - virtual void cleanup() {} + virtual void preloadBIH() { if(!bih) setBIH(); } + virtual void preloadshaders(bool force = false) {} + virtual void preloadmeshes() {} + virtual void cleanup() {} - virtual void startrender() {} - virtual void endrender() {} + virtual void startrender() {} + virtual void endrender() {} - void boundbox(vec ¢er, vec &radius) - { - if(bbradius.x < 0) - { - calcbb(bbcenter, bbradius); - bbradius.add(bbextend); - } - center = bbcenter; - radius = bbradius; - } + void boundbox(vec ¢er, vec &radius) + { + if(bbradius.x < 0) + { + calcbb(bbcenter, bbradius); + bbradius.add(bbextend); + } + center = bbcenter; + radius = bbradius; + } - float collisionbox(vec ¢er, vec &radius) - { - if(collideradius.x < 0) - { - boundbox(collidecenter, collideradius); - if(collidexyradius) - { - collidecenter.x = collidecenter.y = 0; - collideradius.x = collideradius.y = collidexyradius; - } - if(collideheight) - { - collidecenter.z = collideradius.z = collideheight/2; - } - rejectradius = vec(collidecenter).abs().add(collideradius).magnitude(); - } - center = collidecenter; - radius = collideradius; - return rejectradius; - } + float collisionbox(vec ¢er, vec &radius) + { + if(collideradius.x < 0) + { + boundbox(collidecenter, collideradius); + if(collidexyradius) + { + collidecenter.x = collidecenter.y = 0; + collideradius.x = collideradius.y = collidexyradius; + } + if(collideheight) + { + collidecenter.z = collideradius.z = collideheight/2; + } + rejectradius = vec(collidecenter).abs().add(collideradius).magnitude(); + } + center = collidecenter; + radius = collideradius; + return rejectradius; + } - float boundsphere(vec ¢er) - { - vec radius; - boundbox(center, radius); - return radius.magnitude(); - } + float boundsphere(vec ¢er) + { + vec radius; + boundbox(center, radius); + return radius.magnitude(); + } - float above() - { - vec center, radius; - boundbox(center, radius); - return center.z+radius.z; - } + float above() + { + vec center, radius; + boundbox(center, radius); + return center.z+radius.z; + } }; diff --git a/src/engine/mpr.h b/src/engine/mpr.h index b4cfb59..6288e4f 100644 --- a/src/engine/mpr.h +++ b/src/engine/mpr.h @@ -2,574 +2,574 @@ namespace mpr { - struct CubePlanes - { - const clipplanes &p; - - CubePlanes(const clipplanes &p) : p(p) {} - - vec center() const { return p.o; } - - vec supportpoint(const vec &n) const - { - int besti = 7; - float bestd = n.dot(p.v[7]); - loopi(7) - { - float d = n.dot(p.v[i]); - if(d > bestd) { besti = i; bestd = d; } - } - return p.v[besti]; - } - }; - - struct SolidCube - { - vec o; - int size; - - SolidCube(float x, float y, float z, int size) : o(x, y, z), size(size) {} - SolidCube(const vec &o, int size) : o(o), size(size) {} - SolidCube(const ivec &o, int size) : o(o), size(size) {} - - vec center() const { return vec(o).add(size/2); } - - vec supportpoint(const vec &n) const - { - vec p(o); - if(n.x > 0) p.x += size; - if(n.y > 0) p.y += size; - if(n.z > 0) p.z += size; - return p; - } - }; - - struct Ent - { - physent *ent; - - Ent(physent *ent) : ent(ent) {} - - vec center() const { return vec(ent->o.x, ent->o.y, ent->o.z + (ent->aboveeye - ent->eyeheight)/2); } - }; - - struct EntOBB : Ent - { - matrix3 orient; - float zmargin; - - EntOBB(physent *ent, float zmargin = 0) : Ent(ent), zmargin(zmargin) - { - orient.setyaw(ent->yaw*RAD); - } - - vec center() const { return vec(ent->o.x, ent->o.y, ent->o.z + (ent->aboveeye - ent->eyeheight - zmargin)/2); } - - vec contactface(const vec &wn, const vec &wdir) const - { - vec n = orient.transform(wn).div(vec(ent->xradius, ent->yradius, (ent->aboveeye + ent->eyeheight + zmargin)/2)), - dir = orient.transform(wdir), - an(fabs(n.x), fabs(n.y), dir.z ? fabs(n.z) : 0), - fn(0, 0, 0); - if(an.x > an.y) - { - if(an.x > an.z) fn.x = n.x*dir.x < 0 ? (n.x > 0 ? 1 : -1) : 0; - else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; - } - else if(an.y > an.z) fn.y = n.y*dir.y < 0 ? (n.y > 0 ? 1 : -1) : 0; - else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; - return orient.transposedtransform(fn); - } - - vec localsupportpoint(const vec &ln) const - { - return vec(ln.x > 0 ? ent->xradius : -ent->xradius, - ln.y > 0 ? ent->yradius : -ent->yradius, - ln.z > 0 ? ent->aboveeye : -ent->eyeheight - zmargin); - } - - vec supportpoint(const vec &n) const - { - return orient.transposedtransform(localsupportpoint(orient.transform(n))).add(ent->o); - } - - float supportcoordneg(float a, float b, float c) const - { - return localsupportpoint(vec(-a, -b, -c)).dot(vec(a, b, c)); - } - float supportcoord(float a, float b, float c) const - { - return localsupportpoint(vec(a, b, c)).dot(vec(a, b, c)); - } - - float left() const { return supportcoordneg(orient.a.x, orient.b.x, orient.c.x) + ent->o.x; } - float right() const { return supportcoord(orient.a.x, orient.b.x, orient.c.x) + ent->o.x; } - float back() const { return supportcoordneg(orient.a.y, orient.b.y, orient.c.y) + ent->o.y; } - float front() const { return supportcoord(orient.a.y, orient.b.y, orient.c.y) + ent->o.y; } - float bottom() const { return ent->o.z - ent->eyeheight - zmargin; } - float top() const { return ent->o.z + ent->aboveeye; } - }; - - struct EntFuzzy : Ent - { - EntFuzzy(physent *ent) : Ent(ent) {} - - float left() const { return ent->o.x - ent->radius; } - float right() const { return ent->o.x + ent->radius; } - float back() const { return ent->o.y - ent->radius; } - float front() const { return ent->o.y + ent->radius; } - float bottom() const { return ent->o.z - ent->eyeheight; } - float top() const { return ent->o.z + ent->aboveeye; } - }; - - struct EntCylinder : EntFuzzy - { - float zmargin; - - EntCylinder(physent *ent, float zmargin = 0) : EntFuzzy(ent), zmargin(zmargin) {} - - vec center() const { return vec(ent->o.x, ent->o.y, ent->o.z + (ent->aboveeye - ent->eyeheight - zmargin)/2); } - - float bottom() const { return ent->o.z - ent->eyeheight - zmargin; } - - vec contactface(const vec &n, const vec &dir) const - { - float dxy = n.dot2(n)/(ent->radius*ent->radius), dz = n.z*n.z*4/(ent->aboveeye + ent->eyeheight + zmargin); - vec fn(0, 0, 0); - if(dz > dxy && dir.z) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; - else if(n.dot2(dir) < 0) - { - fn.x = n.x; - fn.y = n.y; - fn.normalize(); - } - return fn; - } - - vec supportpoint(const vec &n) const - { - vec p(ent->o); - if(n.z > 0) p.z += ent->aboveeye; - else p.z -= ent->eyeheight + zmargin; - if(n.x || n.y) - { - float r = ent->radius / n.magnitude2(); - p.x += n.x*r; - p.y += n.y*r; - } - return p; - } - }; - - struct EntCapsule : EntFuzzy - { - EntCapsule(physent *ent) : EntFuzzy(ent) {} - - vec supportpoint(const vec &n) const - { - vec p(ent->o); - if(n.z > 0) p.z += ent->aboveeye - ent->radius; - else p.z -= ent->eyeheight - ent->radius; - p.add(vec(n).mul(ent->radius / n.magnitude())); - return p; - } - }; - - struct EntEllipsoid : EntFuzzy - { - EntEllipsoid(physent *ent) : EntFuzzy(ent) {} - - vec supportpoint(const vec &dir) const - { - vec p(ent->o), n = vec(dir).normalize(); - p.x += ent->radius*n.x; - p.y += ent->radius*n.y; - p.z += (ent->aboveeye + ent->eyeheight)/2*(1 + n.z) - ent->eyeheight; - return p; - } - }; - - struct Model - { - vec o, radius; - matrix3 orient; - - Model(const vec &ent, const vec ¢er, const vec &radius, int yaw) : o(ent), radius(radius) - { - orient.setyaw(yaw*RAD); - o.add(orient.transposedtransform(center)); - } - - vec center() const { return o; } - }; - - struct ModelOBB : Model - { - ModelOBB(const vec &ent, const vec ¢er, const vec &radius, int yaw) : - Model(ent, center, radius, yaw) - {} - - vec contactface(const vec &wn, const vec &wdir) const - { - vec n = orient.transform(wn).div(radius), dir = orient.transform(wdir), - an(fabs(n.x), fabs(n.y), dir.z ? fabs(n.z) : 0), - fn(0, 0, 0); - if(an.x > an.y) - { - if(an.x > an.z) fn.x = n.x*dir.x < 0 ? (n.x > 0 ? 1 : -1) : 0; - else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; - } - else if(an.y > an.z) fn.y = n.y*dir.y < 0 ? (n.y > 0 ? 1 : -1) : 0; - else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; - return orient.transposedtransform(fn); - } - - vec supportpoint(const vec &n) const - { - vec ln = orient.transform(n), p(0, 0, 0); - if(ln.x > 0) p.x += radius.x; - else p.x -= radius.x; - if(ln.y > 0) p.y += radius.y; - else p.y -= radius.y; - if(ln.z > 0) p.z += radius.z; - else p.z -= radius.z; - return orient.transposedtransform(p).add(o); - } - }; - - struct ModelEllipse : Model - { - ModelEllipse(const vec &ent, const vec ¢er, const vec &radius, int yaw) : - Model(ent, center, radius, yaw) - {} - - vec contactface(const vec &wn, const vec &wdir) const - { - vec n = orient.transform(wn).div(radius), dir = orient.transform(wdir); - float dxy = n.dot2(n), dz = n.z*n.z; - vec fn(0, 0, 0); - if(dz > dxy && dir.z) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; - else if(n.dot2(dir) < 0) - { - fn.x = n.x*radius.y; - fn.y = n.y*radius.x; - fn.normalize(); - } - return orient.transposedtransform(fn); - } - - vec supportpoint(const vec &n) const - { - vec ln = orient.transform(n), p(0, 0, 0); - if(ln.z > 0) p.z += radius.z; - else p.z -= radius.z; - if(ln.x || ln.y) - { - float r = ln.magnitude2(); - p.x += ln.x*radius.x/r; - p.y += ln.y*radius.y/r; - } - return orient.transposedtransform(p).add(o); - } - }; - - const float boundarytolerance = 1e-3f; - - template - bool collide(const T &p1, const U &p2) - { - // v0 = center of Minkowski difference - vec v0 = p2.center().sub(p1.center()); - if(v0.iszero()) return true; // v0 and origin overlap ==> hit - - // v1 = support in direction of origin - vec n = vec(v0).neg(); - vec v1 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); - if(v1.dot(n) <= 0) return false; // origin outside v1 support plane ==> miss - - // v2 = support perpendicular to plane containing origin, v0 and v1 - n.cross(v1, v0); - if(n.iszero()) return true; // v0, v1 and origin colinear (and origin inside v1 support plane) == > hit - vec v2 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); - if(v2.dot(n) <= 0) return false; // origin outside v2 support plane ==> miss - - // v3 = support perpendicular to plane containing v0, v1 and v2 - n.cross(v0, v1, v2); - - // If the origin is on the - side of the plane, reverse the direction of the plane - if(n.dot(v0) > 0) - { - swap(v1, v2); - n.neg(); - } - - /// - // Phase One: Find a valid portal - - loopi(100) - { - // Obtain the next support point - vec v3 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); - if(v3.dot(n) <= 0) return false; // origin outside v3 support plane ==> miss - - // If origin is outside (v1,v0,v3), then portal is invalid -- eliminate v2 and find new support outside face - vec v3xv0; - v3xv0.cross(v3, v0); - if(v1.dot(v3xv0) < 0) - { - v2 = v3; - n.cross(v0, v1, v3); - continue; - } - - // If origin is outside (v3,v0,v2), then portal is invalid -- eliminate v1 and find new support outside face - if(v2.dot(v3xv0) > 0) - { - v1 = v3; - n.cross(v0, v3, v2); - continue; - } - - /// - // Phase Two: Refine the portal - - for(int j = 0;; j++) - { - // Compute outward facing normal of the portal - n.cross(v1, v2, v3); - - // If the origin is inside the portal, we have a hit - if(n.dot(v1) >= 0) return true; - - n.normalize(); - - // Find the support point in the direction of the portal's normal - vec v4 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); - - // If the origin is outside the support plane or the boundary is thin enough, we have a miss - if(v4.dot(n) <= 0 || vec(v4).sub(v3).dot(n) <= boundarytolerance || j > 100) return false; - - // Test origin against the three planes that separate the new portal candidates: (v1,v4,v0) (v2,v4,v0) (v3,v4,v0) - // Note: We're taking advantage of the triple product identities here as an optimization - // (v1 % v4) * v0 == v1 * (v4 % v0) > 0 if origin inside (v1, v4, v0) - // (v2 % v4) * v0 == v2 * (v4 % v0) > 0 if origin inside (v2, v4, v0) - // (v3 % v4) * v0 == v3 * (v4 % v0) > 0 if origin inside (v3, v4, v0) - vec v4xv0; - v4xv0.cross(v4, v0); - if(v1.dot(v4xv0) > 0) - { - if(v2.dot(v4xv0) > 0) v1 = v4; // Inside v1 & inside v2 ==> eliminate v1 - else v3 = v4; // Inside v1 & outside v2 ==> eliminate v3 - } - else - { - if(v3.dot(v4xv0) > 0) v2 = v4; // Outside v1 & inside v3 ==> eliminate v2 - else v1 = v4; // Outside v1 & outside v3 ==> eliminate v1 - } - } - } - return false; - } - - template - bool collide(const T &p1, const U &p2, vec *contactnormal, vec *contactpoint1, vec *contactpoint2) - { - // v0 = center of Minkowski sum - vec v01 = p1.center(); - vec v02 = p2.center(); - vec v0 = vec(v02).sub(v01); - - // Avoid case where centers overlap -- any direction is fine in this case - if(v0.iszero()) v0 = vec(0, 0, 1e-5f); - - // v1 = support in direction of origin - vec n = vec(v0).neg(); - vec v11 = p1.supportpoint(vec(n).neg()); - vec v12 = p2.supportpoint(n); - vec v1 = vec(v12).sub(v11); - if(v1.dot(n) <= 0) - { - if(contactnormal) *contactnormal = n; - return false; - } - - // v2 - support perpendicular to v1,v0 - n.cross(v1, v0); - if(n.iszero()) - { - n = vec(v1).sub(v0); - n.normalize(); - if(contactnormal) *contactnormal = n; - if(contactpoint1) *contactpoint1 = v11; - if(contactpoint2) *contactpoint2 = v12; - return true; - } - vec v21 = p1.supportpoint(vec(n).neg()); - vec v22 = p2.supportpoint(n); - vec v2 = vec(v22).sub(v21); - if(v2.dot(n) <= 0) - { - if(contactnormal) *contactnormal = n; - return false; - } - - // Determine whether origin is on + or - side of plane (v1,v0,v2) - n.cross(v0, v1, v2); - ASSERT( !n.iszero() ); - // If the origin is on the - side of the plane, reverse the direction of the plane - if(n.dot(v0) > 0) - { - swap(v1, v2); - swap(v11, v21); - swap(v12, v22); - n.neg(); - } - - /// - // Phase One: Identify a portal - - loopi(100) - { - // Obtain the support point in a direction perpendicular to the existing plane - // Note: This point is guaranteed to lie off the plane - vec v31 = p1.supportpoint(vec(n).neg()); - vec v32 = p2.supportpoint(n); - vec v3 = vec(v32).sub(v31); - if(v3.dot(n) <= 0) - { - if(contactnormal) *contactnormal = n; - return false; - } - - // If origin is outside (v1,v0,v3), then eliminate v2 and loop - vec v3xv0; - v3xv0.cross(v3, v0); - if(v1.dot(v3xv0) < 0) - { - v2 = v3; - v21 = v31; - v22 = v32; - n.cross(v0, v1, v3); - continue; - } - - // If origin is outside (v3,v0,v2), then eliminate v1 and loop - if(v2.dot(v3xv0) > 0) - { - v1 = v3; - v11 = v31; - v12 = v32; - n.cross(v0, v3, v2); - continue; - } - - bool hit = false; - - /// - // Phase Two: Refine the portal - - // We are now inside of a wedge... - for(int j = 0;; j++) - { - // Compute normal of the wedge face - n.cross(v1, v2, v3); - - // Can this happen??? Can it be handled more cleanly? - if(n.iszero()) - { - ASSERT(0); - return true; - } - - n.normalize(); - - // If the origin is inside the wedge, we have a hit - if(n.dot(v1) >= 0 && !hit) - { - if(contactnormal) *contactnormal = n; - - // Compute the barycentric coordinates of the origin - if(contactpoint1 || contactpoint2) - { - float b0 = v3.scalartriple(v1, v2), - b1 = v0.scalartriple(v3, v2), - b2 = v3.scalartriple(v0, v1), - b3 = v0.scalartriple(v2, v1), - sum = b0 + b1 + b2 + b3; - if(sum <= 0) - { - b0 = 0; - b1 = n.scalartriple(v2, v3); - b2 = n.scalartriple(v3, v1); - b3 = n.scalartriple(v1, v2); - sum = b1 + b2 + b3; - } - if(contactpoint1) - *contactpoint1 = (vec(v01).mul(b0).add(vec(v11).mul(b1)).add(vec(v21).mul(b2)).add(vec(v31).mul(b3))).mul(1.0f/sum); - if(contactpoint2) - *contactpoint2 = (vec(v02).mul(b0).add(vec(v12).mul(b1)).add(vec(v22).mul(b2)).add(vec(v32).mul(b3))).mul(1.0f/sum); - } - - // HIT!!! - hit = true; - } - - // Find the support point in the direction of the wedge face - vec v41 = p1.supportpoint(vec(n).neg()); - vec v42 = p2.supportpoint(n); - vec v4 = vec(v42).sub(v41); - - // If the boundary is thin enough or the origin is outside the support plane for the newly discovered vertex, then we can terminate - if(v4.dot(n) <= 0 || vec(v4).sub(v3).dot(n) <= boundarytolerance || j > 100) - { - if(contactnormal) *contactnormal = n; - return hit; - } - - // Test origin against the three planes that separate the new portal candidates: (v1,v4,v0) (v2,v4,v0) (v3,v4,v0) - // Note: We're taking advantage of the triple product identities here as an optimization - // (v1 % v4) * v0 == v1 * (v4 % v0) > 0 if origin inside (v1, v4, v0) - // (v2 % v4) * v0 == v2 * (v4 % v0) > 0 if origin inside (v2, v4, v0) - // (v3 % v4) * v0 == v3 * (v4 % v0) > 0 if origin inside (v3, v4, v0) - vec v4xv0; - v4xv0.cross(v4, v0); - if(v1.dot(v4xv0) > 0) // Compute the tetrahedron dividing face d1 = (v4,v0,v1) - { - if(v2.dot(v4xv0) > 0) // Compute the tetrahedron dividing face d2 = (v4,v0,v2) - { - // Inside d1 & inside d2 ==> eliminate v1 - v1 = v4; - v11 = v41; - v12 = v42; - } - else - { - // Inside d1 & outside d2 ==> eliminate v3 - v3 = v4; - v31 = v41; - v32 = v42; - } - } - else - { - if(v3.dot(v4xv0) > 0) // Compute the tetrahedron dividing face d3 = (v4,v0,v3) - { - // Outside d1 & inside d3 ==> eliminate v2 - v2 = v4; - v21 = v41; - v22 = v42; - } - else - { - // Outside d1 & outside d3 ==> eliminate v1 - v1 = v4; - v11 = v41; - v12 = v42; - } - } - } - } - return false; - } + struct CubePlanes + { + const clipplanes &p; + + CubePlanes(const clipplanes &p) : p(p) {} + + vec center() const { return p.o; } + + vec supportpoint(const vec &n) const + { + int besti = 7; + float bestd = n.dot(p.v[7]); + loopi(7) + { + float d = n.dot(p.v[i]); + if(d > bestd) { besti = i; bestd = d; } + } + return p.v[besti]; + } + }; + + struct SolidCube + { + vec o; + int size; + + SolidCube(float x, float y, float z, int size) : o(x, y, z), size(size) {} + SolidCube(const vec &o, int size) : o(o), size(size) {} + SolidCube(const ivec &o, int size) : o(o), size(size) {} + + vec center() const { return vec(o).add(size/2); } + + vec supportpoint(const vec &n) const + { + vec p(o); + if(n.x > 0) p.x += size; + if(n.y > 0) p.y += size; + if(n.z > 0) p.z += size; + return p; + } + }; + + struct Ent + { + physent *ent; + + Ent(physent *ent) : ent(ent) {} + + vec center() const { return vec(ent->o.x, ent->o.y, ent->o.z + (ent->aboveeye - ent->eyeheight)/2); } + }; + + struct EntOBB : Ent + { + matrix3 orient; + float zmargin; + + EntOBB(physent *ent, float zmargin = 0) : Ent(ent), zmargin(zmargin) + { + orient.setyaw(ent->yaw*RAD); + } + + vec center() const { return vec(ent->o.x, ent->o.y, ent->o.z + (ent->aboveeye - ent->eyeheight - zmargin)/2); } + + vec contactface(const vec &wn, const vec &wdir) const + { + vec n = orient.transform(wn).div(vec(ent->xradius, ent->yradius, (ent->aboveeye + ent->eyeheight + zmargin)/2)), + dir = orient.transform(wdir), + an(fabs(n.x), fabs(n.y), dir.z ? fabs(n.z) : 0), + fn(0, 0, 0); + if(an.x > an.y) + { + if(an.x > an.z) fn.x = n.x*dir.x < 0 ? (n.x > 0 ? 1 : -1) : 0; + else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; + } + else if(an.y > an.z) fn.y = n.y*dir.y < 0 ? (n.y > 0 ? 1 : -1) : 0; + else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; + return orient.transposedtransform(fn); + } + + vec localsupportpoint(const vec &ln) const + { + return vec(ln.x > 0 ? ent->xradius : -ent->xradius, + ln.y > 0 ? ent->yradius : -ent->yradius, + ln.z > 0 ? ent->aboveeye : -ent->eyeheight - zmargin); + } + + vec supportpoint(const vec &n) const + { + return orient.transposedtransform(localsupportpoint(orient.transform(n))).add(ent->o); + } + + float supportcoordneg(float a, float b, float c) const + { + return localsupportpoint(vec(-a, -b, -c)).dot(vec(a, b, c)); + } + float supportcoord(float a, float b, float c) const + { + return localsupportpoint(vec(a, b, c)).dot(vec(a, b, c)); + } + + float left() const { return supportcoordneg(orient.a.x, orient.b.x, orient.c.x) + ent->o.x; } + float right() const { return supportcoord(orient.a.x, orient.b.x, orient.c.x) + ent->o.x; } + float back() const { return supportcoordneg(orient.a.y, orient.b.y, orient.c.y) + ent->o.y; } + float front() const { return supportcoord(orient.a.y, orient.b.y, orient.c.y) + ent->o.y; } + float bottom() const { return ent->o.z - ent->eyeheight - zmargin; } + float top() const { return ent->o.z + ent->aboveeye; } + }; + + struct EntFuzzy : Ent + { + EntFuzzy(physent *ent) : Ent(ent) {} + + float left() const { return ent->o.x - ent->radius; } + float right() const { return ent->o.x + ent->radius; } + float back() const { return ent->o.y - ent->radius; } + float front() const { return ent->o.y + ent->radius; } + float bottom() const { return ent->o.z - ent->eyeheight; } + float top() const { return ent->o.z + ent->aboveeye; } + }; + + struct EntCylinder : EntFuzzy + { + float zmargin; + + EntCylinder(physent *ent, float zmargin = 0) : EntFuzzy(ent), zmargin(zmargin) {} + + vec center() const { return vec(ent->o.x, ent->o.y, ent->o.z + (ent->aboveeye - ent->eyeheight - zmargin)/2); } + + float bottom() const { return ent->o.z - ent->eyeheight - zmargin; } + + vec contactface(const vec &n, const vec &dir) const + { + float dxy = n.dot2(n)/(ent->radius*ent->radius), dz = n.z*n.z*4/(ent->aboveeye + ent->eyeheight + zmargin); + vec fn(0, 0, 0); + if(dz > dxy && dir.z) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; + else if(n.dot2(dir) < 0) + { + fn.x = n.x; + fn.y = n.y; + fn.normalize(); + } + return fn; + } + + vec supportpoint(const vec &n) const + { + vec p(ent->o); + if(n.z > 0) p.z += ent->aboveeye; + else p.z -= ent->eyeheight + zmargin; + if(n.x || n.y) + { + float r = ent->radius / n.magnitude2(); + p.x += n.x*r; + p.y += n.y*r; + } + return p; + } + }; + + struct EntCapsule : EntFuzzy + { + EntCapsule(physent *ent) : EntFuzzy(ent) {} + + vec supportpoint(const vec &n) const + { + vec p(ent->o); + if(n.z > 0) p.z += ent->aboveeye - ent->radius; + else p.z -= ent->eyeheight - ent->radius; + p.add(vec(n).mul(ent->radius / n.magnitude())); + return p; + } + }; + + struct EntEllipsoid : EntFuzzy + { + EntEllipsoid(physent *ent) : EntFuzzy(ent) {} + + vec supportpoint(const vec &dir) const + { + vec p(ent->o), n = vec(dir).normalize(); + p.x += ent->radius*n.x; + p.y += ent->radius*n.y; + p.z += (ent->aboveeye + ent->eyeheight)/2*(1 + n.z) - ent->eyeheight; + return p; + } + }; + + struct Model + { + vec o, radius; + matrix3 orient; + + Model(const vec &ent, const vec ¢er, const vec &radius, int yaw) : o(ent), radius(radius) + { + orient.setyaw(yaw*RAD); + o.add(orient.transposedtransform(center)); + } + + vec center() const { return o; } + }; + + struct ModelOBB : Model + { + ModelOBB(const vec &ent, const vec ¢er, const vec &radius, int yaw) : + Model(ent, center, radius, yaw) + {} + + vec contactface(const vec &wn, const vec &wdir) const + { + vec n = orient.transform(wn).div(radius), dir = orient.transform(wdir), + an(fabs(n.x), fabs(n.y), dir.z ? fabs(n.z) : 0), + fn(0, 0, 0); + if(an.x > an.y) + { + if(an.x > an.z) fn.x = n.x*dir.x < 0 ? (n.x > 0 ? 1 : -1) : 0; + else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; + } + else if(an.y > an.z) fn.y = n.y*dir.y < 0 ? (n.y > 0 ? 1 : -1) : 0; + else if(an.z > 0) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; + return orient.transposedtransform(fn); + } + + vec supportpoint(const vec &n) const + { + vec ln = orient.transform(n), p(0, 0, 0); + if(ln.x > 0) p.x += radius.x; + else p.x -= radius.x; + if(ln.y > 0) p.y += radius.y; + else p.y -= radius.y; + if(ln.z > 0) p.z += radius.z; + else p.z -= radius.z; + return orient.transposedtransform(p).add(o); + } + }; + + struct ModelEllipse : Model + { + ModelEllipse(const vec &ent, const vec ¢er, const vec &radius, int yaw) : + Model(ent, center, radius, yaw) + {} + + vec contactface(const vec &wn, const vec &wdir) const + { + vec n = orient.transform(wn).div(radius), dir = orient.transform(wdir); + float dxy = n.dot2(n), dz = n.z*n.z; + vec fn(0, 0, 0); + if(dz > dxy && dir.z) fn.z = n.z*dir.z < 0 ? (n.z > 0 ? 1 : -1) : 0; + else if(n.dot2(dir) < 0) + { + fn.x = n.x*radius.y; + fn.y = n.y*radius.x; + fn.normalize(); + } + return orient.transposedtransform(fn); + } + + vec supportpoint(const vec &n) const + { + vec ln = orient.transform(n), p(0, 0, 0); + if(ln.z > 0) p.z += radius.z; + else p.z -= radius.z; + if(ln.x || ln.y) + { + float r = ln.magnitude2(); + p.x += ln.x*radius.x/r; + p.y += ln.y*radius.y/r; + } + return orient.transposedtransform(p).add(o); + } + }; + + const float boundarytolerance = 1e-3f; + + template + bool collide(const T &p1, const U &p2) + { + // v0 = center of Minkowski difference + vec v0 = p2.center().sub(p1.center()); + if(v0.iszero()) return true; // v0 and origin overlap ==> hit + + // v1 = support in direction of origin + vec n = vec(v0).neg(); + vec v1 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); + if(v1.dot(n) <= 0) return false; // origin outside v1 support plane ==> miss + + // v2 = support perpendicular to plane containing origin, v0 and v1 + n.cross(v1, v0); + if(n.iszero()) return true; // v0, v1 and origin colinear (and origin inside v1 support plane) == > hit + vec v2 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); + if(v2.dot(n) <= 0) return false; // origin outside v2 support plane ==> miss + + // v3 = support perpendicular to plane containing v0, v1 and v2 + n.cross(v0, v1, v2); + + // If the origin is on the - side of the plane, reverse the direction of the plane + if(n.dot(v0) > 0) + { + swap(v1, v2); + n.neg(); + } + + /// + // Phase One: Find a valid portal + + loopi(100) + { + // Obtain the next support point + vec v3 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); + if(v3.dot(n) <= 0) return false; // origin outside v3 support plane ==> miss + + // If origin is outside (v1,v0,v3), then portal is invalid -- eliminate v2 and find new support outside face + vec v3xv0; + v3xv0.cross(v3, v0); + if(v1.dot(v3xv0) < 0) + { + v2 = v3; + n.cross(v0, v1, v3); + continue; + } + + // If origin is outside (v3,v0,v2), then portal is invalid -- eliminate v1 and find new support outside face + if(v2.dot(v3xv0) > 0) + { + v1 = v3; + n.cross(v0, v3, v2); + continue; + } + + /// + // Phase Two: Refine the portal + + for(int j = 0;; j++) + { + // Compute outward facing normal of the portal + n.cross(v1, v2, v3); + + // If the origin is inside the portal, we have a hit + if(n.dot(v1) >= 0) return true; + + n.normalize(); + + // Find the support point in the direction of the portal's normal + vec v4 = p2.supportpoint(n).sub(p1.supportpoint(vec(n).neg())); + + // If the origin is outside the support plane or the boundary is thin enough, we have a miss + if(v4.dot(n) <= 0 || vec(v4).sub(v3).dot(n) <= boundarytolerance || j > 100) return false; + + // Test origin against the three planes that separate the new portal candidates: (v1,v4,v0) (v2,v4,v0) (v3,v4,v0) + // Note: We're taking advantage of the triple product identities here as an optimization + // (v1 % v4) * v0 == v1 * (v4 % v0) > 0 if origin inside (v1, v4, v0) + // (v2 % v4) * v0 == v2 * (v4 % v0) > 0 if origin inside (v2, v4, v0) + // (v3 % v4) * v0 == v3 * (v4 % v0) > 0 if origin inside (v3, v4, v0) + vec v4xv0; + v4xv0.cross(v4, v0); + if(v1.dot(v4xv0) > 0) + { + if(v2.dot(v4xv0) > 0) v1 = v4; // Inside v1 & inside v2 ==> eliminate v1 + else v3 = v4; // Inside v1 & outside v2 ==> eliminate v3 + } + else + { + if(v3.dot(v4xv0) > 0) v2 = v4; // Outside v1 & inside v3 ==> eliminate v2 + else v1 = v4; // Outside v1 & outside v3 ==> eliminate v1 + } + } + } + return false; + } + + template + bool collide(const T &p1, const U &p2, vec *contactnormal, vec *contactpoint1, vec *contactpoint2) + { + // v0 = center of Minkowski sum + vec v01 = p1.center(); + vec v02 = p2.center(); + vec v0 = vec(v02).sub(v01); + + // Avoid case where centers overlap -- any direction is fine in this case + if(v0.iszero()) v0 = vec(0, 0, 1e-5f); + + // v1 = support in direction of origin + vec n = vec(v0).neg(); + vec v11 = p1.supportpoint(vec(n).neg()); + vec v12 = p2.supportpoint(n); + vec v1 = vec(v12).sub(v11); + if(v1.dot(n) <= 0) + { + if(contactnormal) *contactnormal = n; + return false; + } + + // v2 - support perpendicular to v1,v0 + n.cross(v1, v0); + if(n.iszero()) + { + n = vec(v1).sub(v0); + n.normalize(); + if(contactnormal) *contactnormal = n; + if(contactpoint1) *contactpoint1 = v11; + if(contactpoint2) *contactpoint2 = v12; + return true; + } + vec v21 = p1.supportpoint(vec(n).neg()); + vec v22 = p2.supportpoint(n); + vec v2 = vec(v22).sub(v21); + if(v2.dot(n) <= 0) + { + if(contactnormal) *contactnormal = n; + return false; + } + + // Determine whether origin is on + or - side of plane (v1,v0,v2) + n.cross(v0, v1, v2); + ASSERT( !n.iszero() ); + // If the origin is on the - side of the plane, reverse the direction of the plane + if(n.dot(v0) > 0) + { + swap(v1, v2); + swap(v11, v21); + swap(v12, v22); + n.neg(); + } + + /// + // Phase One: Identify a portal + + loopi(100) + { + // Obtain the support point in a direction perpendicular to the existing plane + // Note: This point is guaranteed to lie off the plane + vec v31 = p1.supportpoint(vec(n).neg()); + vec v32 = p2.supportpoint(n); + vec v3 = vec(v32).sub(v31); + if(v3.dot(n) <= 0) + { + if(contactnormal) *contactnormal = n; + return false; + } + + // If origin is outside (v1,v0,v3), then eliminate v2 and loop + vec v3xv0; + v3xv0.cross(v3, v0); + if(v1.dot(v3xv0) < 0) + { + v2 = v3; + v21 = v31; + v22 = v32; + n.cross(v0, v1, v3); + continue; + } + + // If origin is outside (v3,v0,v2), then eliminate v1 and loop + if(v2.dot(v3xv0) > 0) + { + v1 = v3; + v11 = v31; + v12 = v32; + n.cross(v0, v3, v2); + continue; + } + + bool hit = false; + + /// + // Phase Two: Refine the portal + + // We are now inside of a wedge... + for(int j = 0;; j++) + { + // Compute normal of the wedge face + n.cross(v1, v2, v3); + + // Can this happen??? Can it be handled more cleanly? + if(n.iszero()) + { + ASSERT(0); + return true; + } + + n.normalize(); + + // If the origin is inside the wedge, we have a hit + if(n.dot(v1) >= 0 && !hit) + { + if(contactnormal) *contactnormal = n; + + // Compute the barycentric coordinates of the origin + if(contactpoint1 || contactpoint2) + { + float b0 = v3.scalartriple(v1, v2), + b1 = v0.scalartriple(v3, v2), + b2 = v3.scalartriple(v0, v1), + b3 = v0.scalartriple(v2, v1), + sum = b0 + b1 + b2 + b3; + if(sum <= 0) + { + b0 = 0; + b1 = n.scalartriple(v2, v3); + b2 = n.scalartriple(v3, v1); + b3 = n.scalartriple(v1, v2); + sum = b1 + b2 + b3; + } + if(contactpoint1) + *contactpoint1 = (vec(v01).mul(b0).add(vec(v11).mul(b1)).add(vec(v21).mul(b2)).add(vec(v31).mul(b3))).mul(1.0f/sum); + if(contactpoint2) + *contactpoint2 = (vec(v02).mul(b0).add(vec(v12).mul(b1)).add(vec(v22).mul(b2)).add(vec(v32).mul(b3))).mul(1.0f/sum); + } + + // HIT!!! + hit = true; + } + + // Find the support point in the direction of the wedge face + vec v41 = p1.supportpoint(vec(n).neg()); + vec v42 = p2.supportpoint(n); + vec v4 = vec(v42).sub(v41); + + // If the boundary is thin enough or the origin is outside the support plane for the newly discovered vertex, then we can terminate + if(v4.dot(n) <= 0 || vec(v4).sub(v3).dot(n) <= boundarytolerance || j > 100) + { + if(contactnormal) *contactnormal = n; + return hit; + } + + // Test origin against the three planes that separate the new portal candidates: (v1,v4,v0) (v2,v4,v0) (v3,v4,v0) + // Note: We're taking advantage of the triple product identities here as an optimization + // (v1 % v4) * v0 == v1 * (v4 % v0) > 0 if origin inside (v1, v4, v0) + // (v2 % v4) * v0 == v2 * (v4 % v0) > 0 if origin inside (v2, v4, v0) + // (v3 % v4) * v0 == v3 * (v4 % v0) > 0 if origin inside (v3, v4, v0) + vec v4xv0; + v4xv0.cross(v4, v0); + if(v1.dot(v4xv0) > 0) // Compute the tetrahedron dividing face d1 = (v4,v0,v1) + { + if(v2.dot(v4xv0) > 0) // Compute the tetrahedron dividing face d2 = (v4,v0,v2) + { + // Inside d1 & inside d2 ==> eliminate v1 + v1 = v4; + v11 = v41; + v12 = v42; + } + else + { + // Inside d1 & outside d2 ==> eliminate v3 + v3 = v4; + v31 = v41; + v32 = v42; + } + } + else + { + if(v3.dot(v4xv0) > 0) // Compute the tetrahedron dividing face d3 = (v4,v0,v3) + { + // Outside d1 & inside d3 ==> eliminate v2 + v2 = v4; + v21 = v41; + v22 = v42; + } + else + { + // Outside d1 & outside d3 ==> eliminate v1 + v1 = v4; + v11 = v41; + v12 = v42; + } + } + } + } + return false; + } } diff --git a/src/engine/normal.cpp b/src/engine/normal.cpp index d8641ab..dad00b0 100644 --- a/src/engine/normal.cpp +++ b/src/engine/normal.cpp @@ -2,27 +2,27 @@ struct normalgroup { - vec pos; - int flat, normals, tnormals; + vec pos; + int flat, normals, tnormals; - normalgroup() : flat(0), normals(-1), tnormals(-1) {} - normalgroup(const vec &pos) : pos(pos), flat(0), normals(-1), tnormals(-1) {} + normalgroup() : flat(0), normals(-1), tnormals(-1) {} + normalgroup(const vec &pos) : pos(pos), flat(0), normals(-1), tnormals(-1) {} }; -static inline bool htcmp(const vec &v, const normalgroup &n) { return v == n.pos; } +static inline bool htcmp(const vec &v, const normalgroup &n) { return v == n.pos; } struct normal { - int next; - vec surface; + int next; + vec surface; }; struct tnormal { - int next; - float offset; - int normals[2]; - normalgroup *groups[2]; + int next; + float offset; + int normals[2]; + normalgroup *groups[2]; }; hashset normalgroups(1<<16); @@ -36,91 +36,91 @@ static bool usetnormals = true; static int addnormal(const vec &key, const vec &surface) { - normalgroup &g = normalgroups.access(key, key); - normal &n = normals.add(); - n.next = g.normals; - n.surface = surface; - return g.normals = normals.length()-1; + normalgroup &g = normalgroups.access(key, key); + normal &n = normals.add(); + n.next = g.normals; + n.surface = surface; + return g.normals = normals.length()-1; } static void addtnormal(const vec &key, float offset, int normal1, int normal2, normalgroup *group1, normalgroup *group2) { - normalgroup &g = normalgroups.access(key, key); - tnormal &n = tnormals.add(); - n.next = g.tnormals; - n.offset = offset; - n.normals[0] = normal1; - n.normals[1] = normal2; - n.groups[0] = group1; - n.groups[1] = group2; - g.tnormals = tnormals.length()-1; + normalgroup &g = normalgroups.access(key, key); + tnormal &n = tnormals.add(); + n.next = g.tnormals; + n.offset = offset; + n.normals[0] = normal1; + n.normals[1] = normal2; + n.groups[0] = group1; + n.groups[1] = group2; + g.tnormals = tnormals.length()-1; } static int addnormal(const vec &key, int axis) { - normalgroup &g = normalgroups.access(key, key); - g.flat += 1<<(4*axis); - return axis - 6; + normalgroup &g = normalgroups.access(key, key); + g.flat += 1<<(4*axis); + return axis - 6; } static inline void findnormal(const normalgroup &g, const vec &surface, vec &v) { - v = vec(0, 0, 0); - int total = 0; - if(surface.x >= lerpthreshold) { int n = (g.flat>>4)&0xF; v.x += n; total += n; } - else if(surface.x <= -lerpthreshold) { int n = g.flat&0xF; v.x -= n; total += n; } - if(surface.y >= lerpthreshold) { int n = (g.flat>>12)&0xF; v.y += n; total += n; } - else if(surface.y <= -lerpthreshold) { int n = (g.flat>>8)&0xF; v.y -= n; total += n; } - if(surface.z >= lerpthreshold) { int n = (g.flat>>20)&0xF; v.z += n; total += n; } - else if(surface.z <= -lerpthreshold) { int n = (g.flat>>16)&0xF; v.z -= n; total += n; } - for(int cur = g.normals; cur >= 0;) - { - normal &o = normals[cur]; - if(o.surface.dot(surface) >= lerpthreshold) - { - v.add(o.surface); - total++; - } - cur = o.next; - } - if(total > 1) v.normalize(); - else if(!total) v = surface; + v = vec(0, 0, 0); + int total = 0; + if(surface.x >= lerpthreshold) { int n = (g.flat>>4)&0xF; v.x += n; total += n; } + else if(surface.x <= -lerpthreshold) { int n = g.flat&0xF; v.x -= n; total += n; } + if(surface.y >= lerpthreshold) { int n = (g.flat>>12)&0xF; v.y += n; total += n; } + else if(surface.y <= -lerpthreshold) { int n = (g.flat>>8)&0xF; v.y -= n; total += n; } + if(surface.z >= lerpthreshold) { int n = (g.flat>>20)&0xF; v.z += n; total += n; } + else if(surface.z <= -lerpthreshold) { int n = (g.flat>>16)&0xF; v.z -= n; total += n; } + for(int cur = g.normals; cur >= 0;) + { + normal &o = normals[cur]; + if(o.surface.dot(surface) >= lerpthreshold) + { + v.add(o.surface); + total++; + } + cur = o.next; + } + if(total > 1) v.normalize(); + else if(!total) v = surface; } static inline bool findtnormal(const normalgroup &g, const vec &surface, vec &v) { - float bestangle = lerpthreshold; - tnormal *bestnorm = NULL; - for(int cur = g.tnormals; cur >= 0;) - { - tnormal &o = tnormals[cur]; - static const vec flats[6] = { vec(-1, 0, 0), vec(1, 0, 0), vec(0, -1, 0), vec(0, 1, 0), vec(0, 0, -1), vec(0, 0, 1) }; - vec n1 = o.normals[0] < 0 ? flats[o.normals[0]+6] : normals[o.normals[0]].surface, - n2 = o.normals[1] < 0 ? flats[o.normals[1]+6] : normals[o.normals[1]].surface, - nt; - nt.lerp(n1, n2, o.offset).normalize(); - float tangle = nt.dot(surface); - if(tangle >= bestangle) - { - bestangle = tangle; - bestnorm = &o; - } - cur = o.next; - } - if(!bestnorm) return false; - vec n1, n2; - findnormal(*bestnorm->groups[0], surface, n1); - findnormal(*bestnorm->groups[1], surface, n2); - v.lerp(n1, n2, bestnorm->offset).normalize(); - return true; + float bestangle = lerpthreshold; + tnormal *bestnorm = NULL; + for(int cur = g.tnormals; cur >= 0;) + { + tnormal &o = tnormals[cur]; + static const vec flats[6] = { vec(-1, 0, 0), vec(1, 0, 0), vec(0, -1, 0), vec(0, 1, 0), vec(0, 0, -1), vec(0, 0, 1) }; + vec n1 = o.normals[0] < 0 ? flats[o.normals[0]+6] : normals[o.normals[0]].surface, + n2 = o.normals[1] < 0 ? flats[o.normals[1]+6] : normals[o.normals[1]].surface, + nt; + nt.lerp(n1, n2, o.offset).normalize(); + float tangle = nt.dot(surface); + if(tangle >= bestangle) + { + bestangle = tangle; + bestnorm = &o; + } + cur = o.next; + } + if(!bestnorm) return false; + vec n1, n2; + findnormal(*bestnorm->groups[0], surface, n1); + findnormal(*bestnorm->groups[1], surface, n2); + v.lerp(n1, n2, bestnorm->offset).normalize(); + return true; } void findnormal(const vec &key, const vec &surface, vec &v) { - const normalgroup *g = normalgroups.access(key); - if(!g) v = surface; - else if(g->tnormals < 0 || !findtnormal(*g, surface, v)) - findnormal(*g, surface, v); + const normalgroup *g = normalgroups.access(key); + if(!g) v = surface; + else if(g->tnormals < 0 || !findtnormal(*g, surface, v)) + findnormal(*g, surface, v); } VARR(lerpsubdiv, 0, 2, 4); @@ -130,254 +130,254 @@ static uint progress = 0; void show_addnormals_progress() { - float bar1 = float(progress) / float(allocnodes); - renderprogress(bar1, "computing normals..."); + float bar1 = float(progress) / float(allocnodes); + renderprogress(bar1, "computing normals..."); } void addnormals(cube &c, const ivec &o, int size) { - CHECK_CALCLIGHT_PROGRESS(return, show_addnormals_progress); - - if(c.children) - { - progress++; - size >>= 1; - loopi(8) addnormals(c.children[i], ivec(i, o, size), size); - return; - } - else if(isempty(c)) return; - - vec pos[MAXFACEVERTS]; - int norms[MAXFACEVERTS]; - int tj = usetnormals && c.ext ? c.ext->tjoints : -1, vis; - loopi(6) if((vis = visibletris(c, i, o, size))) - { - CHECK_CALCLIGHT_PROGRESS(return, show_addnormals_progress); - if(c.texture[i] == DEFAULT_SKY) continue; - - vec planes[2]; - int numverts = c.ext ? c.ext->surfaces[i].numverts&MAXFACEVERTS : 0, convex = 0, numplanes = 0; - if(numverts) - { - vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts; - vec vo(ivec(o).mask(~0xFFF)); - loopj(numverts) - { - vertinfo &v = verts[j]; - pos[j] = vec(v.x, v.y, v.z).mul(1.0f/8).add(vo); - } - if(!(c.merged&(1<= 0 && tjoints[tj].edge < i*(MAXFACEVERTS+1)) tj = tjoints[tj].next; - while(tj >= 0 && tjoints[tj].edge < (i+1)*(MAXFACEVERTS+1)) - { - int edge = tjoints[tj].edge, e1 = edge%(MAXFACEVERTS+1), e2 = (e1+1)%numverts; - const vec &v1 = pos[e1], &v2 = pos[e2]; - ivec d(vec(v2).sub(v1).mul(8)); - int axis = abs(d.x) > abs(d.y) ? (abs(d.x) > abs(d.z) ? 0 : 2) : (abs(d.y) > abs(d.z) ? 1 : 2); - if(d[axis] < 0) d.neg(); - reduceslope(d); - int origin = int(min(v1[axis], v2[axis])*8)&~0x7FFF, - offset1 = (int(v1[axis]*8) - origin) / d[axis], - offset2 = (int(v2[axis]*8) - origin) / d[axis]; - vec o = vec(v1).sub(vec(d).mul(offset1/8.0f)), n1, n2; - float doffset = 1.0f / (offset2 - offset1); - - while(tj >= 0) - { - tjoint &t = tjoints[tj]; - if(t.edge != edge) break; - float offset = (t.offset - offset1) * doffset; - vec tpos = vec(d).mul(t.offset/8.0f).add(o); - addtnormal(tpos, offset, norms[e1], norms[e2], normalgroups.access(v1), normalgroups.access(v2)); - tj = t.next; - } - } - } + CHECK_CALCLIGHT_PROGRESS(return, show_addnormals_progress); + + if(c.children) + { + progress++; + size >>= 1; + loopi(8) addnormals(c.children[i], ivec(i, o, size), size); + return; + } + else if(isempty(c)) return; + + vec pos[MAXFACEVERTS]; + int norms[MAXFACEVERTS]; + int tj = usetnormals && c.ext ? c.ext->tjoints : -1, vis; + loopi(6) if((vis = visibletris(c, i, o, size))) + { + CHECK_CALCLIGHT_PROGRESS(return, show_addnormals_progress); + if(c.texture[i] == DEFAULT_SKY) continue; + + vec planes[2]; + int numverts = c.ext ? c.ext->surfaces[i].numverts&MAXFACEVERTS : 0, convex = 0, numplanes = 0; + if(numverts) + { + vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts; + vec vo(ivec(o).mask(~0xFFF)); + loopj(numverts) + { + vertinfo &v = verts[j]; + pos[j] = vec(v.x, v.y, v.z).mul(1.0f/8).add(vo); + } + if(!(c.merged&(1<= 0 && tjoints[tj].edge < i*(MAXFACEVERTS+1)) tj = tjoints[tj].next; + while(tj >= 0 && tjoints[tj].edge < (i+1)*(MAXFACEVERTS+1)) + { + int edge = tjoints[tj].edge, e1 = edge%(MAXFACEVERTS+1), e2 = (e1+1)%numverts; + const vec &v1 = pos[e1], &v2 = pos[e2]; + ivec d(vec(v2).sub(v1).mul(8)); + int axis = abs(d.x) > abs(d.y) ? (abs(d.x) > abs(d.z) ? 0 : 2) : (abs(d.y) > abs(d.z) ? 1 : 2); + if(d[axis] < 0) d.neg(); + reduceslope(d); + int origin = int(min(v1[axis], v2[axis])*8)&~0x7FFF, + offset1 = (int(v1[axis]*8) - origin) / d[axis], + offset2 = (int(v2[axis]*8) - origin) / d[axis]; + vec o = vec(v1).sub(vec(d).mul(offset1/8.0f)), n1, n2; + float doffset = 1.0f / (offset2 - offset1); + + while(tj >= 0) + { + tjoint &t = tjoints[tj]; + if(t.edge != edge) break; + float offset = (t.offset - offset1) * doffset; + vec tpos = vec(d).mul(t.offset/8.0f).add(o); + addtnormal(tpos, offset, norms[e1], norms[e2], normalgroups.access(v1), normalgroups.access(v2)); + tj = t.next; + } + } + } } void calcnormals(bool lerptjoints) { - if(!lerpangle) return; - usetnormals = lerptjoints; - if(usetnormals) findtjoints(); - lerpthreshold = cos(lerpangle*RAD) - 1e-5f; - progress = 1; - loopi(8) addnormals(worldroot[i], ivec(i, ivec(0, 0, 0), worldsize/2), worldsize/2); + if(!lerpangle) return; + usetnormals = lerptjoints; + if(usetnormals) findtjoints(); + lerpthreshold = cos(lerpangle*RAD) - 1e-5f; + progress = 1; + loopi(8) addnormals(worldroot[i], ivec(i, ivec(0, 0, 0), worldsize/2), worldsize/2); } void clearnormals() { - normalgroups.clear(); - normals.setsize(0); - tnormals.setsize(0); + normalgroups.clear(); + normals.setsize(0); + tnormals.setsize(0); } void calclerpverts(const vec2 *c, const vec *n, lerpvert *lv, int &numv) { - int i = 0; - loopj(numv) - { - if(j) - { - if(c[j] == c[j-1] && n[j] == n[j-1]) continue; - if(j == numv-1 && c[j] == c[0] && n[j] == n[0]) continue; - } - lv[i].normal = n[j]; - lv[i].tc = c[j]; - i++; - } - numv = i; + int i = 0; + loopj(numv) + { + if(j) + { + if(c[j] == c[j-1] && n[j] == n[j-1]) continue; + if(j == numv-1 && c[j] == c[0] && n[j] == n[0]) continue; + } + lv[i].normal = n[j]; + lv[i].tc = c[j]; + i++; + } + numv = i; } void setlerpstep(float v, lerpbounds &bounds) { - if(bounds.min->tc.y + 1 > bounds.max->tc.y) - { - bounds.nstep = vec(0, 0, 0); - bounds.normal = bounds.min->normal; - if(bounds.min->normal != bounds.max->normal) - { - bounds.normal.add(bounds.max->normal); - bounds.normal.normalize(); - } - bounds.ustep = 0; - bounds.u = bounds.min->tc.x; - return; - } - - bounds.nstep = bounds.max->normal; - bounds.nstep.sub(bounds.min->normal); - bounds.nstep.div(bounds.max->tc.y-bounds.min->tc.y); - - bounds.normal = bounds.nstep; - bounds.normal.mul(v - bounds.min->tc.y); - bounds.normal.add(bounds.min->normal); - - bounds.ustep = (bounds.max->tc.x-bounds.min->tc.x) / (bounds.max->tc.y-bounds.min->tc.y); - bounds.u = bounds.ustep * (v-bounds.min->tc.y) + bounds.min->tc.x; + if(bounds.min->tc.y + 1 > bounds.max->tc.y) + { + bounds.nstep = vec(0, 0, 0); + bounds.normal = bounds.min->normal; + if(bounds.min->normal != bounds.max->normal) + { + bounds.normal.add(bounds.max->normal); + bounds.normal.normalize(); + } + bounds.ustep = 0; + bounds.u = bounds.min->tc.x; + return; + } + + bounds.nstep = bounds.max->normal; + bounds.nstep.sub(bounds.min->normal); + bounds.nstep.div(bounds.max->tc.y-bounds.min->tc.y); + + bounds.normal = bounds.nstep; + bounds.normal.mul(v - bounds.min->tc.y); + bounds.normal.add(bounds.min->normal); + + bounds.ustep = (bounds.max->tc.x-bounds.min->tc.x) / (bounds.max->tc.y-bounds.min->tc.y); + bounds.u = bounds.ustep * (v-bounds.min->tc.y) + bounds.min->tc.x; } void initlerpbounds(float u, float v, const lerpvert *lv, int numv, lerpbounds &start, lerpbounds &end) { - const lerpvert *first = &lv[0], *second = NULL; - loopi(numv-1) - { - if(lv[i+1].tc.y < first->tc.y) { second = first; first = &lv[i+1]; } - else if(!second || lv[i+1].tc.y < second->tc.y) second = &lv[i+1]; - } - - if(int(first->tc.y) < int(second->tc.y)) { start.min = end.min = first; } - else if(first->tc.x > second->tc.x) { start.min = second; end.min = first; } - else { start.min = first; end.min = second; } - - if((lv[1].tc.x - lv->tc.x)*(lv[2].tc.y - lv->tc.y) > (lv[1].tc.y - lv->tc.y)*(lv[2].tc.x - lv->tc.x)) - { - start.winding = end.winding = 1; - start.max = (start.min == lv ? &lv[numv-1] : start.min-1); - end.max = (end.min == &lv[numv-1] ? lv : end.min+1); - } - else - { - start.winding = end.winding = -1; - start.max = (start.min == &lv[numv-1] ? lv : start.min+1); - end.max = (end.min == lv ? &lv[numv-1] : end.min-1); - } - - setlerpstep(v, start); - setlerpstep(v, end); + const lerpvert *first = &lv[0], *second = NULL; + loopi(numv-1) + { + if(lv[i+1].tc.y < first->tc.y) { second = first; first = &lv[i+1]; } + else if(!second || lv[i+1].tc.y < second->tc.y) second = &lv[i+1]; + } + + if(int(first->tc.y) < int(second->tc.y)) { start.min = end.min = first; } + else if(first->tc.x > second->tc.x) { start.min = second; end.min = first; } + else { start.min = first; end.min = second; } + + if((lv[1].tc.x - lv->tc.x)*(lv[2].tc.y - lv->tc.y) > (lv[1].tc.y - lv->tc.y)*(lv[2].tc.x - lv->tc.x)) + { + start.winding = end.winding = 1; + start.max = (start.min == lv ? &lv[numv-1] : start.min-1); + end.max = (end.min == &lv[numv-1] ? lv : end.min+1); + } + else + { + start.winding = end.winding = -1; + start.max = (start.min == &lv[numv-1] ? lv : start.min+1); + end.max = (end.min == lv ? &lv[numv-1] : end.min-1); + } + + setlerpstep(v, start); + setlerpstep(v, end); } void updatelerpbounds(float v, const lerpvert *lv, int numv, lerpbounds &start, lerpbounds &end) { - if(v >= start.max->tc.y) - { - const lerpvert *next = start.winding > 0 ? - (start.max == lv ? &lv[numv-1] : start.max-1) : - (start.max == &lv[numv-1] ? lv : start.max+1); - if(next->tc.y > start.max->tc.y) - { - start.min = start.max; - start.max = next; - setlerpstep(v, start); - } - } - if(v >= end.max->tc.y) - { - const lerpvert *next = end.winding > 0 ? - (end.max == &lv[numv-1] ? lv : end.max+1) : - (end.max == lv ? &lv[numv-1] : end.max-1); - if(next->tc.y > end.max->tc.y) - { - end.min = end.max; - end.max = next; - setlerpstep(v, end); - } - } + if(v >= start.max->tc.y) + { + const lerpvert *next = start.winding > 0 ? + (start.max == lv ? &lv[numv-1] : start.max-1) : + (start.max == &lv[numv-1] ? lv : start.max+1); + if(next->tc.y > start.max->tc.y) + { + start.min = start.max; + start.max = next; + setlerpstep(v, start); + } + } + if(v >= end.max->tc.y) + { + const lerpvert *next = end.winding > 0 ? + (end.max == &lv[numv-1] ? lv : end.max+1) : + (end.max == lv ? &lv[numv-1] : end.max-1); + if(next->tc.y > end.max->tc.y) + { + end.min = end.max; + end.max = next; + setlerpstep(v, end); + } + } } void lerpnormal(float u, float v, const lerpvert *lv, int numv, lerpbounds &start, lerpbounds &end, vec &normal, vec &nstep) -{ - updatelerpbounds(v, lv, numv, start, end); - - if(start.u + 1 > end.u) - { - nstep = vec(0, 0, 0); - normal = start.normal; - normal.add(end.normal); - normal.normalize(); - } - else - { - vec nstart(start.normal), nend(end.normal); - nstart.normalize(); - nend.normalize(); - - nstep = nend; - nstep.sub(nstart); - nstep.div(end.u-start.u); - - normal = nstep; - normal.mul(u-start.u); - normal.add(nstart); - normal.normalize(); - } - - start.normal.add(start.nstep); - start.u += start.ustep; - - end.normal.add(end.nstep); - end.u += end.ustep; +{ + updatelerpbounds(v, lv, numv, start, end); + + if(start.u + 1 > end.u) + { + nstep = vec(0, 0, 0); + normal = start.normal; + normal.add(end.normal); + normal.normalize(); + } + else + { + vec nstart(start.normal), nend(end.normal); + nstart.normalize(); + nend.normalize(); + + nstep = nend; + nstep.sub(nstart); + nstep.div(end.u-start.u); + + normal = nstep; + normal.mul(u-start.u); + normal.add(nstart); + normal.normalize(); + } + + start.normal.add(start.nstep); + start.u += start.ustep; + + end.normal.add(end.nstep); + end.u += end.ustep; } diff --git a/src/engine/octa.cpp b/src/engine/octa.cpp index e4f0901..93077c5 100644 --- a/src/engine/octa.cpp +++ b/src/engine/octa.cpp @@ -7,254 +7,254 @@ int allocnodes = 0; cubeext *growcubeext(cubeext *old, int maxverts) { - cubeext *ext = (cubeext *)new uchar[sizeof(cubeext) + maxverts*sizeof(vertinfo)]; - if(old) - { - ext->va = old->va; - ext->ents = old->ents; - ext->tjoints = old->tjoints; - } - else - { - ext->va = NULL; - ext->ents = NULL; - ext->tjoints = -1; - } - ext->maxverts = maxverts; - return ext; + cubeext *ext = (cubeext *)new uchar[sizeof(cubeext) + maxverts*sizeof(vertinfo)]; + if(old) + { + ext->va = old->va; + ext->ents = old->ents; + ext->tjoints = old->tjoints; + } + else + { + ext->va = NULL; + ext->ents = NULL; + ext->tjoints = -1; + } + ext->maxverts = maxverts; + return ext; } void setcubeext(cube &c, cubeext *ext) { - cubeext *old = c.ext; - if(old == ext) return; - c.ext = ext; - if(old) delete[] (uchar *)old; + cubeext *old = c.ext; + if(old == ext) return; + c.ext = ext; + if(old) delete[] (uchar *)old; } - + cubeext *newcubeext(cube &c, int maxverts, bool init) { - if(c.ext && c.ext->maxverts >= maxverts) return c.ext; - cubeext *ext = growcubeext(c.ext, maxverts); - if(init) - { - if(c.ext) - { - memcpy(ext->surfaces, c.ext->surfaces, sizeof(ext->surfaces)); - memcpy(ext->verts(), c.ext->verts(), c.ext->maxverts*sizeof(vertinfo)); - } - else memset(ext->surfaces, 0, sizeof(ext->surfaces)); - } - setcubeext(c, ext); - return ext; + if(c.ext && c.ext->maxverts >= maxverts) return c.ext; + cubeext *ext = growcubeext(c.ext, maxverts); + if(init) + { + if(c.ext) + { + memcpy(ext->surfaces, c.ext->surfaces, sizeof(ext->surfaces)); + memcpy(ext->verts(), c.ext->verts(), c.ext->maxverts*sizeof(vertinfo)); + } + else memset(ext->surfaces, 0, sizeof(ext->surfaces)); + } + setcubeext(c, ext); + return ext; } cube *newcubes(uint face, int mat) { - cube *c = new cube[8]; - loopi(8) - { - c->children = NULL; - c->ext = NULL; - c->visible = 0; - c->merged = 0; - setfaces(*c, face); - loopl(6) c->texture[l] = DEFAULT_GEOM; - c->material = mat; - c++; - } - allocnodes++; - return c-8; + cube *c = new cube[8]; + loopi(8) + { + c->children = NULL; + c->ext = NULL; + c->visible = 0; + c->merged = 0; + setfaces(*c, face); + loopl(6) c->texture[l] = DEFAULT_GEOM; + c->material = mat; + c++; + } + allocnodes++; + return c-8; } int familysize(const cube &c) { - int size = 1; - if(c.children) loopi(8) size += familysize(c.children[i]); - return size; + int size = 1; + if(c.children) loopi(8) size += familysize(c.children[i]); + return size; } void freeocta(cube *c) { - if(!c) return; - loopi(8) discardchildren(c[i]); - delete[] c; - allocnodes--; + if(!c) return; + loopi(8) discardchildren(c[i]); + delete[] c; + allocnodes--; } void freecubeext(cube &c) { - if(c.ext) - { - delete[] (uchar *)c.ext; - c.ext = NULL; - } + if(c.ext) + { + delete[] (uchar *)c.ext; + c.ext = NULL; + } } void discardchildren(cube &c, bool fixtex, int depth) { - c.material = MAT_AIR; - c.visible = 0; - c.merged = 0; - if(c.ext) - { - if(c.ext->va) destroyva(c.ext->va); - c.ext->va = NULL; - c.ext->tjoints = -1; - freeoctaentities(c); - freecubeext(c); - } - if(c.children) - { - uint filled = F_EMPTY; - loopi(8) - { - discardchildren(c.children[i], fixtex, depth+1); - filled |= c.children[i].faces[0]; - } - if(fixtex) - { - loopi(6) c.texture[i] = getmippedtexture(c, i); - if(depth > 0 && filled != F_EMPTY) c.faces[0] = F_SOLID; - } - DELETEA(c.children); - allocnodes--; - } + c.material = MAT_AIR; + c.visible = 0; + c.merged = 0; + if(c.ext) + { + if(c.ext->va) destroyva(c.ext->va); + c.ext->va = NULL; + c.ext->tjoints = -1; + freeoctaentities(c); + freecubeext(c); + } + if(c.children) + { + uint filled = F_EMPTY; + loopi(8) + { + discardchildren(c.children[i], fixtex, depth+1); + filled |= c.children[i].faces[0]; + } + if(fixtex) + { + loopi(6) c.texture[i] = getmippedtexture(c, i); + if(depth > 0 && filled != F_EMPTY) c.faces[0] = F_SOLID; + } + DELETEA(c.children); + allocnodes--; + } } void getcubevector(cube &c, int d, int x, int y, int z, ivec &p) { - ivec v(d, x, y, z); + ivec v(d, x, y, z); - loopi(3) - p[i] = edgeget(cubeedge(c, i, v[R[i]], v[C[i]]), v[D[i]]); + loopi(3) + p[i] = edgeget(cubeedge(c, i, v[R[i]], v[C[i]]), v[D[i]]); } void setcubevector(cube &c, int d, int x, int y, int z, const ivec &p) { - ivec v(d, x, y, z); + ivec v(d, x, y, z); - loopi(3) - edgeset(cubeedge(c, i, v[R[i]], v[C[i]]), v[D[i]], p[i]); + loopi(3) + edgeset(cubeedge(c, i, v[R[i]], v[C[i]]), v[D[i]], p[i]); } static inline void getcubevector(cube &c, int i, ivec &p) { - p.x = edgeget(cubeedge(c, 0, (i>>R[0])&1, (i>>C[0])&1), (i>>D[0])&1); - p.y = edgeget(cubeedge(c, 1, (i>>R[1])&1, (i>>C[1])&1), (i>>D[1])&1); - p.z = edgeget(cubeedge(c, 2, (i>>R[2])&1, (i>>C[2])&1), (i>>D[2])&1); + p.x = edgeget(cubeedge(c, 0, (i>>R[0])&1, (i>>C[0])&1), (i>>D[0])&1); + p.y = edgeget(cubeedge(c, 1, (i>>R[1])&1, (i>>C[1])&1), (i>>D[1])&1); + p.z = edgeget(cubeedge(c, 2, (i>>R[2])&1, (i>>C[2])&1), (i>>D[2])&1); } static inline void setcubevector(cube &c, int i, const ivec &p) { - edgeset(cubeedge(c, 0, (i>>R[0])&1, (i>>C[0])&1), (i>>D[0])&1, p.x); - edgeset(cubeedge(c, 1, (i>>R[1])&1, (i>>C[1])&1), (i>>D[1])&1, p.y); - edgeset(cubeedge(c, 2, (i>>R[2])&1, (i>>C[2])&1), (i>>D[2])&1, p.z); + edgeset(cubeedge(c, 0, (i>>R[0])&1, (i>>C[0])&1), (i>>D[0])&1, p.x); + edgeset(cubeedge(c, 1, (i>>R[1])&1, (i>>C[1])&1), (i>>D[1])&1, p.y); + edgeset(cubeedge(c, 2, (i>>R[2])&1, (i>>C[2])&1), (i>>D[2])&1, p.z); } void optiface(uchar *p, cube &c) { - uint f = *(uint *)p; - if(((f>>4)&0x0F0F0F0FU) == (f&0x0F0F0F0FU)) emptyfaces(c); + uint f = *(uint *)p; + if(((f>>4)&0x0F0F0F0FU) == (f&0x0F0F0F0FU)) emptyfaces(c); } void printcube() { - cube &c = lookupcube(lu); // assume this is cube being pointed at - conoutf(CON_DEBUG, "= %p = (%d, %d, %d) @ %d", (void *)&c, lu.x, lu.y, lu.z, lusize); - conoutf(CON_DEBUG, " x %.8x", c.faces[0]); - conoutf(CON_DEBUG, " y %.8x", c.faces[1]); - conoutf(CON_DEBUG, " z %.8x", c.faces[2]); + cube &c = lookupcube(lu); // assume this is cube being pointed at + conoutf(CON_DEBUG, "= %p = (%d, %d, %d) @ %d", (void *)&c, lu.x, lu.y, lu.z, lusize); + conoutf(CON_DEBUG, " x %.8x", c.faces[0]); + conoutf(CON_DEBUG, " y %.8x", c.faces[1]); + conoutf(CON_DEBUG, " z %.8x", c.faces[2]); } COMMAND(printcube, ""); bool isvalidcube(const cube &c) { - clipplanes p; - genclipplanes(c, ivec(0, 0, 0), 256, p); - loopi(8) // test that cube is convex - { - vec v = p.v[i]; - loopj(p.size) if(p.p[j].dist(v)>1e-3f) return false; - } - return true; + clipplanes p; + genclipplanes(c, ivec(0, 0, 0), 256, p); + loopi(8) // test that cube is convex + { + vec v = p.v[i]; + loopj(p.size) if(p.p[j].dist(v)>1e-3f) return false; + } + return true; } void validatec(cube *c, int size) { - loopi(8) - { - if(c[i].children) - { - if(size<=1) - { - solidfaces(c[i]); - discardchildren(c[i], true); - } - else validatec(c[i].children, size>>1); - } - else if(size > 0x1000) - { - subdividecube(c[i], true, false); - validatec(c[i].children, size>>1); - } - else - { - loopj(3) - { - uint f = c[i].faces[j], e0 = f&0x0F0F0F0FU, e1 = (f>>4)&0x0F0F0F0FU; - if(e0 == e1 || ((e1+0x07070707U)|(e1-e0))&0xF0F0F0F0U) - { - emptyfaces(c[i]); - break; - } - } - } - } + loopi(8) + { + if(c[i].children) + { + if(size<=1) + { + solidfaces(c[i]); + discardchildren(c[i], true); + } + else validatec(c[i].children, size>>1); + } + else if(size > 0x1000) + { + subdividecube(c[i], true, false); + validatec(c[i].children, size>>1); + } + else + { + loopj(3) + { + uint f = c[i].faces[j], e0 = f&0x0F0F0F0FU, e1 = (f>>4)&0x0F0F0F0FU; + if(e0 == e1 || ((e1+0x07070707U)|(e1-e0))&0xF0F0F0F0U) + { + emptyfaces(c[i]); + break; + } + } + } + } } ivec lu; int lusize; cube &lookupcube(const ivec &to, int tsize, ivec &ro, int &rsize) { - int tx = clamp(to.x, 0, worldsize-1), - ty = clamp(to.y, 0, worldsize-1), - tz = clamp(to.z, 0, worldsize-1); - int scale = worldscale-1, csize = abs(tsize); - cube *c = &worldroot[octastep(tx, ty, tz, scale)]; - if(!(csize>>scale)) do - { - if(!c->children) - { - if(tsize > 0) do - { - subdividecube(*c); - scale--; - c = &c->children[octastep(tx, ty, tz, scale)]; - } while(!(csize>>scale)); - break; - } - scale--; - c = &c->children[octastep(tx, ty, tz, scale)]; - } while(!(csize>>scale)); - ro = ivec(tx, ty, tz).mask(~0U<>scale)) do + { + if(!c->children) + { + if(tsize > 0) do + { + subdividecube(*c); + scale--; + c = &c->children[octastep(tx, ty, tz, scale)]; + } while(!(csize>>scale)); + break; + } + scale--; + c = &c->children[octastep(tx, ty, tz, scale)]; + } while(!(csize>>scale)); + ro = ivec(tx, ty, tz).mask(~0U<children) - { - scale--; - c = &c->children[octastep(o.x, o.y, o.z, scale)]; - } - return c->material; + ivec o(v); + if(!insideworld(o)) return MAT_AIR; + int scale = worldscale-1; + cube *c = &worldroot[octastep(o.x, o.y, o.z, scale)]; + while(c->children) + { + scale--; + c = &c->children[octastep(o.x, o.y, o.z, scale)]; + } + return c->material; } const cube *neighbourstack[32]; @@ -262,198 +262,198 @@ int neighbourdepth = -1; const cube &neighbourcube(const cube &c, int orient, const ivec &co, int size, ivec &ro, int &rsize) { - ivec n = co; - int dim = dimension(orient); - uint diff = n[dim]; - if(dimcoord(orient)) n[dim] += size; else n[dim] -= size; - diff ^= n[dim]; - if(diff >= uint(worldsize)) { ro = n; rsize = size; return c; } - int scale = worldscale; - const cube *nc = worldroot; - if(neighbourdepth >= 0) - { - scale -= neighbourdepth + 1; - diff >>= scale; - do { scale++; diff >>= 1; } while(diff); - nc = neighbourstack[worldscale - scale]; - } - scale--; - nc = &nc[octastep(n.x, n.y, n.z, scale)]; - if(!(size>>scale) && nc->children) do - { - scale--; - nc = &nc->children[octastep(n.x, n.y, n.z, scale)]; - } while(!(size>>scale) && nc->children); - ro = n.mask(~0U<= uint(worldsize)) { ro = n; rsize = size; return c; } + int scale = worldscale; + const cube *nc = worldroot; + if(neighbourdepth >= 0) + { + scale -= neighbourdepth + 1; + diff >>= scale; + do { scale++; diff >>= 1; } while(diff); + nc = neighbourstack[worldscale - scale]; + } + scale--; + nc = &nc[octastep(n.x, n.y, n.z, scale)]; + if(!(size>>scale) && nc->children) do + { + scale--; + nc = &nc->children[octastep(n.x, n.y, n.z, scale)]; + } while(!(size>>scale) && nc->children); + ro = n.mask(~0U< DEFAULT_SKY) loopi(numtexs) if(texs[i] == tex) return tex; - texs[numtexs++] = tex; - } - loopirev(numtexs) if(!i || texs[i] > DEFAULT_SKY) return texs[i]; - return DEFAULT_GEOM; + cube *c = p.children; + int d = dimension(orient), dc = dimcoord(orient), texs[4] = { -1, -1, -1, -1 }, numtexs = 0; + loop(x, 2) loop(y, 2) + { + int n = octaindex(d, x, y, dc); + if(isempty(c[n])) + { + n = oppositeocta(d, n); + if(isempty(c[n])) + continue; + } + int tex = c[n].texture[orient]; + if(tex > DEFAULT_SKY) loopi(numtexs) if(texs[i] == tex) return tex; + texs[numtexs++] = tex; + } + loopirev(numtexs) if(!i || texs[i] > DEFAULT_SKY) return texs[i]; + return DEFAULT_GEOM; } void forcemip(cube &c, bool fixtex) { - cube *ch = c.children; - emptyfaces(c); + cube *ch = c.children; + emptyfaces(c); - loopi(8) loopj(8) - { - int n = i^(j==3 ? 4 : (j==4 ? 3 : j)); - if(!isempty(ch[n])) // breadth first search for cube near vert - { - ivec v; - getcubevector(ch[n], i, v); - // adjust vert to parent size - setcubevector(c, i, ivec(n, v, 8).shr(1)); - break; - } - } + loopi(8) loopj(8) + { + int n = i^(j==3 ? 4 : (j==4 ? 3 : j)); + if(!isempty(ch[n])) // breadth first search for cube near vert + { + ivec v; + getcubevector(ch[n], i, v); + // adjust vert to parent size + setcubevector(c, i, ivec(n, v, 8).shr(1)); + break; + } + } - if(fixtex) loopj(6) - c.texture[j] = getmippedtexture(c, j); + if(fixtex) loopj(6) + c.texture[j] = getmippedtexture(c, j); } static int midedge(const ivec &a, const ivec &b, int xd, int yd, bool &perfect) { - int ax = a[xd], ay = a[yd], bx = b[xd], by = b[yd]; - if(ay==by) return ay; - if(ax==bx) { perfect = false; return ay; } - bool crossx = (ax<8 && bx>8) || (ax>8 && bx<8); - bool crossy = (ay<8 && by>8) || (ay>8 && by<8); - if(crossy && !crossx) { midedge(a,b,yd,xd,perfect); return 8; } // to test perfection - if(ax<=8 && bx<=8) return ax>bx ? ay : by; - if(ax>=8 && bx>=8) return ax16)) perfect = false; - return crossy ? 8 : min(max(y, 0), 16); + int ax = a[xd], ay = a[yd], bx = b[xd], by = b[yd]; + if(ay==by) return ay; + if(ax==bx) { perfect = false; return ay; } + bool crossx = (ax<8 && bx>8) || (ax>8 && bx<8); + bool crossy = (ay<8 && by>8) || (ay>8 && by<8); + if(crossy && !crossx) { midedge(a,b,yd,xd,perfect); return 8; } // to test perfection + if(ax<=8 && bx<=8) return ax>bx ? ay : by; + if(ax>=8 && bx>=8) return ax16)) perfect = false; + return crossy ? 8 : min(max(y, 0), 16); } static inline bool crosscenter(const ivec &a, const ivec &b, int xd, int yd) { - int ax = a[xd], ay = a[yd], bx = b[xd], by = b[yd]; - return (((ax <= 8 && bx <= 8) || (ax >= 8 && bx >= 8)) && - ((ay <= 8 && by <= 8) || (ay >= 8 && by >= 8))) || - (ax + bx == 16 && ay + by == 16); + int ax = a[xd], ay = a[yd], bx = b[xd], by = b[yd]; + return (((ax <= 8 && bx <= 8) || (ax >= 8 && bx >= 8)) && + ((ay <= 8 && by <= 8) || (ay >= 8 && by >= 8))) || + (ax + bx == 16 && ay + by == 16); } bool subdividecube(cube &c, bool fullcheck, bool brighten) { - if(c.children) return true; - if(c.ext) memset(c.ext->surfaces, 0, sizeof(c.ext->surfaces)); + if(c.children) return true; + if(c.ext) memset(c.ext->surfaces, 0, sizeof(c.ext->surfaces)); if(isempty(c) || isentirelysolid(c)) - { + { c.children = newcubes(isempty(c) ? F_EMPTY : F_SOLID, c.material); - loopi(8) - { - loopl(6) c.children[i].texture[l] = c.texture[l]; - if(brighten && !isempty(c)) brightencube(c.children[i]); - } - return true; - } - cube *ch = c.children = newcubes(F_SOLID, c.material); - bool perfect = true; - ivec v[8]; - loopi(8) - { - getcubevector(c, i, v[i]); - v[i].mul(2); - } - - loopj(6) - { - int d = dimension(j), z = dimcoord(j); - const ivec &v00 = v[octaindex(d, 0, 0, z)], - &v10 = v[octaindex(d, 1, 0, z)], - &v01 = v[octaindex(d, 0, 1, z)], - &v11 = v[octaindex(d, 1, 1, z)]; - int e[3][3]; - // corners - e[0][0] = v00[d]; - e[0][2] = v01[d]; - e[2][0] = v10[d]; - e[2][2] = v11[d]; - // edges - e[0][1] = midedge(v00, v01, C[d], d, perfect); - e[1][0] = midedge(v00, v10, R[d], d, perfect); - e[1][2] = midedge(v11, v01, R[d], d, perfect); - e[2][1] = midedge(v11, v10, C[d], d, perfect); - // center - bool p1 = perfect, p2 = perfect; - int c1 = midedge(v00, v11, R[d], d, p1); - int c2 = midedge(v01, v10, R[d], d, p2); - if(z ? c1 > c2 : c1 < c2) - { - e[1][1] = c1; - perfect = p1 && (c1 == c2 || crosscenter(v00, v11, C[d], R[d])); - } - else - { - e[1][1] = c2; - perfect = p2 && (c1 == c2 || crosscenter(v01, v10, C[d], R[d])); - } - - loopi(8) - { - ch[i].texture[j] = c.texture[j]; - int rd = (i>>R[d])&1, cd = (i>>C[d])&1, dd = (i>>D[d])&1; - edgeset(cubeedge(ch[i], d, 0, 0), z, clamp(e[rd][cd] - dd*8, 0, 8)); - edgeset(cubeedge(ch[i], d, 1, 0), z, clamp(e[1+rd][cd] - dd*8, 0, 8)); - edgeset(cubeedge(ch[i], d, 0, 1), z, clamp(e[rd][1+cd] - dd*8, 0, 8)); - edgeset(cubeedge(ch[i], d, 1, 1), z, clamp(e[1+rd][1+cd] - dd*8, 0, 8)); - } - } - - validatec(ch); - if(fullcheck) loopi(8) if(!isvalidcube(ch[i])) // not so good... - { - emptyfaces(ch[i]); - perfect=false; - } - if(brighten) loopi(8) if(!isempty(ch[i])) brightencube(ch[i]); - return perfect; + loopi(8) + { + loopl(6) c.children[i].texture[l] = c.texture[l]; + if(brighten && !isempty(c)) brightencube(c.children[i]); + } + return true; + } + cube *ch = c.children = newcubes(F_SOLID, c.material); + bool perfect = true; + ivec v[8]; + loopi(8) + { + getcubevector(c, i, v[i]); + v[i].mul(2); + } + + loopj(6) + { + int d = dimension(j), z = dimcoord(j); + const ivec &v00 = v[octaindex(d, 0, 0, z)], + &v10 = v[octaindex(d, 1, 0, z)], + &v01 = v[octaindex(d, 0, 1, z)], + &v11 = v[octaindex(d, 1, 1, z)]; + int e[3][3]; + // corners + e[0][0] = v00[d]; + e[0][2] = v01[d]; + e[2][0] = v10[d]; + e[2][2] = v11[d]; + // edges + e[0][1] = midedge(v00, v01, C[d], d, perfect); + e[1][0] = midedge(v00, v10, R[d], d, perfect); + e[1][2] = midedge(v11, v01, R[d], d, perfect); + e[2][1] = midedge(v11, v10, C[d], d, perfect); + // center + bool p1 = perfect, p2 = perfect; + int c1 = midedge(v00, v11, R[d], d, p1); + int c2 = midedge(v01, v10, R[d], d, p2); + if(z ? c1 > c2 : c1 < c2) + { + e[1][1] = c1; + perfect = p1 && (c1 == c2 || crosscenter(v00, v11, C[d], R[d])); + } + else + { + e[1][1] = c2; + perfect = p2 && (c1 == c2 || crosscenter(v01, v10, C[d], R[d])); + } + + loopi(8) + { + ch[i].texture[j] = c.texture[j]; + int rd = (i>>R[d])&1, cd = (i>>C[d])&1, dd = (i>>D[d])&1; + edgeset(cubeedge(ch[i], d, 0, 0), z, clamp(e[rd][cd] - dd*8, 0, 8)); + edgeset(cubeedge(ch[i], d, 1, 0), z, clamp(e[1+rd][cd] - dd*8, 0, 8)); + edgeset(cubeedge(ch[i], d, 0, 1), z, clamp(e[rd][1+cd] - dd*8, 0, 8)); + edgeset(cubeedge(ch[i], d, 1, 1), z, clamp(e[1+rd][1+cd] - dd*8, 0, 8)); + } + } + + validatec(ch); + if(fullcheck) loopi(8) if(!isvalidcube(ch[i])) // not so good... + { + emptyfaces(ch[i]); + perfect=false; + } + if(brighten) loopi(8) if(!isempty(ch[i])) brightencube(ch[i]); + return perfect; } bool crushededge(uchar e, int dc) { return dc ? e==0 : e==0x88; } int visibleorient(const cube &c, int orient) { - loopi(2) - { - int a = faceedgesidx[orient][i*2 + 0]; - int b = faceedgesidx[orient][i*2 + 1]; - loopj(2) - { - if(crushededge(c.edges[a],j) && - crushededge(c.edges[b],j) && - touchingface(c, orient)) return ((a>>2)<<1) + j; - } - } - return orient; + loopi(2) + { + int a = faceedgesidx[orient][i*2 + 0]; + int b = faceedgesidx[orient][i*2 + 1]; + loopj(2) + { + if(crushededge(c.edges[a],j) && + crushededge(c.edges[b],j) && + touchingface(c, orient)) return ((a>>2)<<1) + j; + } + } + return orient; } VAR(mipvis, 0, 0, 1); @@ -462,935 +462,935 @@ static int remipprogress = 0, remiptotal = 0; bool remip(cube &c, const ivec &co, int size) { - cube *ch = c.children; - if(!ch) - { - if(size<<1 <= 0x1000) return true; - subdividecube(c); - ch = c.children; - } - else if((remipprogress++&0xFFF)==1) renderprogress(float(remipprogress)/remiptotal, "remipping..."); - - bool perfect = true; - loopi(8) - { - ivec o(i, co, size); - if(!remip(ch[i], o, size>>1)) perfect = false; - } - - solidfaces(c); // so texmip is more consistent - loopj(6) - c.texture[j] = getmippedtexture(c, j); // parents get child texs regardless - - if(!perfect) return false; - if(size<<1 > 0x1000) return false; - - ushort mat = MAT_AIR; - loopi(8) - { - mat = ch[i].material; - if((mat&MATF_CLIP) == MAT_NOCLIP || mat&MAT_ALPHA) - { - if(i > 0) return false; - while(++i < 8) if(ch[i].material != mat) return false; - break; - } - else if(!isentirelysolid(ch[i])) - { - while(++i < 8) - { - int omat = ch[i].material; - if(isentirelysolid(ch[i]) ? (omat&MATF_CLIP) == MAT_NOCLIP || omat&MAT_ALPHA : mat != omat) return false; - } - break; - } - } - - cube n = c; - n.ext = NULL; - forcemip(n); - n.children = NULL; - if(!subdividecube(n, false, false)) - { freeocta(n.children); return false; } - - cube *nh = n.children; - uchar vis[6] = {0, 0, 0, 0, 0, 0}; - loopi(8) - { - if(ch[i].faces[0] != nh[i].faces[0] || - ch[i].faces[1] != nh[i].faces[1] || - ch[i].faces[2] != nh[i].faces[2]) - { freeocta(nh); return false; } - - if(isempty(ch[i]) && isempty(nh[i])) continue; - - ivec o(i, co, size); - loop(orient, 6) - if(visibleface(ch[i], orient, o, size, MAT_AIR, (mat&MAT_ALPHA)^MAT_ALPHA, MAT_ALPHA)) - { - if(ch[i].texture[orient] != n.texture[orient]) { freeocta(nh); return false; } - vis[orient] |= 1<>1)) perfect = false; + } + + solidfaces(c); // so texmip is more consistent + loopj(6) + c.texture[j] = getmippedtexture(c, j); // parents get child texs regardless + + if(!perfect) return false; + if(size<<1 > 0x1000) return false; + + ushort mat = MAT_AIR; + loopi(8) + { + mat = ch[i].material; + if((mat&MATF_CLIP) == MAT_NOCLIP || mat&MAT_ALPHA) + { + if(i > 0) return false; + while(++i < 8) if(ch[i].material != mat) return false; + break; + } + else if(!isentirelysolid(ch[i])) + { + while(++i < 8) + { + int omat = ch[i].material; + if(isentirelysolid(ch[i]) ? (omat&MATF_CLIP) == MAT_NOCLIP || omat&MAT_ALPHA : mat != omat) return false; + } + break; + } + } + + cube n = c; + n.ext = NULL; + forcemip(n); + n.children = NULL; + if(!subdividecube(n, false, false)) + { freeocta(n.children); return false; } + + cube *nh = n.children; + uchar vis[6] = {0, 0, 0, 0, 0, 0}; + loopi(8) + { + if(ch[i].faces[0] != nh[i].faces[0] || + ch[i].faces[1] != nh[i].faces[1] || + ch[i].faces[2] != nh[i].faces[2]) + { freeocta(nh); return false; } + + if(isempty(ch[i]) && isempty(nh[i])) continue; + + ivec o(i, co, size); + loop(orient, 6) + if(visibleface(ch[i], orient, o, size, MAT_AIR, (mat&MAT_ALPHA)^MAT_ALPHA, MAT_ALPHA)) + { + if(ch[i].texture[orient] != n.texture[orient]) { freeocta(nh); return false; } + vis[orient] |= 1<>1); - remip(worldroot[i], o, worldsize>>2); - } - calcmerges(); - if(!local) allchanged(); + extern selinfo sel; + if(local) game::edittrigger(sel, EDIT_REMIP); + remipprogress = 1; + remiptotal = allocnodes; + loopi(8) + { + ivec o(i, ivec(0, 0, 0), worldsize>>1); + remip(worldroot[i], o, worldsize>>2); + } + calcmerges(); + if(!local) allchanged(); } void remip_() { - mpremip(true); - allchanged(); + mpremip(true); + allchanged(); } COMMANDN(remip, remip_, ""); static inline int edgeval(cube &c, const ivec &p, int dim, int coord) { - return edgeget(cubeedge(c, dim, p[R[dim]]>>3, p[C[dim]]>>3), coord); + return edgeget(cubeedge(c, dim, p[R[dim]]>>3, p[C[dim]]>>3), coord); } void genvertp(cube &c, ivec &p1, ivec &p2, ivec &p3, plane &pl, bool solid = false) { - int dim = 0; - if(p1.y==p2.y && p2.y==p3.y) dim = 1; - else if(p1.z==p2.z && p2.z==p3.z) dim = 2; + int dim = 0; + if(p1.y==p2.y && p2.y==p3.y) dim = 1; + else if(p1.z==p2.z && p2.z==p3.z) dim = 2; - int coord = p1[dim]; - ivec v1(p1), v2(p2), v3(p3); - v1[dim] = solid ? coord*8 : edgeval(c, p1, dim, coord); - v2[dim] = solid ? coord*8 : edgeval(c, p2, dim, coord); - v3[dim] = solid ? coord*8 : edgeval(c, p3, dim, coord); + int coord = p1[dim]; + ivec v1(p1), v2(p2), v3(p3); + v1[dim] = solid ? coord*8 : edgeval(c, p1, dim, coord); + v2[dim] = solid ? coord*8 : edgeval(c, p2, dim, coord); + v3[dim] = solid ? coord*8 : edgeval(c, p3, dim, coord); - pl.toplane(vec(v1), vec(v2), vec(v3)); + pl.toplane(vec(v1), vec(v2), vec(v3)); } static bool threeplaneintersect(plane &pl1, plane &pl2, plane &pl3, vec &dest) { - vec &t1 = dest, t2, t3, t4; - t1.cross(pl1, pl2); t4 = t1; t1.mul(pl3.offset); - t2.cross(pl3, pl1); t2.mul(pl2.offset); - t3.cross(pl2, pl3); t3.mul(pl1.offset); - t1.add(t2); - t1.add(t3); - t1.mul(-1); - float d = t4.dot(pl3); - if(d==0) return false; - t1.div(d); - return true; + vec &t1 = dest, t2, t3, t4; + t1.cross(pl1, pl2); t4 = t1; t1.mul(pl3.offset); + t2.cross(pl3, pl1); t2.mul(pl2.offset); + t3.cross(pl2, pl3); t3.mul(pl1.offset); + t1.add(t2); + t1.add(t3); + t1.mul(-1); + float d = t4.dot(pl3); + if(d==0) return false; + t1.div(d); + return true; } static void genedgespanvert(ivec &p, cube &c, vec &v) { - ivec p1(8-p.x, p.y, p.z); - ivec p2(p.x, 8-p.y, p.z); - ivec p3(p.x, p.y, 8-p.z); + ivec p1(8-p.x, p.y, p.z); + ivec p2(p.x, 8-p.y, p.z); + ivec p3(p.x, p.y, 8-p.z); - plane plane1, plane2, plane3; - genvertp(c, p, p1, p2, plane1); - genvertp(c, p, p2, p3, plane2); - genvertp(c, p, p3, p1, plane3); - if(plane1==plane2) genvertp(c, p, p1, p2, plane1, true); - if(plane1==plane3) genvertp(c, p, p1, p2, plane1, true); - if(plane2==plane3) genvertp(c, p, p2, p3, plane2, true); + plane plane1, plane2, plane3; + genvertp(c, p, p1, p2, plane1); + genvertp(c, p, p2, p3, plane2); + genvertp(c, p, p3, p1, plane3); + if(plane1==plane2) genvertp(c, p, p1, p2, plane1, true); + if(plane1==plane3) genvertp(c, p, p1, p2, plane1, true); + if(plane2==plane3) genvertp(c, p, p2, p3, plane2, true); - ASSERT(threeplaneintersect(plane1, plane2, plane3, v)); - //ASSERT(v.x>=0 && v.x<=8); - //ASSERT(v.y>=0 && v.y<=8); - //ASSERT(v.z>=0 && v.z<=8); - v.x = max(0.0f, min(8.0f, v.x)); - v.y = max(0.0f, min(8.0f, v.y)); - v.z = max(0.0f, min(8.0f, v.z)); + ASSERT(threeplaneintersect(plane1, plane2, plane3, v)); + //ASSERT(v.x>=0 && v.x<=8); + //ASSERT(v.y>=0 && v.y<=8); + //ASSERT(v.z>=0 && v.z<=8); + v.x = max(0.0f, min(8.0f, v.x)); + v.y = max(0.0f, min(8.0f, v.y)); + v.z = max(0.0f, min(8.0f, v.z)); } void edgespan2vectorcube(cube &c) { - if(isentirelysolid(c) || isempty(c)) return; - cube o = c; - loop(x, 2) loop(y, 2) loop(z, 2) - { - ivec p(8*x, 8*y, 8*z); - vec v; - genedgespanvert(p, o, v); + if(isentirelysolid(c) || isempty(c)) return; + cube o = c; + loop(x, 2) loop(y, 2) loop(z, 2) + { + ivec p(8*x, 8*y, 8*z); + vec v; + genedgespanvert(p, o, v); - edgeset(cubeedge(c, 0, y, z), x, int(v.x+0.49f)); - edgeset(cubeedge(c, 1, z, x), y, int(v.y+0.49f)); - edgeset(cubeedge(c, 2, x, y), z, int(v.z+0.49f)); - } + edgeset(cubeedge(c, 0, y, z), x, int(v.x+0.49f)); + edgeset(cubeedge(c, 1, z, x), y, int(v.y+0.49f)); + edgeset(cubeedge(c, 2, x, y), z, int(v.z+0.49f)); + } } const ivec cubecoords[8] = // verts of bounding cube { #define GENCUBEVERT(n, x, y, z) ivec(x, y, z), - GENCUBEVERTS(0, 8, 0, 8, 0, 8) -#undef GENCUBEVERT + GENCUBEVERTS(0, 8, 0, 8, 0, 8) +#undef GENCUBEVERT }; template static inline void gencubevert(const cube &c, int i, T &v) { - switch(i) - { - default: + switch(i) + { + default: #define GENCUBEVERT(n, x, y, z) \ - case n: \ - v = T(edgeget(cubeedge(c, 0, y, z), x), \ - edgeget(cubeedge(c, 1, z, x), y), \ - edgeget(cubeedge(c, 2, x, y), z)); \ - break; - GENCUBEVERTS(0, 1, 0, 1, 0, 1) + case n: \ + v = T(edgeget(cubeedge(c, 0, y, z), x), \ + edgeget(cubeedge(c, 1, z, x), y), \ + edgeget(cubeedge(c, 2, x, y), z)); \ + break; + GENCUBEVERTS(0, 1, 0, 1, 0, 1) #undef GENCUBEVERT - } + } } void genfaceverts(const cube &c, int orient, ivec v[4]) { - switch(orient) - { - default: + switch(orient) + { + default: #define GENFACEORIENT(o, v0, v1, v2, v3) \ - case o: v0 v1 v2 v3 break; + case o: v0 v1 v2 v3 break; #define GENFACEVERT(o, n, x,y,z, xv,yv,zv) \ - v[n] = ivec(edgeget(cubeedge(c, 0, y, z), x), \ - edgeget(cubeedge(c, 1, z, x), y), \ - edgeget(cubeedge(c, 2, x, y), z)); - GENFACEVERTS(0, 1, 0, 1, 0, 1, , , , , , ) - #undef GENFACEORIENT - #undef GENFACEVERT - } + v[n] = ivec(edgeget(cubeedge(c, 0, y, z), x), \ + edgeget(cubeedge(c, 1, z, x), y), \ + edgeget(cubeedge(c, 2, x, y), z)); + GENFACEVERTS(0, 1, 0, 1, 0, 1, , , , , , ) + #undef GENFACEORIENT + #undef GENFACEVERT + } } const ivec facecoords[6][4] = { #define GENFACEORIENT(o, v0, v1, v2, v3) \ - { v0, v1, v2, v3 }, + { v0, v1, v2, v3 }, #define GENFACEVERT(o, n, x,y,z, xv,yv,zv) \ - ivec(x,y,z) - GENFACEVERTS(0, 8, 0, 8, 0, 8, , , , , , ) + ivec(x,y,z) + GENFACEVERTS(0, 8, 0, 8, 0, 8, , , , , , ) #undef GENFACEORIENT #undef GENFACEVERT }; const uchar fv[6][4] = // indexes for cubecoords, per each vert of a face orientation { - { 2, 1, 6, 5 }, - { 3, 4, 7, 0 }, - { 4, 5, 6, 7 }, - { 1, 2, 3, 0 }, - { 6, 1, 0, 7 }, - { 5, 4, 3, 2 }, + { 2, 1, 6, 5 }, + { 3, 4, 7, 0 }, + { 4, 5, 6, 7 }, + { 1, 2, 3, 0 }, + { 6, 1, 0, 7 }, + { 5, 4, 3, 2 }, }; const uchar fvmasks[64] = // mask of verts used given a mask of visible face orientations { - 0x00, 0x66, 0x99, 0xFF, 0xF0, 0xF6, 0xF9, 0xFF, - 0x0F, 0x6F, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xC3, 0xE7, 0xDB, 0xFF, 0xF3, 0xF7, 0xFB, 0xFF, - 0xCF, 0xEF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0x3C, 0x7E, 0xBD, 0xFF, 0xFC, 0xFE, 0xFD, 0xFF, - 0x3F, 0x7F, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x66, 0x99, 0xFF, 0xF0, 0xF6, 0xF9, 0xFF, + 0x0F, 0x6F, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC3, 0xE7, 0xDB, 0xFF, 0xF3, 0xF7, 0xFB, 0xFF, + 0xCF, 0xEF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3C, 0x7E, 0xBD, 0xFF, 0xFC, 0xFE, 0xFD, 0xFF, + 0x3F, 0x7F, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; const uchar faceedgesidx[6][4] = // ordered edges surrounding each orient {//0..1 = row edges, 2..3 = column edges - { 4, 5, 8, 10 }, - { 6, 7, 9, 11 }, - { 8, 9, 0, 2 }, - { 10, 11, 1, 3 }, - { 0, 1, 4, 6 }, - { 2, 3, 5, 7 }, + { 4, 5, 8, 10 }, + { 6, 7, 9, 11 }, + { 8, 9, 0, 2 }, + { 10, 11, 1, 3 }, + { 0, 1, 4, 6 }, + { 2, 3, 5, 7 }, }; bool flataxisface(const cube &c, int orient) { - uint face = c.faces[dimension(orient)]; - if(dimcoord(orient)) face >>= 4; - return (face&0x0F0F0F0F) == 0x01010101*(face&0x0F); + uint face = c.faces[dimension(orient)]; + if(dimcoord(orient)) face >>= 4; + return (face&0x0F0F0F0F) == 0x01010101*(face&0x0F); } bool collideface(const cube &c, int orient) { - if(flataxisface(c, orient)) - { - uchar r1 = c.edges[faceedgesidx[orient][0]], r2 = c.edges[faceedgesidx[orient][1]]; - if(uchar((r1>>4)|(r2&0xF0)) == uchar((r1&0x0F)|(r2<<4))) return false; - uchar c1 = c.edges[faceedgesidx[orient][2]], c2 = c.edges[faceedgesidx[orient][3]]; - if(uchar((c1>>4)|(c2&0xF0)) == uchar((c1&0x0F)|(c2<<4))) return false; - } - return true; + if(flataxisface(c, orient)) + { + uchar r1 = c.edges[faceedgesidx[orient][0]], r2 = c.edges[faceedgesidx[orient][1]]; + if(uchar((r1>>4)|(r2&0xF0)) == uchar((r1&0x0F)|(r2<<4))) return false; + uchar c1 = c.edges[faceedgesidx[orient][2]], c2 = c.edges[faceedgesidx[orient][3]]; + if(uchar((c1>>4)|(c2&0xF0)) == uchar((c1&0x0F)|(c2<<4))) return false; + } + return true; } bool touchingface(const cube &c, int orient) { - uint face = c.faces[dimension(orient)]; - return dimcoord(orient) ? (face&0xF0F0F0F0)==0x80808080 : (face&0x0F0F0F0F)==0; + uint face = c.faces[dimension(orient)]; + return dimcoord(orient) ? (face&0xF0F0F0F0)==0x80808080 : (face&0x0F0F0F0F)==0; } bool notouchingface(const cube &c, int orient) { - uint face = c.faces[dimension(orient)]; - return dimcoord(orient) ? (face&0x80808080)==0 : ((0x88888888-face)&0x08080808) == 0; -} + uint face = c.faces[dimension(orient)]; + return dimcoord(orient) ? (face&0x80808080)==0 : ((0x88888888-face)&0x08080808) == 0; +} int faceconvexity(const ivec v[4]) { - ivec n; - n.cross(ivec(v[1]).sub(v[0]), ivec(v[2]).sub(v[0])); - return ivec(v[0]).sub(v[3]).dot(n); - // 1 if convex, -1 if concave, 0 if flat + ivec n; + n.cross(ivec(v[1]).sub(v[0]), ivec(v[2]).sub(v[0])); + return ivec(v[0]).sub(v[3]).dot(n); + // 1 if convex, -1 if concave, 0 if flat } int faceconvexity(const vertinfo *verts, int numverts, int size) { - if(numverts < 4) return 0; - ivec v0 = verts[0].getxyz(), - e1 = verts[1].getxyz().sub(v0), - e2 = verts[2].getxyz().sub(v0), - n; - if(size >= (8<<5)) - { - if(size >= (8<<10)) n.cross(e1.shr(10), e2.shr(10)); - else n.cross(e1, e2).shr(10); - } - else n.cross(e1, e2); - return verts[3].getxyz().sub(v0).dot(n); + if(numverts < 4) return 0; + ivec v0 = verts[0].getxyz(), + e1 = verts[1].getxyz().sub(v0), + e2 = verts[2].getxyz().sub(v0), + n; + if(size >= (8<<5)) + { + if(size >= (8<<10)) n.cross(e1.shr(10), e2.shr(10)); + else n.cross(e1, e2).shr(10); + } + else n.cross(e1, e2); + return verts[3].getxyz().sub(v0).dot(n); } int faceconvexity(const ivec v[4], int &vis) { - ivec e1, e2, e3, n; - n.cross((e1 = v[1]).sub(v[0]), (e2 = v[2]).sub(v[0])); - int convex = (e3 = v[0]).sub(v[3]).dot(n); - if(!convex) - { - if(ivec().cross(e3, e2).iszero()) { if(!n.iszero()) vis = 1; } - else if(n.iszero()) { vis = 2; } - return 0; - } - return convex; -} + ivec e1, e2, e3, n; + n.cross((e1 = v[1]).sub(v[0]), (e2 = v[2]).sub(v[0])); + int convex = (e3 = v[0]).sub(v[3]).dot(n); + if(!convex) + { + if(ivec().cross(e3, e2).iszero()) { if(!n.iszero()) vis = 1; } + else if(n.iszero()) { vis = 2; } + return 0; + } + return convex; +} int faceconvexity(const cube &c, int orient) { - if(flataxisface(c, orient)) return 0; - ivec v[4]; - genfaceverts(c, orient, v); - return faceconvexity(v); + if(flataxisface(c, orient)) return 0; + ivec v[4]; + genfaceverts(c, orient, v); + return faceconvexity(v); } int faceorder(const cube &c, int orient) // gets above 'fv' so that each face is convex { - return faceconvexity(c, orient)<0 ? 1 : 0; + return faceconvexity(c, orient)<0 ? 1 : 0; } static inline void faceedges(const cube &c, int orient, uchar edges[4]) { - loopk(4) edges[k] = c.edges[faceedgesidx[orient][k]]; + loopk(4) edges[k] = c.edges[faceedgesidx[orient][k]]; } uint faceedges(const cube &c, int orient) { - union { uchar edges[4]; uint face; } u; - faceedges(c, orient, u.edges); - return u.face; + union { uchar edges[4]; uint face; } u; + faceedges(c, orient, u.edges); + return u.face; } static inline int genfacevecs(const cube &cu, int orient, const ivec &pos, int size, bool solid, ivec2 *fvecs, const ivec *v = NULL) { - int i = 0; - if(solid) - { - switch(orient) - { - #define GENFACEORIENT(orient, v0, v1, v2, v3) \ - case orient: \ - { \ - if(dimcoord(orient)) { v0 v1 v2 v3 } else { v3 v2 v1 v0 } \ - break; \ - } - #define GENFACEVERT(orient, vert, xv,yv,zv, x,y,z) \ - { ivec2 &f = fvecs[i]; x ((xv)<<3); y ((yv)<<3); z ((zv)<<3); i++; } - GENFACEVERTS(pos.x, pos.x+size, pos.y, pos.y+size, pos.z, pos.z+size, f.x = , f.x = , f.y = , f.y = , (void), (void)) - #undef GENFACEVERT - } - return 4; - } - ivec buf[4]; - if(!v) { genfaceverts(cu, orient, buf); v = buf; } - ivec2 prev(INT_MAX, INT_MAX); - switch(orient) - { - #define GENFACEVERT(orient, vert, sx,sy,sz, dx,dy,dz) \ - { \ - const ivec &e = v[vert]; \ - ivec ef; \ - ef.dx = e.sx; ef.dy = e.sy; ef.dz = e.sz; \ - if(ef.z == dimcoord(orient)*8) \ - { \ - ivec2 &f = fvecs[i]; \ - ivec pf; \ - pf.dx = pos.sx; pf.dy = pos.sy; pf.dz = pos.sz; \ - f = ivec2(ef.x*size + (pf.x<<3), ef.y*size + (pf.y<<3)); \ - if(f != prev) { prev = f; i++; } \ - } \ - } - GENFACEVERTS(x, x, y, y, z, z, x, x, y, y, z, z) - #undef GENFACEORIENT - #undef GENFACEVERT - } - if(fvecs[0] == prev) i--; - return i; + int i = 0; + if(solid) + { + switch(orient) + { + #define GENFACEORIENT(orient, v0, v1, v2, v3) \ + case orient: \ + { \ + if(dimcoord(orient)) { v0 v1 v2 v3 } else { v3 v2 v1 v0 } \ + break; \ + } + #define GENFACEVERT(orient, vert, xv,yv,zv, x,y,z) \ + { ivec2 &f = fvecs[i]; x ((xv)<<3); y ((yv)<<3); z ((zv)<<3); i++; } + GENFACEVERTS(pos.x, pos.x+size, pos.y, pos.y+size, pos.z, pos.z+size, f.x = , f.x = , f.y = , f.y = , (void), (void)) + #undef GENFACEVERT + } + return 4; + } + ivec buf[4]; + if(!v) { genfaceverts(cu, orient, buf); v = buf; } + ivec2 prev(INT_MAX, INT_MAX); + switch(orient) + { + #define GENFACEVERT(orient, vert, sx,sy,sz, dx,dy,dz) \ + { \ + const ivec &e = v[vert]; \ + ivec ef; \ + ef.dx = e.sx; ef.dy = e.sy; ef.dz = e.sz; \ + if(ef.z == dimcoord(orient)*8) \ + { \ + ivec2 &f = fvecs[i]; \ + ivec pf; \ + pf.dx = pos.sx; pf.dy = pos.sy; pf.dz = pos.sz; \ + f = ivec2(ef.x*size + (pf.x<<3), ef.y*size + (pf.y<<3)); \ + if(f != prev) { prev = f; i++; } \ + } \ + } + GENFACEVERTS(x, x, y, y, z, z, x, x, y, y, z, z) + #undef GENFACEORIENT + #undef GENFACEVERT + } + if(fvecs[0] == prev) i--; + return i; } static inline int clipfacevecy(const ivec2 &o, const ivec2 &dir, int cx, int cy, int size, ivec2 &r) { - if(dir.x >= 0) - { - if(cx <= o.x || cx >= o.x+dir.x) return 0; - } - else if(cx <= o.x+dir.x || cx >= o.x) return 0; + if(dir.x >= 0) + { + if(cx <= o.x || cx >= o.x+dir.x) return 0; + } + else if(cx <= o.x+dir.x || cx >= o.x) return 0; - int t = (o.y-cy) + (cx-o.x)*dir.y/dir.x; - if(t <= 0 || t >= size) return 0; + int t = (o.y-cy) + (cx-o.x)*dir.y/dir.x; + if(t <= 0 || t >= size) return 0; - r.x = cx; - r.y = cy + t; - return 1; + r.x = cx; + r.y = cy + t; + return 1; } static inline int clipfacevecx(const ivec2 &o, const ivec2 &dir, int cx, int cy, int size, ivec2 &r) { - if(dir.y >= 0) - { - if(cy <= o.y || cy >= o.y+dir.y) return 0; - } - else if(cy <= o.y+dir.y || cy >= o.y) return 0; + if(dir.y >= 0) + { + if(cy <= o.y || cy >= o.y+dir.y) return 0; + } + else if(cy <= o.y+dir.y || cy >= o.y) return 0; - int t = (o.x-cx) + (cy-o.y)*dir.x/dir.y; - if(t <= 0 || t >= size) return 0; + int t = (o.x-cx) + (cy-o.y)*dir.x/dir.y; + if(t <= 0 || t >= size) return 0; - r.x = cx + t; - r.y = cy; - return 1; + r.x = cx + t; + r.y = cy; + return 1; } static inline int clipfacevec(const ivec2 &o, const ivec2 &dir, int cx, int cy, int size, ivec2 *rvecs) { - int r = 0; + int r = 0; - if(o.x >= cx && o.x <= cx+size && - o.y >= cy && o.y <= cy+size && - ((o.x != cx && o.x != cx+size) || (o.y != cy && o.y != cy+size))) - { - rvecs[0].x = o.x; - rvecs[0].y = o.y; - r++; - } + if(o.x >= cx && o.x <= cx+size && + o.y >= cy && o.y <= cy+size && + ((o.x != cx && o.x != cx+size) || (o.y != cy && o.y != cy+size))) + { + rvecs[0].x = o.x; + rvecs[0].y = o.y; + r++; + } - r += clipfacevecx(o, dir, cx, cy, size, rvecs[r]); - r += clipfacevecx(o, dir, cx, cy+size, size, rvecs[r]); - r += clipfacevecy(o, dir, cx, cy, size, rvecs[r]); - r += clipfacevecy(o, dir, cx+size, cy, size, rvecs[r]); + r += clipfacevecx(o, dir, cx, cy, size, rvecs[r]); + r += clipfacevecx(o, dir, cx, cy+size, size, rvecs[r]); + r += clipfacevecy(o, dir, cx, cy, size, rvecs[r]); + r += clipfacevecy(o, dir, cx+size, cy, size, rvecs[r]); - ASSERT(r <= 2); - return r; + ASSERT(r <= 2); + return r; } static inline bool insideface(const ivec2 *p, int nump, const ivec2 *o, int numo) { - int bounds = 0; - ivec2 prev = o[numo-1]; - loopi(numo) - { - const ivec2 &cur = o[i]; - ivec2 dir(cur.x-prev.x, cur.y-prev.y); - int offset = dir.x*prev.y - dir.y*prev.x; - loopj(nump) if(dir.x*p[j].y - dir.y*p[j].x > offset) return false; - bounds++; - prev = cur; - } - return bounds>=3; + int bounds = 0; + ivec2 prev = o[numo-1]; + loopi(numo) + { + const ivec2 &cur = o[i]; + ivec2 dir(cur.x-prev.x, cur.y-prev.y); + int offset = dir.x*prev.y - dir.y*prev.x; + loopj(nump) if(dir.x*p[j].y - dir.y*p[j].x > offset) return false; + bounds++; + prev = cur; + } + return bounds>=3; } static inline int clipfacevecs(const ivec2 *o, int numo, int cx, int cy, int size, ivec2 *rvecs) { - cx <<= 3; - cy <<= 3; - size <<= 3; + cx <<= 3; + cy <<= 3; + size <<= 3; - int r = 0; - ivec2 prev = o[numo-1]; - loopi(numo) - { - const ivec2 &cur = o[i]; - r += clipfacevec(prev, ivec2(cur.x-prev.x, cur.y-prev.y), cx, cy, size, &rvecs[r]); - prev = cur; - } - ivec2 corner[4] = {ivec2(cx, cy), ivec2(cx+size, cy), ivec2(cx+size, cy+size), ivec2(cx, cy+size)}; - loopi(4) if(insideface(&corner[i], 1, o, numo)) rvecs[r++] = corner[i]; - ASSERT(r <= 8); - return r; + int r = 0; + ivec2 prev = o[numo-1]; + loopi(numo) + { + const ivec2 &cur = o[i]; + r += clipfacevec(prev, ivec2(cur.x-prev.x, cur.y-prev.y), cx, cy, size, &rvecs[r]); + prev = cur; + } + ivec2 corner[4] = {ivec2(cx, cy), ivec2(cx+size, cy), ivec2(cx+size, cy+size), ivec2(cx, cy+size)}; + loopi(4) if(insideface(&corner[i], 1, o, numo)) rvecs[r++] = corner[i]; + ASSERT(r <= 8); + return r; } bool collapsedface(const cube &c, int orient) { - int e0 = c.edges[faceedgesidx[orient][0]], e1 = c.edges[faceedgesidx[orient][1]], - e2 = c.edges[faceedgesidx[orient][2]], e3 = c.edges[faceedgesidx[orient][3]], - face = dimension(orient)*4, - f0 = c.edges[face+0], f1 = c.edges[face+1], - f2 = c.edges[face+2], f3 = c.edges[face+3]; - if(dimcoord(orient)) { f0 >>= 4; f1 >>= 4; f2 >>= 4; f3 >>= 4; } - else { f0 &= 0xF; f1 &= 0xF; f2 &= 0xF; f3 &= 0xF; } - ivec v0(e0&0xF, e2&0xF, f0), - v1(e0>>4, e3&0xF, f1), - v2(e1>>4, e3>>4, f3), - v3(e1&0xF, e2>>4, f2); - return ivec().cross(v1.sub(v0), v2.sub(v0)).iszero() && - ivec().cross(v2, v3.sub(v0)).iszero(); + int e0 = c.edges[faceedgesidx[orient][0]], e1 = c.edges[faceedgesidx[orient][1]], + e2 = c.edges[faceedgesidx[orient][2]], e3 = c.edges[faceedgesidx[orient][3]], + face = dimension(orient)*4, + f0 = c.edges[face+0], f1 = c.edges[face+1], + f2 = c.edges[face+2], f3 = c.edges[face+3]; + if(dimcoord(orient)) { f0 >>= 4; f1 >>= 4; f2 >>= 4; f3 >>= 4; } + else { f0 &= 0xF; f1 &= 0xF; f2 &= 0xF; f3 &= 0xF; } + ivec v0(e0&0xF, e2&0xF, f0), + v1(e0>>4, e3&0xF, f1), + v2(e1>>4, e3>>4, f3), + v3(e1&0xF, e2>>4, f2); + return ivec().cross(v1.sub(v0), v2.sub(v0)).iszero() && + ivec().cross(v2, v3.sub(v0)).iszero(); } static inline bool occludesface(const cube &c, int orient, const ivec &o, int size, const ivec &vo, int vsize, ushort vmat, ushort nmat, ushort matmask, const ivec2 *vf, int numv) { - int dim = dimension(orient); - if(!c.children) - { - if(nmat != MAT_AIR && (c.material&matmask) == nmat) - { - ivec2 nf[8]; - return clipfacevecs(vf, numv, o[C[dim]], o[R[dim]], size, nf) < 3; - } - if(isentirelysolid(c)) return true; - if(vmat != MAT_AIR && ((c.material&matmask) == vmat || (isliquid(vmat) && isclipped(c.material&MATF_VOLUME)))) return true; - if(touchingface(c, orient) && faceedges(c, orient) == F_SOLID) return true; - ivec2 cf[8]; - int numc = clipfacevecs(vf, numv, o[C[dim]], o[R[dim]], size, cf); - if(numc < 3) return true; - if(isempty(c) || notouchingface(c, orient)) return false; - ivec2 of[4]; - int numo = genfacevecs(c, orient, o, size, false, of); - return numo >= 3 && insideface(cf, numc, of, numo); - } - - size >>= 1; - int coord = dimcoord(orient); - loopi(8) if(octacoord(dim, i) == coord) - { - if(!occludesface(c.children[i], orient, ivec(i, o, size), size, vo, vsize, vmat, nmat, matmask, vf, numv)) return false; - } - - return true; + int dim = dimension(orient); + if(!c.children) + { + if(nmat != MAT_AIR && (c.material&matmask) == nmat) + { + ivec2 nf[8]; + return clipfacevecs(vf, numv, o[C[dim]], o[R[dim]], size, nf) < 3; + } + if(isentirelysolid(c)) return true; + if(vmat != MAT_AIR && ((c.material&matmask) == vmat || (isliquid(vmat) && isclipped(c.material&MATF_VOLUME)))) return true; + if(touchingface(c, orient) && faceedges(c, orient) == F_SOLID) return true; + ivec2 cf[8]; + int numc = clipfacevecs(vf, numv, o[C[dim]], o[R[dim]], size, cf); + if(numc < 3) return true; + if(isempty(c) || notouchingface(c, orient)) return false; + ivec2 of[4]; + int numo = genfacevecs(c, orient, o, size, false, of); + return numo >= 3 && insideface(cf, numc, of, numo); + } + + size >>= 1; + int coord = dimcoord(orient); + loopi(8) if(octacoord(dim, i) == coord) + { + if(!occludesface(c.children[i], orient, ivec(i, o, size), size, vo, vsize, vmat, nmat, matmask, vf, numv)) return false; + } + + return true; } bool visibleface(const cube &c, int orient, const ivec &co, int size, ushort mat, ushort nmat, ushort matmask) { - if(mat != MAT_AIR) - { - if(faceedges(c, orient)==F_SOLID && touchingface(c, orient)) return false; - } - else - { - if(collapsedface(c, orient)) return false; - if(!touchingface(c, orient)) return true; - } - - ivec no; - int nsize; - const cube &o = neighbourcube(c, orient, co, size, no, nsize); - if(&o==&c) return false; - - int opp = opposite(orient); - if(nsize > size || (nsize == size && !o.children)) - { - if(nmat != MAT_AIR && (o.material&matmask) == nmat) return true; - if(isentirelysolid(o)) return false; - if(mat != MAT_AIR && ((o.material&matmask) == mat || (isliquid(mat) && (o.material&MATF_VOLUME) == MAT_GLASS))) return false; - if(isempty(o) || notouchingface(o, opp)) return true; - if(touchingface(o, opp) && faceedges(o, opp) == F_SOLID) return false; - - ivec vo = ivec(co).mask(0xFFF); - no.mask(0xFFF); - ivec2 cf[4], of[4]; - int numc = genfacevecs(c, orient, vo, size, mat != MAT_AIR, cf), - numo = genfacevecs(o, opp, no, nsize, false, of); - return numo < 3 || !insideface(cf, numc, of, numo); - } - - - ivec vo = ivec(co).mask(0xFFF); - no.mask(0xFFF); - ivec2 cf[4]; - int numc = genfacevecs(c, orient, vo, size, mat != MAT_AIR, cf); - return !occludesface(o, opp, no, nsize, vo, size, mat, nmat, matmask, cf, numc); + if(mat != MAT_AIR) + { + if(faceedges(c, orient)==F_SOLID && touchingface(c, orient)) return false; + } + else + { + if(collapsedface(c, orient)) return false; + if(!touchingface(c, orient)) return true; + } + + ivec no; + int nsize; + const cube &o = neighbourcube(c, orient, co, size, no, nsize); + if(&o==&c) return false; + + int opp = opposite(orient); + if(nsize > size || (nsize == size && !o.children)) + { + if(nmat != MAT_AIR && (o.material&matmask) == nmat) return true; + if(isentirelysolid(o)) return false; + if(mat != MAT_AIR && ((o.material&matmask) == mat || (isliquid(mat) && (o.material&MATF_VOLUME) == MAT_GLASS))) return false; + if(isempty(o) || notouchingface(o, opp)) return true; + if(touchingface(o, opp) && faceedges(o, opp) == F_SOLID) return false; + + ivec vo = ivec(co).mask(0xFFF); + no.mask(0xFFF); + ivec2 cf[4], of[4]; + int numc = genfacevecs(c, orient, vo, size, mat != MAT_AIR, cf), + numo = genfacevecs(o, opp, no, nsize, false, of); + return numo < 3 || !insideface(cf, numc, of, numo); + } + + + ivec vo = ivec(co).mask(0xFFF); + no.mask(0xFFF); + ivec2 cf[4]; + int numc = genfacevecs(c, orient, vo, size, mat != MAT_AIR, cf); + return !occludesface(o, opp, no, nsize, vo, size, mat, nmat, matmask, cf, numc); } int classifyface(const cube &c, int orient, const ivec &co, int size) { - if(collapsedface(c, orient)) return 0; - int vismask = (c.material&MATF_CLIP) == MAT_NOCLIP ? 1 : 3; - if(!touchingface(c, orient)) return vismask; - - ivec no; - int nsize; - const cube &o = neighbourcube(c, orient, co, size, no, nsize); - if(&o==&c) return 0; - - int vis = 0, opp = opposite(orient); - if(nsize > size || (nsize == size && !o.children)) - { - if((~c.material & o.material) & MAT_ALPHA) vis |= 1; - if((o.material&MATF_CLIP) == MAT_NOCLIP) vis |= vismask&2; - if(vis == vismask || isentirelysolid(o)) return vis; - if(isempty(o) || notouchingface(o, opp)) return vismask; - if(touchingface(o, opp) && faceedges(o, opp) == F_SOLID) return vis; - - ivec vo = ivec(co).mask(0xFFF); - no.mask(0xFFF); - ivec2 cf[4], of[4]; - int numc = genfacevecs(c, orient, vo, size, false, cf), - numo = genfacevecs(o, opp, no, nsize, false, of); - if(numo < 3 || !insideface(cf, numc, of, numo)) return vismask; - return vis; - } - - ivec vo = ivec(co).mask(0xFFF); - no.mask(0xFFF); - ivec2 cf[4]; - int numc = genfacevecs(c, orient, vo, size, false, cf); - if(!occludesface(o, opp, no, nsize, vo, size, MAT_AIR, (c.material&MAT_ALPHA)^MAT_ALPHA, MAT_ALPHA, cf, numc)) vis |= 1; - if(vismask&2 && !occludesface(o, opp, no, nsize, vo, size, MAT_AIR, MAT_NOCLIP, MATF_CLIP, cf, numc)) vis |= 2; - return vis; + if(collapsedface(c, orient)) return 0; + int vismask = (c.material&MATF_CLIP) == MAT_NOCLIP ? 1 : 3; + if(!touchingface(c, orient)) return vismask; + + ivec no; + int nsize; + const cube &o = neighbourcube(c, orient, co, size, no, nsize); + if(&o==&c) return 0; + + int vis = 0, opp = opposite(orient); + if(nsize > size || (nsize == size && !o.children)) + { + if((~c.material & o.material) & MAT_ALPHA) vis |= 1; + if((o.material&MATF_CLIP) == MAT_NOCLIP) vis |= vismask&2; + if(vis == vismask || isentirelysolid(o)) return vis; + if(isempty(o) || notouchingface(o, opp)) return vismask; + if(touchingface(o, opp) && faceedges(o, opp) == F_SOLID) return vis; + + ivec vo = ivec(co).mask(0xFFF); + no.mask(0xFFF); + ivec2 cf[4], of[4]; + int numc = genfacevecs(c, orient, vo, size, false, cf), + numo = genfacevecs(o, opp, no, nsize, false, of); + if(numo < 3 || !insideface(cf, numc, of, numo)) return vismask; + return vis; + } + + ivec vo = ivec(co).mask(0xFFF); + no.mask(0xFFF); + ivec2 cf[4]; + int numc = genfacevecs(c, orient, vo, size, false, cf); + if(!occludesface(o, opp, no, nsize, vo, size, MAT_AIR, (c.material&MAT_ALPHA)^MAT_ALPHA, MAT_ALPHA, cf, numc)) vis |= 1; + if(vismask&2 && !occludesface(o, opp, no, nsize, vo, size, MAT_AIR, MAT_NOCLIP, MATF_CLIP, cf, numc)) vis |= 2; + return vis; } // more expensive version that checks both triangles of a face independently int visibletris(const cube &c, int orient, const ivec &co, int size, ushort nmat, ushort matmask) { - int vis = 3, touching = 0xF; - ivec v[4], e1, e2, e3, n; - genfaceverts(c, orient, v); - n.cross((e1 = v[1]).sub(v[0]), (e2 = v[2]).sub(v[0])); - int convex = (e3 = v[0]).sub(v[3]).dot(n); - if(!convex) - { - if(ivec().cross(e3, e2).iszero() || v[1] == v[3]) { if(n.iszero()) return 0; vis = 1; touching = 0xF&~(1<<3); } - else if(n.iszero()) { vis = 2; touching = 0xF&~(1<<1); } - } - - int dim = dimension(orient), coord = dimcoord(orient); - if(v[0][dim] != coord*8) touching &= ~(1<<0); - if(v[1][dim] != coord*8) touching &= ~(1<<1); - if(v[2][dim] != coord*8) touching &= ~(1<<2); - if(v[3][dim] != coord*8) touching &= ~(1<<3); - static const int notouchmasks[2][16] = // mask of triangles not touching - { // order 0: flat or convex - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - { 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 1, 3, 0 }, - // order 1: concave - { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 2, 0 }, - }; - int order = convex < 0 ? 1 : 0, notouch = notouchmasks[order][touching]; - if((vis¬ouch)==vis) return vis; - - ivec no; - int nsize; - const cube &o = neighbourcube(c, orient, co, size, no, nsize); - if(&o==&c) return 0; - - if((c.material&matmask) == nmat) nmat = MAT_AIR; - - ivec vo = ivec(co).mask(0xFFF); - no.mask(0xFFF); - ivec2 cf[4], of[4]; - int opp = opposite(orient), numo = 0, numc; - if(nsize > size || (nsize == size && !o.children)) - { - if(isempty(o) || notouchingface(o, opp)) return vis; - if(nmat != MAT_AIR && (o.material&matmask) == nmat) return vis; - if(isentirelysolid(o) || (touchingface(o, opp) && faceedges(o, opp) == F_SOLID)) return vis¬ouch; - - numc = genfacevecs(c, orient, vo, size, false, cf, v); - numo = genfacevecs(o, opp, no, nsize, false, of); - if(numo < 3) return vis; - if(insideface(cf, numc, of, numo)) return vis¬ouch; - } - else - { - numc = genfacevecs(c, orient, vo, size, false, cf, v); - if(occludesface(o, opp, no, nsize, vo, size, MAT_AIR, nmat, matmask, cf, numc)) return vis¬ouch; - } - if(vis != 3 || notouch) return vis; - - static const int triverts[2][2][2][3] = - { // order - { // coord - { { 1, 2, 3 }, { 0, 1, 3 } }, // verts - { { 0, 1, 2 }, { 0, 2, 3 } } - }, - { // coord - { { 0, 1, 2 }, { 3, 0, 2 } }, // verts - { { 1, 2, 3 }, { 1, 3, 0 } } - } - }; - - do - { - loopi(2) - { - const int *verts = triverts[order][coord][i]; - ivec2 tf[3] = { cf[verts[0]], cf[verts[1]], cf[verts[2]] }; - if(numo > 0) { if(!insideface(tf, 3, of, numo)) continue; } - else if(!occludesface(o, opp, no, nsize, vo, size, MAT_AIR, nmat, matmask, tf, 3)) continue; - return vis & ~(1< size || (nsize == size && !o.children)) + { + if(isempty(o) || notouchingface(o, opp)) return vis; + if(nmat != MAT_AIR && (o.material&matmask) == nmat) return vis; + if(isentirelysolid(o) || (touchingface(o, opp) && faceedges(o, opp) == F_SOLID)) return vis¬ouch; + + numc = genfacevecs(c, orient, vo, size, false, cf, v); + numo = genfacevecs(o, opp, no, nsize, false, of); + if(numo < 3) return vis; + if(insideface(cf, numc, of, numo)) return vis¬ouch; + } + else + { + numc = genfacevecs(c, orient, vo, size, false, cf, v); + if(occludesface(o, opp, no, nsize, vo, size, MAT_AIR, nmat, matmask, cf, numc)) return vis¬ouch; + } + if(vis != 3 || notouch) return vis; + + static const int triverts[2][2][2][3] = + { // order + { // coord + { { 1, 2, 3 }, { 0, 1, 3 } }, // verts + { { 0, 1, 2 }, { 0, 2, 3 } } + }, + { // coord + { { 0, 1, 2 }, { 3, 0, 2 } }, // verts + { { 1, 2, 3 }, { 1, 3, 0 } } + } + }; + + do + { + loopi(2) + { + const int *verts = triverts[order][coord][i]; + ivec2 tf[3] = { cf[verts[0]], cf[verts[1]], cf[verts[2]] }; + if(numo > 0) { if(!insideface(tf, 3, of, numo)) continue; } + else if(!occludesface(o, opp, no, nsize, vo, size, MAT_AIR, nmat, matmask, tf, 3)) continue; + return vis & ~(1<=8) v.mul(size/8); - else v.div(8/size); - v.add(ivec(co).shl(3)); + if(solid) v = cubecoords[i]; else gencubevert(c, i, v); + // avoid overflow + if(size>=8) v.mul(size/8); + else v.div(8/size); + v.add(ivec(co).shl(3)); } void calcvert(const cube &c, const ivec &co, int size, vec &v, int i, bool solid) { - if(solid) v = vec(cubecoords[i]); else gencubevert(c, i, v); - v.mul(size/8.0f).add(vec(co)); + if(solid) v = vec(cubecoords[i]); else gencubevert(c, i, v); + v.mul(size/8.0f).add(vec(co)); } int genclipplane(const cube &c, int orient, vec *v, plane *clip) { - int planes = 0, convex = faceconvexity(c, orient), order = convex < 0 ? 1 : 0; - const vec &v0 = v[fv[orient][order]], &v1 = v[fv[orient][order+1]], &v2 = v[fv[orient][order+2]], &v3 = v[fv[orient][(order+3)&3]]; - if(v0==v2) return 0; - if(v0!=v1 && v1!=v2) clip[planes++].toplane(v0, v1, v2); - if(v0!=v3 && v2!=v3 && (!planes || convex)) clip[planes++].toplane(v0, v2, v3); - return planes; + int planes = 0, convex = faceconvexity(c, orient), order = convex < 0 ? 1 : 0; + const vec &v0 = v[fv[orient][order]], &v1 = v[fv[orient][order+1]], &v2 = v[fv[orient][order+2]], &v3 = v[fv[orient][(order+3)&3]]; + if(v0==v2) return 0; + if(v0!=v1 && v1!=v2) clip[planes++].toplane(v0, v1, v2); + if(v0!=v3 && v2!=v3 && (!planes || convex)) clip[planes++].toplane(v0, v2, v3); + return planes; } void genclipplanes(const cube &c, const ivec &co, int size, clipplanes &p, bool collide) { - // generate tight bounding box - calcvert(c, co, size, p.v[0], 0); - vec mx = p.v[0], mn = p.v[0]; - for(int i = 1; i < 8; i++) - { - calcvert(c, co, size, p.v[i], i); - mx.max(p.v[i]); - mn.min(p.v[i]); - } - - p.r = mx.sub(mn).mul(0.5f); - p.o = mn.add(p.r); - - p.size = 0; - p.visible = 0; - if(collide || (c.visible&0xC0) == 0x40) - { - loopi(6) if(c.visible&(1< y.v2) return false; - if(x.u1 < y.u1) return true; - if(x.u1 > y.u1) return false; - return false; + if(x.v2 < y.v2) return true; + if(x.v2 > y.v2) return false; + if(x.u1 < y.u1) return true; + if(x.u1 > y.u1) return false; + return false; } static int mergefacev(int orient, facebounds *m, int sz, facebounds &n) { - for(int i = sz-1; i >= 0; --i) - { - if(m[i].v2 < n.v1) break; - if(m[i].v2 == n.v1 && m[i].u1 == n.u1 && m[i].u2 == n.u2) - { - n.v1 = m[i].v1; - memmove(&m[i], &m[i+1], (sz - (i+1)) * sizeof(facebounds)); - return 1; - } - } - return 0; + for(int i = sz-1; i >= 0; --i) + { + if(m[i].v2 < n.v1) break; + if(m[i].v2 == n.v1 && m[i].u1 == n.u1 && m[i].u2 == n.u2) + { + n.v1 = m[i].v1; + memmove(&m[i], &m[i+1], (sz - (i+1)) * sizeof(facebounds)); + return 1; + } + } + return 0; } static int mergefaceu(int orient, facebounds &m, facebounds &n) { - if(m.v1 == n.v1 && m.v2 == n.v2 && m.u2 == n.u1) - { - n.u1 = m.u1; - return 1; - } - return 0; + if(m.v1 == n.v1 && m.v2 == n.v2 && m.u2 == n.u1) + { + n.u1 = m.u1; + return 1; + } + return 0; } static int mergeface(int orient, facebounds *m, int sz, facebounds &n) { - for(bool merged = false; sz; merged = true) - { - int vmerged = mergefacev(orient, m, sz, n); - sz -= vmerged; - if(!vmerged && merged) break; - if(!sz) break; - int umerged = mergefaceu(orient, m[sz-1], n); - sz -= umerged; - if(!umerged) break; - } - m[sz++] = n; - return sz; + for(bool merged = false; sz; merged = true) + { + int vmerged = mergefacev(orient, m, sz, n); + sz -= vmerged; + if(!vmerged && merged) break; + if(!sz) break; + int umerged = mergefaceu(orient, m[sz-1], n); + sz -= umerged; + if(!umerged) break; + } + m[sz++] = n; + return sz; } int mergefaces(int orient, facebounds *m, int sz) { - quicksort(m, sz, mergefacecmp); + quicksort(m, sz, mergefacecmp); - int nsz = 0; - loopi(sz) nsz = mergeface(orient, m, nsz, m[i]); - return nsz; + int nsz = 0; + loopi(sz) nsz = mergeface(orient, m, nsz, m[i]); + return nsz; } struct cfkey { - uchar orient; - ushort material, tex; - ivec n; - int offset; + uchar orient; + ushort material, tex; + ivec n; + int offset; }; static inline bool htcmp(const cfkey &x, const cfkey &y) { - return x.orient == y.orient && x.tex == y.tex && x.n == y.n && x.offset == y.offset && x.material==y.material; + return x.orient == y.orient && x.tex == y.tex && x.n == y.n && x.offset == y.offset && x.material==y.material; } static inline uint hthash(const cfkey &k) { - return hthash(k.n)^k.offset^k.tex^k.orient^k.material; + return hthash(k.n)^k.offset^k.tex^k.orient^k.material; } void mincubeface(const cube &cu, int orient, const ivec &o, int size, const facebounds &orig, facebounds &cf, ushort nmat, ushort matmask) { - int dim = dimension(orient); - if(cu.children) - { - size >>= 1; - int coord = dimcoord(orient); - loopi(8) if(octacoord(dim, i) == coord) - mincubeface(cu.children[i], orient, ivec(i, o, size), size, orig, cf, nmat, matmask); - return; - } - int c = C[dim], r = R[dim]; - ushort uco = (o[c]&0xFFF)<<3, vco = (o[r]&0xFFF)<<3; - ushort uc1 = uco, vc1 = vco, uc2 = ushort(size<<3)+uco, vc2 = ushort(size<<3)+vco; - uc1 = max(uc1, orig.u1); - uc2 = min(uc2, orig.u2); - vc1 = max(vc1, orig.v1); - vc2 = min(vc2, orig.v2); - if(!isempty(cu) && touchingface(cu, orient) && !(nmat!=MAT_AIR && (cu.material&matmask)==nmat)) - { - uchar r1 = cu.edges[faceedgesidx[orient][0]], r2 = cu.edges[faceedgesidx[orient][1]], - c1 = cu.edges[faceedgesidx[orient][2]], c2 = cu.edges[faceedgesidx[orient][3]]; - ushort u1 = max(c1&0xF, c2&0xF)*size+uco, u2 = min(c1>>4, c2>>4)*size+uco, - v1 = max(r1&0xF, r2&0xF)*size+vco, v2 = min(r1>>4, r2>>4)*size+vco; - u1 = max(u1, orig.u1); - u2 = min(u2, orig.u2); - v1 = max(v1, orig.v1); - v2 = min(v2, orig.v2); - if(v2-v1==vc2-vc1) - { - if(u2-u1==uc2-uc1) return; - if(u1==uc1) uc1 = u2; - if(u2==uc2) uc2 = u1; - } - else if(u2-u1==uc2-uc1) - { - if(v1==vc1) vc1 = v2; - if(v2==vc2) vc2 = v1; - } - } - if(uc1==uc2 || vc1==vc2) return; - cf.u1 = min(cf.u1, uc1); - cf.u2 = max(cf.u2, uc2); - cf.v1 = min(cf.v1, vc1); - cf.v2 = max(cf.v2, vc2); + int dim = dimension(orient); + if(cu.children) + { + size >>= 1; + int coord = dimcoord(orient); + loopi(8) if(octacoord(dim, i) == coord) + mincubeface(cu.children[i], orient, ivec(i, o, size), size, orig, cf, nmat, matmask); + return; + } + int c = C[dim], r = R[dim]; + ushort uco = (o[c]&0xFFF)<<3, vco = (o[r]&0xFFF)<<3; + ushort uc1 = uco, vc1 = vco, uc2 = ushort(size<<3)+uco, vc2 = ushort(size<<3)+vco; + uc1 = max(uc1, orig.u1); + uc2 = min(uc2, orig.u2); + vc1 = max(vc1, orig.v1); + vc2 = min(vc2, orig.v2); + if(!isempty(cu) && touchingface(cu, orient) && !(nmat!=MAT_AIR && (cu.material&matmask)==nmat)) + { + uchar r1 = cu.edges[faceedgesidx[orient][0]], r2 = cu.edges[faceedgesidx[orient][1]], + c1 = cu.edges[faceedgesidx[orient][2]], c2 = cu.edges[faceedgesidx[orient][3]]; + ushort u1 = max(c1&0xF, c2&0xF)*size+uco, u2 = min(c1>>4, c2>>4)*size+uco, + v1 = max(r1&0xF, r2&0xF)*size+vco, v2 = min(r1>>4, r2>>4)*size+vco; + u1 = max(u1, orig.u1); + u2 = min(u2, orig.u2); + v1 = max(v1, orig.v1); + v2 = min(v2, orig.v2); + if(v2-v1==vc2-vc1) + { + if(u2-u1==uc2-uc1) return; + if(u1==uc1) uc1 = u2; + if(u2==uc2) uc2 = u1; + } + else if(u2-u1==uc2-uc1) + { + if(v1==vc1) vc1 = v2; + if(v2==vc2) vc2 = v1; + } + } + if(uc1==uc2 || vc1==vc2) return; + cf.u1 = min(cf.u1, uc1); + cf.u2 = max(cf.u2, uc2); + cf.v1 = min(cf.v1, vc1); + cf.v2 = max(cf.v2, vc2); } bool mincubeface(const cube &cu, int orient, const ivec &co, int size, facebounds &orig) { - ivec no; - int nsize; - const cube &nc = neighbourcube(cu, orient, co, size, no, nsize); - facebounds mincf; - mincf.u1 = orig.u2; - mincf.u2 = orig.u1; - mincf.v1 = orig.v2; - mincf.v2 = orig.v1; - mincubeface(nc, opposite(orient), no, nsize, orig, mincf, cu.material&MAT_ALPHA ? MAT_AIR : MAT_ALPHA, MAT_ALPHA); - bool smaller = false; - if(mincf.u1 > orig.u1) { orig.u1 = mincf.u1; smaller = true; } - if(mincf.u2 < orig.u2) { orig.u2 = mincf.u2; smaller = true; } - if(mincf.v1 > orig.v1) { orig.v1 = mincf.v1; smaller = true; } - if(mincf.v2 < orig.v2) { orig.v2 = mincf.v2; smaller = true; } - return smaller; + ivec no; + int nsize; + const cube &nc = neighbourcube(cu, orient, co, size, no, nsize); + facebounds mincf; + mincf.u1 = orig.u2; + mincf.u2 = orig.u1; + mincf.v1 = orig.v2; + mincf.v2 = orig.v1; + mincubeface(nc, opposite(orient), no, nsize, orig, mincf, cu.material&MAT_ALPHA ? MAT_AIR : MAT_ALPHA, MAT_ALPHA); + bool smaller = false; + if(mincf.u1 > orig.u1) { orig.u1 = mincf.u1; smaller = true; } + if(mincf.u2 < orig.u2) { orig.u2 = mincf.u2; smaller = true; } + if(mincf.v1 > orig.v1) { orig.v1 = mincf.v1; smaller = true; } + if(mincf.v2 < orig.v2) { orig.v2 = mincf.v2; smaller = true; } + return smaller; } VAR(maxmerge, 0, 6, 12); @@ -1398,24 +1398,24 @@ VAR(minface, 0, 4, 12); struct pvert { - ushort x, y; + ushort x, y; - pvert() {} - pvert(ushort x, ushort y) : x(x), y(y) {} + pvert() {} + pvert(ushort x, ushort y) : x(x), y(y) {} - bool operator==(const pvert &o) const { return x == o.x && y == o.y; } - bool operator!=(const pvert &o) const { return x != o.x || y != o.y; } + bool operator==(const pvert &o) const { return x == o.x && y == o.y; } + bool operator!=(const pvert &o) const { return x != o.x || y != o.y; } }; struct pedge { - pvert from, to; + pvert from, to; - pedge() {} - pedge(const pvert &from, const pvert &to) : from(from), to(to) {} + pedge() {} + pedge(const pvert &from, const pvert &to) : from(from), to(to) {} - bool operator==(const pedge &o) const { return from == o.from && to == o.to; } - bool operator!=(const pedge &o) const { return from != o.from || to != o.to; } + bool operator==(const pedge &o) const { return from == o.from && to == o.to; } + bool operator!=(const pedge &o) const { return from != o.from || to != o.to; } }; static inline uint hthash(const pedge &x) { return uint(x.from.x)^(uint(x.from.y)<<8); } @@ -1423,458 +1423,458 @@ static inline bool htcmp(const pedge &x, const pedge &y) { return x == y; } struct poly { - cube *c; - int numverts; - bool merged; - pvert verts[MAXFACEVERTS]; + cube *c; + int numverts; + bool merged; + pvert verts[MAXFACEVERTS]; }; bool clippoly(poly &p, const facebounds &b) { - pvert verts1[MAXFACEVERTS+4], verts2[MAXFACEVERTS+4]; - int numverts1 = 0, numverts2 = 0, px = p.verts[p.numverts-1].x, py = p.verts[p.numverts-1].y; - loopi(p.numverts) - { - int x = p.verts[i].x, y = p.verts[i].y; - if(x < b.u1) - { - if(px > b.u2) verts1[numverts1++] = pvert(b.u2, y + ((y - py)*(b.u2 - x))/(x - px)); - if(px > b.u1) verts1[numverts1++] = pvert(b.u1, y + ((y - py)*(b.u1 - x))/(x - px)); - } - else if(x > b.u2) - { - if(px < b.u1) verts1[numverts1++] = pvert(b.u1, y + ((y - py)*(b.u1 - x))/(x - px)); - if(px < b.u2) verts1[numverts1++] = pvert(b.u2, y + ((y - py)*(b.u2 - x))/(x - px)); - } - else - { - if(px < b.u1) - { - if(x > b.u1) verts1[numverts1++] = pvert(b.u1, y + ((y - py)*(b.u1 - x))/(x - px)); - } - else if(px > b.u2 && x < b.u2) verts1[numverts1++] = pvert(b.u2, y + ((y - py)*(b.u2 - x))/(x - px)); - verts1[numverts1++] = pvert(x, y); - } - px = x; - py = y; - } - if(numverts1 < 3) return false; - px = verts1[numverts1-1].x; - py = verts1[numverts1-1].y; - loopi(numverts1) - { - int x = verts1[i].x, y = verts1[i].y; - if(y < b.v1) - { - if(py > b.v2) verts2[numverts2++] = pvert(x + ((x - px)*(b.v2 - y))/(y - py), b.v2); - if(py > b.v1) verts2[numverts2++] = pvert(x + ((x - px)*(b.v1 - y))/(y - py), b.v1); - } - else if(y > b.v2) - { - if(py < b.v1) verts2[numverts2++] = pvert(x + ((x - px)*(b.v1 - y))/(y - py), b.v1); - if(py < b.v2) verts2[numverts2++] = pvert(x + ((x - px)*(b.v2 - y))/(y - py), b.v2); - } - else - { - if(py < b.v1) - { - if(y > b.v1) verts2[numverts2++] = pvert(x + ((x - px)*(b.v1 - y))/(y - py), b.v1); - } - else if(py > b.v2 && y < b.v2) verts2[numverts2++] = pvert(x + ((x - px)*(b.v2 - y))/(y - py), b.v2); - verts2[numverts2++] = pvert(x, y); - } - px = x; - py = y; - } - if(numverts2 < 3) return false; - if(numverts2 > MAXFACEVERTS) return false; - memcpy(p.verts, verts2, numverts2*sizeof(pvert)); - p.numverts = numverts2; - return true; -} + pvert verts1[MAXFACEVERTS+4], verts2[MAXFACEVERTS+4]; + int numverts1 = 0, numverts2 = 0, px = p.verts[p.numverts-1].x, py = p.verts[p.numverts-1].y; + loopi(p.numverts) + { + int x = p.verts[i].x, y = p.verts[i].y; + if(x < b.u1) + { + if(px > b.u2) verts1[numverts1++] = pvert(b.u2, y + ((y - py)*(b.u2 - x))/(x - px)); + if(px > b.u1) verts1[numverts1++] = pvert(b.u1, y + ((y - py)*(b.u1 - x))/(x - px)); + } + else if(x > b.u2) + { + if(px < b.u1) verts1[numverts1++] = pvert(b.u1, y + ((y - py)*(b.u1 - x))/(x - px)); + if(px < b.u2) verts1[numverts1++] = pvert(b.u2, y + ((y - py)*(b.u2 - x))/(x - px)); + } + else + { + if(px < b.u1) + { + if(x > b.u1) verts1[numverts1++] = pvert(b.u1, y + ((y - py)*(b.u1 - x))/(x - px)); + } + else if(px > b.u2 && x < b.u2) verts1[numverts1++] = pvert(b.u2, y + ((y - py)*(b.u2 - x))/(x - px)); + verts1[numverts1++] = pvert(x, y); + } + px = x; + py = y; + } + if(numverts1 < 3) return false; + px = verts1[numverts1-1].x; + py = verts1[numverts1-1].y; + loopi(numverts1) + { + int x = verts1[i].x, y = verts1[i].y; + if(y < b.v1) + { + if(py > b.v2) verts2[numverts2++] = pvert(x + ((x - px)*(b.v2 - y))/(y - py), b.v2); + if(py > b.v1) verts2[numverts2++] = pvert(x + ((x - px)*(b.v1 - y))/(y - py), b.v1); + } + else if(y > b.v2) + { + if(py < b.v1) verts2[numverts2++] = pvert(x + ((x - px)*(b.v1 - y))/(y - py), b.v1); + if(py < b.v2) verts2[numverts2++] = pvert(x + ((x - px)*(b.v2 - y))/(y - py), b.v2); + } + else + { + if(py < b.v1) + { + if(y > b.v1) verts2[numverts2++] = pvert(x + ((x - px)*(b.v1 - y))/(y - py), b.v1); + } + else if(py > b.v2 && y < b.v2) verts2[numverts2++] = pvert(x + ((x - px)*(b.v2 - y))/(y - py), b.v2); + verts2[numverts2++] = pvert(x, y); + } + px = x; + py = y; + } + if(numverts2 < 3) return false; + if(numverts2 > MAXFACEVERTS) return false; + memcpy(p.verts, verts2, numverts2*sizeof(pvert)); + p.numverts = numverts2; + return true; +} bool genpoly(cube &cu, int orient, const ivec &o, int size, int vis, ivec &n, int &offset, poly &p) { - int dim = dimension(orient), coord = dimcoord(orient); - ivec v[4]; - genfaceverts(cu, orient, v); - if(flataxisface(cu, orient)) - { - n = ivec(0, 0, 0); - n[dim] = coord ? 1 : -1; - } - else - { - if(faceconvexity(v)) return false; - n.cross(ivec(v[1]).sub(v[0]), ivec(v[2]).sub(v[0])); - if(n.iszero()) n.cross(ivec(v[2]).sub(v[0]), ivec(v[3]).sub(v[0])); - reduceslope(n); - } - - ivec po = ivec(o).mask(0xFFF).shl(3); - loopk(4) v[k].mul(size).add(po); - offset = -n.dot(v[3]); - - int r = R[dim], c = C[dim], order = vis&4 ? 1 : 0; - p.numverts = 0; - if(coord) - { - const ivec &v0 = v[order]; p.verts[p.numverts++] = pvert(v0[c], v0[r]); - if(vis&1) { const ivec &v1 = v[order+1]; p.verts[p.numverts++] = pvert(v1[c], v1[r]); } - const ivec &v2 = v[order+2]; p.verts[p.numverts++] = pvert(v2[c], v2[r]); - if(vis&2) { const ivec &v3 = v[(order+3)&3]; p.verts[p.numverts++] = pvert(v3[c], v3[r]); } - } - else - { - if(vis&2) { const ivec &v3 = v[(order+3)&3]; p.verts[p.numverts++] = pvert(v3[c], v3[r]); } - const ivec &v2 = v[order+2]; p.verts[p.numverts++] = pvert(v2[c], v2[r]); - if(vis&1) { const ivec &v1 = v[order+1]; p.verts[p.numverts++] = pvert(v1[c], v1[r]); } - const ivec &v0 = v[order]; p.verts[p.numverts++] = pvert(v0[c], v0[r]); - } - - if(faceedges(cu, orient)!=F_SOLID) - { - int px = int(p.verts[p.numverts-2].x) - int(p.verts[p.numverts-3].x), py = int(p.verts[p.numverts-2].y) - int(p.verts[p.numverts-3].y), - cx = int(p.verts[p.numverts-1].x) - int(p.verts[p.numverts-2].x), cy = int(p.verts[p.numverts-1].y) - int(p.verts[p.numverts-2].y), - dir = px*cy - py*cx; - if(dir > 0) return false; - if(!dir) { if(p.numverts < 4) return false; p.verts[p.numverts-2] = p.verts[p.numverts-1]; p.numverts--; } - px = cx; py = cy; - cx = int(p.verts[0].x) - int(p.verts[p.numverts-1].x); cy = int(p.verts[0].y) - int(p.verts[p.numverts-1].y); - dir = px*cy - py*cx; - if(dir > 0) return false; - if(!dir) { if(p.numverts < 4) return false; p.numverts--; } - px = cx; py = cy; - cx = int(p.verts[1].x) - int(p.verts[0].x); cy = int(p.verts[1].y) - int(p.verts[0].y); - dir = px*cy - py*cx; - if(dir > 0) return false; - if(!dir) { if(p.numverts < 4) return false; p.verts[0] = p.verts[p.numverts-1]; p.numverts--; } - px = cx; py = cy; - cx = int(p.verts[2].x) - int(p.verts[1].x); cy = int(p.verts[2].y) - int(p.verts[1].y); - dir = px*cy - py*cx; - if(dir > 0) return false; - if(!dir) { if(p.numverts < 4) return false; p.verts[1] = p.verts[2]; p.verts[2] = p.verts[3]; p.numverts--; } - } - - p.c = &cu; - p.merged = false; - - if(minface && size >= 1< 0) return false; + if(!dir) { if(p.numverts < 4) return false; p.verts[p.numverts-2] = p.verts[p.numverts-1]; p.numverts--; } + px = cx; py = cy; + cx = int(p.verts[0].x) - int(p.verts[p.numverts-1].x); cy = int(p.verts[0].y) - int(p.verts[p.numverts-1].y); + dir = px*cy - py*cx; + if(dir > 0) return false; + if(!dir) { if(p.numverts < 4) return false; p.numverts--; } + px = cx; py = cy; + cx = int(p.verts[1].x) - int(p.verts[0].x); cy = int(p.verts[1].y) - int(p.verts[0].y); + dir = px*cy - py*cx; + if(dir > 0) return false; + if(!dir) { if(p.numverts < 4) return false; p.verts[0] = p.verts[p.numverts-1]; p.numverts--; } + px = cx; py = cy; + cx = int(p.verts[2].x) - int(p.verts[1].x); cy = int(p.verts[2].y) - int(p.verts[1].y); + dir = px*cy - py*cx; + if(dir > 0) return false; + if(!dir) { if(p.numverts < 4) return false; p.verts[1] = p.verts[2]; p.verts[2] = p.verts[3]; p.numverts--; } + } + + p.c = &cu; + p.merged = false; + + if(minface && size >= 1< &links, vector &queue, int owner, poly &p, poly &q, const pedge &e) { - int pe = -1, qe = -1; - loopi(p.numverts) if(p.verts[i] == e.from) { pe = i; break; } - loopi(q.numverts) if(q.verts[i] == e.to) { qe = i; break; } - if(pe < 0 || qe < 0) return false; - if(p.verts[(pe+1)%p.numverts] != e.to || q.verts[(qe+1)%q.numverts] != e.from) return false; - /* - * c----d - * | | - * F----T - * | P | - * b----a - */ - pvert verts[2*MAXFACEVERTS]; - int numverts = 0, index = pe+2; // starts at A = T+1, ends at F = T+p.numverts - loopi(p.numverts-1) - { - if(index >= p.numverts) index -= p.numverts; - verts[numverts++] = p.verts[index++]; - } - index = qe+2; // starts at C = T+2 = F+1, ends at T = T+q.numverts - int px = int(verts[numverts-1].x) - int(verts[numverts-2].x), py = int(verts[numverts-1].y) - int(verts[numverts-2].y); - loopi(q.numverts-1) - { - if(index >= q.numverts) index -= q.numverts; - pvert &src = q.verts[index++]; - int cx = int(src.x) - int(verts[numverts-1].x), cy = int(src.y) - int(verts[numverts-1].y), - dir = px*cy - py*cx; - if(dir > 0) return false; - if(!dir) numverts--; - verts[numverts++] = src; - px = cx; - py = cy; - } - int cx = int(verts[0].x) - int(verts[numverts-1].x), cy = int(verts[0].y) - int(verts[numverts-1].y), - dir = px*cy - py*cx; - if(dir > 0) return false; - if(!dir) numverts--; - - if(numverts > MAXFACEVERTS) return false; - - q.merged = true; - q.numverts = 0; - - p.merged = true; - p.numverts = numverts; - memcpy(p.verts, verts, numverts*sizeof(pvert)); - - int prev = p.numverts-1; - loopj(p.numverts) - { - pedge e(p.verts[prev], p.verts[j]); - int order = e.from.x > e.to.x || (e.from.x == e.to.x && e.from.y > e.to.y) ? 1 : 0; - if(order) swap(e.from, e.to); - plink &l = links.access(e, e); - bool shouldqueue = l.polys[order] < 0 && l.polys[order^1] >= 0; - l.polys[order] = owner; - if(shouldqueue) queue.add(&l); - prev = j; - } - - return true; + int pe = -1, qe = -1; + loopi(p.numverts) if(p.verts[i] == e.from) { pe = i; break; } + loopi(q.numverts) if(q.verts[i] == e.to) { qe = i; break; } + if(pe < 0 || qe < 0) return false; + if(p.verts[(pe+1)%p.numverts] != e.to || q.verts[(qe+1)%q.numverts] != e.from) return false; + /* + * c----d + * | | + * F----T + * | P | + * b----a + */ + pvert verts[2*MAXFACEVERTS]; + int numverts = 0, index = pe+2; // starts at A = T+1, ends at F = T+p.numverts + loopi(p.numverts-1) + { + if(index >= p.numverts) index -= p.numverts; + verts[numverts++] = p.verts[index++]; + } + index = qe+2; // starts at C = T+2 = F+1, ends at T = T+q.numverts + int px = int(verts[numverts-1].x) - int(verts[numverts-2].x), py = int(verts[numverts-1].y) - int(verts[numverts-2].y); + loopi(q.numverts-1) + { + if(index >= q.numverts) index -= q.numverts; + pvert &src = q.verts[index++]; + int cx = int(src.x) - int(verts[numverts-1].x), cy = int(src.y) - int(verts[numverts-1].y), + dir = px*cy - py*cx; + if(dir > 0) return false; + if(!dir) numverts--; + verts[numverts++] = src; + px = cx; + py = cy; + } + int cx = int(verts[0].x) - int(verts[numverts-1].x), cy = int(verts[0].y) - int(verts[numverts-1].y), + dir = px*cy - py*cx; + if(dir > 0) return false; + if(!dir) numverts--; + + if(numverts > MAXFACEVERTS) return false; + + q.merged = true; + q.numverts = 0; + + p.merged = true; + p.numverts = numverts; + memcpy(p.verts, verts, numverts*sizeof(pvert)); + + int prev = p.numverts-1; + loopj(p.numverts) + { + pedge e(p.verts[prev], p.verts[j]); + int order = e.from.x > e.to.x || (e.from.x == e.to.x && e.from.y > e.to.y) ? 1 : 0; + if(order) swap(e.from, e.to); + plink &l = links.access(e, e); + bool shouldqueue = l.polys[order] < 0 && l.polys[order^1] >= 0; + l.polys[order] = owner; + if(shouldqueue) queue.add(&l); + prev = j; + } + + return true; } void addmerge(cube &cu, int orient, const ivec &co, const ivec &n, int offset, poly &p) { - cu.merged |= 1<surfaces[orient] = ambientsurface; - return; - } - surfaceinfo surf = brightsurface; - vertinfo verts[MAXFACEVERTS]; - surf.numverts |= p.numverts; - int dim = dimension(orient), coord = dimcoord(orient), c = C[dim], r = R[dim]; - loopk(p.numverts) - { - pvert &src = p.verts[coord ? k : p.numverts-1-k]; - vertinfo &dst = verts[k]; - ivec v; - v[c] = src.x; - v[r] = src.y; - v[dim] = -(offset + n[c]*src.x + n[r]*src.y)/n[dim]; - dst.set(v); - } - if(cu.ext) - { - const surfaceinfo &oldsurf = cu.ext->surfaces[orient]; - int numverts = oldsurf.numverts&MAXFACEVERTS; - if(numverts == p.numverts) - { - ivec v0 = verts[0].getxyz(); - const vertinfo *oldverts = cu.ext->verts() + oldsurf.verts; - loopj(numverts) if(v0 == oldverts[j].getxyz()) - { - for(int k = 1; k < numverts; k++) - { - if(++j >= numverts) j = 0; - if(verts[k].getxyz() != oldverts[j].getxyz()) goto nomatch; - } - return; - } - nomatch:; - } - } - setsurface(cu, orient, surf, verts, p.numverts); + cu.merged |= 1<surfaces[orient] = ambientsurface; + return; + } + surfaceinfo surf = brightsurface; + vertinfo verts[MAXFACEVERTS]; + surf.numverts |= p.numverts; + int dim = dimension(orient), coord = dimcoord(orient), c = C[dim], r = R[dim]; + loopk(p.numverts) + { + pvert &src = p.verts[coord ? k : p.numverts-1-k]; + vertinfo &dst = verts[k]; + ivec v; + v[c] = src.x; + v[r] = src.y; + v[dim] = -(offset + n[c]*src.x + n[r]*src.y)/n[dim]; + dst.set(v); + } + if(cu.ext) + { + const surfaceinfo &oldsurf = cu.ext->surfaces[orient]; + int numverts = oldsurf.numverts&MAXFACEVERTS; + if(numverts == p.numverts) + { + ivec v0 = verts[0].getxyz(); + const vertinfo *oldverts = cu.ext->verts() + oldsurf.verts; + loopj(numverts) if(v0 == oldverts[j].getxyz()) + { + for(int k = 1; k < numverts; k++) + { + if(++j >= numverts) j = 0; + if(verts[k].getxyz() != oldverts[j].getxyz()) goto nomatch; + } + return; + } + nomatch:; + } + } + setsurface(cu, orient, surf, verts, p.numverts); } static inline void clearmerge(cube &c, int orient) { - if(c.merged&(1<surfaces[orient] = brightsurface; - } + if(c.merged&(1<surfaces[orient] = brightsurface; + } } void addmerges(int orient, const ivec &co, const ivec &n, int offset, vector &polys) { - loopv(polys) - { - poly &p = polys[i]; - if(p.merged) addmerge(*p.c, orient, co, n, offset, p); - else clearmerge(*p.c, orient); - } + loopv(polys) + { + poly &p = polys[i]; + if(p.merged) addmerge(*p.c, orient, co, n, offset, p); + else clearmerge(*p.c, orient); + } } void mergepolys(int orient, const ivec &co, const ivec &n, int offset, vector &polys) { - if(polys.length() <= 1) { addmerges(orient, co, n, offset, polys); return; } - hashset links(polys.length() <= 32 ? 128 : 1024); - vector queue; - loopv(polys) - { - poly &p = polys[i]; - int prev = p.numverts-1; - loopj(p.numverts) - { - pedge e(p.verts[prev], p.verts[j]); - int order = e.from.x > e.to.x || (e.from.x == e.to.x && e.from.y > e.to.y) ? 1 : 0; - if(order) swap(e.from, e.to); - plink &l = links.access(e, e); - l.polys[order] = i; - if(l.polys[0] >= 0 && l.polys[1] >= 0) queue.add(&l); - prev = j; - } - } - vector nextqueue; - while(queue.length()) - { - loopv(queue) - { - plink &l = *queue[i]; - if(l.polys[0] >= 0 && l.polys[1] >= 0) - mergepolys(orient, links, nextqueue, l.polys[0], polys[l.polys[0]], polys[l.polys[1]], l); - } - queue.setsize(0); - queue.move(nextqueue); - } - addmerges(orient, co, n, offset, polys); + if(polys.length() <= 1) { addmerges(orient, co, n, offset, polys); return; } + hashset links(polys.length() <= 32 ? 128 : 1024); + vector queue; + loopv(polys) + { + poly &p = polys[i]; + int prev = p.numverts-1; + loopj(p.numverts) + { + pedge e(p.verts[prev], p.verts[j]); + int order = e.from.x > e.to.x || (e.from.x == e.to.x && e.from.y > e.to.y) ? 1 : 0; + if(order) swap(e.from, e.to); + plink &l = links.access(e, e); + l.polys[order] = i; + if(l.polys[0] >= 0 && l.polys[1] >= 0) queue.add(&l); + prev = j; + } + } + vector nextqueue; + while(queue.length()) + { + loopv(queue) + { + plink &l = *queue[i]; + if(l.polys[0] >= 0 && l.polys[1] >= 0) + mergepolys(orient, links, nextqueue, l.polys[0], polys[l.polys[0]], polys[l.polys[1]], l); + } + queue.setsize(0); + queue.move(nextqueue); + } + addmerges(orient, co, n, offset, polys); } static int genmergeprogress = 0; struct cfpolys { - vector polys; + vector polys; }; static hashtable cpolys; void genmerges(cube *c = worldroot, const ivec &o = ivec(0, 0, 0), int size = worldsize>>1) { - if((genmergeprogress++&0xFFF)==0) renderprogress(float(genmergeprogress)/allocnodes, "merging faces..."); - neighbourstack[++neighbourdepth] = c; - loopi(8) - { - ivec co(i, o, size); - int vis; - if(c[i].children) genmerges(c[i].children, co, size>>1); - else if(!isempty(c[i])) loopj(6) if((vis = visibletris(c[i], j, co, size))) - { - cfkey k; - poly p; - if(size < 1<= 1<>1); + else if(!isempty(c[i])) loopj(6) if((vis = visibletris(c[i], j, co, size))) + { + cfkey k; + poly p; + if(size < 1<= 1<= x2 && - mo.y <= y1 && mo.y + (1<= y2 && - mo.z <= z1 && mo.z + (1<= z2) - break; - bits++; - } - return bits-3; + ushort x1 = verts[0].x, y1 = verts[0].y, z1 = verts[0].z, + x2 = x1, y2 = y1, z2 = z1; + for(int i = 1; i < numverts; i++) + { + const vertinfo &v = verts[i]; + x1 = min(x1, v.x); + x2 = max(x2, v.x); + y1 = min(y1, v.y); + y2 = max(y2, v.y); + z1 = min(z1, v.z); + z2 = max(z2, v.z); + } + int bits = 0; + while(1<= x2 && + mo.y <= y1 && mo.y + (1<= y2 && + mo.z <= z1 && mo.z + (1<= z2) + break; + bits++; + } + return bits-3; } static void invalidatemerges(cube &c) { - if(c.merged) - { - brightencube(c); - c.merged = 0; - } - if(c.ext) - { - if(c.ext->va) - { - if(!(c.ext->va->hasmerges&(MERGE_PART | MERGE_ORIGIN))) return; - destroyva(c.ext->va); - c.ext->va = NULL; - } - if(c.ext->tjoints >= 0) c.ext->tjoints = -1; - } - if(c.children) loopi(8) invalidatemerges(c.children[i]); + if(c.merged) + { + brightencube(c); + c.merged = 0; + } + if(c.ext) + { + if(c.ext->va) + { + if(!(c.ext->va->hasmerges&(MERGE_PART | MERGE_ORIGIN))) return; + destroyva(c.ext->va); + c.ext->va = NULL; + } + if(c.ext->tjoints >= 0) c.ext->tjoints = -1; + } + if(c.children) loopi(8) invalidatemerges(c.children[i]); } static int invalidatedmerges = 0; void invalidatemerges(cube &c, const ivec &co, int size, bool msg) { - if(msg && invalidatedmerges!=totalmillis) - { - renderprogress(0, "invalidating merged surfaces..."); - invalidatedmerges = totalmillis; - } - invalidatemerges(c); + if(msg && invalidatedmerges!=totalmillis) + { + renderprogress(0, "invalidating merged surfaces..."); + invalidatedmerges = totalmillis; + } + invalidatemerges(c); } void calcmerges() { - genmergeprogress = 0; - genmerges(); + genmergeprogress = 0; + genmerges(); } diff --git a/src/engine/octa.h b/src/engine/octa.h index b14c440..7312b7a 100644 --- a/src/engine/octa.h +++ b/src/engine/octa.h @@ -2,71 +2,71 @@ struct elementset { - ushort texture, lmid, envmap; - uchar dim, layer; - ushort length[2], minvert[2], maxvert[2]; + ushort texture, lmid, envmap; + uchar dim, layer; + ushort length[2], minvert[2], maxvert[2]; }; enum { - EMID_NONE = 0, - EMID_CUSTOM, - EMID_SKY, - EMID_RESERVED + EMID_NONE = 0, + EMID_CUSTOM, + EMID_SKY, + EMID_RESERVED }; struct materialsurface { - ivec o; - ushort csize, rsize; - ushort material, skip; - uchar orient, visible; - union - { - short index; - short depth; - }; - union - { - entity *light; - ushort envmap; - uchar ends; - }; + ivec o; + ushort csize, rsize; + ushort material, skip; + uchar orient, visible; + union + { + short index; + short depth; + }; + union + { + entity *light; + ushort envmap; + uchar ends; + }; }; struct vertinfo { - ushort x, y, z, u, v, norm; + ushort x, y, z, u, v, norm; - void setxyz(ushort a, ushort b, ushort c) { x = a; y = b; z = c; } - void setxyz(const ivec &v) { setxyz(v.x, v.y, v.z); } - void set(ushort a, ushort b, ushort c, ushort s = 0, ushort t = 0, ushort n = 0) { setxyz(a, b, c); u = s; v = t; norm = n; } - void set(const ivec &v, ushort s = 0, ushort t = 0, ushort n = 0) { set(v.x, v.y, v.z, s, t, n); } - ivec getxyz() const { return ivec(x, y, z); } + void setxyz(ushort a, ushort b, ushort c) { x = a; y = b; z = c; } + void setxyz(const ivec &v) { setxyz(v.x, v.y, v.z); } + void set(ushort a, ushort b, ushort c, ushort s = 0, ushort t = 0, ushort n = 0) { setxyz(a, b, c); u = s; v = t; norm = n; } + void set(const ivec &v, ushort s = 0, ushort t = 0, ushort n = 0) { set(v.x, v.y, v.z, s, t, n); } + ivec getxyz() const { return ivec(x, y, z); } }; enum { - LAYER_TOP = (1<<5), - LAYER_BOTTOM = (1<<6), - LAYER_DUP = (1<<7), + LAYER_TOP = (1<<5), + LAYER_BOTTOM = (1<<6), + LAYER_DUP = (1<<7), - LAYER_BLEND = LAYER_TOP|LAYER_BOTTOM, + LAYER_BLEND = LAYER_TOP|LAYER_BOTTOM, - MAXFACEVERTS = 15 + MAXFACEVERTS = 15 }; enum { LMID_AMBIENT = 0, LMID_AMBIENT1, LMID_BRIGHT, LMID_BRIGHT1, LMID_DARK, LMID_DARK1, LMID_RESERVED }; struct surfaceinfo { - uchar lmid[2]; - uchar verts, numverts; + uchar lmid[2]; + uchar verts, numverts; - int totalverts() const { return numverts&LAYER_DUP ? (numverts&MAXFACEVERTS)*2 : numverts&MAXFACEVERTS; } - bool used() const { return lmid[0] != LMID_AMBIENT || lmid[1] != LMID_AMBIENT || numverts&~LAYER_TOP; } - void clear() { lmid[0] = LMID_AMBIENT; lmid[1] = LMID_AMBIENT; numverts = (numverts&MAXFACEVERTS) | LAYER_TOP; } - void brighten() { lmid[0] = LMID_BRIGHT; lmid[1] = LMID_AMBIENT; numverts = (numverts&MAXFACEVERTS) | LAYER_TOP; } + int totalverts() const { return numverts&LAYER_DUP ? (numverts&MAXFACEVERTS)*2 : numverts&MAXFACEVERTS; } + bool used() const { return lmid[0] != LMID_AMBIENT || lmid[1] != LMID_AMBIENT || numverts&~LAYER_TOP; } + void clear() { lmid[0] = LMID_AMBIENT; lmid[1] = LMID_AMBIENT; numverts = (numverts&MAXFACEVERTS) | LAYER_TOP; } + void brighten() { lmid[0] = LMID_BRIGHT; lmid[1] = LMID_AMBIENT; numverts = (numverts&MAXFACEVERTS) | LAYER_TOP; } }; static const surfaceinfo ambientsurface = {{LMID_AMBIENT, LMID_AMBIENT}, 0, LAYER_TOP}; @@ -75,167 +75,167 @@ static const surfaceinfo brightbottomsurface = {{LMID_AMBIENT, LMID_BRIGHT}, 0, struct occludequery { - void *owner; - GLuint id; - int fragments; + void *owner; + GLuint id; + int fragments; }; struct vtxarray; struct octaentities { - vector mapmodels; - vector other; - occludequery *query; - octaentities *next, *rnext; - int distance; - ivec o; - int size; - ivec bbmin, bbmax; - - octaentities(const ivec &o, int size) : query(0), o(o), size(size), bbmin(o), bbmax(o) - { - bbmin.add(size); - } + vector mapmodels; + vector other; + occludequery *query; + octaentities *next, *rnext; + int distance; + ivec o; + int size; + ivec bbmin, bbmax; + + octaentities(const ivec &o, int size) : query(0), o(o), size(size), bbmin(o), bbmax(o) + { + bbmin.add(size); + } }; enum { - OCCLUDE_NOTHING = 0, - OCCLUDE_GEOM, - OCCLUDE_BB, - OCCLUDE_PARENT + OCCLUDE_NOTHING = 0, + OCCLUDE_GEOM, + OCCLUDE_BB, + OCCLUDE_PARENT }; enum { - MERGE_ORIGIN = 1<<0, - MERGE_PART = 1<<1, - MERGE_USE = 1<<2 + MERGE_ORIGIN = 1<<0, + MERGE_PART = 1<<1, + MERGE_USE = 1<<2 }; struct vtxarray { - vtxarray *parent; - vector children; - vtxarray *next, *rnext; // linked list of visible VOBs - vertex *vdata; // vertex data - ushort voffset; // offset into vertex data - ushort *edata, *skydata; // vertex indices - GLuint vbuf, ebuf, skybuf; // VBOs - ushort minvert, maxvert; // DRE info - elementset *eslist; // List of element indices sets (range) per texture - materialsurface *matbuf; // buffer of material surfaces - int verts, tris, texs, blendtris, blends, alphabacktris, alphaback, alphafronttris, alphafront, alphatris, texmask, sky, explicitsky, skyfaces, skyclip, matsurfs, distance; - double skyarea; - ivec o; - int size; // location and size of cube. - ivec geommin, geommax; // BB of geom - ivec shadowmapmin, shadowmapmax; // BB of shadowmapped surfaces - ivec matmin, matmax; // BB of any materials - ivec bbmin, bbmax; // BB of everything including children - uchar curvfc, occluded; - occludequery *query; - vector mapmodels; - int hasmerges, mergelevel; - uint dynlightmask; - bool shadowed; + vtxarray *parent; + vector children; + vtxarray *next, *rnext; // linked list of visible VOBs + vertex *vdata; // vertex data + ushort voffset; // offset into vertex data + ushort *edata, *skydata; // vertex indices + GLuint vbuf, ebuf, skybuf; // VBOs + ushort minvert, maxvert; // DRE info + elementset *eslist; // List of element indices sets (range) per texture + materialsurface *matbuf; // buffer of material surfaces + int verts, tris, texs, blendtris, blends, alphabacktris, alphaback, alphafronttris, alphafront, alphatris, texmask, sky, explicitsky, skyfaces, skyclip, matsurfs, distance; + double skyarea; + ivec o; + int size; // location and size of cube. + ivec geommin, geommax; // BB of geom + ivec shadowmapmin, shadowmapmax; // BB of shadowmapped surfaces + ivec matmin, matmax; // BB of any materials + ivec bbmin, bbmax; // BB of everything including children + uchar curvfc, occluded; + occludequery *query; + vector mapmodels; + int hasmerges, mergelevel; + uint dynlightmask; + bool shadowed; }; struct cube; struct clipplanes { - vec o, r, v[8]; - plane p[12]; - uchar side[12]; - uchar size, visible; - const cube *owner; - int version; + vec o, r, v[8]; + plane p[12]; + uchar side[12]; + uchar size, visible; + const cube *owner; + int version; }; struct facebounds { - ushort u1, u2, v1, v2; + ushort u1, u2, v1, v2; - bool empty() const { return u1 >= u2 || v1 >= v2; } + bool empty() const { return u1 >= u2 || v1 >= v2; } }; struct tjoint { - int next; - ushort offset; - uchar edge; + int next; + ushort offset; + uchar edge; }; struct cubeext { - vtxarray *va; // vertex array for children, or NULL - octaentities *ents; // map entities inside cube - surfaceinfo surfaces[6]; // render info for each surface - int tjoints; // linked list of t-joints - uchar maxverts; // allocated space for verts + vtxarray *va; // vertex array for children, or NULL + octaentities *ents; // map entities inside cube + surfaceinfo surfaces[6]; // render info for each surface + int tjoints; // linked list of t-joints + uchar maxverts; // allocated space for verts - vertinfo *verts() { return (vertinfo *)(this+1); } + vertinfo *verts() { return (vertinfo *)(this+1); } }; struct cube { - cube *children; // points to 8 cube structures which are its children, or NULL. -Z first, then -Y, -X - cubeext *ext; // extended info for the cube - union - { - uchar edges[12]; // edges of the cube, each uchar is 2 4bit values denoting the range. - // see documentation jpgs for more info. - uint faces[3]; // 4 edges of each dimension together representing 2 perpendicular faces - }; - ushort texture[6]; // one for each face. same order as orient. - ushort material; // empty-space material - uchar merged; // merged faces of the cube - union - { - uchar escaped; // mask of which children have escaped merges - uchar visible; // visibility info for faces - }; + cube *children; // points to 8 cube structures which are its children, or NULL. -Z first, then -Y, -X + cubeext *ext; // extended info for the cube + union + { + uchar edges[12]; // edges of the cube, each uchar is 2 4bit values denoting the range. + // see documentation jpgs for more info. + uint faces[3]; // 4 edges of each dimension together representing 2 perpendicular faces + }; + ushort texture[6]; // one for each face. same order as orient. + ushort material; // empty-space material + uchar merged; // merged faces of the cube + union + { + uchar escaped; // mask of which children have escaped merges + uchar visible; // visibility info for faces + }; }; struct block3 { - ivec o, s; - int grid, orient; - block3() {} - block3(const selinfo &sel) : o(sel.o), s(sel.s), grid(sel.grid), orient(sel.orient) {} - cube *c() { return (cube *)(this+1); } - int size() const { return s.x*s.y*s.z; } + ivec o, s; + int grid, orient; + block3() {} + block3(const selinfo &sel) : o(sel.o), s(sel.s), grid(sel.grid), orient(sel.orient) {} + cube *c() { return (cube *)(this+1); } + int size() const { return s.x*s.y*s.z; } }; struct editinfo { - block3 *copy; - editinfo() : copy(NULL) {} + block3 *copy; + editinfo() : copy(NULL) {} }; struct undoent { int i; entity e; }; struct undoblock // undo header, all data sits in payload { - undoblock *prev, *next; - int size, timestamp, numents; // if numents is 0, is a cube undo record, otherwise an entity undo record - - block3 *block() { return (block3 *)(this + 1); } - uchar *gridmap() - { - block3 *ub = block(); - return (uchar *)(ub->c() + ub->size()); - } - undoent *ents() { return (undoent *)(this + 1); } + undoblock *prev, *next; + int size, timestamp, numents; // if numents is 0, is a cube undo record, otherwise an entity undo record + + block3 *block() { return (block3 *)(this + 1); } + uchar *gridmap() + { + block3 *ub = block(); + return (uchar *)(ub->c() + ub->size()); + } + undoent *ents() { return (undoent *)(this + 1); } }; -extern cube *worldroot; // the world data. only a ptr to 8 cubes (ie: like cube.children above) +extern cube *worldroot; // the world data. only a ptr to 8 cubes (ie: like cube.children above) extern int wtris, wverts, vtris, vverts, glde, gbatches, rplanes; extern int allocnodes, allocva, selchildcount, selchildmat; -const uint F_EMPTY = 0; // all edges in the range (0,0) -const uint F_SOLID = 0x80808080; // all edges in the range (0,8) +const uint F_EMPTY = 0; // all edges in the range (0,0) +const uint F_SOLID = 0x80808080; // all edges in the range (0,8) #define isempty(c) ((c).faces[0]==F_EMPTY) #define isentirelysolid(c) ((c).faces[0]==F_SOLID && (c).faces[1]==F_SOLID && (c).faces[2]==F_SOLID) @@ -249,23 +249,23 @@ const uint F_SOLID = 0x80808080; // all edges in the range (0,8) #define cubeedge(c, d, x, y) ((c).edges[(((d)<<2)+((y)<<1)+(x))]) -#define octadim(d) (1<<(d)) // creates mask for bit of given dimension -#define octacoord(d, i) (((i)&octadim(d))>>(d)) +#define octadim(d) (1<<(d)) // creates mask for bit of given dimension +#define octacoord(d, i) (((i)&octadim(d))>>(d)) #define oppositeocta(d, i) ((i)^octadim(D[d])) #define octaindex(d,x,y,z) (((z)<>(scale))&1)<<2) | ((((y)>>(scale))&1)<<1) | (((x)>>(scale))&1)) static inline uchar octaboxoverlap(const ivec &o, int size, const ivec &bbmin, const ivec &bbmax) { - uchar p = 0xFF; // bitmask of possible collisions with octants. 0 bit = 0 octant, etc - ivec mid = ivec(o).add(size); - if(mid.z <= bbmin.z) p &= 0xF0; // not in a -ve Z octant - else if(mid.z >= bbmax.z) p &= 0x0F; // not in a +ve Z octant - if(mid.y <= bbmin.y) p &= 0xCC; // not in a -ve Y octant - else if(mid.y >= bbmax.y) p &= 0x33; // etc.. - if(mid.x <= bbmin.x) p &= 0xAA; - else if(mid.x >= bbmax.x) p &= 0x55; - return p; + uchar p = 0xFF; // bitmask of possible collisions with octants. 0 bit = 0 octant, etc + ivec mid = ivec(o).add(size); + if(mid.z <= bbmin.z) p &= 0xF0; // not in a -ve Z octant + else if(mid.z >= bbmax.z) p &= 0x0F; // not in a +ve Z octant + if(mid.y <= bbmin.y) p &= 0xCC; // not in a -ve Y octant + else if(mid.y >= bbmax.y) p &= 0x33; // etc.. + if(mid.x <= bbmin.x) p &= 0xAA; + else if(mid.x >= bbmax.x) p &= 0x55; + return p; } #define loopoctabox(o, size, bbmin, bbmax) uchar possible = octaboxoverlap(o, size, bbmin, bbmax); loopi(8) if(possible&(1<>1) @@ -287,41 +287,41 @@ enum enum { - VFC_FULL_VISIBLE = 0, - VFC_PART_VISIBLE, - VFC_FOGGED, - VFC_NOT_VISIBLE, - PVS_FULL_VISIBLE, - PVS_PART_VISIBLE, - PVS_FOGGED + VFC_FULL_VISIBLE = 0, + VFC_PART_VISIBLE, + VFC_FOGGED, + VFC_NOT_VISIBLE, + PVS_FULL_VISIBLE, + PVS_PART_VISIBLE, + PVS_FOGGED }; #define GENCUBEVERTS(x0,x1, y0,y1, z0,z1) \ - GENCUBEVERT(0, x1, y1, z0) \ - GENCUBEVERT(1, x0, y1, z0) \ - GENCUBEVERT(2, x0, y1, z1) \ - GENCUBEVERT(3, x1, y1, z1) \ - GENCUBEVERT(4, x1, y0, z1) \ - GENCUBEVERT(5, x0, y0, z1) \ - GENCUBEVERT(6, x0, y0, z0) \ - GENCUBEVERT(7, x1, y0, z0) + GENCUBEVERT(0, x1, y1, z0) \ + GENCUBEVERT(1, x0, y1, z0) \ + GENCUBEVERT(2, x0, y1, z1) \ + GENCUBEVERT(3, x1, y1, z1) \ + GENCUBEVERT(4, x1, y0, z1) \ + GENCUBEVERT(5, x0, y0, z1) \ + GENCUBEVERT(6, x0, y0, z0) \ + GENCUBEVERT(7, x1, y0, z0) #define GENFACEVERTX(o,n, x,y,z, xv,yv,zv) GENFACEVERT(o,n, x,y,z, xv,yv,zv) #define GENFACEVERTSX(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ - GENFACEORIENT(0, GENFACEVERTX(0,0, x0,y1,z1, d0,r1,c1), GENFACEVERTX(0,1, x0,y1,z0, d0,r1,c0), GENFACEVERTX(0,2, x0,y0,z0, d0,r0,c0), GENFACEVERTX(0,3, x0,y0,z1, d0,r0,c1)) \ - GENFACEORIENT(1, GENFACEVERTX(1,0, x1,y1,z1, d1,r1,c1), GENFACEVERTX(1,1, x1,y0,z1, d1,r0,c1), GENFACEVERTX(1,2, x1,y0,z0, d1,r0,c0), GENFACEVERTX(1,3, x1,y1,z0, d1,r1,c0)) + GENFACEORIENT(0, GENFACEVERTX(0,0, x0,y1,z1, d0,r1,c1), GENFACEVERTX(0,1, x0,y1,z0, d0,r1,c0), GENFACEVERTX(0,2, x0,y0,z0, d0,r0,c0), GENFACEVERTX(0,3, x0,y0,z1, d0,r0,c1)) \ + GENFACEORIENT(1, GENFACEVERTX(1,0, x1,y1,z1, d1,r1,c1), GENFACEVERTX(1,1, x1,y0,z1, d1,r0,c1), GENFACEVERTX(1,2, x1,y0,z0, d1,r0,c0), GENFACEVERTX(1,3, x1,y1,z0, d1,r1,c0)) #define GENFACEVERTY(o,n, x,y,z, xv,yv,zv) GENFACEVERT(o,n, x,y,z, xv,yv,zv) #define GENFACEVERTSY(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ - GENFACEORIENT(2, GENFACEVERTY(2,0, x1,y0,z1, c1,d0,r1), GENFACEVERTY(2,1, x0,y0,z1, c0,d0,r1), GENFACEVERTY(2,2, x0,y0,z0, c0,d0,r0), GENFACEVERTY(2,3, x1,y0,z0, c1,d0,r0)) \ - GENFACEORIENT(3, GENFACEVERTY(3,0, x0,y1,z0, c0,d1,r0), GENFACEVERTY(3,1, x0,y1,z1, c0,d1,r1), GENFACEVERTY(3,2, x1,y1,z1, c1,d1,r1), GENFACEVERTY(3,3, x1,y1,z0, c1,d1,r0)) + GENFACEORIENT(2, GENFACEVERTY(2,0, x1,y0,z1, c1,d0,r1), GENFACEVERTY(2,1, x0,y0,z1, c0,d0,r1), GENFACEVERTY(2,2, x0,y0,z0, c0,d0,r0), GENFACEVERTY(2,3, x1,y0,z0, c1,d0,r0)) \ + GENFACEORIENT(3, GENFACEVERTY(3,0, x0,y1,z0, c0,d1,r0), GENFACEVERTY(3,1, x0,y1,z1, c0,d1,r1), GENFACEVERTY(3,2, x1,y1,z1, c1,d1,r1), GENFACEVERTY(3,3, x1,y1,z0, c1,d1,r0)) #define GENFACEVERTZ(o,n, x,y,z, xv,yv,zv) GENFACEVERT(o,n, x,y,z, xv,yv,zv) #define GENFACEVERTSZ(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ - GENFACEORIENT(4, GENFACEVERTZ(4,0, x0,y0,z0, r0,c0,d0), GENFACEVERTZ(4,1, x0,y1,z0, r0,c1,d0), GENFACEVERTZ(4,2, x1,y1,z0, r1,c1,d0), GENFACEVERTZ(4,3, x1,y0,z0, r1,c0,d0)) \ - GENFACEORIENT(5, GENFACEVERTZ(5,0, x0,y0,z1, r0,c0,d1), GENFACEVERTZ(5,1, x1,y0,z1, r1,c0,d1), GENFACEVERTZ(5,2, x1,y1,z1, r1,c1,d1), GENFACEVERTZ(5,3, x0,y1,z1, r0,c1,d1)) + GENFACEORIENT(4, GENFACEVERTZ(4,0, x0,y0,z0, r0,c0,d0), GENFACEVERTZ(4,1, x0,y1,z0, r0,c1,d0), GENFACEVERTZ(4,2, x1,y1,z0, r1,c1,d0), GENFACEVERTZ(4,3, x1,y0,z0, r1,c0,d0)) \ + GENFACEORIENT(5, GENFACEVERTZ(5,0, x0,y0,z1, r0,c0,d1), GENFACEVERTZ(5,1, x1,y0,z1, r1,c0,d1), GENFACEVERTZ(5,2, x1,y1,z1, r1,c1,d1), GENFACEVERTZ(5,3, x0,y1,z1, r0,c1,d1)) #define GENFACEVERTSXY(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ - GENFACEVERTSX(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ - GENFACEVERTSY(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) + GENFACEVERTSX(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ + GENFACEVERTSY(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) #define GENFACEVERTS(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ - GENFACEVERTSXY(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ - GENFACEVERTSZ(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) + GENFACEVERTSXY(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) \ + GENFACEVERTSZ(x0,x1, y0,y1, z0,z1, c0,c1, r0,r1, d0,d1) diff --git a/src/engine/octaedit.cpp b/src/engine/octaedit.cpp index af69db9..fa987d0 100644 --- a/src/engine/octaedit.cpp +++ b/src/engine/octaedit.cpp @@ -6,88 +6,88 @@ bool boxoutline = false; void boxs(int orient, vec o, const vec &s, float size) { - int d = dimension(orient), dc = dimcoord(orient); - float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0; - o[D[d]] += dc * s[D[d]] + f; - - vec r(0, 0, 0), c(0, 0, 0); - r[R[d]] = s[R[d]]; - c[C[d]] = s[C[d]]; - - vec v1 = o, v2 = vec(o).add(r), v3 = vec(o).add(r).add(c), v4 = vec(o).add(c); - - r[R[d]] = 0.5f*size; - c[C[d]] = 0.5f*size; - - gle::defvertex(); - gle::begin(GL_TRIANGLE_STRIP); - gle::attrib(vec(v1).sub(r).sub(c)); - gle::attrib(vec(v1).add(r).add(c)); - gle::attrib(vec(v2).add(r).sub(c)); - gle::attrib(vec(v2).sub(r).add(c)); - gle::attrib(vec(v3).add(r).add(c)); - gle::attrib(vec(v3).sub(r).sub(c)); - gle::attrib(vec(v4).sub(r).add(c)); - gle::attrib(vec(v4).add(r).sub(c)); - gle::attrib(vec(v1).sub(r).sub(c)); - gle::attrib(vec(v1).add(r).add(c)); - xtraverts += gle::end(); + int d = dimension(orient), dc = dimcoord(orient); + float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0; + o[D[d]] += dc * s[D[d]] + f; + + vec r(0, 0, 0), c(0, 0, 0); + r[R[d]] = s[R[d]]; + c[C[d]] = s[C[d]]; + + vec v1 = o, v2 = vec(o).add(r), v3 = vec(o).add(r).add(c), v4 = vec(o).add(c); + + r[R[d]] = 0.5f*size; + c[C[d]] = 0.5f*size; + + gle::defvertex(); + gle::begin(GL_TRIANGLE_STRIP); + gle::attrib(vec(v1).sub(r).sub(c)); + gle::attrib(vec(v1).add(r).add(c)); + gle::attrib(vec(v2).add(r).sub(c)); + gle::attrib(vec(v2).sub(r).add(c)); + gle::attrib(vec(v3).add(r).add(c)); + gle::attrib(vec(v3).sub(r).sub(c)); + gle::attrib(vec(v4).sub(r).add(c)); + gle::attrib(vec(v4).add(r).sub(c)); + gle::attrib(vec(v1).sub(r).sub(c)); + gle::attrib(vec(v1).add(r).add(c)); + xtraverts += gle::end(); } void boxs(int orient, vec o, const vec &s) { - int d = dimension(orient), dc = dimcoord(orient); - float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0; - o[D[d]] += dc * s[D[d]] + f; + int d = dimension(orient), dc = dimcoord(orient); + float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0; + o[D[d]] += dc * s[D[d]] + f; - gle::defvertex(); - gle::begin(GL_LINE_LOOP); + gle::defvertex(); + gle::begin(GL_LINE_LOOP); - gle::attrib(o); o[R[d]] += s[R[d]]; - gle::attrib(o); o[C[d]] += s[C[d]]; - gle::attrib(o); o[R[d]] -= s[R[d]]; - gle::attrib(o); + gle::attrib(o); o[R[d]] += s[R[d]]; + gle::attrib(o); o[C[d]] += s[C[d]]; + gle::attrib(o); o[R[d]] -= s[R[d]]; + gle::attrib(o); - xtraverts += gle::end(); + xtraverts += gle::end(); } void boxs3D(const vec &o, vec s, int g) { - s.mul(g); - loopi(6) - boxs(i, o, s); + s.mul(g); + loopi(6) + boxs(i, o, s); } void boxsgrid(int orient, vec o, vec s, int g) { - int d = dimension(orient), dc = dimcoord(orient); - float ox = o[R[d]], - oy = o[C[d]], - xs = s[R[d]], - ys = s[C[d]], - f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0; - - o[D[d]] += dc * s[D[d]]*g + f; - - gle::defvertex(); - gle::begin(GL_LINES); - loop(x, xs) - { - o[R[d]] += g; - gle::attrib(o); - o[C[d]] += ys*g; - gle::attrib(o); - o[C[d]] = oy; - } - loop(y, ys) - { - o[C[d]] += g; - o[R[d]] = ox; - gle::attrib(o); - o[R[d]] += xs*g; - gle::attrib(o); - } - xtraverts += gle::end(); + int d = dimension(orient), dc = dimcoord(orient); + float ox = o[R[d]], + oy = o[C[d]], + xs = s[R[d]], + ys = s[C[d]], + f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0; + + o[D[d]] += dc * s[D[d]]*g + f; + + gle::defvertex(); + gle::begin(GL_LINES); + loop(x, xs) + { + o[R[d]] += g; + gle::attrib(o); + o[C[d]] += ys*g; + gle::attrib(o); + o[C[d]] = oy; + } + loop(y, ys) + { + o[C[d]] += g; + o[R[d]] = ox; + gle::attrib(o); + o[R[d]] += xs*g; + gle::attrib(o); + } + xtraverts += gle::end(); } selinfo sel, lastsel, savedsel; @@ -106,30 +106,30 @@ int horient = 0; extern int entmoving; VARF(dragging, 0, 0, 1, - if(!dragging || cor[0]<0) return; - lastcur = cur; - lastcor = cor; - sel.grid = gridsize; - sel.orient = orient; + if(!dragging || cor[0]<0) return; + lastcur = cur; + lastcor = cor; + sel.grid = gridsize; + sel.orient = orient; ); int moving = 0; ICOMMAND(moving, "b", (int *n), { - if(*n >= 0) - { - if(!*n || (moving<=1 && !pointinsel(sel, vec(cur).add(1)))) moving = 0; - else if(!moving) moving = 1; - } - intret(moving); + if(*n >= 0) + { + if(!*n || (moving<=1 && !pointinsel(sel, vec(cur).add(1)))) moving = 0; + else if(!moving) moving = 1; + } + intret(moving); }); VARF(gridpower, 0, 3, 12, { - if(dragging) return; - gridsize = 1<=worldsize) gridsize = worldsize/2; - cancelsel(); + if(dragging) return; + gridsize = 1<=worldsize) gridsize = worldsize/2; + cancelsel(); }); VAR(passthroughsel, 0, 0, 1); @@ -143,88 +143,88 @@ extern void hmapcancel(); void cubecancel() { - havesel = false; - moving = dragging = hmapedit = passthroughsel = 0; - forcenextundo(); - hmapcancel(); + havesel = false; + moving = dragging = hmapedit = passthroughsel = 0; + forcenextundo(); + hmapcancel(); } void cancelsel() { - cubecancel(); - entcancel(); + cubecancel(); + entcancel(); } void toggleedit(bool force) { - if(!force) - { - if(!isconnected()) return; - if(player->state!=CS_ALIVE && player->state!=CS_DEAD && player->state!=CS_EDITING) return; // do not allow dead players to edit to avoid state confusion - if(!game::allowedittoggle()) return; // not in most multiplayer modes - } - if(!(editmode = !editmode)) - { - player->state = player->editstate; - player->o.z -= player->eyeheight; // entinmap wants feet pos - entinmap(player); // find spawn closest to current floating pos - } - else - { - game::resetgamestate(); - player->editstate = player->state; - player->state = CS_EDITING; - } - cancelsel(); - stoppaintblendmap(); - keyrepeat(editmode); - editing = entediting = editmode; - extern int fullbright; - if(fullbright) { initlights(); lightents(); } - if(!force) game::edittoggled(editmode); + if(!force) + { + if(!isconnected()) return; + if(player->state!=CS_ALIVE && player->state!=CS_DEAD && player->state!=CS_EDITING) return; // do not allow dead players to edit to avoid state confusion + if(!game::allowedittoggle()) return; // not in most multiplayer modes + } + if(!(editmode = !editmode)) + { + player->state = player->editstate; + player->o.z -= player->eyeheight; // entinmap wants feet pos + entinmap(player); // find spawn closest to current floating pos + } + else + { + game::resetgamestate(); + player->editstate = player->state; + player->state = CS_EDITING; + } + cancelsel(); + stoppaintblendmap(); + keyrepeat(editmode); + editing = entediting = editmode; + extern int fullbright; + if(fullbright) { initlights(); lightents(); } + if(!force) game::edittoggled(editmode); } VARP(editinview, 0, 1, 1); bool noedit(bool view, bool msg) { - if(!editmode) { if(msg) conoutf(CON_ERROR, "operation only allowed in edit mode"); return true; } - if(view || haveselent()) return false; - float r = 1.0f; - vec o(sel.o), s(sel.s); - s.mul(float(sel.grid) / 2.0f); - o.add(s); - r = float(max(s.x, max(s.y, s.z))); - bool viewable = (isvisiblesphere(r, o) != VFC_NOT_VISIBLE); - if(viewable || !editinview) return false; - if(msg) conoutf(CON_ERROR, "selection not in view"); - return true; + if(!editmode) { if(msg) conoutf(CON_ERROR, "operation only allowed in edit mode"); return true; } + if(view || haveselent()) return false; + float r = 1.0f; + vec o(sel.o), s(sel.s); + s.mul(float(sel.grid) / 2.0f); + o.add(s); + r = float(max(s.x, max(s.y, s.z))); + bool viewable = (isvisiblesphere(r, o) != VFC_NOT_VISIBLE); + if(viewable || !editinview) return false; + if(msg) conoutf(CON_ERROR, "selection not in view"); + return true; } void reorient() { - sel.cx = 0; - sel.cy = 0; - sel.cxs = sel.s[R[dimension(orient)]]*2; - sel.cys = sel.s[C[dimension(orient)]]*2; - sel.orient = orient; + sel.cx = 0; + sel.cy = 0; + sel.cxs = sel.s[R[dimension(orient)]]*2; + sel.cys = sel.s[C[dimension(orient)]]*2; + sel.orient = orient; } void selextend() { - if(noedit(true)) return; - loopi(3) - { - if(cur[i]=sel.o[i]+sel.s[i]*sel.grid) - { - sel.s[i] = (cur[i]-sel.o[i])/sel.grid+1; - } - } + if(noedit(true)) return; + loopi(3) + { + if(cur[i]=sel.o[i]+sel.s[i]*sel.grid) + { + sel.s[i] = (cur[i]-sel.o[i])/sel.grid+1; + } + } } ICOMMAND(edittoggle, "", (), toggleedit(false)); @@ -241,24 +241,24 @@ ICOMMAND(selswap, "", (), { if(noedit(true)) return; swap(sel, savedsel); }); ICOMMAND(getselpos, "", (), { - if(noedit(true)) return; - defformatstring(pos, "%s %s %s", floatstr(sel.o.x), floatstr(sel.o.y), floatstr(sel.o.z)); - result(pos); + if(noedit(true)) return; + defformatstring(pos, "%s %s %s", floatstr(sel.o.x), floatstr(sel.o.y), floatstr(sel.o.z)); + result(pos); }); void setselpos(int *x, int *y, int *z) { - if(noedit(moving!=0)) return; - havesel = true; - sel.o = ivec(*x, *y, *z).mask(~(gridsize-1)); + if(noedit(moving!=0)) return; + havesel = true; + sel.o = ivec(*x, *y, *z).mask(~(gridsize-1)); } COMMAND(setselpos, "iii"); void movesel(int *dir, int *dim) { - if(noedit(moving!=0)) return; - if(*dim < 0 || *dim > 2) return; - sel.o[*dim] += *dir * sel.grid; + if(noedit(moving!=0)) return; + if(*dim < 0 || *dim > 2) return; + sel.o[*dim] += *dir * sel.grid; } COMMAND(movesel, "ii"); @@ -266,16 +266,16 @@ COMMAND(movesel, "ii"); cube &blockcube(int x, int y, int z, const block3 &b, int rgrid) // looks up a world cube, based on coordinates mapped by the block { - int dim = dimension(b.orient), dc = dimcoord(b.orient); - ivec s(dim, x*b.grid, y*b.grid, dc*(b.s[dim]-1)*b.grid); - s.add(b.o); - if(dc) s[dim] -= z*b.grid; else s[dim] += z*b.grid; - return lookupcube(s, rgrid); + int dim = dimension(b.orient), dc = dimcoord(b.orient); + ivec s(dim, x*b.grid, y*b.grid, dc*(b.s[dim]-1)*b.grid); + s.add(b.o); + if(dc) s[dim] -= z*b.grid; else s[dim] += z*b.grid; + return lookupcube(s, rgrid); } -#define loopxy(b) loop(y,(b).s[C[dimension((b).orient)]]) loop(x,(b).s[R[dimension((b).orient)]]) +#define loopxy(b) loop(y,(b).s[C[dimension((b).orient)]]) loop(x,(b).s[R[dimension((b).orient)]]) #define loopxyz(b, r, f) { loop(z,(b).s[D[dimension((b).orient)]]) loopxy((b)) { cube &c = blockcube(x,y,z,b,r); f; } } -#define loopselxyz(f) { if(local) makeundo(); loopxyz(sel, sel.grid, f); changed(sel); } +#define loopselxyz(f) { if(local) makeundo(); loopxyz(sel, sel.grid, f); changed(sel); } #define selcube(x, y, z) blockcube(x, y, z, sel, sel.grid) ////////////// cursor /////////////// @@ -286,61 +286,61 @@ ICOMMAND(havesel, "", (), intret(havesel ? selchildcount : 0)); void countselchild(cube *c, const ivec &cor, int size) { - ivec ss = ivec(sel.s).mul(sel.grid); - loopoctaboxsize(cor, size, sel.o, ss) - { - ivec o(i, cor, size); - if(c[i].children) countselchild(c[i].children, o, size/2); - else - { - selchildcount++; - if(c[i].material != MAT_AIR && selchildmat != MAT_AIR) - { - if(selchildmat < 0) selchildmat = c[i].material; - else if(selchildmat != c[i].material) selchildmat = MAT_AIR; - } - } - } + ivec ss = ivec(sel.s).mul(sel.grid); + loopoctaboxsize(cor, size, sel.o, ss) + { + ivec o(i, cor, size); + if(c[i].children) countselchild(c[i].children, o, size/2); + else + { + selchildcount++; + if(c[i].material != MAT_AIR && selchildmat != MAT_AIR) + { + if(selchildmat < 0) selchildmat = c[i].material; + else if(selchildmat != c[i].material) selchildmat = MAT_AIR; + } + } + } } void normalizelookupcube(const ivec &o) { - if(lusize>gridsize) - { - lu.x += (o.x-lu.x)/gridsize*gridsize; - lu.y += (o.y-lu.y)/gridsize*gridsize; - lu.z += (o.z-lu.z)/gridsize*gridsize; - } - else if(gridsize>lusize) - { - lu.x &= ~(gridsize-1); - lu.y &= ~(gridsize-1); - lu.z &= ~(gridsize-1); - } - lusize = gridsize; + if(lusize>gridsize) + { + lu.x += (o.x-lu.x)/gridsize*gridsize; + lu.y += (o.y-lu.y)/gridsize*gridsize; + lu.z += (o.z-lu.z)/gridsize*gridsize; + } + else if(gridsize>lusize) + { + lu.x &= ~(gridsize-1); + lu.y &= ~(gridsize-1); + lu.z &= ~(gridsize-1); + } + lusize = gridsize; } void updateselection() { - sel.o.x = min(lastcur.x, cur.x); - sel.o.y = min(lastcur.y, cur.y); - sel.o.z = min(lastcur.z, cur.z); - sel.s.x = abs(lastcur.x-cur.x)/sel.grid+1; - sel.s.y = abs(lastcur.y-cur.y)/sel.grid+1; - sel.s.z = abs(lastcur.z-cur.z)/sel.grid+1; + sel.o.x = min(lastcur.x, cur.x); + sel.o.y = min(lastcur.y, cur.y); + sel.o.z = min(lastcur.z, cur.z); + sel.s.x = abs(lastcur.x-cur.x)/sel.grid+1; + sel.s.y = abs(lastcur.y-cur.y)/sel.grid+1; + sel.s.z = abs(lastcur.z-cur.z)/sel.grid+1; } bool editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first) { - plane pl(d, off); - float dist = 0.0f; - if(!pl.rayintersect(player->o, ray, dist)) - return false; + plane pl(d, off); + float dist = 0.0f; + if(!pl.rayintersect(player->o, ray, dist)) + return false; - dest = vec(ray).mul(dist).add(player->o); - if(first) handle = vec(dest).sub(o); - dest.sub(handle); - return true; + dest = vec(ray).mul(dist).add(player->o); + if(first) handle = vec(dest).sub(o); + dest.sub(handle); + return true; } inline bool isheightmap(int orient, int d, bool empty, cube *c); @@ -356,203 +356,203 @@ VARF(passthrough, 0, 0, 1, { passthroughsel = passthrough; entcancel(); }); void rendereditcursor() { - int d = dimension(sel.orient), - od = dimension(orient), - odc = dimcoord(orient); - - bool hidecursor = g3d_windowhit(true, false) || blendpaintmode, hovering = false; - hmapsel = false; - - if(moving) - { - static vec dest, handle; - if(editmoveplane(vec(sel.o), camdir, od, sel.o[D[od]]+odc*sel.grid*sel.s[D[od]], handle, dest, moving==1)) - { - if(moving==1) - { - dest.add(handle); - handle = vec(ivec(handle).mask(~(sel.grid-1))); - dest.sub(handle); - moving = 2; - } - ivec o = ivec(dest).mask(~(sel.grid-1)); - sel.o[R[od]] = o[R[od]]; - sel.o[C[od]] = o[C[od]]; - } - } - else - if(entmoving) - { - entdrag(camdir); - } - else - { - ivec w; - float sdist = 0, wdist = 0, t; - int entorient = 0, ent = -1; - - wdist = rayent(player->o, camdir, 1e16f, - (editmode && showmat ? RAY_EDITMAT : 0) // select cubes first - | (!dragging && entediting && (!passthrough || !passthroughent) ? RAY_ENTS : 0) - | RAY_SKIPFIRST - | (passthroughcube || passthrough ? RAY_PASS : 0), gridsize, entorient, ent); - - if((havesel || dragging) && !passthroughsel && !hmapedit) // now try selecting the selection - if(rayboxintersect(vec(sel.o), vec(sel.s).mul(sel.grid), player->o, camdir, sdist, orient)) - { // and choose the nearest of the two - if(sdist < wdist) - { - wdist = sdist; - ent = -1; - } - } - - if((hovering = hoveringonent(hidecursor ? -1 : ent, entorient))) - { - if(!havesel) - { - selchildcount = 0; - selchildmat = -1; - sel.s = ivec(0, 0, 0); - } - } - else - { - vec w = vec(camdir).mul(wdist+0.05f).add(player->o); - if(!insideworld(w)) - { - loopi(3) wdist = min(wdist, ((camdir[i] > 0 ? worldsize : 0) - player->o[i]) / camdir[i]); - w = vec(camdir).mul(wdist-0.05f).add(player->o); - if(!insideworld(w)) - { - wdist = 0; - loopi(3) w[i] = clamp(player->o[i], 0.0f, float(worldsize)); - } - } - cube *c = &lookupcube(ivec(w)); - if(gridlookup && !dragging && !moving && !havesel && hmapedit!=1) gridsize = lusize; - int mag = lusize / gridsize; - normalizelookupcube(ivec(w)); - if(sdist == 0 || sdist > wdist) rayboxintersect(vec(lu), vec(gridsize), player->o, camdir, t=0, orient); // just getting orient - cur = lu; - cor = ivec(vec(w).mul(2).div(gridsize)); - od = dimension(orient); - d = dimension(sel.orient); - - if(hmapedit==1 && dimcoord(horient) == (camdir[dimension(horient)]<0)) - { - hmapsel = isheightmap(horient, dimension(horient), false, c); - if(hmapsel) - od = dimension(orient = horient); - } - - if(dragging) - { - updateselection(); - sel.cx = min(cor[R[d]], lastcor[R[d]]); - sel.cy = min(cor[C[d]], lastcor[C[d]]); - sel.cxs = max(cor[R[d]], lastcor[R[d]]); - sel.cys = max(cor[C[d]], lastcor[C[d]]); - - if(!selectcorners) - { - sel.cx &= ~1; - sel.cy &= ~1; - sel.cxs &= ~1; - sel.cys &= ~1; - sel.cxs -= sel.cx-2; - sel.cys -= sel.cy-2; - } - else - { - sel.cxs -= sel.cx-1; - sel.cys -= sel.cy-1; - } - - sel.cx &= 1; - sel.cy &= 1; - havesel = true; - } - else if(!havesel) - { - sel.o = lu; - sel.s.x = sel.s.y = sel.s.z = 1; - sel.cx = sel.cy = 0; - sel.cxs = sel.cys = 2; - sel.grid = gridsize; - sel.orient = orient; - d = od; - } - - sel.corner = (cor[R[d]]-(lu[R[d]]*2)/gridsize)+(cor[C[d]]-(lu[C[d]]*2)/gridsize)*2; - selchildcount = 0; - selchildmat = -1; - countselchild(worldroot, ivec(0, 0, 0), worldsize/2); - if(mag>=1 && selchildcount==1) - { - selchildmat = c->material; - if(mag>1) selchildcount = -mag; - } - } - } - - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE); - - // cursors - - notextureshader->set(); - - renderentselection(player->o, camdir, entmoving!=0); - - boxoutline = outline!=0; - - enablepolygonoffset(GL_POLYGON_OFFSET_LINE); - - if(!moving && !hovering && !hidecursor) - { - if(hmapedit==1) - gle::colorub(0, hmapsel ? 255 : 40, 0); - else - gle::colorub(120,120,120); - boxs(orient, vec(lu), vec(lusize)); - } - - // selections - if(havesel || moving) - { - d = dimension(sel.orient); - gle::colorub(50,50,50); // grid - boxsgrid(sel.orient, vec(sel.o), vec(sel.s), sel.grid); - gle::colorub(200,0,0); // 0 reference - boxs3D(vec(sel.o).sub(0.5f*min(gridsize*0.25f, 2.0f)), vec(min(gridsize*0.25f, 2.0f)), 1); - gle::colorub(200,200,200);// 2D selection box - vec co(sel.o.v), cs(sel.s.v); - co[R[d]] += 0.5f*(sel.cx*gridsize); - co[C[d]] += 0.5f*(sel.cy*gridsize); - cs[R[d]] = 0.5f*(sel.cxs*gridsize); - cs[C[d]] = 0.5f*(sel.cys*gridsize); - cs[D[d]] *= gridsize; - boxs(sel.orient, co, cs); - if(hmapedit==1) // 3D selection box - gle::colorub(0,120,0); - else - gle::colorub(0,0,120); - boxs3D(vec(sel.o), vec(sel.s), sel.grid); - } - - disablepolygonoffset(GL_POLYGON_OFFSET_LINE); - - boxoutline = false; - - glDisable(GL_BLEND); + int d = dimension(sel.orient), + od = dimension(orient), + odc = dimcoord(orient); + + bool hidecursor = g3d_windowhit(true, false) || blendpaintmode, hovering = false; + hmapsel = false; + + if(moving) + { + static vec dest, handle; + if(editmoveplane(vec(sel.o), camdir, od, sel.o[D[od]]+odc*sel.grid*sel.s[D[od]], handle, dest, moving==1)) + { + if(moving==1) + { + dest.add(handle); + handle = vec(ivec(handle).mask(~(sel.grid-1))); + dest.sub(handle); + moving = 2; + } + ivec o = ivec(dest).mask(~(sel.grid-1)); + sel.o[R[od]] = o[R[od]]; + sel.o[C[od]] = o[C[od]]; + } + } + else + if(entmoving) + { + entdrag(camdir); + } + else + { + ivec w; + float sdist = 0, wdist = 0, t; + int entorient = 0, ent = -1; + + wdist = rayent(player->o, camdir, 1e16f, + (editmode && showmat ? RAY_EDITMAT : 0) // select cubes first + | (!dragging && entediting && (!passthrough || !passthroughent) ? RAY_ENTS : 0) + | RAY_SKIPFIRST + | (passthroughcube || passthrough ? RAY_PASS : 0), gridsize, entorient, ent); + + if((havesel || dragging) && !passthroughsel && !hmapedit) // now try selecting the selection + if(rayboxintersect(vec(sel.o), vec(sel.s).mul(sel.grid), player->o, camdir, sdist, orient)) + { // and choose the nearest of the two + if(sdist < wdist) + { + wdist = sdist; + ent = -1; + } + } + + if((hovering = hoveringonent(hidecursor ? -1 : ent, entorient))) + { + if(!havesel) + { + selchildcount = 0; + selchildmat = -1; + sel.s = ivec(0, 0, 0); + } + } + else + { + vec w = vec(camdir).mul(wdist+0.05f).add(player->o); + if(!insideworld(w)) + { + loopi(3) wdist = min(wdist, ((camdir[i] > 0 ? worldsize : 0) - player->o[i]) / camdir[i]); + w = vec(camdir).mul(wdist-0.05f).add(player->o); + if(!insideworld(w)) + { + wdist = 0; + loopi(3) w[i] = clamp(player->o[i], 0.0f, float(worldsize)); + } + } + cube *c = &lookupcube(ivec(w)); + if(gridlookup && !dragging && !moving && !havesel && hmapedit!=1) gridsize = lusize; + int mag = lusize / gridsize; + normalizelookupcube(ivec(w)); + if(sdist == 0 || sdist > wdist) rayboxintersect(vec(lu), vec(gridsize), player->o, camdir, t=0, orient); // just getting orient + cur = lu; + cor = ivec(vec(w).mul(2).div(gridsize)); + od = dimension(orient); + d = dimension(sel.orient); + + if(hmapedit==1 && dimcoord(horient) == (camdir[dimension(horient)]<0)) + { + hmapsel = isheightmap(horient, dimension(horient), false, c); + if(hmapsel) + od = dimension(orient = horient); + } + + if(dragging) + { + updateselection(); + sel.cx = min(cor[R[d]], lastcor[R[d]]); + sel.cy = min(cor[C[d]], lastcor[C[d]]); + sel.cxs = max(cor[R[d]], lastcor[R[d]]); + sel.cys = max(cor[C[d]], lastcor[C[d]]); + + if(!selectcorners) + { + sel.cx &= ~1; + sel.cy &= ~1; + sel.cxs &= ~1; + sel.cys &= ~1; + sel.cxs -= sel.cx-2; + sel.cys -= sel.cy-2; + } + else + { + sel.cxs -= sel.cx-1; + sel.cys -= sel.cy-1; + } + + sel.cx &= 1; + sel.cy &= 1; + havesel = true; + } + else if(!havesel) + { + sel.o = lu; + sel.s.x = sel.s.y = sel.s.z = 1; + sel.cx = sel.cy = 0; + sel.cxs = sel.cys = 2; + sel.grid = gridsize; + sel.orient = orient; + d = od; + } + + sel.corner = (cor[R[d]]-(lu[R[d]]*2)/gridsize)+(cor[C[d]]-(lu[C[d]]*2)/gridsize)*2; + selchildcount = 0; + selchildmat = -1; + countselchild(worldroot, ivec(0, 0, 0), worldsize/2); + if(mag>=1 && selchildcount==1) + { + selchildmat = c->material; + if(mag>1) selchildcount = -mag; + } + } + } + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + + // cursors + + notextureshader->set(); + + renderentselection(player->o, camdir, entmoving!=0); + + boxoutline = outline!=0; + + enablepolygonoffset(GL_POLYGON_OFFSET_LINE); + + if(!moving && !hovering && !hidecursor) + { + if(hmapedit==1) + gle::colorub(0, hmapsel ? 255 : 40, 0); + else + gle::colorub(120,120,120); + boxs(orient, vec(lu), vec(lusize)); + } + + // selections + if(havesel || moving) + { + d = dimension(sel.orient); + gle::colorub(50,50,50); // grid + boxsgrid(sel.orient, vec(sel.o), vec(sel.s), sel.grid); + gle::colorub(200,0,0); // 0 reference + boxs3D(vec(sel.o).sub(0.5f*min(gridsize*0.25f, 2.0f)), vec(min(gridsize*0.25f, 2.0f)), 1); + gle::colorub(200,200,200);// 2D selection box + vec co(sel.o.v), cs(sel.s.v); + co[R[d]] += 0.5f*(sel.cx*gridsize); + co[C[d]] += 0.5f*(sel.cy*gridsize); + cs[R[d]] = 0.5f*(sel.cxs*gridsize); + cs[C[d]] = 0.5f*(sel.cys*gridsize); + cs[D[d]] *= gridsize; + boxs(sel.orient, co, cs); + if(hmapedit==1) // 3D selection box + gle::colorub(0,120,0); + else + gle::colorub(0,0,120); + boxs3D(vec(sel.o), vec(sel.s), sel.grid); + } + + disablepolygonoffset(GL_POLYGON_OFFSET_LINE); + + boxoutline = false; + + glDisable(GL_BLEND); } void tryedit() { - extern int hidehud; - if(!editmode || hidehud || mainmenu) return; - if(blendpaintmode) trypaintblendmap(); + extern int hidehud; + if(!editmode || hidehud || mainmenu) return; + if(blendpaintmode) trypaintblendmap(); } //////////// ready changes to vertex arrays //////////// @@ -561,197 +561,196 @@ static bool haschanged = false; void readychanges(const ivec &bbmin, const ivec &bbmax, cube *c, const ivec &cor, int size) { - loopoctabox(cor, size, bbmin, bbmax) - { - ivec o(i, cor, size); - if(c[i].ext) - { - if(c[i].ext->va) // removes va s so that octarender will recreate - { - int hasmerges = c[i].ext->va->hasmerges; - destroyva(c[i].ext->va); - c[i].ext->va = NULL; - if(hasmerges) invalidatemerges(c[i], o, size, true); - } - freeoctaentities(c[i]); - c[i].ext->tjoints = -1; - } - if(c[i].children) - { - if(size<=1) - { - solidfaces(c[i]); - discardchildren(c[i], true); - brightencube(c[i]); - } - else readychanges(bbmin, bbmax, c[i].children, o, size/2); - } - else brightencube(c[i]); - } + loopoctabox(cor, size, bbmin, bbmax) + { + ivec o(i, cor, size); + if(c[i].ext) + { + if(c[i].ext->va) // removes va s so that octarender will recreate + { + int hasmerges = c[i].ext->va->hasmerges; + destroyva(c[i].ext->va); + c[i].ext->va = NULL; + if(hasmerges) invalidatemerges(c[i], o, size, true); + } + freeoctaentities(c[i]); + c[i].ext->tjoints = -1; + } + if(c[i].children) + { + if(size<=1) + { + solidfaces(c[i]); + discardchildren(c[i], true); + brightencube(c[i]); + } + else readychanges(bbmin, bbmax, c[i].children, o, size/2); + } + else brightencube(c[i]); + } } void commitchanges(bool force) { - if(!force && !haschanged) return; - haschanged = false; + if(!force && !haschanged) return; + haschanged = false; - int oldlen = valist.length(); - resetclipplanes(); - entitiesinoctanodes(); - inbetweenframes = false; - octarender(); - inbetweenframes = true; - setupmaterials(oldlen); - invalidatepostfx(); - updatevabbs(); + int oldlen = valist.length(); + resetclipplanes(); + entitiesinoctanodes(); + inbetweenframes = false; + octarender(); + inbetweenframes = true; + setupmaterials(oldlen); + updatevabbs(); } void changed(const block3 &sel, bool commit = true) { - if(sel.s.iszero()) return; - readychanges(ivec(sel.o).sub(1), ivec(sel.s).mul(sel.grid).add(sel.o).add(1), worldroot, ivec(0, 0, 0), worldsize/2); - haschanged = true; + if(sel.s.iszero()) return; + readychanges(ivec(sel.o).sub(1), ivec(sel.s).mul(sel.grid).add(sel.o).add(1), worldroot, ivec(0, 0, 0), worldsize/2); + haschanged = true; - if(commit) commitchanges(); + if(commit) commitchanges(); } //////////// copy and undo ///////////// static inline void copycube(const cube &src, cube &dst) { - dst = src; - dst.visible = 0; - dst.merged = 0; - dst.ext = NULL; // src cube is responsible for va destruction - if(src.children) - { - dst.children = newcubes(F_EMPTY); - loopi(8) copycube(src.children[i], dst.children[i]); - } + dst = src; + dst.visible = 0; + dst.merged = 0; + dst.ext = NULL; // src cube is responsible for va destruction + if(src.children) + { + dst.children = newcubes(F_EMPTY); + loopi(8) copycube(src.children[i], dst.children[i]); + } } static inline void pastecube(const cube &src, cube &dst) { - discardchildren(dst); - copycube(src, dst); + discardchildren(dst); + copycube(src, dst); } void blockcopy(const block3 &s, int rgrid, block3 *b) { - *b = s; - cube *q = b->c(); - loopxyz(s, rgrid, copycube(c, *q++)); + *b = s; + cube *q = b->c(); + loopxyz(s, rgrid, copycube(c, *q++)); } block3 *blockcopy(const block3 &s, int rgrid) { - int bsize = sizeof(block3)+sizeof(cube)*s.size(); - if(bsize <= 0 || bsize > (100<<20)) return NULL; - block3 *b = (block3 *)new (false) uchar[bsize]; - if(b) blockcopy(s, rgrid, b); - return b; + int bsize = sizeof(block3)+sizeof(cube)*s.size(); + if(bsize <= 0 || bsize > (100<<20)) return NULL; + block3 *b = (block3 *)new (false) uchar[bsize]; + if(b) blockcopy(s, rgrid, b); + return b; } void freeblock(block3 *b, bool alloced = true) { - cube *q = b->c(); - loopi(b->size()) discardchildren(*q++); - if(alloced) delete[] b; + cube *q = b->c(); + loopi(b->size()) discardchildren(*q++); + if(alloced) delete[] b; } -void selgridmap(selinfo &sel, uchar *g) // generates a map of the cube sizes at each grid point +void selgridmap(selinfo &sel, uchar *g) // generates a map of the cube sizes at each grid point { - loopxyz(sel, -sel.grid, (*g++ = bitscan(lusize), (void)c)); + loopxyz(sel, -sel.grid, (*g++ = bitscan(lusize), (void)c)); } void freeundo(undoblock *u) { - if(!u->numents) freeblock(u->block(), false); - delete[] (uchar *)u; + if(!u->numents) freeblock(u->block(), false); + delete[] (uchar *)u; } void pasteundoblock(block3 *b, uchar *g) { - cube *s = b->c(); - loopxyz(*b, 1<c(); + loopxyz(*b, 1<numents) pasteundoents(u); - else pasteundoblock(u->block(), u->gridmap()); + if(u->numents) pasteundoents(u); + else pasteundoblock(u->block(), u->gridmap()); } static inline int undosize(undoblock *u) { - if(u->numents) return u->numents*sizeof(undoent); - else - { - block3 *b = u->block(); - cube *q = b->c(); - int size = b->size(), total = size; - loopj(size) total += familysize(*q++)*sizeof(cube); - return total; - } + if(u->numents) return u->numents*sizeof(undoent); + else + { + block3 *b = u->block(); + cube *q = b->c(); + int size = b->size(), total = size; + loopj(size) total += familysize(*q++)*sizeof(cube); + return total; + } } struct undolist { - undoblock *first, *last; - - undolist() : first(NULL), last(NULL) {} - - bool empty() { return !first; } - - void add(undoblock *u) - { - u->next = NULL; - u->prev = last; - if(!first) first = last = u; - else - { - last->next = u; - last = u; - } - } - - undoblock *popfirst() - { - undoblock *u = first; - first = first->next; - if(first) first->prev = NULL; - else last = NULL; - return u; - } - - undoblock *poplast() - { - undoblock *u = last; - last = last->prev; - if(last) last->next = NULL; - else first = NULL; - return u; - } + undoblock *first, *last; + + undolist() : first(NULL), last(NULL) {} + + bool empty() { return !first; } + + void add(undoblock *u) + { + u->next = NULL; + u->prev = last; + if(!first) first = last = u; + else + { + last->next = u; + last = u; + } + } + + undoblock *popfirst() + { + undoblock *u = first; + first = first->next; + if(first) first->prev = NULL; + else last = NULL; + return u; + } + + undoblock *poplast() + { + undoblock *u = last; + last = last->prev; + if(last) last->next = NULL; + else first = NULL; + return u; + } }; undolist undos, redos; -VARP(undomegs, 0, 8, 100); // bounded by n megs +VARP(undomegs, 0, 8, 100); // bounded by n megs int totalundos = 0; -void pruneundos(int maxremain) // bound memory +void pruneundos(int maxremain) // bound memory { - while(totalundos > maxremain && !undos.empty()) - { - undoblock *u = undos.popfirst(); - totalundos -= u->size; - freeundo(u); - } - //conoutf(CON_DEBUG, "undo: %d of %d(%%%d)", totalundos, undomegs<<20, totalundos*100/(undomegs<<20)); - while(!redos.empty()) - { - undoblock *u = redos.popfirst(); - totalundos -= u->size; - freeundo(u); - } + while(totalundos > maxremain && !undos.empty()) + { + undoblock *u = undos.popfirst(); + totalundos -= u->size; + freeundo(u); + } + //conoutf(CON_DEBUG, "undo: %d of %d(%%%d)", totalundos, undomegs<<20, totalundos*100/(undomegs<<20)); + while(!redos.empty()) + { + undoblock *u = redos.popfirst(); + totalundos -= u->size; + freeundo(u); + } } void clearundos() { pruneundos(0); } @@ -760,106 +759,106 @@ COMMAND(clearundos, ""); undoblock *newundocube(selinfo &s) { - int ssize = s.size(), - selgridsize = ssize, - blocksize = sizeof(block3)+ssize*sizeof(cube); - if(blocksize <= 0 || blocksize > (undomegs<<20)) return NULL; - undoblock *u = (undoblock *)new (false) uchar[sizeof(undoblock) + blocksize + selgridsize]; - if(!u) return NULL; - u->numents = 0; - block3 *b = u->block(); - blockcopy(s, -s.grid, b); - uchar *g = u->gridmap(); - selgridmap(s, g); - return u; + int ssize = s.size(), + selgridsize = ssize, + blocksize = sizeof(block3)+ssize*sizeof(cube); + if(blocksize <= 0 || blocksize > (undomegs<<20)) return NULL; + undoblock *u = (undoblock *)new (false) uchar[sizeof(undoblock) + blocksize + selgridsize]; + if(!u) return NULL; + u->numents = 0; + block3 *b = u->block(); + blockcopy(s, -s.grid, b); + uchar *g = u->gridmap(); + selgridmap(s, g); + return u; } void addundo(undoblock *u) { - u->size = undosize(u); - u->timestamp = totalmillis; - undos.add(u); - totalundos += u->size; - pruneundos(undomegs<<20); + u->size = undosize(u); + u->timestamp = totalmillis; + undos.add(u); + totalundos += u->size; + pruneundos(undomegs<<20); } VARP(nompedit, 0, 1, 1); void makeundo(selinfo &s) { - undoblock *u = newundocube(s); - if(u) addundo(u); + undoblock *u = newundocube(s); + if(u) addundo(u); } -void makeundo() // stores state of selected cubes before editing +void makeundo() // stores state of selected cubes before editing { - if(lastsel==sel || sel.s.iszero()) return; - lastsel=sel; - makeundo(sel); + if(lastsel==sel || sel.s.iszero()) return; + lastsel=sel; + makeundo(sel); } static inline int countblock(cube *c, int n = 8) { - int r = 0; - loopi(n) if(c[i].children) r += countblock(c[i].children); else ++r; - return r; + int r = 0; + loopi(n) if(c[i].children) r += countblock(c[i].children); else ++r; + return r; } static int countblock(block3 *b) { return countblock(b->c(), b->size()); } void swapundo(undolist &a, undolist &b, int op) { - if(noedit()) return; - if(a.empty()) { conoutf(CON_WARN, "nothing more to %s", op == EDIT_REDO ? "redo" : "undo"); return; } - int ts = a.last->timestamp; - if(multiplayer(false)) - { - int n = 0, ops = 0; - for(undoblock *u = a.last; u && ts==u->timestamp; u = u->prev) - { - ++ops; - n += u->numents ? u->numents : countblock(u->block()); - if(ops > 10 || n > 2500) - { - conoutf(CON_WARN, "undo too big for multiplayer"); - if(nompedit) { multiplayer(); return; } - op = -1; - break; - } - } - } - selinfo l = sel; - while(!a.empty() && ts==a.last->timestamp) - { - if(op >= 0) game::edittrigger(sel, op); - undoblock *u = a.poplast(), *r; - if(u->numents) r = copyundoents(u); - else - { - block3 *ub = u->block(); - l.o = ub->o; - l.s = ub->s; - l.grid = ub->grid; - l.orient = ub->orient; - r = newundocube(l); - } - if(r) - { - r->size = u->size; - r->timestamp = totalmillis; - b.add(r); - } - pasteundo(u); - if(!u->numents) changed(*u->block(), false); - freeundo(u); - } - commitchanges(); - if(!hmapsel) - { - sel = l; - reorient(); - } - forcenextundo(); + if(noedit()) return; + if(a.empty()) { conoutf(CON_WARN, "nothing more to %s", op == EDIT_REDO ? "redo" : "undo"); return; } + int ts = a.last->timestamp; + if(multiplayer(false)) + { + int n = 0, ops = 0; + for(undoblock *u = a.last; u && ts==u->timestamp; u = u->prev) + { + ++ops; + n += u->numents ? u->numents : countblock(u->block()); + if(ops > 10 || n > 2500) + { + conoutf(CON_WARN, "undo too big for multiplayer"); + if(nompedit) { multiplayer(); return; } + op = -1; + break; + } + } + } + selinfo l = sel; + while(!a.empty() && ts==a.last->timestamp) + { + if(op >= 0) game::edittrigger(sel, op); + undoblock *u = a.poplast(), *r; + if(u->numents) r = copyundoents(u); + else + { + block3 *ub = u->block(); + l.o = ub->o; + l.s = ub->s; + l.grid = ub->grid; + l.orient = ub->orient; + r = newundocube(l); + } + if(r) + { + r->size = u->size; + r->timestamp = totalmillis; + b.add(r); + } + pasteundo(u); + if(!u->numents) changed(*u->block(), false); + freeundo(u); + } + commitchanges(); + if(!hmapsel) + { + sel = l; + reorient(); + } + forcenextundo(); } void editundo() { swapundo(undos, redos, EDIT_UNDO); } @@ -874,388 +873,388 @@ editinfo *localedit = NULL; template static void packcube(cube &c, B &buf) { - if(c.children) - { - buf.put(0xFF); - loopi(8) packcube(c.children[i], buf); - } - else - { - cube data = c; - lilswap(data.texture, 6); - buf.put(c.material&0xFF); - buf.put(c.material>>8); - buf.put(data.edges, sizeof(data.edges)); - buf.put((uchar *)data.texture, sizeof(data.texture)); - } + if(c.children) + { + buf.put(0xFF); + loopi(8) packcube(c.children[i], buf); + } + else + { + cube data = c; + lilswap(data.texture, 6); + buf.put(c.material&0xFF); + buf.put(c.material>>8); + buf.put(data.edges, sizeof(data.edges)); + buf.put((uchar *)data.texture, sizeof(data.texture)); + } } template static bool packblock(block3 &b, B &buf) { - if(b.size() <= 0 || b.size() > (1<<20)) return false; - block3 hdr = b; - lilswap(hdr.o.v, 3); - lilswap(hdr.s.v, 3); - lilswap(&hdr.grid, 1); - lilswap(&hdr.orient, 1); - buf.put((const uchar *)&hdr, sizeof(hdr)); - cube *c = b.c(); - loopi(b.size()) packcube(c[i], buf); - return true; + if(b.size() <= 0 || b.size() > (1<<20)) return false; + block3 hdr = b; + lilswap(hdr.o.v, 3); + lilswap(hdr.s.v, 3); + lilswap(&hdr.grid, 1); + lilswap(&hdr.orient, 1); + buf.put((const uchar *)&hdr, sizeof(hdr)); + cube *c = b.c(); + loopi(b.size()) packcube(c[i], buf); + return true; } struct vslothdr { - ushort index; - ushort slot; + ushort index; + ushort slot; }; static void packvslots(cube &c, vector &buf, vector &used) { - if(c.children) - { - loopi(8) packvslots(c.children[i], buf, used); - } - else loopi(6) - { - ushort index = c.texture[i]; - if(vslots.inrange(index) && vslots[index]->changed && used.find(index) < 0) - { - used.add(index); - VSlot &vs = *vslots[index]; - vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr)); - hdr.index = index; - hdr.slot = vs.slot->index; - lilswap(&hdr.index, 2); - packvslot(buf, vs); - } - } + if(c.children) + { + loopi(8) packvslots(c.children[i], buf, used); + } + else loopi(6) + { + ushort index = c.texture[i]; + if(vslots.inrange(index) && vslots[index]->changed && used.find(index) < 0) + { + used.add(index); + VSlot &vs = *vslots[index]; + vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr)); + hdr.index = index; + hdr.slot = vs.slot->index; + lilswap(&hdr.index, 2); + packvslot(buf, vs); + } + } } static void packvslots(block3 &b, vector &buf) { - vector used; - cube *c = b.c(); - loopi(b.size()) packvslots(c[i], buf, used); - memset(buf.pad(sizeof(vslothdr)), 0, sizeof(vslothdr)); + vector used; + cube *c = b.c(); + loopi(b.size()) packvslots(c[i], buf, used); + memset(buf.pad(sizeof(vslothdr)), 0, sizeof(vslothdr)); } template static void unpackcube(cube &c, B &buf) { - int mat = buf.get(); - if(mat == 0xFF) - { - c.children = newcubes(F_EMPTY); - loopi(8) unpackcube(c.children[i], buf); - } - else - { - c.material = mat | (buf.get()<<8); - buf.get(c.edges, sizeof(c.edges)); - buf.get((uchar *)c.texture, sizeof(c.texture)); - lilswap(c.texture, 6); - } + int mat = buf.get(); + if(mat == 0xFF) + { + c.children = newcubes(F_EMPTY); + loopi(8) unpackcube(c.children[i], buf); + } + else + { + c.material = mat | (buf.get()<<8); + buf.get(c.edges, sizeof(c.edges)); + buf.get((uchar *)c.texture, sizeof(c.texture)); + lilswap(c.texture, 6); + } } template static bool unpackblock(block3 *&b, B &buf) { - if(b) { freeblock(b); b = NULL; } - block3 hdr; - if(buf.get((uchar *)&hdr, sizeof(hdr)) < int(sizeof(hdr))) return false; - lilswap(hdr.o.v, 3); - lilswap(hdr.s.v, 3); - lilswap(&hdr.grid, 1); - lilswap(&hdr.orient, 1); - if(hdr.size() > (1<<20) || hdr.grid <= 0 || hdr.grid > (1<<12)) return false; - b = (block3 *)new (false) uchar[sizeof(block3)+hdr.size()*sizeof(cube)]; - if(!b) return false; - *b = hdr; - cube *c = b->c(); - memset(c, 0, b->size()*sizeof(cube)); - loopi(b->size()) unpackcube(c[i], buf); - return true; + if(b) { freeblock(b); b = NULL; } + block3 hdr; + if(buf.get((uchar *)&hdr, sizeof(hdr)) < int(sizeof(hdr))) return false; + lilswap(hdr.o.v, 3); + lilswap(hdr.s.v, 3); + lilswap(&hdr.grid, 1); + lilswap(&hdr.orient, 1); + if(hdr.size() > (1<<20) || hdr.grid <= 0 || hdr.grid > (1<<12)) return false; + b = (block3 *)new (false) uchar[sizeof(block3)+hdr.size()*sizeof(cube)]; + if(!b) return false; + *b = hdr; + cube *c = b->c(); + memset(c, 0, b->size()*sizeof(cube)); + loopi(b->size()) unpackcube(c[i], buf); + return true; } struct vslotmap { - int index; - VSlot *vslot; + int index; + VSlot *vslot; - vslotmap() {} - vslotmap(int index, VSlot *vslot) : index(index), vslot(vslot) {} + vslotmap() {} + vslotmap(int index, VSlot *vslot) : index(index), vslot(vslot) {} }; static vector unpackingvslots; static void unpackvslots(cube &c, ucharbuf &buf) { - if(c.children) - { - loopi(8) unpackvslots(c.children[i], buf); - } - else loopi(6) - { - ushort tex = c.texture[i]; - loopvj(unpackingvslots) if(unpackingvslots[j].index == tex) { c.texture[i] = unpackingvslots[j].vslot->index; break; } - } + if(c.children) + { + loopi(8) unpackvslots(c.children[i], buf); + } + else loopi(6) + { + ushort tex = c.texture[i]; + loopvj(unpackingvslots) if(unpackingvslots[j].index == tex) { c.texture[i] = unpackingvslots[j].vslot->index; break; } + } } static void unpackvslots(block3 &b, ucharbuf &buf) { - while(buf.remaining() >= int(sizeof(vslothdr))) - { - vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr)); - lilswap(&hdr.index, 2); - if(!hdr.index) break; - VSlot &vs = *lookupslot(hdr.slot, false).variants; - VSlot ds; - if(!unpackvslot(buf, ds, false)) break; - if(vs.index < 0 || vs.index == DEFAULT_SKY) continue; - VSlot *edit = editvslot(vs, ds); - unpackingvslots.add(vslotmap(hdr.index, edit ? edit : &vs)); - } + while(buf.remaining() >= int(sizeof(vslothdr))) + { + vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr)); + lilswap(&hdr.index, 2); + if(!hdr.index) break; + VSlot &vs = *lookupslot(hdr.slot, false).variants; + VSlot ds; + if(!unpackvslot(buf, ds, false)) break; + if(vs.index < 0 || vs.index == DEFAULT_SKY) continue; + VSlot *edit = editvslot(vs, ds); + unpackingvslots.add(vslotmap(hdr.index, edit ? edit : &vs)); + } - cube *c = b.c(); - loopi(b.size()) unpackvslots(c[i], buf); + cube *c = b.c(); + loopi(b.size()) unpackvslots(c[i], buf); - unpackingvslots.setsize(0); + unpackingvslots.setsize(0); } static bool compresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen) { - uLongf len = compressBound(inlen); - if(len > (1<<20)) return false; - outbuf = new (false) uchar[len]; - if(!outbuf || compress2((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen, Z_BEST_COMPRESSION) != Z_OK || len > (1<<16)) - { - delete[] outbuf; - outbuf = NULL; - return false; - } - outlen = len; - return true; + uLongf len = compressBound(inlen); + if(len > (1<<20)) return false; + outbuf = new (false) uchar[len]; + if(!outbuf || compress2((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen, Z_BEST_COMPRESSION) != Z_OK || len > (1<<16)) + { + delete[] outbuf; + outbuf = NULL; + return false; + } + outlen = len; + return true; } static bool uncompresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen) { - if(compressBound(outlen) > (1<<20)) return false; - uLongf len = outlen; - outbuf = new (false) uchar[len]; - if(!outbuf || uncompress((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen) != Z_OK) - { - delete[] outbuf; - outbuf = NULL; - return false; - } - outlen = len; - return true; + if(compressBound(outlen) > (1<<20)) return false; + uLongf len = outlen; + outbuf = new (false) uchar[len]; + if(!outbuf || uncompress((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen) != Z_OK) + { + delete[] outbuf; + outbuf = NULL; + return false; + } + outlen = len; + return true; } bool packeditinfo(editinfo *e, int &inlen, uchar *&outbuf, int &outlen) { - vector buf; - if(!e || !e->copy || !packblock(*e->copy, buf)) return false; - packvslots(*e->copy, buf); - inlen = buf.length(); - return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen); + vector buf; + if(!e || !e->copy || !packblock(*e->copy, buf)) return false; + packvslots(*e->copy, buf); + inlen = buf.length(); + return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen); } bool unpackeditinfo(editinfo *&e, const uchar *inbuf, int inlen, int outlen) { - if(e && e->copy) { freeblock(e->copy); e->copy = NULL; } - uchar *outbuf = NULL; - if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false; - ucharbuf buf(outbuf, outlen); - if(!e) e = editinfos.add(new editinfo); - if(!unpackblock(e->copy, buf)) - { - delete[] outbuf; - return false; - } - unpackvslots(*e->copy, buf); - delete[] outbuf; - return true; + if(e && e->copy) { freeblock(e->copy); e->copy = NULL; } + uchar *outbuf = NULL; + if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false; + ucharbuf buf(outbuf, outlen); + if(!e) e = editinfos.add(new editinfo); + if(!unpackblock(e->copy, buf)) + { + delete[] outbuf; + return false; + } + unpackvslots(*e->copy, buf); + delete[] outbuf; + return true; } void freeeditinfo(editinfo *&e) { - if(!e) return; - editinfos.removeobj(e); - if(e->copy) freeblock(e->copy); - delete e; - e = NULL; + if(!e) return; + editinfos.removeobj(e); + if(e->copy) freeblock(e->copy); + delete e; + e = NULL; } bool packundo(undoblock *u, int &inlen, uchar *&outbuf, int &outlen) { - vector buf; - buf.reserve(512); - *(ushort *)buf.pad(2) = lilswap(ushort(u->numents)); - if(u->numents) - { - undoent *ue = u->ents(); - loopi(u->numents) - { - *(ushort *)buf.pad(2) = lilswap(ushort(ue[i].i)); - entity &e = *(entity *)buf.pad(sizeof(entity)); - e = ue[i].e; - lilswap(&e.o.x, 3); - lilswap(&e.attr1, 5); - } - } - else - { - block3 &b = *u->block(); - if(!packblock(b, buf)) return false; - buf.put(u->gridmap(), b.size()); - packvslots(b, buf); - } - inlen = buf.length(); - return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen); + vector buf; + buf.reserve(512); + *(ushort *)buf.pad(2) = lilswap(ushort(u->numents)); + if(u->numents) + { + undoent *ue = u->ents(); + loopi(u->numents) + { + *(ushort *)buf.pad(2) = lilswap(ushort(ue[i].i)); + entity &e = *(entity *)buf.pad(sizeof(entity)); + e = ue[i].e; + lilswap(&e.o.x, 3); + lilswap(&e.attr1, 5); + } + } + else + { + block3 &b = *u->block(); + if(!packblock(b, buf)) return false; + buf.put(u->gridmap(), b.size()); + packvslots(b, buf); + } + inlen = buf.length(); + return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen); } bool unpackundo(const uchar *inbuf, int inlen, int outlen) { - uchar *outbuf = NULL; - if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false; - ucharbuf buf(outbuf, outlen); - if(buf.remaining() < 2) - { - delete[] outbuf; - return false; - } - int numents = lilswap(*(const ushort *)buf.pad(2)); - if(numents) - { - if(buf.remaining() < numents*int(2 + sizeof(entity))) - { - delete[] outbuf; - return false; - } - loopi(numents) - { - int idx = lilswap(*(const ushort *)buf.pad(2)); - entity &e = *(entity *)buf.pad(sizeof(entity)); - lilswap(&e.o.x, 3); - lilswap(&e.attr1, 5); - pasteundoent(idx, e); - } - } - else - { - block3 *b = NULL; - if(!unpackblock(b, buf) || b->grid >= worldsize || buf.remaining() < b->size()) - { - freeblock(b); - delete[] outbuf; - return false; - } - uchar *g = buf.pad(b->size()); - unpackvslots(*b, buf); - pasteundoblock(b, g); - changed(*b, false); - freeblock(b); - } - delete[] outbuf; - commitchanges(); - return true; + uchar *outbuf = NULL; + if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false; + ucharbuf buf(outbuf, outlen); + if(buf.remaining() < 2) + { + delete[] outbuf; + return false; + } + int numents = lilswap(*(const ushort *)buf.pad(2)); + if(numents) + { + if(buf.remaining() < numents*int(2 + sizeof(entity))) + { + delete[] outbuf; + return false; + } + loopi(numents) + { + int idx = lilswap(*(const ushort *)buf.pad(2)); + entity &e = *(entity *)buf.pad(sizeof(entity)); + lilswap(&e.o.x, 3); + lilswap(&e.attr1, 5); + pasteundoent(idx, e); + } + } + else + { + block3 *b = NULL; + if(!unpackblock(b, buf) || b->grid >= worldsize || buf.remaining() < b->size()) + { + freeblock(b); + delete[] outbuf; + return false; + } + uchar *g = buf.pad(b->size()); + unpackvslots(*b, buf); + pasteundoblock(b, g); + changed(*b, false); + freeblock(b); + } + delete[] outbuf; + commitchanges(); + return true; } bool packundo(int op, int &inlen, uchar *&outbuf, int &outlen) { - switch(op) - { - case EDIT_UNDO: return !undos.empty() && packundo(undos.last, inlen, outbuf, outlen); - case EDIT_REDO: return !redos.empty() && packundo(redos.last, inlen, outbuf, outlen); - default: return false; - } + switch(op) + { + case EDIT_UNDO: return !undos.empty() && packundo(undos.last, inlen, outbuf, outlen); + case EDIT_REDO: return !redos.empty() && packundo(redos.last, inlen, outbuf, outlen); + default: return false; + } } struct prefabheader { - char magic[4]; - int version; + char magic[4]; + int version; }; struct prefab : editinfo { - char *name; - GLuint ebo, vbo; - int numtris, numverts; + char *name; + GLuint ebo, vbo; + int numtris, numverts; - prefab() : name(NULL), ebo(0), vbo(0), numtris(0), numverts(0) {} - ~prefab() { DELETEA(name); if(copy) freeblock(copy); } + prefab() : name(NULL), ebo(0), vbo(0), numtris(0), numverts(0) {} + ~prefab() { DELETEA(name); if(copy) freeblock(copy); } - void cleanup() - { - if(ebo) { glDeleteBuffers_(1, &ebo); ebo = 0; } - if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } - numtris = numverts = 0; - } + void cleanup() + { + if(ebo) { glDeleteBuffers_(1, &ebo); ebo = 0; } + if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } + numtris = numverts = 0; + } }; static hashnameset prefabs; void cleanupprefabs() { - enumerate(prefabs, prefab, p, p.cleanup()); + enumerate(prefabs, prefab, p, p.cleanup()); } void delprefab(char *name) { - prefab *p = prefabs.access(name); - if(p) - { - p->cleanup(); - prefabs.remove(name); - conoutf("deleted prefab %s", name); - } + prefab *p = prefabs.access(name); + if(p) + { + p->cleanup(); + prefabs.remove(name); + conoutf("deleted prefab %s", name); + } } COMMAND(delprefab, "s"); void saveprefab(char *name) { - if(!name[0] || noedit(true) || (nompedit && multiplayer())) return; - prefab *b = prefabs.access(name); - if(!b) - { - b = &prefabs[name]; - b->name = newstring(name); - } - if(b->copy) freeblock(b->copy); - protectsel(b->copy = blockcopy(block3(sel), sel.grid)); - changed(sel); - defformatstring(filename, strpbrk(name, "/\\") ? "packages/%s.obr" : "packages/prefab/%s.obr", name); - path(filename); - stream *f = opengzfile(filename, "wb"); - if(!f) { conoutf(CON_ERROR, "could not write prefab to %s", filename); return; } - prefabheader hdr; - memcpy(hdr.magic, "OEBR", 4); - hdr.version = 0; - lilswap(&hdr.version, 1); - f->write(&hdr, sizeof(hdr)); - streambuf s(f); - if(!packblock(*b->copy, s)) { delete f; conoutf(CON_ERROR, "could not pack prefab %s", filename); return; } - delete f; - conoutf("wrote prefab file %s", filename); + if(!name[0] || noedit(true) || (nompedit && multiplayer())) return; + prefab *b = prefabs.access(name); + if(!b) + { + b = &prefabs[name]; + b->name = newstring(name); + } + if(b->copy) freeblock(b->copy); + protectsel(b->copy = blockcopy(block3(sel), sel.grid)); + changed(sel); + defformatstring(filename, strpbrk(name, "/\\") ? "packages/%s.obr" : "packages/prefab/%s.obr", name); + path(filename); + stream *f = opengzfile(filename, "wb"); + if(!f) { conoutf(CON_ERROR, "could not write prefab to %s", filename); return; } + prefabheader hdr; + memcpy(hdr.magic, "OEBR", 4); + hdr.version = 0; + lilswap(&hdr.version, 1); + f->write(&hdr, sizeof(hdr)); + streambuf s(f); + if(!packblock(*b->copy, s)) { delete f; conoutf(CON_ERROR, "could not pack prefab %s", filename); return; } + delete f; + conoutf("wrote prefab file %s", filename); } COMMAND(saveprefab, "s"); void pasteblock(block3 &b, selinfo &sel, bool local) { - sel.s = b.s; - int o = sel.orient; - sel.orient = b.orient; - cube *s = b.c(); - loopselxyz(if(!isempty(*s) || s->children || s->material != MAT_AIR) pastecube(*s, c); s++); // 'transparent'. old opaque by 'delcube; paste' - sel.orient = o; + sel.s = b.s; + int o = sel.orient; + sel.orient = b.orient; + cube *s = b.c(); + loopselxyz(if(!isempty(*s) || s->children || s->material != MAT_AIR) pastecube(*s, c); s++); // 'transparent'. old opaque by 'delcube; paste' + sel.orient = o; } bool prefabloaded(const char *name) { - return prefabs.access(name) != NULL; + return prefabs.access(name) != NULL; } prefab *loadprefab(const char *name, bool msg = true) @@ -1285,252 +1284,252 @@ prefab *loadprefab(const char *name, bool msg = true) void pasteprefab(char *name) { - if(!name[0] || noedit() || (nompedit && multiplayer())) return; - prefab *b = loadprefab(name, true); - if(b) pasteblock(*b->copy, sel, true); + if(!name[0] || noedit() || (nompedit && multiplayer())) return; + prefab *b = loadprefab(name, true); + if(b) pasteblock(*b->copy, sel, true); } COMMAND(pasteprefab, "s"); struct prefabmesh { - struct vertex { vec pos; bvec4 norm; }; - - static const int SIZE = 1<<9; - int table[SIZE]; - vector verts; - vector chain; - vector tris; - - prefabmesh() { memset(table, -1, sizeof(table)); } - - int addvert(const vertex &v) - { - uint h = hthash(v.pos)&(SIZE-1); - for(int i = table[h]; i>=0; i = chain[i]) - { - const vertex &c = verts[i]; - if(c.pos==v.pos && c.norm==v.norm) return i; - } - if(verts.length() >= USHRT_MAX) return -1; - verts.add(v); - chain.add(table[h]); - return table[h] = verts.length()-1; - } - - int addvert(const vec &pos, const bvec &norm) - { - vertex vtx; - vtx.pos = pos; - vtx.norm = norm; - return addvert(vtx); + struct vertex { vec pos; bvec4 norm; }; + + static const int SIZE = 1<<9; + int table[SIZE]; + vector verts; + vector chain; + vector tris; + + prefabmesh() { memset(table, -1, sizeof(table)); } + + int addvert(const vertex &v) + { + uint h = hthash(v.pos)&(SIZE-1); + for(int i = table[h]; i>=0; i = chain[i]) + { + const vertex &c = verts[i]; + if(c.pos==v.pos && c.norm==v.norm) return i; + } + if(verts.length() >= USHRT_MAX) return -1; + verts.add(v); + chain.add(table[h]); + return table[h] = verts.length()-1; + } + + int addvert(const vec &pos, const bvec &norm) + { + vertex vtx; + vtx.pos = pos; + vtx.norm = norm; + return addvert(vtx); } - void setup(prefab &p) - { - if(tris.empty()) return; + void setup(prefab &p) + { + if(tris.empty()) return; - p.cleanup(); + p.cleanup(); - loopv(verts) verts[i].norm.flip(); - if(!p.vbo) glGenBuffers_(1, &p.vbo); - gle::bindvbo(p.vbo); - glBufferData_(GL_ARRAY_BUFFER, verts.length()*sizeof(vertex), verts.getbuf(), GL_STATIC_DRAW); - gle::clearvbo(); - p.numverts = verts.length(); + loopv(verts) verts[i].norm.flip(); + if(!p.vbo) glGenBuffers_(1, &p.vbo); + gle::bindvbo(p.vbo); + glBufferData_(GL_ARRAY_BUFFER, verts.length()*sizeof(vertex), verts.getbuf(), GL_STATIC_DRAW); + gle::clearvbo(); + p.numverts = verts.length(); - if(!p.ebo) glGenBuffers_(1, &p.ebo); - gle::bindebo(p.ebo); - glBufferData_(GL_ELEMENT_ARRAY_BUFFER, tris.length()*sizeof(ushort), tris.getbuf(), GL_STATIC_DRAW); - gle::clearebo(); - p.numtris = tris.length()/3; - } + if(!p.ebo) glGenBuffers_(1, &p.ebo); + gle::bindebo(p.ebo); + glBufferData_(GL_ELEMENT_ARRAY_BUFFER, tris.length()*sizeof(ushort), tris.getbuf(), GL_STATIC_DRAW); + gle::clearebo(); + p.numtris = tris.length()/3; + } }; static void genprefabmesh(prefabmesh &r, cube &c, const ivec &co, int size) { - if(c.children) - { - neighbourstack[++neighbourdepth] = c.children; - loopi(8) - { - ivec o(i, co, size/2); - genprefabmesh(r, c.children[i], o, size/2); - } - --neighbourdepth; - } - else if(!isempty(c)) - { - int vis; - loopi(6) if((vis = visibletris(c, i, co, size))) - { - ivec v[4]; - genfaceverts(c, i, v); - int convex = 0; - if(!flataxisface(c, i)) convex = faceconvexity(v); - int order = vis&4 || convex < 0 ? 1 : 0, numverts = 0; - vec vo(co), pos[4], norm[4]; - pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo); - if(vis&1) pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo); - pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo); - if(vis&2) pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo); - guessnormals(pos, numverts, norm); - int index[4]; - loopj(numverts) index[j] = r.addvert(pos[j], bvec(norm[j])); - loopj(numverts-2) if(index[0]!=index[j+1] && index[j+1]!=index[j+2] && index[j+2]!=index[0]) - { - r.tris.add(index[0]); - r.tris.add(index[j+1]); - r.tris.add(index[j+2]); - } - } - } + if(c.children) + { + neighbourstack[++neighbourdepth] = c.children; + loopi(8) + { + ivec o(i, co, size/2); + genprefabmesh(r, c.children[i], o, size/2); + } + --neighbourdepth; + } + else if(!isempty(c)) + { + int vis; + loopi(6) if((vis = visibletris(c, i, co, size))) + { + ivec v[4]; + genfaceverts(c, i, v); + int convex = 0; + if(!flataxisface(c, i)) convex = faceconvexity(v); + int order = vis&4 || convex < 0 ? 1 : 0, numverts = 0; + vec vo(co), pos[4], norm[4]; + pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo); + if(vis&1) pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo); + pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo); + if(vis&2) pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo); + guessnormals(pos, numverts, norm); + int index[4]; + loopj(numverts) index[j] = r.addvert(pos[j], bvec(norm[j])); + loopj(numverts-2) if(index[0]!=index[j+1] && index[j+1]!=index[j+2] && index[j+2]!=index[0]) + { + r.tris.add(index[0]); + r.tris.add(index[j+1]); + r.tris.add(index[j+2]); + } + } + } } void genprefabmesh(prefab &p) { - block3 b = *p.copy; - b.o = ivec(0, 0, 0); + block3 b = *p.copy; + b.o = ivec(0, 0, 0); - cube *oldworldroot = worldroot; - int oldworldscale = worldscale, oldworldsize = worldsize; + cube *oldworldroot = worldroot; + int oldworldscale = worldscale, oldworldsize = worldsize; - worldroot = newcubes(); - worldscale = 1; - worldsize = 2; - while(worldsize < max(max(b.s.x, b.s.y), b.s.z)*b.grid) - { - worldscale++; - worldsize *= 2; - } + worldroot = newcubes(); + worldscale = 1; + worldsize = 2; + while(worldsize < max(max(b.s.x, b.s.y), b.s.z)*b.grid) + { + worldscale++; + worldsize *= 2; + } - cube *s = p.copy->c(); - loopxyz(b, b.grid, if(!isempty(*s) || s->children) pastecube(*s, c); s++); + cube *s = p.copy->c(); + loopxyz(b, b.grid, if(!isempty(*s) || s->children) pastecube(*s, c); s++); - prefabmesh r; - neighbourstack[++neighbourdepth] = worldroot; - loopi(8) genprefabmesh(r, worldroot[i], ivec(i, ivec(0, 0, 0), worldsize/2), worldsize/2); - --neighbourdepth; - r.setup(p); + prefabmesh r; + neighbourstack[++neighbourdepth] = worldroot; + loopi(8) genprefabmesh(r, worldroot[i], ivec(i, ivec(0, 0, 0), worldsize/2), worldsize/2); + --neighbourdepth; + r.setup(p); - freeocta(worldroot); + freeocta(worldroot); - worldroot = oldworldroot; - worldscale = oldworldscale; - worldsize = oldworldsize; + worldroot = oldworldroot; + worldscale = oldworldscale; + worldsize = oldworldsize; - useshaderbyname("prefab"); + useshaderbyname("prefab"); } extern int outlinecolour; static void renderprefab(prefab &p, const vec &o, float yaw, float pitch, float roll, float size, const vec &color) { - if(!p.numtris) - { - genprefabmesh(p); - if(!p.numtris) return; - } - - block3 &b = *p.copy; - - matrix4 m; - m.identity(); - m.settranslation(o); - if(yaw) m.rotate_around_z(yaw*RAD); - if(pitch) m.rotate_around_x(pitch*RAD); - if(roll) m.rotate_around_y(-roll*RAD); - matrix3 w(m); - if(size > 0 && size != 1) m.scale(size); - m.translate(vec(b.s).mul(-b.grid*0.5f)); - - gle::bindvbo(p.vbo); - gle::bindebo(p.ebo); - gle::enablevertex(); - gle::enablenormal(); - prefabmesh::vertex *v = (prefabmesh::vertex *)0; - gle::vertexpointer(sizeof(prefabmesh::vertex), v->pos.v); - gle::normalpointer(sizeof(prefabmesh::vertex), v->norm.v, GL_BYTE); - - matrix4 pm; - pm.mul(camprojmatrix, m); - GLOBALPARAM(prefabmatrix, pm); - GLOBALPARAM(prefabworld, w); - SETSHADER(prefab); - gle::color(color); - glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0); - - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - enablepolygonoffset(GL_POLYGON_OFFSET_LINE); - - pm.mul(camprojmatrix, m); - GLOBALPARAM(prefabmatrix, pm); - SETSHADER(prefab); - gle::color(vec::hexcolor(outlinecolour)); - glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0); - - disablepolygonoffset(GL_POLYGON_OFFSET_LINE); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - gle::disablevertex(); - gle::disablenormal(); - gle::clearebo(); - gle::clearvbo(); + if(!p.numtris) + { + genprefabmesh(p); + if(!p.numtris) return; + } + + block3 &b = *p.copy; + + matrix4 m; + m.identity(); + m.settranslation(o); + if(yaw) m.rotate_around_z(yaw*RAD); + if(pitch) m.rotate_around_x(pitch*RAD); + if(roll) m.rotate_around_y(-roll*RAD); + matrix3 w(m); + if(size > 0 && size != 1) m.scale(size); + m.translate(vec(b.s).mul(-b.grid*0.5f)); + + gle::bindvbo(p.vbo); + gle::bindebo(p.ebo); + gle::enablevertex(); + gle::enablenormal(); + prefabmesh::vertex *v = (prefabmesh::vertex *)0; + gle::vertexpointer(sizeof(prefabmesh::vertex), v->pos.v); + gle::normalpointer(sizeof(prefabmesh::vertex), v->norm.v, GL_BYTE); + + matrix4 pm; + pm.mul(camprojmatrix, m); + GLOBALPARAM(prefabmatrix, pm); + GLOBALPARAM(prefabworld, w); + SETSHADER(prefab); + gle::color(color); + glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0); + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + enablepolygonoffset(GL_POLYGON_OFFSET_LINE); + + pm.mul(camprojmatrix, m); + GLOBALPARAM(prefabmatrix, pm); + SETSHADER(prefab); + gle::color(vec::hexcolor(outlinecolour)); + glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0); + + disablepolygonoffset(GL_POLYGON_OFFSET_LINE); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + gle::disablevertex(); + gle::disablenormal(); + gle::clearebo(); + gle::clearvbo(); } void renderprefab(const char *name, const vec &o, float yaw, float pitch, float roll, float size, const vec &color) { - prefab *p = loadprefab(name, false); - if(p) renderprefab(*p, o, yaw, pitch, roll, size, color); + prefab *p = loadprefab(name, false); + if(p) renderprefab(*p, o, yaw, pitch, roll, size, color); } void previewprefab(const char *name, const vec &color) { - prefab *p = loadprefab(name, false); - if(p) - { - block3 &b = *p->copy; - float yaw; - vec o = calcmodelpreviewpos(vec(b.s).mul(b.grid*0.5f), yaw); - renderprefab(*p, o, yaw, 0, 0, 1, color); - } + prefab *p = loadprefab(name, false); + if(p) + { + block3 &b = *p->copy; + float yaw; + vec o = calcmodelpreviewpos(vec(b.s).mul(b.grid*0.5f), yaw); + renderprefab(*p, o, yaw, 0, 0, 1, color); + } } void mpcopy(editinfo *&e, selinfo &sel, bool local) { - if(local) game::edittrigger(sel, EDIT_COPY); - if(e==NULL) e = editinfos.add(new editinfo); - if(e->copy) freeblock(e->copy); - e->copy = NULL; - protectsel(e->copy = blockcopy(block3(sel), sel.grid)); - changed(sel); + if(local) game::edittrigger(sel, EDIT_COPY); + if(e==NULL) e = editinfos.add(new editinfo); + if(e->copy) freeblock(e->copy); + e->copy = NULL; + protectsel(e->copy = blockcopy(block3(sel), sel.grid)); + changed(sel); } void mppaste(editinfo *&e, selinfo &sel, bool local) { - if(e==NULL) return; - if(local) game::edittrigger(sel, EDIT_PASTE); - if(e->copy) pasteblock(*e->copy, sel, local); + if(e==NULL) return; + if(local) game::edittrigger(sel, EDIT_PASTE); + if(e->copy) pasteblock(*e->copy, sel, local); } void copy() { - if(noedit(true)) return; - mpcopy(localedit, sel, true); + if(noedit(true)) return; + mpcopy(localedit, sel, true); } void pastehilite() { - if(!localedit) return; + if(!localedit) return; sel.s = localedit->copy->s; - reorient(); - havesel = true; + reorient(); + havesel = true; } void paste() { - if(noedit(true)) return; - mppaste(localedit, sel, true); + if(noedit(true)) return; + mppaste(localedit, sel, true); } COMMAND(copy, ""); @@ -1542,31 +1541,31 @@ COMMANDN(redo, editredo, ""); static vector editingvslots; struct vslotref { - vslotref(int &index) { editingvslots.add(&index); } - ~vslotref() { editingvslots.pop(); } + vslotref(int &index) { editingvslots.add(&index); } + ~vslotref() { editingvslots.pop(); } }; #define editingvslot(...) vslotref vslotrefs[] = { __VA_ARGS__ }; (void)vslotrefs; void compacteditvslots() { - loopv(editingvslots) if(*editingvslots[i]) compactvslot(*editingvslots[i]); - loopv(unpackingvslots) compactvslot(*unpackingvslots[i].vslot); - loopv(editinfos) - { - editinfo *e = editinfos[i]; - compactvslots(e->copy->c(), e->copy->size()); - } - for(undoblock *u = undos.first; u; u = u->next) - if(!u->numents) - compactvslots(u->block()->c(), u->block()->size()); - for(undoblock *u = redos.first; u; u = u->next) - if(!u->numents) - compactvslots(u->block()->c(), u->block()->size()); + loopv(editingvslots) if(*editingvslots[i]) compactvslot(*editingvslots[i]); + loopv(unpackingvslots) compactvslot(*unpackingvslots[i].vslot); + loopv(editinfos) + { + editinfo *e = editinfos[i]; + compactvslots(e->copy->c(), e->copy->size()); + } + for(undoblock *u = undos.first; u; u = u->next) + if(!u->numents) + compactvslots(u->block()->c(), u->block()->size()); + for(undoblock *u = redos.first; u; u = u->next) + if(!u->numents) + compactvslots(u->block()->c(), u->block()->size()); } ///////////// height maps //////////////// -#define MAXBRUSH 64 +#define MAXBRUSH 64 #define MAXBRUSHC 63 #define MAXBRUSH2 32 int brush[MAXBRUSH][MAXBRUSH]; @@ -1578,23 +1577,23 @@ int brushmaxy = 0, brushminy = MAXBRUSH; void clearbrush() { - memset(brush, 0, sizeof brush); - brushmaxx = brushmaxy = 0; - brushminx = brushminy = MAXBRUSH; - paintbrush = false; + memset(brush, 0, sizeof brush); + brushmaxx = brushmaxy = 0; + brushminx = brushminy = MAXBRUSH; + paintbrush = false; } void brushvert(int *x, int *y, int *v) { - *x += MAXBRUSH2 - brushx + 1; // +1 for automatic padding - *y += MAXBRUSH2 - brushy + 1; - if(*x<0 || *y<0 || *x>=MAXBRUSH || *y>=MAXBRUSH) return; - brush[*x][*y] = clamp(*v, 0, 8); - paintbrush = paintbrush || (brush[*x][*y] > 0); - brushmaxx = min(MAXBRUSH-1, max(brushmaxx, *x+1)); - brushmaxy = min(MAXBRUSH-1, max(brushmaxy, *y+1)); - brushminx = max(0, min(brushminx, *x-1)); - brushminy = max(0, min(brushminy, *y-1)); + *x += MAXBRUSH2 - brushx + 1; // +1 for automatic padding + *y += MAXBRUSH2 - brushy + 1; + if(*x<0 || *y<0 || *x>=MAXBRUSH || *y>=MAXBRUSH) return; + brush[*x][*y] = clamp(*v, 0, 8); + paintbrush = paintbrush || (brush[*x][*y] > 0); + brushmaxx = min(MAXBRUSH-1, max(brushmaxx, *x+1)); + brushmaxy = min(MAXBRUSH-1, max(brushmaxy, *y+1)); + brushminx = max(0, min(brushminx, *x-1)); + brushminy = max(0, min(brushminy, *y-1)); } vector htextures; @@ -1604,317 +1603,317 @@ COMMAND(brushvert, "iii"); void hmapcancel() { htextures.setsize(0); } COMMAND(hmapcancel, ""); ICOMMAND(hmapselect, "", (), - int t = lookupcube(cur).texture[orient]; - int i = htextures.find(t); - if(i<0) - htextures.add(t); - else - htextures.remove(i); + int t = lookupcube(cur).texture[orient]; + int i = htextures.find(t); + if(i<0) + htextures.add(t); + else + htextures.remove(i); ); inline bool isheightmap(int o, int d, bool empty, cube *c) { - return havesel || - (empty && isempty(*c)) || - htextures.empty() || - htextures.find(c->texture[o]) >= 0; + return havesel || + (empty && isempty(*c)) || + htextures.empty() || + htextures.find(c->texture[o]) >= 0; } namespace hmap { -# define PAINTED 1 -# define NOTHMAP 2 -# define MAPPED 16 - uchar flags[MAXBRUSH][MAXBRUSH]; - cube *cmap[MAXBRUSHC][MAXBRUSHC][4]; - int mapz[MAXBRUSHC][MAXBRUSHC]; - int map [MAXBRUSH][MAXBRUSH]; - - selinfo changes; - bool selecting; - int d, dc, dr, dcr, biasup, br, hws, fg; - int gx, gy, gz, mx, my, mz, nx, ny, nz, bmx, bmy, bnx, bny; - uint fs; - selinfo hundo; - - cube *getcube(ivec t, int f) - { - t[d] += dcr*f*gridsize; - if(t[d] > nz || t[d] < mz) return NULL; - cube *c = &lookupcube(t, gridsize); - if(c->children) forcemip(*c, false); - discardchildren(*c, true); - if(!isheightmap(sel.orient, d, true, c)) return NULL; - if (t.x < changes.o.x) changes.o.x = t.x; - else if(t.x > changes.s.x) changes.s.x = t.x; - if (t.y < changes.o.y) changes.o.y = t.y; - else if(t.y > changes.s.y) changes.s.y = t.y; - if (t.z < changes.o.z) changes.o.z = t.z; - else if(t.z > changes.s.z) changes.s.z = t.z; - return c; - } - - uint getface(cube *c, int d) - { - return 0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs); - } - - void pushside(cube &c, int d, int x, int y, int z) - { - ivec a; - getcubevector(c, d, x, y, z, a); - a[R[d]] = 8 - a[R[d]]; - setcubevector(c, d, x, y, z, a); - } - - void addpoint(int x, int y, int z, int v) - { - if(!(flags[x][y] & MAPPED)) - map[x][y] = v + (z*8); - flags[x][y] |= MAPPED; - } - - void select(int x, int y, int z) - { - if((NOTHMAP & flags[x][y]) || (PAINTED & flags[x][y])) return; - ivec t(d, x+gx, y+gy, dc ? z : hws-z); - t.shl(gridpower); - - // selections may damage; must makeundo before - hundo.o = t; - hundo.o[D[d]] -= dcr*gridsize*2; - makeundo(hundo); - - cube **c = cmap[x][y]; - loopk(4) c[k] = NULL; - c[1] = getcube(t, 0); - if(!c[1] || !isempty(*c[1])) - { // try up - c[2] = c[1]; - c[1] = getcube(t, 1); - if(!c[1] || isempty(*c[1])) { c[0] = c[1]; c[1] = c[2]; c[2] = NULL; } - else { z++; t[d]+=fg; } - } - else // drop down - { - z--; - t[d]-= fg; - c[0] = c[1]; - c[1] = getcube(t, 0); - } - - if(!c[1] || isempty(*c[1])) { flags[x][y] |= NOTHMAP; return; } - - flags[x][y] |= PAINTED; - mapz [x][y] = z; - - if(!c[0]) c[0] = getcube(t, 1); - if(!c[2]) c[2] = getcube(t, -1); - c[3] = getcube(t, -2); - c[2] = !c[2] || isempty(*c[2]) ? NULL : c[2]; - c[3] = !c[3] || isempty(*c[3]) ? NULL : c[3]; - - uint face = getface(c[1], d); - if(face == 0x08080808 && (!c[0] || !isempty(*c[0]))) { flags[x][y] |= NOTHMAP; return; } - if(c[1]->faces[R[d]] == F_SOLID) // was single - face += 0x08080808; - else // was pair - face += c[2] ? getface(c[2], d) : 0x08080808; - face += 0x08080808; // c[3] - uchar *f = (uchar*)&face; - addpoint(x, y, z, f[0]); - addpoint(x+1, y, z, f[1]); - addpoint(x, y+1, z, f[2]); - addpoint(x+1, y+1, z, f[3]); - - if(selecting) // continue to adjacent cubes - { - if(x>bmx) select(x-1, y, z); - if(xbmy) select(x, y-1, z); - if(y, <, 1, 0, -); - else - pullhmap(worldsize*8, <, >, 0, 8, +); - - cube **c = cmap[x][y]; - int e[2][2]; - int notempty = 0; - - loopk(4) if(c[k]) { - loopi(2) loopj(2) { - e[i][j] = min(8, map[x+i][y+j] - (mapz[x][y]+3-k)*8); - notempty |= e[i][j] > 0; - } - if(notempty) - { - c[k]->texture[sel.orient] = c[1]->texture[sel.orient]; - solidfaces(*c[k]); - loopi(2) loopj(2) - { - int f = e[i][j]; - if(f<0 || (f==0 && e[1-i][j]==0 && e[i][1-j]==0)) - { - f=0; - pushside(*c[k], d, i, j, 0); - pushside(*c[k], d, i, j, 1); - } - edgeset(cubeedge(*c[k], d, i, j), dc, dc ? f : 8-f); - } - } - else - emptyfaces(*c[k]); - } - - if(!changed) return; - if(x>mx) ripple(x-1, y, mapz[x][y], true); - if(xmy) ripple(x, y-1, mapz[x][y], true); - if(y nz || t[d] < mz) return NULL; + cube *c = &lookupcube(t, gridsize); + if(c->children) forcemip(*c, false); + discardchildren(*c, true); + if(!isheightmap(sel.orient, d, true, c)) return NULL; + if (t.x < changes.o.x) changes.o.x = t.x; + else if(t.x > changes.s.x) changes.s.x = t.x; + if (t.y < changes.o.y) changes.o.y = t.y; + else if(t.y > changes.s.y) changes.s.y = t.y; + if (t.z < changes.o.z) changes.o.z = t.z; + else if(t.z > changes.s.z) changes.s.z = t.z; + return c; + } + + uint getface(cube *c, int d) + { + return 0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs); + } + + void pushside(cube &c, int d, int x, int y, int z) + { + ivec a; + getcubevector(c, d, x, y, z, a); + a[R[d]] = 8 - a[R[d]]; + setcubevector(c, d, x, y, z, a); + } + + void addpoint(int x, int y, int z, int v) + { + if(!(flags[x][y] & MAPPED)) + map[x][y] = v + (z*8); + flags[x][y] |= MAPPED; + } + + void select(int x, int y, int z) + { + if((NOTHMAP & flags[x][y]) || (PAINTED & flags[x][y])) return; + ivec t(d, x+gx, y+gy, dc ? z : hws-z); + t.shl(gridpower); + + // selections may damage; must makeundo before + hundo.o = t; + hundo.o[D[d]] -= dcr*gridsize*2; + makeundo(hundo); + + cube **c = cmap[x][y]; + loopk(4) c[k] = NULL; + c[1] = getcube(t, 0); + if(!c[1] || !isempty(*c[1])) + { // try up + c[2] = c[1]; + c[1] = getcube(t, 1); + if(!c[1] || isempty(*c[1])) { c[0] = c[1]; c[1] = c[2]; c[2] = NULL; } + else { z++; t[d]+=fg; } + } + else // drop down + { + z--; + t[d]-= fg; + c[0] = c[1]; + c[1] = getcube(t, 0); + } + + if(!c[1] || isempty(*c[1])) { flags[x][y] |= NOTHMAP; return; } + + flags[x][y] |= PAINTED; + mapz [x][y] = z; + + if(!c[0]) c[0] = getcube(t, 1); + if(!c[2]) c[2] = getcube(t, -1); + c[3] = getcube(t, -2); + c[2] = !c[2] || isempty(*c[2]) ? NULL : c[2]; + c[3] = !c[3] || isempty(*c[3]) ? NULL : c[3]; + + uint face = getface(c[1], d); + if(face == 0x08080808 && (!c[0] || !isempty(*c[0]))) { flags[x][y] |= NOTHMAP; return; } + if(c[1]->faces[R[d]] == F_SOLID) // was single + face += 0x08080808; + else // was pair + face += c[2] ? getface(c[2], d) : 0x08080808; + face += 0x08080808; // c[3] + uchar *f = (uchar*)&face; + addpoint(x, y, z, f[0]); + addpoint(x+1, y, z, f[1]); + addpoint(x, y+1, z, f[2]); + addpoint(x+1, y+1, z, f[3]); + + if(selecting) // continue to adjacent cubes + { + if(x>bmx) select(x-1, y, z); + if(xbmy) select(x, y-1, z); + if(y, <, 1, 0, -); + else + pullhmap(worldsize*8, <, >, 0, 8, +); + + cube **c = cmap[x][y]; + int e[2][2]; + int notempty = 0; + + loopk(4) if(c[k]) { + loopi(2) loopj(2) { + e[i][j] = min(8, map[x+i][y+j] - (mapz[x][y]+3-k)*8); + notempty |= e[i][j] > 0; + } + if(notempty) + { + c[k]->texture[sel.orient] = c[1]->texture[sel.orient]; + solidfaces(*c[k]); + loopi(2) loopj(2) + { + int f = e[i][j]; + if(f<0 || (f==0 && e[1-i][j]==0 && e[i][1-j]==0)) + { + f=0; + pushside(*c[k], d, i, j, 0); + pushside(*c[k], d, i, j, 1); + } + edgeset(cubeedge(*c[k], d, i, j), dc, dc ? f : 8-f); + } + } + else + emptyfaces(*c[k]); + } + + if(!changed) return; + if(x>mx) ripple(x-1, y, mapz[x][y], true); + if(xmy) ripple(x, y-1, mapz[x][y], true); + if(ymx && y>my)); // do diagonals because adjacents - DIAGONAL_RIPPLE(-1, +1, (x>mx && ymy)); - } + if(flags[x a][ y] & PAINTED) \ + ripple(x a, y b, mapz[x a][y], true); \ + else if(flags[x][y b] & PAINTED) \ + ripple(x a, y b, mapz[x][y b], true); \ + } + + DIAGONAL_RIPPLE(-1, -1, (x>mx && y>my)); // do diagonals because adjacents + DIAGONAL_RIPPLE(-1, +1, (x>mx && ymy)); + } #define loopbrush(i) for(int x=bmx; x<=bnx+i; x++) for(int y=bmy; y<=bny+i; y++) - void paint() - { - loopbrush(1) - map[x][y] -= dr * brush[x][y]; - } - - void smooth() - { - int sum, div; - loopbrush(-2) - { - sum = 0; - div = 9; - loopi(3) loopj(3) - if(flags[x+i][y+j] & MAPPED) - sum += map[x+i][y+j]; - else div--; - if(div) - map[x+1][y+1] = sum / div; - } - } - - void rippleandset() - { - loopbrush(0) - ripple(x, y, gz, false); - } - - void run(int dir, int mode) - { - d = dimension(sel.orient); - dc = dimcoord(sel.orient); - dcr= dc ? 1 : -1; - dr = dir>0 ? 1 : -1; - br = dir>0 ? 0x08080808 : 0; - // biasup = mode == dir<0; - biasup = dir<0; - bool paintme = paintbrush; - int cx = (sel.corner&1 ? 0 : -1); - int cy = (sel.corner&2 ? 0 : -1); - hws= (worldsize>>gridpower); - gx = (cur[R[d]] >> gridpower) + cx - MAXBRUSH2; - gy = (cur[C[d]] >> gridpower) + cy - MAXBRUSH2; - gz = (cur[D[d]] >> gridpower); - fs = dc ? 4 : 0; - fg = dc ? gridsize : -gridsize; - mx = max(0, -gx); // ripple range - my = max(0, -gy); - nx = min(MAXBRUSH-1, hws-gx) - 1; - ny = min(MAXBRUSH-1, hws-gy) - 1; - if(havesel) - { // selection range - bmx = mx = max(mx, (sel.o[R[d]]>>gridpower)-gx); - bmy = my = max(my, (sel.o[C[d]]>>gridpower)-gy); - bnx = nx = min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1); - bny = ny = min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1); - } - if(havesel && mode<0) // -ve means smooth selection - paintme = false; - else - { // brush range - bmx = max(mx, brushminx); - bmy = max(my, brushminy); - bnx = min(nx, brushmaxx-1); - bny = min(ny, brushmaxy-1); - } - nz = worldsize-gridsize; - mz = 0; - hundo.s = ivec(d,1,1,5); - hundo.orient = sel.orient; - hundo.grid = gridsize; - forcenextundo(); - - changes.grid = gridsize; - changes.s = changes.o = cur; - memset(map, 0, sizeof map); - memset(flags, 0, sizeof flags); - - selecting = true; - select(clamp(MAXBRUSH2-cx, bmx, bnx), - clamp(MAXBRUSH2-cy, bmy, bny), - dc ? gz : hws - gz); - selecting = false; - if(paintme) - paint(); - else - smooth(); - rippleandset(); // pull up points to cubify, and set - changes.s.sub(changes.o).shr(gridpower).add(1); - changed(changes); - } + void paint() + { + loopbrush(1) + map[x][y] -= dr * brush[x][y]; + } + + void smooth() + { + int sum, div; + loopbrush(-2) + { + sum = 0; + div = 9; + loopi(3) loopj(3) + if(flags[x+i][y+j] & MAPPED) + sum += map[x+i][y+j]; + else div--; + if(div) + map[x+1][y+1] = sum / div; + } + } + + void rippleandset() + { + loopbrush(0) + ripple(x, y, gz, false); + } + + void run(int dir, int mode) + { + d = dimension(sel.orient); + dc = dimcoord(sel.orient); + dcr= dc ? 1 : -1; + dr = dir>0 ? 1 : -1; + br = dir>0 ? 0x08080808 : 0; + // biasup = mode == dir<0; + biasup = dir<0; + bool paintme = paintbrush; + int cx = (sel.corner&1 ? 0 : -1); + int cy = (sel.corner&2 ? 0 : -1); + hws= (worldsize>>gridpower); + gx = (cur[R[d]] >> gridpower) + cx - MAXBRUSH2; + gy = (cur[C[d]] >> gridpower) + cy - MAXBRUSH2; + gz = (cur[D[d]] >> gridpower); + fs = dc ? 4 : 0; + fg = dc ? gridsize : -gridsize; + mx = max(0, -gx); // ripple range + my = max(0, -gy); + nx = min(MAXBRUSH-1, hws-gx) - 1; + ny = min(MAXBRUSH-1, hws-gy) - 1; + if(havesel) + { // selection range + bmx = mx = max(mx, (sel.o[R[d]]>>gridpower)-gx); + bmy = my = max(my, (sel.o[C[d]]>>gridpower)-gy); + bnx = nx = min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1); + bny = ny = min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1); + } + if(havesel && mode<0) // -ve means smooth selection + paintme = false; + else + { // brush range + bmx = max(mx, brushminx); + bmy = max(my, brushminy); + bnx = min(nx, brushmaxx-1); + bny = min(ny, brushmaxy-1); + } + nz = worldsize-gridsize; + mz = 0; + hundo.s = ivec(d,1,1,5); + hundo.orient = sel.orient; + hundo.grid = gridsize; + forcenextundo(); + + changes.grid = gridsize; + changes.s = changes.o = cur; + memset(map, 0, sizeof map); + memset(flags, 0, sizeof flags); + + selecting = true; + select(clamp(MAXBRUSH2-cx, bmx, bnx), + clamp(MAXBRUSH2-cy, bmy, bny), + dc ? gz : hws - gz); + selecting = false; + if(paintme) + paint(); + else + smooth(); + rippleandset(); // pull up points to cubify, and set + changes.s.sub(changes.o).shr(gridpower).add(1); + changed(changes); + } } void edithmap(int dir, int mode) { - if((nompedit && multiplayer()) || !hmapsel) return; - hmap::run(dir, mode); + if((nompedit && multiplayer()) || !hmapsel) return; + hmap::run(dir, mode); } ///////////// main cube edit //////////////// @@ -1923,151 +1922,151 @@ int bounded(int n) { return n<0 ? 0 : (n>8 ? 8 : n); } void pushedge(uchar &edge, int dir, int dc) { - int ne = bounded(edgeget(edge, dc)+dir); - edgeset(edge, dc, ne); - int oe = edgeget(edge, 1-dc); - if((dir<0 && dc && oe>ne) || (dir>0 && dc==0 && oene) || (dir>0 && dc==0 && oe0) == dc && h<=0) || ((dir<0) == dc && h>=worldsize)) return; - if(dir<0) sel.o[d] += sel.grid * seldir; - } - - if(dc) sel.o[d] += sel.us(d)-sel.grid; - sel.s[d] = 1; - - loopselxyz( - if(c.children) solidfaces(c); - ushort mat = getmaterial(c); - discardchildren(c, true); - c.material = mat; - if(mode==1) // fill command - { - if(dir<0) - { - solidfaces(c); - cube &o = blockcube(x, y, 1, sel, -sel.grid); - loopi(6) - c.texture[i] = o.children ? (int) DEFAULT_GEOM : (int) o.texture[i]; - } - else - emptyfaces(c); - } - else - { - uint bak = c.faces[d]; - uchar *p = (uchar *)&c.faces[d]; - - if(mode==2) - linkedpush(c, d, sel.corner&1, sel.corner>>1, dc, seldir); // corner command - else - { - loop(mx,2) loop(my,2) // pull/push edges command - { - if(x==0 && mx==0 && sel.cx) continue; - if(y==0 && my==0 && sel.cy) continue; - if(x==sel.s[R[d]]-1 && mx==1 && (sel.cx+sel.cxs)&1) continue; - if(y==sel.s[C[d]]-1 && my==1 && (sel.cy+sel.cys)&1) continue; - if(p[mx+my*2] != ((uchar *)&bak)[mx+my*2]) continue; - - linkedpush(c, d, mx, my, dc, seldir); - } - } - - optiface(p, c); - if(invalidcubeguard==1 && !isvalidcube(c)) - { - uint newbak = c.faces[d]; - uchar *m = (uchar *)&bak; - uchar *n = (uchar *)&newbak; - loopk(4) if(n[k] != m[k]) // tries to find partial edit that is valid - { - c.faces[d] = bak; - c.edges[d*4+k] = n[k]; - if(isvalidcube(c)) - m[k] = n[k]; - } - c.faces[d] = bak; - } - } - ); - if (mode==1 && dir>0) - sel.o[d] += sel.grid * seldir; + if(mode==1 && (sel.cx || sel.cy || sel.cxs&1 || sel.cys&1)) mode = 0; + int d = dimension(sel.orient); + int dc = dimcoord(sel.orient); + int seldir = dc ? -dir : dir; + + if(local) + game::edittrigger(sel, EDIT_FACE, dir, mode); + + if(mode==1) + { + int h = sel.o[d]+dc*sel.grid; + if(((dir>0) == dc && h<=0) || ((dir<0) == dc && h>=worldsize)) return; + if(dir<0) sel.o[d] += sel.grid * seldir; + } + + if(dc) sel.o[d] += sel.us(d)-sel.grid; + sel.s[d] = 1; + + loopselxyz( + if(c.children) solidfaces(c); + ushort mat = getmaterial(c); + discardchildren(c, true); + c.material = mat; + if(mode==1) // fill command + { + if(dir<0) + { + solidfaces(c); + cube &o = blockcube(x, y, 1, sel, -sel.grid); + loopi(6) + c.texture[i] = o.children ? (int) DEFAULT_GEOM : (int) o.texture[i]; + } + else + emptyfaces(c); + } + else + { + uint bak = c.faces[d]; + uchar *p = (uchar *)&c.faces[d]; + + if(mode==2) + linkedpush(c, d, sel.corner&1, sel.corner>>1, dc, seldir); // corner command + else + { + loop(mx,2) loop(my,2) // pull/push edges command + { + if(x==0 && mx==0 && sel.cx) continue; + if(y==0 && my==0 && sel.cy) continue; + if(x==sel.s[R[d]]-1 && mx==1 && (sel.cx+sel.cxs)&1) continue; + if(y==sel.s[C[d]]-1 && my==1 && (sel.cy+sel.cys)&1) continue; + if(p[mx+my*2] != ((uchar *)&bak)[mx+my*2]) continue; + + linkedpush(c, d, mx, my, dc, seldir); + } + } + + optiface(p, c); + if(invalidcubeguard==1 && !isvalidcube(c)) + { + uint newbak = c.faces[d]; + uchar *m = (uchar *)&bak; + uchar *n = (uchar *)&newbak; + loopk(4) if(n[k] != m[k]) // tries to find partial edit that is valid + { + c.faces[d] = bak; + c.edges[d*4+k] = n[k]; + if(isvalidcube(c)) + m[k] = n[k]; + } + c.faces[d] = bak; + } + } + ); + if (mode==1 && dir>0) + sel.o[d] += sel.grid * seldir; } void editface(int *dir, int *mode) { - if(noedit(moving!=0)) return; - if(hmapedit!=1) - mpeditface(*dir, *mode, sel, true); - else - edithmap(*dir, *mode); + if(noedit(moving!=0)) return; + if(hmapedit!=1) + mpeditface(*dir, *mode, sel, true); + else + edithmap(*dir, *mode); } VAR(selectionsurf, 0, 0, 1); void pushsel(int *dir) { - if(noedit(moving!=0)) return; - int d = dimension(orient); - int s = dimcoord(orient) ? -*dir : *dir; - sel.o[d] += s*sel.grid; - if(selectionsurf==1) - { - player->o[d] += s*sel.grid; - player->resetinterp(); - } + if(noedit(moving!=0)) return; + int d = dimension(orient); + int s = dimcoord(orient) ? -*dir : *dir; + sel.o[d] += s*sel.grid; + if(selectionsurf==1) + { + player->o[d] += s*sel.grid; + player->resetinterp(); + } } void mpdelcube(selinfo &sel, bool local) { - if(local) game::edittrigger(sel, EDIT_DELCUBE); - loopselxyz(discardchildren(c, true); emptyfaces(c)); + if(local) game::edittrigger(sel, EDIT_DELCUBE); + loopselxyz(discardchildren(c, true); emptyfaces(c)); } void delcube() { - if(noedit(true)) return; - mpdelcube(sel, true); + if(noedit(true)) return; + mpdelcube(sel, true); } COMMAND(pushsel, "i"); @@ -2080,14 +2079,14 @@ int curtexindex = -1, lasttex = 0, lasttexmillis = -1; int texpaneltimer = 0; vector texmru; -void tofronttex() // maintain most recently used of the texture lists when applying texture +void tofronttex() // maintain most recently used of the texture lists when applying texture { - int c = curtexindex; - if(texmru.inrange(c)) - { - texmru.insert(0, texmru.remove(c)); - curtexindex = -1; - } + int c = curtexindex; + if(texmru.inrange(c)) + { + texmru.insert(0, texmru.remove(c)); + curtexindex = -1; + } } selinfo repsel; @@ -2099,421 +2098,421 @@ VAR(usevdelta, 1, 0, 0); static VSlot *remapvslot(int index, bool delta, const VSlot &ds) { - loopv(remappedvslots) if(remappedvslots[i].index == index) return remappedvslots[i].vslot; - VSlot &vs = lookupvslot(index, false); - if(vs.index < 0 || vs.index == DEFAULT_SKY) return NULL; - VSlot *edit = NULL; - if(delta) - { - VSlot ms; - mergevslot(ms, vs, ds); - edit = ms.changed ? editvslot(vs, ms) : vs.slot->variants; - } - else edit = ds.changed ? editvslot(vs, ds) : vs.slot->variants; - if(!edit) edit = &vs; - remappedvslots.add(vslotmap(vs.index, edit)); - return edit; + loopv(remappedvslots) if(remappedvslots[i].index == index) return remappedvslots[i].vslot; + VSlot &vs = lookupvslot(index, false); + if(vs.index < 0 || vs.index == DEFAULT_SKY) return NULL; + VSlot *edit = NULL; + if(delta) + { + VSlot ms; + mergevslot(ms, vs, ds); + edit = ms.changed ? editvslot(vs, ms) : vs.slot->variants; + } + else edit = ds.changed ? editvslot(vs, ds) : vs.slot->variants; + if(!edit) edit = &vs; + remappedvslots.add(vslotmap(vs.index, edit)); + return edit; } static void remapvslots(cube &c, bool delta, const VSlot &ds, int orient, bool &findrep, VSlot *&findedit) { - if(c.children) - { - loopi(8) remapvslots(c.children[i], delta, ds, orient, findrep, findedit); - return; - } - static VSlot ms; - if(orient<0) loopi(6) - { - VSlot *edit = remapvslot(c.texture[i], delta, ds); - if(edit) - { - c.texture[i] = edit->index; - if(!findedit) findedit = edit; - } - } - else - { - int i = visibleorient(c, orient); - VSlot *edit = remapvslot(c.texture[i], delta, ds); - if(edit) - { - if(findrep) - { - if(reptex < 0) reptex = c.texture[i]; - else if(reptex != c.texture[i]) findrep = false; - } - c.texture[i] = edit->index; - if(!findedit) findedit = edit; - } - } + if(c.children) + { + loopi(8) remapvslots(c.children[i], delta, ds, orient, findrep, findedit); + return; + } + static VSlot ms; + if(orient<0) loopi(6) + { + VSlot *edit = remapvslot(c.texture[i], delta, ds); + if(edit) + { + c.texture[i] = edit->index; + if(!findedit) findedit = edit; + } + } + else + { + int i = visibleorient(c, orient); + VSlot *edit = remapvslot(c.texture[i], delta, ds); + if(edit) + { + if(findrep) + { + if(reptex < 0) reptex = c.texture[i]; + else if(reptex != c.texture[i]) findrep = false; + } + c.texture[i] = edit->index; + if(!findedit) findedit = edit; + } + } } void edittexcube(cube &c, int tex, int orient, bool &findrep) { - if(orient<0) loopi(6) c.texture[i] = tex; - else - { - int i = visibleorient(c, orient); - if(findrep) - { - if(reptex < 0) reptex = c.texture[i]; - else if(reptex != c.texture[i]) findrep = false; - } - c.texture[i] = tex; - } - if(c.children) loopi(8) edittexcube(c.children[i], tex, orient, findrep); + if(orient<0) loopi(6) c.texture[i] = tex; + else + { + int i = visibleorient(c, orient); + if(findrep) + { + if(reptex < 0) reptex = c.texture[i]; + else if(reptex != c.texture[i]) findrep = false; + } + c.texture[i] = tex; + } + if(c.children) loopi(8) edittexcube(c.children[i], tex, orient, findrep); } VAR(allfaces, 0, 0, 1); void mpeditvslot(int delta, VSlot &ds, int allfaces, selinfo &sel, bool local) { - if(local) - { - game::edittrigger(sel, EDIT_VSLOT, delta, allfaces, 0, &ds); - if(!(lastsel==sel)) tofronttex(); - if(allfaces || !(repsel == sel)) reptex = -1; - repsel = sel; - } - bool findrep = local && !allfaces && reptex < 0; - VSlot *findedit = NULL; - loopselxyz(remapvslots(c, delta != 0, ds, allfaces ? -1 : sel.orient, findrep, findedit)); - remappedvslots.setsize(0); - if(local && findedit) - { - lasttex = findedit->index; - lasttexmillis = totalmillis; - curtexindex = texmru.find(lasttex); - if(curtexindex < 0) - { - curtexindex = texmru.length(); - texmru.add(lasttex); - } - } + if(local) + { + game::edittrigger(sel, EDIT_VSLOT, delta, allfaces, 0, &ds); + if(!(lastsel==sel)) tofronttex(); + if(allfaces || !(repsel == sel)) reptex = -1; + repsel = sel; + } + bool findrep = local && !allfaces && reptex < 0; + VSlot *findedit = NULL; + loopselxyz(remapvslots(c, delta != 0, ds, allfaces ? -1 : sel.orient, findrep, findedit)); + remappedvslots.setsize(0); + if(local && findedit) + { + lasttex = findedit->index; + lasttexmillis = totalmillis; + curtexindex = texmru.find(lasttex); + if(curtexindex < 0) + { + curtexindex = texmru.length(); + texmru.add(lasttex); + } + } } bool mpeditvslot(int delta, int allfaces, selinfo &sel, ucharbuf &buf) { - VSlot ds; - if(!unpackvslot(buf, ds, delta != 0)) return false; - editingvslot(ds.layer); - mpeditvslot(delta, ds, allfaces, sel, false); - return true; + VSlot ds; + if(!unpackvslot(buf, ds, delta != 0)) return false; + editingvslot(ds.layer); + mpeditvslot(delta, ds, allfaces, sel, false); + return true; } void vdelta(char *body) { - if(noedit()) return; - usevdelta++; - execute(body); - usevdelta--; + if(noedit()) return; + usevdelta++; + execute(body); + usevdelta--; } COMMAND(vdelta, "s"); void vrotate(int *n) { - if(noedit()) return; - VSlot ds; - ds.changed = 1<changed && nompedit && multiplayer()) return; - } - editingvslot(ds.layer); - mpeditvslot(usevdelta, ds, allfaces, sel, true); + if(noedit()) return; + VSlot ds; + ds.changed = 1<changed && nompedit && multiplayer()) return; + } + editingvslot(ds.layer); + mpeditvslot(usevdelta, ds, allfaces, sel, true); } COMMAND(vlayer, "i"); ICOMMAND(getvlayer, "i", (int *tex), intret(lookupvslot(*tex, false).layer)); void valpha(float *front, float *back) { - if(noedit()) return; - VSlot ds; - ds.changed = 1< str; - loopv(vslot.params) - { - SlotShaderParam &p = vslot.params[i]; - if(i) str.put(' '); - str.put(p.name, strlen(p.name)); - } - str.add('\0'); - stringret(newstring(str.getbuf(), str.length()-1)); + VSlot &vslot = lookupvslot(*tex, false); + vector str; + loopv(vslot.params) + { + SlotShaderParam &p = vslot.params[i]; + if(i) str.put(' '); + str.put(p.name, strlen(p.name)); + } + str.add('\0'); + stringret(newstring(str.getbuf(), str.length()-1)); }); void mpedittex(int tex, int allfaces, selinfo &sel, bool local) { - if(local) - { - game::edittrigger(sel, EDIT_TEX, tex, allfaces); - if(allfaces || !(repsel == sel)) reptex = -1; - repsel = sel; - } - bool findrep = local && !allfaces && reptex < 0; - loopselxyz(edittexcube(c, tex, allfaces ? -1 : sel.orient, findrep)); + if(local) + { + game::edittrigger(sel, EDIT_TEX, tex, allfaces); + if(allfaces || !(repsel == sel)) reptex = -1; + repsel = sel; + } + bool findrep = local && !allfaces && reptex < 0; + loopselxyz(edittexcube(c, tex, allfaces ? -1 : sel.orient, findrep)); } static int unpacktex(int &tex, ucharbuf &buf, bool insert = true) { - if(tex < 0x10000) return true; - VSlot ds; - if(!unpackvslot(buf, ds, false)) return false; - VSlot &vs = *lookupslot(tex & 0xFFFF, false).variants; - if(vs.index < 0 || vs.index == DEFAULT_SKY) return false; - VSlot *edit = insert ? editvslot(vs, ds) : findvslot(*vs.slot, vs, ds); - if(!edit) return false; - tex = edit->index; - return true; + if(tex < 0x10000) return true; + VSlot ds; + if(!unpackvslot(buf, ds, false)) return false; + VSlot &vs = *lookupslot(tex & 0xFFFF, false).variants; + if(vs.index < 0 || vs.index == DEFAULT_SKY) return false; + VSlot *edit = insert ? editvslot(vs, ds) : findvslot(*vs.slot, vs, ds); + if(!edit) return false; + tex = edit->index; + return true; } int shouldpacktex(int index) { - if(vslots.inrange(index)) - { - VSlot &vs = *vslots[index]; - if(vs.changed) return 0x10000 + vs.slot->index; - } - return 0; + if(vslots.inrange(index)) + { + VSlot &vs = *vslots[index]; + if(vs.changed) return 0x10000 + vs.slot->index; + } + return 0; } bool mpedittex(int tex, int allfaces, selinfo &sel, ucharbuf &buf) { - if(!unpacktex(tex, buf)) return false; - mpedittex(tex, allfaces, sel, false); - return true; + if(!unpacktex(tex, buf)) return false; + mpedittex(tex, allfaces, sel, false); + return true; } void filltexlist() { - if(texmru.length()!=vslots.length()) - { - loopvrev(texmru) if(texmru[i]>=vslots.length()) - { - if(curtexindex > i) curtexindex--; - else if(curtexindex == i) curtexindex = -1; - texmru.remove(i); - } - loopv(vslots) if(texmru.find(i)<0) texmru.add(i); - } + if(texmru.length()!=vslots.length()) + { + loopvrev(texmru) if(texmru[i]>=vslots.length()) + { + if(curtexindex > i) curtexindex--; + else if(curtexindex == i) curtexindex = -1; + texmru.remove(i); + } + loopv(vslots) if(texmru.find(i)<0) texmru.add(i); + } } void compactmruvslots() { - remappedvslots.setsize(0); - loopvrev(texmru) - { - if(vslots.inrange(texmru[i])) - { - VSlot &vs = *vslots[texmru[i]]; - if(vs.index >= 0) - { - texmru[i] = vs.index; - continue; - } - } - if(curtexindex > i) curtexindex--; - else if(curtexindex == i) curtexindex = -1; - texmru.remove(i); - } - if(vslots.inrange(lasttex)) - { - VSlot &vs = *vslots[lasttex]; - lasttex = vs.index >= 0 ? vs.index : 0; - } - else lasttex = 0; - reptex = vslots.inrange(reptex) ? vslots[reptex]->index : -1; + remappedvslots.setsize(0); + loopvrev(texmru) + { + if(vslots.inrange(texmru[i])) + { + VSlot &vs = *vslots[texmru[i]]; + if(vs.index >= 0) + { + texmru[i] = vs.index; + continue; + } + } + if(curtexindex > i) curtexindex--; + else if(curtexindex == i) curtexindex = -1; + texmru.remove(i); + } + if(vslots.inrange(lasttex)) + { + VSlot &vs = *vslots[lasttex]; + lasttex = vs.index >= 0 ? vs.index : 0; + } + else lasttex = 0; + reptex = vslots.inrange(reptex) ? vslots[reptex]->index : -1; } void edittex(int i, bool save = true) { - lasttex = i; - lasttexmillis = totalmillis; - if(save) - { - loopvj(texmru) if(texmru[j]==lasttex) { curtexindex = j; break; } - } - mpedittex(i, allfaces, sel, true); + lasttex = i; + lasttexmillis = totalmillis; + if(save) + { + loopvj(texmru) if(texmru[j]==lasttex) { curtexindex = j; break; } + } + mpedittex(i, allfaces, sel, true); } void edittex_(int *dir) { - if(noedit()) return; - filltexlist(); - if(texmru.empty()) return; - texpaneltimer = 5000; - if(!(lastsel==sel)) tofronttex(); - curtexindex = clamp(curtexindex<0 ? 0 : curtexindex+*dir, 0, texmru.length()-1); - edittex(texmru[curtexindex], false); + if(noedit()) return; + filltexlist(); + if(texmru.empty()) return; + texpaneltimer = 5000; + if(!(lastsel==sel)) tofronttex(); + curtexindex = clamp(curtexindex<0 ? 0 : curtexindex+*dir, 0, texmru.length()-1); + edittex(texmru[curtexindex], false); } void gettex() { - if(noedit(true)) return; - filltexlist(); - int tex = -1; - loopxyz(sel, sel.grid, tex = c.texture[sel.orient]); - loopv(texmru) if(texmru[i]==tex) - { - curtexindex = i; - tofronttex(); - return; - } + if(noedit(true)) return; + filltexlist(); + int tex = -1; + loopxyz(sel, sel.grid, tex = c.texture[sel.orient]); + loopv(texmru) if(texmru[i]==tex) + { + curtexindex = i; + tofronttex(); + return; + } } void getcurtex() { - if(noedit(true)) return; - filltexlist(); - int index = curtexindex < 0 ? 0 : curtexindex; - if(!texmru.inrange(index)) return; - intret(texmru[index]); + if(noedit(true)) return; + filltexlist(); + int index = curtexindex < 0 ? 0 : curtexindex; + if(!texmru.inrange(index)) return; + intret(texmru[index]); } void getseltex() { - if(noedit(true)) return; - cube &c = lookupcube(sel.o, -sel.grid); - if(c.children || isempty(c)) return; - intret(c.texture[sel.orient]); + if(noedit(true)) return; + cube &c = lookupcube(sel.o, -sel.grid); + if(c.children || isempty(c)) return; + intret(c.texture[sel.orient]); } void gettexname(int *tex, int *subslot) { - if(noedit(true) || *tex<0) return; - VSlot &vslot = lookupvslot(*tex, false); - Slot &slot = *vslot.slot; - if(!slot.sts.inrange(*subslot)) return; - result(slot.sts[*subslot].name); + if(noedit(true) || *tex<0) return; + VSlot &vslot = lookupvslot(*tex, false); + Slot &slot = *vslot.slot; + if(!slot.sts.inrange(*subslot)) return; + result(slot.sts[*subslot].name); } void getslottex(int *idx) { - if(*idx < 0 || !slots.inrange(*idx)) { intret(-1); return; } - Slot &slot = lookupslot(*idx, false); - intret(slot.variants->index); + if(*idx < 0 || !slots.inrange(*idx)) { intret(-1); return; } + Slot &slot = lookupslot(*idx, false); + intret(slot.variants->index); } COMMANDN(edittex, edittex_, "i"); @@ -2529,38 +2528,38 @@ ICOMMAND(texloaded, "i", (int *tex), intret(slots.inrange(*tex) && slots[*tex]-> void replacetexcube(cube &c, int oldtex, int newtex) { - loopi(6) if(c.texture[i] == oldtex) c.texture[i] = newtex; - if(c.children) loopi(8) replacetexcube(c.children[i], oldtex, newtex); + loopi(6) if(c.texture[i] == oldtex) c.texture[i] = newtex; + if(c.children) loopi(8) replacetexcube(c.children[i], oldtex, newtex); } void mpreplacetex(int oldtex, int newtex, bool insel, selinfo &sel, bool local) { - if(local) game::edittrigger(sel, EDIT_REPLACE, oldtex, newtex, insel ? 1 : 0); - if(insel) - { - loopselxyz(replacetexcube(c, oldtex, newtex)); - } - else - { - loopi(8) replacetexcube(worldroot[i], oldtex, newtex); - } - allchanged(); + if(local) game::edittrigger(sel, EDIT_REPLACE, oldtex, newtex, insel ? 1 : 0); + if(insel) + { + loopselxyz(replacetexcube(c, oldtex, newtex)); + } + else + { + loopi(8) replacetexcube(worldroot[i], oldtex, newtex); + } + allchanged(); } bool mpreplacetex(int oldtex, int newtex, bool insel, selinfo &sel, ucharbuf &buf) { - if(!unpacktex(oldtex, buf, false)) return false; - editingvslot(oldtex); - if(!unpacktex(newtex, buf)) return false; - mpreplacetex(oldtex, newtex, insel, sel, false); - return true; + if(!unpacktex(oldtex, buf, false)) return false; + editingvslot(oldtex); + if(!unpacktex(newtex, buf)) return false; + mpreplacetex(oldtex, newtex, insel, sel, false); + return true; } void replace(bool insel) { - if(noedit()) return; - if(reptex < 0) { conoutf(CON_ERROR, "can only replace after a texture edit"); return; } - mpreplacetex(reptex, lasttex, insel, sel, true); + if(noedit()) return; + if(reptex < 0) { conoutf(CON_ERROR, "can only replace after a texture edit"); return; } + mpreplacetex(reptex, lasttex, insel, sel, true); } ICOMMAND(replace, "", (), replace(false)); @@ -2574,101 +2573,101 @@ uint mflip(uint face) { return (face&0xFF0000FF) | ((face&0x00FF0000)>>8) | ((fa void flipcube(cube &c, int d) { - swap(c.texture[d*2], c.texture[d*2+1]); - c.faces[D[d]] = dflip(c.faces[D[d]]); - c.faces[C[d]] = cflip(c.faces[C[d]]); - c.faces[R[d]] = rflip(c.faces[R[d]]); - if(c.children) - { - loopi(8) if(i&octadim(d)) swap(c.children[i], c.children[i-octadim(d)]); - loopi(8) flipcube(c.children[i], d); - } + swap(c.texture[d*2], c.texture[d*2+1]); + c.faces[D[d]] = dflip(c.faces[D[d]]); + c.faces[C[d]] = cflip(c.faces[C[d]]); + c.faces[R[d]] = rflip(c.faces[R[d]]); + if(c.children) + { + loopi(8) if(i&octadim(d)) swap(c.children[i], c.children[i-octadim(d)]); + loopi(8) flipcube(c.children[i], d); + } } void rotatequad(cube &a, cube &b, cube &c, cube &d) { - cube t = a; a = b; b = c; c = d; d = t; + cube t = a; a = b; b = c; c = d; d = t; } void rotatecube(cube &c, int d) // rotates cube clockwise. see pics in cvs for help. { - c.faces[D[d]] = cflip (mflip(c.faces[D[d]])); - c.faces[C[d]] = dflip (mflip(c.faces[C[d]])); - c.faces[R[d]] = rflip (mflip(c.faces[R[d]])); - swap(c.faces[R[d]], c.faces[C[d]]); - - swap(c.texture[2*R[d]], c.texture[2*C[d]+1]); - swap(c.texture[2*C[d]], c.texture[2*R[d]+1]); - swap(c.texture[2*C[d]], c.texture[2*C[d]+1]); - - if(c.children) - { - int row = octadim(R[d]); - int col = octadim(C[d]); - for(int i=0; i<=octadim(d); i+=octadim(d)) rotatequad - ( - c.children[i+row], - c.children[i], - c.children[i+col], - c.children[i+col+row] - ); - loopi(8) rotatecube(c.children[i], d); - } + c.faces[D[d]] = cflip (mflip(c.faces[D[d]])); + c.faces[C[d]] = dflip (mflip(c.faces[C[d]])); + c.faces[R[d]] = rflip (mflip(c.faces[R[d]])); + swap(c.faces[R[d]], c.faces[C[d]]); + + swap(c.texture[2*R[d]], c.texture[2*C[d]+1]); + swap(c.texture[2*C[d]], c.texture[2*R[d]+1]); + swap(c.texture[2*C[d]], c.texture[2*C[d]+1]); + + if(c.children) + { + int row = octadim(R[d]); + int col = octadim(C[d]); + for(int i=0; i<=octadim(d); i+=octadim(d)) rotatequad + ( + c.children[i+row], + c.children[i], + c.children[i+col], + c.children[i+col+row] + ); + loopi(8) rotatecube(c.children[i], d); + } } void mpflip(selinfo &sel, bool local) { - if(local) - { - game::edittrigger(sel, EDIT_FLIP); - makeundo(); - } - int zs = sel.s[dimension(sel.orient)]; - loopxy(sel) - { - loop(z,zs) flipcube(selcube(x, y, z), dimension(sel.orient)); - loop(z,zs/2) - { - cube &a = selcube(x, y, z); - cube &b = selcube(x, y, zs-z-1); - swap(a, b); - } - } - changed(sel); + if(local) + { + game::edittrigger(sel, EDIT_FLIP); + makeundo(); + } + int zs = sel.s[dimension(sel.orient)]; + loopxy(sel) + { + loop(z,zs) flipcube(selcube(x, y, z), dimension(sel.orient)); + loop(z,zs/2) + { + cube &a = selcube(x, y, z); + cube &b = selcube(x, y, zs-z-1); + swap(a, b); + } + } + changed(sel); } void flip() { - if(noedit()) return; - mpflip(sel, true); + if(noedit()) return; + mpflip(sel, true); } void mprotate(int cw, selinfo &sel, bool local) { - if(local) game::edittrigger(sel, EDIT_ROTATE, cw); - int d = dimension(sel.orient); - if(!dimcoord(sel.orient)) cw = -cw; - int m = sel.s[C[d]] < sel.s[R[d]] ? C[d] : R[d]; - int ss = sel.s[m] = max(sel.s[R[d]], sel.s[C[d]]); - if(local) makeundo(); - loop(z,sel.s[D[d]]) loopi(cw>0 ? 1 : 3) - { - loopxy(sel) rotatecube(selcube(x,y,z), d); - loop(y,ss/2) loop(x,ss-1-y*2) rotatequad - ( - selcube(ss-1-y, x+y, z), - selcube(x+y, y, z), - selcube(y, ss-1-x-y, z), - selcube(ss-1-x-y, ss-1-y, z) - ); - } - changed(sel); + if(local) game::edittrigger(sel, EDIT_ROTATE, cw); + int d = dimension(sel.orient); + if(!dimcoord(sel.orient)) cw = -cw; + int m = sel.s[C[d]] < sel.s[R[d]] ? C[d] : R[d]; + int ss = sel.s[m] = max(sel.s[R[d]], sel.s[C[d]]); + if(local) makeundo(); + loop(z,sel.s[D[d]]) loopi(cw>0 ? 1 : 3) + { + loopxy(sel) rotatecube(selcube(x,y,z), d); + loop(y,ss/2) loop(x,ss-1-y*2) rotatequad + ( + selcube(ss-1-y, x+y, z), + selcube(x+y, y, z), + selcube(y, ss-1-x-y, z), + selcube(ss-1-x-y, ss-1-y, z) + ); + } + changed(sel); } void rotate(int *cw) { - if(noedit()) return; - mprotate(*cw, sel, true); + if(noedit()) return; + mprotate(*cw, sel, true); } COMMAND(flip, ""); @@ -2677,83 +2676,83 @@ COMMAND(rotate, "i"); enum { EDITMATF_EMPTY = 0x10000, EDITMATF_NOTEMPTY = 0x20000, EDITMATF_SOLID = 0x30000, EDITMATF_NOTSOLID = 0x40000 }; static const struct { const char *name; int filter; } editmatfilters[] = { - { "empty", EDITMATF_EMPTY }, - { "notempty", EDITMATF_NOTEMPTY }, - { "solid", EDITMATF_SOLID }, - { "notsolid", EDITMATF_NOTSOLID } + { "empty", EDITMATF_EMPTY }, + { "notempty", EDITMATF_NOTEMPTY }, + { "solid", EDITMATF_SOLID }, + { "notsolid", EDITMATF_NOTSOLID } }; void setmat(cube &c, ushort mat, ushort matmask, ushort filtermat, ushort filtermask, int filtergeom) { - if(c.children) - loopi(8) setmat(c.children[i], mat, matmask, filtermat, filtermask, filtergeom); - else if((c.material&filtermask) == filtermat) - { - switch(filtergeom) - { - case EDITMATF_EMPTY: if(isempty(c)) break; return; - case EDITMATF_NOTEMPTY: if(!isempty(c)) break; return; - case EDITMATF_SOLID: if(isentirelysolid(c)) break; return; - case EDITMATF_NOTSOLID: if(!isentirelysolid(c)) break; return; - } - if(mat!=MAT_AIR) - { - c.material &= matmask; - c.material |= mat; - } - else c.material = MAT_AIR; - } + if(c.children) + loopi(8) setmat(c.children[i], mat, matmask, filtermat, filtermask, filtergeom); + else if((c.material&filtermask) == filtermat) + { + switch(filtergeom) + { + case EDITMATF_EMPTY: if(isempty(c)) break; return; + case EDITMATF_NOTEMPTY: if(!isempty(c)) break; return; + case EDITMATF_SOLID: if(isentirelysolid(c)) break; return; + case EDITMATF_NOTSOLID: if(!isentirelysolid(c)) break; return; + } + if(mat!=MAT_AIR) + { + c.material &= matmask; + c.material |= mat; + } + else c.material = MAT_AIR; + } } void mpeditmat(int matid, int filter, selinfo &sel, bool local) { - if(local) game::edittrigger(sel, EDIT_MAT, matid, filter); - - ushort filtermat = 0, filtermask = 0, matmask; - int filtergeom = 0; - if(filter >= 0) - { - filtermat = filter&0xFFFF; - filtermask = filtermat&(MATF_VOLUME|MATF_INDEX) ? (int) MATF_VOLUME|MATF_INDEX : (filtermat&MATF_CLIP ? (int) MATF_CLIP : (int) filtermat); - filtergeom = filter&~0xFFFF; - } - if(matid < 0) - { - matid = 0; - matmask = filtermask; - if(isclipped(filtermat&MATF_VOLUME)) matmask &= ~MATF_CLIP; - if(isdeadly(filtermat&MATF_VOLUME)) matmask &= ~MAT_DEATH; - } - else - { - matmask = matid&(MATF_VOLUME|MATF_INDEX) ? 0 : (matid&MATF_CLIP ? ~MATF_CLIP : ~matid); - if(isclipped(matid&MATF_VOLUME)) matid |= MAT_CLIP; - if(isdeadly(matid&MATF_VOLUME)) matid |= MAT_DEATH; - } - loopselxyz(setmat(c, matid, matmask, filtermat, filtermask, filtergeom)); + if(local) game::edittrigger(sel, EDIT_MAT, matid, filter); + + ushort filtermat = 0, filtermask = 0, matmask; + int filtergeom = 0; + if(filter >= 0) + { + filtermat = filter&0xFFFF; + filtermask = filtermat&(MATF_VOLUME|MATF_INDEX) ? (int) MATF_VOLUME|MATF_INDEX : (filtermat&MATF_CLIP ? (int) MATF_CLIP : (int) filtermat); + filtergeom = filter&~0xFFFF; + } + if(matid < 0) + { + matid = 0; + matmask = filtermask; + if(isclipped(filtermat&MATF_VOLUME)) matmask &= ~MATF_CLIP; + if(isdeadly(filtermat&MATF_VOLUME)) matmask &= ~MAT_DEATH; + } + else + { + matmask = matid&(MATF_VOLUME|MATF_INDEX) ? 0 : (matid&MATF_CLIP ? ~MATF_CLIP : ~matid); + if(isclipped(matid&MATF_VOLUME)) matid |= MAT_CLIP; + if(isdeadly(matid&MATF_VOLUME)) matid |= MAT_DEATH; + } + loopselxyz(setmat(c, matid, matmask, filtermat, filtermask, filtergeom)); } void editmat(char *name, char *filtername) { - if(noedit()) return; - int filter = -1; - if(filtername[0]) - { - loopi(sizeof(editmatfilters)/sizeof(editmatfilters[0])) if(!strcmp(editmatfilters[i].name, filtername)) { filter = editmatfilters[i].filter; break; } - if(filter < 0) filter = findmaterial(filtername); - if(filter < 0) - { - conoutf(CON_ERROR, "unknown material \"%s\"", filtername); - return; - } - } - int id = -1; - if(name[0] || filter < 0) - { - id = findmaterial(name); - if(id<0) { conoutf(CON_ERROR, "unknown material \"%s\"", name); return; } - } - mpeditmat(id, filter, sel, true); + if(noedit()) return; + int filter = -1; + if(filtername[0]) + { + loopi(sizeof(editmatfilters)/sizeof(editmatfilters[0])) if(!strcmp(editmatfilters[i].name, filtername)) { filter = editmatfilters[i].filter; break; } + if(filter < 0) filter = findmaterial(filtername); + if(filter < 0) + { + conoutf(CON_ERROR, "unknown material \"%s\"", filtername); + return; + } + } + int id = -1; + if(name[0] || filter < 0) + { + id = findmaterial(name); + if(id<0) { conoutf(CON_ERROR, "unknown material \"%s\"", name); return; } + } + mpeditmat(id, filter, sel, true); } COMMAND(editmat, "ss"); @@ -2773,106 +2772,106 @@ VAR(texguinum, 1, -1, 0); struct texturegui : g3d_callback { - bool menuon; - vec menupos; - int menustart, menutab; - - texturegui() : menustart(-1) {} - - void gui(g3d_gui &g, bool firstpass) - { - int origtab = menutab, numtabs = max((slots.length() + texguiwidth*texguiheight - 1)/(texguiwidth*texguiheight), 1); - if(!firstpass) texguinum = -1; - g.start(menustart, 0.04f, &menutab); - bool oldautotab = g.allowautotab(false); - loopi(numtabs) - { - g.tab(!i ? "Textures" : NULL, 0xFFDD88); - if(i+1 != origtab) continue; //don't load textures on non-visible tabs! - Slot *rollover = NULL; - loop(h, texguiheight) - { - g.pushlist(); - loop(w, texguiwidth) - { - extern VSlot dummyvslot; - int ti = (i*texguiheight+h)*texguiwidth+w; - if(tiset(); - } - } - else - { - g.texture(dummyvslot, texguiscale, false); //create an empty space - } - } - g.poplist(); - } - if(texguiname) - { - if(rollover) - { - defformatstring(name, "%d \f7:\fc %s", texguinum, rollover->sts[0].name); - g.title(name, 0xFFDD88); - } - else g.space(1); - } - } - g.allowautotab(oldautotab); - g.end(); - } - - void showtextures(bool on) - { - if(on == menuon) return; - if((menuon = on)) - { - if(menustart <= lasttexmillis) - menutab = 1+clamp(lookupvslot(lasttex, false).slot->index, 0, slots.length()-1)/(texguiwidth*texguiheight); - menupos = menuinfrontofplayer(); - menustart = starttime(); - } - else texguinum = -1; - } - - void show() - { - if(!menuon) return; - filltexlist(); - extern int usegui2d; - if(!editmode || ((!texgui2d || !usegui2d) && camera1->o.dist(menupos) > menuautoclose)) { menuon = false; texguinum = -1; } - else g3d_addgui(this, menupos, texgui2d ? GUI_2D : 0); - } + bool menuon; + vec menupos; + int menustart, menutab; + + texturegui() : menustart(-1) {} + + void gui(g3d_gui &g, bool firstpass) + { + int origtab = menutab, numtabs = max((slots.length() + texguiwidth*texguiheight - 1)/(texguiwidth*texguiheight), 1); + if(!firstpass) texguinum = -1; + g.start(menustart, 0.04f, &menutab); + bool oldautotab = g.allowautotab(false); + loopi(numtabs) + { + g.tab(!i ? "Textures" : NULL, 0xFFDD88); + if(i+1 != origtab) continue; //don't load textures on non-visible tabs! + Slot *rollover = NULL; + loop(h, texguiheight) + { + g.pushlist(); + loop(w, texguiwidth) + { + extern VSlot dummyvslot; + int ti = (i*texguiheight+h)*texguiwidth+w; + if(tiset(); + } + } + else + { + g.texture(dummyvslot, texguiscale, false); //create an empty space + } + } + g.poplist(); + } + if(texguiname) + { + if(rollover) + { + defformatstring(name, "%d \f7:\fc %s", texguinum, rollover->sts[0].name); + g.title(name, 0xFFDD88); + } + else g.space(1); + } + } + g.allowautotab(oldautotab); + g.end(); + } + + void showtextures(bool on) + { + if(on == menuon) return; + if((menuon = on)) + { + if(menustart <= lasttexmillis) + menutab = 1+clamp(lookupvslot(lasttex, false).slot->index, 0, slots.length()-1)/(texguiwidth*texguiheight); + menupos = menuinfrontofplayer(); + menustart = starttime(); + } + else texguinum = -1; + } + + void show() + { + if(!menuon) return; + filltexlist(); + extern int usegui2d; + if(!editmode || ((!texgui2d || !usegui2d) && camera1->o.dist(menupos) > menuautoclose)) { menuon = false; texguinum = -1; } + else g3d_addgui(this, menupos, texgui2d ? GUI_2D : 0); + } } gui; void g3d_texturemenu() { - gui.show(); + gui.show(); } void showtexgui(int *n) { - if(!editmode) { conoutf(CON_ERROR, "operation only allowed in edit mode"); return; } - gui.showtextures(*n==0 ? !gui.menuon : *n==1); + if(!editmode) { conoutf(CON_ERROR, "operation only allowed in edit mode"); return; } + gui.showtextures(*n==0 ? !gui.menuon : *n==1); } // 0/noargs = toggle, 1 = on, other = off - will autoclose if too far away or exit editmode @@ -2880,97 +2879,97 @@ COMMAND(showtexgui, "i"); bool cleartexgui() { - if(!gui.menuon) return false; - gui.showtextures(false); - return true; + if(!gui.menuon) return false; + gui.showtextures(false); + return true; } ICOMMAND(cleartexgui, "", (), intret(cleartexgui() ? 1 : 0)); void rendertexturepanel(int w, int h) { - if((texpaneltimer -= curtime)>0 && editmode) - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - pushhudmatrix(); - hudmatrix.scale(h/1800.0f, h/1800.0f, 1); - flushhudmatrix(false); - SETSHADER(hudrgb); - - int y = 50, gap = 10; - - gle::defvertex(2); - gle::deftexcoord0(); - - loopi(7) - { - int s = (i == 3 ? 285 : 220), ti = curtexindex+i-3; - if(texmru.inrange(ti)) - { - VSlot &vslot = lookupvslot(texmru[ti]), *layer = NULL; - Slot &slot = *vslot.slot; - Texture *tex = slot.sts.empty() ? notexture : slot.sts[0].t, *glowtex = NULL, *layertex = NULL; - if(slot.texmask&(1<slot->sts.empty() ? notexture : layer->slot->sts[0].t; - } - float sx = min(1.0f, tex->xs/(float)tex->ys), sy = min(1.0f, tex->ys/(float)tex->xs); - int x = w*1800/h-s-50, r = s; - vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; - float xoff = vslot.offset.x, yoff = vslot.offset.y; - if(vslot.rotation) - { - const texrotation &r = texrotations[vslot.rotation]; - if(r.swapxy) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); } - if(r.flipx) { xoff *= -1; loopk(4) tc[k].x *= -1; } - if(r.flipy) { yoff *= -1; loopk(4) tc[k].y *= -1; } - } - loopk(4) { tc[k].x = tc[k].x/sx - xoff/tex->xs; tc[k].y = tc[k].y/sy - yoff/tex->ys; } - glBindTexture(GL_TEXTURE_2D, tex->id); - loopj(glowtex ? 3 : 2) - { - if(j < 2) gle::color(vec(vslot.colorscale).mul(j), texpaneltimer/1000.0f); - else - { - glBindTexture(GL_TEXTURE_2D, glowtex->id); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - gle::color(vslot.glowcolor, texpaneltimer/1000.0f); - } - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x, y); gle::attrib(tc[0]); - gle::attribf(x+r, y); gle::attrib(tc[1]); - gle::attribf(x, y+r); gle::attrib(tc[3]); - gle::attribf(x+r, y+r); gle::attrib(tc[2]); - xtraverts += gle::end(); - if(j==1 && layertex) - { - gle::color(layer->colorscale, texpaneltimer/1000.0f); - glBindTexture(GL_TEXTURE_2D, layertex->id); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x+r/2, y+r/2); gle::attrib(tc[0]); - gle::attribf(x+r, y+r/2); gle::attrib(tc[1]); - gle::attribf(x+r/2, y+r); gle::attrib(tc[3]); - gle::attribf(x+r, y+r); gle::attrib(tc[2]); - xtraverts += gle::end(); - } - if(!j) - { - r -= 10; - x += 5; - y += 5; - } - else if(j == 2) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - } - y += s+gap; - } - - pophudmatrix(true, false); - hudshader->set(); - } + if((texpaneltimer -= curtime)>0 && editmode) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + pushhudmatrix(); + hudmatrix.scale(h/1800.0f, h/1800.0f, 1); + flushhudmatrix(false); + SETSHADER(hudrgb); + + int y = 50, gap = 10; + + gle::defvertex(2); + gle::deftexcoord0(); + + loopi(7) + { + int s = (i == 3 ? 285 : 220), ti = curtexindex+i-3; + if(texmru.inrange(ti)) + { + VSlot &vslot = lookupvslot(texmru[ti]), *layer = NULL; + Slot &slot = *vslot.slot; + Texture *tex = slot.sts.empty() ? notexture : slot.sts[0].t, *glowtex = NULL, *layertex = NULL; + if(slot.texmask&(1<slot->sts.empty() ? notexture : layer->slot->sts[0].t; + } + float sx = min(1.0f, tex->xs/(float)tex->ys), sy = min(1.0f, tex->ys/(float)tex->xs); + int x = w*1800/h-s-50, r = s; + vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; + float xoff = vslot.offset.x, yoff = vslot.offset.y; + if(vslot.rotation) + { + const texrotation &r = texrotations[vslot.rotation]; + if(r.swapxy) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); } + if(r.flipx) { xoff *= -1; loopk(4) tc[k].x *= -1; } + if(r.flipy) { yoff *= -1; loopk(4) tc[k].y *= -1; } + } + loopk(4) { tc[k].x = tc[k].x/sx - xoff/tex->xs; tc[k].y = tc[k].y/sy - yoff/tex->ys; } + glBindTexture(GL_TEXTURE_2D, tex->id); + loopj(glowtex ? 3 : 2) + { + if(j < 2) gle::color(vec(vslot.colorscale).mul(j), texpaneltimer/1000.0f); + else + { + glBindTexture(GL_TEXTURE_2D, glowtex->id); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + gle::color(vslot.glowcolor, texpaneltimer/1000.0f); + } + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x, y); gle::attrib(tc[0]); + gle::attribf(x+r, y); gle::attrib(tc[1]); + gle::attribf(x, y+r); gle::attrib(tc[3]); + gle::attribf(x+r, y+r); gle::attrib(tc[2]); + xtraverts += gle::end(); + if(j==1 && layertex) + { + gle::color(layer->colorscale, texpaneltimer/1000.0f); + glBindTexture(GL_TEXTURE_2D, layertex->id); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x+r/2, y+r/2); gle::attrib(tc[0]); + gle::attribf(x+r, y+r/2); gle::attrib(tc[1]); + gle::attribf(x+r/2, y+r); gle::attrib(tc[3]); + gle::attribf(x+r, y+r); gle::attrib(tc[2]); + xtraverts += gle::end(); + } + if(!j) + { + r -= 10; + x += 5; + y += 5; + } + else if(j == 2) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + y += s+gap; + } + + pophudmatrix(true, false); + hudshader->set(); + } } diff --git a/src/engine/octarender.cpp b/src/engine/octarender.cpp index 217a49a..4605e1a 100644 --- a/src/engine/octarender.cpp +++ b/src/engine/octarender.cpp @@ -4,7 +4,7 @@ struct vboinfo { - int uses; + int uses; }; hashtable vbos; @@ -14,10 +14,10 @@ VARFN(vbosize, maxvbosize, 0, 1<<14, 1<<16, allchanged()); enum { - VBO_VBUF = 0, - VBO_EBUF, - VBO_SKYBUF, - NUMVBO + VBO_VBUF = 0, + VBO_EBUF, + VBO_SKYBUF, + NUMVBO }; static vector vbodata[NUMVBO]; @@ -26,486 +26,486 @@ static int vbosize[NUMVBO]; void destroyvbo(GLuint vbo) { - vboinfo *exists = vbos.access(vbo); - if(!exists) return; - vboinfo &vbi = *exists; - if(vbi.uses <= 0) return; - vbi.uses--; - if(!vbi.uses) - { - glDeleteBuffers_(1, &vbo); - vbos.remove(vbo); - } + vboinfo *exists = vbos.access(vbo); + if(!exists) return; + vboinfo &vbi = *exists; + if(vbi.uses <= 0) return; + vbi.uses--; + if(!vbi.uses) + { + glDeleteBuffers_(1, &vbo); + vbos.remove(vbo); + } } void genvbo(int type, void *buf, int len, vtxarray **vas, int numva) { - gle::disable(); - - GLuint vbo; - glGenBuffers_(1, &vbo); - GLenum target = type==VBO_VBUF ? GL_ARRAY_BUFFER : GL_ELEMENT_ARRAY_BUFFER; - glBindBuffer_(target, vbo); - glBufferData_(target, len, buf, GL_STATIC_DRAW); - glBindBuffer_(target, 0); - - vboinfo &vbi = vbos[vbo]; - vbi.uses = numva; - - if(printvbo) conoutf(CON_DEBUG, "vbo %d: type %d, size %d, %d uses", vbo, type, len, numva); - - loopi(numva) - { - vtxarray *va = vas[i]; - switch(type) - { - case VBO_VBUF: - va->vbuf = vbo; - break; - case VBO_EBUF: - va->ebuf = vbo; - break; - case VBO_SKYBUF: - va->skybuf = vbo; - break; - } - } + gle::disable(); + + GLuint vbo; + glGenBuffers_(1, &vbo); + GLenum target = type==VBO_VBUF ? GL_ARRAY_BUFFER : GL_ELEMENT_ARRAY_BUFFER; + glBindBuffer_(target, vbo); + glBufferData_(target, len, buf, GL_STATIC_DRAW); + glBindBuffer_(target, 0); + + vboinfo &vbi = vbos[vbo]; + vbi.uses = numva; + + if(printvbo) conoutf(CON_DEBUG, "vbo %d: type %d, size %d, %d uses", vbo, type, len, numva); + + loopi(numva) + { + vtxarray *va = vas[i]; + switch(type) + { + case VBO_VBUF: + va->vbuf = vbo; + break; + case VBO_EBUF: + va->ebuf = vbo; + break; + case VBO_SKYBUF: + va->skybuf = vbo; + break; + } + } } bool readva(vtxarray *va, ushort *&edata, vertex *&vdata) { - if(!va->vbuf || !va->ebuf) return false; + if(!va->vbuf || !va->ebuf) return false; - edata = new ushort[3*va->tris]; - vdata = new vertex[va->verts]; + edata = new ushort[3*va->tris]; + vdata = new vertex[va->verts]; - gle::bindebo(va->ebuf); - glGetBufferSubData_(GL_ELEMENT_ARRAY_BUFFER, (size_t)va->edata, 3*va->tris*sizeof(ushort), edata); - gle::clearebo(); + gle::bindebo(va->ebuf); + glGetBufferSubData_(GL_ELEMENT_ARRAY_BUFFER, (size_t)va->edata, 3*va->tris*sizeof(ushort), edata); + gle::clearebo(); - gle::bindvbo(va->vbuf); - glGetBufferSubData_(GL_ARRAY_BUFFER, va->voffset*sizeof(vertex), va->verts*sizeof(vertex), vdata); - gle::clearvbo(); - return true; + gle::bindvbo(va->vbuf); + glGetBufferSubData_(GL_ARRAY_BUFFER, va->voffset*sizeof(vertex), va->verts*sizeof(vertex), vdata); + gle::clearvbo(); + return true; } void flushvbo(int type = -1) { - if(type < 0) - { - loopi(NUMVBO) flushvbo(i); - return; - } - - vector &data = vbodata[type]; - if(data.empty()) return; - vector &vas = vbovas[type]; - genvbo(type, data.getbuf(), data.length(), vas.getbuf(), vas.length()); - data.setsize(0); - vas.setsize(0); - vbosize[type] = 0; + if(type < 0) + { + loopi(NUMVBO) flushvbo(i); + return; + } + + vector &data = vbodata[type]; + if(data.empty()) return; + vector &vas = vbovas[type]; + genvbo(type, data.getbuf(), data.length(), vas.getbuf(), vas.length()); + data.setsize(0); + vas.setsize(0); + vbosize[type] = 0; } uchar *addvbo(vtxarray *va, int type, int numelems, int elemsize) { - vbosize[type] += numelems; + vbosize[type] += numelems; - vector &data = vbodata[type]; - vector &vas = vbovas[type]; + vector &data = vbodata[type]; + vector &vas = vbovas[type]; - vas.add(va); + vas.add(va); - int len = numelems*elemsize; - uchar *buf = data.reserve(len).buf; - data.advance(len); - return buf; + int len = numelems*elemsize; + uchar *buf = data.reserve(len).buf; + data.advance(len); + return buf; } struct verthash { - static const int SIZE = 1<<13; - int table[SIZE]; - vector verts; - vector chain; - - verthash() { clearverts(); } - - void clearverts() - { - memset(table, -1, sizeof(table)); - chain.setsize(0); - verts.setsize(0); - } - - int addvert(const vertex &v) - { - uint h = hthash(v.pos)&(SIZE-1); - for(int i = table[h]; i>=0; i = chain[i]) - { - const vertex &c = verts[i]; - if(c.pos==v.pos && c.tc==v.tc && c.norm==v.norm && c.tangent==v.tangent && (v.lm.iszero() || c.lm==v.lm)) - return i; - } - if(verts.length() >= USHRT_MAX) return -1; - verts.add(v); - chain.add(table[h]); - return table[h] = verts.length()-1; - } - - int addvert(const vec &pos, const vec2 &tc = vec2(0, 0), const svec2 &lm = svec2(0, 0), const bvec &norm = bvec(128, 128, 128), const bvec4 &tangent = bvec4(128, 128, 128, 128)) - { - vertex vtx; - vtx.pos = pos; - vtx.tc = tc; - vtx.lm = lm; - vtx.norm = norm; - vtx.tangent = tangent; - return addvert(vtx); - } + static const int SIZE = 1<<13; + int table[SIZE]; + vector verts; + vector chain; + + verthash() { clearverts(); } + + void clearverts() + { + memset(table, -1, sizeof(table)); + chain.setsize(0); + verts.setsize(0); + } + + int addvert(const vertex &v) + { + uint h = hthash(v.pos)&(SIZE-1); + for(int i = table[h]; i>=0; i = chain[i]) + { + const vertex &c = verts[i]; + if(c.pos==v.pos && c.tc==v.tc && c.norm==v.norm && c.tangent==v.tangent && (v.lm.iszero() || c.lm==v.lm)) + return i; + } + if(verts.length() >= USHRT_MAX) return -1; + verts.add(v); + chain.add(table[h]); + return table[h] = verts.length()-1; + } + + int addvert(const vec &pos, const vec2 &tc = vec2(0, 0), const svec2 &lm = svec2(0, 0), const bvec &norm = bvec(128, 128, 128), const bvec4 &tangent = bvec4(128, 128, 128, 128)) + { + vertex vtx; + vtx.pos = pos; + vtx.tc = tc; + vtx.lm = lm; + vtx.norm = norm; + vtx.tangent = tangent; + return addvert(vtx); + } }; enum { - NO_ALPHA = 0, - ALPHA_BACK, - ALPHA_FRONT + NO_ALPHA = 0, + ALPHA_BACK, + ALPHA_FRONT }; struct sortkey { - ushort tex, lmid, envmap; - uchar dim, layer, alpha; + ushort tex, lmid, envmap; + uchar dim, layer, alpha; - sortkey() {} - sortkey(ushort tex, ushort lmid, uchar dim, uchar layer = LAYER_TOP, ushort envmap = EMID_NONE, uchar alpha = NO_ALPHA) - : tex(tex), lmid(lmid), envmap(envmap), dim(dim), layer(layer), alpha(alpha) - {} + sortkey() {} + sortkey(ushort tex, ushort lmid, uchar dim, uchar layer = LAYER_TOP, ushort envmap = EMID_NONE, uchar alpha = NO_ALPHA) + : tex(tex), lmid(lmid), envmap(envmap), dim(dim), layer(layer), alpha(alpha) + {} - bool operator==(const sortkey &o) const { return tex==o.tex && lmid==o.lmid && envmap==o.envmap && dim==o.dim && layer==o.layer && alpha==o.alpha; } + bool operator==(const sortkey &o) const { return tex==o.tex && lmid==o.lmid && envmap==o.envmap && dim==o.dim && layer==o.layer && alpha==o.alpha; } }; struct sortval { - int unlit; - vector tris[2]; + int unlit; + vector tris[2]; - sortval() : unlit(0) {} + sortval() : unlit(0) {} }; static inline bool htcmp(const sortkey &x, const sortkey &y) { - return x == y; + return x == y; } static inline uint hthash(const sortkey &k) { - return k.tex + k.lmid*9741; + return k.tex + k.lmid*9741; } struct vacollect : verthash { - ivec origin; - int size; - hashtable indices; - vector texs; - vector matsurfs; - vector mapmodels; - vector skyindices, explicitskyindices; - vector skyfaces[6]; - int worldtris, skytris, skymask, skyclip, skyarea; - - void clear() - { - clearverts(); - worldtris = skytris = 0; - skymask = 0; - skyclip = INT_MAX; - skyarea = 0; - indices.clear(); - skyindices.setsize(0); - explicitskyindices.setsize(0); - matsurfs.setsize(0); - mapmodels.setsize(0); - texs.setsize(0); - loopi(6) skyfaces[i].setsize(0); - } - - void remapunlit(vector &remap) - { - uint lastlmid[8] = { LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT }, - firstlmid[8] = { LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT }; - int firstlit[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; - loopv(texs) - { - sortkey &k = texs[i]; - if(k.lmid>=LMID_RESERVED) - { - LightMapTexture &lmtex = lightmaptexs[k.lmid]; - int type = lmtex.type&LM_TYPE; - if(k.layer==LAYER_BLEND) type += 2; - else if(k.alpha) type += 4 + 2*(k.alpha-1); - lastlmid[type] = lmtex.unlitx>=0 ? (int) k.lmid : (int) LMID_AMBIENT; - if(firstlmid[type]==LMID_AMBIENT && lastlmid[type]!=LMID_AMBIENT) - { - firstlit[type] = i; - firstlmid[type] = lastlmid[type]; - } - } - else if(k.lmid==LMID_AMBIENT) - { - Shader *s = lookupvslot(k.tex, false).slot->shader; - int type = s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE; - if(k.layer==LAYER_BLEND) type += 2; - else if(k.alpha) type += 4 + 2*(k.alpha-1); - if(lastlmid[type]!=LMID_AMBIENT) - { - sortval &t = indices[k]; - if(t.unlit<=0) t.unlit = lastlmid[type]; - } - } - } - loopj(2) - { - int offset = 2*j; - if(firstlmid[offset]==LMID_AMBIENT && firstlmid[offset+1]==LMID_AMBIENT) continue; - loopi(max(firstlit[offset], firstlit[offset+1])) - { - sortkey &k = texs[i]; - if((j ? k.layer!=LAYER_BLEND : k.layer==LAYER_BLEND) || k.alpha) continue; - if(k.lmid!=LMID_AMBIENT) continue; - Shader *s = lookupvslot(k.tex, false).slot->shader; - int type = offset + (s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE); - if(firstlmid[type]==LMID_AMBIENT) continue; - indices[k].unlit = firstlmid[type]; - } - } - loopj(2) - { - int offset = 4 + 2*j; - if(firstlmid[offset]==LMID_AMBIENT && firstlmid[offset+1]==LMID_AMBIENT) continue; - loopi(max(firstlit[offset], firstlit[offset+1])) - { - sortkey &k = texs[i]; - if(k.alpha != j+1) continue; - if(k.lmid!=LMID_AMBIENT) continue; - Shader *s = lookupvslot(k.tex, false).slot->shader; - int type = offset + (s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE); - if(firstlmid[type]==LMID_AMBIENT) continue; - indices[k].unlit = firstlmid[type]; - } - } - loopv(remap) - { - sortkey &k = remap[i]; - sortval &t = indices[k]; - if(t.unlit<=0) continue; - LightMapTexture &lm = lightmaptexs[t.unlit]; - svec2 lmtc(short(ceil((lm.unlitx + 0.5f) * SHRT_MAX/lm.w)), - short(ceil((lm.unlity + 0.5f) * SHRT_MAX/lm.h))); - loopl(2) loopvj(t.tris[l]) - { - vertex &vtx = verts[t.tris[l][j]]; - if(vtx.lm.iszero()) vtx.lm = lmtc; - else if(vtx.lm != lmtc) - { - vertex vtx2 = vtx; - vtx2.lm = lmtc; - t.tris[l][j] = addvert(vtx2); - } - } - sortval *dst = indices.access(sortkey(k.tex, t.unlit, k.dim, k.layer, k.envmap, k.alpha)); - if(dst) loopl(2) loopvj(t.tris[l]) dst->tris[l].add(t.tris[l][j]); - } - } - - void optimize() - { - vector remap; - enumeratekt(indices, sortkey, k, sortval, t, - loopl(2) if(t.tris[l].length() && t.unlit<=0) - { - if(k.lmid>=LMID_RESERVED && lightmaptexs[k.lmid].unlitx>=0) - { - sortkey ukey(k.tex, LMID_AMBIENT, k.dim, k.layer, k.envmap, k.alpha); - sortval *uval = indices.access(ukey); - if(uval && uval->unlit<=0) - { - if(uval->unlit<0) texs.removeobj(ukey); - else remap.add(ukey); - uval->unlit = k.lmid; - } - } - else if(k.lmid==LMID_AMBIENT) - { - remap.add(k); - t.unlit = -1; - } - texs.add(k); - break; - } - ); - texs.sort(texsort); - - remapunlit(remap); - - matsurfs.shrink(optimizematsurfs(matsurfs.getbuf(), matsurfs.length())); - } - - static inline bool texsort(const sortkey &x, const sortkey &y) - { - if(x.alpha < y.alpha) return true; - if(x.alpha > y.alpha) return false; - if(x.layer < y.layer) return true; - if(x.layer > y.layer) return false; - if(x.tex == y.tex) - { - if(x.lmid < y.lmid) return true; - if(x.lmid > y.lmid) return false; - if(x.envmap < y.envmap) return true; - if(x.envmap > y.envmap) return false; - if(x.dim < y.dim) return true; - if(x.dim > y.dim) return false; - return false; - } - VSlot &xs = lookupvslot(x.tex, false), &ys = lookupvslot(y.tex, false); - if(xs.slot->shader < ys.slot->shader) return true; - if(xs.slot->shader > ys.slot->shader) return false; - if(xs.slot->params.length() < ys.slot->params.length()) return true; - if(xs.slot->params.length() > ys.slot->params.length()) return false; - if(x.tex < y.tex) return true; - else return false; - } + ivec origin; + int size; + hashtable indices; + vector texs; + vector matsurfs; + vector mapmodels; + vector skyindices, explicitskyindices; + vector skyfaces[6]; + int worldtris, skytris, skymask, skyclip, skyarea; + + void clear() + { + clearverts(); + worldtris = skytris = 0; + skymask = 0; + skyclip = INT_MAX; + skyarea = 0; + indices.clear(); + skyindices.setsize(0); + explicitskyindices.setsize(0); + matsurfs.setsize(0); + mapmodels.setsize(0); + texs.setsize(0); + loopi(6) skyfaces[i].setsize(0); + } + + void remapunlit(vector &remap) + { + uint lastlmid[8] = { LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT }, + firstlmid[8] = { LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT }; + int firstlit[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; + loopv(texs) + { + sortkey &k = texs[i]; + if(k.lmid>=LMID_RESERVED) + { + LightMapTexture &lmtex = lightmaptexs[k.lmid]; + int type = lmtex.type&LM_TYPE; + if(k.layer==LAYER_BLEND) type += 2; + else if(k.alpha) type += 4 + 2*(k.alpha-1); + lastlmid[type] = lmtex.unlitx>=0 ? (int) k.lmid : (int) LMID_AMBIENT; + if(firstlmid[type]==LMID_AMBIENT && lastlmid[type]!=LMID_AMBIENT) + { + firstlit[type] = i; + firstlmid[type] = lastlmid[type]; + } + } + else if(k.lmid==LMID_AMBIENT) + { + Shader *s = lookupvslot(k.tex, false).slot->shader; + int type = s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE; + if(k.layer==LAYER_BLEND) type += 2; + else if(k.alpha) type += 4 + 2*(k.alpha-1); + if(lastlmid[type]!=LMID_AMBIENT) + { + sortval &t = indices[k]; + if(t.unlit<=0) t.unlit = lastlmid[type]; + } + } + } + loopj(2) + { + int offset = 2*j; + if(firstlmid[offset]==LMID_AMBIENT && firstlmid[offset+1]==LMID_AMBIENT) continue; + loopi(max(firstlit[offset], firstlit[offset+1])) + { + sortkey &k = texs[i]; + if((j ? k.layer!=LAYER_BLEND : k.layer==LAYER_BLEND) || k.alpha) continue; + if(k.lmid!=LMID_AMBIENT) continue; + Shader *s = lookupvslot(k.tex, false).slot->shader; + int type = offset + (s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE); + if(firstlmid[type]==LMID_AMBIENT) continue; + indices[k].unlit = firstlmid[type]; + } + } + loopj(2) + { + int offset = 4 + 2*j; + if(firstlmid[offset]==LMID_AMBIENT && firstlmid[offset+1]==LMID_AMBIENT) continue; + loopi(max(firstlit[offset], firstlit[offset+1])) + { + sortkey &k = texs[i]; + if(k.alpha != j+1) continue; + if(k.lmid!=LMID_AMBIENT) continue; + Shader *s = lookupvslot(k.tex, false).slot->shader; + int type = offset + (s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE); + if(firstlmid[type]==LMID_AMBIENT) continue; + indices[k].unlit = firstlmid[type]; + } + } + loopv(remap) + { + sortkey &k = remap[i]; + sortval &t = indices[k]; + if(t.unlit<=0) continue; + LightMapTexture &lm = lightmaptexs[t.unlit]; + svec2 lmtc(short(ceil((lm.unlitx + 0.5f) * SHRT_MAX/lm.w)), + short(ceil((lm.unlity + 0.5f) * SHRT_MAX/lm.h))); + loopl(2) loopvj(t.tris[l]) + { + vertex &vtx = verts[t.tris[l][j]]; + if(vtx.lm.iszero()) vtx.lm = lmtc; + else if(vtx.lm != lmtc) + { + vertex vtx2 = vtx; + vtx2.lm = lmtc; + t.tris[l][j] = addvert(vtx2); + } + } + sortval *dst = indices.access(sortkey(k.tex, t.unlit, k.dim, k.layer, k.envmap, k.alpha)); + if(dst) loopl(2) loopvj(t.tris[l]) dst->tris[l].add(t.tris[l][j]); + } + } + + void optimize() + { + vector remap; + enumeratekt(indices, sortkey, k, sortval, t, + loopl(2) if(t.tris[l].length() && t.unlit<=0) + { + if(k.lmid>=LMID_RESERVED && lightmaptexs[k.lmid].unlitx>=0) + { + sortkey ukey(k.tex, LMID_AMBIENT, k.dim, k.layer, k.envmap, k.alpha); + sortval *uval = indices.access(ukey); + if(uval && uval->unlit<=0) + { + if(uval->unlit<0) texs.removeobj(ukey); + else remap.add(ukey); + uval->unlit = k.lmid; + } + } + else if(k.lmid==LMID_AMBIENT) + { + remap.add(k); + t.unlit = -1; + } + texs.add(k); + break; + } + ); + texs.sort(texsort); + + remapunlit(remap); + + matsurfs.shrink(optimizematsurfs(matsurfs.getbuf(), matsurfs.length())); + } + + static inline bool texsort(const sortkey &x, const sortkey &y) + { + if(x.alpha < y.alpha) return true; + if(x.alpha > y.alpha) return false; + if(x.layer < y.layer) return true; + if(x.layer > y.layer) return false; + if(x.tex == y.tex) + { + if(x.lmid < y.lmid) return true; + if(x.lmid > y.lmid) return false; + if(x.envmap < y.envmap) return true; + if(x.envmap > y.envmap) return false; + if(x.dim < y.dim) return true; + if(x.dim > y.dim) return false; + return false; + } + VSlot &xs = lookupvslot(x.tex, false), &ys = lookupvslot(y.tex, false); + if(xs.slot->shader < ys.slot->shader) return true; + if(xs.slot->shader > ys.slot->shader) return false; + if(xs.slot->params.length() < ys.slot->params.length()) return true; + if(xs.slot->params.length() > ys.slot->params.length()) return false; + if(x.tex < y.tex) return true; + else return false; + } #define GENVERTS(type, ptr, body) do \ - { \ - type *f = (type *)ptr; \ - loopv(verts) \ - { \ - const vertex &v = verts[i]; \ - body; \ - f++; \ - } \ - } while(0) - - void genverts(void *buf) - { - GENVERTS(vertex, buf, { *f = v; f->norm.flip(); f->tangent.flip(); }); - } - - void setupdata(vtxarray *va) - { - va->verts = verts.length(); - va->tris = worldtris/3; - va->vbuf = 0; - va->vdata = 0; - va->minvert = 0; - va->maxvert = va->verts-1; - va->voffset = 0; - if(va->verts) - { - if(vbosize[VBO_VBUF] + verts.length() > maxvbosize || - vbosize[VBO_EBUF] + worldtris > USHRT_MAX || - vbosize[VBO_SKYBUF] + skytris > USHRT_MAX) - flushvbo(); - - va->voffset = vbosize[VBO_VBUF]; - uchar *vdata = addvbo(va, VBO_VBUF, va->verts, sizeof(vertex)); - genverts(vdata); - va->minvert += va->voffset; - va->maxvert += va->voffset; - } - - va->matbuf = NULL; - va->matsurfs = matsurfs.length(); - if(va->matsurfs) - { - va->matbuf = new materialsurface[matsurfs.length()]; - memcpy(va->matbuf, matsurfs.getbuf(), matsurfs.length()*sizeof(materialsurface)); - } - - va->skybuf = 0; - va->skydata = 0; - va->sky = skyindices.length(); - va->explicitsky = explicitskyindices.length(); - if(va->sky + va->explicitsky) - { - va->skydata += vbosize[VBO_SKYBUF]; - ushort *skydata = (ushort *)addvbo(va, VBO_SKYBUF, va->sky+va->explicitsky, sizeof(ushort)); - memcpy(skydata, skyindices.getbuf(), va->sky*sizeof(ushort)); - memcpy(skydata+va->sky, explicitskyindices.getbuf(), va->explicitsky*sizeof(ushort)); - if(va->voffset) loopi(va->sky+va->explicitsky) skydata[i] += va->voffset; - } - - va->eslist = NULL; - va->texs = texs.length(); - va->blendtris = 0; - va->blends = 0; - va->alphabacktris = 0; - va->alphaback = 0; - va->alphafronttris = 0; - va->alphafront = 0; - va->ebuf = 0; - va->edata = 0; - va->texmask = 0; - if(va->texs) - { - va->eslist = new elementset[va->texs]; - va->edata += vbosize[VBO_EBUF]; - ushort *edata = (ushort *)addvbo(va, VBO_EBUF, worldtris, sizeof(ushort)), *curbuf = edata; - loopv(texs) - { - const sortkey &k = texs[i]; - const sortval &t = indices[k]; - elementset &e = va->eslist[i]; - e.texture = k.tex; - e.lmid = t.unlit>0 ? t.unlit : k.lmid; - e.dim = k.dim; - e.layer = k.layer; - e.envmap = k.envmap; - ushort *startbuf = curbuf; - loopl(2) - { - e.minvert[l] = USHRT_MAX; - e.maxvert[l] = 0; - - if(t.tris[l].length()) - { - memcpy(curbuf, t.tris[l].getbuf(), t.tris[l].length() * sizeof(ushort)); - - loopvj(t.tris[l]) - { - curbuf[j] += va->voffset; - e.minvert[l] = min(e.minvert[l], curbuf[j]); - e.maxvert[l] = max(e.maxvert[l], curbuf[j]); - } - - curbuf += t.tris[l].length(); - } - e.length[l] = curbuf-startbuf; - } - if(k.layer==LAYER_BLEND) { va->texs--; va->tris -= e.length[1]/3; va->blends++; va->blendtris += e.length[1]/3; } - else if(k.alpha==ALPHA_BACK) { va->texs--; va->tris -= e.length[1]/3; va->alphaback++; va->alphabacktris += e.length[1]/3; } - else if(k.alpha==ALPHA_FRONT) { va->texs--; va->tris -= e.length[1]/3; va->alphafront++; va->alphafronttris += e.length[1]/3; } - - Slot &slot = *lookupvslot(k.tex, false).slot; - loopvj(slot.sts) va->texmask |= 1<type&SHADER_ENVMAP) va->texmask |= 1<alphatris = va->alphabacktris + va->alphafronttris; - - if(mapmodels.length()) va->mapmodels.put(mapmodels.getbuf(), mapmodels.length()); - } - - bool emptyva() - { - return verts.empty() && matsurfs.empty() && skyindices.empty() && explicitskyindices.empty() && mapmodels.empty(); - } + { \ + type *f = (type *)ptr; \ + loopv(verts) \ + { \ + const vertex &v = verts[i]; \ + body; \ + f++; \ + } \ + } while(0) + + void genverts(void *buf) + { + GENVERTS(vertex, buf, { *f = v; f->norm.flip(); f->tangent.flip(); }); + } + + void setupdata(vtxarray *va) + { + va->verts = verts.length(); + va->tris = worldtris/3; + va->vbuf = 0; + va->vdata = 0; + va->minvert = 0; + va->maxvert = va->verts-1; + va->voffset = 0; + if(va->verts) + { + if(vbosize[VBO_VBUF] + verts.length() > maxvbosize || + vbosize[VBO_EBUF] + worldtris > USHRT_MAX || + vbosize[VBO_SKYBUF] + skytris > USHRT_MAX) + flushvbo(); + + va->voffset = vbosize[VBO_VBUF]; + uchar *vdata = addvbo(va, VBO_VBUF, va->verts, sizeof(vertex)); + genverts(vdata); + va->minvert += va->voffset; + va->maxvert += va->voffset; + } + + va->matbuf = NULL; + va->matsurfs = matsurfs.length(); + if(va->matsurfs) + { + va->matbuf = new materialsurface[matsurfs.length()]; + memcpy(va->matbuf, matsurfs.getbuf(), matsurfs.length()*sizeof(materialsurface)); + } + + va->skybuf = 0; + va->skydata = 0; + va->sky = skyindices.length(); + va->explicitsky = explicitskyindices.length(); + if(va->sky + va->explicitsky) + { + va->skydata += vbosize[VBO_SKYBUF]; + ushort *skydata = (ushort *)addvbo(va, VBO_SKYBUF, va->sky+va->explicitsky, sizeof(ushort)); + memcpy(skydata, skyindices.getbuf(), va->sky*sizeof(ushort)); + memcpy(skydata+va->sky, explicitskyindices.getbuf(), va->explicitsky*sizeof(ushort)); + if(va->voffset) loopi(va->sky+va->explicitsky) skydata[i] += va->voffset; + } + + va->eslist = NULL; + va->texs = texs.length(); + va->blendtris = 0; + va->blends = 0; + va->alphabacktris = 0; + va->alphaback = 0; + va->alphafronttris = 0; + va->alphafront = 0; + va->ebuf = 0; + va->edata = 0; + va->texmask = 0; + if(va->texs) + { + va->eslist = new elementset[va->texs]; + va->edata += vbosize[VBO_EBUF]; + ushort *edata = (ushort *)addvbo(va, VBO_EBUF, worldtris, sizeof(ushort)), *curbuf = edata; + loopv(texs) + { + const sortkey &k = texs[i]; + const sortval &t = indices[k]; + elementset &e = va->eslist[i]; + e.texture = k.tex; + e.lmid = t.unlit>0 ? t.unlit : k.lmid; + e.dim = k.dim; + e.layer = k.layer; + e.envmap = k.envmap; + ushort *startbuf = curbuf; + loopl(2) + { + e.minvert[l] = USHRT_MAX; + e.maxvert[l] = 0; + + if(t.tris[l].length()) + { + memcpy(curbuf, t.tris[l].getbuf(), t.tris[l].length() * sizeof(ushort)); + + loopvj(t.tris[l]) + { + curbuf[j] += va->voffset; + e.minvert[l] = min(e.minvert[l], curbuf[j]); + e.maxvert[l] = max(e.maxvert[l], curbuf[j]); + } + + curbuf += t.tris[l].length(); + } + e.length[l] = curbuf-startbuf; + } + if(k.layer==LAYER_BLEND) { va->texs--; va->tris -= e.length[1]/3; va->blends++; va->blendtris += e.length[1]/3; } + else if(k.alpha==ALPHA_BACK) { va->texs--; va->tris -= e.length[1]/3; va->alphaback++; va->alphabacktris += e.length[1]/3; } + else if(k.alpha==ALPHA_FRONT) { va->texs--; va->tris -= e.length[1]/3; va->alphafront++; va->alphafronttris += e.length[1]/3; } + + Slot &slot = *lookupvslot(k.tex, false).slot; + loopvj(slot.sts) va->texmask |= 1<type&SHADER_ENVMAP) va->texmask |= 1<alphatris = va->alphabacktris + va->alphafronttris; + + if(mapmodels.length()) va->mapmodels.put(mapmodels.getbuf(), mapmodels.length()); + } + + bool emptyva() + { + return verts.empty() && matsurfs.empty() && skyindices.empty() && explicitskyindices.empty() && mapmodels.empty(); + } } vc; int recalcprogress = 0; -#define progress(s) if((recalcprogress++&0xFFF)==0) renderprogress(recalcprogress/(float)allocnodes, s); +#define progress(s) if((recalcprogress++&0xFFF)==0) renderprogress(recalcprogress/(float)allocnodes, s); vector tjoints; @@ -513,323 +513,323 @@ vec shadowmapmin, shadowmapmax; int calcshadowmask(vec *pos, int numpos) { - extern vec shadowdir; - int mask = 0, used = 1; - vec pe = vec(pos[1]).sub(pos[0]); - loopk(numpos-2) - { - vec e = vec(pos[k+2]).sub(pos[0]); - if(vec().cross(pe, e).dot(shadowdir)>0) - { - mask |= 1<0) + { + mask |= 1< &idxs = key.tex==DEFAULT_SKY ? vc.explicitskyindices : vc.indices[key].tris[(shadowmask>>i)&1]; - int left = index[0], mid = index[i+1], right = index[i+2], start = left, i0 = left, i1 = -1; - loopk(4) - { - int i2 = -1, ctj = -1, cedge = -1; - switch(k) - { - case 1: i1 = i2 = mid; cedge = edge+i+1; break; - case 2: if(i1 != mid || i0 == left) { i0 = i1; i1 = right; } i2 = right; if(i+1 == numverts-2) cedge = edge+i+2; break; - case 3: if(i0 == start) { i0 = i1; i1 = left; } i2 = left; // fall-through - default: if(!i) cedge = edge; break; - } - if(i1 != i2) - { - if(total + 3 > USHRT_MAX) return; - total += 3; - idxs.add(i0); - idxs.add(i1); - idxs.add(i2); - i1 = i2; - } - if(cedge >= 0) - { - for(ctj = tj;;) - { - if(ctj < 0) break; - if(tjoints[ctj].edge < cedge) { ctj = tjoints[ctj].next; continue; } - if(tjoints[ctj].edge != cedge) ctj = -1; - break; - } - } - if(ctj >= 0) - { - int e1 = cedge%(MAXFACEVERTS+1), e2 = (e1+1)%numverts; - vertex &v1 = verts[e1], &v2 = verts[e2]; - ivec d(vec(v2.pos).sub(v1.pos).mul(8)); - int axis = abs(d.x) > abs(d.y) ? (abs(d.x) > abs(d.z) ? 0 : 2) : (abs(d.y) > abs(d.z) ? 1 : 2); - if(d[axis] < 0) d.neg(); - reduceslope(d); - int origin = int(min(v1.pos[axis], v2.pos[axis])*8)&~0x7FFF, - offset1 = (int(v1.pos[axis]*8) - origin) / d[axis], - offset2 = (int(v2.pos[axis]*8) - origin) / d[axis]; - vec o = vec(v1.pos).sub(vec(d).mul(offset1/8.0f)); - float doffset = 1.0f / (offset2 - offset1); - - if(i1 < 0) for(;;) - { - tjoint &t = tjoints[ctj]; - if(t.next < 0 || tjoints[t.next].edge != cedge) break; - ctj = t.next; - } - while(ctj >= 0) - { - tjoint &t = tjoints[ctj]; - if(t.edge != cedge) break; - float offset = (t.offset - offset1) * doffset; - vertex vt; - vt.pos = vec(d).mul(t.offset/8.0f).add(o); - vt.tc.lerp(v1.tc, v2.tc, offset); - vt.lm.x = short(v1.lm.x + (v2.lm.x-v1.lm.x)*offset), - vt.lm.y = short(v1.lm.y + (v2.lm.y-v1.lm.y)*offset); - vt.norm.lerp(v1.norm, v2.norm, offset); - vt.tangent.lerp(v1.tangent, v2.tangent, offset); - int i2 = vc.addvert(vt); - if(i2 < 0) return; - if(i1 >= 0) - { - if(total + 3 > USHRT_MAX) return; - total += 3; - idxs.add(i0); - idxs.add(i1); - idxs.add(i2); - i1 = i2; - } - else start = i0 = i2; - ctj = t.next; - } - } - } - } + int &total = key.tex==DEFAULT_SKY ? vc.skytris : vc.worldtris; + int edge = orient*(MAXFACEVERTS+1); + loopi(numverts-2) if(index[0]!=index[i+1] && index[i+1]!=index[i+2] && index[i+2]!=index[0]) + { + vector &idxs = key.tex==DEFAULT_SKY ? vc.explicitskyindices : vc.indices[key].tris[(shadowmask>>i)&1]; + int left = index[0], mid = index[i+1], right = index[i+2], start = left, i0 = left, i1 = -1; + loopk(4) + { + int i2 = -1, ctj = -1, cedge = -1; + switch(k) + { + case 1: i1 = i2 = mid; cedge = edge+i+1; break; + case 2: if(i1 != mid || i0 == left) { i0 = i1; i1 = right; } i2 = right; if(i+1 == numverts-2) cedge = edge+i+2; break; + case 3: if(i0 == start) { i0 = i1; i1 = left; } i2 = left; // fall-through + default: if(!i) cedge = edge; break; + } + if(i1 != i2) + { + if(total + 3 > USHRT_MAX) return; + total += 3; + idxs.add(i0); + idxs.add(i1); + idxs.add(i2); + i1 = i2; + } + if(cedge >= 0) + { + for(ctj = tj;;) + { + if(ctj < 0) break; + if(tjoints[ctj].edge < cedge) { ctj = tjoints[ctj].next; continue; } + if(tjoints[ctj].edge != cedge) ctj = -1; + break; + } + } + if(ctj >= 0) + { + int e1 = cedge%(MAXFACEVERTS+1), e2 = (e1+1)%numverts; + vertex &v1 = verts[e1], &v2 = verts[e2]; + ivec d(vec(v2.pos).sub(v1.pos).mul(8)); + int axis = abs(d.x) > abs(d.y) ? (abs(d.x) > abs(d.z) ? 0 : 2) : (abs(d.y) > abs(d.z) ? 1 : 2); + if(d[axis] < 0) d.neg(); + reduceslope(d); + int origin = int(min(v1.pos[axis], v2.pos[axis])*8)&~0x7FFF, + offset1 = (int(v1.pos[axis]*8) - origin) / d[axis], + offset2 = (int(v2.pos[axis]*8) - origin) / d[axis]; + vec o = vec(v1.pos).sub(vec(d).mul(offset1/8.0f)); + float doffset = 1.0f / (offset2 - offset1); + + if(i1 < 0) for(;;) + { + tjoint &t = tjoints[ctj]; + if(t.next < 0 || tjoints[t.next].edge != cedge) break; + ctj = t.next; + } + while(ctj >= 0) + { + tjoint &t = tjoints[ctj]; + if(t.edge != cedge) break; + float offset = (t.offset - offset1) * doffset; + vertex vt; + vt.pos = vec(d).mul(t.offset/8.0f).add(o); + vt.tc.lerp(v1.tc, v2.tc, offset); + vt.lm.x = short(v1.lm.x + (v2.lm.x-v1.lm.x)*offset), + vt.lm.y = short(v1.lm.y + (v2.lm.y-v1.lm.y)*offset); + vt.norm.lerp(v1.norm, v2.norm, offset); + vt.tangent.lerp(v1.tangent, v2.tangent, offset); + int i2 = vc.addvert(vt); + if(i2 < 0) return; + if(i1 >= 0) + { + if(total + 3 > USHRT_MAX) return; + total += 3; + idxs.add(i0); + idxs.add(i1); + idxs.add(i2); + i1 = i2; + } + else start = i0 = i2; + ctj = t.next; + } + } + } + } } static inline void calctexgen(VSlot &vslot, int dim, vec4 &sgen, vec4 &tgen) { - Texture *tex = vslot.slot->sts.empty() ? notexture : vslot.slot->sts[0].t; - const texrotation &r = texrotations[vslot.rotation]; - float k = TEX_SCALE/vslot.scale, - xs = r.flipx ? -tex->xs : tex->xs, - ys = r.flipy ? -tex->ys : tex->ys, - sk = k/xs, tk = k/ys, - soff = -(r.swapxy ? vslot.offset.y : vslot.offset.x)/xs, - toff = -(r.swapxy ? vslot.offset.x : vslot.offset.y)/ys; - static const int si[] = { 1, 0, 0 }, ti[] = { 2, 2, 1 }; - int sdim = si[dim], tdim = ti[dim]; - sgen = vec4(0, 0, 0, soff); - tgen = vec4(0, 0, 0, toff); - if(r.swapxy) - { - sgen[tdim] = (dim <= 1 ? -sk : sk); - tgen[sdim] = tk; - } - else - { - sgen[sdim] = sk; - tgen[tdim] = (dim <= 1 ? -tk : tk); - } + Texture *tex = vslot.slot->sts.empty() ? notexture : vslot.slot->sts[0].t; + const texrotation &r = texrotations[vslot.rotation]; + float k = TEX_SCALE/vslot.scale, + xs = r.flipx ? -tex->xs : tex->xs, + ys = r.flipy ? -tex->ys : tex->ys, + sk = k/xs, tk = k/ys, + soff = -(r.swapxy ? vslot.offset.y : vslot.offset.x)/xs, + toff = -(r.swapxy ? vslot.offset.x : vslot.offset.y)/ys; + static const int si[] = { 1, 0, 0 }, ti[] = { 2, 2, 1 }; + int sdim = si[dim], tdim = ti[dim]; + sgen = vec4(0, 0, 0, soff); + tgen = vec4(0, 0, 0, toff); + if(r.swapxy) + { + sgen[tdim] = (dim <= 1 ? -sk : sk); + tgen[sdim] = tk; + } + else + { + sgen[sdim] = sk; + tgen[tdim] = (dim <= 1 ? -tk : tk); + } } ushort encodenormal(const vec &n) { - if(n.iszero()) return 0; - int yaw = int(-atan2(n.x, n.y)/RAD), pitch = int(asin(n.z)/RAD); - return ushort(clamp(pitch + 90, 0, 180)*360 + (yaw < 0 ? yaw%360 + 360 : yaw%360) + 1); + if(n.iszero()) return 0; + int yaw = int(-atan2(n.x, n.y)/RAD), pitch = int(asin(n.z)/RAD); + return ushort(clamp(pitch + 90, 0, 180)*360 + (yaw < 0 ? yaw%360 + 360 : yaw%360) + 1); } vec decodenormal(ushort norm) { - if(!norm) return vec(0, 0, 1); - norm--; - const vec2 &yaw = sincos360[norm%360], &pitch = sincos360[norm/360+270]; - return vec(-yaw.y*pitch.x, yaw.x*pitch.x, pitch.y); + if(!norm) return vec(0, 0, 1); + norm--; + const vec2 &yaw = sincos360[norm%360], &pitch = sincos360[norm/360+270]; + return vec(-yaw.y*pitch.x, yaw.x*pitch.x, pitch.y); } void guessnormals(const vec *pos, int numverts, vec *normals) { - vec n1, n2; - n1.cross(pos[0], pos[1], pos[2]); - if(numverts != 4) - { - n1.normalize(); - loopk(numverts) normals[k] = n1; - return; - } - n2.cross(pos[0], pos[2], pos[3]); - if(n1.iszero()) - { - n2.normalize(); - loopk(4) normals[k] = n2; - return; - } - else n1.normalize(); - if(n2.iszero()) - { - loopk(4) normals[k] = n1; - return; - } - else n2.normalize(); - vec avg = vec(n1).add(n2).normalize(); - normals[0] = avg; - normals[1] = n1; - normals[2] = avg; - normals[3] = n2; + vec n1, n2; + n1.cross(pos[0], pos[1], pos[2]); + if(numverts != 4) + { + n1.normalize(); + loopk(numverts) normals[k] = n1; + return; + } + n2.cross(pos[0], pos[2], pos[3]); + if(n1.iszero()) + { + n2.normalize(); + loopk(4) normals[k] = n2; + return; + } + else n1.normalize(); + if(n2.iszero()) + { + loopk(4) normals[k] = n1; + return; + } + else n2.normalize(); + vec avg = vec(n1).add(n2).normalize(); + normals[0] = avg; + normals[1] = n1; + normals[2] = avg; + normals[3] = n2; } void addcubeverts(VSlot &vslot, int orient, int size, vec *pos, int convex, ushort texture, ushort lmid, vertinfo *vinfo, int numverts, int tj = -1, ushort envmap = EMID_NONE, int grassy = 0, bool alpha = false, int layer = LAYER_TOP) { - (void) grassy; - int dim = dimension(orient); - int shadowmask = texture==DEFAULT_SKY || alpha ? 0 : calcshadowmask(pos, numverts); - - LightMap *lm = NULL; - LightMapTexture *lmtex = NULL; - if(lightmaps.inrange(lmid-LMID_RESERVED)) - { - lm = &lightmaps[lmid-LMID_RESERVED]; - if((lm->type&LM_TYPE)==LM_DIFFUSE || - ((lm->type&LM_TYPE)==LM_BUMPMAP0 && - lightmaps.inrange(lmid+1-LMID_RESERVED) && - (lightmaps[lmid+1-LMID_RESERVED].type&LM_TYPE)==LM_BUMPMAP1)) - lmtex = &lightmaptexs[lm->tex]; - else lm = NULL; - } - - vec4 sgen, tgen; - calctexgen(vslot, dim, sgen, tgen); - vertex verts[MAXFACEVERTS]; - int index[MAXFACEVERTS]; - loopk(numverts) - { - vertex &v = verts[k]; - v.pos = pos[k]; - v.tc = vec2(sgen.dot(v.pos), tgen.dot(v.pos)); - if(lmtex) - { - v.lm = svec2(short(ceil((lm->offsetx + vinfo[k].u*(float(LM_PACKW)/float(USHRT_MAX+1)) + 0.5f) * float(SHRT_MAX)/lmtex->w)), - short(ceil((lm->offsety + vinfo[k].v*(float(LM_PACKH)/float(USHRT_MAX+1)) + 0.5f) * float(SHRT_MAX)/lmtex->h))); - } - else v.lm = svec2(0, 0); - if(vinfo && vinfo[k].norm) - { - vec n = decodenormal(vinfo[k].norm), t = orientation_tangent[vslot.rotation][dim]; - t.project(n).normalize(); - v.norm = bvec(n); - v.tangent = bvec4(bvec(t), orientation_bitangent[vslot.rotation][dim].scalartriple(n, t) < 0 ? 0 : 255); - } - else - { - v.norm = vinfo && vinfo[k].norm && envmap != EMID_NONE ? bvec(decodenormal(vinfo[k].norm)) : bvec(128, 128, 255); - v.tangent = bvec4(255, 128, 128, 255); - } - index[k] = vc.addvert(v); - if(index[k] < 0) return; - } - - if(texture == DEFAULT_SKY) - { - loopk(numverts) vc.skyclip = min(vc.skyclip, int(pos[k].z*8)>>3); - vc.skymask |= 0x3F&~(1<= LMID_RESERVED) lmid = lm ? lm->tex : LMID_AMBIENT; - - sortkey key(texture, lmid, !vslot.scroll.iszero() ? dim : 3, layer == LAYER_BLEND ? LAYER_BLEND : LAYER_TOP, envmap, alpha ? (vslot.alphaback ? ALPHA_BACK : (vslot.alphafront ? ALPHA_FRONT : NO_ALPHA)) : NO_ALPHA); - addtris(key, orient, verts, index, numverts, convex, shadowmask, tj); + (void) grassy; + int dim = dimension(orient); + int shadowmask = texture==DEFAULT_SKY || alpha ? 0 : calcshadowmask(pos, numverts); + + LightMap *lm = NULL; + LightMapTexture *lmtex = NULL; + if(lightmaps.inrange(lmid-LMID_RESERVED)) + { + lm = &lightmaps[lmid-LMID_RESERVED]; + if((lm->type&LM_TYPE)==LM_DIFFUSE || + ((lm->type&LM_TYPE)==LM_BUMPMAP0 && + lightmaps.inrange(lmid+1-LMID_RESERVED) && + (lightmaps[lmid+1-LMID_RESERVED].type&LM_TYPE)==LM_BUMPMAP1)) + lmtex = &lightmaptexs[lm->tex]; + else lm = NULL; + } + + vec4 sgen, tgen; + calctexgen(vslot, dim, sgen, tgen); + vertex verts[MAXFACEVERTS]; + int index[MAXFACEVERTS]; + loopk(numverts) + { + vertex &v = verts[k]; + v.pos = pos[k]; + v.tc = vec2(sgen.dot(v.pos), tgen.dot(v.pos)); + if(lmtex) + { + v.lm = svec2(short(ceil((lm->offsetx + vinfo[k].u*(float(LM_PACKW)/float(USHRT_MAX+1)) + 0.5f) * float(SHRT_MAX)/lmtex->w)), + short(ceil((lm->offsety + vinfo[k].v*(float(LM_PACKH)/float(USHRT_MAX+1)) + 0.5f) * float(SHRT_MAX)/lmtex->h))); + } + else v.lm = svec2(0, 0); + if(vinfo && vinfo[k].norm) + { + vec n = decodenormal(vinfo[k].norm), t = orientation_tangent[vslot.rotation][dim]; + t.project(n).normalize(); + v.norm = bvec(n); + v.tangent = bvec4(bvec(t), orientation_bitangent[vslot.rotation][dim].scalartriple(n, t) < 0 ? 0 : 255); + } + else + { + v.norm = vinfo && vinfo[k].norm && envmap != EMID_NONE ? bvec(decodenormal(vinfo[k].norm)) : bvec(128, 128, 255); + v.tangent = bvec4(255, 128, 128, 255); + } + index[k] = vc.addvert(v); + if(index[k] < 0) return; + } + + if(texture == DEFAULT_SKY) + { + loopk(numverts) vc.skyclip = min(vc.skyclip, int(pos[k].z*8)>>3); + vc.skymask |= 0x3F&~(1<= LMID_RESERVED) lmid = lm ? lm->tex : LMID_AMBIENT; + + sortkey key(texture, lmid, !vslot.scroll.iszero() ? dim : 3, layer == LAYER_BLEND ? LAYER_BLEND : LAYER_TOP, envmap, alpha ? (vslot.alphaback ? ALPHA_BACK : (vslot.alphafront ? ALPHA_FRONT : NO_ALPHA)) : NO_ALPHA); + addtris(key, orient, verts, index, numverts, convex, shadowmask, tj); } struct edgegroup { - ivec slope, origin; - int axis; + ivec slope, origin; + int axis; }; static uint hthash(const edgegroup &g) { - return g.slope.x^(g.slope.y<<2)^(g.slope.z<<4)^g.origin.x^g.origin.y^g.origin.z; + return g.slope.x^(g.slope.y<<2)^(g.slope.z<<4)^g.origin.x^g.origin.y^g.origin.z; } static bool htcmp(const edgegroup &x, const edgegroup &y) { - return x.slope==y.slope && x.origin==y.origin; + return x.slope==y.slope && x.origin==y.origin; } enum { - CE_START = 1<<0, - CE_END = 1<<1, - CE_FLIP = 1<<2, - CE_DUP = 1<<3 + CE_START = 1<<0, + CE_END = 1<<1, + CE_FLIP = 1<<2, + CE_DUP = 1<<3 }; struct cubeedge { - cube *c; - int next, offset; - ushort size; - uchar index, flags; + cube *c; + int next, offset; + ushort size; + uchar index, flags; }; vector cubeedges; @@ -837,273 +837,273 @@ hashtable edgegroups(1<<13); void gencubeedges(cube &c, const ivec &co, int size) { - ivec pos[MAXFACEVERTS]; - int vis; - loopi(6) if((vis = visibletris(c, i, co, size))) - { - int numverts = c.ext ? c.ext->surfaces[i].numverts&MAXFACEVERTS : 0; - if(numverts) - { - vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts; - ivec vo = ivec(co).mask(~0xFFF).shl(3); - loopj(numverts) - { - vertinfo &v = verts[j]; - pos[j] = ivec(v.x, v.y, v.z).add(vo); - } - } - else if(c.merged&(1< abs(d.y) ? (abs(d.x) > abs(d.z) ? 0 : 2) : (abs(d.y) > abs(d.z) ? 1 : 2); - if(d[axis] < 0) - { - d.neg(); - swap(e1, e2); - } - reduceslope(d); - - int t1 = pos[e1][axis]/d[axis], - t2 = pos[e2][axis]/d[axis]; - edgegroup g; - g.origin = ivec(pos[e1]).sub(ivec(d).mul(t1)); - g.slope = d; - g.axis = axis; - cubeedge ce; - ce.c = &c; - ce.offset = t1; - ce.size = t2 - t1; - ce.index = i*(MAXFACEVERTS+1)+j; - ce.flags = CE_START | CE_END | (e1!=j ? CE_FLIP : 0); - ce.next = -1; - - bool insert = true; - int *exists = edgegroups.access(g); - if(exists) - { - int prev = -1, cur = *exists; - while(cur >= 0) - { - cubeedge &p = cubeedges[cur]; - if(ce.offset <= p.offset+p.size) - { - if(ce.offset < p.offset) break; - if(p.flags&CE_DUP ? - ce.offset+ce.size <= p.offset+p.size : - ce.offset==p.offset && ce.size==p.size) - { - p.flags |= CE_DUP; - insert = false; - break; - } - if(ce.offset == p.offset+p.size) ce.flags &= ~CE_START; - } - prev = cur; - cur = p.next; - } - if(insert) - { - ce.next = cur; - while(cur >= 0) - { - cubeedge &p = cubeedges[cur]; - if(ce.offset+ce.size==p.offset) { ce.flags &= ~CE_END; break; } - cur = p.next; - } - if(prev>=0) cubeedges[prev].next = cubeedges.length(); - else *exists = cubeedges.length(); - } - } - else edgegroups[g] = cubeedges.length(); - - if(insert) cubeedges.add(ce); - } - } + ivec pos[MAXFACEVERTS]; + int vis; + loopi(6) if((vis = visibletris(c, i, co, size))) + { + int numverts = c.ext ? c.ext->surfaces[i].numverts&MAXFACEVERTS : 0; + if(numverts) + { + vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts; + ivec vo = ivec(co).mask(~0xFFF).shl(3); + loopj(numverts) + { + vertinfo &v = verts[j]; + pos[j] = ivec(v.x, v.y, v.z).add(vo); + } + } + else if(c.merged&(1< abs(d.y) ? (abs(d.x) > abs(d.z) ? 0 : 2) : (abs(d.y) > abs(d.z) ? 1 : 2); + if(d[axis] < 0) + { + d.neg(); + swap(e1, e2); + } + reduceslope(d); + + int t1 = pos[e1][axis]/d[axis], + t2 = pos[e2][axis]/d[axis]; + edgegroup g; + g.origin = ivec(pos[e1]).sub(ivec(d).mul(t1)); + g.slope = d; + g.axis = axis; + cubeedge ce; + ce.c = &c; + ce.offset = t1; + ce.size = t2 - t1; + ce.index = i*(MAXFACEVERTS+1)+j; + ce.flags = CE_START | CE_END | (e1!=j ? CE_FLIP : 0); + ce.next = -1; + + bool insert = true; + int *exists = edgegroups.access(g); + if(exists) + { + int prev = -1, cur = *exists; + while(cur >= 0) + { + cubeedge &p = cubeedges[cur]; + if(ce.offset <= p.offset+p.size) + { + if(ce.offset < p.offset) break; + if(p.flags&CE_DUP ? + ce.offset+ce.size <= p.offset+p.size : + ce.offset==p.offset && ce.size==p.size) + { + p.flags |= CE_DUP; + insert = false; + break; + } + if(ce.offset == p.offset+p.size) ce.flags &= ~CE_START; + } + prev = cur; + cur = p.next; + } + if(insert) + { + ce.next = cur; + while(cur >= 0) + { + cubeedge &p = cubeedges[cur]; + if(ce.offset+ce.size==p.offset) { ce.flags &= ~CE_END; break; } + cur = p.next; + } + if(prev>=0) cubeedges[prev].next = cubeedges.length(); + else *exists = cubeedges.length(); + } + } + else edgegroups[g] = cubeedges.length(); + + if(insert) cubeedges.add(ce); + } + } } void gencubeedges(cube *c = worldroot, const ivec &co = ivec(0, 0, 0), int size = worldsize>>1) { - progress("fixing t-joints..."); - neighbourstack[++neighbourdepth] = c; - loopi(8) - { - ivec o(i, co, size); - if(c[i].ext) c[i].ext->tjoints = -1; - if(c[i].children) gencubeedges(c[i].children, o, size>>1); - else if(!isempty(c[i])) gencubeedges(c[i], o, size); - } - --neighbourdepth; + progress("fixing t-joints..."); + neighbourstack[++neighbourdepth] = c; + loopi(8) + { + ivec o(i, co, size); + if(c[i].ext) c[i].ext->tjoints = -1; + if(c[i].children) gencubeedges(c[i].children, o, size>>1); + else if(!isempty(c[i])) gencubeedges(c[i], o, size); + } + --neighbourdepth; } void gencubeverts(cube &c, const ivec &co, int size, int csi) { - if(!(c.visible&0xC0)) return; - - int vismask = ~c.merged & 0x3F; - if(!(c.visible&0x80)) vismask &= c.visible; - if(!vismask) return; - - int tj = filltjoints && c.ext ? c.ext->tjoints : -1, vis; - loopi(6) if(vismask&(1<surfaces[i].numverts&MAXFACEVERTS : 0, convex = 0; - if(numverts) - { - verts = c.ext->verts() + c.ext->surfaces[i].verts; - vec vo(ivec(co).mask(~0xFFF)); - loopj(numverts) pos[j] = vec(verts[j].getxyz()).mul(1.0f/8).add(vo); - 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 = vis&4 || convex < 0 ? 1 : 0; - vec vo(co); - pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo); - if(vis&1) pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo); - pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo); - if(vis&2) pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo); - } - - VSlot &vslot = lookupvslot(c.texture[i], true), - *layer = vslot.layer && !(c.material&MAT_ALPHA) ? &lookupvslot(vslot.layer, true) : NULL; - ushort envmap = vslot.slot->shader->type&SHADER_ENVMAP ? (int) (vslot.slot->texmask&(1<slot->shader->type&SHADER_ENVMAP ? (int) (layer->slot->texmask&(1<= 0 && tjoints[tj].edge < i*(MAXFACEVERTS+1)) tj = tjoints[tj].next; - int hastj = tj >= 0 && tjoints[tj].edge < (i+1)*(MAXFACEVERTS+1) ? tj : -1; - if(!c.ext) - addcubeverts(vslot, i, size, pos, convex, c.texture[i], LMID_AMBIENT, NULL, numverts, hastj, envmap, 0, (c.material&MAT_ALPHA)!=0); - else - { - const surfaceinfo &surf = c.ext->surfaces[i]; - if(!surf.numverts || surf.numverts&LAYER_TOP) - addcubeverts(vslot, i, size, pos, convex, c.texture[i], surf.lmid[0], verts, numverts, hastj, envmap, 0, (c.material&MAT_ALPHA)!=0, LAYER_TOP|(surf.numverts&LAYER_BLEND)); - if(surf.numverts&LAYER_BOTTOM) - addcubeverts(layer ? *layer : vslot, i, size, pos, convex, vslot.layer, surf.lmid[1], surf.numverts&LAYER_DUP ? verts + numverts : verts, numverts, hastj, envmap2); - } - } + if(!(c.visible&0xC0)) return; + + int vismask = ~c.merged & 0x3F; + if(!(c.visible&0x80)) vismask &= c.visible; + if(!vismask) return; + + int tj = filltjoints && c.ext ? c.ext->tjoints : -1, vis; + loopi(6) if(vismask&(1<surfaces[i].numverts&MAXFACEVERTS : 0, convex = 0; + if(numverts) + { + verts = c.ext->verts() + c.ext->surfaces[i].verts; + vec vo(ivec(co).mask(~0xFFF)); + loopj(numverts) pos[j] = vec(verts[j].getxyz()).mul(1.0f/8).add(vo); + 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 = vis&4 || convex < 0 ? 1 : 0; + vec vo(co); + pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo); + if(vis&1) pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo); + pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo); + if(vis&2) pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo); + } + + VSlot &vslot = lookupvslot(c.texture[i], true), + *layer = vslot.layer && !(c.material&MAT_ALPHA) ? &lookupvslot(vslot.layer, true) : NULL; + ushort envmap = vslot.slot->shader->type&SHADER_ENVMAP ? (int) (vslot.slot->texmask&(1<slot->shader->type&SHADER_ENVMAP ? (int) (layer->slot->texmask&(1<= 0 && tjoints[tj].edge < i*(MAXFACEVERTS+1)) tj = tjoints[tj].next; + int hastj = tj >= 0 && tjoints[tj].edge < (i+1)*(MAXFACEVERTS+1) ? tj : -1; + if(!c.ext) + addcubeverts(vslot, i, size, pos, convex, c.texture[i], LMID_AMBIENT, NULL, numverts, hastj, envmap, 0, (c.material&MAT_ALPHA)!=0); + else + { + const surfaceinfo &surf = c.ext->surfaces[i]; + if(!surf.numverts || surf.numverts&LAYER_TOP) + addcubeverts(vslot, i, size, pos, convex, c.texture[i], surf.lmid[0], verts, numverts, hastj, envmap, 0, (c.material&MAT_ALPHA)!=0, LAYER_TOP|(surf.numverts&LAYER_BLEND)); + if(surf.numverts&LAYER_BOTTOM) + addcubeverts(layer ? *layer : vslot, i, size, pos, convex, vslot.layer, surf.lmid[1], surf.numverts&LAYER_DUP ? verts + numverts : verts, numverts, hastj, envmap2); + } + } } static inline bool skyoccluded(cube &c, int orient) { - return touchingface(c, orient) && faceedges(c, orient) == F_SOLID; + return touchingface(c, orient) && faceedges(c, orient) == F_SOLID; } static int dummyskyfaces[6]; static inline int hasskyfaces(cube &c, const ivec &co, int size, int faces[6] = dummyskyfaces) { - int numfaces = 0; - if(isempty(c) || c.material&MAT_ALPHA) - { - if(co.x == 0) faces[numfaces++] = O_LEFT; - if(co.x + size == worldsize) faces[numfaces++] = O_RIGHT; - if(co.y == 0) faces[numfaces++] = O_BACK; - if(co.y + size == worldsize) faces[numfaces++] = O_FRONT; - if(co.z == 0) faces[numfaces++] = O_BOTTOM; - if(co.z + size == worldsize) faces[numfaces++] = O_TOP; - } - else if(!isentirelysolid(c)) - { - if(co.x == 0 && !skyoccluded(c, O_LEFT)) faces[numfaces++] = O_LEFT; - if(co.x + size == worldsize && !skyoccluded(c, O_RIGHT)) faces[numfaces++] = O_RIGHT; - if(co.y == 0 && !skyoccluded(c, O_BACK)) faces[numfaces++] = O_BACK; - if(co.y + size == worldsize && !skyoccluded(c, O_FRONT)) faces[numfaces++] = O_FRONT; - if(co.z == 0 && !skyoccluded(c, O_BOTTOM)) faces[numfaces++] = O_BOTTOM; - if(co.z + size == worldsize && !skyoccluded(c, O_TOP)) faces[numfaces++] = O_TOP; - } - return numfaces; + int numfaces = 0; + if(isempty(c) || c.material&MAT_ALPHA) + { + if(co.x == 0) faces[numfaces++] = O_LEFT; + if(co.x + size == worldsize) faces[numfaces++] = O_RIGHT; + if(co.y == 0) faces[numfaces++] = O_BACK; + if(co.y + size == worldsize) faces[numfaces++] = O_FRONT; + if(co.z == 0) faces[numfaces++] = O_BOTTOM; + if(co.z + size == worldsize) faces[numfaces++] = O_TOP; + } + else if(!isentirelysolid(c)) + { + if(co.x == 0 && !skyoccluded(c, O_LEFT)) faces[numfaces++] = O_LEFT; + if(co.x + size == worldsize && !skyoccluded(c, O_RIGHT)) faces[numfaces++] = O_RIGHT; + if(co.y == 0 && !skyoccluded(c, O_BACK)) faces[numfaces++] = O_BACK; + if(co.y + size == worldsize && !skyoccluded(c, O_FRONT)) faces[numfaces++] = O_FRONT; + if(co.z == 0 && !skyoccluded(c, O_BOTTOM)) faces[numfaces++] = O_BOTTOM; + if(co.z + size == worldsize && !skyoccluded(c, O_TOP)) faces[numfaces++] = O_TOP; + } + return numfaces; } void minskyface(cube &cu, int orient, const ivec &co, int size, facebounds &orig) { - facebounds mincf; - mincf.u1 = orig.u2; - mincf.u2 = orig.u1; - mincf.v1 = orig.v2; - mincf.v2 = orig.v1; - mincubeface(cu, orient, co, size, orig, mincf, MAT_ALPHA, MAT_ALPHA); - orig.u1 = max(mincf.u1, orig.u1); - orig.u2 = min(mincf.u2, orig.u2); - orig.v1 = max(mincf.v1, orig.v1); - orig.v2 = min(mincf.v2, orig.v2); + facebounds mincf; + mincf.u1 = orig.u2; + mincf.u2 = orig.u1; + mincf.v1 = orig.v2; + mincf.v2 = orig.v1; + mincubeface(cu, orient, co, size, orig, mincf, MAT_ALPHA, MAT_ALPHA); + orig.u1 = max(mincf.u1, orig.u1); + orig.u2 = min(mincf.u2, orig.u2); + orig.v1 = max(mincf.v1, orig.v1); + orig.v2 = min(mincf.v2, orig.v2); } void genskyfaces(cube &c, const ivec &o, int size) { - int faces[6], numfaces = hasskyfaces(c, o, size, faces); - if(!numfaces) return; - - loopi(numfaces) - { - int orient = faces[i], dim = dimension(orient); - facebounds m; - m.u1 = (o[C[dim]]&0xFFF)<<3; - m.u2 = m.u1 + (size<<3); - m.v1 = (o[R[dim]]&0xFFF)<<3; - m.v2 = m.v1 + (size<<3); - minskyface(c, orient, o, size, m); - if(m.u1 >= m.u2 || m.v1 >= m.v2) continue; - vc.skyarea += (int(m.u2-m.u1)*int(m.v2-m.v1) + (1<<(2*3))-1)>>(2*3); - vc.skyfaces[orient].add(m); - } + int faces[6], numfaces = hasskyfaces(c, o, size, faces); + if(!numfaces) return; + + loopi(numfaces) + { + int orient = faces[i], dim = dimension(orient); + facebounds m; + m.u1 = (o[C[dim]]&0xFFF)<<3; + m.u2 = m.u1 + (size<<3); + m.v1 = (o[R[dim]]&0xFFF)<<3; + m.v2 = m.v1 + (size<<3); + minskyface(c, orient, o, size, m); + if(m.u1 >= m.u2 || m.v1 >= m.v2) continue; + vc.skyarea += (int(m.u2-m.u1)*int(m.v2-m.v1) + (1<<(2*3))-1)>>(2*3); + vc.skyfaces[orient].add(m); + } } void addskyverts(const ivec &o, int size) { - loopi(6) - { - int dim = dimension(i), c = C[dim], r = R[dim]; - vector &sf = vc.skyfaces[i]; - if(sf.empty()) continue; - vc.skymask |= 0x3F&~(1<>3); - } - if(vc.skytris + 6 > USHRT_MAX) break; - vc.skytris += 6; - vc.skyindices.add(index[0]); - vc.skyindices.add(index[1]); - vc.skyindices.add(index[2]); - - vc.skyindices.add(index[0]); - vc.skyindices.add(index[2]); - vc.skyindices.add(index[3]); - nextskyface:; - } - } + loopi(6) + { + int dim = dimension(i), c = C[dim], r = R[dim]; + vector &sf = vc.skyfaces[i]; + if(sf.empty()) continue; + vc.skymask |= 0x3F&~(1<>3); + } + if(vc.skytris + 6 > USHRT_MAX) break; + vc.skytris += 6; + vc.skyindices.add(index[0]); + vc.skyindices.add(index[1]); + vc.skyindices.add(index[2]); + + vc.skyindices.add(index[0]); + vc.skyindices.add(index[2]); + vc.skyindices.add(index[3]); + nextskyface:; + } + } } ////////// Vertex Arrays ////////////// @@ -1114,125 +1114,125 @@ vector valist, varoot; vtxarray *newva(const ivec &co, int size) { - vc.optimize(); - - vtxarray *va = new vtxarray; - va->parent = NULL; - va->o = co; - va->size = size; - va->skyarea = vc.skyarea; - va->skyfaces = vc.skymask; - va->skyclip = vc.skyclip < INT_MAX ? vc.skyclip : INT_MAX; - va->curvfc = VFC_NOT_VISIBLE; - va->occluded = OCCLUDE_NOTHING; - va->query = NULL; - va->bbmin = ivec(-1, -1, -1); - va->bbmax = ivec(-1, -1, -1); - va->hasmerges = 0; - va->mergelevel = -1; - - vc.setupdata(va); - - wverts += va->verts; - wtris += va->tris + va->blends + va->alphatris; - allocva++; - valist.add(va); - - return va; + vc.optimize(); + + vtxarray *va = new vtxarray; + va->parent = NULL; + va->o = co; + va->size = size; + va->skyarea = vc.skyarea; + va->skyfaces = vc.skymask; + va->skyclip = vc.skyclip < INT_MAX ? vc.skyclip : INT_MAX; + va->curvfc = VFC_NOT_VISIBLE; + va->occluded = OCCLUDE_NOTHING; + va->query = NULL; + va->bbmin = ivec(-1, -1, -1); + va->bbmax = ivec(-1, -1, -1); + va->hasmerges = 0; + va->mergelevel = -1; + + vc.setupdata(va); + + wverts += va->verts; + wtris += va->tris + va->blends + va->alphatris; + allocva++; + valist.add(va); + + return va; } void destroyva(vtxarray *va, bool reparent) { - wverts -= va->verts; - wtris -= va->tris + va->blends + va->alphatris; - allocva--; - valist.removeobj(va); - if(!va->parent) varoot.removeobj(va); - if(reparent) - { - if(va->parent) va->parent->children.removeobj(va); - loopv(va->children) - { - vtxarray *child = va->children[i]; - child->parent = va->parent; - if(child->parent) child->parent->children.add(child); - } - } - if(va->vbuf) destroyvbo(va->vbuf); - if(va->ebuf) destroyvbo(va->ebuf); - if(va->skybuf) destroyvbo(va->skybuf); - if(va->eslist) delete[] va->eslist; - if(va->matbuf) delete[] va->matbuf; - delete va; + wverts -= va->verts; + wtris -= va->tris + va->blends + va->alphatris; + allocva--; + valist.removeobj(va); + if(!va->parent) varoot.removeobj(va); + if(reparent) + { + if(va->parent) va->parent->children.removeobj(va); + loopv(va->children) + { + vtxarray *child = va->children[i]; + child->parent = va->parent; + if(child->parent) child->parent->children.add(child); + } + } + if(va->vbuf) destroyvbo(va->vbuf); + if(va->ebuf) destroyvbo(va->ebuf); + if(va->skybuf) destroyvbo(va->skybuf); + if(va->eslist) delete[] va->eslist; + if(va->matbuf) delete[] va->matbuf; + delete va; } void clearvas(cube *c) { - loopi(8) - { - if(c[i].ext) - { - if(c[i].ext->va) destroyva(c[i].ext->va, false); - c[i].ext->va = NULL; - c[i].ext->tjoints = -1; - } - if(c[i].children) clearvas(c[i].children); - } + loopi(8) + { + if(c[i].ext) + { + if(c[i].ext->va) destroyva(c[i].ext->va, false); + c[i].ext->va = NULL; + c[i].ext->tjoints = -1; + } + if(c[i].children) clearvas(c[i].children); + } } void updatevabb(vtxarray *va, bool force) { - if(!force && va->bbmin.x >= 0) return; - - va->bbmin = va->geommin; - va->bbmax = va->geommax; - va->bbmin.min(va->matmin); - va->bbmax.max(va->matmax); - loopv(va->children) - { - vtxarray *child = va->children[i]; - updatevabb(child, force); - va->bbmin.min(child->bbmin); - va->bbmax.max(child->bbmax); - } - loopv(va->mapmodels) - { - octaentities *oe = va->mapmodels[i]; - va->bbmin.min(oe->bbmin); - va->bbmax.max(oe->bbmax); - } - va->bbmin.max(va->o); - va->bbmax.min(ivec(va->o).add(va->size)); - - if(va->skyfaces) - { - va->skyfaces |= 0x80; - if(va->sky) loop(dim, 3) if(va->skyfaces&(3<<(2*dim))) - { - int r = R[dim], c = C[dim]; - if((va->skyfaces&(1<<(2*dim)) && va->o[dim] < va->bbmin[dim]) || - (va->skyfaces&(2<<(2*dim)) && va->o[dim]+va->size > va->bbmax[dim]) || - va->o[r] < va->bbmin[r] || va->o[r]+va->size > va->bbmax[r] || - va->o[c] < va->bbmin[c] || va->o[c]+va->size > va->bbmax[c]) - { - va->skyfaces &= ~0x80; - break; - } - } - } + if(!force && va->bbmin.x >= 0) return; + + va->bbmin = va->geommin; + va->bbmax = va->geommax; + va->bbmin.min(va->matmin); + va->bbmax.max(va->matmax); + loopv(va->children) + { + vtxarray *child = va->children[i]; + updatevabb(child, force); + va->bbmin.min(child->bbmin); + va->bbmax.max(child->bbmax); + } + loopv(va->mapmodels) + { + octaentities *oe = va->mapmodels[i]; + va->bbmin.min(oe->bbmin); + va->bbmax.max(oe->bbmax); + } + va->bbmin.max(va->o); + va->bbmax.min(ivec(va->o).add(va->size)); + + if(va->skyfaces) + { + va->skyfaces |= 0x80; + if(va->sky) loop(dim, 3) if(va->skyfaces&(3<<(2*dim))) + { + int r = R[dim], c = C[dim]; + if((va->skyfaces&(1<<(2*dim)) && va->o[dim] < va->bbmin[dim]) || + (va->skyfaces&(2<<(2*dim)) && va->o[dim]+va->size > va->bbmax[dim]) || + va->o[r] < va->bbmin[r] || va->o[r]+va->size > va->bbmax[r] || + va->o[c] < va->bbmin[c] || va->o[c]+va->size > va->bbmax[c]) + { + va->skyfaces &= ~0x80; + break; + } + } + } } void updatevabbs(bool force) { - loopv(varoot) updatevabb(varoot[i], force); + loopv(varoot) updatevabb(varoot[i], force); } struct mergedface { - uchar orient, lmid, numverts; - ushort mat, tex, envmap; - vertinfo *verts; - int tjoints; + uchar orient, lmid, numverts; + ushort mat, tex, envmap; + vertinfo *verts; + int tjoints; }; #define MAXMERGELEVEL 12 @@ -1241,263 +1241,263 @@ static vector vamerges[MAXMERGELEVEL+1]; int genmergedfaces(cube &c, const ivec &co, int size, int minlevel = -1) { - if(!c.ext || isempty(c)) return -1; - int tj = c.ext->tjoints, maxlevel = -1; - loopi(6) if(c.merged&(1<surfaces[i]; - int numverts = surf.numverts&MAXFACEVERTS; - if(!numverts) - { - if(minlevel < 0) vahasmerges |= MERGE_PART; - continue; - } - mergedface mf; - mf.orient = i; - mf.mat = c.material; - mf.tex = c.texture[i]; - mf.envmap = EMID_NONE; - mf.lmid = surf.lmid[0]; - mf.numverts = surf.numverts; - mf.verts = c.ext->verts() + surf.verts; - mf.tjoints = -1; - int level = calcmergedsize(i, co, size, mf.verts, mf.numverts&MAXFACEVERTS); - if(level > minlevel) - { - maxlevel = max(maxlevel, level); - - while(tj >= 0 && tjoints[tj].edge < i*(MAXFACEVERTS+1)) tj = tjoints[tj].next; - if(tj >= 0 && tjoints[tj].edge < (i+1)*(MAXFACEVERTS+1)) mf.tjoints = tj; - - VSlot &vslot = lookupvslot(mf.tex, true), - *layer = vslot.layer && !(c.material&MAT_ALPHA) ? &lookupvslot(vslot.layer, true) : NULL; - if(vslot.slot->shader->type&SHADER_ENVMAP) - mf.envmap = vslot.slot->texmask&(1<slot->shader->type&SHADER_ENVMAP ? (int) (layer->slot->texmask&(1<= 0) - { - vamergemax = max(vamergemax, maxlevel); - vahasmerges |= MERGE_ORIGIN; - } - return maxlevel; + if(!c.ext || isempty(c)) return -1; + int tj = c.ext->tjoints, maxlevel = -1; + loopi(6) if(c.merged&(1<surfaces[i]; + int numverts = surf.numverts&MAXFACEVERTS; + if(!numverts) + { + if(minlevel < 0) vahasmerges |= MERGE_PART; + continue; + } + mergedface mf; + mf.orient = i; + mf.mat = c.material; + mf.tex = c.texture[i]; + mf.envmap = EMID_NONE; + mf.lmid = surf.lmid[0]; + mf.numverts = surf.numverts; + mf.verts = c.ext->verts() + surf.verts; + mf.tjoints = -1; + int level = calcmergedsize(i, co, size, mf.verts, mf.numverts&MAXFACEVERTS); + if(level > minlevel) + { + maxlevel = max(maxlevel, level); + + while(tj >= 0 && tjoints[tj].edge < i*(MAXFACEVERTS+1)) tj = tjoints[tj].next; + if(tj >= 0 && tjoints[tj].edge < (i+1)*(MAXFACEVERTS+1)) mf.tjoints = tj; + + VSlot &vslot = lookupvslot(mf.tex, true), + *layer = vslot.layer && !(c.material&MAT_ALPHA) ? &lookupvslot(vslot.layer, true) : NULL; + if(vslot.slot->shader->type&SHADER_ENVMAP) + mf.envmap = vslot.slot->texmask&(1<slot->shader->type&SHADER_ENVMAP ? (int) (layer->slot->texmask&(1<= 0) + { + vamergemax = max(vamergemax, maxlevel); + vahasmerges |= MERGE_ORIGIN; + } + return maxlevel; } int findmergedfaces(cube &c, const ivec &co, int size, int csi, int minlevel) { - if(c.ext && c.ext->va && !(c.ext->va->hasmerges&MERGE_ORIGIN)) return c.ext->va->mergelevel; - else if(c.children) - { - int maxlevel = -1; - loopi(8) - { - ivec o(i, co, size/2); - int level = findmergedfaces(c.children[i], o, size/2, csi-1, minlevel); - maxlevel = max(maxlevel, level); - } - return maxlevel; - } - else if(c.ext && c.merged) return genmergedfaces(c, co, size, minlevel); - else return -1; + if(c.ext && c.ext->va && !(c.ext->va->hasmerges&MERGE_ORIGIN)) return c.ext->va->mergelevel; + else if(c.children) + { + int maxlevel = -1; + loopi(8) + { + ivec o(i, co, size/2); + int level = findmergedfaces(c.children[i], o, size/2, csi-1, minlevel); + maxlevel = max(maxlevel, level); + } + return maxlevel; + } + else if(c.ext && c.merged) return genmergedfaces(c, co, size, minlevel); + else return -1; } void addmergedverts(int level, const ivec &o) { - vector &mfl = vamerges[level]; - if(mfl.empty()) return; - vec vo(ivec(o).mask(~0xFFF)); - vec pos[MAXFACEVERTS]; - loopv(mfl) - { - mergedface &mf = mfl[i]; - int numverts = mf.numverts&MAXFACEVERTS; - loopi(numverts) - { - vertinfo &v = mf.verts[i]; - pos[i] = vec(v.x, v.y, v.z).mul(1.0f/8).add(vo); - } - VSlot &vslot = lookupvslot(mf.tex, true); - addcubeverts(vslot, mf.orient, 1< &mfl = vamerges[level]; + if(mfl.empty()) return; + vec vo(ivec(o).mask(~0xFFF)); + vec pos[MAXFACEVERTS]; + loopv(mfl) + { + mergedface &mf = mfl[i]; + int numverts = mf.numverts&MAXFACEVERTS; + loopi(numverts) + { + vertinfo &v = mf.verts[i]; + pos[i] = vec(v.x, v.y, v.z).mul(1.0f/8).add(vo); + } + VSlot &vslot = lookupvslot(mf.tex, true); + addcubeverts(vslot, mf.orient, 1<va) - { - maxlevel = max(maxlevel, c.ext->va->mergelevel); - return; // don't re-render - } - - if(c.children) - { - neighbourstack[++neighbourdepth] = c.children; - c.escaped = 0; - loopi(8) - { - ivec o(i, co, size/2); - int level = -1; - rendercube(c.children[i], o, size/2, csi-1, level); - if(level >= csi) - c.escaped |= 1<ents && c.ext->ents->mapmodels.length()) vc.mapmodels.add(c.ext->ents); - } - return; - } - - genskyfaces(c, co, size); - - if(!isempty(c)) - { - gencubeverts(c, co, size, csi); - if(c.merged) maxlevel = max(maxlevel, genmergedfaces(c, co, size)); - } - if(c.material != MAT_AIR) genmatsurfs(c, co, size, vc.matsurfs); - - if(c.ext) - { - if(c.ext->ents && c.ext->ents->mapmodels.length()) vc.mapmodels.add(c.ext->ents); - } - - if(csi <= MAXMERGELEVEL && vamerges[csi].length()) addmergedverts(csi, co); + //if(size<=16) return; + if(c.ext && c.ext->va) + { + maxlevel = max(maxlevel, c.ext->va->mergelevel); + return; // don't re-render + } + + if(c.children) + { + neighbourstack[++neighbourdepth] = c.children; + c.escaped = 0; + loopi(8) + { + ivec o(i, co, size/2); + int level = -1; + rendercube(c.children[i], o, size/2, csi-1, level); + if(level >= csi) + c.escaped |= 1<ents && c.ext->ents->mapmodels.length()) vc.mapmodels.add(c.ext->ents); + } + return; + } + + genskyfaces(c, co, size); + + if(!isempty(c)) + { + gencubeverts(c, co, size, csi); + if(c.merged) maxlevel = max(maxlevel, genmergedfaces(c, co, size)); + } + if(c.material != MAT_AIR) genmatsurfs(c, co, size, vc.matsurfs); + + if(c.ext) + { + if(c.ext->ents && c.ext->ents->mapmodels.length()) vc.mapmodels.add(c.ext->ents); + } + + if(csi <= MAXMERGELEVEL && vamerges[csi].length()) addmergedverts(csi, co); } void calcgeombb(const ivec &co, int size, ivec &bbmin, ivec &bbmax) { - vec vmin(co), vmax = vmin; - vmin.add(size); - - loopv(vc.verts) - { - const vec &v = vc.verts[i].pos; - vmin.min(v); - vmax.max(v); - } - - bbmin = ivec(vmin.mul(8)).shr(3); - bbmax = ivec(vmax.mul(8)).add(7).shr(3); + vec vmin(co), vmax = vmin; + vmin.add(size); + + loopv(vc.verts) + { + const vec &v = vc.verts[i].pos; + vmin.min(v); + vmax.max(v); + } + + bbmin = ivec(vmin.mul(8)).shr(3); + bbmax = ivec(vmax.mul(8)).add(7).shr(3); } void calcmatbb(const ivec &co, int size, ivec &bbmin, ivec &bbmax) { - bbmax = co; - (bbmin = bbmax).add(size); - loopv(vc.matsurfs) - { - materialsurface &m = vc.matsurfs[i]; - switch(m.material&MATF_VOLUME) - { - case MAT_WATER: - case MAT_GLASS: - case MAT_LAVA: - break; - - default: - continue; - } - - int dim = dimension(m.orient), - r = R[dim], - c = C[dim]; - bbmin[dim] = min(bbmin[dim], m.o[dim]); - bbmax[dim] = max(bbmax[dim], m.o[dim]); - - bbmin[r] = min(bbmin[r], m.o[r]); - bbmax[r] = max(bbmax[r], m.o[r] + m.rsize); - - bbmin[c] = min(bbmin[c], m.o[c]); - bbmax[c] = max(bbmax[c], m.o[c] + m.csize); - } + bbmax = co; + (bbmin = bbmax).add(size); + loopv(vc.matsurfs) + { + materialsurface &m = vc.matsurfs[i]; + switch(m.material&MATF_VOLUME) + { + case MAT_WATER: + case MAT_GLASS: + case MAT_LAVA: + break; + + default: + continue; + } + + int dim = dimension(m.orient), + r = R[dim], + c = C[dim]; + bbmin[dim] = min(bbmin[dim], m.o[dim]); + bbmax[dim] = max(bbmax[dim], m.o[dim]); + + bbmin[r] = min(bbmin[r], m.o[r]); + bbmax[r] = max(bbmax[r], m.o[r] + m.rsize); + + bbmin[c] = min(bbmin[c], m.o[c]); + bbmax[c] = max(bbmax[c], m.o[c] + m.csize); + } } void setva(cube &c, const ivec &co, int size, int csi) { - ASSERT(size <= 0x1000); + ASSERT(size <= 0x1000); - int vamergeoffset[MAXMERGELEVEL+1]; - loopi(MAXMERGELEVEL+1) vamergeoffset[i] = vamerges[i].length(); + int vamergeoffset[MAXMERGELEVEL+1]; + loopi(MAXMERGELEVEL+1) vamergeoffset[i] = vamerges[i].length(); - vc.origin = co; - vc.size = size; + vc.origin = co; + vc.size = size; - shadowmapmin = vec(co).add(size); - shadowmapmax = vec(co); + shadowmapmin = vec(co).add(size); + shadowmapmax = vec(co); - int maxlevel = -1; - rendercube(c, co, size, csi, maxlevel); + int maxlevel = -1; + rendercube(c, co, size, csi, maxlevel); - ivec bbmin, bbmax; + ivec bbmin, bbmax; - calcgeombb(co, size, bbmin, bbmax); + calcgeombb(co, size, bbmin, bbmax); - addskyverts(co, size); + addskyverts(co, size); - if(size == min(0x1000, worldsize/2) || !vc.emptyva()) - { - vtxarray *va = newva(co, size); - ext(c).va = va; - va->geommin = bbmin; - va->geommax = bbmax; - calcmatbb(co, size, va->matmin, va->matmax); - va->shadowmapmin = ivec(shadowmapmin.mul(8)).shr(3); - va->shadowmapmax = ivec(shadowmapmax.mul(8)).add(7).shr(3); - va->hasmerges = vahasmerges; - va->mergelevel = vamergemax; - } - else - { - loopi(MAXMERGELEVEL+1) vamerges[i].setsize(vamergeoffset[i]); - } + if(size == min(0x1000, worldsize/2) || !vc.emptyva()) + { + vtxarray *va = newva(co, size); + ext(c).va = va; + va->geommin = bbmin; + va->geommax = bbmax; + calcmatbb(co, size, va->matmin, va->matmax); + va->shadowmapmin = ivec(shadowmapmin.mul(8)).shr(3); + va->shadowmapmax = ivec(shadowmapmax.mul(8)).add(7).shr(3); + va->hasmerges = vahasmerges; + va->mergelevel = vamergemax; + } + else + { + loopi(MAXMERGELEVEL+1) vamerges[i].setsize(vamergeoffset[i]); + } - vc.clear(); + vc.clear(); } static inline int setcubevisibility(cube &c, const ivec &co, int size) { - int numvis = 0, vismask = 0, collidemask = 0, checkmask = 0; - loopi(6) - { - int facemask = classifyface(c, i, co, size); - if(facemask&1) - { - vismask |= 1<surfaces[i].numverts&MAXFACEVERTS) numvis++; - } - else - { - numvis++; - if(c.texture[i] != DEFAULT_SKY && !(c.ext && c.ext->surfaces[i].numverts&MAXFACEVERTS)) checkmask |= 1<surfaces[i].numverts&MAXFACEVERTS) numvis++; + } + else + { + numvis++; + if(c.texture[i] != DEFAULT_SKY && !(c.ext && c.ext->surfaces[i].numverts&MAXFACEVERTS)) checkmask |= 1<va) - { - varoot.add(c[i].ext->va); - if(c[i].ext->va->hasmerges&MERGE_ORIGIN) findmergedfaces(c[i], o, size, csi, csi); - } - else - { - if(c[i].children) count += updateva(c[i].children, o, size/2, csi-1); - else - { - if(!isempty(c[i])) count += setcubevisibility(c[i], o, size); - count += hasskyfaces(c[i], o, size); - } - int tcount = count + (csi <= MAXMERGELEVEL ? vamerges[csi].length() : 0); - if(tcount > vafacemax || (tcount >= vafacemin && size >= vacubesize) || size == min(0x1000, worldsize/2)) - { - loadprogress = clamp(recalcprogress/float(allocnodes), 0.0f, 1.0f); - setva(c[i], o, size, csi); - if(c[i].ext && c[i].ext->va) - { - while(varoot.length() > childpos) - { - vtxarray *child = varoot.pop(); - c[i].ext->va->children.add(child); - child->parent = c[i].ext->va; - } - varoot.add(c[i].ext->va); - if(vamergemax > size) - { - cmergemax = max(cmergemax, vamergemax); - chasmerges |= vahasmerges&~MERGE_USE; - } - continue; - } - else count = 0; - } - } - if(csi+1 <= MAXMERGELEVEL && vamerges[csi].length()) vamerges[csi+1].move(vamerges[csi]); - cmergemax = max(cmergemax, vamergemax); - chasmerges |= vahasmerges; - ccount += count; - } - --neighbourdepth; - vamergemax = cmergemax; - vahasmerges = chasmerges; - - return ccount; + progress("recalculating geometry..."); + int ccount = 0, cmergemax = vamergemax, chasmerges = vahasmerges; + neighbourstack[++neighbourdepth] = c; + loopi(8) // counting number of semi-solid/solid children cubes + { + int count = 0, childpos = varoot.length(); + ivec o(i, co, size); + vamergemax = 0; + vahasmerges = 0; + if(c[i].ext && c[i].ext->va) + { + varoot.add(c[i].ext->va); + if(c[i].ext->va->hasmerges&MERGE_ORIGIN) findmergedfaces(c[i], o, size, csi, csi); + } + else + { + if(c[i].children) count += updateva(c[i].children, o, size/2, csi-1); + else + { + if(!isempty(c[i])) count += setcubevisibility(c[i], o, size); + count += hasskyfaces(c[i], o, size); + } + int tcount = count + (csi <= MAXMERGELEVEL ? vamerges[csi].length() : 0); + if(tcount > vafacemax || (tcount >= vafacemin && size >= vacubesize) || size == min(0x1000, worldsize/2)) + { + loadprogress = clamp(recalcprogress/float(allocnodes), 0.0f, 1.0f); + setva(c[i], o, size, csi); + if(c[i].ext && c[i].ext->va) + { + while(varoot.length() > childpos) + { + vtxarray *child = varoot.pop(); + c[i].ext->va->children.add(child); + child->parent = c[i].ext->va; + } + varoot.add(c[i].ext->va); + if(vamergemax > size) + { + cmergemax = max(cmergemax, vamergemax); + chasmerges |= vahasmerges&~MERGE_USE; + } + continue; + } + else count = 0; + } + } + if(csi+1 <= MAXMERGELEVEL && vamerges[csi].length()) vamerges[csi+1].move(vamerges[csi]); + cmergemax = max(cmergemax, vamergemax); + chasmerges |= vahasmerges; + ccount += count; + } + --neighbourdepth; + vamergemax = cmergemax; + vahasmerges = chasmerges; + + return ccount; } void addtjoint(const edgegroup &g, const cubeedge &e, int offset) { - int vcoord = (g.slope[g.axis]*offset + g.origin[g.axis]) & 0x7FFF; - tjoint &tj = tjoints.add(); - tj.offset = vcoord / g.slope[g.axis]; - tj.edge = e.index; - - int prev = -1, cur = ext(*e.c).tjoints; - while(cur >= 0) - { - tjoint &o = tjoints[cur]; - if(tj.edge < o.edge || (tj.edge==o.edge && (e.flags&CE_FLIP ? tj.offset > o.offset : tj.offset < o.offset))) break; - prev = cur; - cur = o.next; - } - - tj.next = cur; - if(prev < 0) e.c->ext->tjoints = tjoints.length()-1; - else tjoints[prev].next = tjoints.length()-1; + int vcoord = (g.slope[g.axis]*offset + g.origin[g.axis]) & 0x7FFF; + tjoint &tj = tjoints.add(); + tj.offset = vcoord / g.slope[g.axis]; + tj.edge = e.index; + + int prev = -1, cur = ext(*e.c).tjoints; + while(cur >= 0) + { + tjoint &o = tjoints[cur]; + if(tj.edge < o.edge || (tj.edge==o.edge && (e.flags&CE_FLIP ? tj.offset > o.offset : tj.offset < o.offset))) break; + prev = cur; + cur = o.next; + } + + tj.next = cur; + if(prev < 0) e.c->ext->tjoints = tjoints.length()-1; + else tjoints[prev].next = tjoints.length()-1; } void findtjoints(int cur, const edgegroup &g) { - int active = -1; - while(cur >= 0) - { - cubeedge &e = cubeedges[cur]; - int prevactive = -1, curactive = active; - while(curactive >= 0) - { - cubeedge &a = cubeedges[curactive]; - if(a.offset+a.size <= e.offset) - { - if(prevactive >= 0) cubeedges[prevactive].next = a.next; - else active = a.next; - } - else - { - prevactive = curactive; - if(!(a.flags&CE_DUP)) - { - if(e.flags&CE_START && e.offset > a.offset && e.offset < a.offset+a.size) - addtjoint(g, a, e.offset); - if(e.flags&CE_END && e.offset+e.size > a.offset && e.offset+e.size < a.offset+a.size) - addtjoint(g, a, e.offset+e.size); - } - if(!(e.flags&CE_DUP)) - { - if(a.flags&CE_START && a.offset > e.offset && a.offset < e.offset+e.size) - addtjoint(g, e, a.offset); - if(a.flags&CE_END && a.offset+a.size > e.offset && a.offset+a.size < e.offset+e.size) - addtjoint(g, e, a.offset+a.size); - } - } - curactive = a.next; - } - int next = e.next; - e.next = active; - active = cur; - cur = next; - } + int active = -1; + while(cur >= 0) + { + cubeedge &e = cubeedges[cur]; + int prevactive = -1, curactive = active; + while(curactive >= 0) + { + cubeedge &a = cubeedges[curactive]; + if(a.offset+a.size <= e.offset) + { + if(prevactive >= 0) cubeedges[prevactive].next = a.next; + else active = a.next; + } + else + { + prevactive = curactive; + if(!(a.flags&CE_DUP)) + { + if(e.flags&CE_START && e.offset > a.offset && e.offset < a.offset+a.size) + addtjoint(g, a, e.offset); + if(e.flags&CE_END && e.offset+e.size > a.offset && e.offset+e.size < a.offset+a.size) + addtjoint(g, a, e.offset+e.size); + } + if(!(e.flags&CE_DUP)) + { + if(a.flags&CE_START && a.offset > e.offset && a.offset < e.offset+e.size) + addtjoint(g, e, a.offset); + if(a.flags&CE_END && a.offset+a.size > e.offset && a.offset+a.size < e.offset+e.size) + addtjoint(g, e, a.offset+a.size); + } + } + curactive = a.next; + } + int next = e.next; + e.next = active; + active = cur; + cur = next; + } } void findtjoints() { - recalcprogress = 0; - gencubeedges(); - tjoints.setsize(0); - enumeratekt(edgegroups, edgegroup, g, int, e, findtjoints(e, g)); - cubeedges.setsize(0); - edgegroups.clear(); + recalcprogress = 0; + gencubeedges(); + tjoints.setsize(0); + enumeratekt(edgegroups, edgegroup, g, int, e, findtjoints(e, g)); + cubeedges.setsize(0); + edgegroups.clear(); } -void octarender() // creates va s for all leaf cubes that don't already have them +void octarender() // creates va s for all leaf cubes that don't already have them { - int csi = 0; - while(1<explicitsky; - skyarea += va->skyarea; - } - - visibleva = NULL; + int csi = 0; + while(1<explicitsky; + skyarea += va->skyarea; + } + + visibleva = NULL; } void precachetextures() { - vector texs; - loopv(valist) - { - vtxarray *va = valist[i]; - loopj(va->texs + va->blends) if(texs.find(va->eslist[j].texture) < 0) texs.add(va->eslist[j].texture); - } - loopv(texs) - { - loadprogress = float(i+1)/texs.length(); - lookupvslot(texs[i]); - } - loadprogress = 0; + vector texs; + loopv(valist) + { + vtxarray *va = valist[i]; + loopj(va->texs + va->blends) if(texs.find(va->eslist[j].texture) < 0) texs.add(va->eslist[j].texture); + } + loopv(texs) + { + loadprogress = float(i+1)/texs.length(); + lookupvslot(texs[i]); + } + loadprogress = 0; } void allchanged(bool load) { - renderprogress(0, "clearing vertex arrays..."); - clearvas(worldroot); - resetqueries(); - resetclipplanes(); - if(load) - { - setupsky(); - initenvmaps(); - } - guessshadowdir(); - entitiesinoctanodes(); - tjoints.setsize(0); - if(filltjoints) findtjoints(); - octarender(); - if(load) precachetextures(); - setupmaterials(); - invalidatepostfx(); - updatevabbs(true); - lightents(); - if(load) - { - seedparticles(); - drawtextures(); - } + renderprogress(0, "clearing vertex arrays..."); + clearvas(worldroot); + resetqueries(); + resetclipplanes(); + if(load) + { + setupsky(); + initenvmaps(); + } + guessshadowdir(); + entitiesinoctanodes(); + tjoints.setsize(0); + if(filltjoints) findtjoints(); + octarender(); + if(load) precachetextures(); + setupmaterials(); + updatevabbs(true); + lightents(); + if(load) + { + seedparticles(); + drawtextures(); + } } void recalc() { - allchanged(true); + allchanged(true); } COMMAND(recalc, ""); diff --git a/src/engine/physics.cpp b/src/engine/physics.cpp index 4c23739..19eee8b 100644 --- a/src/engine/physics.cpp +++ b/src/engine/physics.cpp @@ -12,88 +12,88 @@ static int clipcacheversion = -2; static inline clipplanes &getclipplanes(const cube &c, const ivec &o, int size, bool collide = true, int offset = 0) { - clipplanes &p = clipcache[int(&c - worldroot)&(MAXCLIPPLANES-1)]; - if(p.owner != &c || p.version != clipcacheversion+offset) - { - p.owner = &c; - p.version = clipcacheversion+offset; - genclipplanes(c, o, size, p, collide); - } - return p; + clipplanes &p = clipcache[int(&c - worldroot)&(MAXCLIPPLANES-1)]; + if(p.owner != &c || p.version != clipcacheversion+offset) + { + p.owner = &c; + p.version = clipcacheversion+offset; + genclipplanes(c, o, size, p, collide); + } + return p; } void resetclipplanes() { - clipcacheversion += 2; - if(!clipcacheversion) - { - memclear(clipcache); - clipcacheversion = 2; - } + clipcacheversion += 2; + if(!clipcacheversion) + { + memclear(clipcache); + clipcacheversion = 2; + } } ///////////////////////// ray - cube collision /////////////////////////////////////////////// #define INTERSECTPLANES(setentry, exit) \ - float enterdist = -1e16f, exitdist = 1e16f; \ - loopi(p.size) \ - { \ - float pdist = p.p[i].dist(v), facing = ray.dot(p.p[i]); \ - if(facing < 0) \ - { \ - pdist /= -facing; \ - if(pdist > enterdist) \ - { \ - if(pdist > exitdist) exit; \ - enterdist = pdist; \ - setentry; \ - } \ - } \ - else if(facing > 0) \ - { \ - pdist /= -facing; \ - if(pdist < exitdist) \ - { \ - if(pdist < enterdist) exit; \ - exitdist = pdist; \ - } \ - } \ - else if(pdist > 0) exit; \ - } + float enterdist = -1e16f, exitdist = 1e16f; \ + loopi(p.size) \ + { \ + float pdist = p.p[i].dist(v), facing = ray.dot(p.p[i]); \ + if(facing < 0) \ + { \ + pdist /= -facing; \ + if(pdist > enterdist) \ + { \ + if(pdist > exitdist) exit; \ + enterdist = pdist; \ + setentry; \ + } \ + } \ + else if(facing > 0) \ + { \ + pdist /= -facing; \ + if(pdist < exitdist) \ + { \ + if(pdist < enterdist) exit; \ + exitdist = pdist; \ + } \ + } \ + else if(pdist > 0) exit; \ + } #define INTERSECTBOX(setentry, exit) \ - loop(i, 3) \ - { \ - if(ray[i]) \ - { \ - float prad = fabs(p.r[i] * invray[i]), pdist = (p.o[i] - v[i]) * invray[i], pmin = pdist - prad, pmax = pdist + prad; \ - if(pmin > enterdist) \ - { \ - if(pmin > exitdist) exit; \ - enterdist = pmin; \ - setentry; \ - } \ - if(pmax < exitdist) \ - { \ - if(pmax < enterdist) exit; \ - exitdist = pmax; \ - } \ - } \ - else if(v[i] < p.o[i]-p.r[i] || v[i] > p.o[i]+p.r[i]) exit; \ - } + loop(i, 3) \ + { \ + if(ray[i]) \ + { \ + float prad = fabs(p.r[i] * invray[i]), pdist = (p.o[i] - v[i]) * invray[i], pmin = pdist - prad, pmax = pdist + prad; \ + if(pmin > enterdist) \ + { \ + if(pmin > exitdist) exit; \ + enterdist = pmin; \ + setentry; \ + } \ + if(pmax < exitdist) \ + { \ + if(pmax < enterdist) exit; \ + exitdist = pmax; \ + } \ + } \ + else if(v[i] < p.o[i]-p.r[i] || v[i] > p.o[i]+p.r[i]) exit; \ + } vec hitsurface; static inline bool raycubeintersect(const clipplanes &p, const cube &c, const vec &v, const vec &ray, const vec &invray, float &dist) { - int entry = -1, bbentry = -1; - INTERSECTPLANES(entry = i, return false); - INTERSECTBOX(bbentry = i, return false); - if(exitdist < 0) return false; - dist = max(enterdist+0.1f, 0.0f); - if(bbentry>=0) { hitsurface = vec(0, 0, 0); hitsurface[bbentry] = ray[bbentry]>0 ? -1 : 1; } - else hitsurface = p.p[entry]; - return true; + int entry = -1, bbentry = -1; + INTERSECTPLANES(entry = i, return false); + INTERSECTBOX(bbentry = i, return false); + if(exitdist < 0) return false; + dist = max(enterdist+0.1f, 0.0f); + if(bbentry>=0) { hitsurface = vec(0, 0, 0); hitsurface[bbentry] = ray[bbentry]>0 ? -1 : 1; } + else hitsurface = p.p[entry]; + return true; } extern void entselectionbox(const entity &e, vec &eo, vec &es); @@ -102,257 +102,257 @@ int hitent, hitorient; static float disttoent(octaentities *oc, const vec &o, const vec &ray, float radius, int mode, extentity *t) { - vec eo, es; - int orient = -1; - float dist = radius, f = 0.0f; - const vector &ents = entities::getents(); - - #define entintersect(mask, type, func) {\ - if((mode&(mask))==(mask)) loopv(oc->type) \ - { \ - extentity &e = *ents[oc->type[i]]; \ - if(!(e.flags&EF_OCTA) || &e==t) continue; \ - func; \ - if(f0 && vec(ray).mul(f).add(o).insidebb(oc->o, oc->size)) \ - { \ - hitentdist = dist = f; \ - hitent = oc->type[i]; \ - hitorient = orient; \ - } \ - } \ - } - - entintersect(RAY_POLY, mapmodels, - if(!mmintersect(e, o, ray, radius, mode, f)) continue; - ); - - entintersect(RAY_ENTS, other, - entselectionbox(e, eo, es); - if(!rayboxintersect(eo, es, o, ray, f, orient)) continue; - ); - - entintersect(RAY_ENTS, mapmodels, - entselectionbox(e, eo, es); - if(!rayboxintersect(eo, es, o, ray, f, orient)) continue; - ); - - return dist; + vec eo, es; + int orient = -1; + float dist = radius, f = 0.0f; + const vector &ents = entities::getents(); + + #define entintersect(mask, type, func) {\ + if((mode&(mask))==(mask)) loopv(oc->type) \ + { \ + extentity &e = *ents[oc->type[i]]; \ + if(!(e.flags&EF_OCTA) || &e==t) continue; \ + func; \ + if(f0 && vec(ray).mul(f).add(o).insidebb(oc->o, oc->size)) \ + { \ + hitentdist = dist = f; \ + hitent = oc->type[i]; \ + hitorient = orient; \ + } \ + } \ + } + + entintersect(RAY_POLY, mapmodels, + if(!mmintersect(e, o, ray, radius, mode, f)) continue; + ); + + entintersect(RAY_ENTS, other, + entselectionbox(e, eo, es); + if(!rayboxintersect(eo, es, o, ray, f, orient)) continue; + ); + + entintersect(RAY_ENTS, mapmodels, + entselectionbox(e, eo, es); + if(!rayboxintersect(eo, es, o, ray, f, orient)) continue; + ); + + return dist; } static float disttooutsideent(const vec &o, const vec &ray, float radius, int mode, extentity *t) { - vec eo, es; - int orient; - float dist = radius, f = 0.0f; - const vector &ents = entities::getents(); - loopv(outsideents) - { - extentity &e = *ents[outsideents[i]]; - if(!(e.flags&EF_OCTA) || &e==t) continue; - entselectionbox(e, eo, es); - if(!rayboxintersect(eo, es, o, ray, f, orient)) continue; - if(f0) - { - hitentdist = dist = f; - hitent = outsideents[i]; - hitorient = orient; - } - } - return dist; + vec eo, es; + int orient; + float dist = radius, f = 0.0f; + const vector &ents = entities::getents(); + loopv(outsideents) + { + extentity &e = *ents[outsideents[i]]; + if(!(e.flags&EF_OCTA) || &e==t) continue; + entselectionbox(e, eo, es); + if(!rayboxintersect(eo, es, o, ray, f, orient)) continue; + if(f0) + { + hitentdist = dist = f; + hitent = outsideents[i]; + hitorient = orient; + } + } + return dist; } // optimized shadow version static float shadowent(octaentities *oc, const vec &o, const vec &ray, float radius, int mode, extentity *t) { - float dist = radius, f = 0.0f; - const vector &ents = entities::getents(); - loopv(oc->mapmodels) - { - extentity &e = *ents[oc->mapmodels[i]]; - if(!(e.flags&EF_OCTA) || &e==t) continue; - if(!mmintersect(e, o, ray, radius, mode, f)) continue; - if(f>0 && f &ents = entities::getents(); + loopv(oc->mapmodels) + { + extentity &e = *ents[oc->mapmodels[i]]; + if(!(e.flags&EF_OCTA) || &e==t) continue; + if(!mmintersect(e, o, ray, radius, mode, f)) continue; + if(f>0 && f 0 ? radius : 1e16f; \ - vec v(o), invray(ray.x ? 1/ray.x : 1e16f, ray.y ? 1/ray.y : 1e16f, ray.z ? 1/ray.z : 1e16f); \ - cube *levels[20]; \ - levels[worldscale] = worldroot; \ - int lshift = worldscale, elvl = mode&RAY_BB ? worldscale : 0; \ - ivec lsizemask(invray.x>0 ? 1 : 0, invray.y>0 ? 1 : 0, invray.z>0 ? 1 : 0); \ + float dist = 0, dent = radius > 0 ? radius : 1e16f; \ + vec v(o), invray(ray.x ? 1/ray.x : 1e16f, ray.y ? 1/ray.y : 1e16f, ray.z ? 1/ray.z : 1e16f); \ + cube *levels[20]; \ + levels[worldscale] = worldroot; \ + int lshift = worldscale, elvl = mode&RAY_BB ? worldscale : 0; \ + ivec lsizemask(invray.x>0 ? 1 : 0, invray.y>0 ? 1 : 0, invray.z>0 ? 1 : 0); \ #define CHECKINSIDEWORLD \ - if(!insideworld(o)) \ - { \ - float disttoworld = 0, exitworld = 1e16f; \ - loopi(3) \ - { \ - float c = v[i]; \ - if(c<0 || c>=worldsize) \ - { \ - float d = ((invray[i]>0?0:worldsize)-c)*invray[i]; \ - if(d<0) return (radius>0?radius:-1); \ - disttoworld = max(disttoworld, 0.1f + d); \ - } \ - float e = ((invray[i]>0?worldsize:0)-c)*invray[i]; \ - exitworld = min(exitworld, e); \ - } \ - if(disttoworld > exitworld) return (radius>0?radius:-1); \ - v.add(vec(ray).mul(disttoworld)); \ - dist += disttoworld; \ - } + if(!insideworld(o)) \ + { \ + float disttoworld = 0, exitworld = 1e16f; \ + loopi(3) \ + { \ + float c = v[i]; \ + if(c<0 || c>=worldsize) \ + { \ + float d = ((invray[i]>0?0:worldsize)-c)*invray[i]; \ + if(d<0) return (radius>0?radius:-1); \ + disttoworld = max(disttoworld, 0.1f + d); \ + } \ + float e = ((invray[i]>0?worldsize:0)-c)*invray[i]; \ + exitworld = min(exitworld, e); \ + } \ + if(disttoworld > exitworld) return (radius>0?radius:-1); \ + v.add(vec(ray).mul(disttoworld)); \ + dist += disttoworld; \ + } #define DOWNOCTREE(disttoent, earlyexit) \ - cube *lc = levels[lshift]; \ - for(;;) \ - { \ - lshift--; \ - lc += octastep(x, y, z, lshift); \ - if(lc->ext && lc->ext->ents && lshift < elvl) \ - { \ - float edist = disttoent(lc->ext->ents, o, ray, dent, mode, t); \ - if(edist < dent) \ - { \ - earlyexit return min(edist, dist); \ - elvl = lshift; \ - dent = min(dent, edist); \ - } \ - } \ - if(lc->children==NULL) break; \ - lc = lc->children; \ - levels[lshift] = lc; \ - } + cube *lc = levels[lshift]; \ + for(;;) \ + { \ + lshift--; \ + lc += octastep(x, y, z, lshift); \ + if(lc->ext && lc->ext->ents && lshift < elvl) \ + { \ + float edist = disttoent(lc->ext->ents, o, ray, dent, mode, t); \ + if(edist < dent) \ + { \ + earlyexit return min(edist, dist); \ + elvl = lshift; \ + dent = min(dent, edist); \ + } \ + } \ + if(lc->children==NULL) break; \ + lc = lc->children; \ + levels[lshift] = lc; \ + } #define FINDCLOSEST(xclosest, yclosest, zclosest) \ - float dx = (lo.x+(lsizemask.x<= uint(worldsize)) exitworld; \ - diff >>= lshift; \ - if(!diff) exitworld; \ - do \ - { \ - lshift++; \ - diff >>= 1; \ - } while(diff); + x = int(v.x); \ + y = int(v.y); \ + z = int(v.z); \ + uint diff = uint(lo.x^x)|uint(lo.y^y)|uint(lo.z^z); \ + if(diff >= uint(worldsize)) exitworld; \ + diff >>= lshift; \ + if(!diff) exitworld; \ + do \ + { \ + lshift++; \ + diff >>= 1; \ + } while(diff); float raycube(const vec &o, const vec &ray, float radius, int mode, int size, extentity *t) { - if(ray.iszero()) return 0; - - INITRAYCUBE; - CHECKINSIDEWORLD; - - int closest = -1, x = int(v.x), y = int(v.y), z = int(v.z); - for(;;) - { - DOWNOCTREE(disttoent, if(mode&RAY_SHADOW)); - - int lsize = 1<0 || !(mode&RAY_SKIPFIRST)) && - (((mode&RAY_CLIPMAT) && isclipped(c.material&MATF_VOLUME)) || - ((mode&RAY_EDITMAT) && c.material != MAT_AIR) || - (!(mode&RAY_PASS) && lsize==size && !isempty(c)) || - isentirelysolid(c) || - dent < dist)) - { - if(closest >= 0) { hitsurface = vec(0, 0, 0); hitsurface[closest] = ray[closest]>0 ? -1 : 1; } - return min(dent, dist); - } - - ivec lo(x&(~0U<0 || !(mode&RAY_SKIPFIRST))) - return min(dent, dist+f); - } - - FINDCLOSEST(closest = 0, closest = 1, closest = 2); - - if(radius>0 && dist>=radius) return min(dent, dist); - - UPOCTREE(return min(dent, radius>0 ? radius : dist)); - } + if(ray.iszero()) return 0; + + INITRAYCUBE; + CHECKINSIDEWORLD; + + int closest = -1, x = int(v.x), y = int(v.y), z = int(v.z); + for(;;) + { + DOWNOCTREE(disttoent, if(mode&RAY_SHADOW)); + + int lsize = 1<0 || !(mode&RAY_SKIPFIRST)) && + (((mode&RAY_CLIPMAT) && isclipped(c.material&MATF_VOLUME)) || + ((mode&RAY_EDITMAT) && c.material != MAT_AIR) || + (!(mode&RAY_PASS) && lsize==size && !isempty(c)) || + isentirelysolid(c) || + dent < dist)) + { + if(closest >= 0) { hitsurface = vec(0, 0, 0); hitsurface[closest] = ray[closest]>0 ? -1 : 1; } + return min(dent, dist); + } + + ivec lo(x&(~0U<0 || !(mode&RAY_SKIPFIRST))) + return min(dent, dist+f); + } + + FINDCLOSEST(closest = 0, closest = 1, closest = 2); + + if(radius>0 && dist>=radius) return min(dent, dist); + + UPOCTREE(return min(dent, radius>0 ? radius : dist)); + } } // optimized version for lightmap shadowing... every cycle here counts!!! float shadowray(const vec &o, const vec &ray, float radius, int mode, extentity *t) { - INITRAYCUBE; - CHECKINSIDEWORLD; - - int side = O_BOTTOM, x = int(v.x), y = int(v.y), z = int(v.z); - for(;;) - { - DOWNOCTREE(shadowent, ); - - cube &c = *lc; - ivec lo(x&(~0U<= 0) - { - if(c.texture[side]==DEFAULT_SKY && mode&RAY_SKIPSKY) - { - if(mode&RAY_SKYTEX) return radius; - } - else return dist+max(enterdist+0.1f, 0.0f); - } - } - } - - nextcube: - FINDCLOSEST(side = O_RIGHT - lsizemask.x, side = O_FRONT - lsizemask.y, side = O_TOP - lsizemask.z); - - if(dist>=radius) return dist; - - UPOCTREE(return radius); - } + INITRAYCUBE; + CHECKINSIDEWORLD; + + int side = O_BOTTOM, x = int(v.x), y = int(v.y), z = int(v.z); + for(;;) + { + DOWNOCTREE(shadowent, ); + + cube &c = *lc; + ivec lo(x&(~0U<= 0) + { + if(c.texture[side]==DEFAULT_SKY && mode&RAY_SKIPSKY) + { + if(mode&RAY_SKYTEX) return radius; + } + else return dist+max(enterdist+0.1f, 0.0f); + } + } + } + + nextcube: + FINDCLOSEST(side = O_RIGHT - lsizemask.x, side = O_FRONT - lsizemask.y, side = O_TOP - lsizemask.z); + + if(dist>=radius) return dist; + + UPOCTREE(return radius); + } } // thread safe version struct ShadowRayCache { - clipplanes clipcache[MAXCLIPPLANES]; - int version; + clipplanes clipcache[MAXCLIPPLANES]; + int version; - ShadowRayCache() : version(-1) {} + ShadowRayCache() : version(-1) {} }; ShadowRayCache *newshadowraycache() { return new ShadowRayCache; } @@ -361,106 +361,106 @@ void freeshadowraycache(ShadowRayCache *&cache) { delete cache; cache = NULL; } void resetshadowraycache(ShadowRayCache *cache) { - cache->version++; - if(!cache->version) - { - memclear(cache->clipcache); - cache->version = 1; - } + cache->version++; + if(!cache->version) + { + memclear(cache->clipcache); + cache->version = 1; + } } float shadowray(ShadowRayCache *cache, const vec &o, const vec &ray, float radius, int mode, extentity *t) { - INITRAYCUBE; - CHECKINSIDEWORLD; - - int side = O_BOTTOM, x = int(v.x), y = int(v.y), z = int(v.z); - for(;;) - { - DOWNOCTREE(shadowent, ); - - cube &c = *lc; - ivec lo(x&(~0U<clipcache[int(&c - worldroot)&(MAXCLIPPLANES-1)]; - if(p.owner != &c || p.version != cache->version) { p.owner = &c; p.version = cache->version; genclipplanes(c, lo, 1<= 0) - { - if(c.texture[side]==DEFAULT_SKY && mode&RAY_SKIPSKY) - { - if(mode&RAY_SKYTEX) return radius; - } - else return dist+max(enterdist+0.1f, 0.0f); - } - } - } - - nextcube: - FINDCLOSEST(side = O_RIGHT - lsizemask.x, side = O_FRONT - lsizemask.y, side = O_TOP - lsizemask.z); - - if(dist>=radius) return dist; - - UPOCTREE(return radius); - } + INITRAYCUBE; + CHECKINSIDEWORLD; + + int side = O_BOTTOM, x = int(v.x), y = int(v.y), z = int(v.z); + for(;;) + { + DOWNOCTREE(shadowent, ); + + cube &c = *lc; + ivec lo(x&(~0U<clipcache[int(&c - worldroot)&(MAXCLIPPLANES-1)]; + if(p.owner != &c || p.version != cache->version) { p.owner = &c; p.version = cache->version; genclipplanes(c, lo, 1<= 0) + { + if(c.texture[side]==DEFAULT_SKY && mode&RAY_SKIPSKY) + { + if(mode&RAY_SKYTEX) return radius; + } + else return dist+max(enterdist+0.1f, 0.0f); + } + } + } + + nextcube: + FINDCLOSEST(side = O_RIGHT - lsizemask.x, side = O_FRONT - lsizemask.y, side = O_TOP - lsizemask.z); + + if(dist>=radius) return dist; + + UPOCTREE(return radius); + } } float rayent(const vec &o, const vec &ray, float radius, int mode, int size, int &orient, int &ent) { - hitent = -1; - hitentdist = radius; - hitorient = -1; - float dist = raycube(o, ray, radius, mode, size); - if((mode&RAY_ENTS) == RAY_ENTS) - { - float dent = disttooutsideent(o, ray, dist < 0 ? 1e16f : dist, mode, NULL); - if(dent < 1e15f && (dist < 0 || dent < dist)) dist = dent; - } - orient = hitorient; - ent = hitentdist == dist ? hitent : -1; - return dist; + hitent = -1; + hitentdist = radius; + hitorient = -1; + float dist = raycube(o, ray, radius, mode, size); + if((mode&RAY_ENTS) == RAY_ENTS) + { + float dent = disttooutsideent(o, ray, dist < 0 ? 1e16f : dist, mode, NULL); + if(dent < 1e15f && (dist < 0 || dent < dist)) dist = dent; + } + orient = hitorient; + ent = hitentdist == dist ? hitent : -1; + return dist; } float raycubepos(const vec &o, const vec &ray, vec &hitpos, float radius, int mode, int size) { - hitpos = ray; - float dist = raycube(o, ray, radius, mode, size); - if(radius>0 && dist>=radius) dist = radius; - hitpos.mul(dist).add(o); - return dist; + hitpos = ray; + float dist = raycube(o, ray, radius, mode, size); + if(radius>0 && dist>=radius) dist = radius; + hitpos.mul(dist).add(o); + return dist; } bool raycubelos(const vec &o, const vec &dest, vec &hitpos) { - vec ray(dest); - ray.sub(o); - float mag = ray.magnitude(); - ray.mul(1/mag); - float distance = raycubepos(o, ray, hitpos, mag, RAY_CLIPMAT|RAY_POLY); - return distance >= mag; + vec ray(dest); + ray.sub(o); + float mag = ray.magnitude(); + ray.mul(1/mag); + float distance = raycubepos(o, ray, hitpos, mag, RAY_CLIPMAT|RAY_POLY); + return distance >= mag; } float rayfloor(const vec &o, vec &floor, int mode, float radius) { - if(o.z<=0) return -1; - hitsurface = vec(0, 0, 1); - float dist = raycube(o, vec(0, 0, -1), radius, mode); - if(dist<0 || (radius>0 && dist>=radius)) return dist; - floor = hitsurface; - return dist; + if(o.z<=0) return -1; + hitsurface = vec(0, 0, 1); + float dist = raycube(o, vec(0, 0, -1), radius, mode); + if(dist<0 || (radius>0 && dist>=radius)) return dist; + floor = hitsurface; + return dist; } ///////////////////////// entity collision /////////////////////////////////////////////// @@ -479,96 +479,96 @@ extern const float GRAVITY = 200.0f; bool ellipseboxcollide(physent *d, const vec &dir, const vec &o, const vec ¢er, float yaw, float xr, float yr, float hi, float lo) { - float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye), - above = (d->o.z-d->eyeheight) - (o.z+center.z+hi); - if(below>=0 || above>=0) return false; - - vec yo(d->o); - yo.sub(o); - yo.rotate_around_z(-yaw*RAD); - yo.sub(center); - - float dx = clamp(yo.x, -xr, xr) - yo.x, dy = clamp(yo.y, -yr, yr) - yo.y, - dist = sqrtf(dx*dx + dy*dy) - d->radius; - if(dist < 0) - { - int sx = yo.x <= -xr ? -1 : (yo.x >= xr ? 1 : 0), - sy = yo.y <= -yr ? -1 : (yo.y >= yr ? 1 : 0); - if(dist > (yo.z < 0 ? below : above) && (sx || sy)) - { - vec ydir(dir); - ydir.rotate_around_z(-yaw*RAD); - if(sx*yo.x - xr > sy*yo.y - yr) - { - if(dir.iszero() || sx*ydir.x < -1e-6f) - { - collidewall = vec(sx, 0, 0); - collidewall.rotate_around_z(yaw*RAD); - return true; - } - } - else if(dir.iszero() || sy*ydir.y < -1e-6f) - { - collidewall = vec(0, sy, 0); - collidewall.rotate_around_z(yaw*RAD); - return true; - } - } - if(yo.z < 0) - { - if(dir.iszero() || (dir.z > 0 && (d->type>=ENT_INANIMATE || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f))) - { - collidewall = vec(0, 0, -1); - return true; - } - } - else if(dir.iszero() || (dir.z < 0 && (d->type>=ENT_INANIMATE || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f))) - { - collidewall = vec(0, 0, 1); - return true; - } - collideinside++; - } - return false; + float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye), + above = (d->o.z-d->eyeheight) - (o.z+center.z+hi); + if(below>=0 || above>=0) return false; + + vec yo(d->o); + yo.sub(o); + yo.rotate_around_z(-yaw*RAD); + yo.sub(center); + + float dx = clamp(yo.x, -xr, xr) - yo.x, dy = clamp(yo.y, -yr, yr) - yo.y, + dist = sqrtf(dx*dx + dy*dy) - d->radius; + if(dist < 0) + { + int sx = yo.x <= -xr ? -1 : (yo.x >= xr ? 1 : 0), + sy = yo.y <= -yr ? -1 : (yo.y >= yr ? 1 : 0); + if(dist > (yo.z < 0 ? below : above) && (sx || sy)) + { + vec ydir(dir); + ydir.rotate_around_z(-yaw*RAD); + if(sx*yo.x - xr > sy*yo.y - yr) + { + if(dir.iszero() || sx*ydir.x < -1e-6f) + { + collidewall = vec(sx, 0, 0); + collidewall.rotate_around_z(yaw*RAD); + return true; + } + } + else if(dir.iszero() || sy*ydir.y < -1e-6f) + { + collidewall = vec(0, sy, 0); + collidewall.rotate_around_z(yaw*RAD); + return true; + } + } + if(yo.z < 0) + { + if(dir.iszero() || (dir.z > 0 && (d->type>=ENT_INANIMATE || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f))) + { + collidewall = vec(0, 0, -1); + return true; + } + } + else if(dir.iszero() || (dir.z < 0 && (d->type>=ENT_INANIMATE || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f))) + { + collidewall = vec(0, 0, 1); + return true; + } + collideinside++; + } + return false; } bool ellipsecollide(physent *d, const vec &dir, const vec &o, const vec ¢er, float yaw, float xr, float yr, float hi, float lo) { - float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye), - above = (d->o.z-d->eyeheight) - (o.z+center.z+hi); - if(below>=0 || above>=0) return false; - vec yo(center); - yo.rotate_around_z(yaw*RAD); - yo.add(o); - float x = yo.x - d->o.x, y = yo.y - d->o.y; - float angle = atan2f(y, x), dangle = angle-(d->yaw+90)*RAD, eangle = angle-(yaw+90)*RAD; - float dx = d->xradius*cosf(dangle), dy = d->yradius*sinf(dangle); - float ex = xr*cosf(eangle), ey = yr*sinf(eangle); - float dist = sqrtf(x*x + y*y) - sqrtf(dx*dx + dy*dy) - sqrtf(ex*ex + ey*ey); - if(dist < 0) - { - if(dist > (d->o.z < yo.z ? below : above) && (dir.iszero() || x*dir.x + y*dir.y > 0)) - { - collidewall = vec(-x, -y, 0); - if(!collidewall.iszero()) collidewall.normalize(); - return true; - } - if(d->o.z < yo.z) - { - if(dir.iszero() || (dir.z > 0 && (d->type>=ENT_INANIMATE || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f))) - { - collidewall = vec(0, 0, -1); - return true; - } - } - else if(dir.iszero() || (dir.z < 0 && (d->type>=ENT_INANIMATE || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f))) - { - collidewall = vec(0, 0, 1); - return true; - } - collideinside++; - } - return false; + float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye), + above = (d->o.z-d->eyeheight) - (o.z+center.z+hi); + if(below>=0 || above>=0) return false; + vec yo(center); + yo.rotate_around_z(yaw*RAD); + yo.add(o); + float x = yo.x - d->o.x, y = yo.y - d->o.y; + float angle = atan2f(y, x), dangle = angle-(d->yaw+90)*RAD, eangle = angle-(yaw+90)*RAD; + float dx = d->xradius*cosf(dangle), dy = d->yradius*sinf(dangle); + float ex = xr*cosf(eangle), ey = yr*sinf(eangle); + float dist = sqrtf(x*x + y*y) - sqrtf(dx*dx + dy*dy) - sqrtf(ex*ex + ey*ey); + if(dist < 0) + { + if(dist > (d->o.z < yo.z ? below : above) && (dir.iszero() || x*dir.x + y*dir.y > 0)) + { + collidewall = vec(-x, -y, 0); + if(!collidewall.iszero()) collidewall.normalize(); + return true; + } + if(d->o.z < yo.z) + { + if(dir.iszero() || (dir.z > 0 && (d->type>=ENT_INANIMATE || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f))) + { + collidewall = vec(0, 0, -1); + return true; + } + } + else if(dir.iszero() || (dir.z < 0 && (d->type>=ENT_INANIMATE || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f))) + { + collidewall = vec(0, 0, 1); + return true; + } + collideinside++; + } + return false; } #define DYNENTCACHESIZE 1024 @@ -577,16 +577,16 @@ static uint dynentframe = 0; static struct dynentcacheentry { - int x, y; - uint frame; - vector dynents; + int x, y; + uint frame; + vector dynents; } dynentcache[DYNENTCACHESIZE]; void cleardynentcache() { - dynentframe++; - if(!dynentframe || dynentframe == 1) loopi(DYNENTCACHESIZE) dynentcache[i].frame = 0; - if(!dynentframe) dynentframe = 1; + dynentframe++; + if(!dynentframe || dynentframe == 1) loopi(DYNENTCACHESIZE) dynentcache[i].frame = 0; + if(!dynentframe) dynentframe = 1; } VARF(dynentsize, 4, 7, 12, cleardynentcache()); @@ -595,989 +595,962 @@ VARF(dynentsize, 4, 7, 12, cleardynentcache()); const vector &checkdynentcache(int x, int y) { - dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)]; - if(dec.x == x && dec.y == y && dec.frame == dynentframe) return dec.dynents; - dec.x = x; - dec.y = y; - dec.frame = dynentframe; - dec.dynents.shrink(0); - int numdyns = game::numdynents(), dsize = 1<state != CS_ALIVE || - d->o.x+d->radius <= dx || d->o.x-d->radius >= dx+dsize || - d->o.y+d->radius <= dy || d->o.y-d->radius >= dy+dsize) - continue; - dec.dynents.add(d); - } - return dec.dynents; + dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)]; + if(dec.x == x && dec.y == y && dec.frame == dynentframe) return dec.dynents; + dec.x = x; + dec.y = y; + dec.frame = dynentframe; + dec.dynents.shrink(0); + int numdyns = game::numdynents(), dsize = 1<state != CS_ALIVE || + d->o.x+d->radius <= dx || d->o.x-d->radius >= dx+dsize || + d->o.y+d->radius <= dy || d->o.y-d->radius >= dy+dsize) + continue; + dec.dynents.add(d); + } + return dec.dynents; } #define loopdynentcache(curx, cury, o, radius) \ - for(int curx = max(int(o.x-radius), 0)>>dynentsize, endx = min(int(o.x+radius), worldsize-1)>>dynentsize; curx <= endx; curx++) \ - for(int cury = max(int(o.y-radius), 0)>>dynentsize, endy = min(int(o.y+radius), worldsize-1)>>dynentsize; cury <= endy; cury++) + for(int curx = max(int(o.x-radius), 0)>>dynentsize, endx = min(int(o.x+radius), worldsize-1)>>dynentsize; curx <= endx; curx++) \ + for(int cury = max(int(o.y-radius), 0)>>dynentsize, endy = min(int(o.y+radius), worldsize-1)>>dynentsize; cury <= endy; cury++) void updatedynentcache(physent *d) { - loopdynentcache(x, y, d->o, d->radius) - { - dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)]; - if(dec.x != x || dec.y != y || dec.frame != dynentframe || dec.dynents.find(d) >= 0) continue; - dec.dynents.add(d); - } + loopdynentcache(x, y, d->o, d->radius) + { + dynentcacheentry &dec = dynentcache[DYNENTHASH(x, y)]; + if(dec.x != x || dec.y != y || dec.frame != dynentframe || dec.dynents.find(d) >= 0) continue; + dec.dynents.add(d); + } } bool overlapsdynent(const vec &o, float radius) { - loopdynentcache(x, y, o, radius) - { - const vector &dynents = checkdynentcache(x, y); - loopv(dynents) - { - physent *d = dynents[i]; - if(o.dist(d->o)-d->radius < radius) return true; - } - } - return false; + loopdynentcache(x, y, o, radius) + { + const vector &dynents = checkdynentcache(x, y); + loopv(dynents) + { + physent *d = dynents[i]; + if(o.dist(d->o)-d->radius < radius) return true; + } + } + return false; } template static inline bool plcollide(physent *d, const vec &dir, physent *o) { - E entvol(d); - O obvol(o); - vec cp; - if(mpr::collide(entvol, obvol, NULL, NULL, &cp)) - { - vec wn = vec(cp).sub(obvol.center()); - collidewall = obvol.contactface(wn, dir.iszero() ? vec(wn).neg() : dir); - if(!collidewall.iszero()) return true; - collideinside++; - } - return false; + E entvol(d); + O obvol(o); + vec cp; + if(mpr::collide(entvol, obvol, NULL, NULL, &cp)) + { + vec wn = vec(cp).sub(obvol.center()); + collidewall = obvol.contactface(wn, dir.iszero() ? vec(wn).neg() : dir); + if(!collidewall.iszero()) return true; + collideinside++; + } + return false; } static inline bool plcollide(physent *d, const vec &dir, physent *o) { - switch(d->collidetype) - { - case COLLIDE_ELLIPSE: - case COLLIDE_ELLIPSE_PRECISE: - if(o->collidetype == COLLIDE_OBB) return ellipseboxcollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight); - else return ellipsecollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight); - case COLLIDE_OBB: - if(o->collidetype == COLLIDE_OBB) return plcollide(d, dir, o); - else return plcollide(d, dir, o); - default: return false; - } -} - -bool plcollide(physent *d, const vec &dir, bool insideplayercol) // collide with player or monster -{ - if(d->type==ENT_CAMERA || d->state!=CS_ALIVE) return false; - int lastinside = collideinside; - physent *insideplayer = NULL; - loopdynentcache(x, y, d->o, d->radius) - { - const vector &dynents = checkdynentcache(x, y); - loopv(dynents) - { - physent *o = dynents[i]; - if(o==d || d->o.reject(o->o, d->radius+o->radius)) continue; - if(plcollide(d, dir, o)) - { - collideplayer = o; - game::dynentcollide(d, o, collidewall); - return true; - } - if(collideinside > lastinside) - { - lastinside = collideinside; - insideplayer = o; - } - } - } - if(insideplayer && insideplayercol) - { - collideplayer = insideplayer; - game::dynentcollide(d, insideplayer, vec(0, 0, 0)); - return true; - } - return false; + switch(d->collidetype) + { + case COLLIDE_ELLIPSE: + case COLLIDE_ELLIPSE_PRECISE: + if(o->collidetype == COLLIDE_OBB) return ellipseboxcollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight); + else return ellipsecollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight); + case COLLIDE_OBB: + if(o->collidetype == COLLIDE_OBB) return plcollide(d, dir, o); + else return plcollide(d, dir, o); + default: return false; + } +} + +bool plcollide(physent *d, const vec &dir, bool insideplayercol) // collide with player or monster +{ + if(d->type==ENT_CAMERA || d->state!=CS_ALIVE) return false; + int lastinside = collideinside; + physent *insideplayer = NULL; + loopdynentcache(x, y, d->o, d->radius) + { + const vector &dynents = checkdynentcache(x, y); + loopv(dynents) + { + physent *o = dynents[i]; + if(o==d || d->o.reject(o->o, d->radius+o->radius)) continue; + if(plcollide(d, dir, o)) + { + collideplayer = o; + game::dynentcollide(d, o, collidewall); + return true; + } + if(collideinside > lastinside) + { + lastinside = collideinside; + insideplayer = o; + } + } + } + if(insideplayer && insideplayercol) + { + collideplayer = insideplayer; + game::dynentcollide(d, insideplayer, vec(0, 0, 0)); + return true; + } + return false; } void rotatebb(vec ¢er, vec &radius, int yaw) { - if(yaw < 0) yaw = 360 + yaw%360; - else if(yaw >= 360) yaw %= 360; - const vec2 &rot = sincos360[yaw]; - vec2 oldcenter(center), oldradius(radius); - center.x = oldcenter.x*rot.x - oldcenter.y*rot.y; - center.y = oldcenter.y*rot.x + oldcenter.x*rot.y; - radius.x = fabs(oldradius.x*rot.x) + fabs(oldradius.y*rot.y); - radius.y = fabs(oldradius.y*rot.x) + fabs(oldradius.x*rot.y); + if(yaw < 0) yaw = 360 + yaw%360; + else if(yaw >= 360) yaw %= 360; + const vec2 &rot = sincos360[yaw]; + vec2 oldcenter(center), oldradius(radius); + center.x = oldcenter.x*rot.x - oldcenter.y*rot.y; + center.y = oldcenter.y*rot.x + oldcenter.x*rot.y; + radius.x = fabs(oldradius.x*rot.x) + fabs(oldradius.y*rot.y); + radius.y = fabs(oldradius.y*rot.x) + fabs(oldradius.x*rot.y); } template static inline bool mmcollide(physent *d, const vec &dir, const extentity &e, const vec ¢er, const vec &radius, float yaw) { - E entvol(d); - M mdlvol(e.o, center, radius, yaw); - vec cp; - if(mpr::collide(entvol, mdlvol, NULL, NULL, &cp)) - { - vec wn = vec(cp).sub(mdlvol.center()); - collidewall = mdlvol.contactface(wn, dir.iszero() ? vec(wn).neg() : dir); - if(!collidewall.iszero()) return true; - collideinside++; - } - return false; -} - -bool mmcollide(physent *d, const vec &dir, octaentities &oc) // collide with a mapmodel -{ - const vector &ents = entities::getents(); - loopv(oc.mapmodels) - { - extentity &e = *ents[oc.mapmodels[i]]; - if(e.flags&EF_NOCOLLIDE) continue; - model *m = loadmapmodel(e.attr2); - if(!m || !m->collide) continue; - - vec center, radius; - float rejectradius = m->collisionbox(center, radius); - if(d->o.reject(e.o, d->radius + rejectradius)) continue; - - float yaw = e.attr1; - switch(d->collidetype) - { - case COLLIDE_ELLIPSE: - case COLLIDE_ELLIPSE_PRECISE: - if(m->ellipsecollide) - { - if(ellipsecollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z)) return true; - } - else if(ellipseboxcollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z)) return true; - break; - case COLLIDE_OBB: - if(m->ellipsecollide) - { - if(mmcollide(d, dir, e, center, radius, yaw)) return true; - } - else if(mmcollide(d, dir, e, center, radius, yaw)) return true; - break; - default: continue; - } - } - return false; + E entvol(d); + M mdlvol(e.o, center, radius, yaw); + vec cp; + if(mpr::collide(entvol, mdlvol, NULL, NULL, &cp)) + { + vec wn = vec(cp).sub(mdlvol.center()); + collidewall = mdlvol.contactface(wn, dir.iszero() ? vec(wn).neg() : dir); + if(!collidewall.iszero()) return true; + collideinside++; + } + return false; +} + +bool mmcollide(physent *d, const vec &dir, octaentities &oc) // collide with a mapmodel +{ + const vector &ents = entities::getents(); + loopv(oc.mapmodels) + { + extentity &e = *ents[oc.mapmodels[i]]; + if(e.flags&EF_NOCOLLIDE) continue; + model *m = loadmapmodel(e.attr2); + if(!m || !m->collide) continue; + + vec center, radius; + float rejectradius = m->collisionbox(center, radius); + if(d->o.reject(e.o, d->radius + rejectradius)) continue; + + float yaw = e.attr1; + switch(d->collidetype) + { + case COLLIDE_ELLIPSE: + case COLLIDE_ELLIPSE_PRECISE: + if(m->ellipsecollide) + { + if(ellipsecollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z)) return true; + } + else if(ellipseboxcollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z)) return true; + break; + case COLLIDE_OBB: + if(m->ellipsecollide) + { + if(mmcollide(d, dir, e, center, radius, yaw)) return true; + } + else if(mmcollide(d, dir, e, center, radius, yaw)) return true; + break; + default: continue; + } + } + return false; } template static bool fuzzycollidesolid(physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with solid cube geometry { - int crad = size/2; - if(fabs(d->o.x - co.x - crad) > d->radius + crad || fabs(d->o.y - co.y - crad) > d->radius + crad || - d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size) - return false; - - E entvol(d); - collidewall = vec(0, 0, 0); - float bestdist = -1e10f; - int visible = isentirelysolid(c) ? c.visible : 0xFF; - #define CHECKSIDE(side, distval, dotval, margin, normal) if(visible&(1< 0) return false; \ - if(dist <= bestdist) continue; \ - if(!dir.iszero()) \ - { \ - if(dotval >= -cutoff*dir.magnitude()) continue; \ - if(d->typeo.x + d->radius), -dir.x, -d->radius, vec(-1, 0, 0)); - CHECKSIDE(O_RIGHT, d->o.x - d->radius - (co.x + size), dir.x, -d->radius, vec(1, 0, 0)); - CHECKSIDE(O_BACK, co.y - (d->o.y + d->radius), -dir.y, -d->radius, vec(0, -1, 0)); - CHECKSIDE(O_FRONT, d->o.y - d->radius - (co.y + size), dir.y, -d->radius, vec(0, 1, 0)); - CHECKSIDE(O_BOTTOM, co.z - (d->o.z + d->aboveeye), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); - CHECKSIDE(O_TOP, d->o.z - d->eyeheight - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); - - if(collidewall.iszero()) - { - collideinside++; - return false; - } - return true; + int crad = size/2; + if(fabs(d->o.x - co.x - crad) > d->radius + crad || fabs(d->o.y - co.y - crad) > d->radius + crad || + d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size) + return false; + + E entvol(d); + collidewall = vec(0, 0, 0); + float bestdist = -1e10f; + int visible = isentirelysolid(c) ? c.visible : 0xFF; + #define CHECKSIDE(side, distval, dotval, margin, normal) if(visible&(1< 0) return false; \ + if(dist <= bestdist) continue; \ + if(!dir.iszero()) \ + { \ + if(dotval >= -cutoff*dir.magnitude()) continue; \ + if(d->typeo.x + d->radius), -dir.x, -d->radius, vec(-1, 0, 0)); + CHECKSIDE(O_RIGHT, d->o.x - d->radius - (co.x + size), dir.x, -d->radius, vec(1, 0, 0)); + CHECKSIDE(O_BACK, co.y - (d->o.y + d->radius), -dir.y, -d->radius, vec(0, -1, 0)); + CHECKSIDE(O_FRONT, d->o.y - d->radius - (co.y + size), dir.y, -d->radius, vec(0, 1, 0)); + CHECKSIDE(O_BOTTOM, co.z - (d->o.z + d->aboveeye), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); + CHECKSIDE(O_TOP, d->o.z - d->eyeheight - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); + + if(collidewall.iszero()) + { + collideinside++; + return false; + } + return true; } template static inline bool clampcollide(const clipplanes &p, const E &entvol, const plane &w, const vec &pw) { - if(w.x && (w.y || w.z) && fabs(pw.x - p.o.x) > p.r.x) - { - vec c = entvol.center(); - float fv = pw.x < p.o.x ? p.o.x-p.r.x : p.o.x+p.r.x, fdist = (w.x*fv + w.y*c.y + w.z*c.z + w.offset) / (w.y*w.y + w.z*w.z); - vec fdir(fv - c.x, -w.y*fdist, -w.z*fdist); - if((pw.y-c.y-fdir.y)*w.y + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen()) return true; - } - if(w.y && (w.x || w.z) && fabs(pw.y - p.o.y) > p.r.y) - { - vec c = entvol.center(); - float fv = pw.y < p.o.y ? p.o.y-p.r.y : p.o.y+p.r.y, fdist = (w.x*c.x + w.y*fv + w.z*c.z + w.offset) / (w.x*w.x + w.z*w.z); - vec fdir(-w.x*fdist, fv - c.y, -w.z*fdist); - if((pw.x-c.x-fdir.x)*w.x + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen()) return true; - } - if(w.z && (w.x || w.y) && fabs(pw.z - p.o.z) > p.r.z) - { - vec c = entvol.center(); - float fv = pw.z < p.o.z ? p.o.z-p.r.z : p.o.z+p.r.z, fdist = (w.x*c.x + w.y*c.y + w.z*fv + w.offset) / (w.x*w.x + w.y*w.y); - vec fdir(-w.x*fdist, -w.y*fdist, fv - c.z); - if((pw.x-c.x-fdir.x)*w.x + (pw.y-c.y-fdir.y)*w.y >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen()) return true; - } - return false; + if(w.x && (w.y || w.z) && fabs(pw.x - p.o.x) > p.r.x) + { + vec c = entvol.center(); + float fv = pw.x < p.o.x ? p.o.x-p.r.x : p.o.x+p.r.x, fdist = (w.x*fv + w.y*c.y + w.z*c.z + w.offset) / (w.y*w.y + w.z*w.z); + vec fdir(fv - c.x, -w.y*fdist, -w.z*fdist); + if((pw.y-c.y-fdir.y)*w.y + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen()) return true; + } + if(w.y && (w.x || w.z) && fabs(pw.y - p.o.y) > p.r.y) + { + vec c = entvol.center(); + float fv = pw.y < p.o.y ? p.o.y-p.r.y : p.o.y+p.r.y, fdist = (w.x*c.x + w.y*fv + w.z*c.z + w.offset) / (w.x*w.x + w.z*w.z); + vec fdir(-w.x*fdist, fv - c.y, -w.z*fdist); + if((pw.x-c.x-fdir.x)*w.x + (pw.z-c.z-fdir.z)*w.z >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen()) return true; + } + if(w.z && (w.x || w.y) && fabs(pw.z - p.o.z) > p.r.z) + { + vec c = entvol.center(); + float fv = pw.z < p.o.z ? p.o.z-p.r.z : p.o.z+p.r.z, fdist = (w.x*c.x + w.y*c.y + w.z*fv + w.offset) / (w.x*w.x + w.y*w.y); + vec fdir(-w.x*fdist, -w.y*fdist, fv - c.z); + if((pw.x-c.x-fdir.x)*w.x + (pw.y-c.y-fdir.y)*w.y >= 0 && entvol.supportpoint(fdir).squaredist(c) < fdir.squaredlen()) return true; + } + return false; } template static bool fuzzycollideplanes(physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with deformed cube geometry { - const clipplanes &p = getclipplanes(c, co, size); - - if(fabs(d->o.x - p.o.x) > p.r.x + d->radius || fabs(d->o.y - p.o.y) > p.r.y + d->radius || - d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z) - return false; - - collidewall = vec(0, 0, 0); - float bestdist = -1e10f; - int visible = p.visible; - CHECKSIDE(O_LEFT, p.o.x - p.r.x - (d->o.x + d->radius), -dir.x, -d->radius, vec(-1, 0, 0)); - CHECKSIDE(O_RIGHT, d->o.x - d->radius - (p.o.x + p.r.x), dir.x, -d->radius, vec(1, 0, 0)); - CHECKSIDE(O_BACK, p.o.y - p.r.y - (d->o.y + d->radius), -dir.y, -d->radius, vec(0, -1, 0)); - CHECKSIDE(O_FRONT, d->o.y - d->radius - (p.o.y + p.r.y), dir.y, -d->radius, vec(0, 1, 0)); - CHECKSIDE(O_BOTTOM, p.o.z - p.r.z - (d->o.z + d->aboveeye), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); - CHECKSIDE(O_TOP, d->o.z - d->eyeheight - (p.o.z + p.r.z), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); - - E entvol(d); - int bestplane = -1; - loopi(p.size) - { - const plane &w = p.p[i]; - vec pw = entvol.supportpoint(vec(w).neg()); - float dist = w.dist(pw); - if(dist >= 0) return false; - if(dist <= bestdist) continue; - bestplane = -1; - bestdist = dist; - if(!dir.iszero()) - { - if(w.dot(dir) >= -cutoff*dir.magnitude()) continue; - if(d->typezmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) : - ((dir.x*w.x < 0 || dir.y*w.y < 0) ? -d->radius : 0))) - continue; - } - if(clampcollide(p, entvol, w, pw)) continue; - bestplane = i; - } - if(bestplane >= 0) collidewall = p.p[bestplane]; - else if(collidewall.iszero()) - { - collideinside++; - return false; - } - return true; + const clipplanes &p = getclipplanes(c, co, size); + + if(fabs(d->o.x - p.o.x) > p.r.x + d->radius || fabs(d->o.y - p.o.y) > p.r.y + d->radius || + d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z) + return false; + + collidewall = vec(0, 0, 0); + float bestdist = -1e10f; + int visible = p.visible; + CHECKSIDE(O_LEFT, p.o.x - p.r.x - (d->o.x + d->radius), -dir.x, -d->radius, vec(-1, 0, 0)); + CHECKSIDE(O_RIGHT, d->o.x - d->radius - (p.o.x + p.r.x), dir.x, -d->radius, vec(1, 0, 0)); + CHECKSIDE(O_BACK, p.o.y - p.r.y - (d->o.y + d->radius), -dir.y, -d->radius, vec(0, -1, 0)); + CHECKSIDE(O_FRONT, d->o.y - d->radius - (p.o.y + p.r.y), dir.y, -d->radius, vec(0, 1, 0)); + CHECKSIDE(O_BOTTOM, p.o.z - p.r.z - (d->o.z + d->aboveeye), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); + CHECKSIDE(O_TOP, d->o.z - d->eyeheight - (p.o.z + p.r.z), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); + + E entvol(d); + int bestplane = -1; + loopi(p.size) + { + const plane &w = p.p[i]; + vec pw = entvol.supportpoint(vec(w).neg()); + float dist = w.dist(pw); + if(dist >= 0) return false; + if(dist <= bestdist) continue; + bestplane = -1; + bestdist = dist; + if(!dir.iszero()) + { + if(w.dot(dir) >= -cutoff*dir.magnitude()) continue; + if(d->typezmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) : + ((dir.x*w.x < 0 || dir.y*w.y < 0) ? -d->radius : 0))) + continue; + } + if(clampcollide(p, entvol, w, pw)) continue; + bestplane = i; + } + if(bestplane >= 0) collidewall = p.p[bestplane]; + else if(collidewall.iszero()) + { + collideinside++; + return false; + } + return true; } template static bool cubecollidesolid(physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with solid cube geometry { - int crad = size/2; - if(fabs(d->o.x - co.x - crad) > d->radius + crad || fabs(d->o.y - co.y - crad) > d->radius + crad || - d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size) - return false; - - E entvol(d); - bool collided = mpr::collide(mpr::SolidCube(co, size), entvol); - if(!collided) return false; - - collidewall = vec(0, 0, 0); - float bestdist = -1e10f; - int visible = isentirelysolid(c) ? c.visible : 0xFF; - CHECKSIDE(O_LEFT, co.x - entvol.right(), -dir.x, -d->radius, vec(-1, 0, 0)); - CHECKSIDE(O_RIGHT, entvol.left() - (co.x + size), dir.x, -d->radius, vec(1, 0, 0)); - CHECKSIDE(O_BACK, co.y - entvol.front(), -dir.y, -d->radius, vec(0, -1, 0)); - CHECKSIDE(O_FRONT, entvol.back() - (co.y + size), dir.y, -d->radius, vec(0, 1, 0)); - CHECKSIDE(O_BOTTOM, co.z - entvol.top(), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); - CHECKSIDE(O_TOP, entvol.bottom() - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); - - if(collidewall.iszero()) - { - collideinside++; - return false; - } - return true; + int crad = size/2; + if(fabs(d->o.x - co.x - crad) > d->radius + crad || fabs(d->o.y - co.y - crad) > d->radius + crad || + d->o.z + d->aboveeye < co.z || d->o.z - d->eyeheight > co.z + size) + return false; + + E entvol(d); + bool collided = mpr::collide(mpr::SolidCube(co, size), entvol); + if(!collided) return false; + + collidewall = vec(0, 0, 0); + float bestdist = -1e10f; + int visible = isentirelysolid(c) ? c.visible : 0xFF; + CHECKSIDE(O_LEFT, co.x - entvol.right(), -dir.x, -d->radius, vec(-1, 0, 0)); + CHECKSIDE(O_RIGHT, entvol.left() - (co.x + size), dir.x, -d->radius, vec(1, 0, 0)); + CHECKSIDE(O_BACK, co.y - entvol.front(), -dir.y, -d->radius, vec(0, -1, 0)); + CHECKSIDE(O_FRONT, entvol.back() - (co.y + size), dir.y, -d->radius, vec(0, 1, 0)); + CHECKSIDE(O_BOTTOM, co.z - entvol.top(), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); + CHECKSIDE(O_TOP, entvol.bottom() - (co.z + size), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); + + if(collidewall.iszero()) + { + collideinside++; + return false; + } + return true; } template static bool cubecollideplanes(physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size) // collide with deformed cube geometry { - const clipplanes &p = getclipplanes(c, co, size); - - if(fabs(d->o.x - p.o.x) > p.r.x + d->radius || fabs(d->o.y - p.o.y) > p.r.y + d->radius || - d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z) - return false; - - E entvol(d); - bool collided = mpr::collide(mpr::CubePlanes(p), entvol); - if(!collided) return false; - - collidewall = vec(0, 0, 0); - float bestdist = -1e10f; - int visible = p.visible; - CHECKSIDE(O_LEFT, p.o.x - p.r.x - entvol.right(), -dir.x, -d->radius, vec(-1, 0, 0)); - CHECKSIDE(O_RIGHT, entvol.left() - (p.o.x + p.r.x), dir.x, -d->radius, vec(1, 0, 0)); - CHECKSIDE(O_BACK, p.o.y - p.r.y - entvol.front(), -dir.y, -d->radius, vec(0, -1, 0)); - CHECKSIDE(O_FRONT, entvol.back() - (p.o.y + p.r.y), dir.y, -d->radius, vec(0, 1, 0)); - CHECKSIDE(O_BOTTOM, p.o.z - p.r.z - entvol.top(), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); - CHECKSIDE(O_TOP, entvol.bottom() - (p.o.z + p.r.z), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); - - int bestplane = -1; - loopi(p.size) - { - const plane &w = p.p[i]; - vec pw = entvol.supportpoint(vec(w).neg()); - float dist = w.dist(pw); - if(dist <= bestdist) continue; - bestplane = -1; - bestdist = dist; - if(!dir.iszero()) - { - if(w.dot(dir) >= -cutoff*dir.magnitude()) continue; - if(d->typezmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) : - ((dir.x*w.x < 0 || dir.y*w.y < 0) ? -d->radius : 0))) - continue; - } - if(clampcollide(p, entvol, w, pw)) continue; - bestplane = i; - } - if(bestplane >= 0) collidewall = p.p[bestplane]; - else if(collidewall.iszero()) - { - collideinside++; - return false; - } - return true; + const clipplanes &p = getclipplanes(c, co, size); + + if(fabs(d->o.x - p.o.x) > p.r.x + d->radius || fabs(d->o.y - p.o.y) > p.r.y + d->radius || + d->o.z + d->aboveeye < p.o.z - p.r.z || d->o.z - d->eyeheight > p.o.z + p.r.z) + return false; + + E entvol(d); + bool collided = mpr::collide(mpr::CubePlanes(p), entvol); + if(!collided) return false; + + collidewall = vec(0, 0, 0); + float bestdist = -1e10f; + int visible = p.visible; + CHECKSIDE(O_LEFT, p.o.x - p.r.x - entvol.right(), -dir.x, -d->radius, vec(-1, 0, 0)); + CHECKSIDE(O_RIGHT, entvol.left() - (p.o.x + p.r.x), dir.x, -d->radius, vec(1, 0, 0)); + CHECKSIDE(O_BACK, p.o.y - p.r.y - entvol.front(), -dir.y, -d->radius, vec(0, -1, 0)); + CHECKSIDE(O_FRONT, entvol.back() - (p.o.y + p.r.y), dir.y, -d->radius, vec(0, 1, 0)); + CHECKSIDE(O_BOTTOM, p.o.z - p.r.z - entvol.top(), -dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/4.0f, vec(0, 0, -1)); + CHECKSIDE(O_TOP, entvol.bottom() - (p.o.z + p.r.z), dir.z, d->zmargin-(d->eyeheight+d->aboveeye)/3.0f, vec(0, 0, 1)); + + int bestplane = -1; + loopi(p.size) + { + const plane &w = p.p[i]; + vec pw = entvol.supportpoint(vec(w).neg()); + float dist = w.dist(pw); + if(dist <= bestdist) continue; + bestplane = -1; + bestdist = dist; + if(!dir.iszero()) + { + if(w.dot(dir) >= -cutoff*dir.magnitude()) continue; + if(d->typezmargin-(d->eyeheight+d->aboveeye)/(dir.z < 0 ? 3.0f : 4.0f) : + ((dir.x*w.x < 0 || dir.y*w.y < 0) ? -d->radius : 0))) + continue; + } + if(clampcollide(p, entvol, w, pw)) continue; + bestplane = i; + } + if(bestplane >= 0) collidewall = p.p[bestplane]; + else if(collidewall.iszero()) + { + collideinside++; + return false; + } + return true; } static inline bool cubecollide(physent *d, const vec &dir, float cutoff, const cube &c, const ivec &co, int size, bool solid) { - switch(d->collidetype) - { - case COLLIDE_OBB: - if(isentirelysolid(c) || solid) return cubecollidesolid(d, dir, cutoff, c, co, size); - else return cubecollideplanes(d, dir, cutoff, c, co, size); - case COLLIDE_ELLIPSE: - if(isentirelysolid(c) || solid) return fuzzycollidesolid(d, dir, cutoff, c, co, size); - else return fuzzycollideplanes(d, dir, cutoff, c, co, size); - case COLLIDE_ELLIPSE_PRECISE: - if(isentirelysolid(c) || solid) return cubecollidesolid(d, dir, cutoff, c, co, size); - else return cubecollideplanes(d, dir, cutoff, c, co, size); - default: return false; - } + switch(d->collidetype) + { + case COLLIDE_OBB: + if(isentirelysolid(c) || solid) return cubecollidesolid(d, dir, cutoff, c, co, size); + else return cubecollideplanes(d, dir, cutoff, c, co, size); + case COLLIDE_ELLIPSE: + if(isentirelysolid(c) || solid) return fuzzycollidesolid(d, dir, cutoff, c, co, size); + else return fuzzycollideplanes(d, dir, cutoff, c, co, size); + case COLLIDE_ELLIPSE_PRECISE: + if(isentirelysolid(c) || solid) return cubecollidesolid(d, dir, cutoff, c, co, size); + else return cubecollideplanes(d, dir, cutoff, c, co, size); + default: return false; + } } static inline bool octacollide(physent *d, const vec &dir, float cutoff, const ivec &bo, const ivec &bs, const cube *c, const ivec &cor, int size) // collide with octants { - loopoctabox(cor, size, bo, bs) - { - if(c[i].ext && c[i].ext->ents) if(mmcollide(d, dir, *c[i].ext->ents)) return true; - ivec o(i, cor, size); - if(c[i].children) - { - if(octacollide(d, dir, cutoff, bo, bs, c[i].children, o, size>>1)) return true; - } - else - { - bool solid = false; - switch(c[i].material&MATF_CLIP) - { - case MAT_NOCLIP: continue; - case MAT_GAMECLIP: if(d->type==ENT_AI) solid = true; break; - case MAT_CLIP: if(isclipped(c[i].material&MATF_VOLUME) || d->typeents) if(mmcollide(d, dir, *c[i].ext->ents)) return true; + ivec o(i, cor, size); + if(c[i].children) + { + if(octacollide(d, dir, cutoff, bo, bs, c[i].children, o, size>>1)) return true; + } + else + { + bool solid = false; + switch(c[i].material&MATF_CLIP) + { + case MAT_NOCLIP: continue; + case MAT_GAMECLIP: if(d->type==ENT_AI) solid = true; break; + case MAT_CLIP: if(isclipped(c[i].material&MATF_VOLUME) || d->type= uint(worldsize)) - return octacollide(d, dir, cutoff, bo, bs, worldroot, ivec(0, 0, 0), worldsize>>1); - const cube *c = &worldroot[octastep(bo.x, bo.y, bo.z, scale)]; - if(c->ext && c->ext->ents && mmcollide(d, dir, *c->ext->ents)) return true; - scale--; - while(c->children && !(diff&(1<children[octastep(bo.x, bo.y, bo.z, scale)]; - if(c->ext && c->ext->ents && mmcollide(d, dir, *c->ext->ents)) return true; - scale--; - } - if(c->children) return octacollide(d, dir, cutoff, bo, bs, c->children, ivec(bo).mask(~((2<material&MATF_CLIP) - { - case MAT_NOCLIP: return false; - case MAT_GAMECLIP: if(d->type==ENT_AI) solid = true; break; - case MAT_CLIP: if(isclipped(c->material&MATF_VOLUME) || d->type= uint(worldsize)) + return octacollide(d, dir, cutoff, bo, bs, worldroot, ivec(0, 0, 0), worldsize>>1); + const cube *c = &worldroot[octastep(bo.x, bo.y, bo.z, scale)]; + if(c->ext && c->ext->ents && mmcollide(d, dir, *c->ext->ents)) return true; + scale--; + while(c->children && !(diff&(1<children[octastep(bo.x, bo.y, bo.z, scale)]; + if(c->ext && c->ext->ents && mmcollide(d, dir, *c->ext->ents)) return true; + scale--; + } + if(c->children) return octacollide(d, dir, cutoff, bo, bs, c->children, ivec(bo).mask(~((2<material&MATF_CLIP) + { + case MAT_NOCLIP: return false; + case MAT_GAMECLIP: if(d->type==ENT_AI) solid = true; break; + case MAT_CLIP: if(isclipped(c->material&MATF_VOLUME) || d->typeo.x-d->radius), int(d->o.y-d->radius), int(d->o.z-d->eyeheight)), - bs(int(d->o.x+d->radius), int(d->o.y+d->radius), int(d->o.z+d->aboveeye)); - bs.add(1); // guard space for rounding errors - return octacollide(d, dir, cutoff, bo, bs) || (playercol && plcollide(d, dir, insideplayercol)); + collideinside = 0; + collideplayer = NULL; + collidewall = vec(0, 0, 0); + ivec bo(int(d->o.x-d->radius), int(d->o.y-d->radius), int(d->o.z-d->eyeheight)), + bs(int(d->o.x+d->radius), int(d->o.y+d->radius), int(d->o.z+d->aboveeye)); + bs.add(1); // guard space for rounding errors + return octacollide(d, dir, cutoff, bo, bs) || (playercol && plcollide(d, dir, insideplayercol)); } void recalcdir(physent *d, const vec &oldvel, vec &dir) { - float speed = oldvel.magnitude(); - if(speed > 1e-6f) - { - float step = dir.magnitude(); - dir = d->vel; - dir.add(d->falling); - dir.mul(step/speed); - } + float speed = oldvel.magnitude(); + if(speed > 1e-6f) + { + float step = dir.magnitude(); + dir = d->vel; + dir.add(d->falling); + dir.mul(step/speed); + } } void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide) { - vec wall(obstacle); - if(foundfloor ? wall.z > 0 : slidecollide) - { - wall.z = 0; - if(!wall.iszero()) wall.normalize(); - } - vec oldvel(d->vel); - oldvel.add(d->falling); - d->vel.project(wall); - d->falling.project(wall); - recalcdir(d, oldvel, dir); + vec wall(obstacle); + if(foundfloor ? wall.z > 0 : slidecollide) + { + wall.z = 0; + if(!wall.iszero()) wall.normalize(); + } + vec oldvel(d->vel); + oldvel.add(d->falling); + d->vel.project(wall); + d->falling.project(wall); + recalcdir(d, oldvel, dir); } void switchfloor(physent *d, vec &dir, const vec &floor) { - if(floor.z >= FLOORZ) d->falling = vec(0, 0, 0); - - vec oldvel(d->vel); - oldvel.add(d->falling); - if(dir.dot(floor) >= 0) - { - if(d->physstate < PHYS_SLIDE || fabs(dir.dot(d->floor)) > 0.01f*dir.magnitude()) return; - d->vel.projectxy(floor, 0.0f); - } - else d->vel.projectxy(floor); - d->falling.project(floor); - recalcdir(d, oldvel, dir); + if(floor.z >= FLOORZ) d->falling = vec(0, 0, 0); + + vec oldvel(d->vel); + oldvel.add(d->falling); + if(dir.dot(floor) >= 0) + { + if(d->physstate < PHYS_SLIDE || fabs(dir.dot(d->floor)) > 0.01f*dir.magnitude()) return; + d->vel.projectxy(floor, 0.0f); + } + else d->vel.projectxy(floor); + d->falling.project(floor); + recalcdir(d, oldvel, dir); } bool trystepup(physent *d, vec &dir, const vec &obstacle, float maxstep, const vec &floor) { - vec old(d->o), stairdir = (obstacle.z >= 0 && obstacle.z < SLOPEZ ? vec(-obstacle.x, -obstacle.y, 0) : vec(dir.x, dir.y, 0)).rescale(1); - bool cansmooth = true; - /* check if there is space atop the stair to move to */ - if(d->physstate != PHYS_STEP_UP) - { - vec checkdir = stairdir; - checkdir.mul(0.1f); - checkdir.z += maxstep + 0.1f; - d->o.add(checkdir); - if(collide(d)) - { - d->o = old; - if(!collide(d, vec(0, 0, -1), SLOPEZ)) return false; - cansmooth = false; - } - } - - if(cansmooth) - { - vec checkdir = stairdir; - checkdir.z += 1; - checkdir.mul(maxstep); - d->o = old; - d->o.add(checkdir); - int scale = 2; - if(collide(d, checkdir)) - { - if(!collide(d, vec(0, 0, -1), SLOPEZ)) - { - d->o = old; - return false; - } - d->o.add(checkdir); - if(collide(d, vec(0, 0, -1), SLOPEZ)) scale = 1; - } - if(scale != 1) - { - d->o = old; - d->o.sub(checkdir.mul(vec(2, 2, 1))); - if(!collide(d, vec(0, 0, -1), SLOPEZ)) scale = 1; - } - - d->o = old; - vec smoothdir(dir.x, dir.y, 0); - float magxy = smoothdir.magnitude(); - if(magxy > 1e-9f) - { - if(magxy > scale*dir.z) - { - smoothdir.mul(1/magxy); - smoothdir.z = 1.0f/scale; - smoothdir.mul(dir.magnitude()/smoothdir.magnitude()); - } - else smoothdir.z = dir.z; - d->o.add(smoothdir); - d->o.z += maxstep + 0.1f; - if(!collide(d, smoothdir)) - { - d->o.z -= maxstep + 0.1f; - if(d->physstate == PHYS_FALL || d->floor != floor) - { - d->timeinair = 0; - d->floor = floor; - switchfloor(d, dir, d->floor); - } - d->physstate = PHYS_STEP_UP; - return true; - } - } - } - - /* try stepping up */ - d->o = old; - d->o.z += dir.magnitude(); - if(!collide(d, vec(0, 0, 1))) - { - if(d->physstate == PHYS_FALL || d->floor != floor) - { - d->timeinair = 0; - d->floor = floor; - switchfloor(d, dir, d->floor); - } - if(cansmooth) d->physstate = PHYS_STEP_UP; - return true; - } - d->o = old; - return false; + vec old(d->o), stairdir = (obstacle.z >= 0 && obstacle.z < SLOPEZ ? vec(-obstacle.x, -obstacle.y, 0) : vec(dir.x, dir.y, 0)).rescale(1); + bool cansmooth = true; + /* check if there is space atop the stair to move to */ + if(d->physstate != PHYS_STEP_UP) + { + vec checkdir = stairdir; + checkdir.mul(0.1f); + checkdir.z += maxstep + 0.1f; + d->o.add(checkdir); + if(collide(d)) + { + d->o = old; + if(!collide(d, vec(0, 0, -1), SLOPEZ)) return false; + cansmooth = false; + } + } + + if(cansmooth) + { + vec checkdir = stairdir; + checkdir.z += 1; + checkdir.mul(maxstep); + d->o = old; + d->o.add(checkdir); + int scale = 2; + if(collide(d, checkdir)) + { + if(!collide(d, vec(0, 0, -1), SLOPEZ)) + { + d->o = old; + return false; + } + d->o.add(checkdir); + if(collide(d, vec(0, 0, -1), SLOPEZ)) scale = 1; + } + if(scale != 1) + { + d->o = old; + d->o.sub(checkdir.mul(vec(2, 2, 1))); + if(!collide(d, vec(0, 0, -1), SLOPEZ)) scale = 1; + } + + d->o = old; + vec smoothdir(dir.x, dir.y, 0); + float magxy = smoothdir.magnitude(); + if(magxy > 1e-9f) + { + if(magxy > scale*dir.z) + { + smoothdir.mul(1/magxy); + smoothdir.z = 1.0f/scale; + smoothdir.mul(dir.magnitude()/smoothdir.magnitude()); + } + else smoothdir.z = dir.z; + d->o.add(smoothdir); + d->o.z += maxstep + 0.1f; + if(!collide(d, smoothdir)) + { + d->o.z -= maxstep + 0.1f; + if(d->physstate == PHYS_FALL || d->floor != floor) + { + d->timeinair = 0; + d->floor = floor; + switchfloor(d, dir, d->floor); + } + d->physstate = PHYS_STEP_UP; + return true; + } + } + } + + /* try stepping up */ + d->o = old; + d->o.z += dir.magnitude(); + if(!collide(d, vec(0, 0, 1))) + { + if(d->physstate == PHYS_FALL || d->floor != floor) + { + d->timeinair = 0; + d->floor = floor; + switchfloor(d, dir, d->floor); + } + if(cansmooth) d->physstate = PHYS_STEP_UP; + return true; + } + d->o = old; + return false; } bool trystepdown(physent *d, vec &dir, float step, float xy, float z, bool init = false) { - vec stepdir(dir.x, dir.y, 0); - stepdir.z = -stepdir.magnitude2()*z/xy; - if(!stepdir.z) return false; - stepdir.normalize(); - - vec old(d->o); - d->o.add(vec(stepdir).mul(STAIRHEIGHT/fabs(stepdir.z))).z -= STAIRHEIGHT; - d->zmargin = -STAIRHEIGHT; - if(collide(d, vec(0, 0, -1), SLOPEZ)) - { - d->o = old; - d->o.add(vec(stepdir).mul(step)); - d->zmargin = 0; - if(!collide(d, vec(0, 0, -1))) - { - vec stepfloor(stepdir); - stepfloor.mul(-stepfloor.z).z += 1; - stepfloor.normalize(); - if(d->physstate >= PHYS_SLOPE && d->floor != stepfloor) - { - // prevent alternating step-down/step-up states if player would keep bumping into the same floor - vec stepped(d->o); - d->o.z -= 0.5f; - d->zmargin = -0.5f; - if(collide(d, stepdir) && collidewall == d->floor) - { - d->o = old; - if(!init) { d->o.x += dir.x; d->o.y += dir.y; if(dir.z <= 0 || collide(d, dir)) d->o.z += dir.z; } - d->zmargin = 0; - d->physstate = PHYS_STEP_DOWN; - d->timeinair = 0; - return true; - } - d->o = init ? old : stepped; - d->zmargin = 0; - } - else if(init) d->o = old; - switchfloor(d, dir, stepfloor); - d->floor = stepfloor; - d->physstate = PHYS_STEP_DOWN; - d->timeinair = 0; - return true; - } - } - d->o = old; - d->zmargin = 0; - return false; + vec stepdir(dir.x, dir.y, 0); + stepdir.z = -stepdir.magnitude2()*z/xy; + if(!stepdir.z) return false; + stepdir.normalize(); + + vec old(d->o); + d->o.add(vec(stepdir).mul(STAIRHEIGHT/fabs(stepdir.z))).z -= STAIRHEIGHT; + d->zmargin = -STAIRHEIGHT; + if(collide(d, vec(0, 0, -1), SLOPEZ)) + { + d->o = old; + d->o.add(vec(stepdir).mul(step)); + d->zmargin = 0; + if(!collide(d, vec(0, 0, -1))) + { + vec stepfloor(stepdir); + stepfloor.mul(-stepfloor.z).z += 1; + stepfloor.normalize(); + if(d->physstate >= PHYS_SLOPE && d->floor != stepfloor) + { + // prevent alternating step-down/step-up states if player would keep bumping into the same floor + vec stepped(d->o); + d->o.z -= 0.5f; + d->zmargin = -0.5f; + if(collide(d, stepdir) && collidewall == d->floor) + { + d->o = old; + if(!init) { d->o.x += dir.x; d->o.y += dir.y; if(dir.z <= 0 || collide(d, dir)) d->o.z += dir.z; } + d->zmargin = 0; + d->physstate = PHYS_STEP_DOWN; + d->timeinair = 0; + return true; + } + d->o = init ? old : stepped; + d->zmargin = 0; + } + else if(init) d->o = old; + switchfloor(d, dir, stepfloor); + d->floor = stepfloor; + d->physstate = PHYS_STEP_DOWN; + d->timeinair = 0; + return true; + } + } + d->o = old; + d->zmargin = 0; + return false; } bool trystepdown(physent *d, vec &dir, bool init = false) { - if((!d->move && !d->strafe) || !game::allowmove(d)) return false; - vec old(d->o); - d->o.z -= STAIRHEIGHT; - d->zmargin = -STAIRHEIGHT; - if(!collide(d, vec(0, 0, -1), SLOPEZ)) - { - d->o = old; - d->zmargin = 0; - return false; - } - d->o = old; - d->zmargin = 0; - float step = dir.magnitude(); + if((!d->move && !d->strafe) || !game::allowmove(d)) return false; + vec old(d->o); + d->o.z -= STAIRHEIGHT; + d->zmargin = -STAIRHEIGHT; + if(!collide(d, vec(0, 0, -1), SLOPEZ)) + { + d->o = old; + d->zmargin = 0; + return false; + } + d->o = old; + d->zmargin = 0; + float step = dir.magnitude(); #if 1 - // weaker check, just enough to avoid hopping up slopes - if(trystepdown(d, dir, step, 4, 1, init)) return true; + // weaker check, just enough to avoid hopping up slopes + if(trystepdown(d, dir, step, 4, 1, init)) return true; #else - if(trystepdown(d, dir, step, 2, 1, init)) return true; - if(trystepdown(d, dir, step, 1, 1, init)) return true; - if(trystepdown(d, dir, step, 1, 2, init)) return true; + if(trystepdown(d, dir, step, 2, 1, init)) return true; + if(trystepdown(d, dir, step, 1, 1, init)) return true; + if(trystepdown(d, dir, step, 1, 2, init)) return true; #endif - return false; + return false; } void falling(physent *d, vec &dir, const vec &floor) { - if(floor.z > 0.0f && floor.z < SLOPEZ) - { - if(floor.z >= WALLZ) switchfloor(d, dir, floor); - d->timeinair = 0; - d->physstate = PHYS_SLIDE; - d->floor = floor; - } - else if(d->physstate < PHYS_SLOPE || dir.dot(d->floor) > 0.01f*dir.magnitude() || (floor.z != 0.0f && floor.z != 1.0f) || !trystepdown(d, dir, true)) - d->physstate = PHYS_FALL; + if(floor.z > 0.0f && floor.z < SLOPEZ) + { + if(floor.z >= WALLZ) switchfloor(d, dir, floor); + d->timeinair = 0; + d->physstate = PHYS_SLIDE; + d->floor = floor; + } + else if(d->physstate < PHYS_SLOPE || dir.dot(d->floor) > 0.01f*dir.magnitude() || (floor.z != 0.0f && floor.z != 1.0f) || !trystepdown(d, dir, true)) + d->physstate = PHYS_FALL; } void landing(physent *d, vec &dir, const vec &floor, bool collided) { #if 0 - if(d->physstate == PHYS_FALL) - { - d->timeinair = 0; - if(dir.z < 0.0f) dir.z = d->vel.z = 0.0f; - } + if(d->physstate == PHYS_FALL) + { + d->timeinair = 0; + if(dir.z < 0.0f) dir.z = d->vel.z = 0.0f; + } #endif - switchfloor(d, dir, floor); - d->timeinair = 0; - if((d->physstate!=PHYS_STEP_UP && d->physstate!=PHYS_STEP_DOWN) || !collided) - d->physstate = floor.z >= FLOORZ ? PHYS_FLOOR : PHYS_SLOPE; - d->floor = floor; + switchfloor(d, dir, floor); + d->timeinair = 0; + if((d->physstate!=PHYS_STEP_UP && d->physstate!=PHYS_STEP_DOWN) || !collided) + d->physstate = floor.z >= FLOORZ ? PHYS_FLOOR : PHYS_SLOPE; + d->floor = floor; } bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec &floor) { - bool found = false; - vec moved(d->o); - d->o.z -= 0.1f; - if(collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? SLOPEZ : FLOORZ)) - { - floor = collidewall; - found = true; - } - else if(collided && obstacle.z >= SLOPEZ) - { - floor = obstacle; - found = true; - slide = false; - } - else if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE) - { - if(collide(d, vec(0, 0, -1)) && collidewall.z > 0.0f) - { - floor = collidewall; - if(floor.z >= SLOPEZ) found = true; - } - } - else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f) - { - if(collide(d, vec(d->floor).neg(), 0.95f) || collide(d, vec(0, 0, -1))) - { - floor = collidewall; - if(floor.z >= SLOPEZ && floor.z < 1.0f) found = true; - } - } - if(collided && (!found || obstacle.z > floor.z)) - { - floor = obstacle; - slide = !found && (floor.z < WALLZ || floor.z >= SLOPEZ); - } - d->o = moved; - return found; + bool found = false; + vec moved(d->o); + d->o.z -= 0.1f; + if(collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? SLOPEZ : FLOORZ)) + { + floor = collidewall; + found = true; + } + else if(collided && obstacle.z >= SLOPEZ) + { + floor = obstacle; + found = true; + slide = false; + } + else if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE) + { + if(collide(d, vec(0, 0, -1)) && collidewall.z > 0.0f) + { + floor = collidewall; + if(floor.z >= SLOPEZ) found = true; + } + } + else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f) + { + if(collide(d, vec(d->floor).neg(), 0.95f) || collide(d, vec(0, 0, -1))) + { + floor = collidewall; + if(floor.z >= SLOPEZ && floor.z < 1.0f) found = true; + } + } + if(collided && (!found || obstacle.z > floor.z)) + { + floor = obstacle; + slide = !found && (floor.z < WALLZ || floor.z >= SLOPEZ); + } + d->o = moved; + return found; } bool move(physent *d, vec &dir) { - vec old(d->o); - bool collided = false, slidecollide = false; - vec obstacle = vec(0, 0, 0); - d->o.add(dir); - if(collide(d, dir) || ((d->type==ENT_AI || d->type==ENT_INANIMATE) && collide(d, vec(0, 0, 0), 0, false))) - { - obstacle = collidewall; - /* check to see if there is an obstacle that would prevent this one from being used as a floor (or ceiling bump) */ - if(d->type==ENT_PLAYER && ((collidewall.z>=SLOPEZ && dir.z<0) || (collidewall.z<=-SLOPEZ && dir.z>0)) && (dir.x || dir.y) && collide(d, vec(dir.x, dir.y, 0))) - { - if(collidewall.dot(dir) >= 0) slidecollide = true; - obstacle = collidewall; - } - d->o = old; - d->o.z -= STAIRHEIGHT; - d->zmargin = -STAIRHEIGHT; - if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR || (collide(d, vec(0, 0, -1), SLOPEZ) && (d->physstate==PHYS_STEP_UP || d->physstate==PHYS_STEP_DOWN || collidewall.z>=FLOORZ))) - { - d->o = old; - d->zmargin = 0; - if(trystepup(d, dir, obstacle, STAIRHEIGHT, d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor : vec(collidewall))) return true; - } - else - { - d->o = old; - d->zmargin = 0; - } - /* can't step over the obstacle, so just slide against it */ - collided = true; - } - else if(d->physstate == PHYS_STEP_UP) - { - if(collide(d, vec(0, 0, -1), SLOPEZ)) - { - d->o = old; - if(trystepup(d, dir, vec(0, 0, 1), STAIRHEIGHT, vec(collidewall))) return true; - d->o.add(dir); - } - } - else if(d->physstate == PHYS_STEP_DOWN && dir.dot(d->floor) <= 1e-6f) - { - vec moved(d->o); - d->o = old; - if(trystepdown(d, dir)) return true; - d->o = moved; - } - vec floor(0, 0, 0); - bool slide = collided, - found = findfloor(d, collided, obstacle, slide, floor); - if(slide || (!collided && floor.z > 0 && floor.z < WALLZ)) - { - slideagainst(d, dir, slide ? obstacle : floor, found, slidecollide); - //if(d->type == ENT_AI || d->type == ENT_INANIMATE) - d->blocked = true; - } - if(found) landing(d, dir, floor, collided); - else falling(d, dir, floor); - return !collided; + vec old(d->o); + bool collided = false, slidecollide = false; + vec obstacle = vec(0, 0, 0); + d->o.add(dir); + if(collide(d, dir) || ((d->type==ENT_AI || d->type==ENT_INANIMATE) && collide(d, vec(0, 0, 0), 0, false))) + { + obstacle = collidewall; + /* check to see if there is an obstacle that would prevent this one from being used as a floor (or ceiling bump) */ + if(d->type==ENT_PLAYER && ((collidewall.z>=SLOPEZ && dir.z<0) || (collidewall.z<=-SLOPEZ && dir.z>0)) && (dir.x || dir.y) && collide(d, vec(dir.x, dir.y, 0))) + { + if(collidewall.dot(dir) >= 0) slidecollide = true; + obstacle = collidewall; + } + d->o = old; + d->o.z -= STAIRHEIGHT; + d->zmargin = -STAIRHEIGHT; + if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR || (collide(d, vec(0, 0, -1), SLOPEZ) && (d->physstate==PHYS_STEP_UP || d->physstate==PHYS_STEP_DOWN || collidewall.z>=FLOORZ))) + { + d->o = old; + d->zmargin = 0; + if(trystepup(d, dir, obstacle, STAIRHEIGHT, d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor : vec(collidewall))) return true; + } + else + { + d->o = old; + d->zmargin = 0; + } + /* can't step over the obstacle, so just slide against it */ + collided = true; + } + else if(d->physstate == PHYS_STEP_UP) + { + if(collide(d, vec(0, 0, -1), SLOPEZ)) + { + d->o = old; + if(trystepup(d, dir, vec(0, 0, 1), STAIRHEIGHT, vec(collidewall))) return true; + d->o.add(dir); + } + } + else if(d->physstate == PHYS_STEP_DOWN && dir.dot(d->floor) <= 1e-6f) + { + vec moved(d->o); + d->o = old; + if(trystepdown(d, dir)) return true; + d->o = moved; + } + vec floor(0, 0, 0); + bool slide = collided, + found = findfloor(d, collided, obstacle, slide, floor); + if(slide || (!collided && floor.z > 0 && floor.z < WALLZ)) + { + slideagainst(d, dir, slide ? obstacle : floor, found, slidecollide); + //if(d->type == ENT_AI || d->type == ENT_INANIMATE) + d->blocked = true; + } + if(found) landing(d, dir, floor, collided); + else falling(d, dir, floor); + return !collided; } bool bounce(physent *d, float secs, float elasticity, float waterfric, float grav) { - // make sure bouncers don't start inside geometry - if(d->physstate!=PHYS_BOUNCE && collide(d, vec(0, 0, 0), 0, false)) return true; - int mat = lookupmaterial(vec(d->o.x, d->o.y, d->o.z + (d->aboveeye - d->eyeheight)/2)); - bool water = isliquid(mat); - if(water) - { - d->vel.z -= grav*GRAVITY/16*secs; - d->vel.mul(max(1.0f - secs/waterfric, 0.0f)); - } - else d->vel.z -= grav*GRAVITY*secs; - vec old(d->o); - loopi(2) - { - vec dir(d->vel); - dir.mul(secs); - d->o.add(dir); - if(!collide(d, dir, 0, true, true)) - { - if(collideinside) - { - d->o = old; - d->vel.mul(-elasticity); - } - break; - } - else if(collideplayer) break; - d->o = old; - game::bounced(d, collidewall); - float c = collidewall.dot(d->vel), - k = 1.0f + (1.0f-elasticity)*c/d->vel.magnitude(); - d->vel.mul(k); - d->vel.sub(vec(collidewall).mul(elasticity*2.0f*c)); - } - if(d->physstate!=PHYS_BOUNCE) - { - // make sure bouncers don't start inside geometry - if(d->o == old) return !collideplayer; - d->physstate = PHYS_BOUNCE; - } - return collideplayer!=NULL; + // make sure bouncers don't start inside geometry + if(d->physstate!=PHYS_BOUNCE && collide(d, vec(0, 0, 0), 0, false)) return true; + int mat = lookupmaterial(vec(d->o.x, d->o.y, d->o.z + (d->aboveeye - d->eyeheight)/2)); + bool water = isliquid(mat); + if(water) + { + d->vel.z -= grav*GRAVITY/16*secs; + d->vel.mul(max(1.0f - secs/waterfric, 0.0f)); + } + else d->vel.z -= grav*GRAVITY*secs; + vec old(d->o); + loopi(2) + { + vec dir(d->vel); + dir.mul(secs); + d->o.add(dir); + if(!collide(d, dir, 0, true, true)) + { + if(collideinside) + { + d->o = old; + d->vel.mul(-elasticity); + } + break; + } + else if(collideplayer) break; + d->o = old; + game::bounced(d, collidewall); + float c = collidewall.dot(d->vel), + k = 1.0f + (1.0f-elasticity)*c/d->vel.magnitude(); + d->vel.mul(k); + d->vel.sub(vec(collidewall).mul(elasticity*2.0f*c)); + } + if(d->physstate!=PHYS_BOUNCE) + { + // make sure bouncers don't start inside geometry + if(d->o == old) return !collideplayer; + d->physstate = PHYS_BOUNCE; + } + return collideplayer!=NULL; } void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space) { - float rad = obstacle->radius+d->radius; - vec bbmin(obstacle->o); - bbmin.x -= rad; - bbmin.y -= rad; - bbmin.z -= obstacle->eyeheight+d->aboveeye; - bbmin.sub(space); - vec bbmax(obstacle->o); - bbmax.x += rad; - bbmax.y += rad; - bbmax.z += obstacle->aboveeye+d->eyeheight; - bbmax.add(space); - - loopi(3) if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i]) return; - - float mindist = 1e16f; - loopi(3) if(dir[i] != 0) - { - float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i]; - mindist = min(mindist, dist); - } - if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist)); + float rad = obstacle->radius+d->radius; + vec bbmin(obstacle->o); + bbmin.x -= rad; + bbmin.y -= rad; + bbmin.z -= obstacle->eyeheight+d->aboveeye; + bbmin.sub(space); + vec bbmax(obstacle->o); + bbmax.x += rad; + bbmax.y += rad; + bbmax.z += obstacle->aboveeye+d->eyeheight; + bbmax.add(space); + + loopi(3) if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i]) return; + + float mindist = 1e16f; + loopi(3) if(dir[i] != 0) + { + float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i]; + mindist = min(mindist, dist); + } + if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist)); } bool movecamera(physent *pl, const vec &dir, float dist, float stepdist) { - int steps = (int)ceil(dist/stepdist); - if(steps <= 0) return true; - - vec d(dir); - d.mul(dist/steps); - loopi(steps) - { - vec oldpos(pl->o); - pl->o.add(d); - if(collide(pl, vec(0, 0, 0), 0, false)) - { - pl->o = oldpos; - return false; - } - } - return true; -} - -bool droptofloor(vec &o, float radius, float height) -{ - static struct dropent : physent - { - dropent() - { - type = ENT_BOUNCE; - vel = vec(0, 0, -1); - } - } d; - d.o = o; - if(!insideworld(d.o)) - { - if(d.o.z < worldsize) return false; - d.o.z = worldsize - 1e-3f; - if(!insideworld(d.o)) return false; - } - vec v(0.0001f, 0.0001f, -1); - v.normalize(); - if(raycube(d.o, v, worldsize) >= worldsize) return false; - d.radius = d.xradius = d.yradius = radius; - d.eyeheight = height; - d.aboveeye = radius; - if(!movecamera(&d, d.vel, worldsize, 1)) - { - o = d.o; - return true; - } - return false; -} + int steps = (int)ceil(dist/stepdist); + if(steps <= 0) return true; -float dropheight(entity &e) -{ - switch(e.type) - { - case ET_PARTICLES: - case ET_MAPMODEL: - return 0.0f; - default: - return 4.0f; - } + vec d(dir); + d.mul(dist/steps); + loopi(steps) + { + vec oldpos(pl->o); + pl->o.add(d); + if(collide(pl, vec(0, 0, 0), 0, false)) + { + pl->o = oldpos; + return false; + } + } + return true; } void dropenttofloor(entity *e) { - droptofloor(e->o, 1.0f, 4.0f); + float radius = 1.0f; + float height = 4.0f; + vec o = e->o; + static struct dropent : physent + { + dropent() + { + type = ENT_BOUNCE; + vel = vec(0, 0, -1); + } + } d; + d.o = o; + if(!insideworld(d.o)) + { + if(d.o.z < worldsize) return; + d.o.z = worldsize - 1e-3f; + if(!insideworld(d.o)) return; + } + vec v(0.0001f, 0.0001f, -1); + v.normalize(); + if(raycube(d.o, v, worldsize) >= worldsize) return; + d.radius = d.xradius = d.yradius = radius; + d.eyeheight = height; + d.aboveeye = radius; + if(!movecamera(&d, d.vel, worldsize, 1)) + o = d.o; } -void phystest() +void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m) { - static const char * const states[] = {"float", "fall", "slide", "slope", "floor", "step up", "step down", "bounce"}; - printf ("PHYS(pl): %s, air %d, floor: (%f, %f, %f), vel: (%f, %f, %f), g: (%f, %f, %f)\n", states[player->physstate], player->timeinair, player->floor.x, player->floor.y, player->floor.z, player->vel.x, player->vel.y, player->vel.z, player->falling.x, player->falling.y, player->falling.z); - printf ("PHYS(cam): %s, air %d, floor: (%f, %f, %f), vel: (%f, %f, %f), g: (%f, %f, %f)\n", states[camera1->physstate], camera1->timeinair, camera1->floor.x, camera1->floor.y, camera1->floor.z, camera1->vel.x, camera1->vel.y, camera1->vel.z, camera1->falling.x, camera1->falling.y, camera1->falling.z); -} + if(move) + { + m.x = move*-sinf(RAD*yaw); + m.y = move*cosf(RAD*yaw); + } + else m.x = m.y = 0; -COMMAND(phystest, ""); + if(pitch) + { + m.x *= cosf(RAD*pitch); + m.y *= cosf(RAD*pitch); + m.z = move*sinf(RAD*pitch); + } + else m.z = 0; -void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m) -{ - if(move) - { - m.x = move*-sinf(RAD*yaw); - m.y = move*cosf(RAD*yaw); - } - else m.x = m.y = 0; - - if(pitch) - { - m.x *= cosf(RAD*pitch); - m.y *= cosf(RAD*pitch); - m.z = move*sinf(RAD*pitch); - } - else m.z = 0; - - if(strafe) - { - m.x += strafe*cosf(RAD*yaw); - m.y += strafe*sinf(RAD*yaw); - } + if(strafe) + { + m.x += strafe*cosf(RAD*yaw); + m.y += strafe*sinf(RAD*yaw); + } } void vectoyawpitch(const vec &v, float &yaw, float &pitch) { - if(v.iszero()) yaw = pitch = 0; - else - { - yaw = -atan2(v.x, v.y)/RAD; - pitch = asin(v.z/v.magnitude())/RAD; - } + if(v.iszero()) yaw = pitch = 0; + else + { + yaw = -atan2(v.x, v.y)/RAD; + pitch = asin(v.z/v.magnitude())/RAD; + } } #define PHYSFRAMETIME 5 @@ -1589,93 +1562,93 @@ VAR(floatspeed, 1, 100, 10000); void modifyvelocity(physent *pl, bool local, bool water, bool floating, int curtime) { - bool allowmove = game::allowmove(pl); - if(floating) - { - if(pl->jumping && allowmove) - { - pl->jumping = false; - pl->vel.z = max(pl->vel.z, JUMPVEL); - } - } - else if(pl->physstate >= PHYS_SLOPE || water) - { - if(water && !pl->inwater) pl->vel.div(8); - if(pl->jumping && allowmove) - { - pl->jumping = false; - - pl->vel.z = max(pl->vel.z, JUMPVEL); // physics impulse upwards - if(water) { pl->vel.x /= 8.0f; pl->vel.y /= 8.0f; } // dampen velocity change even harder, gives correct water feel - - game::physicstrigger(pl, local, 1, 0); - } - } - if(!floating && pl->physstate == PHYS_FALL) pl->timeinair = min(pl->timeinair + curtime, 1000); - - vec m(0.0f, 0.0f, 0.0f); - if((pl->move || pl->strafe) && allowmove) - { - vecfromyawpitch(pl->yaw, floating || water || pl->type==ENT_CAMERA ? pl->pitch : 0, pl->move, pl->strafe, m); - - if(!floating && pl->physstate >= PHYS_SLOPE) - { - /* move up or down slopes in air - * but only move up slopes in water - */ - float dz = -(m.x*pl->floor.x + m.y*pl->floor.y)/pl->floor.z; - m.z = water ? max(m.z, dz) : dz; - } - - m.normalize(); - } - - vec d(m); - speedmodifier*=(pl->physstate!=PHYS_FLOOR)*(speedmodifier>0); - speedmodifier=(speedmodifier>100.0f)?100.0f:speedmodifier; - d.mul(pl->maxspeed + speedmodifier); - - if(pl->type==ENT_PLAYER) - { - if(floating) - { - if(pl==player) d.mul(floatspeed/100.0f); - } - else if(!water && allowmove) d.mul((pl->move && !pl->strafe ? 1.3f : 1.0f) * (pl->physstate < PHYS_SLOPE ? 1.3f : 1.0f)); - } - float fric = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f); - pl->vel.lerp(d, pl->vel, pow(1 - 1/fric, curtime/20.0f)); + bool allowmove = game::allowmove(pl); + if(floating) + { + if(pl->jumping && allowmove) + { + pl->jumping = false; + pl->vel.z = max(pl->vel.z, JUMPVEL); + } + } + else if(pl->physstate >= PHYS_SLOPE || water) + { + if(water && !pl->inwater) pl->vel.div(8); + if(pl->jumping && allowmove) + { + pl->jumping = false; + + pl->vel.z = max(pl->vel.z, JUMPVEL); // physics impulse upwards + if(water) { pl->vel.x /= 8.0f; pl->vel.y /= 8.0f; } // dampen velocity change even harder, gives correct water feel + + game::physicstrigger(pl, local, 1, 0); + } + } + if(!floating && pl->physstate == PHYS_FALL) pl->timeinair = min(pl->timeinair + curtime, 1000); + + vec m(0.0f, 0.0f, 0.0f); + if((pl->move || pl->strafe) && allowmove) + { + vecfromyawpitch(pl->yaw, floating || water || pl->type==ENT_CAMERA ? pl->pitch : 0, pl->move, pl->strafe, m); + + if(!floating && pl->physstate >= PHYS_SLOPE) + { + /* move up or down slopes in air + * but only move up slopes in water + */ + float dz = -(m.x*pl->floor.x + m.y*pl->floor.y)/pl->floor.z; + m.z = water ? max(m.z, dz) : dz; + } + + m.normalize(); + } + + vec d(m); + speedmodifier*=(pl->physstate!=PHYS_FLOOR)*(speedmodifier>0); + speedmodifier=(speedmodifier>100.0f)?100.0f:speedmodifier; + d.mul(pl->maxspeed + speedmodifier); + + if(pl->type==ENT_PLAYER) + { + if(floating) + { + if(pl==player) d.mul(floatspeed/100.0f); + } + else if(!water && allowmove) d.mul((pl->move && !pl->strafe ? 1.3f : 1.0f) * (pl->physstate < PHYS_SLOPE ? 1.3f : 1.0f)); + } + float fric = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f); + pl->vel.lerp(d, pl->vel, pow(1 - 1/fric, curtime/20.0f)); // old fps friction -// float friction = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f); -// float fpsfric = min(curtime/(20.0f*friction), 1.0f); -// pl->vel.lerp(pl->vel, d, fpsfric); +// float friction = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f); +// float fpsfric = min(curtime/(20.0f*friction), 1.0f); +// pl->vel.lerp(pl->vel, d, fpsfric); } void modifygravity(physent *pl, bool water, int curtime) { - float secs = curtime/1000.0f; - vec g(0, 0, 0); - if(pl->physstate == PHYS_FALL) g.z -= GRAVITY*secs; - else if(pl->floor.z > 0 && pl->floor.z < FLOORZ) - { - g.z = -1; - g.project(pl->floor); - g.normalize(); - g.mul(GRAVITY*secs); - } - if(!water || !game::allowmove(pl) || (!pl->move && !pl->strafe)) pl->falling.add(g); - - if(water || pl->physstate >= PHYS_SLOPE) - { - float fric = water ? 2.0f : 6.0f, - c = water ? 1.0f : clamp((pl->floor.z - SLOPEZ)/(FLOORZ-SLOPEZ), 0.0f, 1.0f); - pl->falling.mul(pow(1 - c/fric, curtime/20.0f)); + float secs = curtime/1000.0f; + vec g(0, 0, 0); + if(pl->physstate == PHYS_FALL) g.z -= GRAVITY*secs; + else if(pl->floor.z > 0 && pl->floor.z < FLOORZ) + { + g.z = -1; + g.project(pl->floor); + g.normalize(); + g.mul(GRAVITY*secs); + } + if(!water || !game::allowmove(pl) || (!pl->move && !pl->strafe)) pl->falling.add(g); + + if(water || pl->physstate >= PHYS_SLOPE) + { + float fric = water ? 2.0f : 6.0f, + c = water ? 1.0f : clamp((pl->floor.z - SLOPEZ)/(FLOORZ-SLOPEZ), 0.0f, 1.0f); + pl->falling.mul(pow(1 - c/fric, curtime/20.0f)); // old fps friction -// float friction = water ? 2.0f : 6.0f, -// fpsfric = friction/curtime*20.0f, -// c = water ? 1.0f : clamp((pl->floor.z - SLOPEZ)/(FLOORZ-SLOPEZ), 0.0f, 1.0f); -// pl->falling.mul(1 - c/fpsfric); - } +// float friction = water ? 2.0f : 6.0f, +// fpsfric = friction/curtime*20.0f, +// c = water ? 1.0f : clamp((pl->floor.z - SLOPEZ)/(FLOORZ-SLOPEZ), 0.0f, 1.0f); +// pl->falling.mul(1 - c/fpsfric); + } } // main physics routine, moves a player/monster for a curtime step @@ -1684,377 +1657,218 @@ void modifygravity(physent *pl, bool water, int curtime) bool moveplayer(physent *pl, int moveres, bool local, int curtime) { - int material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (3*pl->aboveeye - pl->eyeheight)/4)); - bool water = isliquid(material&MATF_VOLUME); - bool floating = pl->type==ENT_PLAYER && (pl->state==CS_EDITING || pl->state==CS_SPECTATOR); - float secs = curtime/1000.f; - - // apply gravity - if(!floating) modifygravity(pl, water, curtime); - // apply any player generated changes in velocity - modifyvelocity(pl, local, water, floating, curtime); - - vec d(pl->vel); - if(!floating && water) d.mul(0.5f); - d.add(pl->falling); - d.mul(secs); - - pl->blocked = false; - - if(floating) // just apply velocity - { - if(pl->physstate != PHYS_FLOAT) - { - pl->physstate = PHYS_FLOAT; - pl->timeinair = 0; - pl->falling = vec(0, 0, 0); - } - pl->o.add(d); - } - else // apply velocity with collision - { - const float f = 1.0f/moveres; - const int timeinair = pl->timeinair; - int collisions = 0; - - d.mul(f); - loopi(moveres) if(!move(pl, d) && ++collisions<5) i--; // discrete steps collision detection & sliding - if(timeinair > 800 && !pl->timeinair && !water) // if we land after long time must have been a high jump, make thud sound - { - game::physicstrigger(pl, local, -1, 0); - } - } - - if(pl->state==CS_ALIVE) updatedynentcache(pl); - - // automatically apply smooth roll when strafing - - if(pl->strafe && maxroll) pl->roll = clamp(pl->roll - pow(clamp(1.0f + pl->strafe*pl->roll/maxroll, 0.0f, 1.0f), 0.33f)*pl->strafe*curtime*straferoll, -maxroll, maxroll); - else pl->roll *= curtime == PHYSFRAMETIME ? faderoll : pow(faderoll, curtime/float(PHYSFRAMETIME)); - - // play sounds on water transitions - - if(pl->inwater && !water) - { - material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (pl->aboveeye - pl->eyeheight)/2)); - water = isliquid(material&MATF_VOLUME); - } - if(!pl->inwater && water) game::physicstrigger(pl, local, 0, -1, material&MATF_VOLUME); - else if(pl->inwater && !water) game::physicstrigger(pl, local, 0, 1, pl->inwater); - pl->inwater = water ? material&MATF_VOLUME : MAT_AIR; - - if(pl->state==CS_ALIVE && (pl->o.z < 0 || material&MAT_DEATH)) game::suicide(pl); - - return true; + int material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (3*pl->aboveeye - pl->eyeheight)/4)); + bool water = isliquid(material&MATF_VOLUME); + bool floating = pl->type==ENT_PLAYER && (pl->state==CS_EDITING || pl->state==CS_SPECTATOR); + float secs = curtime/1000.f; + + // apply gravity + if(!floating) modifygravity(pl, water, curtime); + // apply any player generated changes in velocity + modifyvelocity(pl, local, water, floating, curtime); + + vec d(pl->vel); + if(!floating && water) d.mul(0.5f); + d.add(pl->falling); + d.mul(secs); + + pl->blocked = false; + + if(floating) // just apply velocity + { + if(pl->physstate != PHYS_FLOAT) + { + pl->physstate = PHYS_FLOAT; + pl->timeinair = 0; + pl->falling = vec(0, 0, 0); + } + pl->o.add(d); + } + else // apply velocity with collision + { + const float f = 1.0f/moveres; + const int timeinair = pl->timeinair; + int collisions = 0; + + d.mul(f); + loopi(moveres) if(!move(pl, d) && ++collisions<5) i--; // discrete steps collision detection & sliding + if(timeinair > 800 && !pl->timeinair && !water) // if we land after long time must have been a high jump, make thud sound + { + game::physicstrigger(pl, local, -1, 0); + } + } + + if(pl->state==CS_ALIVE) updatedynentcache(pl); + + // automatically apply smooth roll when strafing + + if(pl->strafe && maxroll) pl->roll = clamp(pl->roll - pow(clamp(1.0f + pl->strafe*pl->roll/maxroll, 0.0f, 1.0f), 0.33f)*pl->strafe*curtime*straferoll, -maxroll, maxroll); + else pl->roll *= curtime == PHYSFRAMETIME ? faderoll : pow(faderoll, curtime/float(PHYSFRAMETIME)); + + // play sounds on water transitions + + if(pl->inwater && !water) + { + material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (pl->aboveeye - pl->eyeheight)/2)); + water = isliquid(material&MATF_VOLUME); + } + if(!pl->inwater && water) game::physicstrigger(pl, local, 0, -1, material&MATF_VOLUME); + else if(pl->inwater && !water) game::physicstrigger(pl, local, 0, 1, pl->inwater); + pl->inwater = water ? material&MATF_VOLUME : MAT_AIR; + + if(pl->state==CS_ALIVE && (pl->o.z < 0 || material&MAT_DEATH)) game::suicide(pl); + + return true; } int physsteps = 0, physframetime = PHYSFRAMETIME, lastphysframe = 0; -void physicsframe() // optimally schedule physics frames inside the graphics frames +void physicsframe() // optimally schedule physics frames inside the graphics frames { - int diff = lastmillis - lastphysframe; - if(diff <= 0) physsteps = 0; - else - { - physframetime = clamp(game::scaletime(PHYSFRAMETIME)/100, 1, PHYSFRAMETIME); - physsteps = (diff + physframetime - 1)/physframetime; - lastphysframe += physsteps * physframetime; - } - cleardynentcache(); + int diff = lastmillis - lastphysframe; + if(diff <= 0) physsteps = 0; + else + { + physframetime = clamp(game::scaletime(PHYSFRAMETIME)/100, 1, PHYSFRAMETIME); + physsteps = (diff + physframetime - 1)/physframetime; + lastphysframe += physsteps * physframetime; + } + cleardynentcache(); } VAR(physinterp, 0, 1, 1); void interppos(physent *pl) { - pl->o = pl->newpos; + pl->o = pl->newpos; - int diff = lastphysframe - lastmillis; - if(diff <= 0 || !physinterp) return; + int diff = lastphysframe - lastmillis; + if(diff <= 0 || !physinterp) return; - vec deltapos(pl->deltapos); - deltapos.mul(min(diff, physframetime)/float(physframetime)); - pl->o.add(deltapos); + vec deltapos(pl->deltapos); + deltapos.mul(min(diff, physframetime)/float(physframetime)); + pl->o.add(deltapos); } void moveplayer(physent *pl, int moveres, bool local) { - if(physsteps <= 0) - { - if(local) interppos(pl); - return; - } - - if(local) pl->o = pl->newpos; - loopi(physsteps-1) moveplayer(pl, moveres, local, physframetime); - if(local) pl->deltapos = pl->o; - moveplayer(pl, moveres, local, physframetime); - if(local) - { - pl->newpos = pl->o; - pl->deltapos.sub(pl->newpos); - interppos(pl); - } + if(physsteps <= 0) + { + if(local) interppos(pl); + return; + } + + if(local) pl->o = pl->newpos; + loopi(physsteps-1) moveplayer(pl, moveres, local, physframetime); + if(local) pl->deltapos = pl->o; + moveplayer(pl, moveres, local, physframetime); + if(local) + { + pl->newpos = pl->o; + pl->deltapos.sub(pl->newpos); + interppos(pl); + } } bool bounce(physent *d, float elasticity, float waterfric, float grav) { - if(physsteps <= 0) - { - interppos(d); - return false; - } - - d->o = d->newpos; - bool hitplayer = false; - loopi(physsteps-1) - { - if(bounce(d, physframetime/1000.0f, elasticity, waterfric, grav)) hitplayer = true; - } - d->deltapos = d->o; - if(bounce(d, physframetime/1000.0f, elasticity, waterfric, grav)) hitplayer = true; - d->newpos = d->o; - d->deltapos.sub(d->newpos); - interppos(d); - return hitplayer; + if(physsteps <= 0) + { + interppos(d); + return false; + } + + d->o = d->newpos; + bool hitplayer = false; + loopi(physsteps-1) + { + if(bounce(d, physframetime/1000.0f, elasticity, waterfric, grav)) hitplayer = true; + } + d->deltapos = d->o; + if(bounce(d, physframetime/1000.0f, elasticity, waterfric, grav)) hitplayer = true; + d->newpos = d->o; + d->deltapos.sub(d->newpos); + interppos(d); + return hitplayer; } void updatephysstate(physent *d) { - if(d->physstate == PHYS_FALL) return; - d->timeinair = 0; - vec old(d->o); - /* Attempt to reconstruct the floor state. - * May be inaccurate since movement collisions are not considered. - * If good floor is not found, just keep the old floor and hope it's correct enough. - */ - switch(d->physstate) - { - case PHYS_SLOPE: - case PHYS_FLOOR: - case PHYS_STEP_DOWN: - d->o.z -= 0.15f; - if(collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? SLOPEZ : FLOORZ)) - d->floor = collidewall; - break; - - case PHYS_STEP_UP: - d->o.z -= STAIRHEIGHT+0.15f; - if(collide(d, vec(0, 0, -1), SLOPEZ)) - d->floor = collidewall; - break; - - case PHYS_SLIDE: - d->o.z -= 0.15f; - if(collide(d, vec(0, 0, -1)) && collidewall.z < SLOPEZ) - d->floor = collidewall; - break; - } - if(d->physstate > PHYS_FALL && d->floor.z <= 0) d->floor = vec(0, 0, 1); - d->o = old; -} - -const float PLATFORMMARGIN = 0.2f; -const float PLATFORMBORDER = 10.0f; - -struct platforment -{ - physent *d; - int stacks, chains; - - platforment() {} - platforment(physent *d) : d(d), stacks(-1), chains(-1) {} - - bool operator==(const physent *o) const { return d == o; } -}; - -struct platformcollision -{ - platforment *ent; - int next; - - platformcollision() {} - platformcollision(platforment *ent, int next) : ent(ent), next(next) {} -}; - -template -static inline bool platformcollide(physent *d, const vec &dir, physent *o, float margin) -{ - E entvol(d); - O obvol(o, margin); - vec cp; - if(mpr::collide(entvol, obvol, NULL, NULL, &cp)) - { - vec wn = vec(cp).sub(obvol.center()); - return !obvol.contactface(wn, dir.iszero() ? vec(wn).neg() : dir).iszero(); - } - return false; -} - -bool platformcollide(physent *d, physent *o, const vec &dir, float margin = 0) -{ - if(d->collidetype == COLLIDE_OBB) - { - if(o->collidetype == COLLIDE_OBB) return platformcollide(d, dir, o, margin); - else return platformcollide(d, dir, o, margin); - - } - else if(o->collidetype == COLLIDE_OBB) return ellipseboxcollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight + margin); - else return ellipsecollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight + margin); -} - -bool moveplatform(physent *p, const vec &dir) -{ - if(!insideworld(p->newpos)) return false; - - vec oldpos(p->o); - (p->o = p->newpos).add(dir); - if(collide(p, dir, 0, dir.z<=0)) - { - p->o = oldpos; - return false; - } - p->o = oldpos; - - static vector ents; - ents.setsize(0); - for(int x = int(max(p->o.x-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ex = int(min(p->o.x+p->radius+PLATFORMBORDER, worldsize-1.0f))>>dynentsize; x <= ex; x++) - for(int y = int(max(p->o.y-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ey = int(min(p->o.y+p->radius+PLATFORMBORDER, worldsize-1.0f))>>dynentsize; y <= ey; y++) - { - const vector &dynents = checkdynentcache(x, y); - loopv(dynents) - { - physent *d = dynents[i]; - if(p==d || d->o.z-d->eyeheight < p->o.z+p->aboveeye || p->o.reject(d->o, p->radius+PLATFORMBORDER+d->radius) || ents.find(d) >= 0) continue; - ents.add(d); - } - } - static vector passengers, colliders; - passengers.setsize(0); - colliders.setsize(0); - static vector collisions; - collisions.setsize(0); - // build up collision DAG of colliders to be pushed off, and DAG of stacked passengers - loopv(ents) - { - platforment &ent = ents[i]; - physent *d = ent.d; - // check if the dynent is on top of the platform - if(platformcollide(p, d, vec(0, 0, 1), PLATFORMMARGIN)) passengers.add(&ent); - vec doldpos(d->o); - (d->o = d->newpos).add(dir); - if(collide(d, dir, 0, false)) colliders.add(&ent); - d->o = doldpos; - loopvj(ents) - { - platforment &o = ents[j]; - if(platformcollide(d, o.d, dir)) - { - collisions.add(platformcollision(&ent, o.chains)); - o.chains = collisions.length() - 1; - } - if(d->o.z < o.d->o.z && platformcollide(d, o.d, vec(0, 0, 1), PLATFORMMARGIN)) - { - collisions.add(platformcollision(&o, ent.stacks)); - ent.stacks = collisions.length() - 1; - } - } - } - loopv(colliders) // propagate collisions - { - platforment *ent = colliders[i]; - for(int n = ent->chains; n>=0; n = collisions[n].next) - { - platforment *o = collisions[n].ent; - if(colliders.find(o)<0) colliders.add(o); - } - } - if(dir.z>0) - { - loopv(passengers) // if any stacked passengers collide, stop the platform - { - platforment *ent = passengers[i]; - if(colliders.find(ent)>=0) return false; - for(int n = ent->stacks; n>=0; n = collisions[n].next) - { - platforment *o = collisions[n].ent; - if(passengers.find(o)<0) passengers.add(o); - } - } - loopv(passengers) - { - physent *d = passengers[i]->d; - d->o.add(dir); - d->newpos.add(dir); - if(dir.x || dir.y) updatedynentcache(d); - } - } - else loopv(passengers) // move any stacked passengers who aren't colliding with non-passengers - { - platforment *ent = passengers[i]; - if(colliders.find(ent)>=0) continue; - - physent *d = ent->d; - d->o.add(dir); - d->newpos.add(dir); - if(dir.x || dir.y) updatedynentcache(d); - - for(int n = ent->stacks; n>=0; n = collisions[n].next) - { - platforment *o = collisions[n].ent; - if(passengers.find(o)<0) passengers.add(o); - } - } - - p->o.add(dir); - p->newpos.add(dir); - if(dir.x || dir.y) updatedynentcache(p); - - return true; + if(d->physstate == PHYS_FALL) return; + d->timeinair = 0; + vec old(d->o); + /* Attempt to reconstruct the floor state. + * May be inaccurate since movement collisions are not considered. + * If good floor is not found, just keep the old floor and hope it's correct enough. + */ + switch(d->physstate) + { + case PHYS_SLOPE: + case PHYS_FLOOR: + case PHYS_STEP_DOWN: + d->o.z -= 0.15f; + if(collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? SLOPEZ : FLOORZ)) + d->floor = collidewall; + break; + + case PHYS_STEP_UP: + d->o.z -= STAIRHEIGHT+0.15f; + if(collide(d, vec(0, 0, -1), SLOPEZ)) + d->floor = collidewall; + break; + + case PHYS_SLIDE: + d->o.z -= 0.15f; + if(collide(d, vec(0, 0, -1)) && collidewall.z < SLOPEZ) + d->floor = collidewall; + break; + } + if(d->physstate > PHYS_FALL && d->floor.z <= 0) d->floor = vec(0, 0, 1); + d->o = old; } #define dir(name,v,d,s,os) ICOMMAND(name, "D", (int *down), { player->s = *down!=0; player->v = player->s ? d : (player->os ? -(d) : 0); }); dir(backward, move, -1, k_down, k_up); -dir(forward, move, 1, k_up, k_down); -dir(left, strafe, 1, k_left, k_right); -dir(right, strafe, -1, k_right, k_left); +dir(forward, move, 1, k_up, k_down); +dir(left, strafe, 1, k_left, k_right); +dir(right, strafe, -1, k_right, k_left); ICOMMAND(jump, "D", (int *down), { if(!*down || game::canjump()) player->jumping = *down!=0; }); ICOMMAND(attack, "D", (int *down), { game::doattack(*down!=0); }); -bool entinmap(dynent *d, bool avoidplayers) // brute force but effective way to find a free spawn spot in the map -{ - d->o.z += d->eyeheight; // pos specified is at feet - vec orig = d->o; - loopi(100) // try max 100 times - { - if(i) - { - d->o = orig; - d->o.x += (rnd(21)-10)*i/5; // increasing distance - d->o.y += (rnd(21)-10)*i/5; - d->o.z += (rnd(21)-10)*i/5; - } - - if(!collide(d) && !collideinside) - { - if(collideplayer) - { - if(!avoidplayers) continue; - d->o = orig; - d->resetinterp(); - return false; - } - - d->resetinterp(); - return true; - } - } - // leave ent at original pos, possibly stuck - d->o = orig; - d->resetinterp(); - conoutf(CON_WARN, "can't find entity spawn spot! (%.1f, %.1f, %.1f)", d->o.x, d->o.y, d->o.z); - return false; +bool entinmap(dynent *d, bool avoidplayers) // brute force but effective way to find a free spawn spot in the map +{ + d->o.z += d->eyeheight; // pos specified is at feet + vec orig = d->o; + loopi(100) // try max 100 times + { + if(i) + { + d->o = orig; + d->o.x += (rnd(21)-10)*i/5; // increasing distance + d->o.y += (rnd(21)-10)*i/5; + d->o.z += (rnd(21)-10)*i/5; + } + + if(!collide(d) && !collideinside) + { + if(collideplayer) + { + if(!avoidplayers) continue; + d->o = orig; + d->resetinterp(); + return false; + } + + d->resetinterp(); + return true; + } + } + // leave ent at original pos, possibly stuck + d->o = orig; + d->resetinterp(); + conoutf(CON_WARN, "can't find entity spawn spot! (%.1f, %.1f, %.1f)", d->o.x, d->o.y, d->o.z); + return false; } diff --git a/src/engine/ragdoll.h b/src/engine/ragdoll.h index 2704abd..cf68c01 100644 --- a/src/engine/ragdoll.h +++ b/src/engine/ragdoll.h @@ -1,331 +1,331 @@ struct ragdollskel { - struct vert - { - vec pos; - float radius, weight; - }; - - struct tri - { - int vert[3]; - - bool shareverts(const tri &t) const - { - loopi(3) loopj(3) if(vert[i] == t.vert[j]) return true; - return false; - } - }; - - struct distlimit - { - int vert[2]; - float mindist, maxdist; - }; - - struct rotlimit - { - int tri[2]; - float maxangle; - matrix3 middle; - }; - - struct rotfriction - { - int tri[2]; - matrix3 middle; - }; - - struct joint - { - int bone, tri, vert[3]; - float weight; - matrix4x3 orient; - }; - - struct reljoint - { - int bone, parent; - }; - - bool loaded, animjoints; - int eye; - vector verts; - vector tris; - vector distlimits; - vector rotlimits; - vector rotfrictions; - vector joints; - vector reljoints; - - ragdollskel() : loaded(false), animjoints(false), eye(-1) {} - - void setupjoints() - { - loopv(verts) verts[i].weight = 0; - loopv(joints) - { - joint &j = joints[i]; - j.weight = 0; - vec pos(0, 0, 0); - loopk(3) if(j.vert[k]>=0) - { - pos.add(verts[j.vert[k]].pos); - j.weight++; - verts[j.vert[k]].weight++; - } - if(j.weight) j.weight = 1/j.weight; - pos.mul(j.weight); - - tri &t = tris[j.tri]; - matrix4x3 &m = j.orient; - const vec &v1 = verts[t.vert[0]].pos, - &v2 = verts[t.vert[1]].pos, - &v3 = verts[t.vert[2]].pos; - m.a = vec(v2).sub(v1).normalize(); - m.c.cross(m.a, vec(v3).sub(v1)).normalize(); - m.b.cross(m.c, m.a); - m.d = pos; - m.transpose(); - } - loopv(verts) if(verts[i].weight) verts[i].weight = 1/verts[i].weight; - reljoints.shrink(0); - } - - void setuprotfrictions() - { - rotfrictions.shrink(0); - loopv(tris) for(int j = i+1; j < tris.length(); j++) if(tris[i].shareverts(tris[j])) - { - rotfriction &r = rotfrictions.add(); - r.tri[0] = i; - r.tri[1] = j; - } - } - - void setup() - { - setupjoints(); - setuprotfrictions(); - - loaded = true; - } - - void addreljoint(int bone, int parent) - { - reljoint &r = reljoints.add(); - r.bone = bone; - r.parent = parent; - } + struct vert + { + vec pos; + float radius, weight; + }; + + struct tri + { + int vert[3]; + + bool shareverts(const tri &t) const + { + loopi(3) loopj(3) if(vert[i] == t.vert[j]) return true; + return false; + } + }; + + struct distlimit + { + int vert[2]; + float mindist, maxdist; + }; + + struct rotlimit + { + int tri[2]; + float maxangle; + matrix3 middle; + }; + + struct rotfriction + { + int tri[2]; + matrix3 middle; + }; + + struct joint + { + int bone, tri, vert[3]; + float weight; + matrix4x3 orient; + }; + + struct reljoint + { + int bone, parent; + }; + + bool loaded, animjoints; + int eye; + vector verts; + vector tris; + vector distlimits; + vector rotlimits; + vector rotfrictions; + vector joints; + vector reljoints; + + ragdollskel() : loaded(false), animjoints(false), eye(-1) {} + + void setupjoints() + { + loopv(verts) verts[i].weight = 0; + loopv(joints) + { + joint &j = joints[i]; + j.weight = 0; + vec pos(0, 0, 0); + loopk(3) if(j.vert[k]>=0) + { + pos.add(verts[j.vert[k]].pos); + j.weight++; + verts[j.vert[k]].weight++; + } + if(j.weight) j.weight = 1/j.weight; + pos.mul(j.weight); + + tri &t = tris[j.tri]; + matrix4x3 &m = j.orient; + const vec &v1 = verts[t.vert[0]].pos, + &v2 = verts[t.vert[1]].pos, + &v3 = verts[t.vert[2]].pos; + m.a = vec(v2).sub(v1).normalize(); + m.c.cross(m.a, vec(v3).sub(v1)).normalize(); + m.b.cross(m.c, m.a); + m.d = pos; + m.transpose(); + } + loopv(verts) if(verts[i].weight) verts[i].weight = 1/verts[i].weight; + reljoints.shrink(0); + } + + void setuprotfrictions() + { + rotfrictions.shrink(0); + loopv(tris) for(int j = i+1; j < tris.length(); j++) if(tris[i].shareverts(tris[j])) + { + rotfriction &r = rotfrictions.add(); + r.tri[0] = i; + r.tri[1] = j; + } + } + + void setup() + { + setupjoints(); + setuprotfrictions(); + + loaded = true; + } + + void addreljoint(int bone, int parent) + { + reljoint &r = reljoints.add(); + r.bone = bone; + r.parent = parent; + } }; struct ragdolldata { - struct vert - { - vec oldpos, pos, newpos; - float weight; - bool collided, stuck; - - vert() : pos(0, 0, 0), newpos(0, 0, 0), weight(0), collided(false), stuck(true) {} - }; - - ragdollskel *skel; - int millis, collidemillis, collisions, floating, lastmove, unsticks; - vec offset, center; - float radius, timestep, scale; - vert *verts; - matrix3 *tris; - matrix4x3 *animjoints; - dualquat *reljoints; - - ragdolldata(ragdollskel *skel, float scale = 1) - : skel(skel), - millis(lastmillis), - collidemillis(0), - collisions(0), - floating(0), - lastmove(lastmillis), - unsticks(INT_MAX), - radius(0), - timestep(0), - scale(scale), - verts(new vert[skel->verts.length()]), - tris(new matrix3[skel->tris.length()]), - animjoints(!skel->animjoints || skel->joints.empty() ? NULL : new matrix4x3[skel->joints.length()]), - reljoints(skel->reljoints.empty() ? NULL : new dualquat[skel->reljoints.length()]) - { - } - - ~ragdolldata() - { - delete[] verts; - delete[] tris; - if(animjoints) delete[] animjoints; - if(reljoints) delete[] reljoints; - } - - void calcanimjoint(int i, const matrix4x3 &anim) - { - if(!animjoints) return; - ragdollskel::joint &j = skel->joints[i]; - vec pos(0, 0, 0); - loopk(3) if(j.vert[k]>=0) pos.add(verts[j.vert[k]].pos); - pos.mul(j.weight); - - ragdollskel::tri &t = skel->tris[j.tri]; - matrix4x3 m; - const vec &v1 = verts[t.vert[0]].pos, - &v2 = verts[t.vert[1]].pos, - &v3 = verts[t.vert[2]].pos; - m.a = vec(v2).sub(v1).normalize(); - m.c.cross(m.a, vec(v3).sub(v1)).normalize(); - m.b.cross(m.c, m.a); - m.d = pos; - animjoints[i].transposemul(m, anim); - } - - void calctris() - { - loopv(skel->tris) - { - ragdollskel::tri &t = skel->tris[i]; - matrix3 &m = tris[i]; - const vec &v1 = verts[t.vert[0]].pos, - &v2 = verts[t.vert[1]].pos, - &v3 = verts[t.vert[2]].pos; - m.a = vec(v2).sub(v1).normalize(); - m.c.cross(m.a, vec(v3).sub(v1)).normalize(); - m.b.cross(m.c, m.a); - } - } - - void calcboundsphere() - { - center = vec(0, 0, 0); - loopv(skel->verts) center.add(verts[i].pos); - center.div(skel->verts.length()); - radius = 0; - loopv(skel->verts) radius = max(radius, verts[i].pos.dist(center)); - } - - void init(dynent *d) - { - extern int ragdolltimestepmin; - float ts = ragdolltimestepmin/1000.0f; - loopv(skel->verts) (verts[i].oldpos = verts[i].pos).sub(vec(d->vel).add(d->falling).mul(ts)); - timestep = ts; - - calctris(); - calcboundsphere(); - offset = d->o; - offset.sub(skel->eye >= 0 ? verts[skel->eye].pos : center); - offset.z += (d->eyeheight + d->aboveeye)/2; - } - - void move(dynent *pl, float ts); - void updatepos(); - void constrain(); - void constraindist(); - void applyrotlimit(ragdollskel::tri &t1, ragdollskel::tri &t2, float angle, const vec &axis); - void constrainrot(); - void calcrotfriction(); - void applyrotfriction(float ts); - void tryunstick(float speed); - - static inline bool collidevert(const vec &pos, const vec &dir, float radius) - { - static struct vertent : physent - { - vertent() - { - type = ENT_BOUNCE; - radius = xradius = yradius = eyeheight = aboveeye = 1; - } - } v; - v.o = pos; - if(v.radius != radius) v.radius = v.xradius = v.yradius = v.eyeheight = v.aboveeye = radius; - return collide(&v, dir, 0, false); - } + struct vert + { + vec oldpos, pos, newpos; + float weight; + bool collided, stuck; + + vert() : pos(0, 0, 0), newpos(0, 0, 0), weight(0), collided(false), stuck(true) {} + }; + + ragdollskel *skel; + int millis, collidemillis, collisions, floating, lastmove, unsticks; + vec offset, center; + float radius, timestep, scale; + vert *verts; + matrix3 *tris; + matrix4x3 *animjoints; + dualquat *reljoints; + + ragdolldata(ragdollskel *skel, float scale = 1) + : skel(skel), + millis(lastmillis), + collidemillis(0), + collisions(0), + floating(0), + lastmove(lastmillis), + unsticks(INT_MAX), + radius(0), + timestep(0), + scale(scale), + verts(new vert[skel->verts.length()]), + tris(new matrix3[skel->tris.length()]), + animjoints(!skel->animjoints || skel->joints.empty() ? NULL : new matrix4x3[skel->joints.length()]), + reljoints(skel->reljoints.empty() ? NULL : new dualquat[skel->reljoints.length()]) + { + } + + ~ragdolldata() + { + delete[] verts; + delete[] tris; + if(animjoints) delete[] animjoints; + if(reljoints) delete[] reljoints; + } + + void calcanimjoint(int i, const matrix4x3 &anim) + { + if(!animjoints) return; + ragdollskel::joint &j = skel->joints[i]; + vec pos(0, 0, 0); + loopk(3) if(j.vert[k]>=0) pos.add(verts[j.vert[k]].pos); + pos.mul(j.weight); + + ragdollskel::tri &t = skel->tris[j.tri]; + matrix4x3 m; + const vec &v1 = verts[t.vert[0]].pos, + &v2 = verts[t.vert[1]].pos, + &v3 = verts[t.vert[2]].pos; + m.a = vec(v2).sub(v1).normalize(); + m.c.cross(m.a, vec(v3).sub(v1)).normalize(); + m.b.cross(m.c, m.a); + m.d = pos; + animjoints[i].transposemul(m, anim); + } + + void calctris() + { + loopv(skel->tris) + { + ragdollskel::tri &t = skel->tris[i]; + matrix3 &m = tris[i]; + const vec &v1 = verts[t.vert[0]].pos, + &v2 = verts[t.vert[1]].pos, + &v3 = verts[t.vert[2]].pos; + m.a = vec(v2).sub(v1).normalize(); + m.c.cross(m.a, vec(v3).sub(v1)).normalize(); + m.b.cross(m.c, m.a); + } + } + + void calcboundsphere() + { + center = vec(0, 0, 0); + loopv(skel->verts) center.add(verts[i].pos); + center.div(skel->verts.length()); + radius = 0; + loopv(skel->verts) radius = max(radius, verts[i].pos.dist(center)); + } + + void init(dynent *d) + { + extern int ragdolltimestepmin; + float ts = ragdolltimestepmin/1000.0f; + loopv(skel->verts) (verts[i].oldpos = verts[i].pos).sub(vec(d->vel).add(d->falling).mul(ts)); + timestep = ts; + + calctris(); + calcboundsphere(); + offset = d->o; + offset.sub(skel->eye >= 0 ? verts[skel->eye].pos : center); + offset.z += (d->eyeheight + d->aboveeye)/2; + } + + void move(dynent *pl, float ts); + void updatepos(); + void constrain(); + void constraindist(); + void applyrotlimit(ragdollskel::tri &t1, ragdollskel::tri &t2, float angle, const vec &axis); + void constrainrot(); + void calcrotfriction(); + void applyrotfriction(float ts); + void tryunstick(float speed); + + static inline bool collidevert(const vec &pos, const vec &dir, float radius) + { + static struct vertent : physent + { + vertent() + { + type = ENT_BOUNCE; + radius = xradius = yradius = eyeheight = aboveeye = 1; + } + } v; + v.o = pos; + if(v.radius != radius) v.radius = v.xradius = v.yradius = v.eyeheight = v.aboveeye = radius; + return collide(&v, dir, 0, false); + } }; /* - seed particle position = avg(modelview * base2anim * spherepos) - mapped transform = invert(curtri) * origtrig - parented transform = parent{invert(curtri) * origtrig} * (invert(parent{base2anim}) * base2anim) + seed particle position = avg(modelview * base2anim * spherepos) + mapped transform = invert(curtri) * origtrig + parented transform = parent{invert(curtri) * origtrig} * (invert(parent{base2anim}) * base2anim) */ void ragdolldata::constraindist() { - float invscale = 1.0f/scale; - loopv(skel->distlimits) - { - ragdollskel::distlimit &d = skel->distlimits[i]; - vert &v1 = verts[d.vert[0]], &v2 = verts[d.vert[1]]; - vec dir = vec(v2.pos).sub(v1.pos); - float dist = dir.magnitude()*invscale, cdist; - if(dist < d.mindist) cdist = d.mindist; - else if(dist > d.maxdist) cdist = d.maxdist; - else continue; - if(dist > 1e-4f) dir.mul(cdist*0.5f/dist); - else dir = vec(0, 0, cdist*0.5f/invscale); - vec center = vec(v1.pos).add(v2.pos).mul(0.5f); - v1.newpos.add(vec(center).sub(dir)); - v1.weight++; - v2.newpos.add(vec(center).add(dir)); - v2.weight++; - } + float invscale = 1.0f/scale; + loopv(skel->distlimits) + { + ragdollskel::distlimit &d = skel->distlimits[i]; + vert &v1 = verts[d.vert[0]], &v2 = verts[d.vert[1]]; + vec dir = vec(v2.pos).sub(v1.pos); + float dist = dir.magnitude()*invscale, cdist; + if(dist < d.mindist) cdist = d.mindist; + else if(dist > d.maxdist) cdist = d.maxdist; + else continue; + if(dist > 1e-4f) dir.mul(cdist*0.5f/dist); + else dir = vec(0, 0, cdist*0.5f/invscale); + vec center = vec(v1.pos).add(v2.pos).mul(0.5f); + v1.newpos.add(vec(center).sub(dir)); + v1.weight++; + v2.newpos.add(vec(center).add(dir)); + v2.weight++; + } } inline void ragdolldata::applyrotlimit(ragdollskel::tri &t1, ragdollskel::tri &t2, float angle, const vec &axis) { - vert &v1a = verts[t1.vert[0]], &v1b = verts[t1.vert[1]], &v1c = verts[t1.vert[2]], - &v2a = verts[t2.vert[0]], &v2b = verts[t2.vert[1]], &v2c = verts[t2.vert[2]]; - vec m1 = vec(v1a.pos).add(v1b.pos).add(v1c.pos).div(3), - m2 = vec(v2a.pos).add(v2b.pos).add(v2c.pos).div(3), - q1a, q1b, q1c, q2a, q2b, q2c; - float w1 = q1a.cross(axis, vec(v1a.pos).sub(m1)).magnitude() + - q1b.cross(axis, vec(v1b.pos).sub(m1)).magnitude() + - q1c.cross(axis, vec(v1c.pos).sub(m1)).magnitude(), - w2 = q2a.cross(axis, vec(v2a.pos).sub(m2)).magnitude() + - q2b.cross(axis, vec(v2b.pos).sub(m2)).magnitude() + - q2c.cross(axis, vec(v2c.pos).sub(m2)).magnitude(); - angle /= w1 + w2 + 1e-9f; - float a1 = angle*w2, a2 = -angle*w1, - s1 = sinf(a1), s2 = sinf(a2); - vec c1 = vec(axis).mul(1 - cosf(a1)), c2 = vec(axis).mul(1 - cosf(a2)); - v1a.newpos.add(vec().cross(c1, q1a).madd(q1a, s1).add(v1a.pos)); - v1a.weight++; - v1b.newpos.add(vec().cross(c1, q1b).madd(q1b, s1).add(v1b.pos)); - v1b.weight++; - v1c.newpos.add(vec().cross(c1, q1c).madd(q1c, s1).add(v1c.pos)); - v1c.weight++; - v2a.newpos.add(vec().cross(c2, q2a).madd(q2a, s2).add(v2a.pos)); - v2a.weight++; - v2b.newpos.add(vec().cross(c2, q2b).madd(q2b, s2).add(v2b.pos)); - v2b.weight++; - v2c.newpos.add(vec().cross(c2, q2c).madd(q2c, s2).add(v2c.pos)); - v2c.weight++; + vert &v1a = verts[t1.vert[0]], &v1b = verts[t1.vert[1]], &v1c = verts[t1.vert[2]], + &v2a = verts[t2.vert[0]], &v2b = verts[t2.vert[1]], &v2c = verts[t2.vert[2]]; + vec m1 = vec(v1a.pos).add(v1b.pos).add(v1c.pos).div(3), + m2 = vec(v2a.pos).add(v2b.pos).add(v2c.pos).div(3), + q1a, q1b, q1c, q2a, q2b, q2c; + float w1 = q1a.cross(axis, vec(v1a.pos).sub(m1)).magnitude() + + q1b.cross(axis, vec(v1b.pos).sub(m1)).magnitude() + + q1c.cross(axis, vec(v1c.pos).sub(m1)).magnitude(), + w2 = q2a.cross(axis, vec(v2a.pos).sub(m2)).magnitude() + + q2b.cross(axis, vec(v2b.pos).sub(m2)).magnitude() + + q2c.cross(axis, vec(v2c.pos).sub(m2)).magnitude(); + angle /= w1 + w2 + 1e-9f; + float a1 = angle*w2, a2 = -angle*w1, + s1 = sinf(a1), s2 = sinf(a2); + vec c1 = vec(axis).mul(1 - cosf(a1)), c2 = vec(axis).mul(1 - cosf(a2)); + v1a.newpos.add(vec().cross(c1, q1a).madd(q1a, s1).add(v1a.pos)); + v1a.weight++; + v1b.newpos.add(vec().cross(c1, q1b).madd(q1b, s1).add(v1b.pos)); + v1b.weight++; + v1c.newpos.add(vec().cross(c1, q1c).madd(q1c, s1).add(v1c.pos)); + v1c.weight++; + v2a.newpos.add(vec().cross(c2, q2a).madd(q2a, s2).add(v2a.pos)); + v2a.weight++; + v2b.newpos.add(vec().cross(c2, q2b).madd(q2b, s2).add(v2b.pos)); + v2b.weight++; + v2c.newpos.add(vec().cross(c2, q2c).madd(q2c, s2).add(v2c.pos)); + v2c.weight++; } void ragdolldata::constrainrot() { - loopv(skel->rotlimits) - { - ragdollskel::rotlimit &r = skel->rotlimits[i]; - matrix3 rot; - rot.mul(tris[r.tri[0]], r.middle); - rot.multranspose(tris[r.tri[1]]); - - vec axis; - float angle; - if(!rot.calcangleaxis(angle, axis)) continue; - angle = r.maxangle - fabs(angle); - if(angle >= 0) continue; - angle += 1e-3f; - - applyrotlimit(skel->tris[r.tri[0]], skel->tris[r.tri[1]], angle, axis); - } + loopv(skel->rotlimits) + { + ragdollskel::rotlimit &r = skel->rotlimits[i]; + matrix3 rot; + rot.mul(tris[r.tri[0]], r.middle); + rot.multranspose(tris[r.tri[1]]); + + vec axis; + float angle; + if(!rot.calcangleaxis(angle, axis)) continue; + angle = r.maxangle - fabs(angle); + if(angle >= 0) continue; + angle += 1e-3f; + + applyrotlimit(skel->tris[r.tri[0]], skel->tris[r.tri[1]], angle, axis); + } } VAR(ragdolltimestepmin, 1, 5, 50); @@ -335,103 +335,103 @@ FVAR(ragdollrotfricstop, 0, 0.1f, 1); void ragdolldata::calcrotfriction() { - loopv(skel->rotfrictions) - { - ragdollskel::rotfriction &r = skel->rotfrictions[i]; - r.middle.transposemul(tris[r.tri[0]], tris[r.tri[1]]); - } + loopv(skel->rotfrictions) + { + ragdollskel::rotfriction &r = skel->rotfrictions[i]; + r.middle.transposemul(tris[r.tri[0]], tris[r.tri[1]]); + } } void ragdolldata::applyrotfriction(float ts) { - calctris(); - float stopangle = 2*M_PI*ts*ragdollrotfricstop, rotfric = 1.0f - pow(ragdollrotfric, ts*1000.0f/ragdolltimestepmin); - loopv(skel->rotfrictions) - { - ragdollskel::rotfriction &r = skel->rotfrictions[i]; - matrix3 rot; - rot.mul(tris[r.tri[0]], r.middle); - rot.multranspose(tris[r.tri[1]]); - - vec axis; - float angle; - if(rot.calcangleaxis(angle, axis)) - { - angle *= -(fabs(angle) >= stopangle ? rotfric : 1.0f); - applyrotlimit(skel->tris[r.tri[0]], skel->tris[r.tri[1]], angle, axis); - } - } - loopv(skel->verts) - { - vert &v = verts[i]; - if(v.weight) v.pos = v.newpos.div(v.weight); - v.newpos = vec(0, 0, 0); - v.weight = 0; - } + calctris(); + float stopangle = 2*M_PI*ts*ragdollrotfricstop, rotfric = 1.0f - pow(ragdollrotfric, ts*1000.0f/ragdolltimestepmin); + loopv(skel->rotfrictions) + { + ragdollskel::rotfriction &r = skel->rotfrictions[i]; + matrix3 rot; + rot.mul(tris[r.tri[0]], r.middle); + rot.multranspose(tris[r.tri[1]]); + + vec axis; + float angle; + if(rot.calcangleaxis(angle, axis)) + { + angle *= -(fabs(angle) >= stopangle ? rotfric : 1.0f); + applyrotlimit(skel->tris[r.tri[0]], skel->tris[r.tri[1]], angle, axis); + } + } + loopv(skel->verts) + { + vert &v = verts[i]; + if(v.weight) v.pos = v.newpos.div(v.weight); + v.newpos = vec(0, 0, 0); + v.weight = 0; + } } void ragdolldata::tryunstick(float speed) { - vec unstuck(0, 0, 0); - int stuck = 0; - loopv(skel->verts) - { - vert &v = verts[i]; - if(v.stuck) - { - if(collidevert(v.pos, vec(0, 0, 0), skel->verts[i].radius)) { stuck++; continue; } - v.stuck = false; - } - unstuck.add(v.pos); - } - unsticks = 0; - if(!stuck || stuck >= skel->verts.length()) return; - unstuck.div(skel->verts.length() - stuck); - loopv(skel->verts) - { - vert &v = verts[i]; - if(v.stuck) - { - v.pos.add(vec(unstuck).sub(v.pos).rescale(speed)); - unsticks++; - } - } + vec unstuck(0, 0, 0); + int stuck = 0; + loopv(skel->verts) + { + vert &v = verts[i]; + if(v.stuck) + { + if(collidevert(v.pos, vec(0, 0, 0), skel->verts[i].radius)) { stuck++; continue; } + v.stuck = false; + } + unstuck.add(v.pos); + } + unsticks = 0; + if(!stuck || stuck >= skel->verts.length()) return; + unstuck.div(skel->verts.length() - stuck); + loopv(skel->verts) + { + vert &v = verts[i]; + if(v.stuck) + { + v.pos.add(vec(unstuck).sub(v.pos).rescale(speed)); + unsticks++; + } + } } void ragdolldata::updatepos() { - loopv(skel->verts) - { - vert &v = verts[i]; - if(v.weight) - { - v.newpos.div(v.weight); - if(!collidevert(v.newpos, vec(v.newpos).sub(v.pos), skel->verts[i].radius)) v.pos = v.newpos; - else - { - vec dir = vec(v.newpos).sub(v.oldpos); - if(dir.dot(collidewall) < 0) v.oldpos = vec(v.pos).sub(dir.reflect(collidewall)); - v.collided = true; - } - } - v.newpos = vec(0, 0, 0); - v.weight = 0; - } + loopv(skel->verts) + { + vert &v = verts[i]; + if(v.weight) + { + v.newpos.div(v.weight); + if(!collidevert(v.newpos, vec(v.newpos).sub(v.pos), skel->verts[i].radius)) v.pos = v.newpos; + else + { + vec dir = vec(v.newpos).sub(v.oldpos); + if(dir.dot(collidewall) < 0) v.oldpos = vec(v.pos).sub(dir.reflect(collidewall)); + v.collided = true; + } + } + v.newpos = vec(0, 0, 0); + v.weight = 0; + } } VAR(ragdollconstrain, 1, 5, 100); void ragdolldata::constrain() { - loopi(ragdollconstrain) - { - constraindist(); - updatepos(); - - calctris(); - constrainrot(); - updatepos(); - } + loopi(ragdollconstrain) + { + constraindist(); + updatepos(); + + calctris(); + constrainrot(); + updatepos(); + } } FVAR(ragdollbodyfric, 0, 0.95f, 1); @@ -445,62 +445,62 @@ VAR(ragdollwaterexpireoffset, 0, 3000, 30000); void ragdolldata::move(dynent *pl, float ts) { - extern const float GRAVITY; - if(collidemillis && lastmillis > collidemillis) return; - - int material = lookupmaterial(vec(center.x, center.y, center.z + radius/2)); - bool water = isliquid(material&MATF_VOLUME); - if(!pl->inwater && water) game::physicstrigger(pl, true, 0, -1, material&MATF_VOLUME); - else if(pl->inwater && !water) - { - material = lookupmaterial(center); - water = isliquid(material&MATF_VOLUME); - if(!water) game::physicstrigger(pl, true, 0, 1, pl->inwater); - } - pl->inwater = water ? material&MATF_VOLUME : MAT_AIR; - - calcrotfriction(); + extern const float GRAVITY; + if(collidemillis && lastmillis > collidemillis) return; + + int material = lookupmaterial(vec(center.x, center.y, center.z + radius/2)); + bool water = isliquid(material&MATF_VOLUME); + if(!pl->inwater && water) game::physicstrigger(pl, true, 0, -1, material&MATF_VOLUME); + else if(pl->inwater && !water) + { + material = lookupmaterial(center); + water = isliquid(material&MATF_VOLUME); + if(!water) game::physicstrigger(pl, true, 0, 1, pl->inwater); + } + pl->inwater = water ? material&MATF_VOLUME : MAT_AIR; + + calcrotfriction(); float tsfric = timestep ? ts/timestep : 1, airfric = ragdollairfric + min((ragdollbodyfricscale*collisions)/skel->verts.length(), 1.0f)*(ragdollbodyfric - ragdollairfric); - collisions = 0; - loopv(skel->verts) - { - vert &v = verts[i]; - vec dpos = vec(v.pos).sub(v.oldpos); - dpos.z -= GRAVITY*ts*ts; - if(water) dpos.z += 0.25f*sinf(detrnd(size_t(this)+i, 360)*RAD + lastmillis/10000.0f*M_PI)*ts; - dpos.mul(pow((water ? ragdollwaterfric : 1.0f) * (v.collided ? ragdollgroundfric : airfric), ts*1000.0f/ragdolltimestepmin)*tsfric); - v.oldpos = v.pos; - v.pos.add(dpos); - } - applyrotfriction(ts); - loopv(skel->verts) - { - vert &v = verts[i]; - if(v.pos.z < 0) { v.pos.z = 0; v.oldpos = v.pos; collisions++; } - vec dir = vec(v.pos).sub(v.oldpos); - v.collided = collidevert(v.pos, dir, skel->verts[i].radius); - if(v.collided) - { - v.pos = v.oldpos; - v.oldpos.sub(dir.reflect(collidewall)); - collisions++; - } - } - - if(unsticks && ragdollunstick) tryunstick(ts*ragdollunstick); - - timestep = ts; - if(collisions) - { - floating = 0; - if(!collidemillis) collidemillis = lastmillis + (water ? ragdollwaterexpireoffset : ragdollexpireoffset); - } - else if(++floating > 1 && lastmillis < collidemillis) collidemillis = 0; - - constrain(); - calctris(); - calcboundsphere(); + collisions = 0; + loopv(skel->verts) + { + vert &v = verts[i]; + vec dpos = vec(v.pos).sub(v.oldpos); + dpos.z -= GRAVITY*ts*ts; + if(water) dpos.z += 0.25f*sinf(detrnd(size_t(this)+i, 360)*RAD + lastmillis/10000.0f*M_PI)*ts; + dpos.mul(pow((water ? ragdollwaterfric : 1.0f) * (v.collided ? ragdollgroundfric : airfric), ts*1000.0f/ragdolltimestepmin)*tsfric); + v.oldpos = v.pos; + v.pos.add(dpos); + } + applyrotfriction(ts); + loopv(skel->verts) + { + vert &v = verts[i]; + if(v.pos.z < 0) { v.pos.z = 0; v.oldpos = v.pos; collisions++; } + vec dir = vec(v.pos).sub(v.oldpos); + v.collided = collidevert(v.pos, dir, skel->verts[i].radius); + if(v.collided) + { + v.pos = v.oldpos; + v.oldpos.sub(dir.reflect(collidewall)); + collisions++; + } + } + + if(unsticks && ragdollunstick) tryunstick(ts*ragdollunstick); + + timestep = ts; + if(collisions) + { + floating = 0; + if(!collidemillis) collidemillis = lastmillis + (water ? ragdollwaterexpireoffset : ragdollexpireoffset); + } + else if(++floating > 1 && lastmillis < collidemillis) collidemillis = 0; + + constrain(); + calctris(); + calcboundsphere(); } FVAR(ragdolleyesmooth, 0, 0.5f, 1); @@ -508,26 +508,26 @@ VAR(ragdolleyesmoothmillis, 1, 250, 10000); void moveragdoll(dynent *d) { - if(!curtime || !d->ragdoll) return; - - if(!d->ragdoll->collidemillis || lastmillis < d->ragdoll->collidemillis) - { - int lastmove = d->ragdoll->lastmove; - while(d->ragdoll->lastmove + (lastmove == d->ragdoll->lastmove ? ragdolltimestepmin : ragdolltimestepmax) <= lastmillis) - { - int timestep = min(ragdolltimestepmax, lastmillis - d->ragdoll->lastmove); - d->ragdoll->move(d, timestep/1000.0f); - d->ragdoll->lastmove += timestep; - } - } - - vec eye = d->ragdoll->skel->eye >= 0 ? d->ragdoll->verts[d->ragdoll->skel->eye].pos : d->ragdoll->center; - eye.add(d->ragdoll->offset); - float k = pow(ragdolleyesmooth, float(curtime)/ragdolleyesmoothmillis); - d->o.mul(k).add(eye.mul(1-k)); + if(!curtime || !d->ragdoll) return; + + if(!d->ragdoll->collidemillis || lastmillis < d->ragdoll->collidemillis) + { + int lastmove = d->ragdoll->lastmove; + while(d->ragdoll->lastmove + (lastmove == d->ragdoll->lastmove ? ragdolltimestepmin : ragdolltimestepmax) <= lastmillis) + { + int timestep = min(ragdolltimestepmax, lastmillis - d->ragdoll->lastmove); + d->ragdoll->move(d, timestep/1000.0f); + d->ragdoll->lastmove += timestep; + } + } + + vec eye = d->ragdoll->skel->eye >= 0 ? d->ragdoll->verts[d->ragdoll->skel->eye].pos : d->ragdoll->center; + eye.add(d->ragdoll->offset); + float k = pow(ragdolleyesmooth, float(curtime)/ragdolleyesmoothmillis); + d->o.mul(k).add(eye.mul(1-k)); } void cleanragdoll(dynent *d) { - DELETEP(d->ragdoll); + DELETEP(d->ragdoll); } diff --git a/src/engine/rendergl.cpp b/src/engine/rendergl.cpp index b05be7d..997d15a 100644 --- a/src/engine/rendergl.cpp +++ b/src/engine/rendergl.cpp @@ -12,140 +12,140 @@ VAR(glcompat, 1, 0, 0); PFNGLMULTIDRAWARRAYSPROC glMultiDrawArrays_ = NULL; PFNGLMULTIDRAWELEMENTSPROC glMultiDrawElements_ = NULL; -PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate_ = NULL; +PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate_ = NULL; PFNGLBLENDEQUATIONSEPARATEPROC glBlendEquationSeparate_ = NULL; -PFNGLSTENCILOPSEPARATEPROC glStencilOpSeparate_ = NULL; +PFNGLSTENCILOPSEPARATEPROC glStencilOpSeparate_ = NULL; PFNGLSTENCILFUNCSEPARATEPROC glStencilFuncSeparate_ = NULL; PFNGLSTENCILMASKSEPARATEPROC glStencilMaskSeparate_ = NULL; -PFNGLGENBUFFERSPROC glGenBuffers_ = NULL; -PFNGLBINDBUFFERPROC glBindBuffer_ = NULL; -PFNGLMAPBUFFERPROC glMapBuffer_ = NULL; -PFNGLUNMAPBUFFERPROC glUnmapBuffer_ = NULL; -PFNGLBUFFERDATAPROC glBufferData_ = NULL; -PFNGLBUFFERSUBDATAPROC glBufferSubData_ = NULL; -PFNGLDELETEBUFFERSPROC glDeleteBuffers_ = NULL; +PFNGLGENBUFFERSPROC glGenBuffers_ = NULL; +PFNGLBINDBUFFERPROC glBindBuffer_ = NULL; +PFNGLMAPBUFFERPROC glMapBuffer_ = NULL; +PFNGLUNMAPBUFFERPROC glUnmapBuffer_ = NULL; +PFNGLBUFFERDATAPROC glBufferData_ = NULL; +PFNGLBUFFERSUBDATAPROC glBufferSubData_ = NULL; +PFNGLDELETEBUFFERSPROC glDeleteBuffers_ = NULL; PFNGLGETBUFFERSUBDATAPROC glGetBufferSubData_ = NULL; -PFNGLGENQUERIESPROC glGenQueries_ = NULL; -PFNGLDELETEQUERIESPROC glDeleteQueries_ = NULL; -PFNGLBEGINQUERYPROC glBeginQuery_ = NULL; -PFNGLENDQUERYPROC glEndQuery_ = NULL; -PFNGLGETQUERYIVPROC glGetQueryiv_ = NULL; +PFNGLGENQUERIESPROC glGenQueries_ = NULL; +PFNGLDELETEQUERIESPROC glDeleteQueries_ = NULL; +PFNGLBEGINQUERYPROC glBeginQuery_ = NULL; +PFNGLENDQUERYPROC glEndQuery_ = NULL; +PFNGLGETQUERYIVPROC glGetQueryiv_ = NULL; PFNGLGETQUERYOBJECTIVPROC glGetQueryObjectiv_ = NULL; PFNGLGETQUERYOBJECTUIVPROC glGetQueryObjectuiv_ = NULL; -PFNGLCREATEPROGRAMPROC glCreateProgram_ = NULL; -PFNGLDELETEPROGRAMPROC glDeleteProgram_ = NULL; -PFNGLUSEPROGRAMPROC glUseProgram_ = NULL; -PFNGLCREATESHADERPROC glCreateShader_ = NULL; -PFNGLDELETESHADERPROC glDeleteShader_ = NULL; -PFNGLSHADERSOURCEPROC glShaderSource_ = NULL; -PFNGLCOMPILESHADERPROC glCompileShader_ = NULL; -PFNGLGETSHADERIVPROC glGetShaderiv_ = NULL; -PFNGLGETPROGRAMIVPROC glGetProgramiv_ = NULL; -PFNGLATTACHSHADERPROC glAttachShader_ = NULL; -PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog_ = NULL; -PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog_ = NULL; -PFNGLLINKPROGRAMPROC glLinkProgram_ = NULL; -PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation_ = NULL; -PFNGLUNIFORM1FPROC glUniform1f_ = NULL; -PFNGLUNIFORM2FPROC glUniform2f_ = NULL; -PFNGLUNIFORM3FPROC glUniform3f_ = NULL; -PFNGLUNIFORM4FPROC glUniform4f_ = NULL; -PFNGLUNIFORM1FVPROC glUniform1fv_ = NULL; -PFNGLUNIFORM2FVPROC glUniform2fv_ = NULL; -PFNGLUNIFORM3FVPROC glUniform3fv_ = NULL; -PFNGLUNIFORM4FVPROC glUniform4fv_ = NULL; -PFNGLUNIFORM1IPROC glUniform1i_ = NULL; -PFNGLUNIFORM2IPROC glUniform2i_ = NULL; -PFNGLUNIFORM3IPROC glUniform3i_ = NULL; -PFNGLUNIFORM4IPROC glUniform4i_ = NULL; -PFNGLUNIFORM1IVPROC glUniform1iv_ = NULL; -PFNGLUNIFORM2IVPROC glUniform2iv_ = NULL; -PFNGLUNIFORM3IVPROC glUniform3iv_ = NULL; -PFNGLUNIFORM4IVPROC glUniform4iv_ = NULL; -PFNGLUNIFORMMATRIX2FVPROC glUniformMatrix2fv_ = NULL; -PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv_ = NULL; -PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv_ = NULL; -PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation_ = NULL; -PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform_ = NULL; +PFNGLCREATEPROGRAMPROC glCreateProgram_ = NULL; +PFNGLDELETEPROGRAMPROC glDeleteProgram_ = NULL; +PFNGLUSEPROGRAMPROC glUseProgram_ = NULL; +PFNGLCREATESHADERPROC glCreateShader_ = NULL; +PFNGLDELETESHADERPROC glDeleteShader_ = NULL; +PFNGLSHADERSOURCEPROC glShaderSource_ = NULL; +PFNGLCOMPILESHADERPROC glCompileShader_ = NULL; +PFNGLGETSHADERIVPROC glGetShaderiv_ = NULL; +PFNGLGETPROGRAMIVPROC glGetProgramiv_ = NULL; +PFNGLATTACHSHADERPROC glAttachShader_ = NULL; +PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog_ = NULL; +PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog_ = NULL; +PFNGLLINKPROGRAMPROC glLinkProgram_ = NULL; +PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation_ = NULL; +PFNGLUNIFORM1FPROC glUniform1f_ = NULL; +PFNGLUNIFORM2FPROC glUniform2f_ = NULL; +PFNGLUNIFORM3FPROC glUniform3f_ = NULL; +PFNGLUNIFORM4FPROC glUniform4f_ = NULL; +PFNGLUNIFORM1FVPROC glUniform1fv_ = NULL; +PFNGLUNIFORM2FVPROC glUniform2fv_ = NULL; +PFNGLUNIFORM3FVPROC glUniform3fv_ = NULL; +PFNGLUNIFORM4FVPROC glUniform4fv_ = NULL; +PFNGLUNIFORM1IPROC glUniform1i_ = NULL; +PFNGLUNIFORM2IPROC glUniform2i_ = NULL; +PFNGLUNIFORM3IPROC glUniform3i_ = NULL; +PFNGLUNIFORM4IPROC glUniform4i_ = NULL; +PFNGLUNIFORM1IVPROC glUniform1iv_ = NULL; +PFNGLUNIFORM2IVPROC glUniform2iv_ = NULL; +PFNGLUNIFORM3IVPROC glUniform3iv_ = NULL; +PFNGLUNIFORM4IVPROC glUniform4iv_ = NULL; +PFNGLUNIFORMMATRIX2FVPROC glUniformMatrix2fv_ = NULL; +PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv_ = NULL; +PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv_ = NULL; +PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation_ = NULL; +PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform_ = NULL; PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray_ = NULL; PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray_ = NULL; -PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f_ = NULL; -PFNGLVERTEXATTRIB1FVPROC glVertexAttrib1fv_ = NULL; -PFNGLVERTEXATTRIB1SPROC glVertexAttrib1s_ = NULL; -PFNGLVERTEXATTRIB1SVPROC glVertexAttrib1sv_ = NULL; -PFNGLVERTEXATTRIB2FPROC glVertexAttrib2f_ = NULL; -PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv_ = NULL; -PFNGLVERTEXATTRIB2SPROC glVertexAttrib2s_ = NULL; -PFNGLVERTEXATTRIB2SVPROC glVertexAttrib2sv_ = NULL; -PFNGLVERTEXATTRIB3FPROC glVertexAttrib3f_ = NULL; -PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv_ = NULL; -PFNGLVERTEXATTRIB3SPROC glVertexAttrib3s_ = NULL; -PFNGLVERTEXATTRIB3SVPROC glVertexAttrib3sv_ = NULL; -PFNGLVERTEXATTRIB4FPROC glVertexAttrib4f_ = NULL; -PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv_ = NULL; -PFNGLVERTEXATTRIB4SPROC glVertexAttrib4s_ = NULL; -PFNGLVERTEXATTRIB4SVPROC glVertexAttrib4sv_ = NULL; -PFNGLVERTEXATTRIB4BVPROC glVertexAttrib4bv_ = NULL; -PFNGLVERTEXATTRIB4IVPROC glVertexAttrib4iv_ = NULL; -PFNGLVERTEXATTRIB4UBVPROC glVertexAttrib4ubv_ = NULL; -PFNGLVERTEXATTRIB4UIVPROC glVertexAttrib4uiv_ = NULL; -PFNGLVERTEXATTRIB4USVPROC glVertexAttrib4usv_ = NULL; -PFNGLVERTEXATTRIB4NBVPROC glVertexAttrib4Nbv_ = NULL; -PFNGLVERTEXATTRIB4NIVPROC glVertexAttrib4Niv_ = NULL; -PFNGLVERTEXATTRIB4NUBPROC glVertexAttrib4Nub_ = NULL; -PFNGLVERTEXATTRIB4NUBVPROC glVertexAttrib4Nubv_ = NULL; -PFNGLVERTEXATTRIB4NUIVPROC glVertexAttrib4Nuiv_ = NULL; -PFNGLVERTEXATTRIB4NUSVPROC glVertexAttrib4Nusv_ = NULL; -PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer_ = NULL; +PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f_ = NULL; +PFNGLVERTEXATTRIB1FVPROC glVertexAttrib1fv_ = NULL; +PFNGLVERTEXATTRIB1SPROC glVertexAttrib1s_ = NULL; +PFNGLVERTEXATTRIB1SVPROC glVertexAttrib1sv_ = NULL; +PFNGLVERTEXATTRIB2FPROC glVertexAttrib2f_ = NULL; +PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv_ = NULL; +PFNGLVERTEXATTRIB2SPROC glVertexAttrib2s_ = NULL; +PFNGLVERTEXATTRIB2SVPROC glVertexAttrib2sv_ = NULL; +PFNGLVERTEXATTRIB3FPROC glVertexAttrib3f_ = NULL; +PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv_ = NULL; +PFNGLVERTEXATTRIB3SPROC glVertexAttrib3s_ = NULL; +PFNGLVERTEXATTRIB3SVPROC glVertexAttrib3sv_ = NULL; +PFNGLVERTEXATTRIB4FPROC glVertexAttrib4f_ = NULL; +PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv_ = NULL; +PFNGLVERTEXATTRIB4SPROC glVertexAttrib4s_ = NULL; +PFNGLVERTEXATTRIB4SVPROC glVertexAttrib4sv_ = NULL; +PFNGLVERTEXATTRIB4BVPROC glVertexAttrib4bv_ = NULL; +PFNGLVERTEXATTRIB4IVPROC glVertexAttrib4iv_ = NULL; +PFNGLVERTEXATTRIB4UBVPROC glVertexAttrib4ubv_ = NULL; +PFNGLVERTEXATTRIB4UIVPROC glVertexAttrib4uiv_ = NULL; +PFNGLVERTEXATTRIB4USVPROC glVertexAttrib4usv_ = NULL; +PFNGLVERTEXATTRIB4NBVPROC glVertexAttrib4Nbv_ = NULL; +PFNGLVERTEXATTRIB4NIVPROC glVertexAttrib4Niv_ = NULL; +PFNGLVERTEXATTRIB4NUBPROC glVertexAttrib4Nub_ = NULL; +PFNGLVERTEXATTRIB4NUBVPROC glVertexAttrib4Nubv_ = NULL; +PFNGLVERTEXATTRIB4NUIVPROC glVertexAttrib4Nuiv_ = NULL; +PFNGLVERTEXATTRIB4NUSVPROC glVertexAttrib4Nusv_ = NULL; +PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer_ = NULL; PFNGLDRAWBUFFERSPROC glDrawBuffers_ = NULL; // OpenGL 3.0 -PFNGLGETSTRINGIPROC glGetStringi_ = NULL; +PFNGLGETSTRINGIPROC glGetStringi_ = NULL; PFNGLBINDFRAGDATALOCATIONPROC glBindFragDataLocation_ = NULL; // GL_EXT_framebuffer_object -PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer_ = NULL; -PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers_ = NULL; -PFNGLGENFRAMEBUFFERSPROC glGenRenderbuffers_ = NULL; -PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage_ = NULL; +PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer_ = NULL; +PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers_ = NULL; +PFNGLGENFRAMEBUFFERSPROC glGenRenderbuffers_ = NULL; +PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage_ = NULL; PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus_ = NULL; -PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer_ = NULL; -PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers_ = NULL; -PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers_ = NULL; -PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D_ = NULL; +PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer_ = NULL; +PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers_ = NULL; +PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers_ = NULL; +PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D_ = NULL; PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer_ = NULL; -PFNGLGENERATEMIPMAPPROC glGenerateMipmap_ = NULL; +PFNGLGENERATEMIPMAPPROC glGenerateMipmap_ = NULL; // GL_EXT_framebuffer_blit -PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer_ = NULL; +PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer_ = NULL; // GL_ARB_uniform_buffer_object -PFNGLGETUNIFORMINDICESPROC glGetUniformIndices_ = NULL; -PFNGLGETACTIVEUNIFORMSIVPROC glGetActiveUniformsiv_ = NULL; -PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex_ = NULL; +PFNGLGETUNIFORMINDICESPROC glGetUniformIndices_ = NULL; +PFNGLGETACTIVEUNIFORMSIVPROC glGetActiveUniformsiv_ = NULL; +PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex_ = NULL; PFNGLGETACTIVEUNIFORMBLOCKIVPROC glGetActiveUniformBlockiv_ = NULL; -PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding_ = NULL; -PFNGLBINDBUFFERBASEPROC glBindBufferBase_ = NULL; -PFNGLBINDBUFFERRANGEPROC glBindBufferRange_ = NULL; +PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding_ = NULL; +PFNGLBINDBUFFERBASEPROC glBindBufferBase_ = NULL; +PFNGLBINDBUFFERRANGEPROC glBindBufferRange_ = NULL; // GL_ARB_map_buffer_range -PFNGLMAPBUFFERRANGEPROC glMapBufferRange_ = NULL; +PFNGLMAPBUFFERRANGEPROC glMapBufferRange_ = NULL; PFNGLFLUSHMAPPEDBUFFERRANGEPROC glFlushMappedBufferRange_ = NULL; // GL_ARB_vertex_array_object -PFNGLBINDVERTEXARRAYPROC glBindVertexArray_ = NULL; +PFNGLBINDVERTEXARRAYPROC glBindVertexArray_ = NULL; PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays_ = NULL; -PFNGLGENVERTEXARRAYSPROC glGenVertexArrays_ = NULL; -PFNGLISVERTEXARRAYPROC glIsVertexArray_ = NULL; +PFNGLGENVERTEXARRAYSPROC glGenVertexArrays_ = NULL; +PFNGLISVERTEXARRAYPROC glIsVertexArray_ = NULL; void *getprocaddress(const char *name) { - return SDL_GL_GetProcAddress(name); + return SDL_GL_GetProcAddress(name); } VARP(ati_skybox_bug, 0, 0, 1); @@ -161,406 +161,383 @@ VAR(rtscissor, 0, 1, 1); VAR(blurtile, 0, 1, 1); VAR(rtsharefb, 0, 1, 1); -VAR(dbgexts, 0, 0, 1); - hashset glexts; void parseglexts() { - if(glversion >= 300) - { - GLint numexts = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &numexts); - loopi(numexts) - { - const char *ext = (const char *)glGetStringi_(GL_EXTENSIONS, i); - glexts.add(newstring(ext)); - } - } - else - { - const char *exts = (const char *)glGetString(GL_EXTENSIONS); - for(;;) - { - while(*exts == ' ') exts++; - if(!*exts) break; - const char *ext = exts; - while(*exts && *exts != ' ') exts++; - if(exts > ext) glexts.add(newstring(ext, size_t(exts-ext))); - } - } + if(glversion >= 300) + { + GLint numexts = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numexts); + loopi(numexts) + { + const char *ext = (const char *)glGetStringi_(GL_EXTENSIONS, i); + glexts.add(newstring(ext)); + } + } + else + { + const char *exts = (const char *)glGetString(GL_EXTENSIONS); + for(;;) + { + while(*exts == ' ') exts++; + if(!*exts) break; + const char *ext = exts; + while(*exts && *exts != ' ') exts++; + if(exts > ext) glexts.add(newstring(ext, size_t(exts-ext))); + } + } } bool hasext(const char *ext) { - return glexts.access(ext)!=NULL; + return glexts.access(ext)!=NULL; } void gl_checkextensions() { - const char *vendor = (const char *)glGetString(GL_VENDOR); - const char *renderer = (const char *)glGetString(GL_RENDERER); - const char *version = (const char *)glGetString(GL_VERSION); - conoutf(CON_INIT, "Renderer: %s (%s)", renderer, vendor); - conoutf(CON_INIT, "Driver: %s", version); - - bool mesa = false, intel = false, ati = false, nvidia = false; - if(strstr(renderer, "Mesa") || strstr(version, "Mesa")) - { - mesa = true; - if(strstr(renderer, "Intel")) intel = true; - } - else if(strstr(vendor, "NVIDIA")) - nvidia = true; - else if(strstr(vendor, "ATI") || strstr(vendor, "Advanced Micro Devices")) - ati = true; - else if(strstr(vendor, "Intel")) - intel = true; - - uint glmajorversion, glminorversion; - if(sscanf(version, " %u.%u", &glmajorversion, &glminorversion) != 2) glversion = 100; - else glversion = glmajorversion*100 + glminorversion*10; - - if(glversion < 200) fatal("OpenGL 2.0 or greater is required!"); - - glMultiDrawArrays_ = (PFNGLMULTIDRAWARRAYSPROC) getprocaddress("glMultiDrawArrays"); - glMultiDrawElements_ = (PFNGLMULTIDRAWELEMENTSPROC) getprocaddress("glMultiDrawElements"); - - glBlendFuncSeparate_ = (PFNGLBLENDFUNCSEPARATEPROC) getprocaddress("glBlendFuncSeparate"); - glBlendEquationSeparate_ = (PFNGLBLENDEQUATIONSEPARATEPROC) getprocaddress("glBlendEquationSeparate"); - glStencilOpSeparate_ = (PFNGLSTENCILOPSEPARATEPROC) getprocaddress("glStencilOpSeparate"); - glStencilFuncSeparate_ = (PFNGLSTENCILFUNCSEPARATEPROC) getprocaddress("glStencilFuncSeparate"); - glStencilMaskSeparate_ = (PFNGLSTENCILMASKSEPARATEPROC) getprocaddress("glStencilMaskSeparate"); - - glGenBuffers_ = (PFNGLGENBUFFERSPROC) getprocaddress("glGenBuffers"); - glBindBuffer_ = (PFNGLBINDBUFFERPROC) getprocaddress("glBindBuffer"); - glMapBuffer_ = (PFNGLMAPBUFFERPROC) getprocaddress("glMapBuffer"); - glUnmapBuffer_ = (PFNGLUNMAPBUFFERPROC) getprocaddress("glUnmapBuffer"); - glBufferData_ = (PFNGLBUFFERDATAPROC) getprocaddress("glBufferData"); - glBufferSubData_ = (PFNGLBUFFERSUBDATAPROC) getprocaddress("glBufferSubData"); - glDeleteBuffers_ = (PFNGLDELETEBUFFERSPROC) getprocaddress("glDeleteBuffers"); - glGetBufferSubData_ = (PFNGLGETBUFFERSUBDATAPROC) getprocaddress("glGetBufferSubData"); - - glGetQueryiv_ = (PFNGLGETQUERYIVPROC) getprocaddress("glGetQueryiv"); - glGenQueries_ = (PFNGLGENQUERIESPROC) getprocaddress("glGenQueries"); - glDeleteQueries_ = (PFNGLDELETEQUERIESPROC) getprocaddress("glDeleteQueries"); - glBeginQuery_ = (PFNGLBEGINQUERYPROC) getprocaddress("glBeginQuery"); - glEndQuery_ = (PFNGLENDQUERYPROC) getprocaddress("glEndQuery"); - glGetQueryObjectiv_ = (PFNGLGETQUERYOBJECTIVPROC) getprocaddress("glGetQueryObjectiv"); - glGetQueryObjectuiv_ = (PFNGLGETQUERYOBJECTUIVPROC) getprocaddress("glGetQueryObjectuiv"); - - glCreateProgram_ = (PFNGLCREATEPROGRAMPROC) getprocaddress("glCreateProgram"); - glDeleteProgram_ = (PFNGLDELETEPROGRAMPROC) getprocaddress("glDeleteProgram"); - glUseProgram_ = (PFNGLUSEPROGRAMPROC) getprocaddress("glUseProgram"); - glCreateShader_ = (PFNGLCREATESHADERPROC) getprocaddress("glCreateShader"); - glDeleteShader_ = (PFNGLDELETESHADERPROC) getprocaddress("glDeleteShader"); - glShaderSource_ = (PFNGLSHADERSOURCEPROC) getprocaddress("glShaderSource"); - glCompileShader_ = (PFNGLCOMPILESHADERPROC) getprocaddress("glCompileShader"); - glGetShaderiv_ = (PFNGLGETSHADERIVPROC) getprocaddress("glGetShaderiv"); - glGetProgramiv_ = (PFNGLGETPROGRAMIVPROC) getprocaddress("glGetProgramiv"); - glAttachShader_ = (PFNGLATTACHSHADERPROC) getprocaddress("glAttachShader"); - glGetProgramInfoLog_ = (PFNGLGETPROGRAMINFOLOGPROC) getprocaddress("glGetProgramInfoLog"); - glGetShaderInfoLog_ = (PFNGLGETSHADERINFOLOGPROC) getprocaddress("glGetShaderInfoLog"); - glLinkProgram_ = (PFNGLLINKPROGRAMPROC) getprocaddress("glLinkProgram"); - glGetUniformLocation_ = (PFNGLGETUNIFORMLOCATIONPROC) getprocaddress("glGetUniformLocation"); - glUniform1f_ = (PFNGLUNIFORM1FPROC) getprocaddress("glUniform1f"); - glUniform2f_ = (PFNGLUNIFORM2FPROC) getprocaddress("glUniform2f"); - glUniform3f_ = (PFNGLUNIFORM3FPROC) getprocaddress("glUniform3f"); - glUniform4f_ = (PFNGLUNIFORM4FPROC) getprocaddress("glUniform4f"); - glUniform1fv_ = (PFNGLUNIFORM1FVPROC) getprocaddress("glUniform1fv"); - glUniform2fv_ = (PFNGLUNIFORM2FVPROC) getprocaddress("glUniform2fv"); - glUniform3fv_ = (PFNGLUNIFORM3FVPROC) getprocaddress("glUniform3fv"); - glUniform4fv_ = (PFNGLUNIFORM4FVPROC) getprocaddress("glUniform4fv"); - glUniform1i_ = (PFNGLUNIFORM1IPROC) getprocaddress("glUniform1i"); - glUniform2i_ = (PFNGLUNIFORM2IPROC) getprocaddress("glUniform2i"); - glUniform3i_ = (PFNGLUNIFORM3IPROC) getprocaddress("glUniform3i"); - glUniform4i_ = (PFNGLUNIFORM4IPROC) getprocaddress("glUniform4i"); - glUniform1iv_ = (PFNGLUNIFORM1IVPROC) getprocaddress("glUniform1iv"); - glUniform2iv_ = (PFNGLUNIFORM2IVPROC) getprocaddress("glUniform2iv"); - glUniform3iv_ = (PFNGLUNIFORM3IVPROC) getprocaddress("glUniform3iv"); - glUniform4iv_ = (PFNGLUNIFORM4IVPROC) getprocaddress("glUniform4iv"); - glUniformMatrix2fv_ = (PFNGLUNIFORMMATRIX2FVPROC) getprocaddress("glUniformMatrix2fv"); - glUniformMatrix3fv_ = (PFNGLUNIFORMMATRIX3FVPROC) getprocaddress("glUniformMatrix3fv"); - glUniformMatrix4fv_ = (PFNGLUNIFORMMATRIX4FVPROC) getprocaddress("glUniformMatrix4fv"); - glBindAttribLocation_ = (PFNGLBINDATTRIBLOCATIONPROC) getprocaddress("glBindAttribLocation"); - glGetActiveUniform_ = (PFNGLGETACTIVEUNIFORMPROC) getprocaddress("glGetActiveUniform"); - glEnableVertexAttribArray_ = (PFNGLENABLEVERTEXATTRIBARRAYPROC) getprocaddress("glEnableVertexAttribArray"); - glDisableVertexAttribArray_ = (PFNGLDISABLEVERTEXATTRIBARRAYPROC) getprocaddress("glDisableVertexAttribArray"); - - glVertexAttrib1f_ = (PFNGLVERTEXATTRIB1FPROC) getprocaddress("glVertexAttrib1f"); - glVertexAttrib1fv_ = (PFNGLVERTEXATTRIB1FVPROC) getprocaddress("glVertexAttrib1fv"); - glVertexAttrib1s_ = (PFNGLVERTEXATTRIB1SPROC) getprocaddress("glVertexAttrib1s"); - glVertexAttrib1sv_ = (PFNGLVERTEXATTRIB1SVPROC) getprocaddress("glVertexAttrib1sv"); - glVertexAttrib2f_ = (PFNGLVERTEXATTRIB2FPROC) getprocaddress("glVertexAttrib2f"); - glVertexAttrib2fv_ = (PFNGLVERTEXATTRIB2FVPROC) getprocaddress("glVertexAttrib2fv"); - glVertexAttrib2s_ = (PFNGLVERTEXATTRIB2SPROC) getprocaddress("glVertexAttrib2s"); - glVertexAttrib2sv_ = (PFNGLVERTEXATTRIB2SVPROC) getprocaddress("glVertexAttrib2sv"); - glVertexAttrib3f_ = (PFNGLVERTEXATTRIB3FPROC) getprocaddress("glVertexAttrib3f"); - glVertexAttrib3fv_ = (PFNGLVERTEXATTRIB3FVPROC) getprocaddress("glVertexAttrib3fv"); - glVertexAttrib3s_ = (PFNGLVERTEXATTRIB3SPROC) getprocaddress("glVertexAttrib3s"); - glVertexAttrib3sv_ = (PFNGLVERTEXATTRIB3SVPROC) getprocaddress("glVertexAttrib3sv"); - glVertexAttrib4f_ = (PFNGLVERTEXATTRIB4FPROC) getprocaddress("glVertexAttrib4f"); - glVertexAttrib4fv_ = (PFNGLVERTEXATTRIB4FVPROC) getprocaddress("glVertexAttrib4fv"); - glVertexAttrib4s_ = (PFNGLVERTEXATTRIB4SPROC) getprocaddress("glVertexAttrib4s"); - glVertexAttrib4sv_ = (PFNGLVERTEXATTRIB4SVPROC) getprocaddress("glVertexAttrib4sv"); - glVertexAttrib4bv_ = (PFNGLVERTEXATTRIB4BVPROC) getprocaddress("glVertexAttrib4bv"); - glVertexAttrib4iv_ = (PFNGLVERTEXATTRIB4IVPROC) getprocaddress("glVertexAttrib4iv"); - glVertexAttrib4ubv_ = (PFNGLVERTEXATTRIB4UBVPROC) getprocaddress("glVertexAttrib4ubv"); - glVertexAttrib4uiv_ = (PFNGLVERTEXATTRIB4UIVPROC) getprocaddress("glVertexAttrib4uiv"); - glVertexAttrib4usv_ = (PFNGLVERTEXATTRIB4USVPROC) getprocaddress("glVertexAttrib4usv"); - glVertexAttrib4Nbv_ = (PFNGLVERTEXATTRIB4NBVPROC) getprocaddress("glVertexAttrib4Nbv"); - glVertexAttrib4Niv_ = (PFNGLVERTEXATTRIB4NIVPROC) getprocaddress("glVertexAttrib4Niv"); - glVertexAttrib4Nub_ = (PFNGLVERTEXATTRIB4NUBPROC) getprocaddress("glVertexAttrib4Nub"); - glVertexAttrib4Nubv_ = (PFNGLVERTEXATTRIB4NUBVPROC) getprocaddress("glVertexAttrib4Nubv"); - glVertexAttrib4Nuiv_ = (PFNGLVERTEXATTRIB4NUIVPROC) getprocaddress("glVertexAttrib4Nuiv"); - glVertexAttrib4Nusv_ = (PFNGLVERTEXATTRIB4NUSVPROC) getprocaddress("glVertexAttrib4Nusv"); - glVertexAttribPointer_ = (PFNGLVERTEXATTRIBPOINTERPROC) getprocaddress("glVertexAttribPointer"); - - glDrawBuffers_ = (PFNGLDRAWBUFFERSPROC) getprocaddress("glDrawBuffers"); - - if(glversion >= 300) - { - glGetStringi_ = (PFNGLGETSTRINGIPROC) getprocaddress("glGetStringi"); - glBindFragDataLocation_ = (PFNGLBINDFRAGDATALOCATIONPROC)getprocaddress("glBindFragDataLocation"); - } - - const char *glslstr = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION); - uint glslmajorversion, glslminorversion; - if(glslstr && sscanf(glslstr, " %u.%u", &glslmajorversion, &glslminorversion) == 2) glslversion = glslmajorversion*100 + glslminorversion; - - if(glslversion < 120) fatal("GLSL 1.20 or greater is required!"); - - parseglexts(); - - GLint val; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &val); - hwtexsize = val; - glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &val); - hwcubetexsize = val; - - if(glversion >= 300 || hasext("GL_ARB_texture_float") || hasext("GL_ATI_texture_float")) - { - hasTF = true; - if(glversion < 300 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_float extension."); - shadowmap = 1; - extern int smoothshadowmappeel; - smoothshadowmappeel = 1; - } - - if(glversion >= 300 || hasext("GL_ARB_texture_rg")) - { - hasTRG = true; - if(glversion < 300 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_rg extension."); - } - - if(glversion >= 300 || hasext("GL_ARB_framebuffer_object")) - { - glBindRenderbuffer_ = (PFNGLBINDRENDERBUFFERPROC) getprocaddress("glBindRenderbuffer"); - glDeleteRenderbuffers_ = (PFNGLDELETERENDERBUFFERSPROC) getprocaddress("glDeleteRenderbuffers"); - glGenRenderbuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenRenderbuffers"); - glRenderbufferStorage_ = (PFNGLRENDERBUFFERSTORAGEPROC) getprocaddress("glRenderbufferStorage"); - glCheckFramebufferStatus_ = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) getprocaddress("glCheckFramebufferStatus"); - glBindFramebuffer_ = (PFNGLBINDFRAMEBUFFERPROC) getprocaddress("glBindFramebuffer"); - glDeleteFramebuffers_ = (PFNGLDELETEFRAMEBUFFERSPROC) getprocaddress("glDeleteFramebuffers"); - glGenFramebuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenFramebuffers"); - glFramebufferTexture2D_ = (PFNGLFRAMEBUFFERTEXTURE2DPROC) getprocaddress("glFramebufferTexture2D"); - glFramebufferRenderbuffer_ = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)getprocaddress("glFramebufferRenderbuffer"); - glGenerateMipmap_ = (PFNGLGENERATEMIPMAPPROC) getprocaddress("glGenerateMipmap"); - glBlitFramebuffer_ = (PFNGLBLITFRAMEBUFFERPROC) getprocaddress("glBlitFramebuffer"); - hasAFBO = hasFBO = hasFBB = hasDS = true; - if(glversion < 300 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_framebuffer_object extension."); - } - else if(hasext("GL_EXT_framebuffer_object")) - { - glBindRenderbuffer_ = (PFNGLBINDRENDERBUFFERPROC) getprocaddress("glBindRenderbufferEXT"); - glDeleteRenderbuffers_ = (PFNGLDELETERENDERBUFFERSPROC) getprocaddress("glDeleteRenderbuffersEXT"); - glGenRenderbuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenRenderbuffersEXT"); - glRenderbufferStorage_ = (PFNGLRENDERBUFFERSTORAGEPROC) getprocaddress("glRenderbufferStorageEXT"); - glCheckFramebufferStatus_ = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) getprocaddress("glCheckFramebufferStatusEXT"); - glBindFramebuffer_ = (PFNGLBINDFRAMEBUFFERPROC) getprocaddress("glBindFramebufferEXT"); - glDeleteFramebuffers_ = (PFNGLDELETEFRAMEBUFFERSPROC) getprocaddress("glDeleteFramebuffersEXT"); - glGenFramebuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenFramebuffersEXT"); - glFramebufferTexture2D_ = (PFNGLFRAMEBUFFERTEXTURE2DPROC) getprocaddress("glFramebufferTexture2DEXT"); - glFramebufferRenderbuffer_ = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)getprocaddress("glFramebufferRenderbufferEXT"); - glGenerateMipmap_ = (PFNGLGENERATEMIPMAPPROC) getprocaddress("glGenerateMipmapEXT"); - hasFBO = true; - if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_framebuffer_object extension."); - - if(hasext("GL_EXT_framebuffer_blit")) - { - glBlitFramebuffer_ = (PFNGLBLITFRAMEBUFFERPROC) getprocaddress("glBlitFramebufferEXT"); - hasFBB = true; - if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_framebuffer_blit extension."); - } - - if(hasext("GL_EXT_packed_depth_stencil") || hasext("GL_NV_packed_depth_stencil")) - { - hasDS = true; - if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_packed_depth_stencil extension."); - } - } - else fatal("Framebuffer object support is required!"); - - extern int fpdepthfx; - if(ati) - { - //conoutf(CON_WARN, "WARNING: ATI cards may show garbage in skybox. (use \"/ati_skybox_bug 1\" to fix)"); - - minimizetcusage = 1; - if(hasTF && hasTRG) fpdepthfx = 1; - // On Catalyst 10.2, issuing an occlusion query on the first draw using a given cubemap texture causes a nasty crash - ati_cubemap_bug = 1; - } - else if(nvidia) - { - reservevpparams = 10; - rtsharefb = 0; // work-around for strange driver stalls involving when using many FBOs - extern int filltjoints; - if(glversion < 300 && !hasext("GL_EXT_gpu_shader4")) filltjoints = 0; // DX9 or less NV cards seem to not cause many sparklies - - if(hasTF && hasTRG) fpdepthfx = 1; - } - else - { - reservevpparams = 20; - - if(mesa) mesa_swap_bug = 1; - } - - if(glversion >= 300 || hasext("GL_ARB_map_buffer_range")) - { - glMapBufferRange_ = (PFNGLMAPBUFFERRANGEPROC) getprocaddress("glMapBufferRange"); - glFlushMappedBufferRange_ = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)getprocaddress("glFlushMappedBufferRange"); - hasMBR = true; - if(glversion < 300 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_map_buffer_range."); - } - - if(glversion >= 310 || hasext("GL_ARB_uniform_buffer_object")) - { - glGetUniformIndices_ = (PFNGLGETUNIFORMINDICESPROC) getprocaddress("glGetUniformIndices"); - glGetActiveUniformsiv_ = (PFNGLGETACTIVEUNIFORMSIVPROC) getprocaddress("glGetActiveUniformsiv"); - glGetUniformBlockIndex_ = (PFNGLGETUNIFORMBLOCKINDEXPROC) getprocaddress("glGetUniformBlockIndex"); - glGetActiveUniformBlockiv_ = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)getprocaddress("glGetActiveUniformBlockiv"); - glUniformBlockBinding_ = (PFNGLUNIFORMBLOCKBINDINGPROC) getprocaddress("glUniformBlockBinding"); - glBindBufferBase_ = (PFNGLBINDBUFFERBASEPROC) getprocaddress("glBindBufferBase"); - glBindBufferRange_ = (PFNGLBINDBUFFERRANGEPROC) getprocaddress("glBindBufferRange"); - - useubo = 1; - hasUBO = true; - if(glversion < 310 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_uniform_buffer_object extension."); - } - - if(glversion >= 300 || hasext("GL_ARB_vertex_array_object")) - { - glBindVertexArray_ = (PFNGLBINDVERTEXARRAYPROC) getprocaddress("glBindVertexArray"); - glDeleteVertexArrays_ = (PFNGLDELETEVERTEXARRAYSPROC)getprocaddress("glDeleteVertexArrays"); - glGenVertexArrays_ = (PFNGLGENVERTEXARRAYSPROC) getprocaddress("glGenVertexArrays"); - glIsVertexArray_ = (PFNGLISVERTEXARRAYPROC) getprocaddress("glIsVertexArray"); - hasVAO = true; - if(glversion < 300 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_vertex_array_object extension."); - } - else if(hasext("GL_APPLE_vertex_array_object")) - { - glBindVertexArray_ = (PFNGLBINDVERTEXARRAYPROC) getprocaddress("glBindVertexArrayAPPLE"); - glDeleteVertexArrays_ = (PFNGLDELETEVERTEXARRAYSPROC)getprocaddress("glDeleteVertexArraysAPPLE"); - glGenVertexArrays_ = (PFNGLGENVERTEXARRAYSPROC) getprocaddress("glGenVertexArraysAPPLE"); - glIsVertexArray_ = (PFNGLISVERTEXARRAYPROC) getprocaddress("glIsVertexArrayAPPLE"); - hasVAO = true; - if(dbgexts) conoutf(CON_INIT, "Using GL_APPLE_vertex_array_object extension."); - } - - if(glversion >= 330 || hasext("GL_ARB_texture_swizzle") || hasext("GL_EXT_texture_swizzle")) - { - hasTSW = true; - if(glversion < 330 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_swizzle extension."); - } - - if(hasext("GL_EXT_texture_compression_s3tc")) - { - hasS3TC = true; -#ifdef __APPLE__ - usetexcompress = 1; -#else - if(!mesa) usetexcompress = 2; -#endif - if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_texture_compression_s3tc extension."); - } - else if(hasext("GL_EXT_texture_compression_dxt1") && hasext("GL_ANGLE_texture_compression_dxt3") && hasext("GL_ANGLE_texture_compression_dxt5")) - { - hasS3TC = true; - if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_texture_compression_dxt1 extension."); - } - if(hasext("GL_3DFX_texture_compression_FXT1")) - { - hasFXT1 = true; - if(mesa) usetexcompress = max(usetexcompress, 1); - if(dbgexts) conoutf(CON_INIT, "Using GL_3DFX_texture_compression_FXT1."); - } - if(hasext("GL_EXT_texture_compression_latc")) - { - hasLATC = true; - if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_texture_compression_latc extension."); - } - if(glversion >= 300 || hasext("GL_ARB_texture_compression_rgtc") || hasext("GL_EXT_texture_compression_rgtc")) - { - hasRGTC = true; - if(glversion < 300 && dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_compression_rgtc extension."); - } - - if(hasext("GL_EXT_texture_filter_anisotropic")) - { - GLint val; - glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &val); - hwmaxaniso = val; - hasAF = true; - if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_texture_filter_anisotropic extension."); - } - - if(glversion >= 300 || hasext("GL_EXT_gpu_shader4")) - { - // on DX10 or above class cards (i.e. GF8 or RadeonHD) enable expensive features - extern int glare, maxdynlights, depthfxsize, blurdepthfx, texcompress; - waterfallrefract = 1; - glare = 1; - maxdynlights = MAXDYNLIGHTS; - depthfxsize = 10; - blurdepthfx = 0; - texcompress = max(texcompress, 1024 + 1); - } + const char *vendor = (const char *)glGetString(GL_VENDOR); + const char *renderer = (const char *)glGetString(GL_RENDERER); + const char *version = (const char *)glGetString(GL_VERSION); + conoutf(CON_INIT, "Renderer: %s (%s)", renderer, vendor); + conoutf(CON_INIT, "Driver: %s", version); + + bool mesa = false, intel = false, ati = false, nvidia = false; + if(strstr(renderer, "Mesa") || strstr(version, "Mesa")) + { + mesa = true; + if(strstr(renderer, "Intel")) intel = true; + } + else if(strstr(vendor, "NVIDIA")) + nvidia = true; + else if(strstr(vendor, "ATI") || strstr(vendor, "Advanced Micro Devices")) + ati = true; + else if(strstr(vendor, "Intel")) + intel = true; + + uint glmajorversion, glminorversion; + if(sscanf(version, " %u.%u", &glmajorversion, &glminorversion) != 2) glversion = 100; + else glversion = glmajorversion*100 + glminorversion*10; + + if(glversion < 200) fatal("OpenGL 2.0 or greater is required!"); + + glMultiDrawArrays_ = (PFNGLMULTIDRAWARRAYSPROC) getprocaddress("glMultiDrawArrays"); + glMultiDrawElements_ = (PFNGLMULTIDRAWELEMENTSPROC) getprocaddress("glMultiDrawElements"); + + glBlendFuncSeparate_ = (PFNGLBLENDFUNCSEPARATEPROC) getprocaddress("glBlendFuncSeparate"); + glBlendEquationSeparate_ = (PFNGLBLENDEQUATIONSEPARATEPROC) getprocaddress("glBlendEquationSeparate"); + glStencilOpSeparate_ = (PFNGLSTENCILOPSEPARATEPROC) getprocaddress("glStencilOpSeparate"); + glStencilFuncSeparate_ = (PFNGLSTENCILFUNCSEPARATEPROC) getprocaddress("glStencilFuncSeparate"); + glStencilMaskSeparate_ = (PFNGLSTENCILMASKSEPARATEPROC) getprocaddress("glStencilMaskSeparate"); + + glGenBuffers_ = (PFNGLGENBUFFERSPROC) getprocaddress("glGenBuffers"); + glBindBuffer_ = (PFNGLBINDBUFFERPROC) getprocaddress("glBindBuffer"); + glMapBuffer_ = (PFNGLMAPBUFFERPROC) getprocaddress("glMapBuffer"); + glUnmapBuffer_ = (PFNGLUNMAPBUFFERPROC) getprocaddress("glUnmapBuffer"); + glBufferData_ = (PFNGLBUFFERDATAPROC) getprocaddress("glBufferData"); + glBufferSubData_ = (PFNGLBUFFERSUBDATAPROC) getprocaddress("glBufferSubData"); + glDeleteBuffers_ = (PFNGLDELETEBUFFERSPROC) getprocaddress("glDeleteBuffers"); + glGetBufferSubData_ = (PFNGLGETBUFFERSUBDATAPROC) getprocaddress("glGetBufferSubData"); + + glGetQueryiv_ = (PFNGLGETQUERYIVPROC) getprocaddress("glGetQueryiv"); + glGenQueries_ = (PFNGLGENQUERIESPROC) getprocaddress("glGenQueries"); + glDeleteQueries_ = (PFNGLDELETEQUERIESPROC) getprocaddress("glDeleteQueries"); + glBeginQuery_ = (PFNGLBEGINQUERYPROC) getprocaddress("glBeginQuery"); + glEndQuery_ = (PFNGLENDQUERYPROC) getprocaddress("glEndQuery"); + glGetQueryObjectiv_ = (PFNGLGETQUERYOBJECTIVPROC) getprocaddress("glGetQueryObjectiv"); + glGetQueryObjectuiv_ = (PFNGLGETQUERYOBJECTUIVPROC) getprocaddress("glGetQueryObjectuiv"); + + glCreateProgram_ = (PFNGLCREATEPROGRAMPROC) getprocaddress("glCreateProgram"); + glDeleteProgram_ = (PFNGLDELETEPROGRAMPROC) getprocaddress("glDeleteProgram"); + glUseProgram_ = (PFNGLUSEPROGRAMPROC) getprocaddress("glUseProgram"); + glCreateShader_ = (PFNGLCREATESHADERPROC) getprocaddress("glCreateShader"); + glDeleteShader_ = (PFNGLDELETESHADERPROC) getprocaddress("glDeleteShader"); + glShaderSource_ = (PFNGLSHADERSOURCEPROC) getprocaddress("glShaderSource"); + glCompileShader_ = (PFNGLCOMPILESHADERPROC) getprocaddress("glCompileShader"); + glGetShaderiv_ = (PFNGLGETSHADERIVPROC) getprocaddress("glGetShaderiv"); + glGetProgramiv_ = (PFNGLGETPROGRAMIVPROC) getprocaddress("glGetProgramiv"); + glAttachShader_ = (PFNGLATTACHSHADERPROC) getprocaddress("glAttachShader"); + glGetProgramInfoLog_ = (PFNGLGETPROGRAMINFOLOGPROC) getprocaddress("glGetProgramInfoLog"); + glGetShaderInfoLog_ = (PFNGLGETSHADERINFOLOGPROC) getprocaddress("glGetShaderInfoLog"); + glLinkProgram_ = (PFNGLLINKPROGRAMPROC) getprocaddress("glLinkProgram"); + glGetUniformLocation_ = (PFNGLGETUNIFORMLOCATIONPROC) getprocaddress("glGetUniformLocation"); + glUniform1f_ = (PFNGLUNIFORM1FPROC) getprocaddress("glUniform1f"); + glUniform2f_ = (PFNGLUNIFORM2FPROC) getprocaddress("glUniform2f"); + glUniform3f_ = (PFNGLUNIFORM3FPROC) getprocaddress("glUniform3f"); + glUniform4f_ = (PFNGLUNIFORM4FPROC) getprocaddress("glUniform4f"); + glUniform1fv_ = (PFNGLUNIFORM1FVPROC) getprocaddress("glUniform1fv"); + glUniform2fv_ = (PFNGLUNIFORM2FVPROC) getprocaddress("glUniform2fv"); + glUniform3fv_ = (PFNGLUNIFORM3FVPROC) getprocaddress("glUniform3fv"); + glUniform4fv_ = (PFNGLUNIFORM4FVPROC) getprocaddress("glUniform4fv"); + glUniform1i_ = (PFNGLUNIFORM1IPROC) getprocaddress("glUniform1i"); + glUniform2i_ = (PFNGLUNIFORM2IPROC) getprocaddress("glUniform2i"); + glUniform3i_ = (PFNGLUNIFORM3IPROC) getprocaddress("glUniform3i"); + glUniform4i_ = (PFNGLUNIFORM4IPROC) getprocaddress("glUniform4i"); + glUniform1iv_ = (PFNGLUNIFORM1IVPROC) getprocaddress("glUniform1iv"); + glUniform2iv_ = (PFNGLUNIFORM2IVPROC) getprocaddress("glUniform2iv"); + glUniform3iv_ = (PFNGLUNIFORM3IVPROC) getprocaddress("glUniform3iv"); + glUniform4iv_ = (PFNGLUNIFORM4IVPROC) getprocaddress("glUniform4iv"); + glUniformMatrix2fv_ = (PFNGLUNIFORMMATRIX2FVPROC) getprocaddress("glUniformMatrix2fv"); + glUniformMatrix3fv_ = (PFNGLUNIFORMMATRIX3FVPROC) getprocaddress("glUniformMatrix3fv"); + glUniformMatrix4fv_ = (PFNGLUNIFORMMATRIX4FVPROC) getprocaddress("glUniformMatrix4fv"); + glBindAttribLocation_ = (PFNGLBINDATTRIBLOCATIONPROC) getprocaddress("glBindAttribLocation"); + glGetActiveUniform_ = (PFNGLGETACTIVEUNIFORMPROC) getprocaddress("glGetActiveUniform"); + glEnableVertexAttribArray_ = (PFNGLENABLEVERTEXATTRIBARRAYPROC) getprocaddress("glEnableVertexAttribArray"); + glDisableVertexAttribArray_ = (PFNGLDISABLEVERTEXATTRIBARRAYPROC) getprocaddress("glDisableVertexAttribArray"); + + glVertexAttrib1f_ = (PFNGLVERTEXATTRIB1FPROC) getprocaddress("glVertexAttrib1f"); + glVertexAttrib1fv_ = (PFNGLVERTEXATTRIB1FVPROC) getprocaddress("glVertexAttrib1fv"); + glVertexAttrib1s_ = (PFNGLVERTEXATTRIB1SPROC) getprocaddress("glVertexAttrib1s"); + glVertexAttrib1sv_ = (PFNGLVERTEXATTRIB1SVPROC) getprocaddress("glVertexAttrib1sv"); + glVertexAttrib2f_ = (PFNGLVERTEXATTRIB2FPROC) getprocaddress("glVertexAttrib2f"); + glVertexAttrib2fv_ = (PFNGLVERTEXATTRIB2FVPROC) getprocaddress("glVertexAttrib2fv"); + glVertexAttrib2s_ = (PFNGLVERTEXATTRIB2SPROC) getprocaddress("glVertexAttrib2s"); + glVertexAttrib2sv_ = (PFNGLVERTEXATTRIB2SVPROC) getprocaddress("glVertexAttrib2sv"); + glVertexAttrib3f_ = (PFNGLVERTEXATTRIB3FPROC) getprocaddress("glVertexAttrib3f"); + glVertexAttrib3fv_ = (PFNGLVERTEXATTRIB3FVPROC) getprocaddress("glVertexAttrib3fv"); + glVertexAttrib3s_ = (PFNGLVERTEXATTRIB3SPROC) getprocaddress("glVertexAttrib3s"); + glVertexAttrib3sv_ = (PFNGLVERTEXATTRIB3SVPROC) getprocaddress("glVertexAttrib3sv"); + glVertexAttrib4f_ = (PFNGLVERTEXATTRIB4FPROC) getprocaddress("glVertexAttrib4f"); + glVertexAttrib4fv_ = (PFNGLVERTEXATTRIB4FVPROC) getprocaddress("glVertexAttrib4fv"); + glVertexAttrib4s_ = (PFNGLVERTEXATTRIB4SPROC) getprocaddress("glVertexAttrib4s"); + glVertexAttrib4sv_ = (PFNGLVERTEXATTRIB4SVPROC) getprocaddress("glVertexAttrib4sv"); + glVertexAttrib4bv_ = (PFNGLVERTEXATTRIB4BVPROC) getprocaddress("glVertexAttrib4bv"); + glVertexAttrib4iv_ = (PFNGLVERTEXATTRIB4IVPROC) getprocaddress("glVertexAttrib4iv"); + glVertexAttrib4ubv_ = (PFNGLVERTEXATTRIB4UBVPROC) getprocaddress("glVertexAttrib4ubv"); + glVertexAttrib4uiv_ = (PFNGLVERTEXATTRIB4UIVPROC) getprocaddress("glVertexAttrib4uiv"); + glVertexAttrib4usv_ = (PFNGLVERTEXATTRIB4USVPROC) getprocaddress("glVertexAttrib4usv"); + glVertexAttrib4Nbv_ = (PFNGLVERTEXATTRIB4NBVPROC) getprocaddress("glVertexAttrib4Nbv"); + glVertexAttrib4Niv_ = (PFNGLVERTEXATTRIB4NIVPROC) getprocaddress("glVertexAttrib4Niv"); + glVertexAttrib4Nub_ = (PFNGLVERTEXATTRIB4NUBPROC) getprocaddress("glVertexAttrib4Nub"); + glVertexAttrib4Nubv_ = (PFNGLVERTEXATTRIB4NUBVPROC) getprocaddress("glVertexAttrib4Nubv"); + glVertexAttrib4Nuiv_ = (PFNGLVERTEXATTRIB4NUIVPROC) getprocaddress("glVertexAttrib4Nuiv"); + glVertexAttrib4Nusv_ = (PFNGLVERTEXATTRIB4NUSVPROC) getprocaddress("glVertexAttrib4Nusv"); + glVertexAttribPointer_ = (PFNGLVERTEXATTRIBPOINTERPROC) getprocaddress("glVertexAttribPointer"); + + glDrawBuffers_ = (PFNGLDRAWBUFFERSPROC) getprocaddress("glDrawBuffers"); + + if(glversion >= 300) + { + glGetStringi_ = (PFNGLGETSTRINGIPROC) getprocaddress("glGetStringi"); + glBindFragDataLocation_ = (PFNGLBINDFRAGDATALOCATIONPROC)getprocaddress("glBindFragDataLocation"); + } + + const char *glslstr = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION); + uint glslmajorversion, glslminorversion; + if(glslstr && sscanf(glslstr, " %u.%u", &glslmajorversion, &glslminorversion) == 2) glslversion = glslmajorversion*100 + glslminorversion; + + if(glslversion < 120) fatal("GLSL 1.20 or greater is required!"); + + parseglexts(); + + GLint val; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &val); + hwtexsize = val; + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &val); + hwcubetexsize = val; + + if(glversion >= 300 || hasext("GL_ARB_texture_float") || hasext("GL_ATI_texture_float")) + { + hasTF = true; + shadowmap = 1; + extern int smoothshadowmappeel; + smoothshadowmappeel = 1; + } + + if(glversion >= 300 || hasext("GL_ARB_texture_rg")) + { + hasTRG = true; + } + + if(glversion >= 300 || hasext("GL_ARB_framebuffer_object")) + { + glBindRenderbuffer_ = (PFNGLBINDRENDERBUFFERPROC) getprocaddress("glBindRenderbuffer"); + glDeleteRenderbuffers_ = (PFNGLDELETERENDERBUFFERSPROC) getprocaddress("glDeleteRenderbuffers"); + glGenRenderbuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenRenderbuffers"); + glRenderbufferStorage_ = (PFNGLRENDERBUFFERSTORAGEPROC) getprocaddress("glRenderbufferStorage"); + glCheckFramebufferStatus_ = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) getprocaddress("glCheckFramebufferStatus"); + glBindFramebuffer_ = (PFNGLBINDFRAMEBUFFERPROC) getprocaddress("glBindFramebuffer"); + glDeleteFramebuffers_ = (PFNGLDELETEFRAMEBUFFERSPROC) getprocaddress("glDeleteFramebuffers"); + glGenFramebuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenFramebuffers"); + glFramebufferTexture2D_ = (PFNGLFRAMEBUFFERTEXTURE2DPROC) getprocaddress("glFramebufferTexture2D"); + glFramebufferRenderbuffer_ = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)getprocaddress("glFramebufferRenderbuffer"); + glGenerateMipmap_ = (PFNGLGENERATEMIPMAPPROC) getprocaddress("glGenerateMipmap"); + glBlitFramebuffer_ = (PFNGLBLITFRAMEBUFFERPROC) getprocaddress("glBlitFramebuffer"); + hasAFBO = hasFBO = hasFBB = hasDS = true; + } + else if(hasext("GL_EXT_framebuffer_object")) + { + glBindRenderbuffer_ = (PFNGLBINDRENDERBUFFERPROC) getprocaddress("glBindRenderbufferEXT"); + glDeleteRenderbuffers_ = (PFNGLDELETERENDERBUFFERSPROC) getprocaddress("glDeleteRenderbuffersEXT"); + glGenRenderbuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenRenderbuffersEXT"); + glRenderbufferStorage_ = (PFNGLRENDERBUFFERSTORAGEPROC) getprocaddress("glRenderbufferStorageEXT"); + glCheckFramebufferStatus_ = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) getprocaddress("glCheckFramebufferStatusEXT"); + glBindFramebuffer_ = (PFNGLBINDFRAMEBUFFERPROC) getprocaddress("glBindFramebufferEXT"); + glDeleteFramebuffers_ = (PFNGLDELETEFRAMEBUFFERSPROC) getprocaddress("glDeleteFramebuffersEXT"); + glGenFramebuffers_ = (PFNGLGENFRAMEBUFFERSPROC) getprocaddress("glGenFramebuffersEXT"); + glFramebufferTexture2D_ = (PFNGLFRAMEBUFFERTEXTURE2DPROC) getprocaddress("glFramebufferTexture2DEXT"); + glFramebufferRenderbuffer_ = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)getprocaddress("glFramebufferRenderbufferEXT"); + glGenerateMipmap_ = (PFNGLGENERATEMIPMAPPROC) getprocaddress("glGenerateMipmapEXT"); + hasFBO = true; + + if(hasext("GL_EXT_framebuffer_blit")) + { + glBlitFramebuffer_ = (PFNGLBLITFRAMEBUFFERPROC) getprocaddress("glBlitFramebufferEXT"); + hasFBB = true; + } + + if(hasext("GL_EXT_packed_depth_stencil") || hasext("GL_NV_packed_depth_stencil")) + { + hasDS = true; + } + } + else fatal("Framebuffer object support is required!"); + + extern int fpdepthfx; + if(ati) + { + //conoutf(CON_WARN, "WARNING: ATI cards may show garbage in skybox. (use \"/ati_skybox_bug 1\" to fix)"); + + minimizetcusage = 1; + if(hasTF && hasTRG) fpdepthfx = 1; + // On Catalyst 10.2, issuing an occlusion query on the first draw using a given cubemap texture causes a nasty crash + ati_cubemap_bug = 1; + } + else if(nvidia) + { + reservevpparams = 10; + rtsharefb = 0; // work-around for strange driver stalls involving when using many FBOs + extern int filltjoints; + if(glversion < 300 && !hasext("GL_EXT_gpu_shader4")) filltjoints = 0; // DX9 or less NV cards seem to not cause many sparklies + + if(hasTF && hasTRG) fpdepthfx = 1; + } + else + { + reservevpparams = 20; + + if(mesa) mesa_swap_bug = 1; + } + + if(glversion >= 300 || hasext("GL_ARB_map_buffer_range")) + { + glMapBufferRange_ = (PFNGLMAPBUFFERRANGEPROC) getprocaddress("glMapBufferRange"); + glFlushMappedBufferRange_ = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC)getprocaddress("glFlushMappedBufferRange"); + hasMBR = true; + } + + if(glversion >= 310 || hasext("GL_ARB_uniform_buffer_object")) + { + glGetUniformIndices_ = (PFNGLGETUNIFORMINDICESPROC) getprocaddress("glGetUniformIndices"); + glGetActiveUniformsiv_ = (PFNGLGETACTIVEUNIFORMSIVPROC) getprocaddress("glGetActiveUniformsiv"); + glGetUniformBlockIndex_ = (PFNGLGETUNIFORMBLOCKINDEXPROC) getprocaddress("glGetUniformBlockIndex"); + glGetActiveUniformBlockiv_ = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)getprocaddress("glGetActiveUniformBlockiv"); + glUniformBlockBinding_ = (PFNGLUNIFORMBLOCKBINDINGPROC) getprocaddress("glUniformBlockBinding"); + glBindBufferBase_ = (PFNGLBINDBUFFERBASEPROC) getprocaddress("glBindBufferBase"); + glBindBufferRange_ = (PFNGLBINDBUFFERRANGEPROC) getprocaddress("glBindBufferRange"); + + useubo = 1; + hasUBO = true; + } + + if(glversion >= 300 || hasext("GL_ARB_vertex_array_object")) + { + glBindVertexArray_ = (PFNGLBINDVERTEXARRAYPROC) getprocaddress("glBindVertexArray"); + glDeleteVertexArrays_ = (PFNGLDELETEVERTEXARRAYSPROC)getprocaddress("glDeleteVertexArrays"); + glGenVertexArrays_ = (PFNGLGENVERTEXARRAYSPROC) getprocaddress("glGenVertexArrays"); + glIsVertexArray_ = (PFNGLISVERTEXARRAYPROC) getprocaddress("glIsVertexArray"); + hasVAO = true; + } + else if(hasext("GL_APPLE_vertex_array_object")) + { + glBindVertexArray_ = (PFNGLBINDVERTEXARRAYPROC) getprocaddress("glBindVertexArrayAPPLE"); + glDeleteVertexArrays_ = (PFNGLDELETEVERTEXARRAYSPROC)getprocaddress("glDeleteVertexArraysAPPLE"); + glGenVertexArrays_ = (PFNGLGENVERTEXARRAYSPROC) getprocaddress("glGenVertexArraysAPPLE"); + glIsVertexArray_ = (PFNGLISVERTEXARRAYPROC) getprocaddress("glIsVertexArrayAPPLE"); + hasVAO = true; + } + + if(glversion >= 330 || hasext("GL_ARB_texture_swizzle") || hasext("GL_EXT_texture_swizzle")) + { + hasTSW = true; + } + + if(hasext("GL_EXT_texture_compression_s3tc")) + { + hasS3TC = true; + if(!mesa) usetexcompress = 2; + } + else if(hasext("GL_EXT_texture_compression_dxt1") && hasext("GL_ANGLE_texture_compression_dxt3") && hasext("GL_ANGLE_texture_compression_dxt5")) + { + hasS3TC = true; + } + if(hasext("GL_3DFX_texture_compression_FXT1")) + { + hasFXT1 = true; + if(mesa) usetexcompress = max(usetexcompress, 1); + } + if(hasext("GL_EXT_texture_compression_latc")) + { + hasLATC = true; + } + if(glversion >= 300 || hasext("GL_ARB_texture_compression_rgtc") || hasext("GL_EXT_texture_compression_rgtc")) + { + hasRGTC = true; + } + + if(hasext("GL_EXT_texture_filter_anisotropic")) + { + GLint val; + glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &val); + hwmaxaniso = val; + hasAF = true; + } + + if(glversion >= 300 || hasext("GL_EXT_gpu_shader4")) + { + // on DX10 or above class cards (i.e. GF8 or RadeonHD) enable expensive features + extern int glare, maxdynlights, depthfxsize, blurdepthfx, texcompress; + waterfallrefract = 1; + glare = 1; + maxdynlights = MAXDYNLIGHTS; + depthfxsize = 10; + blurdepthfx = 0; + texcompress = max(texcompress, 1024 + 1); + } } void glext(char *ext) { - intret(hasext(ext) ? 1 : 0); + intret(hasext(ext) ? 1 : 0); } COMMAND(glext, "s"); void gl_resize() { - glViewport(0, 0, screenw, screenh); + glViewport(0, 0, screenw, screenh); } void gl_init() { - glClearColor(0, 0, 0, 0); - glClearDepth(1); - glDepthFunc(GL_LESS); - glDisable(GL_DEPTH_TEST); + glClearColor(0, 0, 0, 0); + glClearDepth(1); + glDepthFunc(GL_LESS); + glDisable(GL_DEPTH_TEST); - glEnable(GL_LINE_SMOOTH); - //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glEnable(GL_LINE_SMOOTH); + //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glFrontFace(GL_CW); - glCullFace(GL_BACK); - glDisable(GL_CULL_FACE); + glFrontFace(GL_CW); + glCullFace(GL_BACK); + glDisable(GL_CULL_FACE); - gle::setup(); + gle::setup(); - setupshaders(); + setupshaders(); - setuptexcompress(); + setuptexcompress(); - gl_resize(); + gl_resize(); } VAR(wireframe, 0, 0, 1); @@ -570,64 +547,64 @@ ICOMMAND(getcampitch, "", (), floatret(camera1->pitch)); ICOMMAND(getcamroll, "", (), floatret(camera1->roll)); ICOMMAND(getcampos, "", (), { - defformatstring(pos, "%s %s %s", floatstr(camera1->o.x), floatstr(camera1->o.y), floatstr(camera1->o.z)); - result(pos); + defformatstring(pos, "%s %s %s", floatstr(camera1->o.x), floatstr(camera1->o.y), floatstr(camera1->o.z)); + result(pos); }); vec worldpos, camdir, camright, camup; void setcammatrix() { - // move from RH to Z-up LH quake style worldspace - cammatrix = viewmatrix; - cammatrix.rotate_around_y(camera1->roll*RAD); - cammatrix.rotate_around_x(camera1->pitch*-RAD); - cammatrix.rotate_around_z(camera1->yaw*-RAD); - cammatrix.translate(vec(camera1->o).neg()); + // move from RH to Z-up LH quake style worldspace + cammatrix = viewmatrix; + cammatrix.rotate_around_y(camera1->roll*RAD); + cammatrix.rotate_around_x(camera1->pitch*-RAD); + cammatrix.rotate_around_z(camera1->yaw*-RAD); + cammatrix.translate(vec(camera1->o).neg()); - cammatrix.transposedtransformnormal(vec(viewmatrix.b), camdir); - cammatrix.transposedtransformnormal(vec(viewmatrix.a).neg(), camright); - cammatrix.transposedtransformnormal(vec(viewmatrix.c), camup); + cammatrix.transposedtransformnormal(vec(viewmatrix.b), camdir); + cammatrix.transposedtransformnormal(vec(viewmatrix.a).neg(), camright); + cammatrix.transposedtransformnormal(vec(viewmatrix.c), camup); - if(!drawtex) - { - if(raycubepos(camera1->o, camdir, worldpos, 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1) - worldpos = vec(camdir).mul(2*worldsize).add(camera1->o); // if nothing is hit, just far away in the view direction - } + if(!drawtex) + { + if(raycubepos(camera1->o, camdir, worldpos, 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1) + worldpos = vec(camdir).mul(2*worldsize).add(camera1->o); // if nothing is hit, just far away in the view direction + } } void setcamprojmatrix(bool init = true, bool flush = false) { - if(init) - { - setcammatrix(); - } + if(init) + { + setcammatrix(); + } - camprojmatrix.muld(projmatrix, cammatrix); + camprojmatrix.muld(projmatrix, cammatrix); - if(init) - { - invcammatrix.invert(cammatrix); - invcamprojmatrix.invert(camprojmatrix); - } + if(init) + { + invcammatrix.invert(cammatrix); + invcamprojmatrix.invert(camprojmatrix); + } - GLOBALPARAM(camprojmatrix, camprojmatrix); + GLOBALPARAM(camprojmatrix, camprojmatrix); - if(fogging) - { - vec fogplane(cammatrix.c); - fogplane.x /= projmatrix.a.x; - fogplane.y /= projmatrix.b.y; - fogplane.z /= projmatrix.c.w; - GLOBALPARAMF(fogplane, fogplane.x, fogplane.y, 0, fogplane.z); - } - else - { - vec2 lineardepthscale = projmatrix.lineardepthscale(); - GLOBALPARAMF(fogplane, 0, 0, lineardepthscale.x, lineardepthscale.y); - } + if(fogging) + { + vec fogplane(cammatrix.c); + fogplane.x /= projmatrix.a.x; + fogplane.y /= projmatrix.b.y; + fogplane.z /= projmatrix.c.w; + GLOBALPARAMF(fogplane, fogplane.x, fogplane.y, 0, fogplane.z); + } + else + { + vec2 lineardepthscale = projmatrix.lineardepthscale(); + GLOBALPARAMF(fogplane, 0, 0, lineardepthscale.x, lineardepthscale.y); + } - if(flush && Shader::lastshader) Shader::lastshader->flushparams(); + if(flush && Shader::lastshader) Shader::lastshader->flushparams(); } matrix4 hudmatrix, hudmatrixstack[64]; @@ -635,47 +612,47 @@ int hudmatrixpos = 0; void resethudmatrix() { - hudmatrixpos = 0; - GLOBALPARAM(hudmatrix, hudmatrix); + hudmatrixpos = 0; + GLOBALPARAM(hudmatrix, hudmatrix); } void pushhudmatrix() { - if(hudmatrixpos >= 0 && hudmatrixpos < int(sizeof(hudmatrixstack)/sizeof(hudmatrixstack[0]))) hudmatrixstack[hudmatrixpos] = hudmatrix; - ++hudmatrixpos; + if(hudmatrixpos >= 0 && hudmatrixpos < int(sizeof(hudmatrixstack)/sizeof(hudmatrixstack[0]))) hudmatrixstack[hudmatrixpos] = hudmatrix; + ++hudmatrixpos; } void flushhudmatrix(bool flushparams) { - GLOBALPARAM(hudmatrix, hudmatrix); - if(flushparams && Shader::lastshader) Shader::lastshader->flushparams(); + GLOBALPARAM(hudmatrix, hudmatrix); + if(flushparams && Shader::lastshader) Shader::lastshader->flushparams(); } void pophudmatrix(bool flush, bool flushparams) { - --hudmatrixpos; - if(hudmatrixpos >= 0 && hudmatrixpos < int(sizeof(hudmatrixstack)/sizeof(hudmatrixstack[0]))) - { - hudmatrix = hudmatrixstack[hudmatrixpos]; - if(flush) flushhudmatrix(flushparams); - } + --hudmatrixpos; + if(hudmatrixpos >= 0 && hudmatrixpos < int(sizeof(hudmatrixstack)/sizeof(hudmatrixstack[0]))) + { + hudmatrix = hudmatrixstack[hudmatrixpos]; + if(flush) flushhudmatrix(flushparams); + } } void pushhudscale(float sx, float sy) { - if(!sy) sy = sx; - pushhudmatrix(); - hudmatrix.scale(sx, sy, 1); - flushhudmatrix(); + if(!sy) sy = sx; + pushhudmatrix(); + hudmatrix.scale(sx, sy, 1); + flushhudmatrix(); } void pushhudtranslate(float tx, float ty, float sx, float sy) { - if(!sy) sy = sx; - pushhudmatrix(); - hudmatrix.translate(tx, ty, 0); - if(sy) hudmatrix.scale(sx, sy, 1); - flushhudmatrix(); + if(!sy) sy = sx; + pushhudmatrix(); + hudmatrix.translate(tx, ty, 0); + if(sy) hudmatrix.scale(sx, sy, 1); + flushhudmatrix(); } float curfov = 100, curavatarfov = 65, fovy, aspect; @@ -694,21 +671,21 @@ VAR(zoom, -1, 0, 1); void disablezoom() { - zoom = 0; - zoomprogress = 0; + zoom = 0; + zoomprogress = 0; } void computezoom() { - if(!zoom) { zoomprogress = 0; curfov = fov; curavatarfov = avatarfov; return; } - if(zoom > 0) zoomprogress = zoominvel ? min(zoomprogress + float(elapsedtime) / zoominvel, 1.0f) : 1; - else - { - zoomprogress = zoomoutvel ? max(zoomprogress - float(elapsedtime) / zoomoutvel, 0.0f) : 0; - if(zoomprogress <= 0) zoom = 0; - } - curfov = zoomfov*zoomprogress + fov*(1 - zoomprogress); - curavatarfov = avatarzoomfov*zoomprogress + avatarfov*(1 - zoomprogress); + if(!zoom) { zoomprogress = 0; curfov = fov; curavatarfov = avatarfov; return; } + if(zoom > 0) zoomprogress = zoominvel ? min(zoomprogress + float(elapsedtime) / zoominvel, 1.0f) : 1; + else + { + zoomprogress = zoomoutvel ? max(zoomprogress - float(elapsedtime) / zoomoutvel, 0.0f) : 0; + if(zoomprogress <= 0) zoom = 0; + } + curfov = zoomfov*zoomprogress + fov*(1 - zoomprogress); + curavatarfov = avatarzoomfov*zoomprogress + avatarfov*(1 - zoomprogress); } FVARP(zoomsens, 1e-3f, 1, 1000); @@ -729,105 +706,105 @@ bool isthirdperson() { return player!=camera1 || detachedcamera || reflecting; } void fixcamerarange() { - const float MAXPITCH = 90.0f; - if(camera1->pitch>MAXPITCH) camera1->pitch = MAXPITCH; - if(camera1->pitch<-MAXPITCH) camera1->pitch = -MAXPITCH; - while(camera1->yaw<0.0f) camera1->yaw += 360.0f; - while(camera1->yaw>=360.0f) camera1->yaw -= 360.0f; + const float MAXPITCH = 90.0f; + if(camera1->pitch>MAXPITCH) camera1->pitch = MAXPITCH; + if(camera1->pitch<-MAXPITCH) camera1->pitch = -MAXPITCH; + while(camera1->yaw<0.0f) camera1->yaw += 360.0f; + while(camera1->yaw>=360.0f) camera1->yaw -= 360.0f; } void mousemove(int dx, int dy) { - if(!game::allowmouselook()) return; - float cursens = sensitivity, curaccel = mouseaccel; - if(zoom) - { - if(zoomautosens) - { - cursens = float(sensitivity*zoomfov)/fov; - curaccel = float(mouseaccel*zoomfov)/fov; - } - else - { - cursens = zoomsens; - curaccel = zoomaccel; - } - } - if(curaccel && curtime && (dx || dy)) cursens += curaccel * sqrtf(dx*dx + dy*dy)/curtime; - cursens /= 33.0f*sensitivityscale; - camera1->yaw += dx*cursens; - camera1->pitch -= dy*cursens*(invmouse ? -1 : 1); - fixcamerarange(); - if(camera1!=player && !detachedcamera) - { - player->yaw = camera1->yaw; - player->pitch = camera1->pitch; - } - speedmodifier += abs(dx)-abs(dy); + if(!game::allowmouselook()) return; + float cursens = sensitivity, curaccel = mouseaccel; + if(zoom) + { + if(zoomautosens) + { + cursens = float(sensitivity*zoomfov)/fov; + curaccel = float(mouseaccel*zoomfov)/fov; + } + else + { + cursens = zoomsens; + curaccel = zoomaccel; + } + } + if(curaccel && curtime && (dx || dy)) cursens += curaccel * sqrtf(dx*dx + dy*dy)/curtime; + cursens /= 33.0f*sensitivityscale; + camera1->yaw += dx*cursens; + camera1->pitch -= dy*cursens*(invmouse ? -1 : 1); + fixcamerarange(); + if(camera1!=player && !detachedcamera) + { + player->yaw = camera1->yaw; + player->pitch = camera1->pitch; + } + speedmodifier += abs(dx)-abs(dy); } void recomputecamera() { - game::setupcamera(); - computezoom(); - - bool allowthirdperson = game::allowthirdperson(); - bool shoulddetach = (allowthirdperson && thirdperson > 1) || game::detachcamera(); - if((!allowthirdperson || !thirdperson) && !shoulddetach) - { - camera1 = player; - detachedcamera = false; - } - else - { - static physent tempcamera; - camera1 = &tempcamera; - if(detachedcamera && shoulddetach) camera1->o = player->o; - else - { - *camera1 = *player; - detachedcamera = shoulddetach; - } - camera1->reset(); - camera1->type = ENT_CAMERA; - camera1->move = -1; - camera1->eyeheight = camera1->aboveeye = camera1->radius = camera1->xradius = camera1->yradius = 2; - - matrix3 orient; - orient.identity(); - orient.rotate_around_z(camera1->yaw*RAD); - orient.rotate_around_x(camera1->pitch*RAD); - orient.rotate_around_y(camera1->roll*-RAD); - vec dir = vec(orient.b).neg(), side = vec(orient.a).neg(), up = orient.c; - - if(game::collidecamera()) - { - movecamera(camera1, dir, thirdpersondistance, 1); - movecamera(camera1, dir, clamp(thirdpersondistance - camera1->o.dist(player->o), 0.0f, 1.0f), 0.1f); - if(thirdpersonup) - { - vec pos = camera1->o; - float dist = fabs(thirdpersonup); - if(thirdpersonup < 0) up.neg(); - movecamera(camera1, up, dist, 1); - movecamera(camera1, up, clamp(dist - camera1->o.dist(pos), 0.0f, 1.0f), 0.1f); - } - if(thirdpersonside) - { - vec pos = camera1->o; - float dist = fabs(thirdpersonside); - if(thirdpersonside < 0) side.neg(); - movecamera(camera1, side, dist, 1); - movecamera(camera1, side, clamp(dist - camera1->o.dist(pos), 0.0f, 1.0f), 0.1f); - } - } - else - { - camera1->o.add(vec(dir).mul(thirdpersondistance)); - if(thirdpersonup) camera1->o.add(vec(up).mul(thirdpersonup)); - if(thirdpersonside) camera1->o.add(vec(side).mul(thirdpersonside)); - } - } + game::setupcamera(); + computezoom(); + + bool allowthirdperson = game::allowthirdperson(); + bool shoulddetach = (allowthirdperson && thirdperson > 1) || game::detachcamera(); + if((!allowthirdperson || !thirdperson) && !shoulddetach) + { + camera1 = player; + detachedcamera = false; + } + else + { + static physent tempcamera; + camera1 = &tempcamera; + if(detachedcamera && shoulddetach) camera1->o = player->o; + else + { + *camera1 = *player; + detachedcamera = shoulddetach; + } + camera1->reset(); + camera1->type = ENT_CAMERA; + camera1->move = -1; + camera1->eyeheight = camera1->aboveeye = camera1->radius = camera1->xradius = camera1->yradius = 2; + + matrix3 orient; + orient.identity(); + orient.rotate_around_z(camera1->yaw*RAD); + orient.rotate_around_x(camera1->pitch*RAD); + orient.rotate_around_y(camera1->roll*-RAD); + vec dir = vec(orient.b).neg(), side = vec(orient.a).neg(), up = orient.c; + + if(game::collidecamera()) + { + movecamera(camera1, dir, thirdpersondistance, 1); + movecamera(camera1, dir, clamp(thirdpersondistance - camera1->o.dist(player->o), 0.0f, 1.0f), 0.1f); + if(thirdpersonup) + { + vec pos = camera1->o; + float dist = fabs(thirdpersonup); + if(thirdpersonup < 0) up.neg(); + movecamera(camera1, up, dist, 1); + movecamera(camera1, up, clamp(dist - camera1->o.dist(pos), 0.0f, 1.0f), 0.1f); + } + if(thirdpersonside) + { + vec pos = camera1->o; + float dist = fabs(thirdpersonside); + if(thirdpersonside < 0) side.neg(); + movecamera(camera1, side, dist, 1); + movecamera(camera1, side, clamp(dist - camera1->o.dist(pos), 0.0f, 1.0f), 0.1f); + } + } + else + { + camera1->o.add(vec(dir).mul(thirdpersondistance)); + if(thirdpersonup) camera1->o.add(vec(up).mul(thirdpersonup)); + if(thirdpersonside) camera1->o.add(vec(side).mul(thirdpersonside)); + } + } } extern const matrix4 viewmatrix(vec(-1, 0, 0), vec(0, 0, 1), vec(0, -1, 0)); @@ -837,18 +814,18 @@ FVAR(nearplane, 0.01f, 0.54f, 2.0f); vec calcavatarpos(const vec &pos, float dist) { - vec eyepos; - cammatrix.transform(pos, eyepos); - GLdouble ydist = nearplane * tan(curavatarfov/2*RAD), xdist = ydist * aspect; - vec4 scrpos; - scrpos.x = eyepos.x*nearplane/xdist; - scrpos.y = eyepos.y*nearplane/ydist; - scrpos.z = (eyepos.z*(farplane + nearplane) - 2*nearplane*farplane) / (farplane - nearplane); - scrpos.w = -eyepos.z; + vec eyepos; + cammatrix.transform(pos, eyepos); + GLdouble ydist = nearplane * tan(curavatarfov/2*RAD), xdist = ydist * aspect; + vec4 scrpos; + scrpos.x = eyepos.x*nearplane/xdist; + scrpos.y = eyepos.y*nearplane/ydist; + scrpos.z = (eyepos.z*(farplane + nearplane) - 2*nearplane*farplane) / (farplane - nearplane); + scrpos.w = -eyepos.z; - vec worldpos = invcamprojmatrix.perspectivetransform(scrpos); - vec dir = vec(worldpos).sub(camera1->o).rescale(dist); - return dir.add(camera1->o); + vec worldpos = invcamprojmatrix.perspectivetransform(scrpos); + vec dir = vec(worldpos).sub(camera1->o).rescale(dist); + return dir.add(camera1->o); } VAR(reflectclip, 0, 6, 64); @@ -858,17 +835,17 @@ matrix4 clipmatrix, noclipmatrix; void renderavatar() { - if(isthirdperson()) return; + if(isthirdperson()) return; - matrix4 oldprojmatrix = projmatrix; - projmatrix.perspective(curavatarfov, aspect, nearplane, farplane); - projmatrix.scalez(avatardepth); - setcamprojmatrix(false); + matrix4 oldprojmatrix = projmatrix; + projmatrix.perspective(curavatarfov, aspect, nearplane, farplane); + projmatrix.scalez(avatardepth); + setcamprojmatrix(false); - game::renderavatar(); + game::renderavatar(); - projmatrix = oldprojmatrix; - setcamprojmatrix(false); + projmatrix = oldprojmatrix; + setcamprojmatrix(false); } FVAR(polygonoffsetfactor, -1e4f, -3.0f, 1e4f); @@ -879,67 +856,67 @@ matrix4 nooffsetmatrix; void enablepolygonoffset(GLenum type) { - if(!depthoffset) - { - glPolygonOffset(polygonoffsetfactor, polygonoffsetunits); - glEnable(type); - return; - } + if(!depthoffset) + { + glPolygonOffset(polygonoffsetfactor, polygonoffsetunits); + glEnable(type); + return; + } - bool clipped = reflectz < 1e15f && reflectclip; + bool clipped = reflectz < 1e15f && reflectclip; - nooffsetmatrix = projmatrix; - projmatrix.d.z += depthoffset * (clipped ? noclipmatrix.c.z : projmatrix.c.z); - setcamprojmatrix(false, true); + nooffsetmatrix = projmatrix; + projmatrix.d.z += depthoffset * (clipped ? noclipmatrix.c.z : projmatrix.c.z); + setcamprojmatrix(false, true); } void disablepolygonoffset(GLenum type) { - if(!depthoffset) - { - glDisable(type); - return; - } + if(!depthoffset) + { + glDisable(type); + return; + } - projmatrix = nooffsetmatrix; - setcamprojmatrix(false, true); + projmatrix = nooffsetmatrix; + setcamprojmatrix(false, true); } void calcspherescissor(const vec ¢er, float size, float &sx1, float &sy1, float &sx2, float &sy2) { - vec worldpos(center), e; - if(reflecting) worldpos.z = 2*reflectz - worldpos.z; - cammatrix.transform(worldpos, e); - if(e.z > 2*size) { sx1 = sy1 = 1; sx2 = sy2 = -1; return; } - float zzrr = e.z*e.z - size*size, - dx = e.x*e.x + zzrr, dy = e.y*e.y + zzrr, - focaldist = 1.0f/tan(fovy*0.5f*RAD); - sx1 = sy1 = -1; - sx2 = sy2 = 1; - #define CHECKPLANE(c, dir, focaldist, low, high) \ - do { \ - float nzc = (cz*cz + 1) / (cz dir drt) - cz, \ - pz = (d##c)/(nzc*e.c - e.z); \ - if(pz > 0) \ - { \ - float c = (focaldist)*nzc, \ - pc = pz*nzc; \ - if(pc < e.c) low = c; \ - else if(pc > e.c) high = c; \ - } \ - } while(0) - if(dx > 0) - { - float cz = e.x/e.z, drt = sqrtf(dx)/size; - CHECKPLANE(x, -, focaldist/aspect, sx1, sx2); - CHECKPLANE(x, +, focaldist/aspect, sx1, sx2); - } - if(dy > 0) - { - float cz = e.y/e.z, drt = sqrtf(dy)/size; - CHECKPLANE(y, -, focaldist, sy1, sy2); - CHECKPLANE(y, +, focaldist, sy1, sy2); - } + vec worldpos(center), e; + if(reflecting) worldpos.z = 2*reflectz - worldpos.z; + cammatrix.transform(worldpos, e); + if(e.z > 2*size) { sx1 = sy1 = 1; sx2 = sy2 = -1; return; } + float zzrr = e.z*e.z - size*size, + dx = e.x*e.x + zzrr, dy = e.y*e.y + zzrr, + focaldist = 1.0f/tan(fovy*0.5f*RAD); + sx1 = sy1 = -1; + sx2 = sy2 = 1; + #define CHECKPLANE(c, dir, focaldist, low, high) \ + do { \ + float nzc = (cz*cz + 1) / (cz dir drt) - cz, \ + pz = (d##c)/(nzc*e.c - e.z); \ + if(pz > 0) \ + { \ + float c = (focaldist)*nzc, \ + pc = pz*nzc; \ + if(pc < e.c) low = c; \ + else if(pc > e.c) high = c; \ + } \ + } while(0) + if(dx > 0) + { + float cz = e.x/e.z, drt = sqrtf(dx)/size; + CHECKPLANE(x, -, focaldist/aspect, sx1, sx2); + CHECKPLANE(x, +, focaldist/aspect, sx1, sx2); + } + if(dy > 0) + { + float cz = e.y/e.z, drt = sqrtf(dy)/size; + CHECKPLANE(y, -, focaldist, sy1, sy2); + CHECKPLANE(y, +, focaldist, sy1, sy2); + } } static int scissoring = 0; @@ -947,191 +924,191 @@ static GLint oldscissor[4]; int pushscissor(float sx1, float sy1, float sx2, float sy2) { - scissoring = 0; + scissoring = 0; - if(sx1 <= -1 && sy1 <= -1 && sx2 >= 1 && sy2 >= 1) return 0; + if(sx1 <= -1 && sy1 <= -1 && sx2 >= 1 && sy2 >= 1) return 0; - sx1 = max(sx1, -1.0f); - sy1 = max(sy1, -1.0f); - sx2 = min(sx2, 1.0f); - sy2 = min(sy2, 1.0f); + sx1 = max(sx1, -1.0f); + sy1 = max(sy1, -1.0f); + sx2 = min(sx2, 1.0f); + sy2 = min(sy2, 1.0f); - GLint viewport[4]; - glGetIntegerv(GL_VIEWPORT, viewport); - int sx = viewport[0] + int(floor((sx1+1)*0.5f*viewport[2])), - sy = viewport[1] + int(floor((sy1+1)*0.5f*viewport[3])), - sw = viewport[0] + int(ceil((sx2+1)*0.5f*viewport[2])) - sx, - sh = viewport[1] + int(ceil((sy2+1)*0.5f*viewport[3])) - sy; - if(sw <= 0 || sh <= 0) return 0; + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + int sx = viewport[0] + int(floor((sx1+1)*0.5f*viewport[2])), + sy = viewport[1] + int(floor((sy1+1)*0.5f*viewport[3])), + sw = viewport[0] + int(ceil((sx2+1)*0.5f*viewport[2])) - sx, + sh = viewport[1] + int(ceil((sy2+1)*0.5f*viewport[3])) - sy; + if(sw <= 0 || sh <= 0) return 0; - if(glIsEnabled(GL_SCISSOR_TEST)) - { - glGetIntegerv(GL_SCISSOR_BOX, oldscissor); - sw += sx; - sh += sy; - sx = max(sx, int(oldscissor[0])); - sy = max(sy, int(oldscissor[1])); - sw = min(sw, int(oldscissor[0] + oldscissor[2])) - sx; - sh = min(sh, int(oldscissor[1] + oldscissor[3])) - sy; - if(sw <= 0 || sh <= 0) return 0; - scissoring = 2; - } - else scissoring = 1; + if(glIsEnabled(GL_SCISSOR_TEST)) + { + glGetIntegerv(GL_SCISSOR_BOX, oldscissor); + sw += sx; + sh += sy; + sx = max(sx, int(oldscissor[0])); + sy = max(sy, int(oldscissor[1])); + sw = min(sw, int(oldscissor[0] + oldscissor[2])) - sx; + sh = min(sh, int(oldscissor[1] + oldscissor[3])) - sy; + if(sw <= 0 || sh <= 0) return 0; + scissoring = 2; + } + else scissoring = 1; - glScissor(sx, sy, sw, sh); - if(scissoring<=1) glEnable(GL_SCISSOR_TEST); + glScissor(sx, sy, sw, sh); + if(scissoring<=1) glEnable(GL_SCISSOR_TEST); - return scissoring; + return scissoring; } void popscissor() { - if(scissoring>1) glScissor(oldscissor[0], oldscissor[1], oldscissor[2], oldscissor[3]); - else if(scissoring) glDisable(GL_SCISSOR_TEST); - scissoring = 0; + if(scissoring>1) glScissor(oldscissor[0], oldscissor[1], oldscissor[2], oldscissor[3]); + else if(scissoring) glDisable(GL_SCISSOR_TEST); + scissoring = 0; } static GLuint screenquadvbo = 0; static void setupscreenquad() { - if(!screenquadvbo) - { - glGenBuffers_(1, &screenquadvbo); - gle::bindvbo(screenquadvbo); - vec2 verts[4] = { vec2(1, -1), vec2(-1, -1), vec2(1, 1), vec2(-1, 1) }; - glBufferData_(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); - gle::clearvbo(); - } + if(!screenquadvbo) + { + glGenBuffers_(1, &screenquadvbo); + gle::bindvbo(screenquadvbo); + vec2 verts[4] = { vec2(1, -1), vec2(-1, -1), vec2(1, 1), vec2(-1, 1) }; + glBufferData_(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); + gle::clearvbo(); + } } static void cleanupscreenquad() { - if(screenquadvbo) { glDeleteBuffers_(1, &screenquadvbo); screenquadvbo = 0; } + if(screenquadvbo) { glDeleteBuffers_(1, &screenquadvbo); screenquadvbo = 0; } } void screenquad() { - setupscreenquad(); - gle::bindvbo(screenquadvbo); - gle::enablevertex(); - gle::vertexpointer(sizeof(vec2), (const vec2 *)0, GL_FLOAT, 2); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - gle::disablevertex(); - gle::clearvbo(); + setupscreenquad(); + gle::bindvbo(screenquadvbo); + gle::enablevertex(); + gle::vertexpointer(sizeof(vec2), (const vec2 *)0, GL_FLOAT, 2); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + gle::disablevertex(); + gle::clearvbo(); } static LocalShaderParam screentexcoord[2] = { LocalShaderParam("screentexcoord0"), LocalShaderParam("screentexcoord1") }; static inline void setscreentexcoord(int i, float w, float h, float x = 0, float y = 0) { - screentexcoord[i].setf(w*0.5f, h*0.5f, x + w*0.5f, y + fabs(h)*0.5f); + screentexcoord[i].setf(w*0.5f, h*0.5f, x + w*0.5f, y + fabs(h)*0.5f); } void screenquad(float sw, float sh) { - setscreentexcoord(0, sw, sh); - screenquad(); + setscreentexcoord(0, sw, sh); + screenquad(); } void screenquadflipped(float sw, float sh) { - setscreentexcoord(0, sw, -sh); - screenquad(); + setscreentexcoord(0, sw, -sh); + screenquad(); } void screenquad(float sw, float sh, float sw2, float sh2) { - setscreentexcoord(0, sw, sh); - setscreentexcoord(1, sw2, sh2); - screenquad(); + setscreentexcoord(0, sw, sh); + setscreentexcoord(1, sw2, sh2); + screenquad(); } void screenquadoffset(float x, float y, float w, float h) { - setscreentexcoord(0, w, h, x, y); - screenquad(); + setscreentexcoord(0, w, h, x, y); + screenquad(); } void screenquadoffset(float x, float y, float w, float h, float x2, float y2, float w2, float h2) { - setscreentexcoord(0, w, h, x, y); - setscreentexcoord(1, w2, h2, x2, y2); - screenquad(); + setscreentexcoord(0, w, h, x, y); + setscreentexcoord(1, w2, h2, x2, y2); + screenquad(); } #define HUDQUAD(x1, y1, x2, y2, sx1, sy1, sx2, sy2) { \ - gle::defvertex(2); \ - gle::deftexcoord0(); \ - gle::begin(GL_TRIANGLE_STRIP); \ - gle::attribf(x2, y1); gle::attribf(sx2, sy1); \ - gle::attribf(x1, y1); gle::attribf(sx1, sy1); \ - gle::attribf(x2, y2); gle::attribf(sx2, sy2); \ - gle::attribf(x1, y2); gle::attribf(sx1, sy2); \ - gle::end(); \ + gle::defvertex(2); \ + gle::deftexcoord0(); \ + gle::begin(GL_TRIANGLE_STRIP); \ + gle::attribf(x2, y1); gle::attribf(sx2, sy1); \ + gle::attribf(x1, y1); gle::attribf(sx1, sy1); \ + gle::attribf(x2, y2); gle::attribf(sx2, sy2); \ + gle::attribf(x1, y2); gle::attribf(sx1, sy2); \ + gle::end(); \ } void hudquad(float x, float y, float w, float h, float tx, float ty, float tw, float th) { - HUDQUAD(x, y, x+w, y+h, tx, ty, tx+tw, ty+th); + HUDQUAD(x, y, x+w, y+h, tx, ty, tx+tw, ty+th); } VARR(fog, 16, 4000, 1000024); bvec fogcolor(0x80, 0x99, 0xB3); HVARFR(fogcolour, 0, 0x8099B3, 0xFFFFFF, { - fogcolor = bvec((fogcolour>>16)&0xFF, (fogcolour>>8)&0xFF, fogcolour&0xFF); + fogcolor = bvec((fogcolour>>16)&0xFF, (fogcolour>>8)&0xFF, fogcolour&0xFF); }); static float findsurface(int fogmat, const vec &v, int &abovemat) { - fogmat &= MATF_VOLUME; - ivec o(v), co; - int csize; - do - { - cube &c = lookupcube(o, 0, co, csize); - int mat = c.material&MATF_VOLUME; - if(mat != fogmat) - { - abovemat = isliquid(mat) ? (int) c.material : (int) MAT_AIR; - return o.z; - } - o.z = co.z + csize; - } - while(o.z < worldsize); - abovemat = MAT_AIR; - return worldsize; + fogmat &= MATF_VOLUME; + ivec o(v), co; + int csize; + do + { + cube &c = lookupcube(o, 0, co, csize); + int mat = c.material&MATF_VOLUME; + if(mat != fogmat) + { + abovemat = isliquid(mat) ? (int) c.material : (int) MAT_AIR; + return o.z; + } + o.z = co.z + csize; + } + while(o.z < worldsize); + abovemat = MAT_AIR; + return worldsize; } static void blendfog(int fogmat, float blend, float logblend, float &start, float &end, vec &fogc) { - switch(fogmat&MATF_VOLUME) - { - case MAT_WATER: - { - const bvec &wcol = getwatercolor(fogmat); - int wfog = getwaterfog(fogmat); - fogc.madd(wcol.tocolor(), blend); - end += logblend*min(fog, max(wfog*4, 32)); - break; - } - - case MAT_LAVA: - { - const bvec &lcol = getlavacolor(fogmat); - int lfog = getlavafog(fogmat); - fogc.madd(lcol.tocolor(), blend); - end += logblend*min(fog, max(lfog*4, 32)); - break; - } - - default: - fogc.madd(fogcolor.tocolor(), blend); - start += logblend*(fog+64)/8; - end += logblend*fog; - break; - } + switch(fogmat&MATF_VOLUME) + { + case MAT_WATER: + { + const bvec &wcol = getwatercolor(fogmat); + int wfog = getwaterfog(fogmat); + fogc.madd(wcol.tocolor(), blend); + end += logblend*min(fog, max(wfog*4, 32)); + break; + } + + case MAT_LAVA: + { + const bvec &lcol = getlavacolor(fogmat); + int lfog = getlavafog(fogmat); + fogc.madd(lcol.tocolor(), blend); + end += logblend*min(fog, max(lfog*4, 32)); + break; + } + + default: + fogc.madd(fogcolor.tocolor(), blend); + start += logblend*(fog+64)/8; + end += logblend*fog; + break; + } } vec oldfogcolor(0, 0, 0), curfogcolor(0, 0, 0); @@ -1139,163 +1116,163 @@ float oldfogstart = 0, oldfogend = 1000000, curfogstart = 0, curfogend = 1000000 void setfogcolor(const vec &v) { - GLOBALPARAM(fogcolor, v); + GLOBALPARAM(fogcolor, v); } void zerofogcolor() { - setfogcolor(vec(0, 0, 0)); + setfogcolor(vec(0, 0, 0)); } void resetfogcolor() { - setfogcolor(curfogcolor); + setfogcolor(curfogcolor); } void pushfogcolor(const vec &v) { - oldfogcolor = curfogcolor; - curfogcolor = v; - resetfogcolor(); + oldfogcolor = curfogcolor; + curfogcolor = v; + resetfogcolor(); } void popfogcolor() { - curfogcolor = oldfogcolor; - resetfogcolor(); + curfogcolor = oldfogcolor; + resetfogcolor(); } void setfogdist(float start, float end) { - GLOBALPARAMF(fogparams, 1/(end - start), end/(end - start)); + GLOBALPARAMF(fogparams, 1/(end - start), end/(end - start)); } void clearfogdist() { - setfogdist(0, 1000000); + setfogdist(0, 1000000); } void resetfogdist() { - setfogdist(curfogstart, curfogend); + setfogdist(curfogstart, curfogend); } void pushfogdist(float start, float end) { - oldfogstart = curfogstart; - oldfogend = curfogend; - curfogstart = start; - curfogend = end; - resetfogdist(); + oldfogstart = curfogstart; + oldfogend = curfogend; + curfogstart = start; + curfogend = end; + resetfogdist(); } void popfogdist() { - curfogstart = oldfogstart; - curfogend = oldfogend; - resetfogdist(); + curfogstart = oldfogstart; + curfogend = oldfogend; + resetfogdist(); } static void resetfog() { - resetfogcolor(); - resetfogdist(); + resetfogcolor(); + resetfogdist(); - glClearColor(curfogcolor.r, curfogcolor.g, curfogcolor.b, 1.0f); + glClearColor(curfogcolor.r, curfogcolor.g, curfogcolor.b, 1.0f); } static void setfog(int fogmat, float below = 1, int abovemat = MAT_AIR) { - float logscale = 256, logblend = log(1 + (logscale - 1)*below) / log(logscale); + float logscale = 256, logblend = log(1 + (logscale - 1)*below) / log(logscale); - curfogstart = curfogend = 0; - curfogcolor = vec(0, 0, 0); - blendfog(fogmat, below, logblend, curfogstart, curfogend, curfogcolor); - if(below < 1) blendfog(abovemat, 1-below, 1-logblend, curfogstart, curfogend, curfogcolor); + curfogstart = curfogend = 0; + curfogcolor = vec(0, 0, 0); + blendfog(fogmat, below, logblend, curfogstart, curfogend, curfogcolor); + if(below < 1) blendfog(abovemat, 1-below, 1-logblend, curfogstart, curfogend, curfogcolor); - resetfog(); + resetfog(); } static void blendfogoverlay(int fogmat, float blend, vec &overlay) { - float maxc; - switch(fogmat&MATF_VOLUME) - { - case MAT_WATER: - { - const bvec &wcol = getwatercolor(fogmat); - maxc = max(wcol.r, max(wcol.g, wcol.b)); - overlay.madd(vec(wcol.r, wcol.g, wcol.b).div(min(32.0f + maxc*7.0f/8.0f, 255.0f)).max(0.4f), blend); - break; - } - - case MAT_LAVA: - { - const bvec &lcol = getlavacolor(fogmat); - maxc = max(lcol.r, max(lcol.g, lcol.b)); - overlay.madd(vec(lcol.r, lcol.g, lcol.b).div(min(32.0f + maxc*7.0f/8.0f, 255.0f)).max(0.4f), blend); - break; - } - - default: - overlay.add(blend); - break; - } + float maxc; + switch(fogmat&MATF_VOLUME) + { + case MAT_WATER: + { + const bvec &wcol = getwatercolor(fogmat); + maxc = max(wcol.r, max(wcol.g, wcol.b)); + overlay.madd(vec(wcol.r, wcol.g, wcol.b).div(min(32.0f + maxc*7.0f/8.0f, 255.0f)).max(0.4f), blend); + break; + } + + case MAT_LAVA: + { + const bvec &lcol = getlavacolor(fogmat); + maxc = max(lcol.r, max(lcol.g, lcol.b)); + overlay.madd(vec(lcol.r, lcol.g, lcol.b).div(min(32.0f + maxc*7.0f/8.0f, 255.0f)).max(0.4f), blend); + break; + } + + default: + overlay.add(blend); + break; + } } void drawfogoverlay(int fogmat, float fogblend, int abovemat) { - SETSHADER(fogoverlay); + SETSHADER(fogoverlay); - glEnable(GL_BLEND); - glBlendFunc(GL_ZERO, GL_SRC_COLOR); - vec overlay(0, 0, 0); - blendfogoverlay(fogmat, fogblend, overlay); - blendfogoverlay(abovemat, 1-fogblend, overlay); + glEnable(GL_BLEND); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); + vec overlay(0, 0, 0); + blendfogoverlay(fogmat, fogblend, overlay); + blendfogoverlay(abovemat, 1-fogblend, overlay); - gle::color(overlay); - screenquad(); + gle::color(overlay); + screenquad(); - glDisable(GL_BLEND); + glDisable(GL_BLEND); } bool renderedgame = false; void rendergame(bool mainpass) { - game::rendergame(mainpass); - if(!shadowmapping) renderedgame = true; + game::rendergame(mainpass); + if(!shadowmapping) renderedgame = true; } VARP(skyboxglare, 0, 1, 1); void drawglare() { - glaring = true; - refracting = -1; + glaring = true; + refracting = -1; - pushfogcolor(vec(0, 0, 0)); + pushfogcolor(vec(0, 0, 0)); - glClearColor(0, 0, 0, 1); - glClear((skyboxglare && !shouldclearskyboxglare() ? 0 : GL_COLOR_BUFFER_BIT) | GL_DEPTH_BUFFER_BIT); + glClearColor(0, 0, 0, 1); + glClear((skyboxglare && !shouldclearskyboxglare() ? 0 : GL_COLOR_BUFFER_BIT) | GL_DEPTH_BUFFER_BIT); - rendergeom(); + rendergeom(); - if(skyboxglare) drawskybox(farplane, false); + if(skyboxglare) drawskybox(farplane, false); - renderreflectedmapmodels(); - rendergame(); - renderavatar(); + renderreflectedmapmodels(); + rendergame(); + renderavatar(); - renderwater(); - rendermaterials(); - renderalphageom(); - renderparticles(); + renderwater(); + rendermaterials(); + renderalphageom(); + renderparticles(); - popfogcolor(); + popfogcolor(); - refracting = 0; - glaring = false; + refracting = 0; + glaring = false; } VARP(reflectmms, 0, 1, 1); @@ -1305,201 +1282,201 @@ matrix4 noreflectmatrix; void drawreflection(float z, bool refract, int fogdepth, const bvec &col) { - reflectz = z < 0 ? 1e16f : z; - reflecting = !refract; - refracting = refract ? (z < 0 || camera1->o.z >= z ? -1 : 1) : 0; - fading = waterrefract && waterfade && z>=0; - fogging = refracting<0 && z>=0; - refractfog = fogdepth; - - if(fogging) - { - pushfogdist(camera1->o.z - z, camera1->o.z - (z - max(refractfog, 1))); - pushfogcolor(col.tocolor()); - } - else - { - vec color(0, 0, 0); - float start = 0, end = 0; - blendfog(MAT_AIR, 1, 1, start, end, color); - pushfogdist(start, end); - pushfogcolor(color); - } - - if(fading) - { - float scale = fogging ? -0.25f : 0.25f, offset = 2*fabs(scale) - scale*z; - GLOBALPARAMF(waterfadeparams, scale, offset, -scale, offset + camera1->o.z*scale); - } - - if(reflecting) - { - noreflectmatrix = cammatrix; - cammatrix.reflectz(z); - - glFrontFace(GL_CCW); - } - - if(reflectclip && z>=0) - { - float zoffset = reflectclip/4.0f, zclip; - if(refracting<0) - { - zclip = z+zoffset; - if(camera1->o.z<=zclip) zclip = z; - } - else - { - zclip = z-zoffset; - if(camera1->o.z>=zclip && camera1->o.z<=z+4.0f) zclip = z; - if(reflecting) zclip = 2*z - zclip; - } - plane clipplane; - invcammatrix.transposedtransform(plane(0, 0, refracting>0 ? 1 : -1, refracting>0 ? -zclip : zclip), clipplane); - clipmatrix.clip(clipplane, projmatrix); - noclipmatrix = projmatrix; - projmatrix = clipmatrix; - } - - setcamprojmatrix(false, true); - - renderreflectedgeom(refracting<0 && z>=0 && caustics, fogging); - - if(reflecting || refracting>0 || (refracting<0 && refractsky) || z<0) - { - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - if(reflectclip && z>=0) - { - projmatrix = noclipmatrix; - setcamprojmatrix(false, true); - } - drawskybox(farplane, false); - if(reflectclip && z>=0) - { - projmatrix = clipmatrix; - setcamprojmatrix(false, true); - } - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - } - else if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - - renderdecals(); - - if(reflectmms) renderreflectedmapmodels(); - rendergame(); - - if(refracting && z>=0 && !isthirdperson() && fabs(camera1->o.z-z) <= 0.5f*(player->eyeheight + player->aboveeye)) - { - matrix4 oldprojmatrix = projmatrix, avatarproj; - avatarproj.perspective(curavatarfov, aspect, nearplane, farplane); - if(reflectclip) - { - matrix4 avatarclip; - plane clipplane; - invcammatrix.transposedtransform(plane(0, 0, refracting, reflectclipavatar/4.0f - refracting*z), clipplane); - avatarclip.clip(clipplane, avatarproj); - projmatrix = avatarclip; - } - else projmatrix = avatarproj; - setcamprojmatrix(false, true); - game::renderavatar(); - projmatrix = oldprojmatrix; - setcamprojmatrix(false, true); - } - - rendermaterials(); - renderalphageom(fogging); - renderparticles(); - - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - if(reflectclip && z>=0) projmatrix = noclipmatrix; - - if(reflecting) - { - cammatrix = noreflectmatrix; - - glFrontFace(GL_CW); - } - - popfogdist(); - popfogcolor(); - - reflectz = 1e16f; - refracting = 0; - reflecting = fading = fogging = false; - - setcamprojmatrix(false, true); + reflectz = z < 0 ? 1e16f : z; + reflecting = !refract; + refracting = refract ? (z < 0 || camera1->o.z >= z ? -1 : 1) : 0; + fading = waterrefract && waterfade && z>=0; + fogging = refracting<0 && z>=0; + refractfog = fogdepth; + + if(fogging) + { + pushfogdist(camera1->o.z - z, camera1->o.z - (z - max(refractfog, 1))); + pushfogcolor(col.tocolor()); + } + else + { + vec color(0, 0, 0); + float start = 0, end = 0; + blendfog(MAT_AIR, 1, 1, start, end, color); + pushfogdist(start, end); + pushfogcolor(color); + } + + if(fading) + { + float scale = fogging ? -0.25f : 0.25f, offset = 2*fabs(scale) - scale*z; + GLOBALPARAMF(waterfadeparams, scale, offset, -scale, offset + camera1->o.z*scale); + } + + if(reflecting) + { + noreflectmatrix = cammatrix; + cammatrix.reflectz(z); + + glFrontFace(GL_CCW); + } + + if(reflectclip && z>=0) + { + float zoffset = reflectclip/4.0f, zclip; + if(refracting<0) + { + zclip = z+zoffset; + if(camera1->o.z<=zclip) zclip = z; + } + else + { + zclip = z-zoffset; + if(camera1->o.z>=zclip && camera1->o.z<=z+4.0f) zclip = z; + if(reflecting) zclip = 2*z - zclip; + } + plane clipplane; + invcammatrix.transposedtransform(plane(0, 0, refracting>0 ? 1 : -1, refracting>0 ? -zclip : zclip), clipplane); + clipmatrix.clip(clipplane, projmatrix); + noclipmatrix = projmatrix; + projmatrix = clipmatrix; + } + + setcamprojmatrix(false, true); + + renderreflectedgeom(refracting<0 && z>=0 && caustics, fogging); + + if(reflecting || refracting>0 || (refracting<0 && refractsky) || z<0) + { + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + if(reflectclip && z>=0) + { + projmatrix = noclipmatrix; + setcamprojmatrix(false, true); + } + drawskybox(farplane, false); + if(reflectclip && z>=0) + { + projmatrix = clipmatrix; + setcamprojmatrix(false, true); + } + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + } + else if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + + renderdecals(); + + if(reflectmms) renderreflectedmapmodels(); + rendergame(); + + if(refracting && z>=0 && !isthirdperson() && fabs(camera1->o.z-z) <= 0.5f*(player->eyeheight + player->aboveeye)) + { + matrix4 oldprojmatrix = projmatrix, avatarproj; + avatarproj.perspective(curavatarfov, aspect, nearplane, farplane); + if(reflectclip) + { + matrix4 avatarclip; + plane clipplane; + invcammatrix.transposedtransform(plane(0, 0, refracting, reflectclipavatar/4.0f - refracting*z), clipplane); + avatarclip.clip(clipplane, avatarproj); + projmatrix = avatarclip; + } + else projmatrix = avatarproj; + setcamprojmatrix(false, true); + game::renderavatar(); + projmatrix = oldprojmatrix; + setcamprojmatrix(false, true); + } + + rendermaterials(); + renderalphageom(fogging); + renderparticles(); + + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + if(reflectclip && z>=0) projmatrix = noclipmatrix; + + if(reflecting) + { + cammatrix = noreflectmatrix; + + glFrontFace(GL_CW); + } + + popfogdist(); + popfogcolor(); + + reflectz = 1e16f; + refracting = 0; + reflecting = fading = fogging = false; + + setcamprojmatrix(false, true); } int drawtex = 0; void drawcubemap(int size, const vec &o, float yaw, float pitch, const cubemapside &side, bool onlysky) { - drawtex = DRAWTEX_ENVMAP; + drawtex = DRAWTEX_ENVMAP; - physent *oldcamera = camera1; - static physent cmcamera; - cmcamera = *player; - cmcamera.reset(); - cmcamera.type = ENT_CAMERA; - cmcamera.o = o; - cmcamera.yaw = yaw; - cmcamera.pitch = pitch; - cmcamera.roll = 0; - camera1 = &cmcamera; + physent *oldcamera = camera1; + static physent cmcamera; + cmcamera = *player; + cmcamera.reset(); + cmcamera.type = ENT_CAMERA; + cmcamera.o = o; + cmcamera.yaw = yaw; + cmcamera.pitch = pitch; + cmcamera.roll = 0; + camera1 = &cmcamera; - int fogmat = lookupmaterial(o)&(MATF_VOLUME|MATF_INDEX); + int fogmat = lookupmaterial(o)&(MATF_VOLUME|MATF_INDEX); - setfog(fogmat); + setfog(fogmat); - int farplane = worldsize*2; + int farplane = worldsize*2; - projmatrix.perspective(90.0f, 1.0f, nearplane, farplane); - if(!side.flipx || !side.flipy) projmatrix.scalexy(!side.flipx ? -1 : 1, !side.flipy ? -1 : 1); - if(side.swapxy) - { - swap(projmatrix.a.x, projmatrix.a.y); - swap(projmatrix.b.x, projmatrix.b.y); - swap(projmatrix.c.x, projmatrix.c.y); - swap(projmatrix.d.x, projmatrix.d.y); - } - setcamprojmatrix(); + projmatrix.perspective(90.0f, 1.0f, nearplane, farplane); + if(!side.flipx || !side.flipy) projmatrix.scalexy(!side.flipx ? -1 : 1, !side.flipy ? -1 : 1); + if(side.swapxy) + { + swap(projmatrix.a.x, projmatrix.a.y); + swap(projmatrix.b.x, projmatrix.b.y); + swap(projmatrix.c.x, projmatrix.c.y); + swap(projmatrix.d.x, projmatrix.d.y); + } + setcamprojmatrix(); - xtravertsva = xtraverts = glde = gbatches = 0; + xtravertsva = xtraverts = glde = gbatches = 0; - visiblecubes(); + visiblecubes(); - if(onlysky) drawskybox(farplane, false, true); - else - { - glClear(GL_DEPTH_BUFFER_BIT); + if(onlysky) drawskybox(farplane, false, true); + else + { + glClear(GL_DEPTH_BUFFER_BIT); - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); - if(limitsky()) drawskybox(farplane, true); + if(limitsky()) drawskybox(farplane, true); - rendergeom(); + rendergeom(); - if(!limitsky()) drawskybox(farplane, false); + if(!limitsky()) drawskybox(farplane, false); -// queryreflections(); +// queryreflections(); - rendermapmodels(); - renderalphageom(); + rendermapmodels(); + renderalphageom(); -// drawreflections(); +// drawreflections(); -// renderwater(); -// rendermaterials(); +// renderwater(); +// rendermaterials(); - glDisable(GL_DEPTH_TEST); - glDisable(GL_CULL_FACE); - } + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + } - camera1 = oldcamera; - drawtex = 0; + camera1 = oldcamera; + drawtex = 0; } VAR(modelpreviewfov, 10, 20, 100); @@ -1507,254 +1484,197 @@ VAR(modelpreviewpitch, -90, -15, 90); namespace modelpreview { - physent *oldcamera; - physent camera; + physent *oldcamera; + physent camera; - float oldaspect, oldfovy, oldfov; - int oldfarplane; - matrix4 oldprojmatrix; + float oldaspect, oldfovy, oldfov; + int oldfarplane; + matrix4 oldprojmatrix; - void start(int x, int y, int w, int h, bool background) - { - drawtex = DRAWTEX_MODELPREVIEW; + void start(int x, int y, int w, int h, bool background) + { + drawtex = DRAWTEX_MODELPREVIEW; - glViewport(x, y, w, h); - glScissor(x, y, w, h); - glEnable(GL_SCISSOR_TEST); + glViewport(x, y, w, h); + glScissor(x, y, w, h); + glEnable(GL_SCISSOR_TEST); - oldcamera = camera1; - camera = *camera1; - camera.reset(); - camera.type = ENT_CAMERA; - camera.o = vec(0, 0, 0); - camera.yaw = 0; - camera.pitch = modelpreviewpitch; - camera.roll = 0; - camera1 = &camera; + oldcamera = camera1; + camera = *camera1; + camera.reset(); + camera.type = ENT_CAMERA; + camera.o = vec(0, 0, 0); + camera.yaw = 0; + camera.pitch = modelpreviewpitch; + camera.roll = 0; + camera1 = &camera; - oldaspect = aspect; - oldfovy = fovy; - oldfov = curfov; - oldfarplane = farplane; - oldprojmatrix = projmatrix; + oldaspect = aspect; + oldfovy = fovy; + oldfov = curfov; + oldfarplane = farplane; + oldprojmatrix = projmatrix; - aspect = w/float(h); - fovy = modelpreviewfov; - curfov = 2*atan2(tan(fovy/2*RAD), 1/aspect)/RAD; - farplane = 1024; + aspect = w/float(h); + fovy = modelpreviewfov; + curfov = 2*atan2(tan(fovy/2*RAD), 1/aspect)/RAD; + farplane = 1024; - clearfogdist(); - zerofogcolor(); - glClearColor(0, 0, 0, 1); + clearfogdist(); + zerofogcolor(); + glClearColor(0, 0, 0, 1); - glClear((background ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT); + glClear((background ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT); - projmatrix.perspective(fovy, aspect, nearplane, farplane); - setcamprojmatrix(); + projmatrix.perspective(fovy, aspect, nearplane, farplane); + setcamprojmatrix(); - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); - } + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + } - void end() - { - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); + void end() + { + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); - resetfogdist(); - resetfogcolor(); - glClearColor(curfogcolor.r, curfogcolor.g, curfogcolor.b, 1); + resetfogdist(); + resetfogcolor(); + glClearColor(curfogcolor.r, curfogcolor.g, curfogcolor.b, 1); - aspect = oldaspect; - fovy = oldfovy; - curfov = oldfov; - farplane = oldfarplane; + aspect = oldaspect; + fovy = oldfovy; + curfov = oldfov; + farplane = oldfarplane; - camera1 = oldcamera; - drawtex = 0; + camera1 = oldcamera; + drawtex = 0; - glDisable(GL_SCISSOR_TEST); - glViewport(0, 0, screenw, screenh); + glDisable(GL_SCISSOR_TEST); + glViewport(0, 0, screenw, screenh); - projmatrix = oldprojmatrix; - setcamprojmatrix(); - } + projmatrix = oldprojmatrix; + setcamprojmatrix(); + } } vec calcmodelpreviewpos(const vec &radius, float &yaw) { - yaw = fmod(lastmillis/10000.0f*360.0f, 360.0f); - float dist = 1.15f*max(radius.magnitude2()/aspect, radius.magnitude())/sinf(fovy/2*RAD); - return vec(0, dist, 0).rotate_around_x(camera1->pitch*RAD); + yaw = fmod(lastmillis/10000.0f*360.0f, 360.0f); + float dist = 1.15f*max(radius.magnitude2()/aspect, radius.magnitude())/sinf(fovy/2*RAD); + return vec(0, dist, 0).rotate_around_x(camera1->pitch*RAD); } bool deferdrawtextures = false; void drawtextures() { - if(minimized) { deferdrawtextures = true; return; } - deferdrawtextures = false; - genenvmaps(); -} - -GLuint motiontex = 0; -int motionw = 0, motionh = 0, lastmotion = 0; - -void cleanupmotionblur() -{ - if(motiontex) { glDeleteTextures(1, &motiontex); motiontex = 0; } - motionw = motionh = 0; - lastmotion = 0; -} - -VARFP(motionblur, 0, 0, 1, { if(!motionblur) cleanupmotionblur(); }); -VARP(motionblurmillis, 1, 5, 1000); -FVARP(motionblurscale, 0, 0.5f, 1); - -void addmotionblur() -{ - if(!motionblur || max(screenw, screenh) > hwtexsize) return; - - if(game::ispaused()) { lastmotion = 0; return; } - - if(!motiontex || motionw != screenw || motionh != screenh) - { - if(!motiontex) glGenTextures(1, &motiontex); - motionw = screenw; - motionh = screenh; - lastmotion = 0; - createtexture(motiontex, motionw, motionh, NULL, 3, 0, GL_RGB); - } - - glBindTexture(GL_TEXTURE_2D, motiontex); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - SETSHADER(screenrect); - - gle::colorf(1, 1, 1, lastmotion ? pow(motionblurscale, max(float(lastmillis - lastmotion)/motionblurmillis, 1.0f)) : 0); - screenquad(1, 1); - - glDisable(GL_BLEND); - - if(lastmillis - lastmotion >= motionblurmillis) - { - lastmotion = lastmillis - lastmillis%motionblurmillis; - - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, screenw, screenh); - } -} - -bool dopostfx = false; - -void invalidatepostfx() -{ - dopostfx = false; + if(minimized) { deferdrawtextures = true; return; } + deferdrawtextures = false; + genenvmaps(); } int xtraverts, xtravertsva; void gl_drawframe() { - if(deferdrawtextures) drawtextures(); + if(deferdrawtextures) drawtextures(); - updatedynlights(); + updatedynlights(); - int w = screenw, h = screenh; - aspect = forceaspect ? forceaspect : w/float(h); - fovy = 2*atan2(tan(curfov/2*RAD), aspect)/RAD; + int w = screenw, h = screenh; + aspect = forceaspect ? forceaspect : w/float(h); + fovy = 2*atan2(tan(curfov/2*RAD), aspect)/RAD; - int fogmat = lookupmaterial(camera1->o)&(MATF_VOLUME|MATF_INDEX), abovemat = MAT_AIR; - float fogblend = 1.0f, causticspass = 0.0f; - if(isliquid(fogmat&MATF_VOLUME)) - { - float z = findsurface(fogmat, camera1->o, abovemat) - WATER_OFFSET; - if(camera1->o.z < z + 1) fogblend = min(z + 1 - camera1->o.z, 1.0f); - else fogmat = abovemat; - if(caustics && (fogmat&MATF_VOLUME)==MAT_WATER && camera1->o.z < z) - causticspass = min(z - camera1->o.z, 1.0f); - } - else fogmat = MAT_AIR; - setfog(fogmat, fogblend, abovemat); - if(fogmat!=MAT_AIR) - { - float blend = abovemat==MAT_AIR ? fogblend : 1.0f; - fovy += blend*sinf(lastmillis/1000.0)*2.0f; - aspect += blend*sinf(lastmillis/1000.0+M_PI)*0.1f; - } + int fogmat = lookupmaterial(camera1->o)&(MATF_VOLUME|MATF_INDEX), abovemat = MAT_AIR; + float fogblend = 1.0f, causticspass = 0.0f; + if(isliquid(fogmat&MATF_VOLUME)) + { + float z = findsurface(fogmat, camera1->o, abovemat) - WATER_OFFSET; + if(camera1->o.z < z + 1) fogblend = min(z + 1 - camera1->o.z, 1.0f); + else fogmat = abovemat; + if(caustics && (fogmat&MATF_VOLUME)==MAT_WATER && camera1->o.z < z) + causticspass = min(z - camera1->o.z, 1.0f); + } + else fogmat = MAT_AIR; + setfog(fogmat, fogblend, abovemat); + if(fogmat!=MAT_AIR) + { + float blend = abovemat==MAT_AIR ? fogblend : 1.0f; + fovy += blend*sinf(lastmillis/1000.0)*2.0f; + aspect += blend*sinf(lastmillis/1000.0+M_PI)*0.1f; + } - farplane = worldsize*2; + farplane = worldsize*2; - projmatrix.perspective(fovy, aspect, nearplane, farplane); - setcamprojmatrix(); + projmatrix.perspective(fovy, aspect, nearplane, farplane); + setcamprojmatrix(); - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); - xtravertsva = xtraverts = glde = gbatches = 0; + xtravertsva = xtraverts = glde = gbatches = 0; - visiblecubes(); + visiblecubes(); - glClear(GL_DEPTH_BUFFER_BIT|(wireframe && editmode ? GL_COLOR_BUFFER_BIT : 0)); + glClear(GL_DEPTH_BUFFER_BIT|(wireframe && editmode ? GL_COLOR_BUFFER_BIT : 0)); - if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - if(limitsky()) drawskybox(farplane, true); + if(limitsky()) drawskybox(farplane, true); - rendergeom(causticspass); + rendergeom(causticspass); - extern int outline; - if(!wireframe && editmode && outline) renderoutline(); + extern int outline; + if(!wireframe && editmode && outline) renderoutline(); - queryreflections(); + queryreflections(); - if(!limitsky()) drawskybox(farplane, false); + if(!limitsky()) drawskybox(farplane, false); - renderdecals(true); + renderdecals(true); - rendermapmodels(); - rendergame(true); - renderavatar(); + rendermapmodels(); + rendergame(true); + renderavatar(); - if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - drawglaretex(); - drawdepthfxtex(); - drawreflections(); + drawglaretex(); + drawdepthfxtex(); + drawreflections(); - if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - renderwater(); + renderwater(); - rendermaterials(); - renderalphageom(); + rendermaterials(); + renderalphageom(); - if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - renderparticles(true); + renderparticles(true); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); - addmotionblur(); - addglare(); - if(isliquid(fogmat&MATF_VOLUME)) drawfogoverlay(fogmat, fogblend, abovemat); - renderpostfx(); + addglare(); + if(isliquid(fogmat&MATF_VOLUME)) drawfogoverlay(fogmat, fogblend, abovemat); + renderpostfx(); - gl_drawhud(); + gl_drawhud(); - renderedgame = false; + renderedgame = false; } void gl_drawmainmenu() { - xtravertsva = xtraverts = glde = gbatches = 0; + xtravertsva = xtraverts = glde = gbatches = 0; - renderbackground(NULL, NULL, NULL, NULL, true, true); - renderpostfx(); + renderbackground(NULL, NULL, NULL, NULL, true, true); + renderpostfx(); - gl_drawhud(); + gl_drawhud(); } VARNP(damagecompass, usedamagecompass, 0, 1, 1); @@ -1768,99 +1688,58 @@ float damagedirs[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; void damagecompass(int n, const vec &loc) { - if(!usedamagecompass || minimized) return; - vec delta(loc); - delta.sub(camera1->o); - float yaw = 0, pitch; - if(delta.magnitude() > 4) - { - vectoyawpitch(delta, yaw, pitch); - yaw -= camera1->yaw; - } - if(yaw >= 360) yaw = fmod(yaw, 360); - else if(yaw < 0) yaw = 360 - fmod(-yaw, 360); - int dir = (int(yaw+22.5f)%360)/45; - damagedirs[dir] += max(n, damagecompassmin)/float(damagecompassmax); - if(damagedirs[dir]>1) damagedirs[dir] = 1; + if(!usedamagecompass || minimized) return; + vec delta(loc); + delta.sub(camera1->o); + float yaw = 0, pitch; + if(delta.magnitude() > 4) + { + vectoyawpitch(delta, yaw, pitch); + yaw -= camera1->yaw; + } + if(yaw >= 360) yaw = fmod(yaw, 360); + else if(yaw < 0) yaw = 360 - fmod(-yaw, 360); + int dir = (int(yaw+22.5f)%360)/45; + damagedirs[dir] += max(n, damagecompassmin)/float(damagecompassmax); + if(damagedirs[dir]>1) damagedirs[dir] = 1; } void drawdamagecompass(int w, int h) { - hudnotextureshader->set(); - - int dirs = 0; - float size = damagecompasssize/100.0f*min(h, w)/2.0f; - loopi(8) if(damagedirs[i]>0) - { - if(!dirs) - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gle::colorf(1, 0, 0, damagecompassalpha/100.0f); - gle::defvertex(); - gle::begin(GL_TRIANGLES); - } - dirs++; - - float logscale = 32, - scale = log(1 + (logscale - 1)*damagedirs[i]) / log(logscale), - offset = -size/2.0f-min(h, w)/4.0f; - matrix4x3 m; - m.identity(); - m.settranslation(w/2, h/2, 0); - m.rotate_around_z(i*45*RAD); - m.translate(0, offset, 0); - m.scale(size*scale); - - gle::attrib(m.transform(vec2(1, 1))); - gle::attrib(m.transform(vec2(-1, 1))); - gle::attrib(m.transform(vec2(0, 0))); - - // fade in log space so short blips don't disappear too quickly - scale -= float(curtime)/damagecompassfade; - damagedirs[i] = scale > 0 ? (pow(logscale, scale) - 1) / (logscale - 1) : 0; - } - if(dirs) gle::end(); -} - -int damageblendmillis = 0; - -VARFP(damagescreen, 0, 1, 1, { if(!damagescreen) damageblendmillis = 0; }); -VARP(damagescreenfactor, 1, 7, 100); -VARP(damagescreenalpha, 1, 45, 100); -VARP(damagescreenfade, 0, 125, 1000); -VARP(damagescreenmin, 1, 10, 1000); -VARP(damagescreenmax, 1, 100, 1000); - -void damageblend(int n) -{ - if(!damagescreen || minimized) return; - if(lastmillis > damageblendmillis) damageblendmillis = lastmillis; - damageblendmillis += clamp(n, damagescreenmin, damagescreenmax)*damagescreenfactor; -} - -void drawdamagescreen(int w, int h) -{ - if(lastmillis >= damageblendmillis) return; - - hudshader->set(); - - static Texture *damagetex = NULL; - if(!damagetex) damagetex = textureload("packages/hud/damage.png", 3); - - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, damagetex->id); - float fade = damagescreenalpha/100.0f; - if(damageblendmillis - lastmillis < damagescreenfade) - fade *= float(damageblendmillis - lastmillis)/damagescreenfade; - gle::colorf(fade, fade, fade, fade); - - hudquad(0, 0, w, h); -} - -void cleardamagescreen() -{ - damageblendmillis = 0; - loopi(8) damagedirs[i] = 0; + hudnotextureshader->set(); + + int dirs = 0; + float size = damagecompasssize/100.0f*min(h, w)/2.0f; + loopi(8) if(damagedirs[i]>0) + { + if(!dirs) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gle::colorf(1, 0, 0, damagecompassalpha/100.0f); + gle::defvertex(); + gle::begin(GL_TRIANGLES); + } + dirs++; + + float logscale = 32, + scale = log(1 + (logscale - 1)*damagedirs[i]) / log(logscale), + offset = -size/2.0f-min(h, w)/4.0f; + matrix4x3 m; + m.identity(); + m.settranslation(w/2, h/2, 0); + m.rotate_around_z(i*45*RAD); + m.translate(0, offset, 0); + m.scale(size*scale); + + gle::attrib(m.transform(vec2(1, 1))); + gle::attrib(m.transform(vec2(-1, 1))); + gle::attrib(m.transform(vec2(0, 0))); + + // fade in log space so short blips don't disappear too quickly + scale -= float(curtime)/damagecompassfade; + damagedirs[i] = scale > 0 ? (pow(logscale, scale) - 1) / (logscale - 1) : 0; + } + if(dirs) gle::end(); } VAR(hidestats, 0, 0, 1); @@ -1876,14 +1755,14 @@ static Texture *crosshairs[MAXCROSSHAIRS] = { NULL, NULL, NULL, NULL }; void loadcrosshair(const char *name, int i) { - if(i < 0 || i >= MAXCROSSHAIRS) return; + if(i < 0 || i >= MAXCROSSHAIRS) return; crosshairs[i] = name ? textureload(name, 3, true) : notexture; - if(crosshairs[i] == notexture) - { - name = game::defaultcrosshair(i); - if(!name) name = "data/crosshair.png"; - crosshairs[i] = textureload(name, 3, true); - } + if(crosshairs[i] == notexture) + { + name = game::defaultcrosshair(i); + if(!name) name = "data/crosshair.png"; + crosshairs[i] = textureload(name, 3, true); + } } void loadcrosshair_(const char *name, int *i) @@ -1895,68 +1774,62 @@ COMMANDN(loadcrosshair, loadcrosshair_, "si"); ICOMMAND(getcrosshair, "i", (int *i), { - const char *name = ""; - if(*i >= 0 && *i < MAXCROSSHAIRS) - { - name = crosshairs[*i] ? crosshairs[*i]->name : game::defaultcrosshair(*i); - if(!name) name = "data/crosshair.png"; - } - result(name); + const char *name = ""; + if(*i >= 0 && *i < MAXCROSSHAIRS) + { + name = crosshairs[*i] ? crosshairs[*i]->name : game::defaultcrosshair(*i); + if(!name) name = "data/crosshair.png"; + } + result(name); }); void writecrosshairs(stream *f) { - loopi(MAXCROSSHAIRS) if(crosshairs[i] && crosshairs[i]!=notexture) - f->printf("loadcrosshair %s %d\n", escapestring(crosshairs[i]->name), i); - f->printf("\n"); + loopi(MAXCROSSHAIRS) if(crosshairs[i] && crosshairs[i]!=notexture) + f->printf("loadcrosshair %s %d\n", escapestring(crosshairs[i]->name), i); + f->printf("\n"); } void drawcrosshair(int w, int h) { - bool windowhit = g3d_windowhit(true, false); - if(!windowhit && (hidehud || mainmenu)) return; //(hidehud || player->state==CS_SPECTATOR || player->state==CS_DEAD)) return; - - vec color(1, 1, 1); - float cx = 0.5f, cy = 0.5f, chsize; - Texture *crosshair; - if(windowhit) - { - static Texture *cursor = NULL; - if(!cursor) cursor = textureload("data/guicursor.png", 3, true); - crosshair = cursor; - chsize = cursorsize*w/900.0f; - g3d_cursorpos(cx, cy); - } - else - { - int index = game::selectcrosshair(color); - if(index < 0) return; - if(!crosshairfx) index = 0; - if(!crosshairfx || !crosshaircolors) color = vec(1, 1, 1); - crosshair = crosshairs[index]; - if(!crosshair) - { - loadcrosshair(NULL, index); - crosshair = crosshairs[index]; - } - chsize = crosshairsize*w/900.0f; - } - if(crosshair->type&Texture::ALPHA) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - else glBlendFunc(GL_ONE, GL_ONE); - float x = cx*w - (windowhit ? 0 : chsize/2.0f); - float y = cy*h - (windowhit ? 0 : chsize/2.0f); - glBindTexture(GL_TEXTURE_2D, crosshair->id); - - hudshader->set(); - gle::color(color); - hudquad(x, y, chsize, chsize); -} - -VARP(wallclock, 0, 0, 1); -VARP(wallclock24, 0, 0, 1); -VARP(wallclocksecs, 0, 0, 1); - -static time_t walltime = 0; + bool windowhit = g3d_windowhit(true, false); + if(!windowhit && (hidehud || mainmenu)) return; //(hidehud || player->state==CS_SPECTATOR || player->state==CS_DEAD)) return; + + vec color(1, 1, 1); + float cx = 0.5f, cy = 0.5f, chsize; + Texture *crosshair; + if(windowhit) + { + static Texture *cursor = NULL; + if(!cursor) cursor = textureload("data/guicursor.png", 3, true); + crosshair = cursor; + chsize = cursorsize*w/900.0f; + g3d_cursorpos(cx, cy); + } + else + { + int index = game::selectcrosshair(color); + if(index < 0) return; + if(!crosshairfx) index = 0; + if(!crosshairfx || !crosshaircolors) color = vec(1, 1, 1); + crosshair = crosshairs[index]; + if(!crosshair) + { + loadcrosshair(NULL, index); + crosshair = crosshairs[index]; + } + chsize = crosshairsize*w/900.0f; + } + if(crosshair->type&Texture::ALPHA) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + else glBlendFunc(GL_ONE, GL_ONE); + float x = cx*w - (windowhit ? 0 : chsize/2.0f); + float y = cy*h - (windowhit ? 0 : chsize/2.0f); + glBindTexture(GL_TEXTURE_2D, crosshair->id); + + hudshader->set(); + gle::color(color); + hudquad(x, y, chsize, chsize); +} VARP(showfps, 0, 1, 1); VARP(showfpsrange, 0, 0, 1); @@ -1967,187 +1840,165 @@ FVARP(conscale, 1e-3f, 0.33f, 1e3f); void gl_drawhud() { - g3d_render(); - - int w = screenw, h = screenh; - if(forceaspect) w = int(ceil(h*forceaspect)); - - if(editmode && !hidehud && !mainmenu) - { - glEnable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - - renderblendbrush(); - - rendereditcursor(); - - glDepthMask(GL_TRUE); - glDisable(GL_DEPTH_TEST); - } - - gettextres(w, h); - - hudmatrix.ortho(0, w, h, 0, -1, 1); - resethudmatrix(); - - gle::colorf(1, 1, 1); - - glEnable(GL_BLEND); - - if(!mainmenu) - { - drawdamagescreen(w, h); - drawdamagecompass(w, h); - } - - hudshader->set(); - - int conw = int(w/conscale), conh = int(h/conscale), abovehud = conh - FONTH, limitgui = abovehud; - if(!hidehud && !mainmenu) - { - if(!hidestats) - { - pushhudmatrix(); - hudmatrix.scale(conscale, conscale, 1); - flushhudmatrix(); - - int roffset = 0; - if(showfps) - { - static int lastfps = 0, prevfps[3] = { 0, 0, 0 }, curfps[3] = { 0, 0, 0 }; - if(totalmillis - lastfps >= statrate) - { - memcpy(prevfps, curfps, sizeof(prevfps)); - lastfps = totalmillis - (totalmillis%statrate); - } - int nextfps[3]; - getfps(nextfps[0], nextfps[1], nextfps[2]); - loopi(3) if(prevfps[i]==curfps[i]) curfps[i] = nextfps[i]; - if(showfpsrange) draw_textf("fps %d+%d-%d", conw-7*FONTH, conh-FONTH*3/2, curfps[0], curfps[1], curfps[2]); - else draw_textf("fps %d", conw-5*FONTH, conh-FONTH*3/2, curfps[0]); - roffset += FONTH; - } - - if(wallclock) - { - if(!walltime) { walltime = time(NULL); walltime -= totalmillis/1000; if(!walltime) walltime++; } - time_t walloffset = walltime + totalmillis/1000; - struct tm *localvals = localtime(&walloffset); - static string buf; - if(localvals && strftime(buf, sizeof(buf), wallclocksecs ? (wallclock24 ? "%H:%M:%S" : "%I:%M:%S%p") : (wallclock24 ? "%H:%M" : "%I:%M%p"), localvals)) - { - // hack because not all platforms (windows) support %P lowercase option - // also strip leading 0 from 12 hour time - char *dst = buf; - const char *src = &buf[!wallclock24 && buf[0]=='0' ? 1 : 0]; - while(*src) *dst++ = tolower(*src++); - *dst++ = '\0'; - draw_text(buf, conw-5*FONTH, conh-FONTH*3/2-roffset); - roffset += FONTH; - } - } - - if(editmode || showeditstats) - { - static int laststats = 0, prevstats[8] = { 0, 0, 0, 0, 0, 0, 0 }, curstats[8] = { 0, 0, 0, 0, 0, 0, 0 }; - if(totalmillis - laststats >= statrate) - { - memcpy(prevstats, curstats, sizeof(prevstats)); - laststats = totalmillis - (totalmillis%statrate); - } - int nextstats[8] = - { - vtris*100/max(wtris, 1), - vverts*100/max(wverts, 1), - xtraverts/1024, - xtravertsva/1024, - glde, - gbatches, - getnumqueries(), - rplanes - }; - loopi(8) if(prevstats[i]==curstats[i]) curstats[i] = nextstats[i]; - - abovehud -= 2*FONTH; - draw_textf("wtr:%dk(%d%%) wvt:%dk(%d%%) evt:%dk eva:%dk", FONTH/2, abovehud, wtris/1024, curstats[0], wverts/1024, curstats[1], curstats[2], curstats[3]); - draw_textf("ond:%d va:%d gl:%d(%d) oq:%d lm:%d rp:%d", FONTH/2, abovehud+FONTH, allocnodes*8, allocva, curstats[4], curstats[5], curstats[6], lightmaps.length(), curstats[7]); - limitgui = abovehud; - } - - if(editmode) - { - abovehud -= FONTH; - draw_textf("cube %s%d%s", FONTH/2, abovehud, selchildcount<0 ? "1/" : "", abs(selchildcount), showmat && selchildmat > 0 ? getmaterialdesc(selchildmat, ": ") : ""); - - if(char *editinfo = execidentstr("edithud")) - { - if(editinfo[0]) - { - int tw, th; - text_bounds(editinfo, tw, th); - th += FONTH-1; th -= th%FONTH; - abovehud -= max(th, FONTH); - draw_text(editinfo, FONTH/2, abovehud); - } - DELETEA(editinfo); - } - } - else if(char *gameinfo = execidentstr("gamehud")) - { - if(gameinfo[0]) - { - int tw, th; - text_bounds(gameinfo, tw, th); - th += FONTH-1; th -= th%FONTH; - roffset += max(th, FONTH); - draw_text(gameinfo, conw-max(5*FONTH, 2*FONTH+tw), conh-FONTH/2-roffset); - } - DELETEA(gameinfo); - } - - pophudmatrix(); - } - - if(hidestats || (!editmode && !showeditstats)) - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - game::gameplayhud(w, h); - limitgui = abovehud = min(abovehud, int(conh*game::abovegameplayhud(w, h))); - } - - rendertexturepanel(w, h); - } - - glDisable(GL_BLEND); - - g3d_limitscale((2*limitgui - conh) / float(conh)); - g3d_render2d(); - - glEnable(GL_BLEND); - - hudmatrix.ortho(0, w, h, 0, -1, 1); - resethudmatrix(); - - pushhudmatrix(); - hudmatrix.scale(conscale, conscale, 1); - flushhudmatrix(); - abovehud -= rendercommand(FONTH/2, abovehud - FONTH/2, conw-FONTH); - extern int fullconsole; - if(!hidehud || fullconsole) renderconsole(conw, conh, abovehud - FONTH/2); - pophudmatrix(); - - drawcrosshair(w, h); - - glDisable(GL_BLEND); + g3d_render(); + + int w = screenw, h = screenh; + if(forceaspect) w = int(ceil(h*forceaspect)); + + if(editmode && !hidehud && !mainmenu) + { + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + renderblendbrush(); + + rendereditcursor(); + + glDepthMask(GL_TRUE); + glDisable(GL_DEPTH_TEST); + } + + gettextres(w, h); + + hudmatrix.ortho(0, w, h, 0, -1, 1); + resethudmatrix(); + + gle::colorf(1, 1, 1); + + glEnable(GL_BLEND); + + if(!mainmenu) + { + drawdamagecompass(w, h); + } + + hudshader->set(); + + int conw = int(w/conscale), conh = int(h/conscale), abovehud = conh - FONTH, limitgui = abovehud; + if(!hidehud && !mainmenu) + { + if(!hidestats) + { + pushhudmatrix(); + hudmatrix.scale(conscale, conscale, 1); + flushhudmatrix(); + + int roffset = 0; + if(showfps) + { + static int lastfps = 0, prevfps[3] = { 0, 0, 0 }, curfps[3] = { 0, 0, 0 }; + if(totalmillis - lastfps >= statrate) + { + memcpy(prevfps, curfps, sizeof(prevfps)); + lastfps = totalmillis - (totalmillis%statrate); + } + int nextfps[3]; + getfps(nextfps[0], nextfps[1], nextfps[2]); + loopi(3) if(prevfps[i]==curfps[i]) curfps[i] = nextfps[i]; + if(showfpsrange) draw_textf("fps %d+%d-%d", conw-7*FONTH, conh-FONTH*3/2, curfps[0], curfps[1], curfps[2]); + else draw_textf("fps %d", conw-5*FONTH, conh-FONTH*3/2, curfps[0]); + roffset += FONTH; + } + + if(editmode || showeditstats) + { + static int laststats = 0, prevstats[8] = { 0, 0, 0, 0, 0, 0, 0 }, curstats[8] = { 0, 0, 0, 0, 0, 0, 0 }; + if(totalmillis - laststats >= statrate) + { + memcpy(prevstats, curstats, sizeof(prevstats)); + laststats = totalmillis - (totalmillis%statrate); + } + int nextstats[8] = + { + vtris*100/max(wtris, 1), + vverts*100/max(wverts, 1), + xtraverts/1024, + xtravertsva/1024, + glde, + gbatches, + getnumqueries(), + rplanes + }; + loopi(8) if(prevstats[i]==curstats[i]) curstats[i] = nextstats[i]; + + abovehud -= 2*FONTH; + draw_textf("wtr:%dk(%d%%) wvt:%dk(%d%%) evt:%dk eva:%dk", FONTH/2, abovehud, wtris/1024, curstats[0], wverts/1024, curstats[1], curstats[2], curstats[3]); + draw_textf("ond:%d va:%d gl:%d(%d) oq:%d lm:%d rp:%d", FONTH/2, abovehud+FONTH, allocnodes*8, allocva, curstats[4], curstats[5], curstats[6], lightmaps.length(), curstats[7]); + limitgui = abovehud; + } + + if(editmode) + { + abovehud -= FONTH; + draw_textf("cube %s%d%s", FONTH/2, abovehud, selchildcount<0 ? "1/" : "", abs(selchildcount), showmat && selchildmat > 0 ? getmaterialdesc(selchildmat, ": ") : ""); + + if(char *editinfo = execidentstr("edithud")) + { + if(editinfo[0]) + { + int tw, th; + text_bounds(editinfo, tw, th); + th += FONTH-1; th -= th%FONTH; + abovehud -= max(th, FONTH); + draw_text(editinfo, FONTH/2, abovehud); + } + DELETEA(editinfo); + } + } + else if(char *gameinfo = execidentstr("gamehud")) + { + if(gameinfo[0]) + { + int tw, th; + text_bounds(gameinfo, tw, th); + th += FONTH-1; th -= th%FONTH; + roffset += max(th, FONTH); + draw_text(gameinfo, conw-max(5*FONTH, 2*FONTH+tw), conh-FONTH/2-roffset); + } + DELETEA(gameinfo); + } + + pophudmatrix(); + } + + if(hidestats || (!editmode && !showeditstats)) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + game::gameplayhud(w, h); + limitgui = abovehud = min(abovehud, int(conh*game::abovegameplayhud(w, h))); + } + + rendertexturepanel(w, h); + } + + glDisable(GL_BLEND); + + g3d_limitscale((2*limitgui - conh) / float(conh)); + g3d_render2d(); + + glEnable(GL_BLEND); + + hudmatrix.ortho(0, w, h, 0, -1, 1); + resethudmatrix(); + + pushhudmatrix(); + hudmatrix.scale(conscale, conscale, 1); + flushhudmatrix(); + abovehud -= rendercommand(FONTH/2, abovehud - FONTH/2, conw-FONTH); + extern int fullconsole; + if(!hidehud || fullconsole) renderconsole(conw, conh, abovehud - FONTH/2); + pophudmatrix(); + + drawcrosshair(w, h); + + glDisable(GL_BLEND); } void cleanupgl() { - cleanupmotionblur(); - - cleanupscreenquad(); + cleanupscreenquad(); - gle::cleanup(); + gle::cleanup(); } diff --git a/src/engine/rendermodel.cpp b/src/engine/rendermodel.cpp index c8a7eb6..5c8a219 100644 --- a/src/engine/rendermodel.cpp +++ b/src/engine/rendermodel.cpp @@ -14,14 +14,14 @@ static model *(__cdecl *modeltypes[NUMMODELTYPES])(const char *); static int addmodeltype(int type, model *(__cdecl *loader)(const char *)) { - modeltypes[type] = loader; - return type; + modeltypes[type] = loader; + return type; } #define MODELTYPE(modeltype, modelclass) \ static model *__loadmodel__##modelclass(const char *filename) \ { \ - return new modelclass(filename); \ + return new modelclass(filename); \ } \ UNUSED static int __dummy__##modelclass = addmodeltype((modeltype), __loadmodel__##modelclass); @@ -37,281 +37,281 @@ MODELTYPE(MDL_IQM, iqm); void mdlcullface(int *cullface) { - checkmdl; - loadingmodel->setcullface(*cullface!=0); + checkmdl; + loadingmodel->setcullface(*cullface!=0); } COMMAND(mdlcullface, "i"); void mdlcollide(int *collide) { - checkmdl; - loadingmodel->collide = *collide!=0; + checkmdl; + loadingmodel->collide = *collide!=0; } COMMAND(mdlcollide, "i"); void mdlellipsecollide(int *collide) { - checkmdl; - loadingmodel->ellipsecollide = *collide!=0; + checkmdl; + loadingmodel->ellipsecollide = *collide!=0; } COMMAND(mdlellipsecollide, "i"); void mdlspec(int *percent) { - checkmdl; - float spec = 1.0f; - if(*percent>0) spec = *percent/100.0f; - else if(*percent<0) spec = 0.0f; - loadingmodel->setspec(spec); + checkmdl; + float spec = 1.0f; + if(*percent>0) spec = *percent/100.0f; + else if(*percent<0) spec = 0.0f; + loadingmodel->setspec(spec); } COMMAND(mdlspec, "i"); void mdlambient(int *percent) { - checkmdl; - float ambient = 0.3f; - if(*percent>0) ambient = *percent/100.0f; - else if(*percent<0) ambient = 0.0f; - loadingmodel->setambient(ambient); + checkmdl; + float ambient = 0.3f; + if(*percent>0) ambient = *percent/100.0f; + else if(*percent<0) ambient = 0.0f; + loadingmodel->setambient(ambient); } COMMAND(mdlambient, "i"); void mdlalphatest(float *cutoff) { - checkmdl; - loadingmodel->setalphatest(max(0.0f, min(1.0f, *cutoff))); + checkmdl; + loadingmodel->setalphatest(max(0.0f, min(1.0f, *cutoff))); } COMMAND(mdlalphatest, "f"); void mdlalphablend(int *blend) { - checkmdl; - loadingmodel->setalphablend(*blend!=0); + checkmdl; + loadingmodel->setalphablend(*blend!=0); } COMMAND(mdlalphablend, "i"); void mdlalphadepth(int *depth) { - checkmdl; - loadingmodel->alphadepth = *depth!=0; + checkmdl; + loadingmodel->alphadepth = *depth!=0; } COMMAND(mdlalphadepth, "i"); void mdldepthoffset(int *offset) { - checkmdl; - loadingmodel->depthoffset = *offset!=0; + checkmdl; + loadingmodel->depthoffset = *offset!=0; } COMMAND(mdldepthoffset, "i"); void mdlglow(int *percent, int *delta, float *pulse) { - checkmdl; - float glow = 3.0f, glowdelta = *delta/100.0f, glowpulse = *pulse > 0 ? *pulse/1000.0f : 0; - if(*percent>0) glow = *percent/100.0f; - else if(*percent<0) glow = 0.0f; - glowdelta -= glow; - loadingmodel->setglow(glow, glowdelta, glowpulse); + checkmdl; + float glow = 3.0f, glowdelta = *delta/100.0f, glowpulse = *pulse > 0 ? *pulse/1000.0f : 0; + if(*percent>0) glow = *percent/100.0f; + else if(*percent<0) glow = 0.0f; + glowdelta -= glow; + loadingmodel->setglow(glow, glowdelta, glowpulse); } COMMAND(mdlglow, "iif"); void mdlglare(float *specglare, float *glowglare) { - checkmdl; - loadingmodel->setglare(*specglare, *glowglare); + checkmdl; + loadingmodel->setglare(*specglare, *glowglare); } COMMAND(mdlglare, "ff"); void mdlenvmap(float *envmapmax, float *envmapmin, char *envmap) { - checkmdl; - loadingmodel->setenvmap(*envmapmin, *envmapmax, envmap[0] ? cubemapload(envmap) : NULL); + checkmdl; + loadingmodel->setenvmap(*envmapmin, *envmapmax, envmap[0] ? cubemapload(envmap) : NULL); } COMMAND(mdlenvmap, "ffs"); void mdlfullbright(float *fullbright) { - checkmdl; - loadingmodel->setfullbright(*fullbright); + checkmdl; + loadingmodel->setfullbright(*fullbright); } COMMAND(mdlfullbright, "f"); void mdlshader(char *shader) { - checkmdl; - loadingmodel->setshader(lookupshaderbyname(shader)); + checkmdl; + loadingmodel->setshader(lookupshaderbyname(shader)); } COMMAND(mdlshader, "s"); void mdlspin(float *yaw, float *pitch) { - checkmdl; - loadingmodel->spinyaw = *yaw; - loadingmodel->spinpitch = *pitch; + checkmdl; + loadingmodel->spinyaw = *yaw; + loadingmodel->spinpitch = *pitch; } COMMAND(mdlspin, "ff"); void mdlscale(int *percent) { - checkmdl; - float scale = 1.0f; - if(*percent>0) scale = *percent/100.0f; - loadingmodel->scale = scale; + checkmdl; + float scale = 1.0f; + if(*percent>0) scale = *percent/100.0f; + loadingmodel->scale = scale; } COMMAND(mdlscale, "i"); void mdltrans(float *x, float *y, float *z) { - checkmdl; - loadingmodel->translate = vec(*x, *y, *z); + checkmdl; + loadingmodel->translate = vec(*x, *y, *z); } COMMAND(mdltrans, "fff"); void mdlyaw(float *angle) { - checkmdl; - loadingmodel->offsetyaw = *angle; + checkmdl; + loadingmodel->offsetyaw = *angle; } COMMAND(mdlyaw, "f"); void mdlpitch(float *angle) { - checkmdl; - loadingmodel->offsetpitch = *angle; + checkmdl; + loadingmodel->offsetpitch = *angle; } COMMAND(mdlpitch, "f"); void mdlshadow(int *shadow) { - checkmdl; - loadingmodel->shadow = *shadow!=0; + checkmdl; + loadingmodel->shadow = *shadow!=0; } COMMAND(mdlshadow, "i"); void mdlbb(float *rad, float *h, float *eyeheight) { - checkmdl; - loadingmodel->collidexyradius = *rad; - loadingmodel->collideheight = *h; - loadingmodel->eyeheight = *eyeheight; + checkmdl; + loadingmodel->collidexyradius = *rad; + loadingmodel->collideheight = *h; + loadingmodel->eyeheight = *eyeheight; } COMMAND(mdlbb, "fff"); void mdlextendbb(float *x, float *y, float *z) { - checkmdl; - loadingmodel->bbextend = vec(*x, *y, *z); + checkmdl; + loadingmodel->bbextend = vec(*x, *y, *z); } COMMAND(mdlextendbb, "fff"); void mdlname() { - checkmdl; - result(loadingmodel->name); + checkmdl; + result(loadingmodel->name); } COMMAND(mdlname, ""); #define checkragdoll \ - checkmdl; \ - if(!loadingmodel->skeletal()) { conoutf(CON_ERROR, "not loading a skeletal model"); return; } \ - skelmodel *m = (skelmodel *)loadingmodel; \ - if(m->parts.empty()) return; \ - skelmodel::skelmeshgroup *meshes = (skelmodel::skelmeshgroup *)m->parts.last()->meshes; \ - if(!meshes) return; \ - skelmodel::skeleton *skel = meshes->skel; \ - if(!skel->ragdoll) skel->ragdoll = new ragdollskel; \ - ragdollskel *ragdoll = skel->ragdoll; \ - if(ragdoll->loaded) return; + checkmdl; \ + if(!loadingmodel->skeletal()) { conoutf(CON_ERROR, "not loading a skeletal model"); return; } \ + skelmodel *m = (skelmodel *)loadingmodel; \ + if(m->parts.empty()) return; \ + skelmodel::skelmeshgroup *meshes = (skelmodel::skelmeshgroup *)m->parts.last()->meshes; \ + if(!meshes) return; \ + skelmodel::skeleton *skel = meshes->skel; \ + if(!skel->ragdoll) skel->ragdoll = new ragdollskel; \ + ragdollskel *ragdoll = skel->ragdoll; \ + if(ragdoll->loaded) return; void rdvert(float *x, float *y, float *z, float *radius) { - checkragdoll; - ragdollskel::vert &v = ragdoll->verts.add(); - v.pos = vec(*x, *y, *z); - v.radius = *radius > 0 ? *radius : 1; + checkragdoll; + ragdollskel::vert &v = ragdoll->verts.add(); + v.pos = vec(*x, *y, *z); + v.radius = *radius > 0 ? *radius : 1; } COMMAND(rdvert, "ffff"); void rdeye(int *v) { - checkragdoll; - ragdoll->eye = *v; + checkragdoll; + ragdoll->eye = *v; } COMMAND(rdeye, "i"); void rdtri(int *v1, int *v2, int *v3) { - checkragdoll; - ragdollskel::tri &t = ragdoll->tris.add(); - t.vert[0] = *v1; - t.vert[1] = *v2; - t.vert[2] = *v3; + checkragdoll; + ragdollskel::tri &t = ragdoll->tris.add(); + t.vert[0] = *v1; + t.vert[1] = *v2; + t.vert[2] = *v3; } COMMAND(rdtri, "iii"); void rdjoint(int *n, int *t, int *v1, int *v2, int *v3) { - checkragdoll; - if(*n < 0 || *n >= skel->numbones) return; - ragdollskel::joint &j = ragdoll->joints.add(); - j.bone = *n; - j.tri = *t; - j.vert[0] = *v1; - j.vert[1] = *v2; - j.vert[2] = *v3; + checkragdoll; + if(*n < 0 || *n >= skel->numbones) return; + ragdollskel::joint &j = ragdoll->joints.add(); + j.bone = *n; + j.tri = *t; + j.vert[0] = *v1; + j.vert[1] = *v2; + j.vert[2] = *v3; } COMMAND(rdjoint, "iibbb"); void rdlimitdist(int *v1, int *v2, float *mindist, float *maxdist) { - checkragdoll; - ragdollskel::distlimit &d = ragdoll->distlimits.add(); - d.vert[0] = *v1; - d.vert[1] = *v2; - d.mindist = *mindist; - d.maxdist = max(*maxdist, *mindist); + checkragdoll; + ragdollskel::distlimit &d = ragdoll->distlimits.add(); + d.vert[0] = *v1; + d.vert[1] = *v2; + d.mindist = *mindist; + d.maxdist = max(*maxdist, *mindist); } COMMAND(rdlimitdist, "iiff"); void rdlimitrot(int *t1, int *t2, float *maxangle, float *qx, float *qy, float *qz, float *qw) { - checkragdoll; - ragdollskel::rotlimit &r = ragdoll->rotlimits.add(); - r.tri[0] = *t1; - r.tri[1] = *t2; - r.maxangle = *maxangle * RAD; - r.middle = matrix3(quat(*qx, *qy, *qz, *qw)); + checkragdoll; + ragdollskel::rotlimit &r = ragdoll->rotlimits.add(); + r.tri[0] = *t1; + r.tri[1] = *t2; + r.maxangle = *maxangle * RAD; + r.middle = matrix3(quat(*qx, *qy, *qz, *qw)); } COMMAND(rdlimitrot, "iifffff"); void rdanimjoints(int *on) { - checkragdoll; - ragdoll->animjoints = *on!=0; + checkragdoll; + ragdoll->animjoints = *on!=0; } COMMAND(rdanimjoints, "i"); @@ -321,20 +321,20 @@ vector mapmodels; void mmodel(char *name) { - mapmodelinfo &mmi = mapmodels.add(); - copystring(mmi.name, name); - mmi.m = NULL; + mapmodelinfo &mmi = mapmodels.add(); + copystring(mmi.name, name); + mmi.m = NULL; } void mapmodelcompat(int *rad, int *h, int *tex, char *name, char *shadow) { - mmodel(name); + mmodel(name); } void mapmodelreset(int *n) { - if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; - mapmodels.shrink(clamp(*n, 0, mapmodels.length())); + if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; + mapmodels.shrink(clamp(*n, 0, mapmodels.length())); } mapmodelinfo *getmminfo(int i) { return mapmodels.inrange(i) ? &mapmodels[i] : 0; } @@ -355,185 +355,185 @@ vector preloadmodels; void preloadmodel(const char *name) { - if(!name || !name[0] || models.access(name)) return; - preloadmodels.add(newstring(name)); + if(!name || !name[0] || models.access(name)) return; + preloadmodels.add(newstring(name)); } void flushpreloadedmodels(bool msg) { - loopv(preloadmodels) - { - loadprogress = float(i+1)/preloadmodels.length(); - model *m = loadmodel(preloadmodels[i], -1, msg); - if(!m) { if(msg) conoutf(CON_WARN, "could not load model: %s", preloadmodels[i]); } - else - { - m->preloadmeshes(); - } - } - preloadmodels.deletearrays(); - loadprogress = 0; + loopv(preloadmodels) + { + loadprogress = float(i+1)/preloadmodels.length(); + model *m = loadmodel(preloadmodels[i], -1, msg); + if(!m) { if(msg) conoutf(CON_WARN, "could not load model: %s", preloadmodels[i]); } + else + { + m->preloadmeshes(); + } + } + preloadmodels.deletearrays(); + loadprogress = 0; } void preloadusedmapmodels(bool msg, bool bih) { - vector &ents = entities::getents(); - vector mapmodels; - loopv(ents) - { - extentity &e = *ents[i]; - if(e.type==ET_MAPMODEL && e.attr2 >= 0 && mapmodels.find(e.attr2) < 0) mapmodels.add(e.attr2); - } - - loopv(mapmodels) - { - loadprogress = float(i+1)/mapmodels.length(); - int mmindex = mapmodels[i]; - mapmodelinfo *mmi = getmminfo(mmindex); - if(!mmi) { if(msg) conoutf(CON_WARN, "could not find map model: %d", mmindex); } - else if(mmi->name[0] && !loadmodel(NULL, mmindex, msg)) { if(msg) conoutf(CON_WARN, "could not load model: %s", mmi->name); } - else if(mmi->m) - { - if(bih) mmi->m->preloadBIH(); - mmi->m->preloadmeshes(); - } - } - loadprogress = 0; + vector &ents = entities::getents(); + vector mapmodels; + loopv(ents) + { + extentity &e = *ents[i]; + if(e.type==ET_MAPMODEL && e.attr2 >= 0 && mapmodels.find(e.attr2) < 0) mapmodels.add(e.attr2); + } + + loopv(mapmodels) + { + loadprogress = float(i+1)/mapmodels.length(); + int mmindex = mapmodels[i]; + mapmodelinfo *mmi = getmminfo(mmindex); + if(!mmi) { if(msg) conoutf(CON_WARN, "could not find map model: %d", mmindex); } + else if(mmi->name[0] && !loadmodel(NULL, mmindex, msg)) { if(msg) conoutf(CON_WARN, "could not load model: %s", mmi->name); } + else if(mmi->m) + { + if(bih) mmi->m->preloadBIH(); + mmi->m->preloadmeshes(); + } + } + loadprogress = 0; } bool modelloaded(const char *name) { - return models.find(name, NULL) != NULL; + return models.find(name, NULL) != NULL; } model *loadmodel(const char *name, int i, bool msg) { - if(!name) - { - if(!mapmodels.inrange(i)) return NULL; - mapmodelinfo &mmi = mapmodels[i]; - if(mmi.m) return mmi.m; - name = mmi.name; - } - model **mm = models.access(name); - model *m; - if(mm) m = *mm; - else - { - if(!name[0] || loadingmodel || lightmapping > 1) return NULL; - if(msg) - { - defformatstring(filename, "packages/models/%s", name); - renderprogress(loadprogress, filename); - } - loopi(NUMMODELTYPES) - { - m = modeltypes[i](name); - if(!m) continue; - loadingmodel = m; - if(m->load()) break; - DELETEP(m); - } - loadingmodel = NULL; - if(!m) return NULL; - models.access(m->name, m); - m->preloadshaders(); - } - if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m; - return m; + if(!name) + { + if(!mapmodels.inrange(i)) return NULL; + mapmodelinfo &mmi = mapmodels[i]; + if(mmi.m) return mmi.m; + name = mmi.name; + } + model **mm = models.access(name); + model *m; + if(mm) m = *mm; + else + { + if(!name[0] || loadingmodel || lightmapping > 1) return NULL; + if(msg) + { + defformatstring(filename, "packages/models/%s", name); + renderprogress(loadprogress, filename); + } + loopi(NUMMODELTYPES) + { + m = modeltypes[i](name); + if(!m) continue; + loadingmodel = m; + if(m->load()) break; + DELETEP(m); + } + loadingmodel = NULL; + if(!m) return NULL; + models.access(m->name, m); + m->preloadshaders(); + } + if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m; + return m; } void preloadmodelshaders(bool force) { - if(initing) return; - enumerate(models, model *, m, m->preloadshaders(force)); + if(initing) return; + enumerate(models, model *, m, m->preloadshaders(force)); } void clear_mdls() { - enumerate(models, model *, m, delete m); + enumerate(models, model *, m, delete m); } void cleanupmodels() { - enumerate(models, model *, m, m->cleanup()); + enumerate(models, model *, m, m->cleanup()); } void clearmodel(char *name) { - model **m = models.access(name); - if(!m) { conoutf(CON_WARN, "model %s is not loaded", name); return; } - loopv(mapmodels) if(mapmodels[i].m==*m) mapmodels[i].m = NULL; - models.remove(name); - (*m)->cleanup(); - delete *m; - conoutf("cleared model %s", name); + model **m = models.access(name); + if(!m) { conoutf(CON_WARN, "model %s is not loaded", name); return; } + loopv(mapmodels) if(mapmodels[i].m==*m) mapmodels[i].m = NULL; + models.remove(name); + (*m)->cleanup(); + delete *m; + conoutf("cleared model %s", name); } COMMAND(clearmodel, "s"); bool modeloccluded(const vec ¢er, float radius) { - ivec bbmin(vec(center).sub(radius)), bbmax(ivec(center).add(radius+1)); - return bboccluded(bbmin, bbmax); + ivec bbmin(vec(center).sub(radius)), bbmax(ivec(center).add(radius+1)); + return bboccluded(bbmin, bbmax); } VAR(showboundingbox, 0, 0, 2); void render2dbox(vec &o, float x, float y, float z) { - gle::begin(GL_LINE_LOOP); - gle::attribf(o.x, o.y, o.z); - gle::attribf(o.x, o.y, o.z+z); - gle::attribf(o.x+x, o.y+y, o.z+z); - gle::attribf(o.x+x, o.y+y, o.z); - xtraverts += gle::end(); + gle::begin(GL_LINE_LOOP); + gle::attribf(o.x, o.y, o.z); + gle::attribf(o.x, o.y, o.z+z); + gle::attribf(o.x+x, o.y+y, o.z+z); + gle::attribf(o.x+x, o.y+y, o.z); + xtraverts += gle::end(); } void render3dbox(vec &o, float tofloor, float toceil, float xradius, float yradius) { - if(yradius<=0) yradius = xradius; - vec c = o; - c.sub(vec(xradius, yradius, tofloor)); - float xsz = xradius*2, ysz = yradius*2; - float h = tofloor+toceil; - gle::colorf(1, 1, 1); - gle::defvertex(); - render2dbox(c, xsz, 0, h); - render2dbox(c, 0, ysz, h); - c.add(vec(xsz, ysz, 0)); - render2dbox(c, -xsz, 0, h); - render2dbox(c, 0, -ysz, h); + if(yradius<=0) yradius = xradius; + vec c = o; + c.sub(vec(xradius, yradius, tofloor)); + float xsz = xradius*2, ysz = yradius*2; + float h = tofloor+toceil; + gle::colorf(1, 1, 1); + gle::defvertex(); + render2dbox(c, xsz, 0, h); + render2dbox(c, 0, ysz, h); + c.add(vec(xsz, ysz, 0)); + render2dbox(c, -xsz, 0, h); + render2dbox(c, 0, -ysz, h); } void renderellipse(vec &o, float xradius, float yradius, float yaw) { - gle::colorf(0.5f, 0.5f, 0.5f); - gle::defvertex(); - gle::begin(GL_LINE_LOOP); - loopi(15) - { - const vec2 &sc = sincos360[i*(360/15)]; - gle::attrib(vec(xradius*sc.x, yradius*sc.y, 0).rotate_around_z((yaw+90)*RAD).add(o)); - } - xtraverts += gle::end(); + gle::colorf(0.5f, 0.5f, 0.5f); + gle::defvertex(); + gle::begin(GL_LINE_LOOP); + loopi(15) + { + const vec2 &sc = sincos360[i*(360/15)]; + gle::attrib(vec(xradius*sc.x, yradius*sc.y, 0).rotate_around_z((yaw+90)*RAD).add(o)); + } + xtraverts += gle::end(); } struct batchedmodel { - vec pos, color, dir; - int anim; - float yaw, pitch, transparent; - int basetime, basetime2, flags; - dynent *d; - int attached; - occludequery *query; + vec pos, color, dir; + int anim; + float yaw, pitch, transparent; + int basetime, basetime2, flags; + dynent *d; + int attached; + occludequery *query; }; struct modelbatch { - model *m; - int flags; - vector batched; + model *m; + int flags; + vector batched; }; static vector batches; static vector modelattached; @@ -542,487 +542,487 @@ static occludequery *modelquery = NULL; void startmodelbatches() { - numbatches = 0; - modelattached.setsize(0); + numbatches = 0; + modelattached.setsize(0); } modelbatch &addbatchedmodel(model *m) { - modelbatch *b = NULL; - if(m->batch>=0 && m->batchbatch]->m==m) b = batches[m->batch]; - else - { - if(numbatchesbatched.setsize(0); - } - else b = batches.add(new modelbatch); - b->m = m; - b->flags = 0; - m->batch = numbatches++; - } - return *b; + modelbatch *b = NULL; + if(m->batch>=0 && m->batchbatch]->m==m) b = batches[m->batch]; + else + { + if(numbatchesbatched.setsize(0); + } + else b = batches.add(new modelbatch); + b->m = m; + b->flags = 0; + m->batch = numbatches++; + } + return *b; } void renderbatchedmodel(model *m, batchedmodel &b) { - modelattach *a = NULL; - if(b.attached>=0) a = &modelattached[b.attached]; + modelattach *a = NULL; + if(b.attached>=0) a = &modelattached[b.attached]; - int anim = b.anim; - if(shadowmapping) - { - anim |= ANIM_NOSKIN; - GLOBALPARAMF(shadowintensity, b.transparent); - } - else - { - if(b.flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; - if(b.flags&MDL_GHOST) anim |= ANIM_GHOST; - } + int anim = b.anim; + if(shadowmapping) + { + anim |= ANIM_NOSKIN; + GLOBALPARAMF(shadowintensity, b.transparent); + } + else + { + if(b.flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; + if(b.flags&MDL_GHOST) anim |= ANIM_GHOST; + } - m->render(anim, b.basetime, b.basetime2, b.pos, b.yaw, b.pitch, b.d, a, b.color, b.dir, b.transparent); + m->render(anim, b.basetime, b.basetime2, b.pos, b.yaw, b.pitch, b.d, a, b.color, b.dir, b.transparent); } struct transparentmodel { - model *m; - batchedmodel *batched; - float dist; + model *m; + batchedmodel *batched; + float dist; }; static inline bool sorttransparentmodels(const transparentmodel &x, const transparentmodel &y) { - return x.dist < y.dist; + return x.dist < y.dist; } void endmodelbatches() { - vector transparent; - loopi(numbatches) - { - modelbatch &b = *batches[i]; - if(b.batched.empty()) continue; - - bool rendered = false; - occludequery *query = NULL; - if(b.flags&MDL_GHOST) - { - loopvj(b.batched) - { - batchedmodel &bm = b.batched[j]; - if((bm.flags&(MDL_CULL_VFC|MDL_GHOST))!=MDL_GHOST || bm.query) continue; - if(!rendered) { b.m->startrender(); rendered = true; } - renderbatchedmodel(b.m, bm); - } - if(rendered) - { - b.m->endrender(); - rendered = false; - } - } - loopvj(b.batched) - { - batchedmodel &bm = b.batched[j]; - if(bm.flags&(MDL_CULL_VFC|MDL_GHOST)) continue; - if(bm.query!=query) - { - if(query) endquery(query); - query = bm.query; - if(query) startquery(query); - } - if(bm.transparent < 1 && (!query || query->owner==bm.d) && !shadowmapping) - { - transparentmodel &tm = transparent.add(); - tm.m = b.m; - tm.batched = &bm; - tm.dist = camera1->o.dist(bm.d && bm.d->ragdoll ? bm.d->ragdoll->center : bm.pos); - continue; - } - if(!rendered) { b.m->startrender(); rendered = true; } - renderbatchedmodel(b.m, bm); - } - if(query) endquery(query); - if(rendered) b.m->endrender(); - } - if(transparent.length()) - { - transparent.sort(sorttransparentmodels); - model *lastmodel = NULL; - occludequery *query = NULL; - loopv(transparent) - { - transparentmodel &tm = transparent[i]; - if(lastmodel!=tm.m) - { - if(lastmodel) lastmodel->endrender(); - (lastmodel = tm.m)->startrender(); - } - if(query!=tm.batched->query) - { - if(query) endquery(query); - query = tm.batched->query; - if(query) startquery(query); - } - renderbatchedmodel(tm.m, *tm.batched); - } - if(query) endquery(query); - if(lastmodel) lastmodel->endrender(); - } - numbatches = -1; + vector transparent; + loopi(numbatches) + { + modelbatch &b = *batches[i]; + if(b.batched.empty()) continue; + + bool rendered = false; + occludequery *query = NULL; + if(b.flags&MDL_GHOST) + { + loopvj(b.batched) + { + batchedmodel &bm = b.batched[j]; + if((bm.flags&(MDL_CULL_VFC|MDL_GHOST))!=MDL_GHOST || bm.query) continue; + if(!rendered) { b.m->startrender(); rendered = true; } + renderbatchedmodel(b.m, bm); + } + if(rendered) + { + b.m->endrender(); + rendered = false; + } + } + loopvj(b.batched) + { + batchedmodel &bm = b.batched[j]; + if(bm.flags&(MDL_CULL_VFC|MDL_GHOST)) continue; + if(bm.query!=query) + { + if(query) endquery(query); + query = bm.query; + if(query) startquery(query); + } + if(bm.transparent < 1 && (!query || query->owner==bm.d) && !shadowmapping) + { + transparentmodel &tm = transparent.add(); + tm.m = b.m; + tm.batched = &bm; + tm.dist = camera1->o.dist(bm.d && bm.d->ragdoll ? bm.d->ragdoll->center : bm.pos); + continue; + } + if(!rendered) { b.m->startrender(); rendered = true; } + renderbatchedmodel(b.m, bm); + } + if(query) endquery(query); + if(rendered) b.m->endrender(); + } + if(transparent.length()) + { + transparent.sort(sorttransparentmodels); + model *lastmodel = NULL; + occludequery *query = NULL; + loopv(transparent) + { + transparentmodel &tm = transparent[i]; + if(lastmodel!=tm.m) + { + if(lastmodel) lastmodel->endrender(); + (lastmodel = tm.m)->startrender(); + } + if(query!=tm.batched->query) + { + if(query) endquery(query); + query = tm.batched->query; + if(query) startquery(query); + } + renderbatchedmodel(tm.m, *tm.batched); + } + if(query) endquery(query); + if(lastmodel) lastmodel->endrender(); + } + numbatches = -1; } void startmodelquery(occludequery *query) { - modelquery = query; + modelquery = query; } void endmodelquery() { - int querybatches = 0; - loopi(numbatches) - { - modelbatch &b = *batches[i]; - if(b.batched.empty() || b.batched.last().query!=modelquery) continue; - querybatches++; - } - if(querybatches<=1) - { - if(!querybatches) modelquery->fragments = 0; - modelquery = NULL; - return; - } - int minattached = modelattached.length(); - startquery(modelquery); - loopi(numbatches) - { - modelbatch &b = *batches[i]; - if(b.batched.empty() || b.batched.last().query!=modelquery) continue; - b.m->startrender(); - do - { - batchedmodel &bm = b.batched.pop(); - if(bm.attached>=0) minattached = min(minattached, bm.attached); - renderbatchedmodel(b.m, bm); - } - while(b.batched.length() && b.batched.last().query==modelquery); - b.m->endrender(); - } - endquery(modelquery); - modelquery = NULL; - modelattached.setsize(minattached); + int querybatches = 0; + loopi(numbatches) + { + modelbatch &b = *batches[i]; + if(b.batched.empty() || b.batched.last().query!=modelquery) continue; + querybatches++; + } + if(querybatches<=1) + { + if(!querybatches) modelquery->fragments = 0; + modelquery = NULL; + return; + } + int minattached = modelattached.length(); + startquery(modelquery); + loopi(numbatches) + { + modelbatch &b = *batches[i]; + if(b.batched.empty() || b.batched.last().query!=modelquery) continue; + b.m->startrender(); + do + { + batchedmodel &bm = b.batched.pop(); + if(bm.attached>=0) minattached = min(minattached, bm.attached); + renderbatchedmodel(b.m, bm); + } + while(b.batched.length() && b.batched.last().query==modelquery); + b.m->endrender(); + } + endquery(modelquery); + modelquery = NULL; + modelattached.setsize(minattached); } VAR(maxmodelradiusdistance, 10, 200, 1000); static inline void enablecullmodelquery() { - startbb(); + startbb(); } static inline void rendercullmodelquery(model *m, dynent *d, const vec ¢er, float radius) { - if(fabs(camera1->o.x-center.x) < radius+1 && - fabs(camera1->o.y-center.y) < radius+1 && - fabs(camera1->o.z-center.z) < radius+1) - { - d->query = NULL; - return; - } - d->query = newquery(d); - if(!d->query) return; - startquery(d->query); - int br = int(radius*2)+1; - drawbb(ivec(int(center.x-radius), int(center.y-radius), int(center.z-radius)), ivec(br, br, br)); - endquery(d->query); + if(fabs(camera1->o.x-center.x) < radius+1 && + fabs(camera1->o.y-center.y) < radius+1 && + fabs(camera1->o.z-center.z) < radius+1) + { + d->query = NULL; + return; + } + d->query = newquery(d); + if(!d->query) return; + startquery(d->query); + int br = int(radius*2)+1; + drawbb(ivec(int(center.x-radius), int(center.y-radius), int(center.z-radius)), ivec(br, br, br)); + endquery(d->query); } static inline void disablecullmodelquery() { - endbb(); + endbb(); } static inline int cullmodel(model *m, const vec ¢er, float radius, int flags, dynent *d = NULL, bool shadow = false) { - if(flags&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return MDL_CULL_DIST; - if(flags&MDL_CULL_VFC) - { - if(reflecting || refracting) - { - if(reflecting || refracting>0) - { - if(center.z+radius<=reflectz) return MDL_CULL_VFC; - } - else - { - if(fogging && center.z+radius=reflectz) return MDL_CULL_VFC; - } - if(center.dist(camera1->o)-radius>reflectdist) return MDL_CULL_VFC; - } - if(isfoggedsphere(radius, center)) return MDL_CULL_VFC; - if(shadowmapping && !isshadowmapcaster(center, radius)) return MDL_CULL_VFC; - } - if(shadowmapping) - { - if(d) - { - if(flags&MDL_CULL_OCCLUDED && d->occluded>=OCCLUDE_PARENT) return MDL_CULL_OCCLUDED; - if(flags&MDL_CULL_QUERY && d->occluded+1>=OCCLUDE_BB && d->query && d->query->owner==d && checkquery(d->query)) return MDL_CULL_QUERY; - } - if(!addshadowmapcaster(center, radius, radius)) return MDL_CULL_VFC; - } - else if(flags&MDL_CULL_OCCLUDED && modeloccluded(center, radius)) - { - if(!reflecting && !refracting && d) d->occluded = OCCLUDE_PARENT; - return MDL_CULL_OCCLUDED; - } - else if(flags&MDL_CULL_QUERY && d->query && d->query->owner==d && checkquery(d->query)) - { - if(!reflecting && !refracting && d->occludedoccluded++; - return MDL_CULL_QUERY; - } - return 0; + if(flags&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return MDL_CULL_DIST; + if(flags&MDL_CULL_VFC) + { + if(reflecting || refracting) + { + if(reflecting || refracting>0) + { + if(center.z+radius<=reflectz) return MDL_CULL_VFC; + } + else + { + if(fogging && center.z+radius=reflectz) return MDL_CULL_VFC; + } + if(center.dist(camera1->o)-radius>reflectdist) return MDL_CULL_VFC; + } + if(isfoggedsphere(radius, center)) return MDL_CULL_VFC; + if(shadowmapping && !isshadowmapcaster(center, radius)) return MDL_CULL_VFC; + } + if(shadowmapping) + { + if(d) + { + if(flags&MDL_CULL_OCCLUDED && d->occluded>=OCCLUDE_PARENT) return MDL_CULL_OCCLUDED; + if(flags&MDL_CULL_QUERY && d->occluded+1>=OCCLUDE_BB && d->query && d->query->owner==d && checkquery(d->query)) return MDL_CULL_QUERY; + } + if(!addshadowmapcaster(center, radius, radius)) return MDL_CULL_VFC; + } + else if(flags&MDL_CULL_OCCLUDED && modeloccluded(center, radius)) + { + if(!reflecting && !refracting && d) d->occluded = OCCLUDE_PARENT; + return MDL_CULL_OCCLUDED; + } + else if(flags&MDL_CULL_QUERY && d->query && d->query->owner==d && checkquery(d->query)) + { + if(!reflecting && !refracting && d->occludedoccluded++; + return MDL_CULL_QUERY; + } + return 0; } void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, float yaw, float pitch, int flags, dynent *d, modelattach *a, int basetime, int basetime2, float trans) { - if(shadowmapping && !(flags&(MDL_SHADOW|MDL_DYNSHADOW))) return; - model *m = loadmodel(mdl); - if(!m) return; - vec center(0, 0, 0), bbradius(0, 0, 0); - float radius = 0; - bool shadow = !shadowmap && !glaring && (flags&(MDL_SHADOW|MDL_DYNSHADOW)); - - if(flags&(MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED|MDL_CULL_QUERY|MDL_SHADOW|MDL_DYNSHADOW)) - { - if(flags&MDL_CULL_QUERY) - { - if(!oqfrags || !oqdynent || !d) flags &= ~MDL_CULL_QUERY; - } - - m->boundbox(center, bbradius); - radius = bbradius.magnitude(); - if(d && d->ragdoll) - { - radius = max(radius, d->ragdoll->radius); - center = d->ragdoll->center; - } - else - { - center.rotate_around_z(yaw*RAD); - center.add(o); - } - - int culled = cullmodel(m, center, radius, flags, d, shadow); - if(culled) - { - if(culled&(MDL_CULL_OCCLUDED|MDL_CULL_QUERY) && flags&MDL_CULL_QUERY && !reflecting && !refracting) - { - enablecullmodelquery(); - rendercullmodelquery(m, d, center, radius); - disablecullmodelquery(); - } - return; - } - - if(reflecting || refracting || shadowmapping) flags &= ~MDL_CULL_QUERY; - } - - if(flags&MDL_NORENDER) anim |= ANIM_NORENDER; - else if(showboundingbox && !shadowmapping && !reflecting && !refracting && editmode) - { - notextureshader->set(); - if(d && showboundingbox==1) - { - render3dbox(d->o, d->eyeheight, d->aboveeye, d->radius); - renderellipse(d->o, d->xradius, d->yradius, d->yaw); - } - else - { - vec center, radius; - if(showboundingbox==1) m->collisionbox(center, radius); - else m->boundbox(center, radius); - rotatebb(center, radius, int(yaw)); - center.add(o); - render3dbox(center, radius.z, radius.z, radius.x, radius.y); - } - } - - vec lightcolor(1, 1, 1), lightdir(0, 0, 1); - if(!shadowmapping) - { - vec pos = o; - if(d) - { - if(!reflecting && !refracting) d->occluded = OCCLUDE_NOTHING; - if(!light) light = &d->light; - if(flags&MDL_LIGHT && light->millis!=lastmillis) - { - if(d->ragdoll) - { - pos = d->ragdoll->center; - pos.z += radius/2; - } - else if(d->type < ENT_CAMERA) pos.z += 0.75f*(d->eyeheight + d->aboveeye); - lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); - dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); - game::lighteffects(d, light->color, light->dir); - light->millis = lastmillis; - } - } - else if(flags&MDL_LIGHT) - { - if(!light) - { - lightreaching(pos, lightcolor, lightdir, (flags&MDL_LIGHT_FAST)!=0); - dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); - } - else if(light->millis!=lastmillis) - { - lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); - dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); - light->millis = lastmillis; - } - } - if(light) { lightcolor = light->color; lightdir = light->dir; } - if(flags&MDL_DYNLIGHT) dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); - } - - if(a) for(int i = 0; a[i].tag; i++) - { - if(a[i].name) a[i].m = loadmodel(a[i].name); - //if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL; - } - - if(numbatches>=0) - { - modelbatch &mb = addbatchedmodel(m); - batchedmodel &b = mb.batched.add(); - b.query = modelquery; - b.pos = o; - b.color = lightcolor; - b.dir = lightdir; - b.anim = anim; - b.yaw = yaw; - b.pitch = pitch; - b.basetime = basetime; - b.basetime2 = basetime2; - b.transparent = trans; - b.flags = flags & ~(MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); - if(!shadow || reflecting || refracting>0) - { - b.flags &= ~(MDL_SHADOW|MDL_DYNSHADOW); - if((flags&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) b.flags |= MDL_CULL_VFC; - } - mb.flags |= b.flags; - b.d = d; - b.attached = a ? modelattached.length() : -1; - if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].tag) break; } - if(flags&MDL_CULL_QUERY) d->query = b.query = newquery(d); - return; - } - - m->startrender(); - - if(shadowmapping) - { - anim |= ANIM_NOSKIN; - GLOBALPARAMF(shadowintensity, trans); - } - else - { - if(flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; - if(flags&MDL_GHOST) anim |= ANIM_GHOST; - } - - if(flags&MDL_CULL_QUERY) - { - d->query = newquery(d); - if(d->query) startquery(d->query); - } - - m->render(anim, basetime, basetime2, o, yaw, pitch, d, a, lightcolor, lightdir, trans); - - if(flags&MDL_CULL_QUERY && d->query) endquery(d->query); - - m->endrender(); + if(shadowmapping && !(flags&(MDL_SHADOW|MDL_DYNSHADOW))) return; + model *m = loadmodel(mdl); + if(!m) return; + vec center(0, 0, 0), bbradius(0, 0, 0); + float radius = 0; + bool shadow = !shadowmap && !glaring && (flags&(MDL_SHADOW|MDL_DYNSHADOW)); + + if(flags&(MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED|MDL_CULL_QUERY|MDL_SHADOW|MDL_DYNSHADOW)) + { + if(flags&MDL_CULL_QUERY) + { + if(!oqfrags || !oqdynent || !d) flags &= ~MDL_CULL_QUERY; + } + + m->boundbox(center, bbradius); + radius = bbradius.magnitude(); + if(d && d->ragdoll) + { + radius = max(radius, d->ragdoll->radius); + center = d->ragdoll->center; + } + else + { + center.rotate_around_z(yaw*RAD); + center.add(o); + } + + int culled = cullmodel(m, center, radius, flags, d, shadow); + if(culled) + { + if(culled&(MDL_CULL_OCCLUDED|MDL_CULL_QUERY) && flags&MDL_CULL_QUERY && !reflecting && !refracting) + { + enablecullmodelquery(); + rendercullmodelquery(m, d, center, radius); + disablecullmodelquery(); + } + return; + } + + if(reflecting || refracting || shadowmapping) flags &= ~MDL_CULL_QUERY; + } + + if(flags&MDL_NORENDER) anim |= ANIM_NORENDER; + else if(showboundingbox && !shadowmapping && !reflecting && !refracting && editmode) + { + notextureshader->set(); + if(d && showboundingbox==1) + { + render3dbox(d->o, d->eyeheight, d->aboveeye, d->radius); + renderellipse(d->o, d->xradius, d->yradius, d->yaw); + } + else + { + vec center, radius; + if(showboundingbox==1) m->collisionbox(center, radius); + else m->boundbox(center, radius); + rotatebb(center, radius, int(yaw)); + center.add(o); + render3dbox(center, radius.z, radius.z, radius.x, radius.y); + } + } + + vec lightcolor(1, 1, 1), lightdir(0, 0, 1); + if(!shadowmapping) + { + vec pos = o; + if(d) + { + if(!reflecting && !refracting) d->occluded = OCCLUDE_NOTHING; + if(!light) light = &d->light; + if(flags&MDL_LIGHT && light->millis!=lastmillis) + { + if(d->ragdoll) + { + pos = d->ragdoll->center; + pos.z += radius/2; + } + else if(d->type < ENT_CAMERA) pos.z += 0.75f*(d->eyeheight + d->aboveeye); + lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); + dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); + game::lighteffects(d, light->color, light->dir); + light->millis = lastmillis; + } + } + else if(flags&MDL_LIGHT) + { + if(!light) + { + lightreaching(pos, lightcolor, lightdir, (flags&MDL_LIGHT_FAST)!=0); + dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); + } + else if(light->millis!=lastmillis) + { + lightreaching(pos, light->color, light->dir, (flags&MDL_LIGHT_FAST)!=0); + dynlightreaching(pos, light->color, light->dir, (flags&MDL_HUD)!=0); + light->millis = lastmillis; + } + } + if(light) { lightcolor = light->color; lightdir = light->dir; } + if(flags&MDL_DYNLIGHT) dynlightreaching(pos, lightcolor, lightdir, (flags&MDL_HUD)!=0); + } + + if(a) for(int i = 0; a[i].tag; i++) + { + if(a[i].name) a[i].m = loadmodel(a[i].name); + //if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL; + } + + if(numbatches>=0) + { + modelbatch &mb = addbatchedmodel(m); + batchedmodel &b = mb.batched.add(); + b.query = modelquery; + b.pos = o; + b.color = lightcolor; + b.dir = lightdir; + b.anim = anim; + b.yaw = yaw; + b.pitch = pitch; + b.basetime = basetime; + b.basetime2 = basetime2; + b.transparent = trans; + b.flags = flags & ~(MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); + if(!shadow || reflecting || refracting>0) + { + b.flags &= ~(MDL_SHADOW|MDL_DYNSHADOW); + if((flags&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) b.flags |= MDL_CULL_VFC; + } + mb.flags |= b.flags; + b.d = d; + b.attached = a ? modelattached.length() : -1; + if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].tag) break; } + if(flags&MDL_CULL_QUERY) d->query = b.query = newquery(d); + return; + } + + m->startrender(); + + if(shadowmapping) + { + anim |= ANIM_NOSKIN; + GLOBALPARAMF(shadowintensity, trans); + } + else + { + if(flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT; + if(flags&MDL_GHOST) anim |= ANIM_GHOST; + } + + if(flags&MDL_CULL_QUERY) + { + d->query = newquery(d); + if(d->query) startquery(d->query); + } + + m->render(anim, basetime, basetime2, o, yaw, pitch, d, a, lightcolor, lightdir, trans); + + if(flags&MDL_CULL_QUERY && d->query) endquery(d->query); + + m->endrender(); } void abovemodel(vec &o, const char *mdl) { - model *m = loadmodel(mdl); - if(!m) return; - o.z += m->above(); + model *m = loadmodel(mdl); + if(!m) return; + o.z += m->above(); } bool matchanim(const char *name, const char *pattern) { - for(;; pattern++) - { - const char *s = name; - char c; - for(;; pattern++) - { - c = *pattern; - if(!c || c=='|') break; - else if(c=='*') - { - if(!*s || iscubespace(*s)) break; - do s++; while(*s && !iscubespace(*s)); - } - else if(c!=*s) break; - else s++; - } - if(!*s && (!c || c=='|')) return true; - pattern = strchr(pattern, '|'); - if(!pattern) break; - } - return false; + for(;; pattern++) + { + const char *s = name; + char c; + for(;; pattern++) + { + c = *pattern; + if(!c || c=='|') break; + else if(c=='*') + { + if(!*s || iscubespace(*s)) break; + do s++; while(*s && !iscubespace(*s)); + } + else if(c!=*s) break; + else s++; + } + if(!*s && (!c || c=='|')) return true; + pattern = strchr(pattern, '|'); + if(!pattern) break; + } + return false; } void findanims(const char *pattern, vector &anims) { - loopi(sizeof(animnames)/sizeof(animnames[0])) if(matchanim(animnames[i], pattern)) anims.add(i); + loopi(sizeof(animnames)/sizeof(animnames[0])) if(matchanim(animnames[i], pattern)) anims.add(i); } ICOMMAND(findanims, "s", (char *name), { - vector anims; - findanims(name, anims); - vector buf; - string num; - loopv(anims) - { - formatstring(num, "%d", anims[i]); - if(i > 0) buf.add(' '); - buf.put(num, strlen(num)); - } - buf.add('\0'); - result(buf.getbuf()); + vector anims; + findanims(name, anims); + vector buf; + string num; + loopv(anims) + { + formatstring(num, "%d", anims[i]); + if(i > 0) buf.add(' '); + buf.put(num, strlen(num)); + } + buf.add('\0'); + result(buf.getbuf()); }); void loadskin(const char *dir, const char *altdir, Texture *&skin, Texture *&masks) // model skin sharing { #define ifnoload(tex, path) if((tex = textureload(path, 0, true, false))==notexture) #define tryload(tex, prefix, cmd, name) \ - ifnoload(tex, makerelpath(mdir, name ".jpg", prefix, cmd)) \ - { \ - ifnoload(tex, makerelpath(mdir, name ".png", prefix, cmd)) \ - { \ - ifnoload(tex, makerelpath(maltdir, name ".jpg", prefix, cmd)) \ - { \ - ifnoload(tex, makerelpath(maltdir, name ".png", prefix, cmd)) return; \ - } \ - } \ - } - - defformatstring(mdir, "packages/models/%s", dir); - defformatstring(maltdir, "packages/models/%s", altdir); - masks = notexture; - tryload(skin, NULL, NULL, "skin"); - tryload(masks, NULL, NULL, "masks"); + ifnoload(tex, makerelpath(mdir, name ".jpg", prefix, cmd)) \ + { \ + ifnoload(tex, makerelpath(mdir, name ".png", prefix, cmd)) \ + { \ + ifnoload(tex, makerelpath(maltdir, name ".jpg", prefix, cmd)) \ + { \ + ifnoload(tex, makerelpath(maltdir, name ".png", prefix, cmd)) return; \ + } \ + } \ + } + + defformatstring(mdir, "packages/models/%s", dir); + defformatstring(maltdir, "packages/models/%s", altdir); + masks = notexture; + tryload(skin, NULL, NULL, "skin"); + tryload(masks, NULL, NULL, "masks"); } // convenient function that covers the usual anims for players/monsters/npcs @@ -1033,86 +1033,86 @@ VAR(testpitch, -90, 0, 90); void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int hold, int attack, int attackdelay, int lastaction, int lastpain, float fade, bool ragdoll) { - int anim = hold ? hold : ANIM_IDLE|ANIM_LOOP; - float yaw = testanims && d==player ? 0 : d->yaw+90, - pitch = testpitch && d==player ? testpitch : d->pitch; - vec o = d->feetpos(); - int basetime = 0; - if(animoverride) anim = (animoverride<0 ? ANIM_ALL : animoverride)|ANIM_LOOP; - else if(d->state==CS_DEAD) - { - anim = ANIM_DYING|ANIM_NOPITCH; - basetime = lastpain; - if(ragdoll) - { - if(!d->ragdoll || d->ragdoll->millis < basetime) - { - DELETEP(d->ragdoll); - anim |= ANIM_RAGDOLL; - } - } - else if(lastmillis-basetime>1000) anim = ANIM_DEAD|ANIM_LOOP|ANIM_NOPITCH; - } - else if(d->state==CS_EDITING || d->state==CS_SPECTATOR) anim = ANIM_EDIT|ANIM_LOOP; - else if(d->state==CS_LAGGED) anim = ANIM_LAG|ANIM_LOOP; - else - { - if(lastmillis-lastpain < 300) - { - anim = ANIM_PAIN; - basetime = lastpain; - } - else if(lastpain < lastaction && (attack < 0 || (d->type != ENT_AI && lastmillis-lastaction < attackdelay))) - { - anim = attack < 0 ? -attack : attack; - basetime = lastaction; - } - - if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((game::allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->falling.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<timeinair>100) anim |= (ANIM_JUMP|ANIM_END)<move || d->strafe)) - { - if(d->move>0) anim |= (ANIM_FORWARD|ANIM_LOOP)<strafe) - { - if(d->move<0) anim |= ((d->strafe>0 ? ANIM_RIGHT : ANIM_LEFT)|ANIM_REVERSE|ANIM_LOOP)<strafe>0 ? ANIM_LEFT : ANIM_RIGHT)|ANIM_LOOP)<move<0) anim |= (ANIM_BACKWARD|ANIM_LOOP)<>ANIM_SECONDARY)&ANIM_INDEX) anim >>= ANIM_SECONDARY; - } - if(d->ragdoll && (!ragdoll || (anim&ANIM_INDEX)!=ANIM_DYING)) DELETEP(d->ragdoll); - if(!((anim>>ANIM_SECONDARY)&ANIM_INDEX)) anim |= (ANIM_IDLE|ANIM_LOOP)<type==ENT_PLAYER) flags |= MDL_FULLBRIGHT; - else flags |= MDL_CULL_DIST; - if(d->state==CS_LAGGED) fade = min(fade, 0.3f); - else flags |= MDL_DYNSHADOW; - if(drawtex == DRAWTEX_MODELPREVIEW) flags &= ~(MDL_LIGHT | MDL_FULLBRIGHT | MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY | MDL_CULL_DIST | MDL_DYNSHADOW); - rendermodel(NULL, mdlname, anim, o, yaw, pitch, flags, d, attachments, basetime, 0, fade); + int anim = hold ? hold : ANIM_IDLE|ANIM_LOOP; + float yaw = testanims && d==player ? 0 : d->yaw+90, + pitch = testpitch && d==player ? testpitch : d->pitch; + vec o = d->feetpos(); + int basetime = 0; + if(animoverride) anim = (animoverride<0 ? ANIM_ALL : animoverride)|ANIM_LOOP; + else if(d->state==CS_DEAD) + { + anim = ANIM_DYING|ANIM_NOPITCH; + basetime = lastpain; + if(ragdoll) + { + if(!d->ragdoll || d->ragdoll->millis < basetime) + { + DELETEP(d->ragdoll); + anim |= ANIM_RAGDOLL; + } + } + else if(lastmillis-basetime>1000) anim = ANIM_DEAD|ANIM_LOOP|ANIM_NOPITCH; + } + else if(d->state==CS_EDITING || d->state==CS_SPECTATOR) anim = ANIM_EDIT|ANIM_LOOP; + else if(d->state==CS_LAGGED) anim = ANIM_LAG|ANIM_LOOP; + else + { + if(lastmillis-lastpain < 300) + { + anim = ANIM_PAIN; + basetime = lastpain; + } + else if(lastpain < lastaction && (attack < 0 || (d->type != ENT_AI && lastmillis-lastaction < attackdelay))) + { + anim = attack < 0 ? -attack : attack; + basetime = lastaction; + } + + if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((game::allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->falling.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<timeinair>100) anim |= (ANIM_JUMP|ANIM_END)<move || d->strafe)) + { + if(d->move>0) anim |= (ANIM_FORWARD|ANIM_LOOP)<strafe) + { + if(d->move<0) anim |= ((d->strafe>0 ? ANIM_RIGHT : ANIM_LEFT)|ANIM_REVERSE|ANIM_LOOP)<strafe>0 ? ANIM_LEFT : ANIM_RIGHT)|ANIM_LOOP)<move<0) anim |= (ANIM_BACKWARD|ANIM_LOOP)<>ANIM_SECONDARY)&ANIM_INDEX) anim >>= ANIM_SECONDARY; + } + if(d->ragdoll && (!ragdoll || (anim&ANIM_INDEX)!=ANIM_DYING)) DELETEP(d->ragdoll); + if(!((anim>>ANIM_SECONDARY)&ANIM_INDEX)) anim |= (ANIM_IDLE|ANIM_LOOP)<type==ENT_PLAYER) flags |= MDL_FULLBRIGHT; + else flags |= MDL_CULL_DIST; + if(d->state==CS_LAGGED) fade = min(fade, 0.3f); + else flags |= MDL_DYNSHADOW; + if(drawtex == DRAWTEX_MODELPREVIEW) flags &= ~(MDL_LIGHT | MDL_FULLBRIGHT | MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY | MDL_CULL_DIST | MDL_DYNSHADOW); + rendermodel(NULL, mdlname, anim, o, yaw, pitch, flags, d, attachments, basetime, 0, fade); } void setbbfrommodel(dynent *d, const char *mdl) { - model *m = loadmodel(mdl); - if(!m) return; - vec center, radius; - m->collisionbox(center, radius); - if(d->type==ENT_INANIMATE && !m->ellipsecollide) - d->collidetype = COLLIDE_OBB; - d->xradius = radius.x + fabs(center.x); - d->yradius = radius.y + fabs(center.y); - d->radius = d->collidetype==COLLIDE_OBB ? sqrtf(d->xradius*d->xradius + d->yradius*d->yradius) : max(d->xradius, d->yradius); - d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight; - d->aboveeye = radius.z*2*(1.0f-m->eyeheight); - if (d->aboveeye + d->eyeheight <= 0.5f) - { - float zrad = (0.5f - (d->aboveeye + d->eyeheight)) / 2; - d->aboveeye += zrad; - d->eyeheight += zrad; - } + model *m = loadmodel(mdl); + if(!m) return; + vec center, radius; + m->collisionbox(center, radius); + if(d->type==ENT_INANIMATE && !m->ellipsecollide) + d->collidetype = COLLIDE_OBB; + d->xradius = radius.x + fabs(center.x); + d->yradius = radius.y + fabs(center.y); + d->radius = d->collidetype==COLLIDE_OBB ? sqrtf(d->xradius*d->xradius + d->yradius*d->yradius) : max(d->xradius, d->yradius); + d->eyeheight = (center.z-radius.z) + radius.z*2*m->eyeheight; + d->aboveeye = radius.z*2*(1.0f-m->eyeheight); + if (d->aboveeye + d->eyeheight <= 0.5f) + { + float zrad = (0.5f - (d->aboveeye + d->eyeheight)) / 2; + d->aboveeye += zrad; + d->eyeheight += zrad; + } } diff --git a/src/engine/renderparticles.cpp b/src/engine/renderparticles.cpp index c6994bf..accc204 100644 --- a/src/engine/renderparticles.cpp +++ b/src/engine/renderparticles.cpp @@ -15,54 +15,51 @@ static bool canemit = false, regenemitters = false, canstep = false; static bool canemitparticles() { - if(reflecting || refracting) return false; - return canemit || emitoffset; + if(reflecting || refracting) return false; + return canemit || emitoffset; } VARP(showparticles, 0, 1, 1); VAR(cullparticles, 0, 1, 1); VAR(replayparticles, 0, 1, 1); VARN(seedparticles, seedmillis, 0, 3000, 10000); -VAR(dbgpcull, 0, 0, 1); -VAR(dbgpseed, 0, 0, 1); struct particleemitter { - extentity *ent; - vec bbmin, bbmax; - vec center; - float radius; - ivec cullmin, cullmax; - int maxfade, lastemit, lastcull; - - particleemitter(extentity *ent) - : ent(ent), bbmin(ent->o), bbmax(ent->o), maxfade(-1), lastemit(0), lastcull(0) - {} - - void finalize() - { - center = vec(bbmin).add(bbmax).mul(0.5f); - radius = bbmin.dist(bbmax)/2; - cullmin = ivec(int(floor(bbmin.x)), int(floor(bbmin.y)), int(floor(bbmin.z))); - cullmax = ivec(int(ceil(bbmax.x)), int(ceil(bbmax.y)), int(ceil(bbmax.z))); - if(dbgpseed) conoutf(CON_DEBUG, "radius: %f, maxfade: %d", radius, maxfade); - } - - void extendbb(const vec &o, float size = 0) - { - bbmin.x = min(bbmin.x, o.x - size); - bbmin.y = min(bbmin.y, o.y - size); - bbmin.z = min(bbmin.z, o.z - size); - bbmax.x = max(bbmax.x, o.x + size); - bbmax.y = max(bbmax.y, o.y + size); - bbmax.z = max(bbmax.z, o.z + size); - } - - void extendbb(float z, float size = 0) - { - bbmin.z = min(bbmin.z, z - size); - bbmax.z = max(bbmax.z, z + size); - } + extentity *ent; + vec bbmin, bbmax; + vec center; + float radius; + ivec cullmin, cullmax; + int maxfade, lastemit, lastcull; + + particleemitter(extentity *ent) + : ent(ent), bbmin(ent->o), bbmax(ent->o), maxfade(-1), lastemit(0), lastcull(0) + {} + + void finalize() + { + center = vec(bbmin).add(bbmax).mul(0.5f); + radius = bbmin.dist(bbmax)/2; + cullmin = ivec(int(floor(bbmin.x)), int(floor(bbmin.y)), int(floor(bbmin.z))); + cullmax = ivec(int(ceil(bbmax.x)), int(ceil(bbmax.y)), int(ceil(bbmax.z))); + } + + void extendbb(const vec &o, float size = 0) + { + bbmin.x = min(bbmin.x, o.x - size); + bbmin.y = min(bbmin.y, o.y - size); + bbmin.z = min(bbmin.z, o.z - size); + bbmax.x = max(bbmax.x, o.x + size); + bbmax.y = max(bbmax.y, o.y + size); + bbmax.z = max(bbmax.z, o.z + size); + } + + void extendbb(float z, float size = 0) + { + bbmin.z = min(bbmin.z, z - size); + bbmax.z = max(bbmax.z, z + size); + } }; static vector emitters; @@ -70,80 +67,80 @@ static particleemitter *seedemitter = NULL; void clearparticleemitters() { - emitters.setsize(0); - regenemitters = true; + emitters.setsize(0); + regenemitters = true; } void addparticleemitters() { - emitters.setsize(0); - const vector &ents = entities::getents(); - loopv(ents) - { - extentity &e = *ents[i]; - if(e.type != ET_PARTICLES) continue; - emitters.add(particleemitter(&e)); - } - regenemitters = false; + emitters.setsize(0); + const vector &ents = entities::getents(); + loopv(ents) + { + extentity &e = *ents[i]; + if(e.type != ET_PARTICLES) continue; + emitters.add(particleemitter(&e)); + } + regenemitters = false; } enum { - PT_PART = 0, - PT_TAPE, - PT_TRAIL, - PT_TEXT, - PT_TEXTICON, - PT_METER, - PT_METERVS, - PT_FIREBALL, - PT_LIGHTNING, - PT_FLARE, - - PT_MOD = 1<<8, - PT_RND4 = 1<<9, - PT_LERP = 1<<10, // use very sparingly - order of blending issues - PT_TRACK = 1<<11, - PT_GLARE = 1<<12, - PT_SOFT = 1<<13, - PT_HFLIP = 1<<14, - PT_VFLIP = 1<<15, - PT_ROT = 1<<16, - PT_CULL = 1<<17, - PT_FEW = 1<<18, - PT_ICON = 1<<19, - PT_NOTEX = 1<<20, - PT_SHADER = 1<<21, - PT_FLIP = PT_HFLIP | PT_VFLIP | PT_ROT + PT_PART = 0, + PT_TAPE, + PT_TRAIL, + PT_TEXT, + PT_TEXTICON, + PT_METER, + PT_METERVS, + PT_FIREBALL, + PT_LIGHTNING, + PT_FLARE, + + PT_MOD = 1<<8, + PT_RND4 = 1<<9, + PT_LERP = 1<<10, // use very sparingly - order of blending issues + PT_TRACK = 1<<11, + PT_GLARE = 1<<12, + PT_SOFT = 1<<13, + PT_HFLIP = 1<<14, + PT_VFLIP = 1<<15, + PT_ROT = 1<<16, + PT_CULL = 1<<17, + PT_FEW = 1<<18, + PT_ICON = 1<<19, + PT_NOTEX = 1<<20, + PT_SHADER = 1<<21, + PT_FLIP = PT_HFLIP | PT_VFLIP | PT_ROT }; const char *partnames[] = { "part", "tape", "trail", "text", "texticon", "meter", "metervs", "fireball", "lightning", "flare" }; struct particle { - vec o, d; - int gravity, fade, millis; - bvec color; - uchar flags; - float size; - union - { - const char *text; - float val; - physent *owner; - struct - { - uchar color2[3]; - uchar progress; - }; - }; + vec o, d; + int gravity, fade, millis; + bvec color; + uchar flags; + float size; + union + { + const char *text; + float val; + physent *owner; + struct + { + uchar color2[3]; + uchar progress; + }; + }; }; struct partvert { - vec pos; - bvec4 color; - vec2 tc; + vec pos; + bvec4 color; + vec2 tc; }; #define COLLIDERADIUS 8.0f @@ -151,381 +148,381 @@ struct partvert struct partrenderer { - Texture *tex; - const char *texname; - int texclamp; - uint type; - int collide; - string info; - - partrenderer(const char *texname, int texclamp, int type, int collide = 0) - : tex(NULL), texname(texname), texclamp(texclamp), type(type), collide(collide) - { - } - partrenderer(int type, int collide = 0) - : tex(NULL), texname(NULL), texclamp(0), type(type), collide(collide) - { - } - virtual ~partrenderer() - { - } - - virtual void init(int n) { } - virtual void reset() = 0; - virtual void resettracked(physent *owner) { } - virtual particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity = 0) = 0; - virtual int adddepthfx(vec &bbmin, vec &bbmax) { return 0; } - virtual void update() { } - virtual void render() = 0; - virtual bool haswork() = 0; - virtual void cleanup() {} - - virtual void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) - { - } - - //blend = 0 => remove it - void calc(particle *p, int &blend, int &ts, vec &o, vec &d, bool step = true) - { - o = p->o; - d = p->d; - if(type&PT_TRACK && p->owner) game::particletrack(p->owner, o, d); - if(p->fade <= 5) - { - ts = 1; - blend = 255; - } - else - { - ts = lastmillis-p->millis; - blend = max(255 - (ts<<8)/p->fade, 0); - if(p->gravity) - { - if(ts > p->fade) ts = p->fade; - float t = ts; - o.add(vec(d).mul(t/5000.0f)); - o.z -= t*t/(2.0f * 5000.0f * p->gravity); - } - if(collide && o.z < p->val && step) - { - if(collide >= 0) - { - vec surface; - float floorz = rayfloor(vec(o.x, o.y, p->val), surface, RAY_CLIPMAT, COLLIDERADIUS); - float collidez = floorz<0 ? o.z-COLLIDERADIUS : p->val - floorz; - if(o.z >= collidez+COLLIDEERROR) - p->val = collidez+COLLIDEERROR; - else - { - adddecal(collide, vec(o.x, o.y, collidez), vec(p->o).sub(o).normalize(), 2*p->size, p->color, type&PT_RND4 ? (p->flags>>5)&3 : 0); - blend = 0; - } - } - else blend = 0; - } - } - } + Texture *tex; + const char *texname; + int texclamp; + uint type; + int collide; + string info; + + partrenderer(const char *texname, int texclamp, int type, int collide = 0) + : tex(NULL), texname(texname), texclamp(texclamp), type(type), collide(collide) + { + } + partrenderer(int type, int collide = 0) + : tex(NULL), texname(NULL), texclamp(0), type(type), collide(collide) + { + } + virtual ~partrenderer() + { + } + + virtual void init(int n) { } + virtual void reset() = 0; + virtual void resettracked(physent *owner) { } + virtual particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity = 0) = 0; + virtual int adddepthfx(vec &bbmin, vec &bbmax) { return 0; } + virtual void update() { } + virtual void render() = 0; + virtual bool haswork() = 0; + virtual void cleanup() {} + + virtual void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) + { + } + + //blend = 0 => remove it + void calc(particle *p, int &blend, int &ts, vec &o, vec &d, bool step = true) + { + o = p->o; + d = p->d; + if(type&PT_TRACK && p->owner) game::particletrack(p->owner, o, d); + if(p->fade <= 5) + { + ts = 1; + blend = 255; + } + else + { + ts = lastmillis-p->millis; + blend = max(255 - (ts<<8)/p->fade, 0); + if(p->gravity) + { + if(ts > p->fade) ts = p->fade; + float t = ts; + o.add(vec(d).mul(t/5000.0f)); + o.z -= t*t/(2.0f * 5000.0f * p->gravity); + } + if(collide && o.z < p->val && step) + { + if(collide >= 0) + { + vec surface; + float floorz = rayfloor(vec(o.x, o.y, p->val), surface, RAY_CLIPMAT, COLLIDERADIUS); + float collidez = floorz<0 ? o.z-COLLIDERADIUS : p->val - floorz; + if(o.z >= collidez+COLLIDEERROR) + p->val = collidez+COLLIDEERROR; + else + { + adddecal(collide, vec(o.x, o.y, collidez), vec(p->o).sub(o).normalize(), 2*p->size, p->color, type&PT_RND4 ? (p->flags>>5)&3 : 0); + blend = 0; + } + } + else blend = 0; + } + } + } }; struct listparticle : particle { - listparticle *next; + listparticle *next; }; VARP(outlinemeters, 0, 0, 1); struct listrenderer : partrenderer { - static listparticle *parempty; - listparticle *list; - - listrenderer(const char *texname, int texclamp, int type, int collide = 0) - : partrenderer(texname, texclamp, type, collide), list(NULL) - { - } - listrenderer(int type, int collide = 0) - : partrenderer(type, collide), list(NULL) - { - } - - virtual ~listrenderer() - { - } - - virtual void killpart(listparticle *p) - { - } - - void reset() - { - if(!list) return; - listparticle *p = list; - for(;;) - { - killpart(p); - if(p->next) p = p->next; - else break; - } - p->next = parempty; - parempty = list; - list = NULL; - } - - void resettracked(physent *owner) - { - if(!(type&PT_TRACK)) return; - for(listparticle **prev = &list, *cur = list; cur; cur = *prev) - { - if(!owner || cur->owner==owner) - { - *prev = cur->next; - cur->next = parempty; - parempty = cur; - } - else prev = &cur->next; - } - } - - particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) - { - if(!parempty) - { - listparticle *ps = new listparticle[256]; - loopi(255) ps[i].next = &ps[i+1]; - ps[255].next = parempty; - parempty = ps; - } - listparticle *p = parempty; - parempty = p->next; - p->next = list; - list = p; - p->o = o; - p->d = d; - p->gravity = gravity; - p->fade = fade; - p->millis = lastmillis + emitoffset; - p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF); - p->size = size; - p->owner = NULL; - p->flags = 0; - return p; - } - - int count() - { - int num = 0; - listparticle *lp; - for(lp = list; lp; lp = lp->next) num++; - return num; - } - - bool haswork() - { - return (list != NULL); - } - - virtual void startrender() = 0; - virtual void endrender() = 0; - virtual void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) = 0; - - void render() - { - startrender(); - if(texname) - { - if(!tex) tex = textureload(texname, texclamp); - glBindTexture(GL_TEXTURE_2D, tex->id); - } - - for(listparticle **prev = &list, *p = list; p; p = *prev) - { - vec o, d; - int blend, ts; - calc(p, blend, ts, o, d, canstep); - if(blend > 0) - { - renderpart(p, o, d, blend, ts); - - if(p->fade > 5 || !canstep) - { - prev = &p->next; - continue; - } - } - //remove - *prev = p->next; - p->next = parempty; - killpart(p); - parempty = p; - } - - endrender(); - } + static listparticle *parempty; + listparticle *list; + + listrenderer(const char *texname, int texclamp, int type, int collide = 0) + : partrenderer(texname, texclamp, type, collide), list(NULL) + { + } + listrenderer(int type, int collide = 0) + : partrenderer(type, collide), list(NULL) + { + } + + virtual ~listrenderer() + { + } + + virtual void killpart(listparticle *p) + { + } + + void reset() + { + if(!list) return; + listparticle *p = list; + for(;;) + { + killpart(p); + if(p->next) p = p->next; + else break; + } + p->next = parempty; + parempty = list; + list = NULL; + } + + void resettracked(physent *owner) + { + if(!(type&PT_TRACK)) return; + for(listparticle **prev = &list, *cur = list; cur; cur = *prev) + { + if(!owner || cur->owner==owner) + { + *prev = cur->next; + cur->next = parempty; + parempty = cur; + } + else prev = &cur->next; + } + } + + particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) + { + if(!parempty) + { + listparticle *ps = new listparticle[256]; + loopi(255) ps[i].next = &ps[i+1]; + ps[255].next = parempty; + parempty = ps; + } + listparticle *p = parempty; + parempty = p->next; + p->next = list; + list = p; + p->o = o; + p->d = d; + p->gravity = gravity; + p->fade = fade; + p->millis = lastmillis + emitoffset; + p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF); + p->size = size; + p->owner = NULL; + p->flags = 0; + return p; + } + + int count() + { + int num = 0; + listparticle *lp; + for(lp = list; lp; lp = lp->next) num++; + return num; + } + + bool haswork() + { + return (list != NULL); + } + + virtual void startrender() = 0; + virtual void endrender() = 0; + virtual void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) = 0; + + void render() + { + startrender(); + if(texname) + { + if(!tex) tex = textureload(texname, texclamp); + glBindTexture(GL_TEXTURE_2D, tex->id); + } + + for(listparticle **prev = &list, *p = list; p; p = *prev) + { + vec o, d; + int blend, ts; + calc(p, blend, ts, o, d, canstep); + if(blend > 0) + { + renderpart(p, o, d, blend, ts); + + if(p->fade > 5 || !canstep) + { + prev = &p->next; + continue; + } + } + //remove + *prev = p->next; + p->next = parempty; + killpart(p); + parempty = p; + } + + endrender(); + } }; listparticle *listrenderer::parempty = NULL; struct meterrenderer : listrenderer { - meterrenderer(int type) - : listrenderer(type|PT_NOTEX|PT_LERP) - {} - - void startrender() - { - glDisable(GL_BLEND); - gle::defvertex(); - } - - void endrender() - { - glEnable(GL_BLEND); - } - - void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) - { - int basetype = type&0xFF; - float scale = FONTH*p->size/80.0f, right = 8, left = p->progress/100.0f*right; - matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), o); - m.scale(scale); - m.translate(-right/2.0f, 0, 0); - - if(outlinemeters) - { - gle::colorf(0, 0.8f, 0); - gle::begin(GL_TRIANGLE_STRIP); - loopk(10) - { - const vec2 &sc = sincos360[k*(180/(10-1))]; - float c = (0.5f + 0.1f)*sc.y, s = 0.5f - (0.5f + 0.1f)*sc.x; - gle::attrib(m.transform(vec2(-c, s))); - gle::attrib(m.transform(vec2(right + c, s))); - } - gle::end(); - } - - if(basetype==PT_METERVS) gle::colorub(p->color2[0], p->color2[1], p->color2[2]); - else gle::colorf(0, 0, 0); - gle::begin(GL_TRIANGLE_STRIP); - loopk(10) - { - const vec2 &sc = sincos360[k*(180/(10-1))]; - float c = 0.5f*sc.y, s = 0.5f - 0.5f*sc.x; - gle::attrib(m.transform(vec2(left + c, s))); - gle::attrib(m.transform(vec2(right + c, s))); - } - gle::end(); - - if(outlinemeters) - { - gle::colorf(0, 0.8f, 0); - gle::begin(GL_TRIANGLE_FAN); - loopk(10) - { - const vec2 &sc = sincos360[k*(180/(10-1))]; - float c = (0.5f + 0.1f)*sc.y, s = 0.5f - (0.5f + 0.1f)*sc.x; - gle::attrib(m.transform(vec2(left + c, s))); - } - gle::end(); - } - - gle::color(p->color); - gle::begin(GL_TRIANGLE_STRIP); - loopk(10) - { - const vec2 &sc = sincos360[k*(180/(10-1))]; - float c = 0.5f*sc.y, s = 0.5f - 0.5f*sc.x; - gle::attrib(m.transform(vec2(-c, s))); - gle::attrib(m.transform(vec2(left + c, s))); - } - gle::end(); - } + meterrenderer(int type) + : listrenderer(type|PT_NOTEX|PT_LERP) + {} + + void startrender() + { + glDisable(GL_BLEND); + gle::defvertex(); + } + + void endrender() + { + glEnable(GL_BLEND); + } + + void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) + { + int basetype = type&0xFF; + float scale = FONTH*p->size/80.0f, right = 8, left = p->progress/100.0f*right; + matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), o); + m.scale(scale); + m.translate(-right/2.0f, 0, 0); + + if(outlinemeters) + { + gle::colorf(0, 0.8f, 0); + gle::begin(GL_TRIANGLE_STRIP); + loopk(10) + { + const vec2 &sc = sincos360[k*(180/(10-1))]; + float c = (0.5f + 0.1f)*sc.y, s = 0.5f - (0.5f + 0.1f)*sc.x; + gle::attrib(m.transform(vec2(-c, s))); + gle::attrib(m.transform(vec2(right + c, s))); + } + gle::end(); + } + + if(basetype==PT_METERVS) gle::colorub(p->color2[0], p->color2[1], p->color2[2]); + else gle::colorf(0, 0, 0); + gle::begin(GL_TRIANGLE_STRIP); + loopk(10) + { + const vec2 &sc = sincos360[k*(180/(10-1))]; + float c = 0.5f*sc.y, s = 0.5f - 0.5f*sc.x; + gle::attrib(m.transform(vec2(left + c, s))); + gle::attrib(m.transform(vec2(right + c, s))); + } + gle::end(); + + if(outlinemeters) + { + gle::colorf(0, 0.8f, 0); + gle::begin(GL_TRIANGLE_FAN); + loopk(10) + { + const vec2 &sc = sincos360[k*(180/(10-1))]; + float c = (0.5f + 0.1f)*sc.y, s = 0.5f - (0.5f + 0.1f)*sc.x; + gle::attrib(m.transform(vec2(left + c, s))); + } + gle::end(); + } + + gle::color(p->color); + gle::begin(GL_TRIANGLE_STRIP); + loopk(10) + { + const vec2 &sc = sincos360[k*(180/(10-1))]; + float c = 0.5f*sc.y, s = 0.5f - 0.5f*sc.x; + gle::attrib(m.transform(vec2(-c, s))); + gle::attrib(m.transform(vec2(left + c, s))); + } + gle::end(); + } }; static meterrenderer meters(PT_METER), metervs(PT_METERVS); struct textrenderer : listrenderer { - textrenderer(int type) - : listrenderer(type) - {} - - void startrender() - { - } - - void endrender() - { - } - - void killpart(listparticle *p) - { - if(p->text && p->flags&1) delete[] p->text; - } - - void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) - { - float scale = p->size/80.0f, xoff = -(text_width(p->text) + ((p->flags>>1)&7)*FONTH)/2, yoff = 0; - - matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), o); - m.scale(scale); - m.translate(xoff, yoff, 50); - - textmatrix = &m; - draw_text(p->text, 0, 0, p->color.r, p->color.g, p->color.b, blend); - textmatrix = NULL; - } + textrenderer(int type) + : listrenderer(type) + {} + + void startrender() + { + } + + void endrender() + { + } + + void killpart(listparticle *p) + { + if(p->text && p->flags&1) delete[] p->text; + } + + void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) + { + float scale = p->size/80.0f, xoff = -(text_width(p->text) + ((p->flags>>1)&7)*FONTH)/2, yoff = 0; + + matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), o); + m.scale(scale); + m.translate(xoff, yoff, 50); + + textmatrix = &m; + draw_text(p->text, 0, 0, p->color.r, p->color.g, p->color.b, blend); + textmatrix = NULL; + } }; static textrenderer texts(PT_TEXT|PT_LERP); struct texticonrenderer : listrenderer { - texticonrenderer(const char *texname, int type) - : listrenderer(texname, 3, type) - {} - - void startrender() - { - gle::defvertex(); - gle::deftexcoord0(); - gle::defcolor(4, GL_UNSIGNED_BYTE); - gle::begin(GL_QUADS); - } - - void endrender() - { - gle::end(); - } - - void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) - { - float scale = p->size/80.0f, xoff = p->val, yoff = 0; - - matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), o); - m.scale(scale); - m.translate(xoff, yoff, 50); - - float tx = 0.25f*(p->flags&3), ty = 0.25f*((p->flags>>2)&3); - - gle::attrib(m.transform(vec2(0, 0))); - gle::attrib(tx, ty); - gle::attrib(p->color, blend); - gle::attrib(m.transform(vec2(FONTH, 0))); - gle::attrib(tx + 0.25f, ty); - gle::attrib(p->color, blend); - gle::attrib(m.transform(vec2(FONTH, FONTH))); - gle::attrib(tx + 0.25f, ty + 0.25f); - gle::attrib(p->color, blend); - gle::attrib(m.transform(vec2(0, FONTH))); - gle::attrib(tx, ty + 0.25f); - gle::attrib(p->color, blend); - } + texticonrenderer(const char *texname, int type) + : listrenderer(texname, 3, type) + {} + + void startrender() + { + gle::defvertex(); + gle::deftexcoord0(); + gle::defcolor(4, GL_UNSIGNED_BYTE); + gle::begin(GL_QUADS); + } + + void endrender() + { + gle::end(); + } + + void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts) + { + float scale = p->size/80.0f, xoff = p->val, yoff = 0; + + matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), o); + m.scale(scale); + m.translate(xoff, yoff, 50); + + float tx = 0.25f*(p->flags&3), ty = 0.25f*((p->flags>>2)&3); + + gle::attrib(m.transform(vec2(0, 0))); + gle::attrib(tx, ty); + gle::attrib(p->color, blend); + gle::attrib(m.transform(vec2(FONTH, 0))); + gle::attrib(tx + 0.25f, ty); + gle::attrib(p->color, blend); + gle::attrib(m.transform(vec2(FONTH, FONTH))); + gle::attrib(tx + 0.25f, ty + 0.25f); + gle::attrib(p->color, blend); + gle::attrib(m.transform(vec2(0, FONTH))); + gle::attrib(tx, ty + 0.25f); + gle::attrib(p->color, blend); + } }; static texticonrenderer texticons("packages/hud/items.png", PT_TEXTICON|PT_LERP); template static inline void modifyblend(const vec &o, int &blend) { - blend = min(blend<<2, 255); + blend = min(blend<<2, 255); } template<> @@ -536,306 +533,306 @@ inline void modifyblend(const vec &o, int &blend) template static inline void genpos(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs) { - vec udir = vec(camup).sub(camright).mul(size); - vec vdir = vec(camup).add(camright).mul(size); - vs[0].pos = vec(o.x + udir.x, o.y + udir.y, o.z + udir.z); - vs[1].pos = vec(o.x + vdir.x, o.y + vdir.y, o.z + vdir.z); - vs[2].pos = vec(o.x - udir.x, o.y - udir.y, o.z - udir.z); - vs[3].pos = vec(o.x - vdir.x, o.y - vdir.y, o.z - vdir.z); + vec udir = vec(camup).sub(camright).mul(size); + vec vdir = vec(camup).add(camright).mul(size); + vs[0].pos = vec(o.x + udir.x, o.y + udir.y, o.z + udir.z); + vs[1].pos = vec(o.x + vdir.x, o.y + vdir.y, o.z + vdir.z); + vs[2].pos = vec(o.x - udir.x, o.y - udir.y, o.z - udir.z); + vs[3].pos = vec(o.x - vdir.x, o.y - vdir.y, o.z - vdir.z); } template<> inline void genpos(const vec &o, const vec &d, float size, int ts, int grav, partvert *vs) { - vec dir1 = d, dir2 = d, c; - dir1.sub(o); - dir2.sub(camera1->o); - c.cross(dir2, dir1).normalize().mul(size); - vs[0].pos = vec(d.x-c.x, d.y-c.y, d.z-c.z); - vs[1].pos = vec(o.x-c.x, o.y-c.y, o.z-c.z); - vs[2].pos = vec(o.x+c.x, o.y+c.y, o.z+c.z); - vs[3].pos = vec(d.x+c.x, d.y+c.y, d.z+c.z); + vec dir1 = d, dir2 = d, c; + dir1.sub(o); + dir2.sub(camera1->o); + c.cross(dir2, dir1).normalize().mul(size); + vs[0].pos = vec(d.x-c.x, d.y-c.y, d.z-c.z); + vs[1].pos = vec(o.x-c.x, o.y-c.y, o.z-c.z); + vs[2].pos = vec(o.x+c.x, o.y+c.y, o.z+c.z); + vs[3].pos = vec(d.x+c.x, d.y+c.y, d.z+c.z); } template<> inline void genpos(const vec &o, const vec &d, float size, int ts, int grav, partvert *vs) { - vec e = d; - if(grav) e.z -= float(ts)/grav; - e.div(-75.0f).add(o); - genpos(o, e, size, ts, grav, vs); + vec e = d; + if(grav) e.z -= float(ts)/grav; + e.div(-75.0f).add(o); + genpos(o, e, size, ts, grav, vs); } template static inline void genrotpos(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs, int rot) { - genpos(o, d, size, grav, ts, vs); + genpos(o, d, size, grav, ts, vs); } #define ROTCOEFFS(n) { \ - vec(-1, 1, 0).rotate_around_z(n*2*M_PI/32.0f), \ - vec( 1, 1, 0).rotate_around_z(n*2*M_PI/32.0f), \ - vec( 1, -1, 0).rotate_around_z(n*2*M_PI/32.0f), \ - vec(-1, -1, 0).rotate_around_z(n*2*M_PI/32.0f) \ + vec(-1, 1, 0).rotate_around_z(n*2*M_PI/32.0f), \ + vec( 1, 1, 0).rotate_around_z(n*2*M_PI/32.0f), \ + vec( 1, -1, 0).rotate_around_z(n*2*M_PI/32.0f), \ + vec(-1, -1, 0).rotate_around_z(n*2*M_PI/32.0f) \ } static const vec rotcoeffs[32][4] = { - ROTCOEFFS(0), ROTCOEFFS(1), ROTCOEFFS(2), ROTCOEFFS(3), ROTCOEFFS(4), ROTCOEFFS(5), ROTCOEFFS(6), ROTCOEFFS(7), - ROTCOEFFS(8), ROTCOEFFS(9), ROTCOEFFS(10), ROTCOEFFS(11), ROTCOEFFS(12), ROTCOEFFS(13), ROTCOEFFS(14), ROTCOEFFS(15), - ROTCOEFFS(16), ROTCOEFFS(17), ROTCOEFFS(18), ROTCOEFFS(19), ROTCOEFFS(20), ROTCOEFFS(21), ROTCOEFFS(22), ROTCOEFFS(7), - ROTCOEFFS(24), ROTCOEFFS(25), ROTCOEFFS(26), ROTCOEFFS(27), ROTCOEFFS(28), ROTCOEFFS(29), ROTCOEFFS(30), ROTCOEFFS(31), + ROTCOEFFS(0), ROTCOEFFS(1), ROTCOEFFS(2), ROTCOEFFS(3), ROTCOEFFS(4), ROTCOEFFS(5), ROTCOEFFS(6), ROTCOEFFS(7), + ROTCOEFFS(8), ROTCOEFFS(9), ROTCOEFFS(10), ROTCOEFFS(11), ROTCOEFFS(12), ROTCOEFFS(13), ROTCOEFFS(14), ROTCOEFFS(15), + ROTCOEFFS(16), ROTCOEFFS(17), ROTCOEFFS(18), ROTCOEFFS(19), ROTCOEFFS(20), ROTCOEFFS(21), ROTCOEFFS(22), ROTCOEFFS(7), + ROTCOEFFS(24), ROTCOEFFS(25), ROTCOEFFS(26), ROTCOEFFS(27), ROTCOEFFS(28), ROTCOEFFS(29), ROTCOEFFS(30), ROTCOEFFS(31), }; template<> inline void genrotpos(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs, int rot) { - const vec *coeffs = rotcoeffs[rot]; - (vs[0].pos = o).add(vec(camright).mul(coeffs[0].x*size)).add(vec(camup).mul(coeffs[0].y*size)); - (vs[1].pos = o).add(vec(camright).mul(coeffs[1].x*size)).add(vec(camup).mul(coeffs[1].y*size)); - (vs[2].pos = o).add(vec(camright).mul(coeffs[2].x*size)).add(vec(camup).mul(coeffs[2].y*size)); - (vs[3].pos = o).add(vec(camright).mul(coeffs[3].x*size)).add(vec(camup).mul(coeffs[3].y*size)); + const vec *coeffs = rotcoeffs[rot]; + (vs[0].pos = o).add(vec(camright).mul(coeffs[0].x*size)).add(vec(camup).mul(coeffs[0].y*size)); + (vs[1].pos = o).add(vec(camright).mul(coeffs[1].x*size)).add(vec(camup).mul(coeffs[1].y*size)); + (vs[2].pos = o).add(vec(camright).mul(coeffs[2].x*size)).add(vec(camup).mul(coeffs[2].y*size)); + (vs[3].pos = o).add(vec(camright).mul(coeffs[3].x*size)).add(vec(camup).mul(coeffs[3].y*size)); } template static inline void seedpos(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav) { - if(grav) - { - vec end(o); - float t = fade; - end.add(vec(d).mul(t/5000.0f)); - end.z -= t*t/(2.0f * 5000.0f * grav); - pe.extendbb(end, size); - - float tpeak = d.z*grav; - if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size); - } + if(grav) + { + vec end(o); + float t = fade; + end.add(vec(d).mul(t/5000.0f)); + end.z -= t*t/(2.0f * 5000.0f * grav); + pe.extendbb(end, size); + + float tpeak = d.z*grav; + if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size); + } } template<> inline void seedpos(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav) { - pe.extendbb(d, size); + pe.extendbb(d, size); } template<> inline void seedpos(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav) { - vec e = d; - if(grav) e.z -= float(fade)/grav; - e.div(-75.0f).add(o); - pe.extendbb(e, size); + vec e = d; + if(grav) e.z -= float(fade)/grav; + e.div(-75.0f).add(o); + pe.extendbb(e, size); } template struct varenderer : partrenderer { - partvert *verts; - particle *parts; - int maxparts, numparts, lastupdate, rndmask; - GLuint vbo; - - varenderer(const char *texname, int type, int collide = 0) - : partrenderer(texname, 3, type, collide), - verts(NULL), parts(NULL), maxparts(0), numparts(0), lastupdate(-1), rndmask(0), vbo(0) - { - if(type & PT_HFLIP) rndmask |= 0x01; - if(type & PT_VFLIP) rndmask |= 0x02; - if(type & PT_ROT) rndmask |= 0x1F<<2; - if(type & PT_RND4) rndmask |= 0x03<<5; - } - - void cleanup() - { - if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } - } - - void init(int n) - { - DELETEA(parts); - DELETEA(verts); - parts = new particle[n]; - verts = new partvert[n*4]; - maxparts = n; - numparts = 0; - lastupdate = -1; - } - - void reset() - { - numparts = 0; - lastupdate = -1; - } - - void resettracked(physent *owner) - { - if(!(type&PT_TRACK)) return; - loopi(numparts) - { - particle *p = parts+i; - if(!owner || (p->owner == owner)) p->fade = -1; - } - lastupdate = -1; - } - - int count() - { - return numparts; - } - - bool haswork() - { - return (numparts > 0); - } - - particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) - { - particle *p = parts + (numparts < maxparts ? numparts++ : rnd(maxparts)); //next free slot, or kill a random kitten - p->o = o; - p->d = d; - p->gravity = gravity; - p->fade = fade; - p->millis = lastmillis + emitoffset; - p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF); - p->size = size; - p->owner = NULL; - p->flags = 0x80 | (rndmask ? rnd(0x80) & rndmask : 0); - lastupdate = -1; - return p; - } - - void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) - { - pe.maxfade = max(pe.maxfade, fade); - size *= SQRT2; - pe.extendbb(o, size); - - seedpos(pe, o, d, fade, size, gravity); - if(!gravity) return; - - vec end(o); - float t = fade; - end.add(vec(d).mul(t/5000.0f)); - end.z -= t*t/(2.0f * 5000.0f * gravity); - pe.extendbb(end, size); - - float tpeak = d.z*gravity; - if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size); - } - - void genverts(particle *p, partvert *vs, bool regen) - { - vec o, d; - int blend, ts; - - calc(p, blend, ts, o, d); - if(blend <= 1 || p->fade <= 5) p->fade = -1; //mark to remove on next pass (i.e. after render) - - modifyblend(o, blend); - - if(regen) - { - p->flags &= ~0x80; - - #define SETTEXCOORDS(u1c, u2c, v1c, v2c, body) \ - { \ - float u1 = u1c, u2 = u2c, v1 = v1c, v2 = v2c; \ - body; \ - vs[0].tc = vec2(u1, v1); \ - vs[1].tc = vec2(u2, v1); \ - vs[2].tc = vec2(u2, v2); \ - vs[3].tc = vec2(u1, v2); \ - } - if(type&PT_RND4) - { - float tx = 0.5f*((p->flags>>5)&1), ty = 0.5f*((p->flags>>6)&1); - SETTEXCOORDS(tx, tx + 0.5f, ty, ty + 0.5f, - { - if(p->flags&0x01) swap(u1, u2); - if(p->flags&0x02) swap(v1, v2); - }); - } - else if(type&PT_ICON) - { - float tx = 0.25f*(p->flags&3), ty = 0.25f*((p->flags>>2)&3); - SETTEXCOORDS(tx, tx + 0.25f, ty, ty + 0.25f, {}); - } - else SETTEXCOORDS(0, 1, 0, 1, {}); - - #define SETCOLOR(r, g, b, a) \ - do { \ - bvec4 col(r, g, b, a); \ - loopi(4) vs[i].color = col; \ - } while(0) - #define SETMODCOLOR SETCOLOR((p->color.r*blend)>>8, (p->color.g*blend)>>8, (p->color.b*blend)>>8, 255) - if(type&PT_MOD) SETMODCOLOR; - else SETCOLOR(p->color.r, p->color.g, p->color.b, blend); - } - else if(type&PT_MOD) SETMODCOLOR; - else loopi(4) vs[i].color.a = blend; - - if(type&PT_ROT) genrotpos(o, d, p->size, ts, p->gravity, vs, (p->flags>>2)&0x1F); - else genpos(o, d, p->size, ts, p->gravity, vs); - } - - void genverts() - { - loopi(numparts) - { - particle *p = &parts[i]; - partvert *vs = &verts[i*4]; - if(p->fade < 0) - { - do - { - --numparts; - if(numparts <= i) return; - } - while(parts[numparts].fade < 0); - *p = parts[numparts]; - genverts(p, vs, true); - } - else genverts(p, vs, (p->flags&0x80)!=0); - } - } - - void update() - { - if(lastmillis == lastupdate && vbo) return; - lastupdate = lastmillis; - - genverts(); - - if(!vbo) glGenBuffers_(1, &vbo); - gle::bindvbo(vbo); - glBufferData_(GL_ARRAY_BUFFER, maxparts*4*sizeof(partvert), NULL, GL_STREAM_DRAW); - glBufferSubData_(GL_ARRAY_BUFFER, 0, numparts*4*sizeof(partvert), verts); - gle::clearvbo(); - } - - void render() - { - if(!tex) tex = textureload(texname, texclamp); - glBindTexture(GL_TEXTURE_2D, tex->id); - - gle::bindvbo(vbo); - const partvert *ptr = 0; - gle::vertexpointer(sizeof(partvert), ptr->pos.v); - gle::texcoord0pointer(sizeof(partvert), ptr->tc.v); - gle::colorpointer(sizeof(partvert), ptr->color.v); - gle::enablevertex(); - gle::enabletexcoord0(); - gle::enablecolor(); - gle::enablequads(); - - gle::drawquads(0, numparts); - - gle::disablequads(); - gle::disablevertex(); - gle::disabletexcoord0(); - gle::disablecolor(); - gle::clearvbo(); - } + partvert *verts; + particle *parts; + int maxparts, numparts, lastupdate, rndmask; + GLuint vbo; + + varenderer(const char *texname, int type, int collide = 0) + : partrenderer(texname, 3, type, collide), + verts(NULL), parts(NULL), maxparts(0), numparts(0), lastupdate(-1), rndmask(0), vbo(0) + { + if(type & PT_HFLIP) rndmask |= 0x01; + if(type & PT_VFLIP) rndmask |= 0x02; + if(type & PT_ROT) rndmask |= 0x1F<<2; + if(type & PT_RND4) rndmask |= 0x03<<5; + } + + void cleanup() + { + if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } + } + + void init(int n) + { + DELETEA(parts); + DELETEA(verts); + parts = new particle[n]; + verts = new partvert[n*4]; + maxparts = n; + numparts = 0; + lastupdate = -1; + } + + void reset() + { + numparts = 0; + lastupdate = -1; + } + + void resettracked(physent *owner) + { + if(!(type&PT_TRACK)) return; + loopi(numparts) + { + particle *p = parts+i; + if(!owner || (p->owner == owner)) p->fade = -1; + } + lastupdate = -1; + } + + int count() + { + return numparts; + } + + bool haswork() + { + return (numparts > 0); + } + + particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) + { + particle *p = parts + (numparts < maxparts ? numparts++ : rnd(maxparts)); //next free slot, or kill a random kitten + p->o = o; + p->d = d; + p->gravity = gravity; + p->fade = fade; + p->millis = lastmillis + emitoffset; + p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF); + p->size = size; + p->owner = NULL; + p->flags = 0x80 | (rndmask ? rnd(0x80) & rndmask : 0); + lastupdate = -1; + return p; + } + + void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) + { + pe.maxfade = max(pe.maxfade, fade); + size *= SQRT2; + pe.extendbb(o, size); + + seedpos(pe, o, d, fade, size, gravity); + if(!gravity) return; + + vec end(o); + float t = fade; + end.add(vec(d).mul(t/5000.0f)); + end.z -= t*t/(2.0f * 5000.0f * gravity); + pe.extendbb(end, size); + + float tpeak = d.z*gravity; + if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size); + } + + void genverts(particle *p, partvert *vs, bool regen) + { + vec o, d; + int blend, ts; + + calc(p, blend, ts, o, d); + if(blend <= 1 || p->fade <= 5) p->fade = -1; //mark to remove on next pass (i.e. after render) + + modifyblend(o, blend); + + if(regen) + { + p->flags &= ~0x80; + + #define SETTEXCOORDS(u1c, u2c, v1c, v2c, body) \ + { \ + float u1 = u1c, u2 = u2c, v1 = v1c, v2 = v2c; \ + body; \ + vs[0].tc = vec2(u1, v1); \ + vs[1].tc = vec2(u2, v1); \ + vs[2].tc = vec2(u2, v2); \ + vs[3].tc = vec2(u1, v2); \ + } + if(type&PT_RND4) + { + float tx = 0.5f*((p->flags>>5)&1), ty = 0.5f*((p->flags>>6)&1); + SETTEXCOORDS(tx, tx + 0.5f, ty, ty + 0.5f, + { + if(p->flags&0x01) swap(u1, u2); + if(p->flags&0x02) swap(v1, v2); + }); + } + else if(type&PT_ICON) + { + float tx = 0.25f*(p->flags&3), ty = 0.25f*((p->flags>>2)&3); + SETTEXCOORDS(tx, tx + 0.25f, ty, ty + 0.25f, {}); + } + else SETTEXCOORDS(0, 1, 0, 1, {}); + + #define SETCOLOR(r, g, b, a) \ + do { \ + bvec4 col(r, g, b, a); \ + loopi(4) vs[i].color = col; \ + } while(0) + #define SETMODCOLOR SETCOLOR((p->color.r*blend)>>8, (p->color.g*blend)>>8, (p->color.b*blend)>>8, 255) + if(type&PT_MOD) SETMODCOLOR; + else SETCOLOR(p->color.r, p->color.g, p->color.b, blend); + } + else if(type&PT_MOD) SETMODCOLOR; + else loopi(4) vs[i].color.a = blend; + + if(type&PT_ROT) genrotpos(o, d, p->size, ts, p->gravity, vs, (p->flags>>2)&0x1F); + else genpos(o, d, p->size, ts, p->gravity, vs); + } + + void genverts() + { + loopi(numparts) + { + particle *p = &parts[i]; + partvert *vs = &verts[i*4]; + if(p->fade < 0) + { + do + { + --numparts; + if(numparts <= i) return; + } + while(parts[numparts].fade < 0); + *p = parts[numparts]; + genverts(p, vs, true); + } + else genverts(p, vs, (p->flags&0x80)!=0); + } + } + + void update() + { + if(lastmillis == lastupdate && vbo) return; + lastupdate = lastmillis; + + genverts(); + + if(!vbo) glGenBuffers_(1, &vbo); + gle::bindvbo(vbo); + glBufferData_(GL_ARRAY_BUFFER, maxparts*4*sizeof(partvert), NULL, GL_STREAM_DRAW); + glBufferSubData_(GL_ARRAY_BUFFER, 0, numparts*4*sizeof(partvert), verts); + gle::clearvbo(); + } + + void render() + { + if(!tex) tex = textureload(texname, texclamp); + glBindTexture(GL_TEXTURE_2D, tex->id); + + gle::bindvbo(vbo); + const partvert *ptr = 0; + gle::vertexpointer(sizeof(partvert), ptr->pos.v); + gle::texcoord0pointer(sizeof(partvert), ptr->tc.v); + gle::colorpointer(sizeof(partvert), ptr->color.v); + gle::enablevertex(); + gle::enabletexcoord0(); + gle::enablecolor(); + gle::enablequads(); + + gle::drawquads(0, numparts); + + gle::disablequads(); + gle::disablevertex(); + gle::disabletexcoord0(); + gle::disablecolor(); + gle::clearvbo(); + } }; typedef varenderer quadrenderer; typedef varenderer taperenderer; @@ -847,92 +844,92 @@ typedef varenderer trailrenderer; struct softquadrenderer : quadrenderer { - softquadrenderer(const char *texname, int type, int collide = 0) - : quadrenderer(texname, type|PT_SOFT, collide) - { - } - - int adddepthfx(vec &bbmin, vec &bbmax) - { - if(!depthfxtex.highprecision() && !depthfxtex.emulatehighprecision()) return 0; - int numsoft = 0; - loopi(numparts) - { - particle &p = parts[i]; - float radius = p.size*SQRT2; - vec o, d; - int blend, ts; - calc(&p, blend, ts, o, d, false); - if(!isfoggedsphere(radius, p.o) && (depthfxscissor!=2 || depthfxtex.addscissorbox(p.o, radius))) - { - numsoft++; - loopk(3) - { - bbmin[k] = min(bbmin[k], o[k] - radius); - bbmax[k] = max(bbmax[k], o[k] + radius); - } - } - } - return numsoft; - } + softquadrenderer(const char *texname, int type, int collide = 0) + : quadrenderer(texname, type|PT_SOFT, collide) + { + } + + int adddepthfx(vec &bbmin, vec &bbmax) + { + if(!depthfxtex.highprecision() && !depthfxtex.emulatehighprecision()) return 0; + int numsoft = 0; + loopi(numparts) + { + particle &p = parts[i]; + float radius = p.size*SQRT2; + vec o, d; + int blend, ts; + calc(&p, blend, ts, o, d, false); + if(!isfoggedsphere(radius, p.o) && (depthfxscissor!=2 || depthfxtex.addscissorbox(p.o, radius))) + { + numsoft++; + loopk(3) + { + bbmin[k] = min(bbmin[k], o[k] - radius); + bbmax[k] = max(bbmax[k], o[k] + radius); + } + } + } + return numsoft; + } }; static partrenderer *parts[] = { - new quadrenderer("packages/particles/blood.png", PT_PART|PT_FLIP|PT_MOD|PT_RND4, DECAL_BLOOD), // blood spats (note: rgb is inverted) - new trailrenderer("packages/particles/base.png", PT_TRAIL|PT_LERP), // water, entity - new quadrenderer("packages/particles/smoke.png", PT_PART|PT_FLIP|PT_LERP), // smoke - new quadrenderer("packages/particles/steam.png", PT_PART|PT_FLIP), // steam - new quadrenderer("packages/particles/flames.png", PT_PART|PT_HFLIP|PT_RND4|PT_GLARE), // flame on - no flipping please, they have orientation - new quadrenderer("packages/particles/ball1.png", PT_PART|PT_FEW|PT_GLARE), // fireball1 - new quadrenderer("packages/particles/ball2.png", PT_PART|PT_FEW|PT_GLARE), // fireball2 - new quadrenderer("packages/particles/ball3.png", PT_PART|PT_FEW|PT_GLARE), // fireball3 - new taperenderer("packages/particles/flare.png", PT_TAPE|PT_GLARE), // streak - &lightnings, // lightning - &fireballs, // explosion fireball - &bluefireballs, // bluish explosion fireball - new quadrenderer("packages/particles/spark.png", PT_PART|PT_FLIP|PT_GLARE), // sparks - new quadrenderer("packages/particles/base.png", PT_PART|PT_FLIP|PT_GLARE), // edit mode entities - new quadrenderer("packages/particles/snow.png", PT_PART|PT_FLIP|PT_RND4, -1), // colliding snow - new quadrenderer("packages/particles/muzzleflash1.png", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash - new quadrenderer("packages/particles/muzzleflash2.png", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash - new quadrenderer("packages/particles/muzzleflash3.png", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash - new quadrenderer("packages/hud/items.png", PT_PART|PT_FEW|PT_ICON), // hud icon - new quadrenderer("packages/hud/items.png", PT_PART|PT_FEW|PT_ICON), // grey hud icon - &texts, // text - &texticons, // text icons - &meters, // meter - &metervs, // meter vs. + new quadrenderer("packages/particles/blood.png", PT_PART|PT_FLIP|PT_MOD|PT_RND4, DECAL_BLOOD), // blood spats (note: rgb is inverted) + new trailrenderer("packages/particles/base.png", PT_TRAIL|PT_LERP), // water, entity + new quadrenderer("packages/particles/smoke.png", PT_PART|PT_FLIP|PT_LERP), // smoke + new quadrenderer("packages/particles/steam.png", PT_PART|PT_FLIP), // steam + new quadrenderer("packages/particles/flames.png", PT_PART|PT_HFLIP|PT_RND4|PT_GLARE), // flame on - no flipping please, they have orientation + new quadrenderer("packages/particles/ball1.png", PT_PART|PT_FEW|PT_GLARE), // fireball1 + new quadrenderer("packages/particles/ball2.png", PT_PART|PT_FEW|PT_GLARE), // fireball2 + new quadrenderer("packages/particles/ball3.png", PT_PART|PT_FEW|PT_GLARE), // fireball3 + new taperenderer("packages/particles/flare.png", PT_TAPE|PT_GLARE), // streak + &lightnings, // lightning + &fireballs, // explosion fireball + &bluefireballs, // bluish explosion fireball + new quadrenderer("packages/particles/spark.png", PT_PART|PT_FLIP|PT_GLARE), // sparks + new quadrenderer("packages/particles/base.png", PT_PART|PT_FLIP|PT_GLARE), // edit mode entities + new quadrenderer("packages/particles/snow.png", PT_PART|PT_FLIP|PT_RND4, -1), // colliding snow + new quadrenderer("packages/particles/muzzleflash1.png", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash + new quadrenderer("packages/particles/muzzleflash2.png", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash + new quadrenderer("packages/particles/muzzleflash3.png", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash + new quadrenderer("packages/hud/items.png", PT_PART|PT_FEW|PT_ICON), // hud icon + new quadrenderer("packages/hud/items.png", PT_PART|PT_FEW|PT_ICON), // grey hud icon + &texts, // text + &texticons, // text icons + &meters, // meter + &metervs, // meter vs. }; void finddepthfxranges() { - depthfxmin = vec(1e16f, 1e16f, 1e16f); - depthfxmax = vec(0, 0, 0); - numdepthfxranges = fireballs.finddepthfxranges(depthfxowners, depthfxranges, 0, MAXDFXRANGES, depthfxmin, depthfxmax); - numdepthfxranges = bluefireballs.finddepthfxranges(depthfxowners, depthfxranges, numdepthfxranges, MAXDFXRANGES, depthfxmin, depthfxmax); - loopk(3) - { - depthfxmin[k] -= depthfxmargin; - depthfxmax[k] += depthfxmargin; - } - if(depthfxparts) - { - loopi(sizeof(parts)/sizeof(parts[0])) - { - partrenderer *p = parts[i]; - if(p->type&PT_SOFT && p->adddepthfx(depthfxmin, depthfxmax)) - { - if(!numdepthfxranges) - { - numdepthfxranges = 1; - depthfxowners[0] = NULL; - depthfxranges[0] = 0; - } - } - } - } - if(depthfxscissor<2 && numdepthfxranges>0) depthfxtex.addscissorbox(depthfxmin, depthfxmax); + depthfxmin = vec(1e16f, 1e16f, 1e16f); + depthfxmax = vec(0, 0, 0); + numdepthfxranges = fireballs.finddepthfxranges(depthfxowners, depthfxranges, 0, MAXDFXRANGES, depthfxmin, depthfxmax); + numdepthfxranges = bluefireballs.finddepthfxranges(depthfxowners, depthfxranges, numdepthfxranges, MAXDFXRANGES, depthfxmin, depthfxmax); + loopk(3) + { + depthfxmin[k] -= depthfxmargin; + depthfxmax[k] += depthfxmargin; + } + if(depthfxparts) + { + loopi(sizeof(parts)/sizeof(parts[0])) + { + partrenderer *p = parts[i]; + if(p->type&PT_SOFT && p->adddepthfx(depthfxmin, depthfxmax)) + { + if(!numdepthfxranges) + { + numdepthfxranges = 1; + depthfxowners[0] = NULL; + depthfxranges[0] = 0; + } + } + } + } + if(depthfxscissor<2 && numdepthfxranges>0) depthfxtex.addscissorbox(depthfxmin, depthfxmax); } VARFP(maxparticles, 10, 4000, 40000, initparticles()); @@ -940,185 +937,185 @@ VARFP(fewparticles, 10, 100, 40000, initparticles()); void initparticles() { - if(!particleshader) particleshader = lookupshaderbyname("particle"); - if(!particlenotextureshader) particlenotextureshader = lookupshaderbyname("particlenotexture"); - loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->init(parts[i]->type&PT_FEW ? min(fewparticles, maxparticles) : maxparticles); + if(!particleshader) particleshader = lookupshaderbyname("particle"); + if(!particlenotextureshader) particlenotextureshader = lookupshaderbyname("particlenotexture"); + loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->init(parts[i]->type&PT_FEW ? min(fewparticles, maxparticles) : maxparticles); } void clearparticles() { - loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->reset(); - clearparticleemitters(); + loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->reset(); + clearparticleemitters(); } void cleanupparticles() { - loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->cleanup(); + loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->cleanup(); } void removetrackedparticles(physent *owner) { - loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->resettracked(owner); + loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->resettracked(owner); } VARP(particleglare, 0, 2, 100); void renderparticles(bool mainpass) { - canstep = mainpass; - - if(glaring && !particleglare) return; - - loopi(sizeof(parts)/sizeof(parts[0])) - { - if(glaring && !(parts[i]->type&PT_GLARE)) continue; - parts[i]->update(); - } - - bool rendered = false; - uint lastflags = PT_LERP|PT_SHADER, - flagmask = PT_LERP|PT_MOD|PT_SHADER|PT_NOTEX; - - if(binddepthfxtex()) flagmask |= PT_SOFT; - - loopi(sizeof(parts)/sizeof(parts[0])) - { - partrenderer *p = parts[i]; - if(glaring && !(p->type&PT_GLARE)) continue; - if(!p->haswork()) continue; - - if(!rendered) - { - rendered = true; - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - if(glaring) GLOBALPARAMF(colorscale, particleglare, particleglare, particleglare, 1); - else GLOBALPARAMF(colorscale, 1, 1, 1, 1); - } - - uint flags = p->type & flagmask, changedbits = (flags ^ lastflags); - if(changedbits) - { - if(changedbits&PT_LERP) - { - if(flags&PT_LERP) resetfogcolor(); - else zerofogcolor(); - } - if(changedbits&(PT_LERP|PT_MOD)) - { - if(flags&PT_LERP) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - else if(flags&PT_MOD) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); - else glBlendFunc(GL_SRC_ALPHA, GL_ONE); - } - if(!(flags&PT_SHADER)) - { - if(changedbits&(PT_SOFT|PT_SHADER|PT_NOTEX|PT_LERP)) - { - if(flags&PT_SOFT) - { - if(!depthfxtex.highprecision()) SETSHADER(particlesoft8); - else SETSHADER(particlesoft); - - binddepthfxparams(depthfxpartblend); - } - else if(flags&PT_NOTEX) particlenotextureshader->set(); - else particleshader->set(); - } - } - lastflags = flags; - } - p->render(); - } - - if(rendered) - { - if(lastflags&(PT_LERP|PT_MOD)) glBlendFunc(GL_SRC_ALPHA, GL_ONE); - if(!(lastflags&PT_LERP)) resetfogcolor(); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); - } + canstep = mainpass; + + if(glaring && !particleglare) return; + + loopi(sizeof(parts)/sizeof(parts[0])) + { + if(glaring && !(parts[i]->type&PT_GLARE)) continue; + parts[i]->update(); + } + + bool rendered = false; + uint lastflags = PT_LERP|PT_SHADER, + flagmask = PT_LERP|PT_MOD|PT_SHADER|PT_NOTEX; + + if(binddepthfxtex()) flagmask |= PT_SOFT; + + loopi(sizeof(parts)/sizeof(parts[0])) + { + partrenderer *p = parts[i]; + if(glaring && !(p->type&PT_GLARE)) continue; + if(!p->haswork()) continue; + + if(!rendered) + { + rendered = true; + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if(glaring) GLOBALPARAMF(colorscale, particleglare, particleglare, particleglare, 1); + else GLOBALPARAMF(colorscale, 1, 1, 1, 1); + } + + uint flags = p->type & flagmask, changedbits = (flags ^ lastflags); + if(changedbits) + { + if(changedbits&PT_LERP) + { + if(flags&PT_LERP) resetfogcolor(); + else zerofogcolor(); + } + if(changedbits&(PT_LERP|PT_MOD)) + { + if(flags&PT_LERP) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + else if(flags&PT_MOD) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + else glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + if(!(flags&PT_SHADER)) + { + if(changedbits&(PT_SOFT|PT_SHADER|PT_NOTEX|PT_LERP)) + { + if(flags&PT_SOFT) + { + if(!depthfxtex.highprecision()) SETSHADER(particlesoft8); + else SETSHADER(particlesoft); + + binddepthfxparams(depthfxpartblend); + } + else if(flags&PT_NOTEX) particlenotextureshader->set(); + else particleshader->set(); + } + } + lastflags = flags; + } + p->render(); + } + + if(rendered) + { + if(lastflags&(PT_LERP|PT_MOD)) glBlendFunc(GL_SRC_ALPHA, GL_ONE); + if(!(lastflags&PT_LERP)) resetfogcolor(); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + } } static int addedparticles = 0; static inline particle *newparticle(const vec &o, const vec &d, int fade, int type, int color, float size, int gravity = 0) { - static particle dummy; - if(seedemitter) - { - parts[type]->seedemitter(*seedemitter, o, d, fade, size, gravity); - return &dummy; - } - if(fade + emitoffset < 0) return &dummy; - addedparticles++; - return parts[type]->addpart(o, d, fade, color, size, gravity); + static particle dummy; + if(seedemitter) + { + parts[type]->seedemitter(*seedemitter, o, d, fade, size, gravity); + return &dummy; + } + if(fade + emitoffset < 0) return &dummy; + addedparticles++; + return parts[type]->addpart(o, d, fade, color, size, gravity); } VARP(maxparticledistance, 256, 1024, 4096); static void splash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity) { - if(camera1->o.dist(p) > maxparticledistance && !seedemitter) return; - float collidez = parts[type]->collide ? p.z - raycube(p, vec(0, 0, -1), COLLIDERADIUS, RAY_CLIPMAT) + (parts[type]->collide >= 0 ? COLLIDEERROR : 0) : -1; - int fmin = 1; - int fmax = fade*3; - loopi(num) - { - int x, y, z; - do - { - x = rnd(radius*2)-radius; - y = rnd(radius*2)-radius; - z = rnd(radius*2)-radius; - } - while(x*x+y*y+z*z>radius*radius); - vec tmp = vec((float)x, (float)y, (float)z); - int f = (num < 10) ? (fmin + rnd(fmax)) : (fmax - (i*(fmax-fmin))/(num-1)); //help deallocater by using fade distribution rather than random - newparticle(p, tmp, f, type, color, size, gravity)->val = collidez; - } + if(camera1->o.dist(p) > maxparticledistance && !seedemitter) return; + float collidez = parts[type]->collide ? p.z - raycube(p, vec(0, 0, -1), COLLIDERADIUS, RAY_CLIPMAT) + (parts[type]->collide >= 0 ? COLLIDEERROR : 0) : -1; + int fmin = 1; + int fmax = fade*3; + loopi(num) + { + int x, y, z; + do + { + x = rnd(radius*2)-radius; + y = rnd(radius*2)-radius; + z = rnd(radius*2)-radius; + } + while(x*x+y*y+z*z>radius*radius); + vec tmp = vec((float)x, (float)y, (float)z); + int f = (num < 10) ? (fmin + rnd(fmax)) : (fmax - (i*(fmax-fmin))/(num-1)); //help deallocater by using fade distribution rather than random + newparticle(p, tmp, f, type, color, size, gravity)->val = collidez; + } } static void regularsplash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity, int delay = 0) { - if(!canemitparticles() || (delay > 0 && rnd(delay) != 0)) return; - splash(type, color, radius, num, fade, p, size, gravity); + if(!canemitparticles() || (delay > 0 && rnd(delay) != 0)) return; + splash(type, color, radius, num, fade, p, size, gravity); } bool canaddparticles() { - return !renderedgame && !shadowmapping && !minimized; + return !renderedgame && !shadowmapping && !minimized; } void regular_particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity, int delay) { - if(!canaddparticles()) return; - regularsplash(type, color, radius, num, fade, p, size, gravity, delay); + if(!canaddparticles()) return; + regularsplash(type, color, radius, num, fade, p, size, gravity, delay); } void particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity) { - if(!canaddparticles()) return; - splash(type, color, radius, num, fade, p, size, gravity); + if(!canaddparticles()) return; + splash(type, color, radius, num, fade, p, size, gravity); } VARP(maxtrail, 1, 500, 10000); void particle_trail(int type, int fade, const vec &s, const vec &e, int color, float size, int gravity) { - if(!canaddparticles()) return; - vec v; - float d = e.dist(s, v); - int steps = clamp(int(d*2), 1, maxtrail); - v.div(steps); - vec p = s; - loopi(steps) - { - p.add(v); - vec tmp = vec(float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5)); - newparticle(p, tmp, rnd(fade)+fade, type, color, size, gravity); - } + if(!canaddparticles()) return; + vec v; + float d = e.dist(s, v); + int steps = clamp(int(d*2), 1, maxtrail); + v.div(steps); + vec p = s; + loopi(steps) + { + p.add(v); + vec tmp = vec(float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5)); + newparticle(p, tmp, rnd(fade)+fade, type, color, size, gravity); + } } VARP(particletext, 0, 1, 1); @@ -1126,74 +1123,74 @@ VARP(maxparticletextdistance, 0, 128, 10000); void particle_text(const vec &s, const char *t, int type, int fade, int color, float size, int gravity, int icons) { - if(!canaddparticles()) return; - if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; - particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); - p->text = t; - p->flags = icons<<1; + if(!canaddparticles()) return; + if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; + particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); + p->text = t; + p->flags = icons<<1; } void particle_textcopy(const vec &s, const char *t, int type, int fade, int color, float size, int gravity) { - if(!canaddparticles()) return; - if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; - particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); - p->text = newstring(t); - p->flags = 1; + if(!canaddparticles()) return; + if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; + particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); + p->text = newstring(t); + p->flags = 1; } void particle_texticon(const vec &s, int ix, int iy, float offset, int type, int fade, int color, float size, int gravity) { - if(!canaddparticles()) return; - if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; - particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); - p->flags |= ix | (iy<<2); - p->val = offset; + if(!canaddparticles()) return; + if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return; + particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); + p->flags |= ix | (iy<<2); + p->val = offset; } void particle_icon(const vec &s, int ix, int iy, int type, int fade, int color, float size, int gravity) { - if(!canaddparticles()) return; - particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); - p->flags |= ix | (iy<<2); + if(!canaddparticles()) return; + particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity); + p->flags |= ix | (iy<<2); } void particle_meter(const vec &s, float val, int type, int fade, int color, int color2, float size) { - if(!canaddparticles()) return; - particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size); - p->color2[0] = color2>>16; - p->color2[1] = (color2>>8)&0xFF; - p->color2[2] = color2&0xFF; - p->progress = clamp(int(val*100), 0, 100); + if(!canaddparticles()) return; + particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size); + p->color2[0] = color2>>16; + p->color2[1] = (color2>>8)&0xFF; + p->color2[2] = color2&0xFF; + p->progress = clamp(int(val*100), 0, 100); } void particle_flare(const vec &p, const vec &dest, int fade, int type, int color, float size, physent *owner) { - if(!canaddparticles()) return; - newparticle(p, dest, fade, type, color, size)->owner = owner; + if(!canaddparticles()) return; + newparticle(p, dest, fade, type, color, size)->owner = owner; } void particle_fireball(const vec &dest, float maxsize, int type, int fade, int color, float size) { - if(!canaddparticles()) return; - float growth = maxsize - size; - if(fade < 0) fade = int(growth*20); - newparticle(dest, vec(0, 0, 1), fade, type, color, size)->val = growth; + if(!canaddparticles()) return; + float growth = maxsize - size; + if(fade < 0) fade = int(growth*20); + newparticle(dest, vec(0, 0, 1), fade, type, color, size)->val = growth; } //dir = 0..6 where 0=up static inline vec offsetvec(vec o, int dir, int dist) { - vec v = vec(o); - v[(2+dir)%3] += (dir>2)?(-dist):dist; - return v; + vec v = vec(o); + v[(2+dir)%3] += (dir>2)?(-dist):dist; + return v; } //converts a 16bit color to 24bit static inline int colorfromattr(int attr) { - return (((attr&0xF)<<4) | ((attr&0xF0)<<8) | ((attr&0xF00)<<12)) + 0x0F0F0F; + return (((attr&0xF)<<4) | ((attr&0xF0)<<8) | ((attr&0xF00)<<12)) + 0x0F0F0F; } /* Experiments in shapes... @@ -1209,300 +1206,299 @@ static inline int colorfromattr(int attr) */ void regularshape(int type, int radius, int color, int dir, int num, int fade, const vec &p, float size, int gravity, int vel = 200) { - if(!canemitparticles()) return; - - int basetype = parts[type]->type&0xFF; - bool flare = (basetype == PT_TAPE) || (basetype == PT_LIGHTNING), - inv = (dir&0x20)!=0, taper = (dir&0x40)!=0 && !seedemitter; - dir &= 0x1F; - loopi(num) - { - vec to, from; - if(dir < 12) - { - const vec2 &sc = sincos360[rnd(360)]; - to[dir%3] = sc.y*radius; - to[(dir+1)%3] = sc.x*radius; - to[(dir+2)%3] = 0.0; - to.add(p); - if(dir < 3) //circle - from = p; - else if(dir < 6) //cylinder - { - from = to; - to[(dir+2)%3] += radius; - from[(dir+2)%3] -= radius; - } - else //cone - { - from = p; - to[(dir+2)%3] += (dir < 9)?radius:(-radius); - } - } - else if(dir < 15) //plane - { - to[dir%3] = float(rnd(radius<<4)-(radius<<3))/8.0; - to[(dir+1)%3] = float(rnd(radius<<4)-(radius<<3))/8.0; - to[(dir+2)%3] = radius; - to.add(p); - from = to; - from[(dir+2)%3] -= 2*radius; - } - else if(dir < 21) //line - { - if(dir < 18) - { - to[dir%3] = float(rnd(radius<<4)-(radius<<3))/8.0; - to[(dir+1)%3] = 0.0; - } - else - { - to[dir%3] = 0.0; - to[(dir+1)%3] = float(rnd(radius<<4)-(radius<<3))/8.0; - } - to[(dir+2)%3] = 0.0; - to.add(p); - from = to; - to[(dir+2)%3] += radius; - } - else if(dir < 24) //sphere - { - to = vec(2*M_PI*float(rnd(1000))/1000.0, M_PI*float(rnd(1000)-500)/1000.0).mul(radius); - to.add(p); - from = p; - } - else if(dir < 27) // flat plane - { - to[dir%3] = float(rndscale(2*radius)-radius); - to[(dir+1)%3] = float(rndscale(2*radius)-radius); - to[(dir+2)%3] = 0.0; - to.add(p); - from = to; - } - else from = to = p; - - if(inv) swap(from, to); - - if(taper) - { - float dist = clamp(from.dist2(camera1->o)/maxparticledistance, 0.0f, 1.0f); - if(dist > 0.2f) - { - dist = 1 - (dist - 0.2f)/0.8f; - if(rnd(0x10000) > dist*dist*0xFFFF) continue; - } - } - - if(flare) - newparticle(from, to, rnd(fade*3)+1, type, color, size, gravity); - else - { - vec d = vec(to).sub(from).rescale(vel); //velocity - particle *n = newparticle(from, d, rnd(fade*3)+1, type, color, size, gravity); - if(parts[type]->collide) - n->val = from.z - raycube(from, vec(0, 0, -1), parts[type]->collide >= 0 ? COLLIDERADIUS : max(from.z, 0.0f), RAY_CLIPMAT) + (parts[type]->collide >= 0 ? COLLIDEERROR : 0); - } - } + if(!canemitparticles()) return; + + int basetype = parts[type]->type&0xFF; + bool flare = (basetype == PT_TAPE) || (basetype == PT_LIGHTNING), + inv = (dir&0x20)!=0, taper = (dir&0x40)!=0 && !seedemitter; + dir &= 0x1F; + loopi(num) + { + vec to, from; + if(dir < 12) + { + const vec2 &sc = sincos360[rnd(360)]; + to[dir%3] = sc.y*radius; + to[(dir+1)%3] = sc.x*radius; + to[(dir+2)%3] = 0.0; + to.add(p); + if(dir < 3) //circle + from = p; + else if(dir < 6) //cylinder + { + from = to; + to[(dir+2)%3] += radius; + from[(dir+2)%3] -= radius; + } + else //cone + { + from = p; + to[(dir+2)%3] += (dir < 9)?radius:(-radius); + } + } + else if(dir < 15) //plane + { + to[dir%3] = float(rnd(radius<<4)-(radius<<3))/8.0; + to[(dir+1)%3] = float(rnd(radius<<4)-(radius<<3))/8.0; + to[(dir+2)%3] = radius; + to.add(p); + from = to; + from[(dir+2)%3] -= 2*radius; + } + else if(dir < 21) //line + { + if(dir < 18) + { + to[dir%3] = float(rnd(radius<<4)-(radius<<3))/8.0; + to[(dir+1)%3] = 0.0; + } + else + { + to[dir%3] = 0.0; + to[(dir+1)%3] = float(rnd(radius<<4)-(radius<<3))/8.0; + } + to[(dir+2)%3] = 0.0; + to.add(p); + from = to; + to[(dir+2)%3] += radius; + } + else if(dir < 24) //sphere + { + to = vec(2*M_PI*float(rnd(1000))/1000.0, M_PI*float(rnd(1000)-500)/1000.0).mul(radius); + to.add(p); + from = p; + } + else if(dir < 27) // flat plane + { + to[dir%3] = float(rndscale(2*radius)-radius); + to[(dir+1)%3] = float(rndscale(2*radius)-radius); + to[(dir+2)%3] = 0.0; + to.add(p); + from = to; + } + else from = to = p; + + if(inv) swap(from, to); + + if(taper) + { + float dist = clamp(from.dist2(camera1->o)/maxparticledistance, 0.0f, 1.0f); + if(dist > 0.2f) + { + dist = 1 - (dist - 0.2f)/0.8f; + if(rnd(0x10000) > dist*dist*0xFFFF) continue; + } + } + + if(flare) + newparticle(from, to, rnd(fade*3)+1, type, color, size, gravity); + else + { + vec d = vec(to).sub(from).rescale(vel); //velocity + particle *n = newparticle(from, d, rnd(fade*3)+1, type, color, size, gravity); + if(parts[type]->collide) + n->val = from.z - raycube(from, vec(0, 0, -1), parts[type]->collide >= 0 ? COLLIDERADIUS : max(from.z, 0.0f), RAY_CLIPMAT) + (parts[type]->collide >= 0 ? COLLIDEERROR : 0); + } + } } static void regularflame(int type, const vec &p, float radius, float height, int color, int density = 3, float scale = 2.0f, float speed = 200.0f, float fade = 600.0f, int gravity = -15) { - if(!canemitparticles()) return; - - float size = scale * min(radius, height); - vec v(0, 0, min(1.0f, height)*speed); - loopi(density) - { - vec s = p; - s.x += rndscale(radius*2.0f)-radius; - s.y += rndscale(radius*2.0f)-radius; - newparticle(s, v, rnd(max(int(fade*height), 1))+1, type, color, size, gravity); - } + if(!canemitparticles()) return; + + float size = scale * min(radius, height); + vec v(0, 0, min(1.0f, height)*speed); + loopi(density) + { + vec s = p; + s.x += rndscale(radius*2.0f)-radius; + s.y += rndscale(radius*2.0f)-radius; + newparticle(s, v, rnd(max(int(fade*height), 1))+1, type, color, size, gravity); + } } void regular_particle_flame(int type, const vec &p, float radius, float height, int color, int density, float scale, float speed, float fade, int gravity) { - if(!canaddparticles()) return; - regularflame(type, p, radius, height, color, density, scale, speed, fade, gravity); + if(!canaddparticles()) return; + regularflame(type, p, radius, height, color, density, scale, speed, fade, gravity); } static void makeparticles(entity &e) { - switch(e.attr1) - { - case 0: //fire and smoke - - 0 values default to compat for old maps - { - //regularsplash(PART_FIREBALL1, 0xFFC8C8, 150, 1, 40, e.o, 4.8f); - //regularsplash(PART_SMOKE, 0x897661, 50, 1, 200, vec(e.o.x, e.o.y, e.o.z+3.0f), 2.4f, -20, 3); - float radius = e.attr2 ? float(e.attr2)/100.0f : 1.5f, - height = e.attr3 ? float(e.attr3)/100.0f : radius/3; - regularflame(PART_FLAME, e.o, radius, height, e.attr4 ? colorfromattr(e.attr4) : 0x903020, 3, 2.0f); - regularflame(PART_SMOKE, vec(e.o.x, e.o.y, e.o.z + 4.0f*min(radius, height)), radius, height, 0x303020, 1, 4.0f, 100.0f, 2000.0f, -20); - break; - } - case 1: //steam vent - - regularsplash(PART_STEAM, 0x897661, 50, 1, 200, offsetvec(e.o, e.attr2, rnd(10)), 2.4f, -20); - break; - case 2: //water fountain - - { - int color; - if(e.attr3 > 0) color = colorfromattr(e.attr3); - else - { - int mat = MAT_WATER + clamp(-e.attr3, 0, 3); - const bvec &wfcol = getwaterfallcolor(mat); - color = (int(wfcol[0])<<16) | (int(wfcol[1])<<8) | int(wfcol[2]); - if(!color) - { - const bvec &wcol = getwatercolor(mat); - color = (int(wcol[0])<<16) | (int(wcol[1])<<8) | int(wcol[2]); - } - } - regularsplash(PART_WATER, color, 150, 4, 200, offsetvec(e.o, e.attr2, rnd(10)), 0.6f, 2); - break; - } - case 3: //fire ball - - newparticle(e.o, vec(0, 0, 1), 1, PART_EXPLOSION, colorfromattr(e.attr3), 4.0f)->val = 1+e.attr2; - break; - case 4: //tape - - case 7: //lightning - case 9: //steam - case 10: //water - case 13: //snow - { - static const int typemap[] = { PART_STREAK, -1, -1, PART_LIGHTNING, -1, PART_STEAM, PART_WATER, -1, -1, PART_SNOW }; - static const float sizemap[] = { 0.28f, 0.0f, 0.0f, 1.0f, 0.0f, 2.4f, 0.60f, 0.0f, 0.0f, 0.5f }; - static const int gravmap[] = { 0, 0, 0, 0, 0, -20, 2, 0, 0, 20 }; - int type = typemap[e.attr1-4]; - float size = sizemap[e.attr1-4]; - int gravity = gravmap[e.attr1-4]; - if(e.attr2 >= 256) regularshape(type, max(1+e.attr3, 1), colorfromattr(e.attr4), e.attr2-256, 5, e.attr5 > 0 ? min(int(e.attr5), 10000) : 200, e.o, size, gravity); - else newparticle(e.o, offsetvec(e.o, e.attr2, max(1+e.attr3, 0)), 1, type, colorfromattr(e.attr4), size, gravity); - break; - } - case 5: //meter, metervs - - case 6: - { - particle *p = newparticle(e.o, vec(0, 0, 1), 1, e.attr1==5 ? PART_METER : PART_METER_VS, colorfromattr(e.attr3), 2.0f); - int color2 = colorfromattr(e.attr4); - p->color2[0] = color2>>16; - p->color2[1] = (color2>>8)&0xFF; - p->color2[2] = color2&0xFF; - p->progress = clamp(int(e.attr2), 0, 100); - break; - } - case 11: // flame - radius=100, height=100 is the classic size - regularflame(PART_FLAME, e.o, float(e.attr2)/100.0f, float(e.attr3)/100.0f, colorfromattr(e.attr4), 3, 2.0f); - break; - case 12: // smoke plume - regularflame(PART_SMOKE, e.o, float(e.attr2)/100.0f, float(e.attr3)/100.0f, colorfromattr(e.attr4), 1, 4.0f, 100.0f, 2000.0f, -20); - break; - case 32: //lens flares - plain/sparkle/sun/sparklesun - case 33: - case 34: - case 35: - break; - default: - if(!editmode) - { - defformatstring(ds, "particles %d?", e.attr1); - particle_textcopy(e.o, ds, PART_TEXT, 1, 0x6496FF, 2.0f); - } - break; - } + switch(e.attr1) + { + case 0: //fire and smoke - - 0 values default to compat for old maps + { + //regularsplash(PART_FIREBALL1, 0xFFC8C8, 150, 1, 40, e.o, 4.8f); + //regularsplash(PART_SMOKE, 0x897661, 50, 1, 200, vec(e.o.x, e.o.y, e.o.z+3.0f), 2.4f, -20, 3); + float radius = e.attr2 ? float(e.attr2)/100.0f : 1.5f, + height = e.attr3 ? float(e.attr3)/100.0f : radius/3; + regularflame(PART_FLAME, e.o, radius, height, e.attr4 ? colorfromattr(e.attr4) : 0x903020, 3, 2.0f); + regularflame(PART_SMOKE, vec(e.o.x, e.o.y, e.o.z + 4.0f*min(radius, height)), radius, height, 0x303020, 1, 4.0f, 100.0f, 2000.0f, -20); + break; + } + case 1: //steam vent - + regularsplash(PART_STEAM, 0x897661, 50, 1, 200, offsetvec(e.o, e.attr2, rnd(10)), 2.4f, -20); + break; + case 2: //water fountain - + { + int color; + if(e.attr3 > 0) color = colorfromattr(e.attr3); + else + { + int mat = MAT_WATER + clamp(-e.attr3, 0, 3); + const bvec &wfcol = getwaterfallcolor(mat); + color = (int(wfcol[0])<<16) | (int(wfcol[1])<<8) | int(wfcol[2]); + if(!color) + { + const bvec &wcol = getwatercolor(mat); + color = (int(wcol[0])<<16) | (int(wcol[1])<<8) | int(wcol[2]); + } + } + regularsplash(PART_WATER, color, 150, 4, 200, offsetvec(e.o, e.attr2, rnd(10)), 0.6f, 2); + break; + } + case 3: //fire ball - + newparticle(e.o, vec(0, 0, 1), 1, PART_EXPLOSION, colorfromattr(e.attr3), 4.0f)->val = 1+e.attr2; + break; + case 4: //tape - + case 7: //lightning + case 9: //steam + case 10: //water + case 13: //snow + { + static const int typemap[] = { PART_STREAK, -1, -1, PART_LIGHTNING, -1, PART_STEAM, PART_WATER, -1, -1, PART_SNOW }; + static const float sizemap[] = { 0.28f, 0.0f, 0.0f, 1.0f, 0.0f, 2.4f, 0.60f, 0.0f, 0.0f, 0.5f }; + static const int gravmap[] = { 0, 0, 0, 0, 0, -20, 2, 0, 0, 20 }; + int type = typemap[e.attr1-4]; + float size = sizemap[e.attr1-4]; + int gravity = gravmap[e.attr1-4]; + if(e.attr2 >= 256) regularshape(type, max(1+e.attr3, 1), colorfromattr(e.attr4), e.attr2-256, 5, e.attr5 > 0 ? min(int(e.attr5), 10000) : 200, e.o, size, gravity); + else newparticle(e.o, offsetvec(e.o, e.attr2, max(1+e.attr3, 0)), 1, type, colorfromattr(e.attr4), size, gravity); + break; + } + case 5: //meter, metervs - + case 6: + { + particle *p = newparticle(e.o, vec(0, 0, 1), 1, e.attr1==5 ? PART_METER : PART_METER_VS, colorfromattr(e.attr3), 2.0f); + int color2 = colorfromattr(e.attr4); + p->color2[0] = color2>>16; + p->color2[1] = (color2>>8)&0xFF; + p->color2[2] = color2&0xFF; + p->progress = clamp(int(e.attr2), 0, 100); + break; + } + case 11: // flame - radius=100, height=100 is the classic size + regularflame(PART_FLAME, e.o, float(e.attr2)/100.0f, float(e.attr3)/100.0f, colorfromattr(e.attr4), 3, 2.0f); + break; + case 12: // smoke plume + regularflame(PART_SMOKE, e.o, float(e.attr2)/100.0f, float(e.attr3)/100.0f, colorfromattr(e.attr4), 1, 4.0f, 100.0f, 2000.0f, -20); + break; + case 32: //lens flares - plain/sparkle/sun/sparklesun + case 33: + case 34: + case 35: + break; + default: + if(!editmode) + { + defformatstring(ds, "particles %d?", e.attr1); + particle_textcopy(e.o, ds, PART_TEXT, 1, 0x6496FF, 2.0f); + } + break; + } } bool printparticles(extentity &e, char *buf, int len) { - switch(e.attr1) - { - case 0: case 4: case 7: case 8: case 9: case 10: case 11: case 12: case 13: - nformatstring(buf, len, "%s %d %d %d 0x%.3hX %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); - return true; - case 3: - nformatstring(buf, len, "%s %d %d 0x%.3hX %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); - return true; - case 5: case 6: - nformatstring(buf, len, "%s %d %d 0x%.3hX 0x%.3hX %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); - return true; - } - return false; + switch(e.attr1) + { + case 0: case 4: case 7: case 8: case 9: case 10: case 11: case 12: case 13: + nformatstring(buf, len, "%s %d %d %d 0x%.3hX %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); + return true; + case 3: + nformatstring(buf, len, "%s %d %d 0x%.3hX %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); + return true; + case 5: case 6: + nformatstring(buf, len, "%s %d %d 0x%.3hX 0x%.3hX %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); + return true; + } + return false; } void seedparticles() { - renderprogress(0, "seeding particles"); - addparticleemitters(); - canemit = true; - loopv(emitters) - { - particleemitter &pe = emitters[i]; - extentity &e = *pe.ent; - seedemitter = &pe; - for(int millis = 0; millis < seedmillis; millis += min(emitmillis, seedmillis/10)) - makeparticles(e); - seedemitter = NULL; - pe.lastemit = -seedmillis; - pe.finalize(); - } + renderprogress(0, "seeding particles"); + addparticleemitters(); + canemit = true; + loopv(emitters) + { + particleemitter &pe = emitters[i]; + extentity &e = *pe.ent; + seedemitter = &pe; + for(int millis = 0; millis < seedmillis; millis += min(emitmillis, seedmillis/10)) + makeparticles(e); + seedemitter = NULL; + pe.lastemit = -seedmillis; + pe.finalize(); + } } void updateparticles() { - if(regenemitters) addparticleemitters(); - - if(minimized) { canemit = false; return; } - - if(lastmillis - lastemitframe >= emitmillis) - { - canemit = true; - lastemitframe = lastmillis - (lastmillis%emitmillis); - } - else canemit = false; - - if(!editmode || showparticles) - { - int emitted = 0, replayed = 0; - addedparticles = 0; - loopv(emitters) - { - particleemitter &pe = emitters[i]; - extentity &e = *pe.ent; - if(e.o.dist(camera1->o) > maxparticledistance) { pe.lastemit = lastmillis; continue; } - if(cullparticles && pe.maxfade >= 0) - { - if(isfoggedsphere(pe.radius, pe.center)) { pe.lastcull = lastmillis; continue; } - } - makeparticles(e); - emitted++; - if(replayparticles && pe.maxfade > 5 && pe.lastcull > pe.lastemit) - { - for(emitoffset = max(pe.lastemit + emitmillis - lastmillis, -pe.maxfade); emitoffset < 0; emitoffset += emitmillis) - { - makeparticles(e); - replayed++; - } - emitoffset = 0; - } - pe.lastemit = lastmillis; - } - if(dbgpcull && (canemit || replayed) && addedparticles) conoutf(CON_DEBUG, "%d emitters, %d particles", emitted, addedparticles); - } - if(editmode) // show sparkly thingies for map entities in edit mode - { - const vector &ents = entities::getents(); - // note: order matters in this case as particles of the same type are drawn in the reverse order that they are added - loopv(entgroup) - { - entity &e = *ents[entgroup[i]]; - particle_textcopy(e.o, entname(e), PART_TEXT, 1, 0xFF4B19, 2.0f); - } - loopv(ents) - { - entity &e = *ents[i]; - if(e.type==ET_EMPTY) continue; - particle_textcopy(e.o, entname(e), PART_TEXT, 1, 0x1EC850, 2.0f); - regular_particle_splash(PART_EDIT, 2, 40, e.o, 0x3232FF, 0.32f*particlesize/100.0f); - } - } + if(regenemitters) addparticleemitters(); + + if(minimized) { canemit = false; return; } + + if(lastmillis - lastemitframe >= emitmillis) + { + canemit = true; + lastemitframe = lastmillis - (lastmillis%emitmillis); + } + else canemit = false; + + if(!editmode || showparticles) + { + int emitted = 0, replayed = 0; + addedparticles = 0; + loopv(emitters) + { + particleemitter &pe = emitters[i]; + extentity &e = *pe.ent; + if(e.o.dist(camera1->o) > maxparticledistance) { pe.lastemit = lastmillis; continue; } + if(cullparticles && pe.maxfade >= 0) + { + if(isfoggedsphere(pe.radius, pe.center)) { pe.lastcull = lastmillis; continue; } + } + makeparticles(e); + emitted++; + if(replayparticles && pe.maxfade > 5 && pe.lastcull > pe.lastemit) + { + for(emitoffset = max(pe.lastemit + emitmillis - lastmillis, -pe.maxfade); emitoffset < 0; emitoffset += emitmillis) + { + makeparticles(e); + replayed++; + } + emitoffset = 0; + } + pe.lastemit = lastmillis; + } + } + if(editmode) // show sparkly thingies for map entities in edit mode + { + const vector &ents = entities::getents(); + // note: order matters in this case as particles of the same type are drawn in the reverse order that they are added + loopv(entgroup) + { + entity &e = *ents[entgroup[i]]; + particle_textcopy(e.o, entname(e), PART_TEXT, 1, 0xFF4B19, 2.0f); + } + loopv(ents) + { + entity &e = *ents[i]; + if(e.type==ET_EMPTY) continue; + particle_textcopy(e.o, entname(e), PART_TEXT, 1, 0x1EC850, 2.0f); + regular_particle_splash(PART_EDIT, 2, 40, e.o, 0x3232FF, 0.32f*particlesize/100.0f); + } + } } diff --git a/src/engine/rendersky.cpp b/src/engine/rendersky.cpp index 32ca947..458befb 100644 --- a/src/engine/rendersky.cpp +++ b/src/engine/rendersky.cpp @@ -4,55 +4,55 @@ Texture *sky[6] = { 0, 0, 0, 0, 0, 0 }, *clouds[6] = { 0, 0, 0, 0, 0, 0 }; void loadsky(const char *basename, Texture *texs[6]) { - const char *wildcard = strchr(basename, '*'); - loopi(6) - { - const char *side = cubemapsides[i].name; - string name; - copystring(name, makerelpath("packages", basename)); - if(wildcard) - { - char *chop = strchr(name, '*'); - if(chop) { *chop = '\0'; concatstring(name, side); concatstring(name, wildcard+1); } - texs[i] = textureload(name, 3, true, false); - } - else - { - defformatstring(ext, "_%s.jpg", side); - concatstring(name, ext); - if((texs[i] = textureload(name, 3, true, false))==notexture) - { - strcpy(name+strlen(name)-3, "png"); - texs[i] = textureload(name, 3, true, false); - } - } - if(texs[i]==notexture) conoutf(CON_ERROR, "could not load side %s of sky texture %s", side, basename); - } + const char *wildcard = strchr(basename, '*'); + loopi(6) + { + const char *side = cubemapsides[i].name; + string name; + copystring(name, makerelpath("packages", basename)); + if(wildcard) + { + char *chop = strchr(name, '*'); + if(chop) { *chop = '\0'; concatstring(name, side); concatstring(name, wildcard+1); } + texs[i] = textureload(name, 3, true, false); + } + else + { + defformatstring(ext, "_%s.jpg", side); + concatstring(name, ext); + if((texs[i] = textureload(name, 3, true, false))==notexture) + { + strcpy(name+strlen(name)-3, "png"); + texs[i] = textureload(name, 3, true, false); + } + } + if(texs[i]==notexture) conoutf(CON_ERROR, "could not load side %s of sky texture %s", side, basename); + } } Texture *cloudoverlay = NULL; Texture *loadskyoverlay(const char *basename) { - const char *ext = strrchr(basename, '.'); - string name; - copystring(name, makerelpath("packages", basename)); - Texture *t = notexture; - if(ext) t = textureload(name, 0, true, false); - else - { - concatstring(name, ".jpg"); - if((t = textureload(name, 0, true, false)) == notexture) - { - strcpy(name+strlen(name)-3, "png"); - t = textureload(name, 0, true, false); - } - } - if(t==notexture) conoutf(CON_ERROR, "could not load sky overlay texture %s", basename); - return t; + const char *ext = strrchr(basename, '.'); + string name; + copystring(name, makerelpath("packages", basename)); + Texture *t = notexture; + if(ext) t = textureload(name, 0, true, false); + else + { + concatstring(name, ".jpg"); + if((t = textureload(name, 0, true, false)) == notexture) + { + strcpy(name+strlen(name)-3, "png"); + t = textureload(name, 0, true, false); + } + } + if(t==notexture) conoutf(CON_ERROR, "could not load sky overlay texture %s", basename); + return t; } -SVARFR(skybox, "", { if(skybox[0]) loadsky(skybox, sky); }); +SVARFR(skybox, "", { if(skybox[0]) loadsky(skybox, sky); }); HVARR(skyboxcolour, 0, 0xFFFFFF, 0xFFFFFF); FVARR(spinsky, -720, 0, 720); VARR(yawsky, 0, 0, 360); @@ -77,101 +77,101 @@ VARR(cloudsubdiv, 4, 16, 64); HVARR(cloudcolour, 0, 0xFFFFFF, 0xFFFFFF); void drawenvboxface(float s0, float t0, int x0, int y0, int z0, - float s1, float t1, int x1, int y1, int z1, - float s2, float t2, int x2, int y2, int z2, - float s3, float t3, int x3, int y3, int z3, - Texture *tex) + float s1, float t1, int x1, int y1, int z1, + float s2, float t2, int x2, int y2, int z2, + float s3, float t3, int x3, int y3, int z3, + Texture *tex) { - glBindTexture(GL_TEXTURE_2D, (tex ? tex : notexture)->id); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x3, y3, z3); gle::attribf(s3, t3); - gle::attribf(x2, y2, z2); gle::attribf(s2, t2); - gle::attribf(x0, y0, z0); gle::attribf(s0, t0); - gle::attribf(x1, y1, z1); gle::attribf(s1, t1); - xtraverts += gle::end(); + glBindTexture(GL_TEXTURE_2D, (tex ? tex : notexture)->id); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x3, y3, z3); gle::attribf(s3, t3); + gle::attribf(x2, y2, z2); gle::attribf(s2, t2); + gle::attribf(x0, y0, z0); gle::attribf(s0, t0); + gle::attribf(x1, y1, z1); gle::attribf(s1, t1); + xtraverts += gle::end(); } void drawenvbox(int w, float z1clip = 0.0f, float z2clip = 1.0f, int faces = 0x3F, Texture **sky = NULL) { - if(z1clip >= z2clip) return; - - float v1 = 1-z1clip, v2 = 1-z2clip; - int z1 = int(ceil(2*w*(z1clip-0.5f))), z2 = int(ceil(2*w*(z2clip-0.5f))); - - gle::defvertex(); - gle::deftexcoord0(); - - if(faces&0x01) - drawenvboxface(0.0f, v2, -w, -w, z2, - 1.0f, v2, -w, w, z2, - 1.0f, v1, -w, w, z1, - 0.0f, v1, -w, -w, z1, sky[0]); - - if(faces&0x02) - drawenvboxface(1.0f, v1, w, -w, z1, - 0.0f, v1, w, w, z1, - 0.0f, v2, w, w, z2, - 1.0f, v2, w, -w, z2, sky[1]); - - if(faces&0x04) - drawenvboxface(1.0f, v1, -w, -w, z1, - 0.0f, v1, w, -w, z1, - 0.0f, v2, w, -w, z2, - 1.0f, v2, -w, -w, z2, sky[2]); - - if(faces&0x08) - drawenvboxface(1.0f, v1, w, w, z1, - 0.0f, v1, -w, w, z1, - 0.0f, v2, -w, w, z2, - 1.0f, v2, w, w, z2, sky[3]); - - if(z1clip <= 0 && faces&0x10) - drawenvboxface(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]); - - if(z2clip >= 1 && faces&0x20) - drawenvboxface(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]); + if(z1clip >= z2clip) return; + + float v1 = 1-z1clip, v2 = 1-z2clip; + int z1 = int(ceil(2*w*(z1clip-0.5f))), z2 = int(ceil(2*w*(z2clip-0.5f))); + + gle::defvertex(); + gle::deftexcoord0(); + + if(faces&0x01) + drawenvboxface(0.0f, v2, -w, -w, z2, + 1.0f, v2, -w, w, z2, + 1.0f, v1, -w, w, z1, + 0.0f, v1, -w, -w, z1, sky[0]); + + if(faces&0x02) + drawenvboxface(1.0f, v1, w, -w, z1, + 0.0f, v1, w, w, z1, + 0.0f, v2, w, w, z2, + 1.0f, v2, w, -w, z2, sky[1]); + + if(faces&0x04) + drawenvboxface(1.0f, v1, -w, -w, z1, + 0.0f, v1, w, -w, z1, + 0.0f, v2, w, -w, z2, + 1.0f, v2, -w, -w, z2, sky[2]); + + if(faces&0x08) + drawenvboxface(1.0f, v1, w, w, z1, + 0.0f, v1, -w, w, z1, + 0.0f, v2, -w, w, z2, + 1.0f, v2, w, w, z2, sky[3]); + + if(z1clip <= 0 && faces&0x10) + drawenvboxface(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]); + + if(z2clip >= 1 && faces&0x20) + drawenvboxface(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]); } void drawenvoverlay(int w, Texture *overlay = NULL, float tx = 0, float ty = 0) { - float z = w*cloudheight, tsz = 0.5f*(1-cloudfade)/cloudscale, psz = w*(1-cloudfade); - glBindTexture(GL_TEXTURE_2D, overlay ? overlay->id : notexture->id); - vec color = vec::hexcolor(cloudcolour); - gle::color(color, cloudalpha); - gle::defvertex(); - gle::deftexcoord0(); - gle::begin(GL_TRIANGLE_FAN); - loopi(cloudsubdiv+1) - { - vec p(1, 1, 0); - p.rotate_around_z((-2.0f*M_PI*i)/cloudsubdiv); - gle::attribf(p.x*psz, p.y*psz, z); - gle::attribf(tx + p.x*tsz, ty + p.y*tsz); - } - xtraverts += gle::end(); - float tsz2 = 0.5f/cloudscale; - gle::defvertex(); - gle::deftexcoord0(); - gle::defcolor(4); - gle::begin(GL_TRIANGLE_STRIP); - loopi(cloudsubdiv+1) - { - vec p(1, 1, 0); - p.rotate_around_z((-2.0f*M_PI*i)/cloudsubdiv); - gle::attribf(p.x*psz, p.y*psz, z); - gle::attribf(tx + p.x*tsz, ty + p.y*tsz); - gle::attrib(color, cloudalpha); - gle::attribf(p.x*w, p.y*w, z); - gle::attribf(tx + p.x*tsz2, ty + p.y*tsz2); - gle::attrib(color, 0.0f); - } - xtraverts += gle::end(); + float z = w*cloudheight, tsz = 0.5f*(1-cloudfade)/cloudscale, psz = w*(1-cloudfade); + glBindTexture(GL_TEXTURE_2D, overlay ? overlay->id : notexture->id); + vec color = vec::hexcolor(cloudcolour); + gle::color(color, cloudalpha); + gle::defvertex(); + gle::deftexcoord0(); + gle::begin(GL_TRIANGLE_FAN); + loopi(cloudsubdiv+1) + { + vec p(1, 1, 0); + p.rotate_around_z((-2.0f*M_PI*i)/cloudsubdiv); + gle::attribf(p.x*psz, p.y*psz, z); + gle::attribf(tx + p.x*tsz, ty + p.y*tsz); + } + xtraverts += gle::end(); + float tsz2 = 0.5f/cloudscale; + gle::defvertex(); + gle::deftexcoord0(); + gle::defcolor(4); + gle::begin(GL_TRIANGLE_STRIP); + loopi(cloudsubdiv+1) + { + vec p(1, 1, 0); + p.rotate_around_z((-2.0f*M_PI*i)/cloudsubdiv); + gle::attribf(p.x*psz, p.y*psz, z); + gle::attribf(tx + p.x*tsz, ty + p.y*tsz); + gle::attrib(color, cloudalpha); + gle::attribf(p.x*w, p.y*w, z); + gle::attribf(tx + p.x*tsz2, ty + p.y*tsz2); + gle::attrib(color, 0.0f); + } + xtraverts += gle::end(); } FVARR(fogdomeheight, -1, -0.5f, 1); @@ -182,207 +182,207 @@ FVARR(fogdomeclip, 0, 1, 1); bvec fogdomecolor(0, 0, 0); HVARFR(fogdomecolour, 0, 0, 0xFFFFFF, { - fogdomecolor = bvec((fogdomecolour>>16)&0xFF, (fogdomecolour>>8)&0xFF, fogdomecolour&0xFF); + fogdomecolor = bvec((fogdomecolour>>16)&0xFF, (fogdomecolour>>8)&0xFF, fogdomecolour&0xFF); }); VARR(fogdomeclouds, 0, 1, 1); namespace fogdome { - struct vert - { - vec pos; - bvec4 color; - - vert() {} - vert(const vec &pos, const bvec &fcolor, float alpha) : pos(pos), color(fcolor, uchar(alpha*255)) - { - } - vert(const vert &v0, const vert &v1) : pos(vec(v0.pos).add(v1.pos).normalize()), color(v0.color) - { - if(v0.pos.z != v1.pos.z) color.a += uchar((v1.color.a - v0.color.a) * (pos.z - v0.pos.z) / (v1.pos.z - v0.pos.z)); - } - } *verts = NULL; - GLushort *indices = NULL; - int numverts = 0, numindices = 0, capindices = 0; - GLuint vbuf = 0, ebuf = 0; - bvec lastcolor(0, 0, 0); - float lastminalpha = 0, lastmaxalpha = 0, lastcapsize = -1, lastclipz = 1; - - void subdivide(int depth, int face); - - void genface(int depth, int i1, int i2, int i3) - { - int face = numindices; numindices += 3; - indices[face] = i3; - indices[face+1] = i2; - indices[face+2] = i1; - subdivide(depth, face); - } - - void subdivide(int depth, int face) - { - if(depth-- <= 0) return; - int idx[6]; - loopi(3) idx[i] = indices[face+2-i]; - loopi(3) - { - int curvert = numverts++; - verts[curvert] = vert(verts[idx[i]], verts[idx[(i+1)%3]]); //push on to unit sphere - idx[3+i] = curvert; - indices[face+2-i] = curvert; - } - subdivide(depth, face); - loopi(3) genface(depth, idx[i], idx[3+i], idx[3+(i+2)%3]); - } - - int sortcap(GLushort x, GLushort y) - { - const vec &xv = verts[x].pos, &yv = verts[y].pos; - return xv.y < 0 ? yv.y >= 0 || xv.x < yv.x : yv.y >= 0 && xv.x > yv.x; - } - - void init(const bvec &color, float minalpha = 0.0f, float maxalpha = 1.0f, float capsize = -1, float clipz = 1, int hres = 16, int depth = 2) - { - const int tris = hres << (2*depth); - numverts = numindices = capindices = 0; - verts = new vert[tris+1 + (capsize >= 0 ? 1 : 0)]; - indices = new GLushort[(tris + (capsize >= 0 ? hres<= 1) - { - verts[numverts++] = vert(vec(0.0f, 0.0f, 1.0f), color, minalpha); //build initial 'hres' sided pyramid - loopi(hres) verts[numverts++] = vert(vec(sincos360[(360*i)/hres], 0.0f), color, maxalpha); - loopi(hres) genface(depth, 0, i+1, 1+(i+1)%hres); - } - else if(clipz <= 0) - { - loopi(hres<= 0) - { - GLushort *cap = &indices[numindices]; - int capverts = 0; - loopi(numverts) if(!verts[i].pos.z) cap[capverts++] = i; - verts[numverts++] = vert(vec(0.0f, 0.0f, -capsize), color, maxalpha); - quicksort(cap, capverts, sortcap); - loopi(capverts) - { - int n = capverts-1-i; - cap[n*3] = cap[n]; - cap[n*3+1] = cap[(n+1)%capverts]; - cap[n*3+2] = numverts-1; - capindices += 3; - } - } - - if(!vbuf) glGenBuffers_(1, &vbuf); - gle::bindvbo(vbuf); - glBufferData_(GL_ARRAY_BUFFER, numverts*sizeof(vert), verts, GL_STATIC_DRAW); - DELETEA(verts); - - if(!ebuf) glGenBuffers_(1, &ebuf); - gle::bindebo(ebuf); - glBufferData_(GL_ELEMENT_ARRAY_BUFFER, (numindices + capindices)*sizeof(GLushort), indices, GL_STATIC_DRAW); - DELETEA(indices); - } - - void cleanup() - { - numverts = numindices = 0; - if(vbuf) { glDeleteBuffers_(1, &vbuf); vbuf = 0; } - if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } - } - - void draw() - { - float capsize = fogdomecap && fogdomeheight < 1 ? (1 + fogdomeheight) / (1 - fogdomeheight) : -1; - bvec color = fogdomecolour ? fogdomecolor : fogcolor; - if(!numverts || lastcolor != color || lastminalpha != fogdomemin || lastmaxalpha != fogdomemax || lastcapsize != capsize || lastclipz != fogdomeclip) - { - init(color, min(fogdomemin, fogdomemax), fogdomemax, capsize, fogdomeclip); - lastcolor = color; - lastminalpha = fogdomemin; - lastmaxalpha = fogdomemax; - lastcapsize = capsize; - lastclipz = fogdomeclip; - } - - gle::bindvbo(vbuf); - gle::bindebo(ebuf); - - gle::vertexpointer(sizeof(vert), &verts->pos); - gle::colorpointer(sizeof(vert), &verts->color); - gle::enablevertex(); - gle::enablecolor(); - - glDrawRangeElements_(GL_TRIANGLES, 0, numverts-1, numindices + fogdomecap*capindices, GL_UNSIGNED_SHORT, indices); - xtraverts += numverts; - glde++; - - gle::disablevertex(); - gle::disablecolor(); - - gle::clearvbo(); - gle::clearebo(); - } + struct vert + { + vec pos; + bvec4 color; + + vert() {} + vert(const vec &pos, const bvec &fcolor, float alpha) : pos(pos), color(fcolor, uchar(alpha*255)) + { + } + vert(const vert &v0, const vert &v1) : pos(vec(v0.pos).add(v1.pos).normalize()), color(v0.color) + { + if(v0.pos.z != v1.pos.z) color.a += uchar((v1.color.a - v0.color.a) * (pos.z - v0.pos.z) / (v1.pos.z - v0.pos.z)); + } + } *verts = NULL; + GLushort *indices = NULL; + int numverts = 0, numindices = 0, capindices = 0; + GLuint vbuf = 0, ebuf = 0; + bvec lastcolor(0, 0, 0); + float lastminalpha = 0, lastmaxalpha = 0, lastcapsize = -1, lastclipz = 1; + + void subdivide(int depth, int face); + + void genface(int depth, int i1, int i2, int i3) + { + int face = numindices; numindices += 3; + indices[face] = i3; + indices[face+1] = i2; + indices[face+2] = i1; + subdivide(depth, face); + } + + void subdivide(int depth, int face) + { + if(depth-- <= 0) return; + int idx[6]; + loopi(3) idx[i] = indices[face+2-i]; + loopi(3) + { + int curvert = numverts++; + verts[curvert] = vert(verts[idx[i]], verts[idx[(i+1)%3]]); //push on to unit sphere + idx[3+i] = curvert; + indices[face+2-i] = curvert; + } + subdivide(depth, face); + loopi(3) genface(depth, idx[i], idx[3+i], idx[3+(i+2)%3]); + } + + int sortcap(GLushort x, GLushort y) + { + const vec &xv = verts[x].pos, &yv = verts[y].pos; + return xv.y < 0 ? yv.y >= 0 || xv.x < yv.x : yv.y >= 0 && xv.x > yv.x; + } + + void init(const bvec &color, float minalpha = 0.0f, float maxalpha = 1.0f, float capsize = -1, float clipz = 1, int hres = 16, int depth = 2) + { + const int tris = hres << (2*depth); + numverts = numindices = capindices = 0; + verts = new vert[tris+1 + (capsize >= 0 ? 1 : 0)]; + indices = new GLushort[(tris + (capsize >= 0 ? hres<= 1) + { + verts[numverts++] = vert(vec(0.0f, 0.0f, 1.0f), color, minalpha); //build initial 'hres' sided pyramid + loopi(hres) verts[numverts++] = vert(vec(sincos360[(360*i)/hres], 0.0f), color, maxalpha); + loopi(hres) genface(depth, 0, i+1, 1+(i+1)%hres); + } + else if(clipz <= 0) + { + loopi(hres<= 0) + { + GLushort *cap = &indices[numindices]; + int capverts = 0; + loopi(numverts) if(!verts[i].pos.z) cap[capverts++] = i; + verts[numverts++] = vert(vec(0.0f, 0.0f, -capsize), color, maxalpha); + quicksort(cap, capverts, sortcap); + loopi(capverts) + { + int n = capverts-1-i; + cap[n*3] = cap[n]; + cap[n*3+1] = cap[(n+1)%capverts]; + cap[n*3+2] = numverts-1; + capindices += 3; + } + } + + if(!vbuf) glGenBuffers_(1, &vbuf); + gle::bindvbo(vbuf); + glBufferData_(GL_ARRAY_BUFFER, numverts*sizeof(vert), verts, GL_STATIC_DRAW); + DELETEA(verts); + + if(!ebuf) glGenBuffers_(1, &ebuf); + gle::bindebo(ebuf); + glBufferData_(GL_ELEMENT_ARRAY_BUFFER, (numindices + capindices)*sizeof(GLushort), indices, GL_STATIC_DRAW); + DELETEA(indices); + } + + void cleanup() + { + numverts = numindices = 0; + if(vbuf) { glDeleteBuffers_(1, &vbuf); vbuf = 0; } + if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } + } + + void draw() + { + float capsize = fogdomecap && fogdomeheight < 1 ? (1 + fogdomeheight) / (1 - fogdomeheight) : -1; + bvec color = fogdomecolour ? fogdomecolor : fogcolor; + if(!numverts || lastcolor != color || lastminalpha != fogdomemin || lastmaxalpha != fogdomemax || lastcapsize != capsize || lastclipz != fogdomeclip) + { + init(color, min(fogdomemin, fogdomemax), fogdomemax, capsize, fogdomeclip); + lastcolor = color; + lastminalpha = fogdomemin; + lastmaxalpha = fogdomemax; + lastcapsize = capsize; + lastclipz = fogdomeclip; + } + + gle::bindvbo(vbuf); + gle::bindebo(ebuf); + + gle::vertexpointer(sizeof(vert), &verts->pos); + gle::colorpointer(sizeof(vert), &verts->color); + gle::enablevertex(); + gle::enablecolor(); + + glDrawRangeElements_(GL_TRIANGLES, 0, numverts-1, numindices + fogdomecap*capindices, GL_UNSIGNED_SHORT, indices); + xtraverts += numverts; + glde++; + + gle::disablevertex(); + gle::disablecolor(); + + gle::clearvbo(); + gle::clearebo(); + } } static void drawfogdome(int farplane) { - SETSHADER(skyfog); + SETSHADER(skyfog); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - matrix4 skymatrix = cammatrix, skyprojmatrix; - skymatrix.settranslation(vec(cammatrix.c).mul(farplane*fogdomeheight*0.5f)); - skymatrix.scale(farplane/2, farplane/2, farplane*(0.5f - fogdomeheight*0.5f)); - skyprojmatrix.mul(projmatrix, skymatrix); - LOCALPARAM(skymatrix, skyprojmatrix); + matrix4 skymatrix = cammatrix, skyprojmatrix; + skymatrix.settranslation(vec(cammatrix.c).mul(farplane*fogdomeheight*0.5f)); + skymatrix.scale(farplane/2, farplane/2, farplane*(0.5f - fogdomeheight*0.5f)); + skyprojmatrix.mul(projmatrix, skymatrix); + LOCALPARAM(skymatrix, skyprojmatrix); - fogdome::draw(); + fogdome::draw(); - glDisable(GL_BLEND); + glDisable(GL_BLEND); } void cleanupsky() { - fogdome::cleanup(); + fogdome::cleanup(); } extern int atmo; void preloadatmoshaders(bool force = false) { - static bool needatmo = false; - if(force) needatmo = true; - if(!atmo || !needatmo) return; + static bool needatmo = false; + if(force) needatmo = true; + if(!atmo || !needatmo) return; - useshaderbyname("atmosphere"); - useshaderbyname("atmosphereglare"); + useshaderbyname("atmosphere"); + useshaderbyname("atmosphereglare"); } void setupsky() { - preloadatmoshaders(true); + preloadatmoshaders(true); } VARFR(atmo, 0, 0, 1, preloadatmoshaders()); @@ -392,15 +392,15 @@ FVARR(atmobright, 0, 1, 16); bvec atmosunlightcolor(0, 0, 0); HVARFR(atmosunlight, 0, 0, 0xFFFFFF, { - if(atmosunlight <= 255) atmosunlight |= (atmosunlight<<8) | (atmosunlight<<16); - atmosunlightcolor = bvec((atmosunlight>>16)&0xFF, (atmosunlight>>8)&0xFF, atmosunlight&0xFF); + if(atmosunlight <= 255) atmosunlight |= (atmosunlight<<8) | (atmosunlight<<16); + atmosunlightcolor = bvec((atmosunlight>>16)&0xFF, (atmosunlight>>8)&0xFF, atmosunlight&0xFF); }); FVARR(atmosunlightscale, 0, 1, 16); bvec atmosundiskcolor(0, 0, 0); HVARFR(atmosundisk, 0, 0, 0xFFFFFF, { - if(atmosundisk <= 255) atmosundisk |= (atmosundisk<<8) | (atmosundisk<<16); - atmosundiskcolor = bvec((atmosundisk>>16)&0xFF, (atmosundisk>>8)&0xFF, atmosundisk&0xFF); + if(atmosundisk <= 255) atmosundisk |= (atmosundisk<<8) | (atmosundisk<<16); + atmosundiskcolor = bvec((atmosundisk>>16)&0xFF, (atmosundisk>>8)&0xFF, atmosundisk&0xFF); }); FVARR(atmosundisksize, 0, 12, 90); FVARR(atmosundiskcorona, 0, 0.4f, 1); @@ -412,344 +412,344 @@ FVARR(atmoalpha, 0, 1, 1); static void drawatmosphere(int w, float z1clip = 0.0f, float z2clip = 1.0f, int faces = 0x3F) { - if(z1clip >= z2clip) return; - - if(glaring) SETSHADER(atmosphereglare); - else SETSHADER(atmosphere); - - matrix4 skymatrix = cammatrix, skyprojmatrix; - skymatrix.settranslation(0, 0, 0); - skyprojmatrix.mul(projmatrix, skymatrix); - LOCALPARAM(skymatrix, skyprojmatrix); - - // optical depth scales for 3 different shells of atmosphere - air, haze, ozone - const float earthradius = 6371e3f, earthairheight = 8.4e3f, earthhazeheight = 1.25e3f, earthozoneheight = 50e3f; - float planetradius = earthradius*atmoplanetsize; - vec atmoshells = vec(earthairheight, earthhazeheight, earthozoneheight).mul(atmoheight).add(planetradius).square().sub(planetradius*planetradius); - LOCALPARAM(opticaldepthparams, vec4(atmoshells, planetradius)); - - // Henyey-Greenstein approximation, 1/(4pi) * (1 - g^2)/(1 + g^2 - 2gcos)]^1.5 - // Hoffman-Preetham variation uses (1-g)^2 instead of 1-g^2 which avoids excessive glare - // clamp values near 0 angle to avoid spotlight artifact inside sundisk - float gm = max(0.95f - 0.2f*atmohaze, 0.65f), miescale = pow((1-gm)*(1-gm)/(4*M_PI), -2.0f/3.0f); - LOCALPARAMF(mieparams, miescale*(1 + gm*gm), miescale*-2*gm, 1 - (1 - cosf(0.5f*atmosundisksize*(1 - atmosundiskcorona)*RAD))); - - static const vec lambda(680e-9f, 550e-9f, 450e-9f), - k(0.686f, 0.678f, 0.666f), - ozone(3.426f, 8.298f, 0.356f); - vec betar = vec(lambda).square().square().recip().mul(1.241e-30f/M_LN2 * atmodensity), - betam = vec(lambda).recip().square().mul(k).mul(9.072e-17f/M_LN2 * atmohaze), - betao = vec(ozone).mul(1.5e-7f/M_LN2 * atmoozone); - LOCALPARAM(betarayleigh, betar); - LOCALPARAM(betamie, betam); - LOCALPARAM(betaozone, betao); - - // extinction in direction of sun - float sunoffset = sunlightdir.z*planetradius; - vec sundepth = vec(atmoshells).add(sunoffset*sunoffset).sqrt().sub(sunoffset); - vec sunweight = vec(betar).mul(sundepth.x).madd(betam, sundepth.y).madd(betao, sundepth.z - sundepth.x); - vec sunextinction = vec(sunweight).neg().exp2(); - vec suncolor = atmosunlight ? atmosunlightcolor.tocolor().mul(atmosunlightscale) : sunlightcolor.tocolor().mul(sunlightscale); - // assume sunlight color is gamma encoded, so decode to linear light, then apply extinction - vec sunscale = vec(suncolor).square().mul(atmobright * 16).mul(sunextinction); - float maxsunweight = max(max(sunweight.x, sunweight.y), sunweight.z); - if(maxsunweight > 127) sunweight.mul(127/maxsunweight); - sunweight.add(1e-4f); - LOCALPARAM(sunweight, sunweight); - LOCALPARAM(sunlight, vec4(sunscale, atmoalpha)); - LOCALPARAM(sundir, sunlightdir); - - // invert extinction at zenith to get an approximation of how bright the sun disk should be - vec zenithdepth = vec(atmoshells).add(planetradius*planetradius).sqrt().sub(planetradius); - vec zenithweight = vec(betar).mul(zenithdepth.x).madd(betam, zenithdepth.y).madd(betao, zenithdepth.z - zenithdepth.x); - vec zenithextinction = vec(zenithweight).sub(sunweight).exp2(); - vec diskcolor = (atmosundisk ? atmosundiskcolor.tocolor() : suncolor).square().mul(zenithextinction).mul(atmosundiskbright * (glaring ? 1 : 1.5f)).min(1); - LOCALPARAM(sundiskcolor, diskcolor); - - // convert from view cosine into mu^2 for limb darkening, where mu = sqrt(1 - sin^2) and sin^2 = 1 - cos^2, thus mu^2 = 1 - (1 - cos^2*scale) - // convert corona offset into scale for mu^2, where sin = (1-corona) and thus mu^2 = 1 - (1-corona^2) - float sundiskscale = sinf(0.5f*atmosundisksize*RAD); - float coronamu = 1 - (1-atmosundiskcorona)*(1-atmosundiskcorona); - if(sundiskscale > 0) LOCALPARAMF(sundiskparams, 1.0f/(sundiskscale*sundiskscale), 1.0f/max(coronamu, 1e-3f)); - else LOCALPARAMF(sundiskparams, 0, 0); - - float z1 = 2*w*(z1clip-0.5f), z2 = ceil(2*w*(z2clip-0.5f)); - - gle::defvertex(); - - if(glaring) - { - if(sundiskscale > 0 && sunlightdir.z*w + sundiskscale > z1 && sunlightdir.z*w - sundiskscale < z2) - { - gle::begin(GL_TRIANGLE_FAN); - vec spoke; - spoke.orthogonal(sunlightdir); - spoke.rescale(2*sundiskscale); - loopi(4) gle::attrib(vec(spoke).rotate(-2*M_PI*i/4.0f, sunlightdir).add(sunlightdir).mul(w)); - xtraverts += gle::end(); - } - return; - } - - gle::begin(GL_QUADS); - - if(faces&0x01) - { - gle::attribf(-w, -w, z1); - gle::attribf(-w, w, z1); - gle::attribf(-w, w, z2); - gle::attribf(-w, -w, z2); - } - - if(faces&0x02) - { - gle::attribf(w, -w, z2); - gle::attribf(w, w, z2); - gle::attribf(w, w, z1); - gle::attribf(w, -w, z1); - } - - if(faces&0x04) - { - gle::attribf(-w, -w, z2); - gle::attribf( w, -w, z2); - gle::attribf( w, -w, z1); - gle::attribf(-w, -w, z1); - } - - if(faces&0x08) - { - gle::attribf( w, w, z2); - gle::attribf(-w, w, z2); - gle::attribf(-w, w, z1); - gle::attribf( w, w, z1); - } - - if(z1clip <= 0 && faces&0x10) - { - gle::attribf(-w, -w, -w); - gle::attribf( w, -w, -w); - gle::attribf( w, w, -w); - gle::attribf(-w, w, -w); - } - - if(z2clip >= 1 && faces&0x20) - { - gle::attribf( w, -w, w); - gle::attribf(-w, -w, w); - gle::attribf(-w, w, w); - gle::attribf( w, w, w); - } - - xtraverts += gle::end(); + if(z1clip >= z2clip) return; + + if(glaring) SETSHADER(atmosphereglare); + else SETSHADER(atmosphere); + + matrix4 skymatrix = cammatrix, skyprojmatrix; + skymatrix.settranslation(0, 0, 0); + skyprojmatrix.mul(projmatrix, skymatrix); + LOCALPARAM(skymatrix, skyprojmatrix); + + // optical depth scales for 3 different shells of atmosphere - air, haze, ozone + const float earthradius = 6371e3f, earthairheight = 8.4e3f, earthhazeheight = 1.25e3f, earthozoneheight = 50e3f; + float planetradius = earthradius*atmoplanetsize; + vec atmoshells = vec(earthairheight, earthhazeheight, earthozoneheight).mul(atmoheight).add(planetradius).square().sub(planetradius*planetradius); + LOCALPARAM(opticaldepthparams, vec4(atmoshells, planetradius)); + + // Henyey-Greenstein approximation, 1/(4pi) * (1 - g^2)/(1 + g^2 - 2gcos)]^1.5 + // Hoffman-Preetham variation uses (1-g)^2 instead of 1-g^2 which avoids excessive glare + // clamp values near 0 angle to avoid spotlight artifact inside sundisk + float gm = max(0.95f - 0.2f*atmohaze, 0.65f), miescale = pow((1-gm)*(1-gm)/(4*M_PI), -2.0f/3.0f); + LOCALPARAMF(mieparams, miescale*(1 + gm*gm), miescale*-2*gm, 1 - (1 - cosf(0.5f*atmosundisksize*(1 - atmosundiskcorona)*RAD))); + + static const vec lambda(680e-9f, 550e-9f, 450e-9f), + k(0.686f, 0.678f, 0.666f), + ozone(3.426f, 8.298f, 0.356f); + vec betar = vec(lambda).square().square().recip().mul(1.241e-30f/M_LN2 * atmodensity), + betam = vec(lambda).recip().square().mul(k).mul(9.072e-17f/M_LN2 * atmohaze), + betao = vec(ozone).mul(1.5e-7f/M_LN2 * atmoozone); + LOCALPARAM(betarayleigh, betar); + LOCALPARAM(betamie, betam); + LOCALPARAM(betaozone, betao); + + // extinction in direction of sun + float sunoffset = sunlightdir.z*planetradius; + vec sundepth = vec(atmoshells).add(sunoffset*sunoffset).sqrt().sub(sunoffset); + vec sunweight = vec(betar).mul(sundepth.x).madd(betam, sundepth.y).madd(betao, sundepth.z - sundepth.x); + vec sunextinction = vec(sunweight).neg().exp2(); + vec suncolor = atmosunlight ? atmosunlightcolor.tocolor().mul(atmosunlightscale) : sunlightcolor.tocolor().mul(sunlightscale); + // assume sunlight color is gamma encoded, so decode to linear light, then apply extinction + vec sunscale = vec(suncolor).square().mul(atmobright * 16).mul(sunextinction); + float maxsunweight = max(max(sunweight.x, sunweight.y), sunweight.z); + if(maxsunweight > 127) sunweight.mul(127/maxsunweight); + sunweight.add(1e-4f); + LOCALPARAM(sunweight, sunweight); + LOCALPARAM(sunlight, vec4(sunscale, atmoalpha)); + LOCALPARAM(sundir, sunlightdir); + + // invert extinction at zenith to get an approximation of how bright the sun disk should be + vec zenithdepth = vec(atmoshells).add(planetradius*planetradius).sqrt().sub(planetradius); + vec zenithweight = vec(betar).mul(zenithdepth.x).madd(betam, zenithdepth.y).madd(betao, zenithdepth.z - zenithdepth.x); + vec zenithextinction = vec(zenithweight).sub(sunweight).exp2(); + vec diskcolor = (atmosundisk ? atmosundiskcolor.tocolor() : suncolor).square().mul(zenithextinction).mul(atmosundiskbright * (glaring ? 1 : 1.5f)).min(1); + LOCALPARAM(sundiskcolor, diskcolor); + + // convert from view cosine into mu^2 for limb darkening, where mu = sqrt(1 - sin^2) and sin^2 = 1 - cos^2, thus mu^2 = 1 - (1 - cos^2*scale) + // convert corona offset into scale for mu^2, where sin = (1-corona) and thus mu^2 = 1 - (1-corona^2) + float sundiskscale = sinf(0.5f*atmosundisksize*RAD); + float coronamu = 1 - (1-atmosundiskcorona)*(1-atmosundiskcorona); + if(sundiskscale > 0) LOCALPARAMF(sundiskparams, 1.0f/(sundiskscale*sundiskscale), 1.0f/max(coronamu, 1e-3f)); + else LOCALPARAMF(sundiskparams, 0, 0); + + float z1 = 2*w*(z1clip-0.5f), z2 = ceil(2*w*(z2clip-0.5f)); + + gle::defvertex(); + + if(glaring) + { + if(sundiskscale > 0 && sunlightdir.z*w + sundiskscale > z1 && sunlightdir.z*w - sundiskscale < z2) + { + gle::begin(GL_TRIANGLE_FAN); + vec spoke; + spoke.orthogonal(sunlightdir); + spoke.rescale(2*sundiskscale); + loopi(4) gle::attrib(vec(spoke).rotate(-2*M_PI*i/4.0f, sunlightdir).add(sunlightdir).mul(w)); + xtraverts += gle::end(); + } + return; + } + + gle::begin(GL_QUADS); + + if(faces&0x01) + { + gle::attribf(-w, -w, z1); + gle::attribf(-w, w, z1); + gle::attribf(-w, w, z2); + gle::attribf(-w, -w, z2); + } + + if(faces&0x02) + { + gle::attribf(w, -w, z2); + gle::attribf(w, w, z2); + gle::attribf(w, w, z1); + gle::attribf(w, -w, z1); + } + + if(faces&0x04) + { + gle::attribf(-w, -w, z2); + gle::attribf( w, -w, z2); + gle::attribf( w, -w, z1); + gle::attribf(-w, -w, z1); + } + + if(faces&0x08) + { + gle::attribf( w, w, z2); + gle::attribf(-w, w, z2); + gle::attribf(-w, w, z1); + gle::attribf( w, w, z1); + } + + if(z1clip <= 0 && faces&0x10) + { + gle::attribf(-w, -w, -w); + gle::attribf( w, -w, -w); + gle::attribf( w, w, -w); + gle::attribf(-w, w, -w); + } + + if(z2clip >= 1 && faces&0x20) + { + gle::attribf( w, -w, w); + gle::attribf(-w, -w, w); + gle::attribf(-w, w, w); + gle::attribf( w, w, w); + } + + xtraverts += gle::end(); } VARP(sparklyfix, 0, 0, 1); -VAR(showsky, 0, 1, 1); +VAR(showsky, 0, 1, 1); VAR(clipsky, 0, 1, 1); bool drawskylimits(bool explicitonly) { - nocolorshader->set(); + nocolorshader->set(); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - bool rendered = rendersky(explicitonly); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + bool rendered = rendersky(explicitonly); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - return rendered; + return rendered; } void drawskyoutline() { - notextureshader->set(); - - glDepthMask(GL_FALSE); - extern int wireframe; - if(!wireframe) - { - enablepolygonoffset(GL_POLYGON_OFFSET_LINE); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } - gle::colorf(0.5f, 0.0f, 0.5f); - rendersky(true); - if(!wireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - disablepolygonoffset(GL_POLYGON_OFFSET_LINE); - } - glDepthMask(GL_TRUE); + notextureshader->set(); + + glDepthMask(GL_FALSE); + extern int wireframe; + if(!wireframe) + { + enablepolygonoffset(GL_POLYGON_OFFSET_LINE); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + gle::colorf(0.5f, 0.0f, 0.5f); + rendersky(true); + if(!wireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + disablepolygonoffset(GL_POLYGON_OFFSET_LINE); + } + glDepthMask(GL_TRUE); } VAR(clampsky, 0, 1, 1); static int yawskyfaces(int faces, int yaw, float spin = 0) { - if(spin || yaw%90) return faces&0x0F ? faces | 0x0F : faces; - static const int faceidxs[3][4] = - { - { 3, 2, 0, 1 }, - { 1, 0, 3, 2 }, - { 2, 3, 1, 0 } - }; - yaw /= 90; - if(yaw < 1 || yaw > 3) return faces; - const int *idxs = faceidxs[yaw - 1]; - return (faces & ~0x0F) | (((faces>>idxs[0])&1)<<0) | (((faces>>idxs[1])&1)<<1) | (((faces>>idxs[2])&1)<<2) | (((faces>>idxs[3])&1)<<3); + if(spin || yaw%90) return faces&0x0F ? faces | 0x0F : faces; + static const int faceidxs[3][4] = + { + { 3, 2, 0, 1 }, + { 1, 0, 3, 2 }, + { 2, 3, 1, 0 } + }; + yaw /= 90; + if(yaw < 1 || yaw > 3) return faces; + const int *idxs = faceidxs[yaw - 1]; + return (faces & ~0x0F) | (((faces>>idxs[0])&1)<<0) | (((faces>>idxs[1])&1)<<1) | (((faces>>idxs[2])&1)<<2) | (((faces>>idxs[3])&1)<<3); } void drawskybox(int farplane, bool limited, bool force) { - extern int renderedskyfaces, renderedskyclip; // , renderedsky, renderedexplicitsky; - bool alwaysrender = editmode || !insideworld(camera1->o) || reflecting || force, - explicitonly = false; - if(limited) - { - explicitonly = alwaysrender || !sparklyfix || refracting; - if(!drawskylimits(explicitonly) && !alwaysrender) return; - extern int ati_skybox_bug; - if(!alwaysrender && !renderedskyfaces && !ati_skybox_bug) explicitonly = false; - } - else if(!alwaysrender) - { - renderedskyfaces = 0; - renderedskyclip = INT_MAX; - for(vtxarray *va = visibleva; va; va = va->next) - { - if(va->occluded >= OCCLUDE_BB && va->skyfaces&0x80) continue; - renderedskyfaces |= va->skyfaces&0x3F; - if(!(va->skyfaces&0x1F) || camera1->o.z < va->skyclip) renderedskyclip = min(renderedskyclip, va->skyclip); - else renderedskyclip = 0; - } - if(!renderedskyfaces) return; - } - - if(alwaysrender) - { - renderedskyfaces = 0x3F; - renderedskyclip = 0; - } - - float skyclip = clipsky ? max(renderedskyclip-1, 0) : 0, topclip = 1; - if(reflectzo.z)/float(worldsize); - else if(reflectz>skyclip) skyclip = reflectz; - } - if(skyclip) skyclip = 0.5f + 0.5f*(skyclip-camera1->o.z)/float(worldsize); - - if(limited) - { - if(explicitonly) glDisable(GL_DEPTH_TEST); - else glDepthFunc(GL_GEQUAL); - } - else glDepthFunc(GL_LEQUAL); - - glDepthMask(GL_FALSE); - - if(clampsky) glDepthRange(1, 1); - - if(!atmo || (skybox[0] && atmoalpha < 1)) - { - if(glaring) SETSHADER(skyboxglare); - else SETSHADER(skybox); - - gle::color(vec::hexcolor(skyboxcolour)); - - matrix4 skymatrix = cammatrix, skyprojmatrix; - skymatrix.settranslation(0, 0, 0); - skymatrix.rotate_around_z((spinsky*lastmillis/1000.0f+yawsky)*-RAD); - skyprojmatrix.mul(projmatrix, skymatrix); - LOCALPARAM(skymatrix, skyprojmatrix); - - drawenvbox(farplane/2, skyclip, topclip, yawskyfaces(renderedskyfaces, yawsky, spinsky), sky); - } - - if(atmo) - { - if(atmoalpha < 1) - { - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - - drawatmosphere(farplane/2, skyclip, topclip, renderedskyfaces); - - if(atmoalpha < 1) glDisable(GL_BLEND); - } - - if(!glaring) - { - if(fogdomemax && !fogdomeclouds) - { - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - drawfogdome(farplane); - } - - if(cloudbox[0]) - { - SETSHADER(skybox); - - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - gle::color(vec::hexcolor(cloudboxcolour), cloudboxalpha); - - matrix4 skymatrix = cammatrix, skyprojmatrix; - skymatrix.settranslation(0, 0, 0); - skymatrix.rotate_around_z((spinclouds*lastmillis/1000.0f+yawclouds)*-RAD); - skyprojmatrix.mul(projmatrix, skymatrix); - LOCALPARAM(skymatrix, skyprojmatrix); - - drawenvbox(farplane/2, skyclip ? skyclip : cloudclip, topclip, yawskyfaces(renderedskyfaces, yawclouds, spinclouds), clouds); - - glDisable(GL_BLEND); - } - - if(cloudlayer[0] && cloudheight && renderedskyfaces&(cloudheight < 0 ? 0x1F : 0x2F)) - { - SETSHADER(skybox); - - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - - glDisable(GL_CULL_FACE); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - matrix4 skymatrix = cammatrix, skyprojmatrix; - skymatrix.settranslation(0, 0, 0); - skymatrix.rotate_around_z((spincloudlayer*lastmillis/1000.0f+yawcloudlayer)*-RAD); - skyprojmatrix.mul(projmatrix, skymatrix); - LOCALPARAM(skymatrix, skyprojmatrix); + extern int renderedskyfaces, renderedskyclip; // , renderedsky, renderedexplicitsky; + bool alwaysrender = editmode || !insideworld(camera1->o) || reflecting || force, + explicitonly = false; + if(limited) + { + explicitonly = alwaysrender || !sparklyfix || refracting; + if(!drawskylimits(explicitonly) && !alwaysrender) return; + extern int ati_skybox_bug; + if(!alwaysrender && !renderedskyfaces && !ati_skybox_bug) explicitonly = false; + } + else if(!alwaysrender) + { + renderedskyfaces = 0; + renderedskyclip = INT_MAX; + for(vtxarray *va = visibleva; va; va = va->next) + { + if(va->occluded >= OCCLUDE_BB && va->skyfaces&0x80) continue; + renderedskyfaces |= va->skyfaces&0x3F; + if(!(va->skyfaces&0x1F) || camera1->o.z < va->skyclip) renderedskyclip = min(renderedskyclip, va->skyclip); + else renderedskyclip = 0; + } + if(!renderedskyfaces) return; + } + + if(alwaysrender) + { + renderedskyfaces = 0x3F; + renderedskyclip = 0; + } + + float skyclip = clipsky ? max(renderedskyclip-1, 0) : 0, topclip = 1; + if(reflectzo.z)/float(worldsize); + else if(reflectz>skyclip) skyclip = reflectz; + } + if(skyclip) skyclip = 0.5f + 0.5f*(skyclip-camera1->o.z)/float(worldsize); + + if(limited) + { + if(explicitonly) glDisable(GL_DEPTH_TEST); + else glDepthFunc(GL_GEQUAL); + } + else glDepthFunc(GL_LEQUAL); + + glDepthMask(GL_FALSE); + + if(clampsky) glDepthRange(1, 1); + + if(!atmo || (skybox[0] && atmoalpha < 1)) + { + if(glaring) SETSHADER(skyboxglare); + else SETSHADER(skybox); + + gle::color(vec::hexcolor(skyboxcolour)); + + matrix4 skymatrix = cammatrix, skyprojmatrix; + skymatrix.settranslation(0, 0, 0); + skymatrix.rotate_around_z((spinsky*lastmillis/1000.0f+yawsky)*-RAD); + skyprojmatrix.mul(projmatrix, skymatrix); + LOCALPARAM(skymatrix, skyprojmatrix); + + drawenvbox(farplane/2, skyclip, topclip, yawskyfaces(renderedskyfaces, yawsky, spinsky), sky); + } + + if(atmo) + { + if(atmoalpha < 1) + { + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + drawatmosphere(farplane/2, skyclip, topclip, renderedskyfaces); + + if(atmoalpha < 1) glDisable(GL_BLEND); + } + + if(!glaring) + { + if(fogdomemax && !fogdomeclouds) + { + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + drawfogdome(farplane); + } + + if(cloudbox[0]) + { + SETSHADER(skybox); + + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + gle::color(vec::hexcolor(cloudboxcolour), cloudboxalpha); + + matrix4 skymatrix = cammatrix, skyprojmatrix; + skymatrix.settranslation(0, 0, 0); + skymatrix.rotate_around_z((spinclouds*lastmillis/1000.0f+yawclouds)*-RAD); + skyprojmatrix.mul(projmatrix, skymatrix); + LOCALPARAM(skymatrix, skyprojmatrix); + + drawenvbox(farplane/2, skyclip ? skyclip : cloudclip, topclip, yawskyfaces(renderedskyfaces, yawclouds, spinclouds), clouds); + + glDisable(GL_BLEND); + } + + if(cloudlayer[0] && cloudheight && renderedskyfaces&(cloudheight < 0 ? 0x1F : 0x2F)) + { + SETSHADER(skybox); + + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + + glDisable(GL_CULL_FACE); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + matrix4 skymatrix = cammatrix, skyprojmatrix; + skymatrix.settranslation(0, 0, 0); + skymatrix.rotate_around_z((spincloudlayer*lastmillis/1000.0f+yawcloudlayer)*-RAD); + skyprojmatrix.mul(projmatrix, skymatrix); + LOCALPARAM(skymatrix, skyprojmatrix); - drawenvoverlay(farplane/2, cloudoverlay, cloudoffsetx + cloudscrollx * lastmillis/1000.0f, cloudoffsety + cloudscrolly * lastmillis/1000.0f); + drawenvoverlay(farplane/2, cloudoverlay, cloudoffsetx + cloudscrollx * lastmillis/1000.0f, cloudoffsety + cloudscrolly * lastmillis/1000.0f); - glDisable(GL_BLEND); + glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); - } + glEnable(GL_CULL_FACE); + } - if(fogdomemax && fogdomeclouds) - { - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - drawfogdome(farplane); - } - } + if(fogdomemax && fogdomeclouds) + { + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + drawfogdome(farplane); + } + } - if(clampsky) glDepthRange(0, 1); + if(clampsky) glDepthRange(0, 1); - glDepthMask(GL_TRUE); + glDepthMask(GL_TRUE); - if(limited) - { - if(explicitonly) glEnable(GL_DEPTH_TEST); - else glDepthFunc(GL_LESS); - if(!reflecting && !refracting && !drawtex && editmode && showsky) drawskyoutline(); - } - else glDepthFunc(GL_LESS); + if(limited) + { + if(explicitonly) glEnable(GL_DEPTH_TEST); + else glDepthFunc(GL_LESS); + if(!reflecting && !refracting && !drawtex && editmode && showsky) drawskyoutline(); + } + else glDepthFunc(GL_LESS); } VARNR(skytexture, useskytexture, 0, 1, 1); @@ -759,16 +759,16 @@ double skyarea = 0; bool limitsky() { - return (explicitsky && (useskytexture || editmode)) || (sparklyfix && skyarea / (double(worldsize)*double(worldsize)*6) < 0.9); + return (explicitsky && (useskytexture || editmode)) || (sparklyfix && skyarea / (double(worldsize)*double(worldsize)*6) < 0.9); } bool shouldrenderskyenvmap() { - return atmo != 0; + return atmo != 0; } bool shouldclearskyboxglare() { - return atmo && (!skybox[0] || atmoalpha >= 1); + return atmo && (!skybox[0] || atmoalpha >= 1); } diff --git a/src/engine/rendertarget.h b/src/engine/rendertarget.h index f1e13e6..a22298b 100644 --- a/src/engine/rendertarget.h +++ b/src/engine/rendertarget.h @@ -2,390 +2,390 @@ extern int rtsharefb, rtscissor, blurtile; struct rendertarget { - int texw, texh, vieww, viewh; - GLenum colorfmt, depthfmt; - GLuint rendertex, renderfb, renderdb, blurtex, blurfb, blurdb; - int blursize, blurysize; - float blursigma; - float blurweights[MAXBLURRADIUS+1], bluroffsets[MAXBLURRADIUS+1]; - float bluryweights[MAXBLURRADIUS+1], bluryoffsets[MAXBLURRADIUS+1]; - - float scissorx1, scissory1, scissorx2, scissory2; + int texw, texh, vieww, viewh; + GLenum colorfmt, depthfmt; + GLuint rendertex, renderfb, renderdb, blurtex, blurfb, blurdb; + int blursize, blurysize; + float blursigma; + float blurweights[MAXBLURRADIUS+1], bluroffsets[MAXBLURRADIUS+1]; + float bluryweights[MAXBLURRADIUS+1], bluryoffsets[MAXBLURRADIUS+1]; + + float scissorx1, scissory1, scissorx2, scissory2; #define BLURTILES 32 #define BLURTILEMASK (0xFFFFFFFFU>>(32-BLURTILES)) - uint blurtiles[BLURTILES+1]; - - bool initialized; - - rendertarget() : texw(0), texh(0), vieww(0), viewh(0), colorfmt(GL_FALSE), depthfmt(GL_FALSE), rendertex(0), renderfb(0), renderdb(0), blurtex(0), blurfb(0), blurdb(0), blursize(0), blurysize(0), blursigma(0), initialized(false) - { - } - - virtual ~rendertarget() {} - - virtual GLenum attachment() const - { - return GL_COLOR_ATTACHMENT0; - } - - virtual const GLenum *colorformats() const - { - static const GLenum colorfmts[] = { GL_RGB, GL_RGB8, GL_FALSE }; - return colorfmts; - } - - virtual const GLenum *depthformats() const - { - static const GLenum depthfmts[] = { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT32, GL_FALSE }; - return depthfmts; - } - - virtual bool depthtest() const { return true; } - - void cleanup(bool fullclean = false) - { - if(renderfb) { glDeleteFramebuffers_(1, &renderfb); renderfb = 0; } - if(renderdb) { glDeleteRenderbuffers_(1, &renderdb); renderdb = 0; } - if(rendertex) { glDeleteTextures(1, &rendertex); rendertex = 0; } - texw = texh = 0; - cleanupblur(); - - if(fullclean) colorfmt = depthfmt = GL_FALSE; - } - - void cleanupblur() - { - if(blurfb) { glDeleteFramebuffers_(1, &blurfb); blurfb = 0; } - if(blurtex) { glDeleteTextures(1, &blurtex); blurtex = 0; } - if(blurdb) { glDeleteRenderbuffers_(1, &blurdb); blurdb = 0; } - blursize = blurysize = 0; - blursigma = 0.0f; - } - - void setupblur() - { - if(!blurtex) glGenTextures(1, &blurtex); - createtexture(blurtex, texw, texh, NULL, 3, 1, colorfmt); - - if(!swaptexs() || rtsharefb) return; - if(!blurfb) glGenFramebuffers_(1, &blurfb); - glBindFramebuffer_(GL_FRAMEBUFFER, blurfb); - glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, blurtex, 0); - if(depthtest()) - { - if(!blurdb) glGenRenderbuffers_(1, &blurdb); - glGenRenderbuffers_(1, &blurdb); - glBindRenderbuffer_(GL_RENDERBUFFER, blurdb); - glRenderbufferStorage_(GL_RENDERBUFFER, depthfmt, texw, texh); - glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, blurdb); - } - glBindFramebuffer_(GL_FRAMEBUFFER, 0); - } - - void setup(int w, int h) - { - if(!renderfb) glGenFramebuffers_(1, &renderfb); - glBindFramebuffer_(GL_FRAMEBUFFER, renderfb); - if(!rendertex) glGenTextures(1, &rendertex); - - GLenum attach = attachment(); - if(attach == GL_DEPTH_ATTACHMENT) - { - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - } - - const GLenum *colorfmts = colorformats(); - int find = 0; - do - { - createtexture(rendertex, w, h, NULL, 3, filter() ? 1 : 0, colorfmt ? colorfmt : colorfmts[find]); - glFramebufferTexture2D_(GL_FRAMEBUFFER, attach, GL_TEXTURE_2D, rendertex, 0); - if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; - } - while(!colorfmt && colorfmts[++find]); - if(!colorfmt) colorfmt = colorfmts[find]; - - if(attach != GL_DEPTH_ATTACHMENT && depthtest()) - { - if(!renderdb) { glGenRenderbuffers_(1, &renderdb); depthfmt = GL_FALSE; } - if(!depthfmt) glBindRenderbuffer_(GL_RENDERBUFFER, renderdb); - const GLenum *depthfmts = depthformats(); - find = 0; - do - { - if(!depthfmt) glRenderbufferStorage_(GL_RENDERBUFFER, depthfmts[find], w, h); - glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderdb); - if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; - } - while(!depthfmt && depthfmts[++find]); - if(!depthfmt) depthfmt = depthfmts[find]; - } - - glBindFramebuffer_(GL_FRAMEBUFFER, 0); - - texw = w; - texh = h; - initialized = false; - } - - bool addblurtiles(float x1, float y1, float x2, float y2, float blurmargin = 0) - { - if(x1 >= 1 || y1 >= 1 || x2 <= -1 || y2 <= -1) return false; - - scissorx1 = min(scissorx1, max(x1, -1.0f)); - scissory1 = min(scissory1, max(y1, -1.0f)); - scissorx2 = max(scissorx2, min(x2, 1.0f)); - scissory2 = max(scissory2, min(y2, 1.0f)); - - float blurerror = 2.0f*float(2*blursize + blurmargin); - int tx1 = max(0, min(BLURTILES - 1, int((x1-blurerror/vieww + 1)/2 * BLURTILES))), - ty1 = max(0, min(BLURTILES - 1, int((y1-blurerror/viewh + 1)/2 * BLURTILES))), - tx2 = max(0, min(BLURTILES - 1, int((x2+blurerror/vieww + 1)/2 * BLURTILES))), - ty2 = max(0, min(BLURTILES - 1, int((y2+blurerror/viewh + 1)/2 * BLURTILES))); - - uint mask = (BLURTILEMASK>>(BLURTILES - (tx2+1))) & (BLURTILEMASK< scissorx2 || y1-blurerror/viewh > scissory2) - return false; - - if(!blurtile) return true; - - int tx1 = max(0, min(BLURTILES - 1, int((x1 + 1)/2 * BLURTILES))), - ty1 = max(0, min(BLURTILES - 1, int((y1 + 1)/2 * BLURTILES))), - tx2 = max(0, min(BLURTILES - 1, int((x2 + 1)/2 * BLURTILES))), - ty2 = max(0, min(BLURTILES - 1, int((y2 + 1)/2 * BLURTILES))); - - uint mask = (BLURTILEMASK>>(BLURTILES - (tx2+1))) & (BLURTILEMASK<>= 8; x += 8; } - while(!(mask&1)) { mask >>= 1; x++; } - int xstart = x; - do { mask >>= 1; x++; } while(mask&1); - uint strip = (BLURTILEMASK>>(BLURTILES - x)) & (BLURTILEMASK< 0 && sh > 0; - if(!scissoring) { sx = sy = 0; sw = vieww; sh = viewh; } - blur(blursize, blursigma, blurysize, sx, sy, sw, sh, scissoring); - } - - virtual bool scissorrender(int &x, int &y, int &w, int &h) - { - if(scissorx1 >= scissorx2 || scissory1 >= scissory2) - { - if(vieww < texw || viewh < texh) - { - x = y = 0; - w = vieww; - h = viewh; - return true; - } - return false; - } - x = max(int(floor((scissorx1+1)/2*vieww)) - 2*blursize, 0); - y = max(int(floor((scissory1+1)/2*viewh)) - 2*blursize, 0); - w = min(int(ceil((scissorx2+1)/2*vieww)) + 2*blursize, vieww) - x; - h = min(int(ceil((scissory2+1)/2*viewh)) + 2*blursize, viewh) - y; - return true; - } - - virtual bool scissorblur(int &x, int &y, int &w, int &h) - { - if(scissorx1 >= scissorx2 || scissory1 >= scissory2) - { - if(vieww < texw || viewh < texh) - { - x = y = 0; - w = vieww; - h = viewh; - return true; - } - return false; - } - x = max(int(floor((scissorx1+1)/2*vieww)), 0); - y = max(int(floor((scissory1+1)/2*viewh)), 0); - w = min(int(ceil((scissorx2+1)/2*vieww)), vieww) - x; - h = min(int(ceil((scissory2+1)/2*viewh)), viewh) - y; - return true; - } - - virtual void doclear() {} - - virtual bool screenrect() const { return false; } - virtual bool filter() const { return true; } - - void render(int w, int h, int blursize = 0, float blursigma = 0, int blurysize = 0) - { - w = min(w, hwtexsize); - h = min(h, hwtexsize); - if(screenrect()) - { - if(w > screenw) w = screenw; - if(h > screenh) h = screenh; - } - vieww = w; - viewh = h; - if(w!=texw || h!=texh || (swaptexs() && !rtsharefb ? !blurfb : blurfb)) cleanup(); - - if(!filter()) - { - if(blurtex) cleanupblur(); - blursize = blurysize = 0; - } - - if(!rendertex) setup(w, h); - - scissorx2 = scissory2 = -1; - scissorx1 = scissory1 = 1; - memset(blurtiles, 0, sizeof(blurtiles)); - - if(!shouldrender()) return; - - if(blursize && !blurtex) setupblur(); - if(swaptexs() && blursize) - { - swap(rendertex, blurtex); - if(!rtsharefb) - { - swap(renderfb, blurfb); - swap(renderdb, blurdb); - } - } - glBindFramebuffer_(GL_FRAMEBUFFER, renderfb); - if(swaptexs() && blursize && rtsharefb) - glFramebufferTexture2D_(GL_FRAMEBUFFER, attachment(), GL_TEXTURE_2D, rendertex, 0); - glViewport(0, 0, vieww, viewh); - - doclear(); - - int sx, sy, sw, sh; - bool scissoring = rtscissor && scissorrender(sx, sy, sw, sh) && sw > 0 && sh > 0; - if(scissoring) - { - glScissor(sx, sy, sw, sh); - glEnable(GL_SCISSOR_TEST); - } - else - { - sx = sy = 0; - sw = vieww; - sh = viewh; - } - - if(!depthtest()) glDisable(GL_DEPTH_TEST); - - bool succeeded = dorender(); - - if(!depthtest()) glEnable(GL_DEPTH_TEST); - - if(scissoring) glDisable(GL_SCISSOR_TEST); - - if(succeeded) - { - initialized = true; - - if(blursize) doblur(blursize, blursigma, blurysize ? blurysize : blursize); - } - - glBindFramebuffer_(GL_FRAMEBUFFER, 0); - glViewport(0, 0, screenw, screenh); - } + uint blurtiles[BLURTILES+1]; + + bool initialized; + + rendertarget() : texw(0), texh(0), vieww(0), viewh(0), colorfmt(GL_FALSE), depthfmt(GL_FALSE), rendertex(0), renderfb(0), renderdb(0), blurtex(0), blurfb(0), blurdb(0), blursize(0), blurysize(0), blursigma(0), initialized(false) + { + } + + virtual ~rendertarget() {} + + virtual GLenum attachment() const + { + return GL_COLOR_ATTACHMENT0; + } + + virtual const GLenum *colorformats() const + { + static const GLenum colorfmts[] = { GL_RGB, GL_RGB8, GL_FALSE }; + return colorfmts; + } + + virtual const GLenum *depthformats() const + { + static const GLenum depthfmts[] = { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT32, GL_FALSE }; + return depthfmts; + } + + virtual bool depthtest() const { return true; } + + void cleanup(bool fullclean = false) + { + if(renderfb) { glDeleteFramebuffers_(1, &renderfb); renderfb = 0; } + if(renderdb) { glDeleteRenderbuffers_(1, &renderdb); renderdb = 0; } + if(rendertex) { glDeleteTextures(1, &rendertex); rendertex = 0; } + texw = texh = 0; + cleanupblur(); + + if(fullclean) colorfmt = depthfmt = GL_FALSE; + } + + void cleanupblur() + { + if(blurfb) { glDeleteFramebuffers_(1, &blurfb); blurfb = 0; } + if(blurtex) { glDeleteTextures(1, &blurtex); blurtex = 0; } + if(blurdb) { glDeleteRenderbuffers_(1, &blurdb); blurdb = 0; } + blursize = blurysize = 0; + blursigma = 0.0f; + } + + void setupblur() + { + if(!blurtex) glGenTextures(1, &blurtex); + createtexture(blurtex, texw, texh, NULL, 3, 1, colorfmt); + + if(!swaptexs() || rtsharefb) return; + if(!blurfb) glGenFramebuffers_(1, &blurfb); + glBindFramebuffer_(GL_FRAMEBUFFER, blurfb); + glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, blurtex, 0); + if(depthtest()) + { + if(!blurdb) glGenRenderbuffers_(1, &blurdb); + glGenRenderbuffers_(1, &blurdb); + glBindRenderbuffer_(GL_RENDERBUFFER, blurdb); + glRenderbufferStorage_(GL_RENDERBUFFER, depthfmt, texw, texh); + glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, blurdb); + } + glBindFramebuffer_(GL_FRAMEBUFFER, 0); + } + + void setup(int w, int h) + { + if(!renderfb) glGenFramebuffers_(1, &renderfb); + glBindFramebuffer_(GL_FRAMEBUFFER, renderfb); + if(!rendertex) glGenTextures(1, &rendertex); + + GLenum attach = attachment(); + if(attach == GL_DEPTH_ATTACHMENT) + { + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + } + + const GLenum *colorfmts = colorformats(); + int find = 0; + do + { + createtexture(rendertex, w, h, NULL, 3, filter() ? 1 : 0, colorfmt ? colorfmt : colorfmts[find]); + glFramebufferTexture2D_(GL_FRAMEBUFFER, attach, GL_TEXTURE_2D, rendertex, 0); + if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; + } + while(!colorfmt && colorfmts[++find]); + if(!colorfmt) colorfmt = colorfmts[find]; + + if(attach != GL_DEPTH_ATTACHMENT && depthtest()) + { + if(!renderdb) { glGenRenderbuffers_(1, &renderdb); depthfmt = GL_FALSE; } + if(!depthfmt) glBindRenderbuffer_(GL_RENDERBUFFER, renderdb); + const GLenum *depthfmts = depthformats(); + find = 0; + do + { + if(!depthfmt) glRenderbufferStorage_(GL_RENDERBUFFER, depthfmts[find], w, h); + glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderdb); + if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; + } + while(!depthfmt && depthfmts[++find]); + if(!depthfmt) depthfmt = depthfmts[find]; + } + + glBindFramebuffer_(GL_FRAMEBUFFER, 0); + + texw = w; + texh = h; + initialized = false; + } + + bool addblurtiles(float x1, float y1, float x2, float y2, float blurmargin = 0) + { + if(x1 >= 1 || y1 >= 1 || x2 <= -1 || y2 <= -1) return false; + + scissorx1 = min(scissorx1, max(x1, -1.0f)); + scissory1 = min(scissory1, max(y1, -1.0f)); + scissorx2 = max(scissorx2, min(x2, 1.0f)); + scissory2 = max(scissory2, min(y2, 1.0f)); + + float blurerror = 2.0f*float(2*blursize + blurmargin); + int tx1 = max(0, min(BLURTILES - 1, int((x1-blurerror/vieww + 1)/2 * BLURTILES))), + ty1 = max(0, min(BLURTILES - 1, int((y1-blurerror/viewh + 1)/2 * BLURTILES))), + tx2 = max(0, min(BLURTILES - 1, int((x2+blurerror/vieww + 1)/2 * BLURTILES))), + ty2 = max(0, min(BLURTILES - 1, int((y2+blurerror/viewh + 1)/2 * BLURTILES))); + + uint mask = (BLURTILEMASK>>(BLURTILES - (tx2+1))) & (BLURTILEMASK< scissorx2 || y1-blurerror/viewh > scissory2) + return false; + + if(!blurtile) return true; + + int tx1 = max(0, min(BLURTILES - 1, int((x1 + 1)/2 * BLURTILES))), + ty1 = max(0, min(BLURTILES - 1, int((y1 + 1)/2 * BLURTILES))), + tx2 = max(0, min(BLURTILES - 1, int((x2 + 1)/2 * BLURTILES))), + ty2 = max(0, min(BLURTILES - 1, int((y2 + 1)/2 * BLURTILES))); + + uint mask = (BLURTILEMASK>>(BLURTILES - (tx2+1))) & (BLURTILEMASK<>= 8; x += 8; } + while(!(mask&1)) { mask >>= 1; x++; } + int xstart = x; + do { mask >>= 1; x++; } while(mask&1); + uint strip = (BLURTILEMASK>>(BLURTILES - x)) & (BLURTILEMASK< 0 && sh > 0; + if(!scissoring) { sx = sy = 0; sw = vieww; sh = viewh; } + blur(blursize, blursigma, blurysize, sx, sy, sw, sh, scissoring); + } + + virtual bool scissorrender(int &x, int &y, int &w, int &h) + { + if(scissorx1 >= scissorx2 || scissory1 >= scissory2) + { + if(vieww < texw || viewh < texh) + { + x = y = 0; + w = vieww; + h = viewh; + return true; + } + return false; + } + x = max(int(floor((scissorx1+1)/2*vieww)) - 2*blursize, 0); + y = max(int(floor((scissory1+1)/2*viewh)) - 2*blursize, 0); + w = min(int(ceil((scissorx2+1)/2*vieww)) + 2*blursize, vieww) - x; + h = min(int(ceil((scissory2+1)/2*viewh)) + 2*blursize, viewh) - y; + return true; + } + + virtual bool scissorblur(int &x, int &y, int &w, int &h) + { + if(scissorx1 >= scissorx2 || scissory1 >= scissory2) + { + if(vieww < texw || viewh < texh) + { + x = y = 0; + w = vieww; + h = viewh; + return true; + } + return false; + } + x = max(int(floor((scissorx1+1)/2*vieww)), 0); + y = max(int(floor((scissory1+1)/2*viewh)), 0); + w = min(int(ceil((scissorx2+1)/2*vieww)), vieww) - x; + h = min(int(ceil((scissory2+1)/2*viewh)), viewh) - y; + return true; + } + + virtual void doclear() {} + + virtual bool screenrect() const { return false; } + virtual bool filter() const { return true; } + + void render(int w, int h, int blursize = 0, float blursigma = 0, int blurysize = 0) + { + w = min(w, hwtexsize); + h = min(h, hwtexsize); + if(screenrect()) + { + if(w > screenw) w = screenw; + if(h > screenh) h = screenh; + } + vieww = w; + viewh = h; + if(w!=texw || h!=texh || (swaptexs() && !rtsharefb ? !blurfb : blurfb)) cleanup(); + + if(!filter()) + { + if(blurtex) cleanupblur(); + blursize = blurysize = 0; + } + + if(!rendertex) setup(w, h); + + scissorx2 = scissory2 = -1; + scissorx1 = scissory1 = 1; + memset(blurtiles, 0, sizeof(blurtiles)); + + if(!shouldrender()) return; + + if(blursize && !blurtex) setupblur(); + if(swaptexs() && blursize) + { + swap(rendertex, blurtex); + if(!rtsharefb) + { + swap(renderfb, blurfb); + swap(renderdb, blurdb); + } + } + glBindFramebuffer_(GL_FRAMEBUFFER, renderfb); + if(swaptexs() && blursize && rtsharefb) + glFramebufferTexture2D_(GL_FRAMEBUFFER, attachment(), GL_TEXTURE_2D, rendertex, 0); + glViewport(0, 0, vieww, viewh); + + doclear(); + + int sx, sy, sw, sh; + bool scissoring = rtscissor && scissorrender(sx, sy, sw, sh) && sw > 0 && sh > 0; + if(scissoring) + { + glScissor(sx, sy, sw, sh); + glEnable(GL_SCISSOR_TEST); + } + else + { + sx = sy = 0; + sw = vieww; + sh = viewh; + } + + if(!depthtest()) glDisable(GL_DEPTH_TEST); + + bool succeeded = dorender(); + + if(!depthtest()) glEnable(GL_DEPTH_TEST); + + if(scissoring) glDisable(GL_SCISSOR_TEST); + + if(succeeded) + { + initialized = true; + + if(blursize) doblur(blursize, blursigma, blurysize ? blurysize : blursize); + } + + glBindFramebuffer_(GL_FRAMEBUFFER, 0); + glViewport(0, 0, screenw, screenh); + } }; diff --git a/src/engine/rendertext.cpp b/src/engine/rendertext.cpp index 44ad136..925e1a8 100644 --- a/src/engine/rendertext.cpp +++ b/src/engine/rendertext.cpp @@ -9,67 +9,67 @@ int curfonttex = 0; void newfont(char *name, char *tex, int *defaultw, int *defaulth) { - font *f = &fonts[name]; - if(!f->name) f->name = newstring(name); - f->texs.shrink(0); - f->texs.add(textureload(tex)); - f->chars.shrink(0); - f->charoffset = '!'; - f->defaultw = *defaultw; - f->defaulth = *defaulth; - f->scale = f->defaulth; - - fontdef = f; - fontdeftex = 0; + font *f = &fonts[name]; + if(!f->name) f->name = newstring(name); + f->texs.shrink(0); + f->texs.add(textureload(tex)); + f->chars.shrink(0); + f->charoffset = '!'; + f->defaultw = *defaultw; + f->defaulth = *defaulth; + f->scale = f->defaulth; + + fontdef = f; + fontdeftex = 0; } void fontoffset(char *c) { - if(!fontdef) return; - - fontdef->charoffset = c[0]; + if(!fontdef) return; + + fontdef->charoffset = c[0]; } void fontscale(int *scale) { - if(!fontdef) return; + if(!fontdef) return; - fontdef->scale = *scale > 0 ? *scale : fontdef->defaulth; + fontdef->scale = *scale > 0 ? *scale : fontdef->defaulth; } void fonttex(char *s) { - if(!fontdef) return; + if(!fontdef) return; - Texture *t = textureload(s); - loopv(fontdef->texs) if(fontdef->texs[i] == t) { fontdeftex = i; return; } - fontdeftex = fontdef->texs.length(); - fontdef->texs.add(t); + Texture *t = textureload(s); + loopv(fontdef->texs) if(fontdef->texs[i] == t) { fontdeftex = i; return; } + fontdeftex = fontdef->texs.length(); + fontdef->texs.add(t); } void fontchar(int *x, int *y, int *w, int *h, int *offsetx, int *offsety, int *advance) { - if(!fontdef) return; - - font::charinfo &c = fontdef->chars.add(); - c.x = *x; - c.y = *y; - c.w = *w ? *w : fontdef->defaultw; - c.h = *h ? *h : fontdef->defaulth; - c.offsetx = *offsetx; - c.offsety = *offsety; - c.advance = *advance ? *advance : c.offsetx + c.w; - c.tex = fontdeftex; + if(!fontdef) return; + + font::charinfo &c = fontdef->chars.add(); + c.x = *x; + c.y = *y; + c.w = *w ? *w : fontdef->defaultw; + c.h = *h ? *h : fontdef->defaulth; + c.offsetx = *offsetx; + c.offsety = *offsety; + c.advance = *advance ? *advance : c.offsetx + c.w; + c.tex = fontdeftex; } void fontskip(int *n) { - if(!fontdef) return; - loopi(max(*n, 1)) - { - font::charinfo &c = fontdef->chars.add(); - c.x = c.y = c.w = c.h = c.offsetx = c.offsety = c.advance = c.tex = 0; - } + if(!fontdef) return; + loopi(max(*n, 1)) + { + font::charinfo &c = fontdef->chars.add(); + c.x = c.y = c.w = c.h = c.offsetx = c.offsety = c.advance = c.tex = 0; + } } COMMANDN(font, newfont, "ssii"); @@ -81,67 +81,67 @@ COMMAND(fontskip, "i"); void fontalias(const char *dst, const char *src) { - font *s = fonts.access(src); - if(!s) return; - font *d = &fonts[dst]; - if(!d->name) d->name = newstring(dst); - d->texs = s->texs; - d->chars = s->chars; - d->charoffset = s->charoffset; - d->defaultw = s->defaultw; - d->defaulth = s->defaulth; - d->scale = s->scale; - - fontdef = d; - fontdeftex = d->texs.length()-1; + font *s = fonts.access(src); + if(!s) return; + font *d = &fonts[dst]; + if(!d->name) d->name = newstring(dst); + d->texs = s->texs; + d->chars = s->chars; + d->charoffset = s->charoffset; + d->defaultw = s->defaultw; + d->defaulth = s->defaulth; + d->scale = s->scale; + + fontdef = d; + fontdeftex = d->texs.length()-1; } COMMAND(fontalias, "ss"); bool setfont(const char *name) { - font *f = fonts.access(name); - if(!f) return false; - curfont = f; - return true; + font *f = fonts.access(name); + if(!f) return false; + curfont = f; + return true; } static vector fontstack; void pushfont() { - fontstack.add(curfont); + fontstack.add(curfont); } bool popfont() { - if(fontstack.empty()) return false; - curfont = fontstack.pop(); - return true; + if(fontstack.empty()) return false; + curfont = fontstack.pop(); + return true; } void gettextres(int &w, int &h) { - if(w < MINRESW || h < MINRESH) - { - if(MINRESW > w*MINRESH/h) - { - h = h*MINRESW/w; - w = MINRESW; - } - else - { - w = w*MINRESH/h; - h = MINRESH; - } - } + if(w < MINRESW || h < MINRESH) + { + if(MINRESW > w*MINRESH/h) + { + h = h*MINRESW/w; + w = MINRESW; + } + else + { + w = w*MINRESH/h; + h = MINRESH; + } + } } -float text_widthf(const char *str) +float text_widthf(const char *str) { - float width, height; - text_boundsf(str, width, height); - return width; + float width, height; + text_boundsf(str, width, height); + return width; } #define FONTTAB (4*FONTW) @@ -149,244 +149,244 @@ float text_widthf(const char *str) void tabify(const char *str, int *numtabs) { - int tw = max(*numtabs, 0)*FONTTAB-1, tabs = 0; - for(float w = text_widthf(str); w <= tw; w = TEXTTAB(w)) ++tabs; - int len = strlen(str); - char *tstr = newstring(len + tabs); - memcpy(tstr, str, len); - memset(&tstr[len], '\t', tabs); - tstr[len+tabs] = '\0'; - stringret(tstr); + int tw = max(*numtabs, 0)*FONTTAB-1, tabs = 0; + for(float w = text_widthf(str); w <= tw; w = TEXTTAB(w)) ++tabs; + int len = strlen(str); + char *tstr = newstring(len + tabs); + memcpy(tstr, str, len); + memset(&tstr[len], '\t', tabs); + tstr[len+tabs] = '\0'; + stringret(tstr); } COMMAND(tabify, "si"); - + void draw_textf(const char *fstr, int left, int top, ...) { - defvformatstring(str, top, fstr); - draw_text(str, left, top); + defvformatstring(str, top, fstr); + draw_text(str, left, top); } const matrix4x3 *textmatrix = NULL; static float draw_char(Texture *&tex, int c, float x, float y, float scale) { - font::charinfo &info = curfont->chars[c-curfont->charoffset]; - if(tex != curfont->texs[info.tex]) - { - xtraverts += gle::end(); - tex = curfont->texs[info.tex]; - glBindTexture(GL_TEXTURE_2D, tex->id); - } - - float x1 = x + scale*info.offsetx, - y1 = y + scale*info.offsety, - x2 = x + scale*(info.offsetx + info.w), - y2 = y + scale*(info.offsety + info.h), - tx1 = info.x / float(tex->xs), - ty1 = info.y / float(tex->ys), - tx2 = (info.x + info.w) / float(tex->xs), - ty2 = (info.y + info.h) / float(tex->ys); - - if(textmatrix) - { - gle::attrib(textmatrix->transform(vec2(x1, y1))); gle::attribf(tx1, ty1); - gle::attrib(textmatrix->transform(vec2(x2, y1))); gle::attribf(tx2, ty1); - gle::attrib(textmatrix->transform(vec2(x2, y2))); gle::attribf(tx2, ty2); - gle::attrib(textmatrix->transform(vec2(x1, y2))); gle::attribf(tx1, ty2); - } - else - { - gle::attribf(x1, y1); gle::attribf(tx1, ty1); - gle::attribf(x2, y1); gle::attribf(tx2, ty1); - gle::attribf(x2, y2); gle::attribf(tx2, ty2); - gle::attribf(x1, y2); gle::attribf(tx1, ty2); - } - - return scale*info.advance; + font::charinfo &info = curfont->chars[c-curfont->charoffset]; + if(tex != curfont->texs[info.tex]) + { + xtraverts += gle::end(); + tex = curfont->texs[info.tex]; + glBindTexture(GL_TEXTURE_2D, tex->id); + } + + float x1 = x + scale*info.offsetx, + y1 = y + scale*info.offsety, + x2 = x + scale*(info.offsetx + info.w), + y2 = y + scale*(info.offsety + info.h), + tx1 = info.x / float(tex->xs), + ty1 = info.y / float(tex->ys), + tx2 = (info.x + info.w) / float(tex->xs), + ty2 = (info.y + info.h) / float(tex->ys); + + if(textmatrix) + { + gle::attrib(textmatrix->transform(vec2(x1, y1))); gle::attribf(tx1, ty1); + gle::attrib(textmatrix->transform(vec2(x2, y1))); gle::attribf(tx2, ty1); + gle::attrib(textmatrix->transform(vec2(x2, y2))); gle::attribf(tx2, ty2); + gle::attrib(textmatrix->transform(vec2(x1, y2))); gle::attribf(tx1, ty2); + } + else + { + gle::attribf(x1, y1); gle::attribf(tx1, ty1); + gle::attribf(x2, y1); gle::attribf(tx2, ty1); + gle::attribf(x2, y2); gle::attribf(tx2, ty2); + gle::attribf(x1, y2); gle::attribf(tx1, ty2); + } + + return scale*info.advance; } //stack[sp] is current color index -static void text_color(char c, char *stack, int size, int &sp, bvec color, int a) +static void text_color(char c, char *stack, int size, int &sp, bvec color, int a) { - if(c=='s') // save color - { - c = stack[sp]; - if(sp 0) --sp; c = stack[sp]; } // restore color - else stack[sp] = c; - switch(c) - { - case '0': color = bvec( 64, 255, 128); break; // green: player talk - case '1': color = bvec( 96, 160, 255); break; // blue: "echo" command - case '2': color = bvec(255, 192, 64); break; // yellow: gameplay messages - case '3': color = bvec(255, 64, 64); break; // red: important errors - case '4': color = bvec(128, 128, 128); break; // gray - case '5': color = bvec(192, 64, 192); break; // magenta - case '6': color = bvec(255, 128, 0); break; // orange - case '7': color = bvec(255, 255, 255); break; // white - case '8': color = bvec( 96, 240, 255); break; // cyan - // provided color: everything else - } - gle::color(color, a); - } + if(c=='s') // save color + { + c = stack[sp]; + if(sp 0) --sp; c = stack[sp]; } // restore color + else stack[sp] = c; + switch(c) + { + case '0': color = bvec( 64, 255, 128); break; // green: player talk + case '1': color = bvec( 96, 160, 255); break; // blue: "echo" command + case '2': color = bvec(255, 192, 64); break; // yellow: gameplay messages + case '3': color = bvec(255, 64, 64); break; // red: important errors + case '4': color = bvec(128, 128, 128); break; // gray + case '5': color = bvec(192, 64, 192); break; // magenta + case '6': color = bvec(255, 128, 0); break; // orange + case '7': color = bvec(255, 255, 255); break; // white + case '8': color = bvec( 96, 240, 255); break; // cyan + // provided color: everything else + } + gle::color(color, a); + } } #define TEXTSKELETON \ - float y = 0, x = 0, scale = curfont->scale/float(curfont->defaulth);\ - int i;\ - for(i = 0; str[i]; i++)\ - {\ - TEXTINDEX(i)\ - int c = uchar(str[i]);\ - if(c=='\t') { x = TEXTTAB(x); TEXTWHITE(i) }\ - else if(c==' ') { x += scale*curfont->defaultw; TEXTWHITE(i) }\ - else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\ - else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\ - else if(curfont->chars.inrange(c-curfont->charoffset))\ - {\ - float cw = scale*curfont->chars[c-curfont->charoffset].advance;\ - if(cw <= 0) continue;\ - if(maxwidth != -1)\ - {\ - int j = i;\ - float w = cw;\ - for(; str[i+1]; i++)\ - {\ - int c = uchar(str[i+1]);\ - if(c=='\f') { if(str[i+2]) i++; continue; }\ - if(i-j > 16) break;\ - if(!curfont->chars.inrange(c-curfont->charoffset)) break;\ - float cw = scale*curfont->chars[c-curfont->charoffset].advance;\ - if(cw <= 0 || w + cw > maxwidth) break;\ - w += cw;\ - }\ - if(x + w > maxwidth && j!=0) { TEXTLINE(j-1) x = 0; y += FONTH; }\ - TEXTWORD\ - }\ - else\ - { TEXTCHAR(i) }\ - }\ - } + float y = 0, x = 0, scale = curfont->scale/float(curfont->defaulth);\ + int i;\ + for(i = 0; str[i]; i++)\ + {\ + TEXTINDEX(i)\ + int c = uchar(str[i]);\ + if(c=='\t') { x = TEXTTAB(x); TEXTWHITE(i) }\ + else if(c==' ') { x += scale*curfont->defaultw; TEXTWHITE(i) }\ + else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\ + else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\ + else if(curfont->chars.inrange(c-curfont->charoffset))\ + {\ + float cw = scale*curfont->chars[c-curfont->charoffset].advance;\ + if(cw <= 0) continue;\ + if(maxwidth != -1)\ + {\ + int j = i;\ + float w = cw;\ + for(; str[i+1]; i++)\ + {\ + int c = uchar(str[i+1]);\ + if(c=='\f') { if(str[i+2]) i++; continue; }\ + if(i-j > 16) break;\ + if(!curfont->chars.inrange(c-curfont->charoffset)) break;\ + float cw = scale*curfont->chars[c-curfont->charoffset].advance;\ + if(cw <= 0 || w + cw > maxwidth) break;\ + w += cw;\ + }\ + if(x + w > maxwidth && j!=0) { TEXTLINE(j-1) x = 0; y += FONTH; }\ + TEXTWORD\ + }\ + else\ + { TEXTCHAR(i) }\ + }\ + } //all the chars are guaranteed to be either drawable or color commands #define TEXTWORDSKELETON \ - for(; j <= i; j++)\ - {\ - TEXTINDEX(j)\ - int c = uchar(str[j]);\ - if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\ - else { float cw = scale*curfont->chars[c-curfont->charoffset].advance; TEXTCHAR(j) }\ - } + for(; j <= i; j++)\ + {\ + TEXTINDEX(j)\ + int c = uchar(str[j]);\ + if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\ + else { float cw = scale*curfont->chars[c-curfont->charoffset].advance; TEXTCHAR(j) }\ + } #define TEXTEND(cursor) if(cursor >= i) { do { TEXTINDEX(cursor); } while(0); } int text_visible(const char *str, float hitx, float hity, int maxwidth) { - #define TEXTINDEX(idx) - #define TEXTWHITE(idx) if(y+FONTH > hity && x >= hitx) return idx; - #define TEXTLINE(idx) if(y+FONTH > hity) return idx; - #define TEXTCOLOR(idx) - #define TEXTCHAR(idx) x += cw; TEXTWHITE(idx) - #define TEXTWORD TEXTWORDSKELETON - TEXTSKELETON - #undef TEXTINDEX - #undef TEXTWHITE - #undef TEXTLINE - #undef TEXTCOLOR - #undef TEXTCHAR - #undef TEXTWORD - return i; + #define TEXTINDEX(idx) + #define TEXTWHITE(idx) if(y+FONTH > hity && x >= hitx) return idx; + #define TEXTLINE(idx) if(y+FONTH > hity) return idx; + #define TEXTCOLOR(idx) + #define TEXTCHAR(idx) x += cw; TEXTWHITE(idx) + #define TEXTWORD TEXTWORDSKELETON + TEXTSKELETON + #undef TEXTINDEX + #undef TEXTWHITE + #undef TEXTLINE + #undef TEXTCOLOR + #undef TEXTCHAR + #undef TEXTWORD + return i; } //inverse of text_visible -void text_posf(const char *str, int cursor, float &cx, float &cy, int maxwidth) +void text_posf(const char *str, int cursor, float &cx, float &cy, int maxwidth) { - #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; break; } - #define TEXTWHITE(idx) - #define TEXTLINE(idx) - #define TEXTCOLOR(idx) - #define TEXTCHAR(idx) x += cw; - #define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break; - cx = cy = 0; - TEXTSKELETON - TEXTEND(cursor) - #undef TEXTINDEX - #undef TEXTWHITE - #undef TEXTLINE - #undef TEXTCOLOR - #undef TEXTCHAR - #undef TEXTWORD + #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; break; } + #define TEXTWHITE(idx) + #define TEXTLINE(idx) + #define TEXTCOLOR(idx) + #define TEXTCHAR(idx) x += cw; + #define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break; + cx = cy = 0; + TEXTSKELETON + TEXTEND(cursor) + #undef TEXTINDEX + #undef TEXTWHITE + #undef TEXTLINE + #undef TEXTCOLOR + #undef TEXTCHAR + #undef TEXTWORD } void text_boundsf(const char *str, float &width, float &height, int maxwidth) { - #define TEXTINDEX(idx) - #define TEXTWHITE(idx) - #define TEXTLINE(idx) if(x > width) width = x; - #define TEXTCOLOR(idx) - #define TEXTCHAR(idx) x += cw; - #define TEXTWORD x += w; - width = 0; - TEXTSKELETON - height = y + FONTH; - TEXTLINE(_) - #undef TEXTINDEX - #undef TEXTWHITE - #undef TEXTLINE - #undef TEXTCOLOR - #undef TEXTCHAR - #undef TEXTWORD + #define TEXTINDEX(idx) + #define TEXTWHITE(idx) + #define TEXTLINE(idx) if(x > width) width = x; + #define TEXTCOLOR(idx) + #define TEXTCHAR(idx) x += cw; + #define TEXTWORD x += w; + width = 0; + TEXTSKELETON + height = y + FONTH; + TEXTLINE(_) + #undef TEXTINDEX + #undef TEXTWHITE + #undef TEXTLINE + #undef TEXTCOLOR + #undef TEXTCHAR + #undef TEXTWORD } -void draw_text(const char *str, int left, int top, int r, int g, int b, int a, int cursor, int maxwidth) +void draw_text(const char *str, int left, int top, int r, int g, int b, int a, int cursor, int maxwidth) { - #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; } - #define TEXTWHITE(idx) - #define TEXTLINE(idx) - #define TEXTCOLOR(idx) if(usecolor) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a); - #define TEXTCHAR(idx) draw_char(tex, c, left+x, top+y, scale); x += cw; - #define TEXTWORD TEXTWORDSKELETON - char colorstack[10]; - colorstack[0] = 'c'; //indicate user color - bvec color(r, g, b); - int colorpos = 0; - float cx = -FONTW, cy = 0; - bool usecolor = true; - if(a < 0) { usecolor = false; a = -a; } - Texture *tex = curfont->texs[0]; - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, tex->id); - gle::color(color, a); - gle::defvertex(textmatrix ? 3 : 2); - gle::deftexcoord0(); - gle::begin(GL_QUADS); - TEXTSKELETON - TEXTEND(cursor) - xtraverts += gle::end(); - if(cursor >= 0 && (totalmillis/250)&1) - { - gle::color(color, a); - if(maxwidth != -1 && cx >= maxwidth) { cx = 0; cy += FONTH; } - draw_char(tex, '_', left+cx, top+cy, scale); - xtraverts += gle::end(); - } - #undef TEXTINDEX - #undef TEXTWHITE - #undef TEXTLINE - #undef TEXTCOLOR - #undef TEXTCHAR - #undef TEXTWORD + #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; } + #define TEXTWHITE(idx) + #define TEXTLINE(idx) + #define TEXTCOLOR(idx) if(usecolor) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a); + #define TEXTCHAR(idx) draw_char(tex, c, left+x, top+y, scale); x += cw; + #define TEXTWORD TEXTWORDSKELETON + char colorstack[10]; + colorstack[0] = 'c'; //indicate user color + bvec color(r, g, b); + int colorpos = 0; + float cx = -FONTW, cy = 0; + bool usecolor = true; + if(a < 0) { usecolor = false; a = -a; } + Texture *tex = curfont->texs[0]; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, tex->id); + gle::color(color, a); + gle::defvertex(textmatrix ? 3 : 2); + gle::deftexcoord0(); + gle::begin(GL_QUADS); + TEXTSKELETON + TEXTEND(cursor) + xtraverts += gle::end(); + if(cursor >= 0 && (totalmillis/250)&1) + { + gle::color(color, a); + if(maxwidth != -1 && cx >= maxwidth) { cx = 0; cy += FONTH; } + draw_char(tex, '_', left+cx, top+cy, scale); + xtraverts += gle::end(); + } + #undef TEXTINDEX + #undef TEXTWHITE + #undef TEXTLINE + #undef TEXTCOLOR + #undef TEXTCHAR + #undef TEXTWORD } void reloadfonts() { - enumerate(fonts, font, f, - loopv(f.texs) if(!reloadtexture(*f.texs[i])) fatal("failed to reload font texture"); - ); + enumerate(fonts, font, f, + loopv(f.texs) if(!reloadtexture(*f.texs[i])) fatal("failed to reload font texture"); + ); } diff --git a/src/engine/renderva.cpp b/src/engine/renderva.cpp index cef5ac5..dc3dca1 100644 --- a/src/engine/renderva.cpp +++ b/src/engine/renderva.cpp @@ -4,13 +4,13 @@ static inline void drawtris(GLsizei numindices, const GLvoid *indices, ushort minvert, ushort maxvert) { - glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, numindices, GL_UNSIGNED_SHORT, indices); - glde++; + glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, numindices, GL_UNSIGNED_SHORT, indices); + glde++; } static inline void drawvatris(vtxarray *va, GLsizei numindices, const GLvoid *indices) { - drawtris(numindices, indices, va->minvert, va->maxvert); + drawtris(numindices, indices, va->minvert, va->maxvert); } ///////// view frustrum culling /////////////////////// @@ -23,65 +23,65 @@ vtxarray *visibleva; bool isfoggedsphere(float rad, const vec &cv) { - loopi(4) if(vfcP[i].dist(cv) < -rad) return true; - float dist = vfcP[4].dist(cv); - return dist < -rad || dist > vfcDfog + rad; + loopi(4) if(vfcP[i].dist(cv) < -rad) return true; + float dist = vfcP[4].dist(cv); + return dist < -rad || dist > vfcDfog + rad; } int isvisiblesphere(float rad, const vec &cv) { - int v = VFC_FULL_VISIBLE; - float dist; + int v = VFC_FULL_VISIBLE; + float dist; - loopi(5) - { - dist = vfcP[i].dist(cv); - if(dist < -rad) return VFC_NOT_VISIBLE; - if(dist < rad) v = VFC_PART_VISIBLE; - } + loopi(5) + { + dist = vfcP[i].dist(cv); + if(dist < -rad) return VFC_NOT_VISIBLE; + if(dist < rad) v = VFC_PART_VISIBLE; + } - dist -= vfcDfog; - if(dist > rad) return VFC_FOGGED; //VFC_NOT_VISIBLE; // culling when fog is closer than size of world results in HOM - if(dist > -rad) v = VFC_PART_VISIBLE; + dist -= vfcDfog; + if(dist > rad) return VFC_FOGGED; //VFC_NOT_VISIBLE; // culling when fog is closer than size of world results in HOM + if(dist > -rad) v = VFC_PART_VISIBLE; - return v; + return v; } static inline int ishiddencube(const ivec &o, int size) { - loopi(5) if(o.dist(vfcP[i]) < -vfcDfar[i]*size) return true; - return false; + loopi(5) if(o.dist(vfcP[i]) < -vfcDfar[i]*size) return true; + return false; } static inline int isfoggedcube(const ivec &o, int size) { - loopi(4) if(o.dist(vfcP[i]) < -vfcDfar[i]*size) return true; - float dist = o.dist(vfcP[4]); - return dist < -vfcDfar[4]*size || dist > vfcDfog - vfcDnear[4]*size; + loopi(4) if(o.dist(vfcP[i]) < -vfcDfar[i]*size) return true; + float dist = o.dist(vfcP[4]); + return dist < -vfcDfar[4]*size || dist > vfcDfog - vfcDnear[4]*size; } int isvisiblecube(const ivec &o, int size) { - int v = VFC_FULL_VISIBLE; - float dist; + int v = VFC_FULL_VISIBLE; + float dist; - loopi(5) - { - dist = o.dist(vfcP[i]); - if(dist < -vfcDfar[i]*size) return VFC_NOT_VISIBLE; - if(dist < -vfcDnear[i]*size) v = VFC_PART_VISIBLE; - } + loopi(5) + { + dist = o.dist(vfcP[i]); + if(dist < -vfcDfar[i]*size) return VFC_NOT_VISIBLE; + if(dist < -vfcDnear[i]*size) v = VFC_PART_VISIBLE; + } - dist -= vfcDfog; - if(dist > -vfcDnear[4]*size) return VFC_FOGGED; - if(dist > -vfcDfar[4]*size) v = VFC_PART_VISIBLE; + dist -= vfcDfog; + if(dist > -vfcDnear[4]*size) return VFC_FOGGED; + if(dist > -vfcDfar[4]*size) v = VFC_PART_VISIBLE; - return v; + return v; } float vadist(vtxarray *va, const vec &p) { - return p.dist_to_bb(va->bbmin, va->bbmax); + return p.dist_to_bb(va->bbmin, va->bbmax); } #define VASORTSIZE 64 @@ -90,128 +90,128 @@ static vtxarray *vasort[VASORTSIZE]; void addvisibleva(vtxarray *va) { - float dist = vadist(va, camera1->o); - va->distance = int(dist); /*cv.dist(camera1->o) - va->size*SQRT3/2*/ + float dist = vadist(va, camera1->o); + va->distance = int(dist); /*cv.dist(camera1->o) - va->size*SQRT3/2*/ - int hash = clamp(int(dist*VASORTSIZE/worldsize), 0, VASORTSIZE-1); - vtxarray **prev = &vasort[hash], *cur = vasort[hash]; + int hash = clamp(int(dist*VASORTSIZE/worldsize), 0, VASORTSIZE-1); + vtxarray **prev = &vasort[hash], *cur = vasort[hash]; - while(cur && va->distance >= cur->distance) - { - prev = &cur->next; - cur = cur->next; - } + while(cur && va->distance >= cur->distance) + { + prev = &cur->next; + cur = cur->next; + } - va->next = *prev; - *prev = va; + va->next = *prev; + *prev = va; } void sortvisiblevas() { - visibleva = NULL; - vtxarray **last = &visibleva; - loopi(VASORTSIZE) if(vasort[i]) - { - vtxarray *va = vasort[i]; - *last = va; - while(va->next) va = va->next; - last = &va->next; - } + visibleva = NULL; + vtxarray **last = &visibleva; + loopi(VASORTSIZE) if(vasort[i]) + { + vtxarray *va = vasort[i]; + *last = va; + while(va->next) va = va->next; + last = &va->next; + } } void findvisiblevas(vector &vas, bool resetocclude = false) { - loopv(vas) - { - vtxarray &v = *vas[i]; - int prevvfc = resetocclude ? (int) VFC_NOT_VISIBLE : (int) v.curvfc; - v.curvfc = isvisiblecube(v.o, v.size); - if(v.curvfc!=VFC_NOT_VISIBLE) - { - addvisibleva(&v); - if(v.children.length()) findvisiblevas(v.children, prevvfc>=VFC_NOT_VISIBLE); - if(prevvfc>=VFC_NOT_VISIBLE) - { - v.occluded = !v.texs ? OCCLUDE_GEOM : OCCLUDE_NOTHING; - v.query = NULL; - } - } - } + loopv(vas) + { + vtxarray &v = *vas[i]; + int prevvfc = resetocclude ? (int) VFC_NOT_VISIBLE : (int) v.curvfc; + v.curvfc = isvisiblecube(v.o, v.size); + if(v.curvfc!=VFC_NOT_VISIBLE) + { + addvisibleva(&v); + if(v.children.length()) findvisiblevas(v.children, prevvfc>=VFC_NOT_VISIBLE); + if(prevvfc>=VFC_NOT_VISIBLE) + { + v.occluded = !v.texs ? OCCLUDE_GEOM : OCCLUDE_NOTHING; + v.query = NULL; + } + } + } } void calcvfcD() { - loopi(5) - { - plane &p = vfcP[i]; - vfcDnear[i] = vfcDfar[i] = 0; - loopk(3) if(p[k] > 0) vfcDfar[i] += p[k]; - else vfcDnear[i] += p[k]; - } + loopi(5) + { + plane &p = vfcP[i]; + vfcDnear[i] = vfcDfar[i] = 0; + loopk(3) if(p[k] > 0) vfcDfar[i] += p[k]; + else vfcDnear[i] += p[k]; + } } void setvfcP(float z, const vec &bbmin, const vec &bbmax) { - vec4 px = camprojmatrix.rowx(), py = camprojmatrix.rowy(), pz = camprojmatrix.rowz(), pw = camprojmatrix.roww(); - vfcP[0] = plane(vec4(pw).mul(-bbmin.x).add(px)).normalize(); // left plane - vfcP[1] = plane(vec4(pw).mul(bbmax.x).sub(px)).normalize(); // right plane - vfcP[2] = plane(vec4(pw).mul(-bbmin.y).add(py)).normalize(); // bottom plane - vfcP[3] = plane(vec4(pw).mul(bbmax.y).sub(py)).normalize(); // top plane - vfcP[4] = plane(vec4(pw).add(pz)).normalize(); // near/far planes - if(z >= 0) loopi(5) vfcP[i].reflectz(z); + vec4 px = camprojmatrix.rowx(), py = camprojmatrix.rowy(), pz = camprojmatrix.rowz(), pw = camprojmatrix.roww(); + vfcP[0] = plane(vec4(pw).mul(-bbmin.x).add(px)).normalize(); // left plane + vfcP[1] = plane(vec4(pw).mul(bbmax.x).sub(px)).normalize(); // right plane + vfcP[2] = plane(vec4(pw).mul(-bbmin.y).add(py)).normalize(); // bottom plane + vfcP[3] = plane(vec4(pw).mul(bbmax.y).sub(py)).normalize(); // top plane + vfcP[4] = plane(vec4(pw).add(pz)).normalize(); // near/far planes + if(z >= 0) loopi(5) vfcP[i].reflectz(z); - vfcDfog = fog; - calcvfcD(); + vfcDfog = fog; + calcvfcD(); } plane oldvfcP[5]; void savevfcP() { - memcpy(oldvfcP, vfcP, sizeof(vfcP)); + memcpy(oldvfcP, vfcP, sizeof(vfcP)); } void restorevfcP() { - memcpy(vfcP, oldvfcP, sizeof(vfcP)); - calcvfcD(); + memcpy(vfcP, oldvfcP, sizeof(vfcP)); + calcvfcD(); } void visiblecubes(bool cull) { - memclear(vasort); - - if(cull) - { - setvfcP(); - findvisiblevas(varoot); - sortvisiblevas(); - } - else - { - memclear(vfcP); - vfcDfog = 1000000; - memclear(vfcDnear); - memclear(vfcDfar); - visibleva = NULL; - loopv(valist) - { - vtxarray *va = valist[i]; - va->distance = 0; - va->curvfc = VFC_FULL_VISIBLE; - va->occluded = !va->texs ? OCCLUDE_GEOM : OCCLUDE_NOTHING; - va->query = NULL; - va->next = visibleva; - visibleva = va; - } - } + memclear(vasort); + + if(cull) + { + setvfcP(); + findvisiblevas(varoot); + sortvisiblevas(); + } + else + { + memclear(vfcP); + vfcDfog = 1000000; + memclear(vfcDnear); + memclear(vfcDfar); + visibleva = NULL; + loopv(valist) + { + vtxarray *va = valist[i]; + va->distance = 0; + va->curvfc = VFC_FULL_VISIBLE; + va->occluded = !va->texs ? OCCLUDE_GEOM : OCCLUDE_NOTHING; + va->query = NULL; + va->next = visibleva; + visibleva = va; + } + } } static inline bool insideva(const vtxarray *va, const vec &v, int margin = 2) { - int size = va->size + margin; - return v.x>=va->o.x-margin && v.y>=va->o.y-margin && v.z>=va->o.z-margin && - v.x<=va->o.x+size && v.y<=va->o.y+size && v.z<=va->o.z+size; + int size = va->size + margin; + return v.x>=va->o.x-margin && v.y>=va->o.y-margin && v.z>=va->o.z-margin && + v.x<=va->o.x+size && v.y<=va->o.y+size && v.z<=va->o.z+size; } ///////// occlusion queries ///////////// @@ -221,37 +221,37 @@ static inline bool insideva(const vtxarray *va, const vec &v, int margin = 2) struct queryframe { - int cur, max; - occludequery queries[MAXQUERY]; - - queryframe() : cur(0), max(0) {} - - void flip() { loopi(cur) queries[i].owner = NULL; cur = 0; } - - occludequery *newquery(void *owner) - { - if(cur >= max) - { - if(max >= MAXQUERY) return NULL; - glGenQueries_(1, &queries[max++].id); - } - occludequery *query = &queries[cur++]; - query->owner = owner; - query->fragments = -1; - return query; - } - - void reset() { loopi(max) queries[i].owner = NULL; } - - void cleanup() - { - loopi(max) - { - glDeleteQueries_(1, &queries[i].id); - queries[i].owner = NULL; - } - cur = max = 0; - } + int cur, max; + occludequery queries[MAXQUERY]; + + queryframe() : cur(0), max(0) {} + + void flip() { loopi(cur) queries[i].owner = NULL; cur = 0; } + + occludequery *newquery(void *owner) + { + if(cur >= max) + { + if(max >= MAXQUERY) return NULL; + glGenQueries_(1, &queries[max++].id); + } + occludequery *query = &queries[cur++]; + query->owner = owner; + query->fragments = -1; + return query; + } + + void reset() { loopi(max) queries[i].owner = NULL; } + + void cleanup() + { + loopi(max) + { + glDeleteQueries_(1, &queries[i].id); + queries[i].owner = NULL; + } + cur = max = 0; + } }; static queryframe queryframes[MAXQUERYFRAMES]; @@ -259,28 +259,28 @@ static uint flipquery = 0; int getnumqueries() { - return queryframes[flipquery].cur; + return queryframes[flipquery].cur; } void flipqueries() { - flipquery = (flipquery + 1) % MAXQUERYFRAMES; - queryframes[flipquery].flip(); + flipquery = (flipquery + 1) % MAXQUERYFRAMES; + queryframes[flipquery].flip(); } occludequery *newquery(void *owner) { - return queryframes[flipquery].newquery(owner); + return queryframes[flipquery].newquery(owner); } void resetqueries() { - loopi(MAXQUERYFRAMES) queryframes[i].reset(); + loopi(MAXQUERYFRAMES) queryframes[i].reset(); } void clearqueries() { - loopi(MAXQUERYFRAMES) queryframes[i].cleanup(); + loopi(MAXQUERYFRAMES) queryframes[i].cleanup(); } VAR(oqfrags, 0, 8, 64); @@ -288,107 +288,107 @@ VAR(oqwait, 0, 1, 1); void startquery(occludequery *query) { - glBeginQuery_(GL_SAMPLES_PASSED, query->id); + glBeginQuery_(GL_SAMPLES_PASSED, query->id); } void endquery(occludequery *query) { - glEndQuery_(GL_SAMPLES_PASSED); + glEndQuery_(GL_SAMPLES_PASSED); } bool checkquery(occludequery *query, bool nowait) { - GLuint fragments; - if(query->fragments >= 0) fragments = query->fragments; - else - { - if(nowait || !oqwait) - { - GLint avail; - glGetQueryObjectiv_(query->id, GL_QUERY_RESULT_AVAILABLE, &avail); - if(!avail) return false; - } - glGetQueryObjectuiv_(query->id, GL_QUERY_RESULT, &fragments); - query->fragments = fragments; - } - return fragments < uint(oqfrags); + GLuint fragments; + if(query->fragments >= 0) fragments = query->fragments; + else + { + if(nowait || !oqwait) + { + GLint avail; + glGetQueryObjectiv_(query->id, GL_QUERY_RESULT_AVAILABLE, &avail); + if(!avail) return false; + } + glGetQueryObjectuiv_(query->id, GL_QUERY_RESULT, &fragments); + query->fragments = fragments; + } + return fragments < uint(oqfrags); } static GLuint bbvbo = 0, bbebo = 0; static void setupbb() { - if(!bbvbo) - { - glGenBuffers_(1, &bbvbo); - gle::bindvbo(bbvbo); - vec verts[8]; - loopi(8) verts[i] = vec(i&1, (i>>1)&1, (i>>2)&1); - glBufferData_(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); - gle::clearvbo(); - } - if(!bbebo) - { - glGenBuffers_(1, &bbebo); - gle::bindebo(bbebo); - GLushort tris[3*2*6]; - #define GENFACEORIENT(orient, v0, v1, v2, v3) do { \ - int offset = orient*3*2; \ - tris[offset + 0] = v0; \ - tris[offset + 1] = v1; \ - tris[offset + 2] = v2; \ - tris[offset + 3] = v0; \ - tris[offset + 4] = v2; \ - tris[offset + 5] = v3; \ - } while(0); - #define GENFACEVERT(orient, vert, ox,oy,oz, rx,ry,rz) (ox | oy | oz) - GENFACEVERTS(0, 1, 0, 2, 0, 4, , , , , , ) - #undef GENFACEORIENT - #undef GENFACEVERT - glBufferData_(GL_ELEMENT_ARRAY_BUFFER, sizeof(tris), tris, GL_STATIC_DRAW); - gle::clearebo(); - } + if(!bbvbo) + { + glGenBuffers_(1, &bbvbo); + gle::bindvbo(bbvbo); + vec verts[8]; + loopi(8) verts[i] = vec(i&1, (i>>1)&1, (i>>2)&1); + glBufferData_(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); + gle::clearvbo(); + } + if(!bbebo) + { + glGenBuffers_(1, &bbebo); + gle::bindebo(bbebo); + GLushort tris[3*2*6]; + #define GENFACEORIENT(orient, v0, v1, v2, v3) do { \ + int offset = orient*3*2; \ + tris[offset + 0] = v0; \ + tris[offset + 1] = v1; \ + tris[offset + 2] = v2; \ + tris[offset + 3] = v0; \ + tris[offset + 4] = v2; \ + tris[offset + 5] = v3; \ + } while(0); + #define GENFACEVERT(orient, vert, ox,oy,oz, rx,ry,rz) (ox | oy | oz) + GENFACEVERTS(0, 1, 0, 2, 0, 4, , , , , , ) + #undef GENFACEORIENT + #undef GENFACEVERT + glBufferData_(GL_ELEMENT_ARRAY_BUFFER, sizeof(tris), tris, GL_STATIC_DRAW); + gle::clearebo(); + } } static void cleanupbb() { - if(bbvbo) { glDeleteBuffers_(1, &bbvbo); bbvbo = 0; } - if(bbebo) { glDeleteBuffers_(1, &bbebo); bbebo = 0; } + if(bbvbo) { glDeleteBuffers_(1, &bbvbo); bbvbo = 0; } + if(bbebo) { glDeleteBuffers_(1, &bbebo); bbebo = 0; } } void startbb(bool mask) { - setupbb(); - gle::bindvbo(bbvbo); - gle::bindebo(bbebo); - gle::vertexpointer(sizeof(vec), (const vec *)0); - gle::enablevertex(); - SETSHADER(bbquery); - if(mask) - { - glDepthMask(GL_FALSE); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - } + setupbb(); + gle::bindvbo(bbvbo); + gle::bindebo(bbebo); + gle::vertexpointer(sizeof(vec), (const vec *)0); + gle::enablevertex(); + SETSHADER(bbquery); + if(mask) + { + glDepthMask(GL_FALSE); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + } } void endbb(bool mask) { - gle::disablevertex(); - gle::clearvbo(); - gle::clearebo(); - if(mask) - { - glDepthMask(GL_TRUE); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - } + gle::disablevertex(); + gle::clearvbo(); + gle::clearebo(); + if(mask) + { + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } } void drawbb(const ivec &bo, const ivec &br) { - LOCALPARAMF(bborigin, bo.x, bo.y, bo.z); - LOCALPARAMF(bbsize, br.x, br.y, br.z); - glDrawRangeElements_(GL_TRIANGLES, 0, 8-1, 3*2*6, GL_UNSIGNED_SHORT, (ushort *)0); - xtraverts += 8; + LOCALPARAMF(bborigin, bo.x, bo.y, bo.z); + LOCALPARAMF(bbsize, br.x, br.y, br.z); + glDrawRangeElements_(GL_TRIANGLES, 0, 8-1, 3*2*6, GL_UNSIGNED_SHORT, (ushort *)0); + xtraverts += 8; } extern int octaentsize; @@ -397,214 +397,214 @@ static octaentities *visiblemms, **lastvisiblemms; static inline bool insideoe(const octaentities *oe, const vec &v, int margin = 1) { - return v.x>=oe->bbmin.x-margin && v.y>=oe->bbmin.y-margin && v.z>=oe->bbmin.z-margin && - v.x<=oe->bbmax.x+margin && v.y<=oe->bbmax.y+margin && v.z<=oe->bbmax.z+margin; + return v.x>=oe->bbmin.x-margin && v.y>=oe->bbmin.y-margin && v.z>=oe->bbmin.z-margin && + v.x<=oe->bbmax.x+margin && v.y<=oe->bbmax.y+margin && v.z<=oe->bbmax.z+margin; } void findvisiblemms(const vector &ents, bool doquery) { - visiblemms = NULL; - lastvisiblemms = &visiblemms; - for(vtxarray *va = visibleva; va; va = va->next) - { - if(va->mapmodels.empty() || va->curvfc >= VFC_FOGGED || va->occluded >= OCCLUDE_BB) continue; - loopv(va->mapmodels) - { - octaentities *oe = va->mapmodels[i]; - if(isfoggedcube(oe->o, oe->size)) continue; - - bool occluded = doquery && oe->query && oe->query->owner == oe && checkquery(oe->query); - if(occluded) - { - oe->distance = -1; - - oe->next = NULL; - *lastvisiblemms = oe; - lastvisiblemms = &oe->next; - } - else - { - int visible = 0; - loopv(oe->mapmodels) - { - extentity &e = *ents[oe->mapmodels[i]]; - if(e.flags&EF_NOVIS) continue; - e.flags |= EF_RENDER; - ++visible; - } - if(!visible) continue; - - oe->distance = int(camera1->o.dist_to_bb(oe->o, oe->size)); - - octaentities **prev = &visiblemms, *cur = visiblemms; - while(cur && cur->distance >= 0 && oe->distance > cur->distance) - { - prev = &cur->next; - cur = cur->next; - } - - if(*prev == NULL) lastvisiblemms = &oe->next; - oe->next = *prev; - *prev = oe; - } - } - } + visiblemms = NULL; + lastvisiblemms = &visiblemms; + for(vtxarray *va = visibleva; va; va = va->next) + { + if(va->mapmodels.empty() || va->curvfc >= VFC_FOGGED || va->occluded >= OCCLUDE_BB) continue; + loopv(va->mapmodels) + { + octaentities *oe = va->mapmodels[i]; + if(isfoggedcube(oe->o, oe->size)) continue; + + bool occluded = doquery && oe->query && oe->query->owner == oe && checkquery(oe->query); + if(occluded) + { + oe->distance = -1; + + oe->next = NULL; + *lastvisiblemms = oe; + lastvisiblemms = &oe->next; + } + else + { + int visible = 0; + loopv(oe->mapmodels) + { + extentity &e = *ents[oe->mapmodels[i]]; + if(e.flags&EF_NOVIS) continue; + e.flags |= EF_RENDER; + ++visible; + } + if(!visible) continue; + + oe->distance = int(camera1->o.dist_to_bb(oe->o, oe->size)); + + octaentities **prev = &visiblemms, *cur = visiblemms; + while(cur && cur->distance >= 0 && oe->distance > cur->distance) + { + prev = &cur->next; + cur = cur->next; + } + + if(*prev == NULL) lastvisiblemms = &oe->next; + oe->next = *prev; + *prev = oe; + } + } + } } VAR(oqmm, 0, 4, 8); void rendermapmodel(extentity &e) { - int anim = ANIM_MAPMODEL|ANIM_LOOP, basetime = 0; - mapmodelinfo *mmi = getmminfo(e.attr2); - if(mmi) rendermodel(&e.light, mmi->name, anim, e.o, e.attr1, 0, MDL_CULL_VFC | MDL_CULL_DIST | MDL_DYNLIGHT, NULL, NULL, basetime); + int anim = ANIM_MAPMODEL|ANIM_LOOP, basetime = 0; + mapmodelinfo *mmi = getmminfo(e.attr2); + if(mmi) rendermodel(&e.light, mmi->name, anim, e.o, e.attr1, 0, MDL_CULL_VFC | MDL_CULL_DIST | MDL_DYNLIGHT, NULL, NULL, basetime); } vtxarray *reflectedva; void renderreflectedmapmodels() { - const vector &ents = entities::getents(); - - octaentities *mms = visiblemms; - if(reflecting) - { - octaentities **lastmms = &mms; - for(vtxarray *va = reflectedva; va; va = va->rnext) - { - if(va->mapmodels.empty() || va->distance > reflectdist) continue; - loopv(va->mapmodels) - { - octaentities *oe = va->mapmodels[i]; - *lastmms = oe; - lastmms = &oe->rnext; - } - } - *lastmms = NULL; - } - for(octaentities *oe = mms; oe; oe = reflecting ? oe->rnext : oe->next) if(reflecting || oe->distance >= 0) - { - if(reflecting || refracting>0 ? oe->bbmax.z <= reflectz : oe->bbmin.z >= reflectz) continue; - if(isfoggedcube(oe->o, oe->size)) continue; - loopv(oe->mapmodels) - { - extentity &e = *ents[oe->mapmodels[i]]; - if(e.flags&(EF_NOVIS | EF_RENDER)) continue; - e.flags |= EF_RENDER; - } - } - if(mms) - { - startmodelbatches(); - for(octaentities *oe = mms; oe; oe = reflecting ? oe->rnext : oe->next) - { - loopv(oe->mapmodels) - { - extentity &e = *ents[oe->mapmodels[i]]; - if(!(e.flags&EF_RENDER)) continue; - rendermapmodel(e); - e.flags &= ~EF_RENDER; - } - } - endmodelbatches(); - } + const vector &ents = entities::getents(); + + octaentities *mms = visiblemms; + if(reflecting) + { + octaentities **lastmms = &mms; + for(vtxarray *va = reflectedva; va; va = va->rnext) + { + if(va->mapmodels.empty() || va->distance > reflectdist) continue; + loopv(va->mapmodels) + { + octaentities *oe = va->mapmodels[i]; + *lastmms = oe; + lastmms = &oe->rnext; + } + } + *lastmms = NULL; + } + for(octaentities *oe = mms; oe; oe = reflecting ? oe->rnext : oe->next) if(reflecting || oe->distance >= 0) + { + if(reflecting || refracting>0 ? oe->bbmax.z <= reflectz : oe->bbmin.z >= reflectz) continue; + if(isfoggedcube(oe->o, oe->size)) continue; + loopv(oe->mapmodels) + { + extentity &e = *ents[oe->mapmodels[i]]; + if(e.flags&(EF_NOVIS | EF_RENDER)) continue; + e.flags |= EF_RENDER; + } + } + if(mms) + { + startmodelbatches(); + for(octaentities *oe = mms; oe; oe = reflecting ? oe->rnext : oe->next) + { + loopv(oe->mapmodels) + { + extentity &e = *ents[oe->mapmodels[i]]; + if(!(e.flags&EF_RENDER)) continue; + rendermapmodel(e); + e.flags &= ~EF_RENDER; + } + } + endmodelbatches(); + } } void rendermapmodels() { - static int skipoq = 0; - bool doquery = !drawtex && oqfrags && oqmm; - const vector &ents = entities::getents(); - findvisiblemms(ents, doquery); - - startmodelbatches(); - for(octaentities *oe = visiblemms; oe; oe = oe->next) if(oe->distance>=0) - { - bool rendered = false; - loopv(oe->mapmodels) - { - extentity &e = *ents[oe->mapmodels[i]]; - if(!(e.flags&EF_RENDER)) continue; - if(!rendered) - { - rendered = true; - oe->query = doquery && oe->distance>0 && !(++skipoq%oqmm) ? newquery(oe) : NULL; - if(oe->query) startmodelquery(oe->query); - } - rendermapmodel(e); - e.flags &= ~EF_RENDER; - } - if(rendered && oe->query) endmodelquery(); - } - endmodelbatches(); - - bool queried = true; - for(octaentities *oe = visiblemms; oe; oe = oe->next) if(oe->distance<0) - { - oe->query = doquery && !insideoe(oe, camera1->o) ? newquery(oe) : NULL; - if(!oe->query) continue; - if(queried) - { - startbb(); - queried = false; - } - startquery(oe->query); - drawbb(oe->bbmin, ivec(oe->bbmax).sub(oe->bbmin)); - endquery(oe->query); - } - if(!queried) - { - endbb(); - } + static int skipoq = 0; + bool doquery = !drawtex && oqfrags && oqmm; + const vector &ents = entities::getents(); + findvisiblemms(ents, doquery); + + startmodelbatches(); + for(octaentities *oe = visiblemms; oe; oe = oe->next) if(oe->distance>=0) + { + bool rendered = false; + loopv(oe->mapmodels) + { + extentity &e = *ents[oe->mapmodels[i]]; + if(!(e.flags&EF_RENDER)) continue; + if(!rendered) + { + rendered = true; + oe->query = doquery && oe->distance>0 && !(++skipoq%oqmm) ? newquery(oe) : NULL; + if(oe->query) startmodelquery(oe->query); + } + rendermapmodel(e); + e.flags &= ~EF_RENDER; + } + if(rendered && oe->query) endmodelquery(); + } + endmodelbatches(); + + bool queried = true; + for(octaentities *oe = visiblemms; oe; oe = oe->next) if(oe->distance<0) + { + oe->query = doquery && !insideoe(oe, camera1->o) ? newquery(oe) : NULL; + if(!oe->query) continue; + if(queried) + { + startbb(); + queried = false; + } + startquery(oe->query); + drawbb(oe->bbmin, ivec(oe->bbmax).sub(oe->bbmin)); + endquery(oe->query); + } + if(!queried) + { + endbb(); + } } static inline bool bbinsideva(const ivec &bo, const ivec &br, vtxarray *va) { - return bo.x >= va->bbmin.x && bo.y >= va->bbmin.y && bo.z >= va->bbmin.z && - br.x <= va->bbmax.x && br.y <= va->bbmax.y && br.z <= va->bbmax.z; + return bo.x >= va->bbmin.x && bo.y >= va->bbmin.y && bo.z >= va->bbmin.z && + br.x <= va->bbmax.x && br.y <= va->bbmax.y && br.z <= va->bbmax.z; } static inline bool bboccluded(const ivec &bo, const ivec &br, cube *c, const ivec &o, int size) { - loopoctabox(o, size, bo, br) - { - ivec co(i, o, size); - if(c[i].ext && c[i].ext->va) - { - vtxarray *va = c[i].ext->va; - if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) continue; - } - if(c[i].children && bboccluded(bo, br, c[i].children, co, size>>1)) continue; - return false; - } - return true; + loopoctabox(o, size, bo, br) + { + ivec co(i, o, size); + if(c[i].ext && c[i].ext->va) + { + vtxarray *va = c[i].ext->va; + if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) continue; + } + if(c[i].children && bboccluded(bo, br, c[i].children, co, size>>1)) continue; + return false; + } + return true; } bool bboccluded(const ivec &bo, const ivec &br) { - int diff = (bo.x^br.x) | (bo.y^br.y) | (bo.z^br.z); - if(diff&~((1<ext && c->ext->va) - { - vtxarray *va = c->ext->va; - if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true; - } - scale--; - while(c->children && !(diff&(1<children[octastep(bo.x, bo.y, bo.z, scale)]; - if(c->ext && c->ext->va) - { - vtxarray *va = c->ext->va; - if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true; - } - scale--; - } - if(c->children) return bboccluded(bo, br, c->children, ivec(bo).mask(~((2<ext && c->ext->va) + { + vtxarray *va = c->ext->va; + if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true; + } + scale--; + while(c->children && !(diff&(1<children[octastep(bo.x, bo.y, bo.z, scale)]; + if(c->ext && c->ext->va) + { + vtxarray *va = c->ext->va; + if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true; + } + scale--; + } + if(c->children) return bboccluded(bo, br, c->children, ivec(bo).mask(~((2<set(); + notextureshader->set(); - gle::enablevertex(); + gle::enablevertex(); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - gle::color(vec::hexcolor(outlinecolour)); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + gle::color(vec::hexcolor(outlinecolour)); - enablepolygonoffset(GL_POLYGON_OFFSET_LINE); + enablepolygonoffset(GL_POLYGON_OFFSET_LINE); - if(!dtoutline) glDisable(GL_DEPTH_TEST); + if(!dtoutline) glDisable(GL_DEPTH_TEST); - vtxarray *prev = NULL; - for(vtxarray *va = visibleva; va; va = va->next) - { - if(va->occluded >= OCCLUDE_BB) continue; - if(!va->alphaback && !va->alphafront && (!va->texs || va->occluded >= OCCLUDE_GEOM)) continue; + vtxarray *prev = NULL; + for(vtxarray *va = visibleva; va; va = va->next) + { + if(va->occluded >= OCCLUDE_BB) continue; + if(!va->alphaback && !va->alphafront && (!va->texs || va->occluded >= OCCLUDE_GEOM)) continue; - if(!prev || va->vbuf != prev->vbuf) - { - gle::bindvbo(va->vbuf); - gle::bindebo(va->ebuf); - const vertex *ptr = 0; - gle::vertexpointer(sizeof(vertex), ptr->pos.v); - } + if(!prev || va->vbuf != prev->vbuf) + { + gle::bindvbo(va->vbuf); + gle::bindebo(va->ebuf); + const vertex *ptr = 0; + gle::vertexpointer(sizeof(vertex), ptr->pos.v); + } - if(va->texs && va->occluded < OCCLUDE_GEOM) - { - drawvatris(va, 3*va->tris, va->edata); - xtravertsva += va->verts; - } - if(va->alphatris) - { - drawvatris(va, 3*va->alphatris, &va->edata[3*(va->tris + va->blendtris)]); - xtravertsva += 3*va->alphatris; - } + if(va->texs && va->occluded < OCCLUDE_GEOM) + { + drawvatris(va, 3*va->tris, va->edata); + xtravertsva += va->verts; + } + if(va->alphatris) + { + drawvatris(va, 3*va->alphatris, &va->edata[3*(va->tris + va->blendtris)]); + xtravertsva += 3*va->alphatris; + } - prev = va; - } + prev = va; + } - if(!dtoutline) glEnable(GL_DEPTH_TEST); + if(!dtoutline) glEnable(GL_DEPTH_TEST); - disablepolygonoffset(GL_POLYGON_OFFSET_LINE); + disablepolygonoffset(GL_POLYGON_OFFSET_LINE); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - gle::clearvbo(); - gle::clearebo(); - gle::disablevertex(); + gle::clearvbo(); + gle::clearebo(); + gle::disablevertex(); } HVAR(blendbrushcolor, 0, 0x0000C0, 0xFFFFFF); void renderblendbrush(GLuint tex, float x, float y, float w, float h) { - SETSHADER(blendbrush); + SETSHADER(blendbrush); - gle::enablevertex(); + gle::enablevertex(); - glDepthFunc(GL_LEQUAL); + glDepthFunc(GL_LEQUAL); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, tex); - gle::color(vec::hexcolor(blendbrushcolor), 0.25f); + glBindTexture(GL_TEXTURE_2D, tex); + gle::color(vec::hexcolor(blendbrushcolor), 0.25f); - LOCALPARAMF(texgenS, 1.0f/w, 0, 0, -x/w); - LOCALPARAMF(texgenT, 0, 1.0f/h, 0, -y/h); + LOCALPARAMF(texgenS, 1.0f/w, 0, 0, -x/w); + LOCALPARAMF(texgenT, 0, 1.0f/h, 0, -y/h); - vtxarray *prev = NULL; - for(vtxarray *va = visibleva; va; va = va->next) - { - if(!va->texs || va->occluded >= OCCLUDE_GEOM) continue; - if(va->o.x + va->size <= x || va->o.y + va->size <= y || va->o.x >= x + w || va->o.y >= y + h) continue; + vtxarray *prev = NULL; + for(vtxarray *va = visibleva; va; va = va->next) + { + if(!va->texs || va->occluded >= OCCLUDE_GEOM) continue; + if(va->o.x + va->size <= x || va->o.y + va->size <= y || va->o.x >= x + w || va->o.y >= y + h) continue; - if(!prev || va->vbuf != prev->vbuf) - { - gle::bindvbo(va->vbuf); - gle::bindebo(va->ebuf); - const vertex *ptr = 0; - gle::vertexpointer(sizeof(vertex), ptr->pos.v); - } + if(!prev || va->vbuf != prev->vbuf) + { + gle::bindvbo(va->vbuf); + gle::bindebo(va->ebuf); + const vertex *ptr = 0; + gle::vertexpointer(sizeof(vertex), ptr->pos.v); + } - drawvatris(va, 3*va->tris, va->edata); - xtravertsva += va->verts; + drawvatris(va, 3*va->tris, va->edata); + xtravertsva += va->verts; - prev = va; - } + prev = va; + } - glDisable(GL_BLEND); + glDisable(GL_BLEND); - glDepthFunc(GL_LESS); + glDepthFunc(GL_LESS); - gle::clearvbo(); - gle::clearebo(); - gle::disablevertex(); + gle::clearvbo(); + gle::clearebo(); + gle::disablevertex(); } void rendershadowmapreceivers() { - SETSHADER(shadowmapreceiver); + SETSHADER(shadowmapreceiver); - gle::enablevertex(); + gle::enablevertex(); - glCullFace(GL_FRONT); - glDepthMask(GL_FALSE); - glDepthFunc(GL_GREATER); + glCullFace(GL_FRONT); + glDepthMask(GL_FALSE); + glDepthFunc(GL_GREATER); - extern int ati_minmax_bug; - if(!ati_minmax_bug) glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE); + extern int ati_minmax_bug; + if(!ati_minmax_bug) glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE); - glEnable(GL_BLEND); - glBlendEquation_(GL_MAX); - glBlendFunc(GL_ONE, GL_ONE); + glEnable(GL_BLEND); + glBlendEquation_(GL_MAX); + glBlendFunc(GL_ONE, GL_ONE); - vtxarray *prev = NULL; - for(vtxarray *va = visibleva; va; va = va->next) - { - if(!va->texs || va->curvfc >= VFC_FOGGED || !isshadowmapreceiver(va)) continue; + vtxarray *prev = NULL; + for(vtxarray *va = visibleva; va; va = va->next) + { + if(!va->texs || va->curvfc >= VFC_FOGGED || !isshadowmapreceiver(va)) continue; - if(!prev || va->vbuf != prev->vbuf) - { - gle::bindvbo(va->vbuf); - gle::bindebo(va->ebuf); - const vertex *ptr = 0; - gle::vertexpointer(sizeof(vertex), ptr->pos.v); - } + if(!prev || va->vbuf != prev->vbuf) + { + gle::bindvbo(va->vbuf); + gle::bindebo(va->ebuf); + const vertex *ptr = 0; + gle::vertexpointer(sizeof(vertex), ptr->pos.v); + } - drawvatris(va, 3*va->tris, va->edata); - xtravertsva += va->verts; + drawvatris(va, 3*va->tris, va->edata); + xtravertsva += va->verts; - prev = va; - } + prev = va; + } - glDisable(GL_BLEND); - glBlendEquation_(GL_FUNC_ADD); + glDisable(GL_BLEND); + glBlendEquation_(GL_FUNC_ADD); - glCullFace(GL_BACK); - glDepthMask(GL_TRUE); - glDepthFunc(GL_LESS); + glCullFace(GL_BACK); + glDepthMask(GL_TRUE); + glDepthFunc(GL_LESS); - if(!ati_minmax_bug) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + if(!ati_minmax_bug) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - gle::clearvbo(); - gle::clearebo(); - gle::disablevertex(); + gle::clearvbo(); + gle::clearebo(); + gle::disablevertex(); } void renderdepthobstacles(const vec &bbmin, const vec &bbmax, float scale, float *ranges, int numranges) { - float scales[4] = { 0, 0, 0, 0 }, offsets[4] = { 0, 0, 0, 0 }; - if(numranges < 0) - { - SETSHADER(depthfxsplitworld); - - loopi(-numranges) - { - if(!i) scales[i] = 1.0f/scale; - else scales[i] = scales[i-1]*256; - } - } - else - { - SETSHADER(depthfxworld); - - if(!numranges) loopi(4) scales[i] = 1.0f/scale; - else loopi(numranges) - { - scales[i] = 1.0f/scale; - offsets[i] = -ranges[i]/scale; - } - } - LOCALPARAMF(depthscale, scales[0], scales[1], scales[2], scales[3]); - LOCALPARAMF(depthoffsets, offsets[0], offsets[1], offsets[2], offsets[3]); - - gle::enablevertex(); - - vtxarray *prev = NULL; - for(vtxarray *va = visibleva; va; va = va->next) - { - if(!va->texs || va->occluded >= OCCLUDE_GEOM || - va->o.x > bbmax.x || va->o.y > bbmax.y || va->o.z > bbmax.z || - va->o.x + va->size < bbmin.x || va->o.y + va->size < bbmin.y || va->o.z + va->size < bbmin.z) - continue; - - if(!prev || va->vbuf != prev->vbuf) - { - gle::bindvbo(va->vbuf); - gle::bindebo(va->ebuf); - const vertex *ptr = 0; - gle::vertexpointer(sizeof(vertex), ptr->pos.v); - } - - drawvatris(va, 3*va->tris, va->edata); - xtravertsva += va->verts; - if(va->alphatris > 0) - { - drawvatris(va, 3*va->alphatris, va->edata + 3*(va->tris + va->blendtris)); - xtravertsva += 3*va->alphatris; - } - - prev = va; - } - - gle::clearvbo(); - gle::clearebo(); - gle::disablevertex(); + float scales[4] = { 0, 0, 0, 0 }, offsets[4] = { 0, 0, 0, 0 }; + if(numranges < 0) + { + SETSHADER(depthfxsplitworld); + + loopi(-numranges) + { + if(!i) scales[i] = 1.0f/scale; + else scales[i] = scales[i-1]*256; + } + } + else + { + SETSHADER(depthfxworld); + + if(!numranges) loopi(4) scales[i] = 1.0f/scale; + else loopi(numranges) + { + scales[i] = 1.0f/scale; + offsets[i] = -ranges[i]/scale; + } + } + LOCALPARAMF(depthscale, scales[0], scales[1], scales[2], scales[3]); + LOCALPARAMF(depthoffsets, offsets[0], offsets[1], offsets[2], offsets[3]); + + gle::enablevertex(); + + vtxarray *prev = NULL; + for(vtxarray *va = visibleva; va; va = va->next) + { + if(!va->texs || va->occluded >= OCCLUDE_GEOM || + va->o.x > bbmax.x || va->o.y > bbmax.y || va->o.z > bbmax.z || + va->o.x + va->size < bbmin.x || va->o.y + va->size < bbmin.y || va->o.z + va->size < bbmin.z) + continue; + + if(!prev || va->vbuf != prev->vbuf) + { + gle::bindvbo(va->vbuf); + gle::bindebo(va->ebuf); + const vertex *ptr = 0; + gle::vertexpointer(sizeof(vertex), ptr->pos.v); + } + + drawvatris(va, 3*va->tris, va->edata); + xtravertsva += va->verts; + if(va->alphatris > 0) + { + drawvatris(va, 3*va->alphatris, va->edata + 3*(va->tris + va->blendtris)); + xtravertsva += 3*va->alphatris; + } + + prev = va; + } + + gle::clearvbo(); + gle::clearebo(); + gle::disablevertex(); } VAR(oqdist, 0, 256, 1024); @@ -828,101 +828,101 @@ VAR(envpass, 0, 1, 1); struct renderstate { - bool colormask, depthmask, blending; - int alphaing; - GLuint vbuf; - bool vattribs, vquery; - vec colorscale, lightcolor; - float alphascale; - GLuint textures[8]; - Slot *slot, *texgenslot; - VSlot *vslot, *texgenvslot; - vec2 texgenscroll; - int texgendim; - int visibledynlights; - uint dynlightmask; - - renderstate() : colormask(true), depthmask(true), blending(false), alphaing(0), vbuf(0), vattribs(false), vquery(false), colorscale(1, 1, 1), alphascale(0), slot(NULL), texgenslot(NULL), vslot(NULL), texgenvslot(NULL), texgenscroll(0, 0), texgendim(-1), visibledynlights(0), dynlightmask(0) - { - loopk(8) textures[k] = 0; - } + bool colormask, depthmask, blending; + int alphaing; + GLuint vbuf; + bool vattribs, vquery; + vec colorscale, lightcolor; + float alphascale; + GLuint textures[8]; + Slot *slot, *texgenslot; + VSlot *vslot, *texgenvslot; + vec2 texgenscroll; + int texgendim; + int visibledynlights; + uint dynlightmask; + + renderstate() : colormask(true), depthmask(true), blending(false), alphaing(0), vbuf(0), vattribs(false), vquery(false), colorscale(1, 1, 1), alphascale(0), slot(NULL), texgenslot(NULL), vslot(NULL), texgenvslot(NULL), texgenscroll(0, 0), texgendim(-1), visibledynlights(0), dynlightmask(0) + { + loopk(8) textures[k] = 0; + } }; static inline void disablevbuf(renderstate &cur) { - gle::clearvbo(); - gle::clearebo(); - cur.vbuf = 0; + gle::clearvbo(); + gle::clearebo(); + cur.vbuf = 0; } static inline void enablevquery(renderstate &cur) { - if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); } - if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); } - startbb(false); - cur.vquery = true; + if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); } + if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); } + startbb(false); + cur.vquery = true; } static inline void disablevquery(renderstate &cur) { - endbb(false); - cur.vquery = false; + endbb(false); + cur.vquery = false; } static void renderquery(renderstate &cur, occludequery *query, vtxarray *va, bool full = true) { - if(!cur.vquery) enablevquery(cur); + if(!cur.vquery) enablevquery(cur); - startquery(query); + startquery(query); - if(full) drawbb(ivec(va->bbmin).sub(1), ivec(va->bbmax).sub(va->bbmin).add(2)); - else drawbb(va->geommin, ivec(va->geommax).sub(va->geommin)); + if(full) drawbb(ivec(va->bbmin).sub(1), ivec(va->bbmax).sub(va->bbmin).add(2)); + else drawbb(va->geommin, ivec(va->geommax).sub(va->geommin)); - endquery(query); + endquery(query); } enum { - RENDERPASS_LIGHTMAP = 0, - RENDERPASS_Z, - RENDERPASS_CAUSTICS, - RENDERPASS_FOG, - RENDERPASS_LIGHTMAP_BLEND + RENDERPASS_LIGHTMAP = 0, + RENDERPASS_Z, + RENDERPASS_CAUSTICS, + RENDERPASS_FOG, + RENDERPASS_LIGHTMAP_BLEND }; struct geombatch { - const elementset &es; - VSlot &vslot; - ushort *edata; - vtxarray *va; - int next, batch; - - geombatch(const elementset &es, ushort *edata, vtxarray *va) - : es(es), vslot(lookupvslot(es.texture)), edata(edata), va(va), - next(-1), batch(-1) - {} - - int compare(const geombatch &b) const - { - if(va->vbuf < b.va->vbuf) return -1; - if(va->vbuf > b.va->vbuf) return 1; - if(va->dynlightmask < b.va->dynlightmask) return -1; - if(va->dynlightmask > b.va->dynlightmask) return 1; - if(vslot.slot->shader < b.vslot.slot->shader) return -1; - if(vslot.slot->shader > b.vslot.slot->shader) return 1; - if(vslot.slot->params.length() < b.vslot.slot->params.length()) return -1; - if(vslot.slot->params.length() > b.vslot.slot->params.length()) return 1; - if(es.texture < b.es.texture) return -1; - if(es.texture > b.es.texture) return 1; - if(es.lmid < b.es.lmid) return -1; - if(es.lmid > b.es.lmid) return 1; - if(es.envmap < b.es.envmap) return -1; - if(es.envmap > b.es.envmap) return 1; - if(es.dim < b.es.dim) return -1; - if(es.dim > b.es.dim) return 1; - return 0; - } + const elementset &es; + VSlot &vslot; + ushort *edata; + vtxarray *va; + int next, batch; + + geombatch(const elementset &es, ushort *edata, vtxarray *va) + : es(es), vslot(lookupvslot(es.texture)), edata(edata), va(va), + next(-1), batch(-1) + {} + + int compare(const geombatch &b) const + { + if(va->vbuf < b.va->vbuf) return -1; + if(va->vbuf > b.va->vbuf) return 1; + if(va->dynlightmask < b.va->dynlightmask) return -1; + if(va->dynlightmask > b.va->dynlightmask) return 1; + if(vslot.slot->shader < b.vslot.slot->shader) return -1; + if(vslot.slot->shader > b.vslot.slot->shader) return 1; + if(vslot.slot->params.length() < b.vslot.slot->params.length()) return -1; + if(vslot.slot->params.length() > b.vslot.slot->params.length()) return 1; + if(es.texture < b.es.texture) return -1; + if(es.texture > b.es.texture) return 1; + if(es.lmid < b.es.lmid) return -1; + if(es.lmid > b.es.lmid) return 1; + if(es.envmap < b.es.envmap) return -1; + if(es.envmap > b.es.envmap) return 1; + if(es.dim < b.es.dim) return -1; + if(es.dim > b.es.dim) return 1; + return 0; + } }; static vector geombatches; @@ -930,486 +930,486 @@ static int firstbatch = -1, numbatches = 0; static void mergetexs(renderstate &cur, vtxarray *va, elementset *texs = NULL, int numtexs = 0, ushort *edata = NULL) { - if(!texs) - { - texs = va->eslist; - numtexs = va->texs; - edata = va->edata; - if(cur.alphaing) - { - texs += va->texs + va->blends; - edata += 3*(va->tris + va->blendtris); - numtexs = va->alphaback; - if(cur.alphaing > 1) numtexs += va->alphafront; - } - } - - if(firstbatch < 0) - { - firstbatch = geombatches.length(); - numbatches = numtexs; - loopi(numtexs-1) - { - geombatches.add(geombatch(texs[i], edata, va)).next = i+1; - edata += texs[i].length[1]; - } - geombatches.add(geombatch(texs[numtexs-1], edata, va)); - return; - } - - int prevbatch = -1, curbatch = firstbatch, curtex = 0; - do - { - geombatch &b = geombatches.add(geombatch(texs[curtex], edata, va)); - edata += texs[curtex].length[1]; - int dir = -1; - while(curbatch >= 0) - { - dir = b.compare(geombatches[curbatch]); - if(dir <= 0) break; - prevbatch = curbatch; - curbatch = geombatches[curbatch].next; - } - if(!dir) - { - int last = curbatch, next; - for(;;) - { - next = geombatches[last].batch; - if(next < 0) break; - last = next; - } - if(last==curbatch) - { - b.batch = curbatch; - b.next = geombatches[curbatch].next; - if(prevbatch < 0) firstbatch = geombatches.length()-1; - else geombatches[prevbatch].next = geombatches.length()-1; - curbatch = geombatches.length()-1; - } - else - { - b.batch = next; - geombatches[last].batch = geombatches.length()-1; - } - } - else - { - numbatches++; - b.next = curbatch; - if(prevbatch < 0) firstbatch = geombatches.length()-1; - else geombatches[prevbatch].next = geombatches.length()-1; - prevbatch = geombatches.length()-1; - } - } - while(++curtex < numtexs); + if(!texs) + { + texs = va->eslist; + numtexs = va->texs; + edata = va->edata; + if(cur.alphaing) + { + texs += va->texs + va->blends; + edata += 3*(va->tris + va->blendtris); + numtexs = va->alphaback; + if(cur.alphaing > 1) numtexs += va->alphafront; + } + } + + if(firstbatch < 0) + { + firstbatch = geombatches.length(); + numbatches = numtexs; + loopi(numtexs-1) + { + geombatches.add(geombatch(texs[i], edata, va)).next = i+1; + edata += texs[i].length[1]; + } + geombatches.add(geombatch(texs[numtexs-1], edata, va)); + return; + } + + int prevbatch = -1, curbatch = firstbatch, curtex = 0; + do + { + geombatch &b = geombatches.add(geombatch(texs[curtex], edata, va)); + edata += texs[curtex].length[1]; + int dir = -1; + while(curbatch >= 0) + { + dir = b.compare(geombatches[curbatch]); + if(dir <= 0) break; + prevbatch = curbatch; + curbatch = geombatches[curbatch].next; + } + if(!dir) + { + int last = curbatch, next; + for(;;) + { + next = geombatches[last].batch; + if(next < 0) break; + last = next; + } + if(last==curbatch) + { + b.batch = curbatch; + b.next = geombatches[curbatch].next; + if(prevbatch < 0) firstbatch = geombatches.length()-1; + else geombatches[prevbatch].next = geombatches.length()-1; + curbatch = geombatches.length()-1; + } + else + { + b.batch = next; + geombatches[last].batch = geombatches.length()-1; + } + } + else + { + numbatches++; + b.next = curbatch; + if(prevbatch < 0) firstbatch = geombatches.length()-1; + else geombatches[prevbatch].next = geombatches.length()-1; + prevbatch = geombatches.length()-1; + } + } + while(++curtex < numtexs); } static inline void enablevattribs(renderstate &cur, bool all = true) { - gle::enablevertex(); - if(all) - { - gle::enabletexcoord0(); - gle::enabletexcoord1(); - gle::enablenormal(); - gle::enabletangent(); - } - cur.vattribs = true; + gle::enablevertex(); + if(all) + { + gle::enabletexcoord0(); + gle::enabletexcoord1(); + gle::enablenormal(); + gle::enabletangent(); + } + cur.vattribs = true; } static inline void disablevattribs(renderstate &cur, bool all = true) { - gle::disablevertex(); - if(all) - { - gle::disabletexcoord0(); - gle::disabletexcoord1(); - gle::disablenormal(); - gle::disabletangent(); - } - cur.vattribs = false; + gle::disablevertex(); + if(all) + { + gle::disabletexcoord0(); + gle::disabletexcoord1(); + gle::disablenormal(); + gle::disabletangent(); + } + cur.vattribs = false; } static void changevbuf(renderstate &cur, int pass, vtxarray *va) { - gle::bindvbo(va->vbuf); - gle::bindebo(va->ebuf); - cur.vbuf = va->vbuf; + gle::bindvbo(va->vbuf); + gle::bindebo(va->ebuf); + cur.vbuf = va->vbuf; - vertex *vdata = (vertex *)0; - gle::vertexpointer(sizeof(vertex), vdata->pos.v); + vertex *vdata = (vertex *)0; + gle::vertexpointer(sizeof(vertex), vdata->pos.v); - if(pass==RENDERPASS_LIGHTMAP) - { - gle::normalpointer(sizeof(vertex), vdata->norm.v, GL_BYTE); - gle::texcoord0pointer(sizeof(vertex), vdata->tc.v); - gle::texcoord1pointer(sizeof(vertex), vdata->lm.v, GL_SHORT); - gle::tangentpointer(sizeof(vertex), vdata->tangent.v, GL_BYTE); - } + if(pass==RENDERPASS_LIGHTMAP) + { + gle::normalpointer(sizeof(vertex), vdata->norm.v, GL_BYTE); + gle::texcoord0pointer(sizeof(vertex), vdata->tc.v); + gle::texcoord1pointer(sizeof(vertex), vdata->lm.v, GL_SHORT); + gle::tangentpointer(sizeof(vertex), vdata->tangent.v, GL_BYTE); + } } static void changebatchtmus(renderstate &cur, int pass, geombatch &b) { - bool changed = false; - extern bool brightengeom; - extern int fullbright; - int lmid = brightengeom && (b.es.lmid < LMID_RESERVED || (fullbright && editmode)) ? (int) LMID_BRIGHT : (int) b.es.lmid; - if(cur.textures[1]!=lightmaptexs[lmid].id) - { - glActiveTexture_(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, cur.textures[1] = lightmaptexs[lmid].id); - changed = true; - } - int tmu = 2; - if(b.vslot.slot->shader->type&SHADER_NORMALSLMS) - { - if(cur.textures[tmu]!=lightmaptexs[lmid+1].id) - { - glActiveTexture_(GL_TEXTURE0+tmu); - glBindTexture(GL_TEXTURE_2D, cur.textures[tmu] = lightmaptexs[lmid+1].id); - changed = true; - } - tmu++; - } - if(b.vslot.slot->shader->type&SHADER_ENVMAP && b.es.envmap!=EMID_CUSTOM) - { - GLuint emtex = lookupenvmap(b.es.envmap); - if(cur.textures[tmu]!=emtex) - { - glActiveTexture_(GL_TEXTURE0+tmu); - glBindTexture(GL_TEXTURE_CUBE_MAP, cur.textures[tmu] = emtex); - changed = true; - } - } - if(changed) glActiveTexture_(GL_TEXTURE0); - - if(cur.dynlightmask != b.va->dynlightmask) - { - cur.visibledynlights = setdynlights(b.va); - cur.dynlightmask = b.va->dynlightmask; - } + bool changed = false; + extern bool brightengeom; + extern int fullbright; + int lmid = brightengeom && (b.es.lmid < LMID_RESERVED || (fullbright && editmode)) ? (int) LMID_BRIGHT : (int) b.es.lmid; + if(cur.textures[1]!=lightmaptexs[lmid].id) + { + glActiveTexture_(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, cur.textures[1] = lightmaptexs[lmid].id); + changed = true; + } + int tmu = 2; + if(b.vslot.slot->shader->type&SHADER_NORMALSLMS) + { + if(cur.textures[tmu]!=lightmaptexs[lmid+1].id) + { + glActiveTexture_(GL_TEXTURE0+tmu); + glBindTexture(GL_TEXTURE_2D, cur.textures[tmu] = lightmaptexs[lmid+1].id); + changed = true; + } + tmu++; + } + if(b.vslot.slot->shader->type&SHADER_ENVMAP && b.es.envmap!=EMID_CUSTOM) + { + GLuint emtex = lookupenvmap(b.es.envmap); + if(cur.textures[tmu]!=emtex) + { + glActiveTexture_(GL_TEXTURE0+tmu); + glBindTexture(GL_TEXTURE_CUBE_MAP, cur.textures[tmu] = emtex); + changed = true; + } + } + if(changed) glActiveTexture_(GL_TEXTURE0); + + if(cur.dynlightmask != b.va->dynlightmask) + { + cur.visibledynlights = setdynlights(b.va); + cur.dynlightmask = b.va->dynlightmask; + } } static void changeslottmus(renderstate &cur, int pass, Slot &slot, VSlot &vslot) { - if(pass==RENDERPASS_LIGHTMAP) - { - GLuint diffusetex = slot.sts.empty() ? notexture->id : slot.sts[0].t->id; - if(cur.textures[0]!=diffusetex) - glBindTexture(GL_TEXTURE_2D, cur.textures[0] = diffusetex); - } - - if(cur.alphaing) - { - float alpha = cur.alphaing > 1 ? vslot.alphafront : vslot.alphaback; - if(cur.colorscale != vslot.colorscale || cur.alphascale != alpha) - { - cur.colorscale = vslot.colorscale; - cur.alphascale = alpha; - GLOBALPARAMF(colorparams, 2*alpha*vslot.colorscale.x, 2*alpha*vslot.colorscale.y, 2*alpha*vslot.colorscale.z, alpha); - setfogcolor(vec(curfogcolor).mul(alpha)); - } - } - else if(cur.colorscale != vslot.colorscale) - { - cur.colorscale = vslot.colorscale; - GLOBALPARAMF(colorparams, 2*vslot.colorscale.x, 2*vslot.colorscale.y, 2*vslot.colorscale.z, 1); - } - int tmu = 2, envmaptmu = -1; - if(slot.shader->type&SHADER_NORMALSLMS) tmu++; - if(slot.shader->type&SHADER_ENVMAP) envmaptmu = tmu++; - loopvj(slot.sts) - { - Slot::Tex &t = slot.sts[j]; - if(t.type==TEX_DIFFUSE || t.combined>=0) continue; - if(t.type==TEX_ENVMAP) - { - if(envmaptmu>=0 && t.t && cur.textures[envmaptmu]!=t.t->id) - { - glActiveTexture_(GL_TEXTURE0+envmaptmu); - glBindTexture(GL_TEXTURE_CUBE_MAP, cur.textures[envmaptmu] = t.t->id); - } - } - else - { - if(cur.textures[tmu]!=t.t->id) - { - glActiveTexture_(GL_TEXTURE0+tmu); - glBindTexture(GL_TEXTURE_2D, cur.textures[tmu] = t.t->id); - } - if(++tmu >= 8) break; - } - } - glActiveTexture_(GL_TEXTURE0); - - cur.slot = &slot; - cur.vslot = &vslot; + if(pass==RENDERPASS_LIGHTMAP) + { + GLuint diffusetex = slot.sts.empty() ? notexture->id : slot.sts[0].t->id; + if(cur.textures[0]!=diffusetex) + glBindTexture(GL_TEXTURE_2D, cur.textures[0] = diffusetex); + } + + if(cur.alphaing) + { + float alpha = cur.alphaing > 1 ? vslot.alphafront : vslot.alphaback; + if(cur.colorscale != vslot.colorscale || cur.alphascale != alpha) + { + cur.colorscale = vslot.colorscale; + cur.alphascale = alpha; + GLOBALPARAMF(colorparams, 2*alpha*vslot.colorscale.x, 2*alpha*vslot.colorscale.y, 2*alpha*vslot.colorscale.z, alpha); + setfogcolor(vec(curfogcolor).mul(alpha)); + } + } + else if(cur.colorscale != vslot.colorscale) + { + cur.colorscale = vslot.colorscale; + GLOBALPARAMF(colorparams, 2*vslot.colorscale.x, 2*vslot.colorscale.y, 2*vslot.colorscale.z, 1); + } + int tmu = 2, envmaptmu = -1; + if(slot.shader->type&SHADER_NORMALSLMS) tmu++; + if(slot.shader->type&SHADER_ENVMAP) envmaptmu = tmu++; + loopvj(slot.sts) + { + Slot::Tex &t = slot.sts[j]; + if(t.type==TEX_DIFFUSE || t.combined>=0) continue; + if(t.type==TEX_ENVMAP) + { + if(envmaptmu>=0 && t.t && cur.textures[envmaptmu]!=t.t->id) + { + glActiveTexture_(GL_TEXTURE0+envmaptmu); + glBindTexture(GL_TEXTURE_CUBE_MAP, cur.textures[envmaptmu] = t.t->id); + } + } + else + { + if(cur.textures[tmu]!=t.t->id) + { + glActiveTexture_(GL_TEXTURE0+tmu); + glBindTexture(GL_TEXTURE_2D, cur.textures[tmu] = t.t->id); + } + if(++tmu >= 8) break; + } + } + glActiveTexture_(GL_TEXTURE0); + + cur.slot = &slot; + cur.vslot = &vslot; } static void changeshader(renderstate &cur, Shader *s, Slot &slot, VSlot &vslot, bool shadowed) { - if(glaring) - { - static Shader *noglareshader = NULL, *noglareblendshader = NULL, *noglarealphashader = NULL; - Shader *fallback; - if(cur.blending) { if(!noglareblendshader) noglareblendshader = lookupshaderbyname("noglareblendworld"); fallback = noglareblendshader; } - else if(cur.alphaing) { if(!noglarealphashader) noglarealphashader = lookupshaderbyname("noglarealphaworld"); fallback = noglarealphashader; } - else { if(!noglareshader) noglareshader = lookupshaderbyname("noglareworld"); fallback = noglareshader; } - if(s->hasoption(4)) s->setvariant(cur.visibledynlights, 4, slot, vslot, fallback); - else s->setvariant(cur.blending ? 1 : 0, 4, slot, vslot, fallback); - } - else if(fading && !cur.blending && !cur.alphaing) - { - if(shadowed) s->setvariant(cur.visibledynlights, 3, slot, vslot); - else s->setvariant(cur.visibledynlights, 2, slot, vslot); - } - else if(shadowed) s->setvariant(cur.visibledynlights, 1, slot, vslot); - else if(!cur.visibledynlights) s->set(slot, vslot); - else s->setvariant(cur.visibledynlights-1, 0, slot, vslot); + if(glaring) + { + static Shader *noglareshader = NULL, *noglareblendshader = NULL, *noglarealphashader = NULL; + Shader *fallback; + if(cur.blending) { if(!noglareblendshader) noglareblendshader = lookupshaderbyname("noglareblendworld"); fallback = noglareblendshader; } + else if(cur.alphaing) { if(!noglarealphashader) noglarealphashader = lookupshaderbyname("noglarealphaworld"); fallback = noglarealphashader; } + else { if(!noglareshader) noglareshader = lookupshaderbyname("noglareworld"); fallback = noglareshader; } + if(s->hasoption(4)) s->setvariant(cur.visibledynlights, 4, slot, vslot, fallback); + else s->setvariant(cur.blending ? 1 : 0, 4, slot, vslot, fallback); + } + else if(fading && !cur.blending && !cur.alphaing) + { + if(shadowed) s->setvariant(cur.visibledynlights, 3, slot, vslot); + else s->setvariant(cur.visibledynlights, 2, slot, vslot); + } + else if(shadowed) s->setvariant(cur.visibledynlights, 1, slot, vslot); + else if(!cur.visibledynlights) s->set(slot, vslot); + else s->setvariant(cur.visibledynlights-1, 0, slot, vslot); } static void changetexgen(renderstate &cur, int dim, Slot &slot, VSlot &vslot) { - if(cur.texgenslot != &slot || cur.texgenvslot != &vslot) - { - Texture *curtex = !cur.texgenslot || cur.texgenslot->sts.empty() ? notexture : cur.texgenslot->sts[0].t, - *tex = slot.sts.empty() ? notexture : slot.sts[0].t; - if(!cur.texgenvslot || slot.sts.empty() || - (curtex->xs != tex->xs || curtex->ys != tex->ys || - cur.texgenvslot->rotation != vslot.rotation || cur.texgenvslot->scale != vslot.scale || - cur.texgenvslot->offset != vslot.offset || cur.texgenvslot->scroll != vslot.scroll)) - { - const texrotation &r = texrotations[vslot.rotation]; - float xs = r.flipx ? -tex->xs : tex->xs, - ys = r.flipy ? -tex->ys : tex->ys; - vec2 scroll(vslot.scroll); - if(r.swapxy) swap(scroll.x, scroll.y); - scroll.x *= lastmillis*tex->xs/xs; - scroll.y *= lastmillis*tex->ys/ys; - if(cur.texgenscroll != scroll) - { - cur.texgenscroll = scroll; - cur.texgendim = -1; - } - } - cur.texgenslot = &slot; - cur.texgenvslot = &vslot; - } - - if(cur.texgendim == dim) return; - GLOBALPARAM(texgenscroll, cur.texgenscroll); - cur.texgendim = dim; + if(cur.texgenslot != &slot || cur.texgenvslot != &vslot) + { + Texture *curtex = !cur.texgenslot || cur.texgenslot->sts.empty() ? notexture : cur.texgenslot->sts[0].t, + *tex = slot.sts.empty() ? notexture : slot.sts[0].t; + if(!cur.texgenvslot || slot.sts.empty() || + (curtex->xs != tex->xs || curtex->ys != tex->ys || + cur.texgenvslot->rotation != vslot.rotation || cur.texgenvslot->scale != vslot.scale || + cur.texgenvslot->offset != vslot.offset || cur.texgenvslot->scroll != vslot.scroll)) + { + const texrotation &r = texrotations[vslot.rotation]; + float xs = r.flipx ? -tex->xs : tex->xs, + ys = r.flipy ? -tex->ys : tex->ys; + vec2 scroll(vslot.scroll); + if(r.swapxy) swap(scroll.x, scroll.y); + scroll.x *= lastmillis*tex->xs/xs; + scroll.y *= lastmillis*tex->ys/ys; + if(cur.texgenscroll != scroll) + { + cur.texgenscroll = scroll; + cur.texgendim = -1; + } + } + cur.texgenslot = &slot; + cur.texgenvslot = &vslot; + } + + if(cur.texgendim == dim) return; + GLOBALPARAM(texgenscroll, cur.texgenscroll); + cur.texgendim = dim; } static void renderbatch(renderstate &cur, int pass, geombatch &b) { - geombatch *shadowed = NULL; - int rendered = -1; - for(geombatch *curbatch = &b;; curbatch = &geombatches[curbatch->batch]) - { - ushort len = curbatch->es.length[curbatch->va->shadowed ? 0 : 1]; - if(len) - { - if(rendered < 0) - { - changeshader(cur, b.vslot.slot->shader, *b.vslot.slot, b.vslot, false); - rendered = 0; - gbatches++; - } - ushort minvert = curbatch->es.minvert[0], maxvert = curbatch->es.maxvert[0]; - if(!curbatch->va->shadowed) { minvert = min(minvert, curbatch->es.minvert[1]); maxvert = max(maxvert, curbatch->es.maxvert[1]); } - drawtris(len, curbatch->edata, minvert, maxvert); - vtris += len/3; - } - if(curbatch->es.length[1] > len && !shadowed) shadowed = curbatch; - if(curbatch->batch < 0) break; - } - if(shadowed) for(geombatch *curbatch = shadowed;; curbatch = &geombatches[curbatch->batch]) - { - if(curbatch->va->shadowed && curbatch->es.length[1] > curbatch->es.length[0]) - { - if(rendered < 1) - { - changeshader(cur, b.vslot.slot->shader, *b.vslot.slot, b.vslot, true); - rendered = 1; - gbatches++; - } - ushort len = curbatch->es.length[1] - curbatch->es.length[0]; - drawtris(len, curbatch->edata + curbatch->es.length[0], curbatch->es.minvert[1], curbatch->es.maxvert[1]); - vtris += len/3; - } - if(curbatch->batch < 0) break; - } + geombatch *shadowed = NULL; + int rendered = -1; + for(geombatch *curbatch = &b;; curbatch = &geombatches[curbatch->batch]) + { + ushort len = curbatch->es.length[curbatch->va->shadowed ? 0 : 1]; + if(len) + { + if(rendered < 0) + { + changeshader(cur, b.vslot.slot->shader, *b.vslot.slot, b.vslot, false); + rendered = 0; + gbatches++; + } + ushort minvert = curbatch->es.minvert[0], maxvert = curbatch->es.maxvert[0]; + if(!curbatch->va->shadowed) { minvert = min(minvert, curbatch->es.minvert[1]); maxvert = max(maxvert, curbatch->es.maxvert[1]); } + drawtris(len, curbatch->edata, minvert, maxvert); + vtris += len/3; + } + if(curbatch->es.length[1] > len && !shadowed) shadowed = curbatch; + if(curbatch->batch < 0) break; + } + if(shadowed) for(geombatch *curbatch = shadowed;; curbatch = &geombatches[curbatch->batch]) + { + if(curbatch->va->shadowed && curbatch->es.length[1] > curbatch->es.length[0]) + { + if(rendered < 1) + { + changeshader(cur, b.vslot.slot->shader, *b.vslot.slot, b.vslot, true); + rendered = 1; + gbatches++; + } + ushort len = curbatch->es.length[1] - curbatch->es.length[0]; + drawtris(len, curbatch->edata + curbatch->es.length[0], curbatch->es.minvert[1], curbatch->es.maxvert[1]); + vtris += len/3; + } + if(curbatch->batch < 0) break; + } } static void resetbatches() { - geombatches.setsize(0); - firstbatch = -1; - numbatches = 0; + geombatches.setsize(0); + firstbatch = -1; + numbatches = 0; } static void renderbatches(renderstate &cur, int pass) { - cur.slot = NULL; - cur.vslot = NULL; - int curbatch = firstbatch; - if(curbatch >= 0) - { - if(cur.alphaing) - { - if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); } - } - else if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } - if(!cur.colormask) { cur.colormask = true; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, cur.alphaing ? GL_FALSE : GL_TRUE); } - if(!cur.vattribs) - { - if(cur.vquery) disablevquery(cur); - enablevattribs(cur); - } - } - while(curbatch >= 0) - { - geombatch &b = geombatches[curbatch]; - curbatch = b.next; - - if(cur.vbuf != b.va->vbuf) changevbuf(cur, pass, b.va); - if(cur.vslot != &b.vslot) - { - changeslottmus(cur, pass, *b.vslot.slot, b.vslot); - if(cur.texgendim != b.es.dim || (cur.texgendim <= 2 && cur.texgenvslot != &b.vslot)) changetexgen(cur, b.es.dim, *b.vslot.slot, b.vslot); - } - else if(cur.texgendim != b.es.dim) changetexgen(cur, b.es.dim, *b.vslot.slot, b.vslot); - if(pass == RENDERPASS_LIGHTMAP) changebatchtmus(cur, pass, b); - - renderbatch(cur, pass, b); - } - - resetbatches(); + cur.slot = NULL; + cur.vslot = NULL; + int curbatch = firstbatch; + if(curbatch >= 0) + { + if(cur.alphaing) + { + if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); } + } + else if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } + if(!cur.colormask) { cur.colormask = true; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, cur.alphaing ? GL_FALSE : GL_TRUE); } + if(!cur.vattribs) + { + if(cur.vquery) disablevquery(cur); + enablevattribs(cur); + } + } + while(curbatch >= 0) + { + geombatch &b = geombatches[curbatch]; + curbatch = b.next; + + if(cur.vbuf != b.va->vbuf) changevbuf(cur, pass, b.va); + if(cur.vslot != &b.vslot) + { + changeslottmus(cur, pass, *b.vslot.slot, b.vslot); + if(cur.texgendim != b.es.dim || (cur.texgendim <= 2 && cur.texgenvslot != &b.vslot)) changetexgen(cur, b.es.dim, *b.vslot.slot, b.vslot); + } + else if(cur.texgendim != b.es.dim) changetexgen(cur, b.es.dim, *b.vslot.slot, b.vslot); + if(pass == RENDERPASS_LIGHTMAP) changebatchtmus(cur, pass, b); + + renderbatch(cur, pass, b); + } + + resetbatches(); } void renderzpass(renderstate &cur, vtxarray *va) { - if(!cur.vattribs) - { - if(cur.vquery) disablevquery(cur); - enablevattribs(cur, false); - } - if(cur.vbuf!=va->vbuf) changevbuf(cur, RENDERPASS_Z, va); - if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } - if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); } - int firsttex = 0, numtris = va->tris; - ushort *edata = va->edata; - if(cur.alphaing) - { - firsttex += va->texs + va->blends; - edata += 3*(va->tris + va->blendtris); - numtris = va->alphatris; - xtravertsva += 3*numtris; - } - else xtravertsva += va->verts; - nocolorshader->set(); - drawvatris(va, 3*numtris, edata); + if(!cur.vattribs) + { + if(cur.vquery) disablevquery(cur); + enablevattribs(cur, false); + } + if(cur.vbuf!=va->vbuf) changevbuf(cur, RENDERPASS_Z, va); + if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } + if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); } + int firsttex = 0, numtris = va->tris; + ushort *edata = va->edata; + if(cur.alphaing) + { + firsttex += va->texs + va->blends; + edata += 3*(va->tris + va->blendtris); + numtris = va->alphatris; + xtravertsva += 3*numtris; + } + else xtravertsva += va->verts; + nocolorshader->set(); + drawvatris(va, 3*numtris, edata); } vector foggedvas; #define startvaquery(va, flush) \ - do { \ - if(va->query) \ - { \ - flush; \ - startquery(va->query); \ - } \ - } while(0) + do { \ + if(va->query) \ + { \ + flush; \ + startquery(va->query); \ + } \ + } while(0) #define endvaquery(va, flush) \ - do { \ - if(va->query) \ - { \ - flush; \ - endquery(va->query); \ - } \ - } while(0) + do { \ + if(va->query) \ + { \ + flush; \ + endquery(va->query); \ + } \ + } while(0) void renderfoggedvas(renderstate &cur, bool doquery = false) { - static Shader *fogshader = NULL; - if(!fogshader) fogshader = lookupshaderbyname("fogworld"); - if(fading) fogshader->setvariant(0, 2); - else fogshader->set(); + static Shader *fogshader = NULL; + if(!fogshader) fogshader = lookupshaderbyname("fogworld"); + if(fading) fogshader->setvariant(0, 2); + else fogshader->set(); - if(!cur.vattribs) enablevattribs(cur, false); + if(!cur.vattribs) enablevattribs(cur, false); - loopv(foggedvas) - { - vtxarray *va = foggedvas[i]; - if(cur.vbuf!=va->vbuf) changevbuf(cur, RENDERPASS_FOG, va); + loopv(foggedvas) + { + vtxarray *va = foggedvas[i]; + if(cur.vbuf!=va->vbuf) changevbuf(cur, RENDERPASS_FOG, va); - if(doquery) startvaquery(va, ); - drawvatris(va, 3*va->tris, va->edata); - vtris += va->tris; - if(doquery) endvaquery(va, ); - } + if(doquery) startvaquery(va, ); + drawvatris(va, 3*va->tris, va->edata); + vtris += va->tris; + if(doquery) endvaquery(va, ); + } - foggedvas.setsize(0); + foggedvas.setsize(0); } VAR(batchgeom, 0, 1, 1); void renderva(renderstate &cur, vtxarray *va, int pass = RENDERPASS_LIGHTMAP, bool fogpass = false, bool doquery = false) { - switch(pass) - { - case RENDERPASS_LIGHTMAP: - if(!cur.alphaing) vverts += va->verts; - va->shadowed = false; - va->dynlightmask = 0; - if(fogpass ? va->geommax.z<=reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) - { - if(!cur.alphaing && !cur.blending) foggedvas.add(va); - break; - } - if(!drawtex && !glaring && !cur.alphaing) - { - va->shadowed = isshadowmapreceiver(va); - calcdynlightmask(va); - } - if(doquery) startvaquery(va, { if(geombatches.length()) renderbatches(cur, pass); }); - mergetexs(cur, va); - if(doquery) endvaquery(va, { if(geombatches.length()) renderbatches(cur, pass); }); - else if(!batchgeom && geombatches.length()) renderbatches(cur, pass); - break; - - case RENDERPASS_LIGHTMAP_BLEND: - { - if(doquery) startvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); }); - mergetexs(cur, va, &va->eslist[va->texs], va->blends, va->edata + 3*va->tris); - if(doquery) endvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); }); - else if(!batchgeom && geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); - break; - } - - case RENDERPASS_FOG: - if(cur.vbuf!=va->vbuf) changevbuf(cur, pass, va); - drawvatris(va, 3*va->tris, va->edata); - xtravertsva += va->verts; - break; - - case RENDERPASS_CAUSTICS: - if(cur.vbuf!=va->vbuf) changevbuf(cur, pass, va); - drawvatris(va, 3*va->tris, va->edata); - xtravertsva += va->verts; - break; - - case RENDERPASS_Z: - if(doquery) startvaquery(va, ); - renderzpass(cur, va); - if(doquery) endvaquery(va, ); - break; - } + switch(pass) + { + case RENDERPASS_LIGHTMAP: + if(!cur.alphaing) vverts += va->verts; + va->shadowed = false; + va->dynlightmask = 0; + if(fogpass ? va->geommax.z<=reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) + { + if(!cur.alphaing && !cur.blending) foggedvas.add(va); + break; + } + if(!drawtex && !glaring && !cur.alphaing) + { + va->shadowed = isshadowmapreceiver(va); + calcdynlightmask(va); + } + if(doquery) startvaquery(va, { if(geombatches.length()) renderbatches(cur, pass); }); + mergetexs(cur, va); + if(doquery) endvaquery(va, { if(geombatches.length()) renderbatches(cur, pass); }); + else if(!batchgeom && geombatches.length()) renderbatches(cur, pass); + break; + + case RENDERPASS_LIGHTMAP_BLEND: + { + if(doquery) startvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); }); + mergetexs(cur, va, &va->eslist[va->texs], va->blends, va->edata + 3*va->tris); + if(doquery) endvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); }); + else if(!batchgeom && geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); + break; + } + + case RENDERPASS_FOG: + if(cur.vbuf!=va->vbuf) changevbuf(cur, pass, va); + drawvatris(va, 3*va->tris, va->edata); + xtravertsva += va->verts; + break; + + case RENDERPASS_CAUSTICS: + if(cur.vbuf!=va->vbuf) changevbuf(cur, pass, va); + drawvatris(va, 3*va->tris, va->edata); + xtravertsva += va->verts; + break; + + case RENDERPASS_Z: + if(doquery) startvaquery(va, ); + renderzpass(cur, va); + if(doquery) endvaquery(va, ); + break; + } } #define NUMCAUSTICS 32 @@ -1418,24 +1418,24 @@ static Texture *caustictex[NUMCAUSTICS] = { NULL }; void loadcaustics(bool force) { - static bool needcaustics = false; - if(force) needcaustics = true; - if(!caustics || !needcaustics) return; - useshaderbyname("caustic"); - if(caustictex[0]) return; - loopi(NUMCAUSTICS) - { - defformatstring(name, "packages/caustics/caust%.2d.png", i); - caustictex[i] = textureload(name); - } + static bool needcaustics = false; + if(force) needcaustics = true; + if(!caustics || !needcaustics) return; + useshaderbyname("caustic"); + if(caustictex[0]) return; + loopi(NUMCAUSTICS) + { + defformatstring(name, "packages/caustics/caust%.2d.png", i); + caustictex[i] = textureload(name); + } } void cleanupva() { - clearvas(worldroot); - clearqueries(); - cleanupbb(); - loopi(NUMCAUSTICS) caustictex[i] = NULL; + clearvas(worldroot); + clearqueries(); + cleanupbb(); + loopi(NUMCAUSTICS) caustictex[i] = NULL; } VARR(causticscale, 0, 50, 10000); @@ -1445,38 +1445,38 @@ VARFP(caustics, 0, 1, 1, loadcaustics()); void setupcaustics(float blend) { - if(!caustictex[0]) loadcaustics(true); + if(!caustictex[0]) loadcaustics(true); - vec s = vec(0.011f, 0, 0.0066f).mul(100.0f/causticscale), t = vec(0, 0.011f, 0.0066f).mul(100.0f/causticscale); - int tex = (lastmillis/causticmillis)%NUMCAUSTICS; - float frac = float(lastmillis%causticmillis)/causticmillis; - loopi(2) - { - glActiveTexture_(GL_TEXTURE0+i); - glBindTexture(GL_TEXTURE_2D, caustictex[(tex+i)%NUMCAUSTICS]->id); - } - glActiveTexture_(GL_TEXTURE0); - SETSHADER(caustic); - LOCALPARAM(texgenS, s); - LOCALPARAM(texgenT, t); - blend *= causticcontrast; - LOCALPARAMF(frameblend, blend*(1-frac), blend*frac, blend, 1 - blend); + vec s = vec(0.011f, 0, 0.0066f).mul(100.0f/causticscale), t = vec(0, 0.011f, 0.0066f).mul(100.0f/causticscale); + int tex = (lastmillis/causticmillis)%NUMCAUSTICS; + float frac = float(lastmillis%causticmillis)/causticmillis; + loopi(2) + { + glActiveTexture_(GL_TEXTURE0+i); + glBindTexture(GL_TEXTURE_2D, caustictex[(tex+i)%NUMCAUSTICS]->id); + } + glActiveTexture_(GL_TEXTURE0); + SETSHADER(caustic); + LOCALPARAM(texgenS, s); + LOCALPARAM(texgenT, t); + blend *= causticcontrast; + LOCALPARAMF(frameblend, blend*(1-frac), blend*frac, blend, 1 - blend); } void setupgeom(renderstate &cur) { - GLOBALPARAMF(colorparams, 2, 2, 2, 1); - GLOBALPARAM(camera, camera1->o); - GLOBALPARAMF(ambient, ambientcolor.x/255.0f, ambientcolor.y/255.0f, ambientcolor.z/255.0f); - GLOBALPARAMF(millis, lastmillis/1000.0f); + GLOBALPARAMF(colorparams, 2, 2, 2, 1); + GLOBALPARAM(camera, camera1->o); + GLOBALPARAMF(ambient, ambientcolor.x/255.0f, ambientcolor.y/255.0f, ambientcolor.z/255.0f); + GLOBALPARAMF(millis, lastmillis/1000.0f); - glActiveTexture_(GL_TEXTURE0); + glActiveTexture_(GL_TEXTURE0); } void cleanupgeom(renderstate &cur) { - if(cur.vattribs) disablevattribs(cur); - if(cur.vbuf) disablevbuf(cur); + if(cur.vattribs) disablevattribs(cur); + if(cur.vbuf) disablevbuf(cur); } #define FIRSTVA (reflecting ? reflectedva : visibleva) @@ -1484,398 +1484,398 @@ void cleanupgeom(renderstate &cur) static void rendergeommultipass(renderstate &cur, int pass, bool fogpass) { - if(cur.vbuf) disablevbuf(cur); - if(!cur.vattribs) enablevattribs(cur, false); - cur.texgendim = -1; - for(vtxarray *va = FIRSTVA; va; va = NEXTVA) - { - if(!va->texs) continue; - if(refracting) - { - if((refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) || va->occluded >= OCCLUDE_GEOM) continue; - if(ishiddencube(va->o, va->size)) continue; - } - else if(reflecting) - { - if(va->geommax.z <= reflectz) continue; - } - else if(va->occluded >= OCCLUDE_GEOM) continue; - if(fogpass ? va->geommax.z <= reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) continue; - renderva(cur, va, pass, fogpass); - } - if(geombatches.length()) renderbatches(cur, pass); + if(cur.vbuf) disablevbuf(cur); + if(!cur.vattribs) enablevattribs(cur, false); + cur.texgendim = -1; + for(vtxarray *va = FIRSTVA; va; va = NEXTVA) + { + if(!va->texs) continue; + if(refracting) + { + if((refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) || va->occluded >= OCCLUDE_GEOM) continue; + if(ishiddencube(va->o, va->size)) continue; + } + else if(reflecting) + { + if(va->geommax.z <= reflectz) continue; + } + else if(va->occluded >= OCCLUDE_GEOM) continue; + if(fogpass ? va->geommax.z <= reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) continue; + renderva(cur, va, pass, fogpass); + } + if(geombatches.length()) renderbatches(cur, pass); } VAR(oqgeom, 0, 1, 1); void rendergeom(float causticspass, bool fogpass) { - if(causticspass && (!causticscale || !causticmillis)) causticspass = 0; - - bool mainpass = !reflecting && !refracting && !drawtex && !glaring, - doOQ = oqfrags && oqgeom && mainpass, - doZP = doOQ && zpass, - doSM = shadowmap && !drawtex && !glaring; - renderstate cur; - if(mainpass) - { - flipqueries(); - vtris = vverts = 0; - } - if(!doZP) - { - if(shadowmap && mainpass) rendershadowmap(); - setupgeom(cur); - if(doSM) pushshadowmap(); - } - - finddynlights(); - - resetbatches(); - - int blends = 0; - for(vtxarray *va = FIRSTVA; va; va = NEXTVA) - { - if(!va->texs) continue; - if(refracting) - { - if((refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) || va->occluded >= OCCLUDE_GEOM) continue; - if(ishiddencube(va->o, va->size)) continue; - } - else if(reflecting) - { - if(va->geommax.z <= reflectz) continue; - } - else if(doOQ && (zpass || va->distance > oqdist) && !insideva(va, camera1->o)) - { - if(va->parent && va->parent->occluded >= OCCLUDE_BB) - { - va->query = NULL; - va->occluded = OCCLUDE_PARENT; - continue; - } - va->occluded = va->query && va->query->owner == va && checkquery(va->query) ? min(va->occluded+1, int(OCCLUDE_BB)) : OCCLUDE_NOTHING; - va->query = newquery(va); - if((!va->query && zpass) || !va->occluded) - va->occluded = OCCLUDE_NOTHING; - if(va->occluded >= OCCLUDE_GEOM) - { - if(va->query) - { - if(!zpass && geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); - if(cur.vattribs) disablevattribs(cur, !doZP); - if(cur.vbuf) disablevbuf(cur); - renderquery(cur, va->query, va); - } - continue; - } - } - else - { - va->query = NULL; - va->occluded = OCCLUDE_NOTHING; - } - - if(!doZP) blends += va->blends; - renderva(cur, va, doZP ? RENDERPASS_Z : RENDERPASS_LIGHTMAP, fogpass, doOQ); - } - - if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); - - if(cur.vquery) disablevquery(cur); - if(cur.vattribs) disablevattribs(cur, !doZP); - if(cur.vbuf) disablevbuf(cur); - - if(!cur.colormask) { cur.colormask = true; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } - if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } - - bool multipassing = false; - - if(doZP) - { + if(causticspass && (!causticscale || !causticmillis)) causticspass = 0; + + bool mainpass = !reflecting && !refracting && !drawtex && !glaring, + doOQ = oqfrags && oqgeom && mainpass, + doZP = doOQ && zpass, + doSM = shadowmap && !drawtex && !glaring; + renderstate cur; + if(mainpass) + { + flipqueries(); + vtris = vverts = 0; + } + if(!doZP) + { + if(shadowmap && mainpass) rendershadowmap(); + setupgeom(cur); + if(doSM) pushshadowmap(); + } + + finddynlights(); + + resetbatches(); + + int blends = 0; + for(vtxarray *va = FIRSTVA; va; va = NEXTVA) + { + if(!va->texs) continue; + if(refracting) + { + if((refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) || va->occluded >= OCCLUDE_GEOM) continue; + if(ishiddencube(va->o, va->size)) continue; + } + else if(reflecting) + { + if(va->geommax.z <= reflectz) continue; + } + else if(doOQ && (zpass || va->distance > oqdist) && !insideva(va, camera1->o)) + { + if(va->parent && va->parent->occluded >= OCCLUDE_BB) + { + va->query = NULL; + va->occluded = OCCLUDE_PARENT; + continue; + } + va->occluded = va->query && va->query->owner == va && checkquery(va->query) ? min(va->occluded+1, int(OCCLUDE_BB)) : OCCLUDE_NOTHING; + va->query = newquery(va); + if((!va->query && zpass) || !va->occluded) + va->occluded = OCCLUDE_NOTHING; + if(va->occluded >= OCCLUDE_GEOM) + { + if(va->query) + { + if(!zpass && geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); + if(cur.vattribs) disablevattribs(cur, !doZP); + if(cur.vbuf) disablevbuf(cur); + renderquery(cur, va->query, va); + } + continue; + } + } + else + { + va->query = NULL; + va->occluded = OCCLUDE_NOTHING; + } + + if(!doZP) blends += va->blends; + renderva(cur, va, doZP ? RENDERPASS_Z : RENDERPASS_LIGHTMAP, fogpass, doOQ); + } + + if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); + + if(cur.vquery) disablevquery(cur); + if(cur.vattribs) disablevattribs(cur, !doZP); + if(cur.vbuf) disablevbuf(cur); + + if(!cur.colormask) { cur.colormask = true; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } + if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } + + bool multipassing = false; + + if(doZP) + { glFlush(); - if(shadowmap && mainpass) rendershadowmap(); - setupgeom(cur); - if(doSM) pushshadowmap(); - - if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); } - cur.texgendim = -1; - - for(vtxarray *va = visibleva; va; va = va->next) - { - if(!va->texs || va->occluded >= OCCLUDE_GEOM) continue; - blends += va->blends; - renderva(cur, va, RENDERPASS_LIGHTMAP, fogpass); - } - if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); - for(vtxarray *va = visibleva; va; va = va->next) - { - if(!va->texs || va->occluded < OCCLUDE_GEOM) continue; - else if((va->parent && va->parent->occluded >= OCCLUDE_BB) || - (va->query && checkquery(va->query))) - { - va->occluded = OCCLUDE_BB; - continue; - } - - blends += va->blends; - renderva(cur, va, RENDERPASS_LIGHTMAP, fogpass); - } - if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); - } - - if(blends) - { - if(cur.vbuf) disablevbuf(cur); - - if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); } - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - - cur.texgendim = -1; - cur.blending = true; - for(vtxarray *va = FIRSTVA; va; va = NEXTVA) - { - if(!va->blends) continue; - if(refracting) - { - if(refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) continue; - if(ishiddencube(va->o, va->size)) continue; - if(va->occluded >= OCCLUDE_GEOM) continue; - } - else if(reflecting) - { - if(va->geommax.z <= reflectz) continue; - } - else if(va->occluded >= OCCLUDE_GEOM) continue; - if(fogpass ? va->geommax.z <= reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) continue; - renderva(cur, va, RENDERPASS_LIGHTMAP_BLEND, fogpass); - } - if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); - cur.blending = false; - - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); - } - - if(doSM) popshadowmap(); - - if(cur.vattribs) disablevattribs(cur); - - if(foggedvas.length()) renderfoggedvas(cur, doOQ && !zpass); - - if(causticspass) - { - if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); } - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - - setupcaustics(causticspass); - glBlendFunc(GL_ZERO, GL_SRC_COLOR); - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - rendergeommultipass(cur, RENDERPASS_CAUSTICS, fogpass); - if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); - } - - if(multipassing) glDepthFunc(GL_LESS); - - cleanupgeom(cur); + if(shadowmap && mainpass) rendershadowmap(); + setupgeom(cur); + if(doSM) pushshadowmap(); + + if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); } + cur.texgendim = -1; + + for(vtxarray *va = visibleva; va; va = va->next) + { + if(!va->texs || va->occluded >= OCCLUDE_GEOM) continue; + blends += va->blends; + renderva(cur, va, RENDERPASS_LIGHTMAP, fogpass); + } + if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); + for(vtxarray *va = visibleva; va; va = va->next) + { + if(!va->texs || va->occluded < OCCLUDE_GEOM) continue; + else if((va->parent && va->parent->occluded >= OCCLUDE_BB) || + (va->query && checkquery(va->query))) + { + va->occluded = OCCLUDE_BB; + continue; + } + + blends += va->blends; + renderva(cur, va, RENDERPASS_LIGHTMAP, fogpass); + } + if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); + } + + if(blends) + { + if(cur.vbuf) disablevbuf(cur); + + if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); } + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + + cur.texgendim = -1; + cur.blending = true; + for(vtxarray *va = FIRSTVA; va; va = NEXTVA) + { + if(!va->blends) continue; + if(refracting) + { + if(refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) continue; + if(ishiddencube(va->o, va->size)) continue; + if(va->occluded >= OCCLUDE_GEOM) continue; + } + else if(reflecting) + { + if(va->geommax.z <= reflectz) continue; + } + else if(va->occluded >= OCCLUDE_GEOM) continue; + if(fogpass ? va->geommax.z <= reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) continue; + renderva(cur, va, RENDERPASS_LIGHTMAP_BLEND, fogpass); + } + if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); + cur.blending = false; + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + } + + if(doSM) popshadowmap(); + + if(cur.vattribs) disablevattribs(cur); + + if(foggedvas.length()) renderfoggedvas(cur, doOQ && !zpass); + + if(causticspass) + { + if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); } + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + + setupcaustics(causticspass); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + rendergeommultipass(cur, RENDERPASS_CAUSTICS, fogpass); + if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + } + + if(multipassing) glDepthFunc(GL_LESS); + + cleanupgeom(cur); } void renderalphageom(bool fogpass) { - static vector alphavas; - alphavas.setsize(0); - bool hasback = false; - for(vtxarray *va = FIRSTVA; va; va = NEXTVA) - { - if(!va->alphatris) continue; - if(refracting) - { - if((refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) || va->occluded >= OCCLUDE_BB) continue; - if(ishiddencube(va->o, va->size)) continue; - } - else if(reflecting) - { - if(va->geommax.z <= reflectz) continue; - } - else - { - if(va->occluded >= OCCLUDE_BB) continue; - } - if(fogpass ? va->geommax.z <= reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) continue; - alphavas.add(va); - if(va->alphabacktris) hasback = true; - } - if(alphavas.empty()) return; - - resetbatches(); - - renderstate cur; - cur.alphaing = 1; - - loop(front, 2) if(front || hasback) - { - cur.alphaing = front+1; - if(!front) glCullFace(GL_FRONT); - cur.vbuf = 0; - cur.texgendim = -1; - loopv(alphavas) renderva(cur, alphavas[i], RENDERPASS_Z); - if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); } - cur.colormask = true; - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); - - if(cur.vattribs) disablevattribs(cur, false); - if(cur.vbuf) disablevbuf(cur); - - setupgeom(cur); - - glDepthFunc(GL_LEQUAL); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - cur.vbuf = 0; - cur.texgendim = -1; - cur.colorscale = vec(1, 1, 1); - cur.alphascale = -1; - loopv(alphavas) if(front || alphavas[i]->alphabacktris) renderva(cur, alphavas[i], RENDERPASS_LIGHTMAP, fogpass); - if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); - - cleanupgeom(cur); - - resetfogcolor(); - if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } - glDisable(GL_BLEND); - glDepthFunc(GL_LESS); - if(!front) glCullFace(GL_BACK); - } - - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE); + static vector alphavas; + alphavas.setsize(0); + bool hasback = false; + for(vtxarray *va = FIRSTVA; va; va = NEXTVA) + { + if(!va->alphatris) continue; + if(refracting) + { + if((refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) || va->occluded >= OCCLUDE_BB) continue; + if(ishiddencube(va->o, va->size)) continue; + } + else if(reflecting) + { + if(va->geommax.z <= reflectz) continue; + } + else + { + if(va->occluded >= OCCLUDE_BB) continue; + } + if(fogpass ? va->geommax.z <= reflectz-refractfog || !refractfog : va->curvfc==VFC_FOGGED) continue; + alphavas.add(va); + if(va->alphabacktris) hasback = true; + } + if(alphavas.empty()) return; + + resetbatches(); + + renderstate cur; + cur.alphaing = 1; + + loop(front, 2) if(front || hasback) + { + cur.alphaing = front+1; + if(!front) glCullFace(GL_FRONT); + cur.vbuf = 0; + cur.texgendim = -1; + loopv(alphavas) renderva(cur, alphavas[i], RENDERPASS_Z); + if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); } + cur.colormask = true; + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + + if(cur.vattribs) disablevattribs(cur, false); + if(cur.vbuf) disablevbuf(cur); + + setupgeom(cur); + + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + cur.vbuf = 0; + cur.texgendim = -1; + cur.colorscale = vec(1, 1, 1); + cur.alphascale = -1; + loopv(alphavas) if(front || alphavas[i]->alphabacktris) renderva(cur, alphavas[i], RENDERPASS_LIGHTMAP, fogpass); + if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); + + cleanupgeom(cur); + + resetfogcolor(); + if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); } + glDisable(GL_BLEND); + glDepthFunc(GL_LESS); + if(!front) glCullFace(GL_BACK); + } + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE); } void findreflectedvas(vector &vas, int prevvfc = VFC_PART_VISIBLE) { - loopv(vas) - { - vtxarray *va = vas[i]; - if(prevvfc >= VFC_NOT_VISIBLE) va->curvfc = prevvfc; - if(va->curvfc == VFC_FOGGED || va->curvfc == PVS_FOGGED || va->o.z+va->size <= reflectz || isfoggedcube(va->o, va->size)) continue; - bool render = true; - if(va->curvfc == VFC_FULL_VISIBLE) - { - if(va->occluded >= OCCLUDE_BB) continue; - if(va->occluded >= OCCLUDE_GEOM) render = false; - } - else if(va->curvfc == PVS_FULL_VISIBLE) continue; - if(render) - { - if(va->curvfc >= VFC_NOT_VISIBLE) va->distance = (int)vadist(va, camera1->o); - vtxarray **vprev = &reflectedva, *vcur = reflectedva; - while(vcur && va->distance > vcur->distance) - { - vprev = &vcur->rnext; - vcur = vcur->rnext; - } - va->rnext = *vprev; - *vprev = va; - } - if(va->children.length()) findreflectedvas(va->children, va->curvfc); - } + loopv(vas) + { + vtxarray *va = vas[i]; + if(prevvfc >= VFC_NOT_VISIBLE) va->curvfc = prevvfc; + if(va->curvfc == VFC_FOGGED || va->curvfc == PVS_FOGGED || va->o.z+va->size <= reflectz || isfoggedcube(va->o, va->size)) continue; + bool render = true; + if(va->curvfc == VFC_FULL_VISIBLE) + { + if(va->occluded >= OCCLUDE_BB) continue; + if(va->occluded >= OCCLUDE_GEOM) render = false; + } + else if(va->curvfc == PVS_FULL_VISIBLE) continue; + if(render) + { + if(va->curvfc >= VFC_NOT_VISIBLE) va->distance = (int)vadist(va, camera1->o); + vtxarray **vprev = &reflectedva, *vcur = reflectedva; + while(vcur && va->distance > vcur->distance) + { + vprev = &vcur->rnext; + vcur = vcur->rnext; + } + va->rnext = *vprev; + *vprev = va; + } + if(va->children.length()) findreflectedvas(va->children, va->curvfc); + } } void renderreflectedgeom(bool causticspass, bool fogpass) { - if(reflecting) - { - reflectedva = NULL; - findreflectedvas(varoot); - rendergeom(causticspass ? 1 : 0, fogpass); - } - else rendergeom(causticspass ? 1 : 0, fogpass); + if(reflecting) + { + reflectedva = NULL; + findreflectedvas(varoot); + rendergeom(causticspass ? 1 : 0, fogpass); + } + else rendergeom(causticspass ? 1 : 0, fogpass); } static vtxarray *prevskyva = NULL; void renderskyva(vtxarray *va, bool explicitonly = false) { - if(!prevskyva || va->vbuf != prevskyva->vbuf) - { - gle::bindvbo(va->vbuf); - gle::bindebo(va->skybuf); - const vertex *ptr = 0; - gle::vertexpointer(sizeof(vertex), ptr->pos.v); - if(!prevskyva) gle::enablevertex(); - } + if(!prevskyva || va->vbuf != prevskyva->vbuf) + { + gle::bindvbo(va->vbuf); + gle::bindebo(va->skybuf); + const vertex *ptr = 0; + gle::vertexpointer(sizeof(vertex), ptr->pos.v); + if(!prevskyva) gle::enablevertex(); + } - drawvatris(va, explicitonly ? va->explicitsky : va->sky+va->explicitsky, explicitonly ? va->skydata+va->sky : va->skydata); + drawvatris(va, explicitonly ? va->explicitsky : va->sky+va->explicitsky, explicitonly ? va->skydata+va->sky : va->skydata); - if(!explicitonly) xtraverts += va->sky/3; - xtraverts += va->explicitsky/3; + if(!explicitonly) xtraverts += va->sky/3; + xtraverts += va->explicitsky/3; - prevskyva = va; + prevskyva = va; } int renderedsky = 0, renderedexplicitsky = 0, renderedskyfaces = 0, renderedskyclip = INT_MAX; static inline void updateskystats(vtxarray *va) { - renderedsky += va->sky; - renderedexplicitsky += va->explicitsky; - renderedskyfaces |= va->skyfaces&0x3F; - if(!(va->skyfaces&0x1F) || camera1->o.z < va->skyclip) renderedskyclip = min(renderedskyclip, va->skyclip); - else renderedskyclip = 0; + renderedsky += va->sky; + renderedexplicitsky += va->explicitsky; + renderedskyfaces |= va->skyfaces&0x3F; + if(!(va->skyfaces&0x1F) || camera1->o.z < va->skyclip) renderedskyclip = min(renderedskyclip, va->skyclip); + else renderedskyclip = 0; } void renderreflectedskyvas(vector &vas, int prevvfc = VFC_PART_VISIBLE) { - loopv(vas) - { - vtxarray *va = vas[i]; - if(prevvfc >= VFC_NOT_VISIBLE) va->curvfc = prevvfc; - if((va->curvfc == VFC_FULL_VISIBLE && va->occluded >= OCCLUDE_BB) || va->curvfc==PVS_FULL_VISIBLE) continue; - if(va->o.z+va->size <= reflectz || ishiddencube(va->o, va->size)) continue; - if(va->sky+va->explicitsky) - { - updateskystats(va); - renderskyva(va); - } - if(va->children.length()) renderreflectedskyvas(va->children, va->curvfc); - } + loopv(vas) + { + vtxarray *va = vas[i]; + if(prevvfc >= VFC_NOT_VISIBLE) va->curvfc = prevvfc; + if((va->curvfc == VFC_FULL_VISIBLE && va->occluded >= OCCLUDE_BB) || va->curvfc==PVS_FULL_VISIBLE) continue; + if(va->o.z+va->size <= reflectz || ishiddencube(va->o, va->size)) continue; + if(va->sky+va->explicitsky) + { + updateskystats(va); + renderskyva(va); + } + if(va->children.length()) renderreflectedskyvas(va->children, va->curvfc); + } } bool rendersky(bool explicitonly) { - prevskyva = NULL; - renderedsky = renderedexplicitsky = renderedskyfaces = 0; - renderedskyclip = INT_MAX; - - if(reflecting) - { - renderreflectedskyvas(varoot); - } - else for(vtxarray *va = visibleva; va; va = va->next) - { - if((va->occluded >= OCCLUDE_BB && va->skyfaces&0x80) || !(va->sky+va->explicitsky)) continue; - - // count possibly visible sky even if not actually rendered - updateskystats(va); - if(explicitonly && !va->explicitsky) continue; - renderskyva(va, explicitonly); - } - - if(prevskyva) - { - gle::disablevertex(); - gle::clearvbo(); - gle::clearebo(); - } - - return renderedsky+renderedexplicitsky > 0; + prevskyva = NULL; + renderedsky = renderedexplicitsky = renderedskyfaces = 0; + renderedskyclip = INT_MAX; + + if(reflecting) + { + renderreflectedskyvas(varoot); + } + else for(vtxarray *va = visibleva; va; va = va->next) + { + if((va->occluded >= OCCLUDE_BB && va->skyfaces&0x80) || !(va->sky+va->explicitsky)) continue; + + // count possibly visible sky even if not actually rendered + updateskystats(va); + if(explicitonly && !va->explicitsky) continue; + renderskyva(va, explicitonly); + } + + if(prevskyva) + { + gle::disablevertex(); + gle::clearvbo(); + gle::clearebo(); + } + + return renderedsky+renderedexplicitsky > 0; } diff --git a/src/engine/server.cpp b/src/engine/server.cpp index 9348b67..7e338b3 100644 --- a/src/engine/server.cpp +++ b/src/engine/server.cpp @@ -9,73 +9,73 @@ static FILE *logfile = NULL; void closelogfile() { - if(logfile) - { - fclose(logfile); - logfile = NULL; - } + if(logfile) + { + fclose(logfile); + logfile = NULL; + } } FILE *getlogfile() { - return logfile ? logfile : stdout; + return logfile ? logfile : stdout; } void setlogfile(const char *fname) { - closelogfile(); - if(fname && fname[0]) - { - fname = findfile(fname, "w"); - if(fname) logfile = fopen(fname, "w"); - } - FILE *f = getlogfile(); - if(f) setvbuf(f, NULL, _IOLBF, BUFSIZ); + closelogfile(); + if(fname && fname[0]) + { + fname = findfile(fname, "w"); + if(fname) logfile = fopen(fname, "w"); + } + FILE *f = getlogfile(); + if(f) setvbuf(f, NULL, _IOLBF, BUFSIZ); } void logoutf(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - logoutfv(fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + logoutfv(fmt, args); + va_end(args); } static void writelog(FILE *file, const char *buf) { - static uchar ubuf[512]; - size_t len = strlen(buf), carry = 0; - while(carry < len) - { - size_t numu = encodeutf8(ubuf, sizeof(ubuf)-1, &((const uchar *)buf)[carry], len - carry, &carry); - if(carry >= len) ubuf[numu++] = '\n'; - fwrite(ubuf, 1, numu, file); - } + static uchar ubuf[512]; + size_t len = strlen(buf), carry = 0; + while(carry < len) + { + size_t numu = encodeutf8(ubuf, sizeof(ubuf)-1, &((const uchar *)buf)[carry], len - carry, &carry); + if(carry >= len) ubuf[numu++] = '\n'; + fwrite(ubuf, 1, numu, file); + } } static void writelogv(FILE *file, const char *fmt, va_list args) { - static char buf[LOGSTRLEN]; - vformatstring(buf, fmt, args, sizeof(buf)); - writelog(file, buf); + static char buf[LOGSTRLEN]; + vformatstring(buf, fmt, args, sizeof(buf)); + writelog(file, buf); } #ifdef STANDALONE void fatal(const char *fmt, ...) { - void cleanupserver(); - cleanupserver(); + void cleanupserver(); + cleanupserver(); defvformatstring(msg,fmt,fmt); if(logfile) logoutf("%s", msg); - fprintf(stderr, "server error: %s\n", msg); - closelogfile(); - exit(EXIT_FAILURE); + fprintf(stderr, "server error: %s\n", msg); + closelogfile(); + exit(EXIT_FAILURE); } void conoutfv(int type, const char *fmt, va_list args) { - logoutfv(fmt, args); + logoutfv(fmt, args); } #endif @@ -83,13 +83,13 @@ void conoutfv(int type, const char *fmt, va_list args) enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; -struct client // server side version of "dynent" type +struct client // server side version of "dynent" type { - int type; - int num; - ENetPeer *peer; - string hostname; - void *info; + int type; + int num; + ENetPeer *peer; + string hostname; + void *info; }; vector clients; @@ -105,54 +105,54 @@ bool haslocalclients() { return localclients!=0; } client &addclient(int type) { - client *c = NULL; - loopv(clients) if(clients[i]->type==ST_EMPTY) - { - c = clients[i]; - break; - } - if(!c) - { - c = new client; - c->num = clients.length(); - clients.add(c); - } - c->info = server::newclientinfo(); - c->type = type; - switch(type) - { - case ST_TCPIP: nonlocalclients++; break; - case ST_LOCAL: localclients++; break; - } - return *c; + client *c = NULL; + loopv(clients) if(clients[i]->type==ST_EMPTY) + { + c = clients[i]; + break; + } + if(!c) + { + c = new client; + c->num = clients.length(); + clients.add(c); + } + c->info = server::newclientinfo(); + c->type = type; + switch(type) + { + case ST_TCPIP: nonlocalclients++; break; + case ST_LOCAL: localclients++; break; + } + return *c; } void delclient(client *c) { - if(!c) return; - switch(c->type) - { - case ST_TCPIP: nonlocalclients--; if(c->peer) c->peer->data = NULL; break; - case ST_LOCAL: localclients--; break; - case ST_EMPTY: return; - } - c->type = ST_EMPTY; - c->peer = NULL; - if(c->info) - { - server::deleteclientinfo(c->info); - c->info = NULL; - } + if(!c) return; + switch(c->type) + { + case ST_TCPIP: nonlocalclients--; if(c->peer) c->peer->data = NULL; break; + case ST_LOCAL: localclients--; break; + case ST_EMPTY: return; + } + c->type = ST_EMPTY; + c->peer = NULL; + if(c->info) + { + server::deleteclientinfo(c->info); + c->info = NULL; + } } void cleanupserver() { - if(serverhost) enet_host_destroy(serverhost); - serverhost = NULL; + if(serverhost) enet_host_destroy(serverhost); + serverhost = NULL; - if(pongsock != ENET_SOCKET_NULL) enet_socket_destroy(pongsock); - if(lansock != ENET_SOCKET_NULL) enet_socket_destroy(lansock); - pongsock = lansock = ENET_SOCKET_NULL; + if(pongsock != ENET_SOCKET_NULL) enet_socket_destroy(pongsock); + if(lansock != ENET_SOCKET_NULL) enet_socket_destroy(lansock); + pongsock = lansock = ENET_SOCKET_NULL; } VARF(maxclients, 0, DEFAULTCLIENTS, MAXCLIENTS, { if(!maxclients) maxclients = DEFAULTCLIENTS; }); @@ -164,181 +164,181 @@ void process(ENetPacket *packet, int sender, int chan); int getservermtu() { return serverhost ? serverhost->mtu : -1; } void *getclientinfo(int i) { return !clients.inrange(i) || clients[i]->type==ST_EMPTY ? NULL : clients[i]->info; } ENetPeer *getclientpeer(int i) { return clients.inrange(i) && clients[i]->type==ST_TCPIP ? clients[i]->peer : NULL; } -int getnumclients() { return clients.length(); } -uint getclientip(int n) { return clients.inrange(n) && clients[n]->type==ST_TCPIP ? clients[n]->peer->address.host : 0; } +int getnumclients() { return clients.length(); } +uint getclientip(int n) { return clients.inrange(n) && clients[n]->type==ST_TCPIP ? clients[n]->peer->address.host : 0; } void sendpacket(int n, int chan, ENetPacket *packet, int exclude) { - if(n<0) - { - server::recordpacket(chan, packet->data, packet->dataLength); - loopv(clients) if(i!=exclude && server::allowbroadcast(i)) sendpacket(i, chan, packet); - return; - } - switch(clients[n]->type) - { - case ST_TCPIP: - { - enet_peer_send(clients[n]->peer, chan, packet); - break; - } + if(n<0) + { + server::recordpacket(chan, packet->data, packet->dataLength); + loopv(clients) if(i!=exclude && server::allowbroadcast(i)) sendpacket(i, chan, packet); + return; + } + switch(clients[n]->type) + { + case ST_TCPIP: + { + enet_peer_send(clients[n]->peer, chan, packet); + break; + } #ifndef STANDALONE - case ST_LOCAL: - localservertoclient(chan, packet); - break; + case ST_LOCAL: + localservertoclient(chan, packet); + break; #endif - } + } } ENetPacket *sendf(int cn, int chan, const char *format, ...) { - int exclude = -1; - bool reliable = false; - if(*format=='r') { reliable = true; ++format; } - packetbuf p(MAXTRANS, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); - va_list args; - va_start(args, format); - while(*format) switch(*format++) - { - case 'x': - exclude = va_arg(args, int); - break; - - case 'v': - { - int n = va_arg(args, int); - int *v = va_arg(args, int *); - loopi(n) putint(p, v[i]); - break; - } - - case 'i': - { - int n = isdigit(*format) ? *format++-'0' : 1; - loopi(n) putint(p, va_arg(args, int)); - break; - } - case 'f': - { - int n = isdigit(*format) ? *format++-'0' : 1; - loopi(n) putfloat(p, (float)va_arg(args, double)); - break; - } - case 's': sendstring(va_arg(args, const char *), p); break; - case 'm': - { - int n = va_arg(args, int); - p.put(va_arg(args, uchar *), n); - break; - } - } - va_end(args); - ENetPacket *packet = p.finalize(); - sendpacket(cn, chan, packet, exclude); - return packet->referenceCount > 0 ? packet : NULL; + int exclude = -1; + bool reliable = false; + if(*format=='r') { reliable = true; ++format; } + packetbuf p(MAXTRANS, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); + va_list args; + va_start(args, format); + while(*format) switch(*format++) + { + case 'x': + exclude = va_arg(args, int); + break; + + case 'v': + { + int n = va_arg(args, int); + int *v = va_arg(args, int *); + loopi(n) putint(p, v[i]); + break; + } + + case 'i': + { + int n = isdigit(*format) ? *format++-'0' : 1; + loopi(n) putint(p, va_arg(args, int)); + break; + } + case 'f': + { + int n = isdigit(*format) ? *format++-'0' : 1; + loopi(n) putfloat(p, (float)va_arg(args, double)); + break; + } + case 's': sendstring(va_arg(args, const char *), p); break; + case 'm': + { + int n = va_arg(args, int); + p.put(va_arg(args, uchar *), n); + break; + } + } + va_end(args); + ENetPacket *packet = p.finalize(); + sendpacket(cn, chan, packet, exclude); + return packet->referenceCount > 0 ? packet : NULL; } ENetPacket *sendfile(int cn, int chan, stream *file, const char *format, ...) { - if(cn < 0) - { + if(cn < 0) + { #ifdef STANDALONE - return NULL; + return NULL; #endif - } - else if(!clients.inrange(cn)) return NULL; - - int len = (int)min(file->size(), stream::offset(INT_MAX)); - if(len <= 0 || len > 16<<20) return NULL; - - packetbuf p(MAXTRANS+len, ENET_PACKET_FLAG_RELIABLE); - va_list args; - va_start(args, format); - while(*format) switch(*format++) - { - case 'i': - { - int n = isdigit(*format) ? *format++-'0' : 1; - loopi(n) putint(p, va_arg(args, int)); - break; - } - case 's': sendstring(va_arg(args, const char *), p); break; - case 'l': putint(p, len); break; - } - va_end(args); - - file->seek(0, SEEK_SET); - file->read(p.subbuf(len).buf, len); - - ENetPacket *packet = p.finalize(); - if(cn >= 0) sendpacket(cn, chan, packet, -1); + } + else if(!clients.inrange(cn)) return NULL; + + int len = (int)min(file->size(), stream::offset(INT_MAX)); + if(len <= 0 || len > 16<<20) return NULL; + + packetbuf p(MAXTRANS+len, ENET_PACKET_FLAG_RELIABLE); + va_list args; + va_start(args, format); + while(*format) switch(*format++) + { + case 'i': + { + int n = isdigit(*format) ? *format++-'0' : 1; + loopi(n) putint(p, va_arg(args, int)); + break; + } + case 's': sendstring(va_arg(args, const char *), p); break; + case 'l': putint(p, len); break; + } + va_end(args); + + file->seek(0, SEEK_SET); + file->read(p.subbuf(len).buf, len); + + ENetPacket *packet = p.finalize(); + if(cn >= 0) sendpacket(cn, chan, packet, -1); #ifndef STANDALONE - else sendclientpacket(packet, chan); + else sendclientpacket(packet, chan); #endif - return packet->referenceCount > 0 ? packet : NULL; + return packet->referenceCount > 0 ? packet : NULL; } const char *disconnectreason(int reason) { - switch(reason) - { - case DISC_EOP: return "end of packet"; - case DISC_LOCAL: return "server is in local mode"; - case DISC_KICK: return "kicked/banned"; - case DISC_MSGERR: return "message error"; - case DISC_IPBAN: return "ip is banned"; - case DISC_PRIVATE: return "server is in private mode"; - case DISC_MAXCLIENTS: return "server FULL"; - case DISC_TIMEOUT: return "connection timed out"; - case DISC_OVERFLOW: return "overflow"; - case DISC_PASSWORD: return "invalid password"; - default: return NULL; - } + switch(reason) + { + case DISC_EOP: return "end of packet"; + case DISC_LOCAL: return "server is in local mode"; + case DISC_KICK: return "kicked/banned"; + case DISC_MSGERR: return "message error"; + case DISC_IPBAN: return "ip is banned"; + case DISC_PRIVATE: return "server is in private mode"; + case DISC_MAXCLIENTS: return "server FULL"; + case DISC_TIMEOUT: return "connection timed out"; + case DISC_OVERFLOW: return "overflow"; + case DISC_PASSWORD: return "invalid password"; + default: return NULL; + } } void disconnect_client(int n, int reason) { - if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return; - enet_peer_disconnect(clients[n]->peer, reason); - server::clientdisconnect(n); - delclient(clients[n]); - const char *msg = disconnectreason(reason); - string s; - if(msg) formatstring(s, "client (%s) disconnected because: %s", clients[n]->hostname, msg); - else formatstring(s, "client (%s) disconnected", clients[n]->hostname); - logoutf("%s", s); - server::sendservmsg(s); + if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return; + enet_peer_disconnect(clients[n]->peer, reason); + server::clientdisconnect(n); + delclient(clients[n]); + const char *msg = disconnectreason(reason); + string s; + if(msg) formatstring(s, "client (%s) disconnected because: %s", clients[n]->hostname, msg); + else formatstring(s, "client (%s) disconnected", clients[n]->hostname); + logoutf("%s", s); + server::sendservmsg(s); } void kicknonlocalclients(int reason) { - loopv(clients) if(clients[i]->type==ST_TCPIP) disconnect_client(i, reason); + loopv(clients) if(clients[i]->type==ST_TCPIP) disconnect_client(i, reason); } void process(ENetPacket *packet, int sender, int chan) // sender may be -1 { - packetbuf p(packet); - server::parsepacket(sender, chan, p); - if(p.overread()) { disconnect_client(sender, DISC_EOP); return; } + packetbuf p(packet); + server::parsepacket(sender, chan, p); + if(p.overread()) { disconnect_client(sender, DISC_EOP); return; } } void localclienttoserver(int chan, ENetPacket *packet) { - client *c = NULL; - loopv(clients) if(clients[i]->type==ST_LOCAL) { c = clients[i]; break; } - if(c) process(packet, c->num, chan); + client *c = NULL; + loopv(clients) if(clients[i]->type==ST_LOCAL) { c = clients[i]; break; } + if(c) process(packet, c->num, chan); } #ifdef STANDALONE bool resolverwait(const char *name, ENetAddress *address) { - return enet_address_set_host(address, name) >= 0; + return enet_address_set_host(address, name) >= 0; } int connectwithtimeout(ENetSocket sock, const char *hostname, const ENetAddress &remoteaddress) { - return enet_socket_connect(sock, &remoteaddress); + return enet_socket_connect(sock, &remoteaddress); } #endif @@ -351,21 +351,21 @@ VARN(updatemaster, allowupdatemaster, 0, 1, 1); void disconnectmaster() { - if(mastersock != ENET_SOCKET_NULL) - { - server::masterdisconnected(); - enet_socket_destroy(mastersock); - mastersock = ENET_SOCKET_NULL; - } + if(mastersock != ENET_SOCKET_NULL) + { + server::masterdisconnected(); + enet_socket_destroy(mastersock); + mastersock = ENET_SOCKET_NULL; + } - masterout.setsize(0); - masterin.setsize(0); - masteroutpos = masterinpos = 0; + masterout.setsize(0); + masterin.setsize(0); + masteroutpos = masterinpos = 0; - masteraddress.host = ENET_HOST_ANY; - masteraddress.port = ENET_PORT_ANY; + masteraddress.host = ENET_HOST_ANY; + masteraddress.port = ENET_PORT_ANY; - lastupdatemaster = masterconnecting = masterconnected = 0; + lastupdatemaster = masterconnecting = masterconnected = 0; } SVARF(mastername, server::defaultmaster(), disconnectmaster()); @@ -373,199 +373,199 @@ VARF(masterport, 1, server::masterport(), 0xFFFF, disconnectmaster()); ENetSocket connectmaster(bool wait) { - if(!mastername[0]) return ENET_SOCKET_NULL; - if(masteraddress.host == ENET_HOST_ANY) - { - if(isdedicatedserver()) logoutf("looking up %s...", mastername); - masteraddress.port = masterport; - if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL; - } - ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM); - if(sock == ENET_SOCKET_NULL) - { - if(isdedicatedserver()) logoutf("could not open master server socket"); - return ENET_SOCKET_NULL; - } - if(wait || serveraddress.host == ENET_HOST_ANY || !enet_socket_bind(sock, &serveraddress)) - { - enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1); - if(wait) - { - if(!connectwithtimeout(sock, mastername, masteraddress)) return sock; - } - else if(!enet_socket_connect(sock, &masteraddress)) return sock; - } - enet_socket_destroy(sock); - if(isdedicatedserver()) logoutf("could not connect to master server"); - return ENET_SOCKET_NULL; + if(!mastername[0]) return ENET_SOCKET_NULL; + if(masteraddress.host == ENET_HOST_ANY) + { + if(isdedicatedserver()) logoutf("looking up %s...", mastername); + masteraddress.port = masterport; + if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL; + } + ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM); + if(sock == ENET_SOCKET_NULL) + { + if(isdedicatedserver()) logoutf("could not open master server socket"); + return ENET_SOCKET_NULL; + } + if(wait || serveraddress.host == ENET_HOST_ANY || !enet_socket_bind(sock, &serveraddress)) + { + enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1); + if(wait) + { + if(!connectwithtimeout(sock, mastername, masteraddress)) return sock; + } + else if(!enet_socket_connect(sock, &masteraddress)) return sock; + } + enet_socket_destroy(sock); + if(isdedicatedserver()) logoutf("could not connect to master server"); + return ENET_SOCKET_NULL; } bool requestmaster(const char *req) { - if(mastersock == ENET_SOCKET_NULL) - { - mastersock = connectmaster(false); - if(mastersock == ENET_SOCKET_NULL) return false; - lastconnectmaster = masterconnecting = totalmillis ? totalmillis : 1; - } + if(mastersock == ENET_SOCKET_NULL) + { + mastersock = connectmaster(false); + if(mastersock == ENET_SOCKET_NULL) return false; + lastconnectmaster = masterconnecting = totalmillis ? totalmillis : 1; + } - if(masterout.length() >= 4096) return false; + if(masterout.length() >= 4096) return false; - masterout.put(req, strlen(req)); - return true; + masterout.put(req, strlen(req)); + return true; } bool requestmasterf(const char *fmt, ...) { - defvformatstring(req, fmt, fmt); - return requestmaster(req); + defvformatstring(req, fmt, fmt); + return requestmaster(req); } void processmasterinput() { - if(masterinpos >= masterin.length()) return; + if(masterinpos >= masterin.length()) return; - char *input = &masterin[masterinpos], *end = (char *)memchr(input, '\n', masterin.length() - masterinpos); - while(end) - { - *end = '\0'; + char *input = &masterin[masterinpos], *end = (char *)memchr(input, '\n', masterin.length() - masterinpos); + while(end) + { + *end = '\0'; - const char *args = input; - while(args < end && !iscubespace(*args)) args++; - int cmdlen = args - input; - while(args < end && iscubespace(*args)) args++; + const char *args = input; + while(args < end && !iscubespace(*args)) args++; + int cmdlen = args - input; + while(args < end && iscubespace(*args)) args++; - if(matchstring(input, cmdlen, "failreg")) - conoutf(CON_ERROR, "master server registration failed: %s", args); - else if(matchstring(input, cmdlen, "succreg")) - conoutf("master server registration succeeded"); - else server::processmasterinput(input, cmdlen, args); + if(matchstring(input, cmdlen, "failreg")) + conoutf(CON_ERROR, "master server registration failed: %s", args); + else if(matchstring(input, cmdlen, "succreg")) + conoutf("master server registration succeeded"); + else server::processmasterinput(input, cmdlen, args); - end++; - masterinpos = end - masterin.getbuf(); - input = end; - end = (char *)memchr(input, '\n', masterin.length() - masterinpos); - } + end++; + masterinpos = end - masterin.getbuf(); + input = end; + end = (char *)memchr(input, '\n', masterin.length() - masterinpos); + } - if(masterinpos >= masterin.length()) - { - masterin.setsize(0); - masterinpos = 0; - } + if(masterinpos >= masterin.length()) + { + masterin.setsize(0); + masterinpos = 0; + } } void flushmasteroutput() { - if(masterconnecting && totalmillis - masterconnecting >= 60000) - { - logoutf("could not connect to master server"); - disconnectmaster(); - } - if(masterout.empty() || !masterconnected) return; - - ENetBuffer buf; - buf.data = &masterout[masteroutpos]; - buf.dataLength = masterout.length() - masteroutpos; - int sent = enet_socket_send(mastersock, NULL, &buf, 1); - if(sent >= 0) - { - masteroutpos += sent; - if(masteroutpos >= masterout.length()) - { - masterout.setsize(0); - masteroutpos = 0; - } - } - else disconnectmaster(); + if(masterconnecting && totalmillis - masterconnecting >= 60000) + { + logoutf("could not connect to master server"); + disconnectmaster(); + } + if(masterout.empty() || !masterconnected) return; + + ENetBuffer buf; + buf.data = &masterout[masteroutpos]; + buf.dataLength = masterout.length() - masteroutpos; + int sent = enet_socket_send(mastersock, NULL, &buf, 1); + if(sent >= 0) + { + masteroutpos += sent; + if(masteroutpos >= masterout.length()) + { + masterout.setsize(0); + masteroutpos = 0; + } + } + else disconnectmaster(); } void flushmasterinput() { - if(masterin.length() >= masterin.capacity()) - masterin.reserve(4096); + if(masterin.length() >= masterin.capacity()) + masterin.reserve(4096); - ENetBuffer buf; - buf.data = masterin.getbuf() + masterin.length(); - buf.dataLength = masterin.capacity() - masterin.length(); - int recv = enet_socket_receive(mastersock, NULL, &buf, 1); - if(recv > 0) - { - masterin.advance(recv); - processmasterinput(); - } - else disconnectmaster(); + ENetBuffer buf; + buf.data = masterin.getbuf() + masterin.length(); + buf.dataLength = masterin.capacity() - masterin.length(); + int recv = enet_socket_receive(mastersock, NULL, &buf, 1); + if(recv > 0) + { + masterin.advance(recv); + processmasterinput(); + } + else disconnectmaster(); } static ENetAddress pongaddr; void sendserverinforeply(ucharbuf &p) { - ENetBuffer buf; - buf.data = p.buf; - buf.dataLength = p.length(); - enet_socket_send(pongsock, &pongaddr, &buf, 1); + ENetBuffer buf; + buf.data = p.buf; + buf.dataLength = p.length(); + enet_socket_send(pongsock, &pongaddr, &buf, 1); } #define MAXPINGDATA 32 -void checkserversockets() // reply all server info requests -{ - static ENetSocketSet readset, writeset; - ENET_SOCKETSET_EMPTY(readset); - ENET_SOCKETSET_EMPTY(writeset); - ENetSocket maxsock = pongsock; - ENET_SOCKETSET_ADD(readset, pongsock); - if(mastersock != ENET_SOCKET_NULL) - { - maxsock = max(maxsock, mastersock); - ENET_SOCKETSET_ADD(readset, mastersock); - if(!masterconnected) ENET_SOCKETSET_ADD(writeset, mastersock); - } - if(lansock != ENET_SOCKET_NULL) - { - maxsock = max(maxsock, lansock); - ENET_SOCKETSET_ADD(readset, lansock); - } - if(enet_socketset_select(maxsock, &readset, &writeset, 0) <= 0) return; - - ENetBuffer buf; - uchar pong[MAXTRANS]; - loopi(2) - { - ENetSocket sock = i ? lansock : pongsock; - if(sock == ENET_SOCKET_NULL || !ENET_SOCKETSET_CHECK(readset, sock)) continue; - - buf.data = pong; - buf.dataLength = sizeof(pong); - int len = enet_socket_receive(sock, &pongaddr, &buf, 1); - if(len < 0 || len > MAXPINGDATA) continue; - ucharbuf req(pong, len), p(pong, sizeof(pong)); - p.len += len; - server::serverinforeply(req, p); - } - - if(mastersock != ENET_SOCKET_NULL) - { - if(!masterconnected) - { - if(ENET_SOCKETSET_CHECK(readset, mastersock) || ENET_SOCKETSET_CHECK(writeset, mastersock)) - { - int error = 0; - if(enet_socket_get_option(mastersock, ENET_SOCKOPT_ERROR, &error) < 0 || error) - { - logoutf("could not connect to master server"); - disconnectmaster(); - } - else - { - masterconnecting = 0; - masterconnected = totalmillis ? totalmillis : 1; - server::masterconnected(); - } - } - } - if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(readset, mastersock)) flushmasterinput(); - } +void checkserversockets() // reply all server info requests +{ + static ENetSocketSet readset, writeset; + ENET_SOCKETSET_EMPTY(readset); + ENET_SOCKETSET_EMPTY(writeset); + ENetSocket maxsock = pongsock; + ENET_SOCKETSET_ADD(readset, pongsock); + if(mastersock != ENET_SOCKET_NULL) + { + maxsock = max(maxsock, mastersock); + ENET_SOCKETSET_ADD(readset, mastersock); + if(!masterconnected) ENET_SOCKETSET_ADD(writeset, mastersock); + } + if(lansock != ENET_SOCKET_NULL) + { + maxsock = max(maxsock, lansock); + ENET_SOCKETSET_ADD(readset, lansock); + } + if(enet_socketset_select(maxsock, &readset, &writeset, 0) <= 0) return; + + ENetBuffer buf; + uchar pong[MAXTRANS]; + loopi(2) + { + ENetSocket sock = i ? lansock : pongsock; + if(sock == ENET_SOCKET_NULL || !ENET_SOCKETSET_CHECK(readset, sock)) continue; + + buf.data = pong; + buf.dataLength = sizeof(pong); + int len = enet_socket_receive(sock, &pongaddr, &buf, 1); + if(len < 0 || len > MAXPINGDATA) continue; + ucharbuf req(pong, len), p(pong, sizeof(pong)); + p.len += len; + server::serverinforeply(req, p); + } + + if(mastersock != ENET_SOCKET_NULL) + { + if(!masterconnected) + { + if(ENET_SOCKETSET_CHECK(readset, mastersock) || ENET_SOCKETSET_CHECK(writeset, mastersock)) + { + int error = 0; + if(enet_socket_get_option(mastersock, ENET_SOCKOPT_ERROR, &error) < 0 || error) + { + logoutf("could not connect to master server"); + disconnectmaster(); + } + else + { + masterconnecting = 0; + masterconnected = totalmillis ? totalmillis : 1; + server::masterconnected(); + } + } + } + if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(readset, mastersock)) flushmasterinput(); + } } VAR(serveruprate, 0, 0, INT_MAX); @@ -578,143 +578,143 @@ int curtime = 0, lastmillis = 0, elapsedtime = 0, totalmillis = 0; void updatemasterserver() { - if(!masterconnected && lastconnectmaster && totalmillis-lastconnectmaster <= 5*60*1000) return; - if(mastername[0] && allowupdatemaster) requestmasterf("regserv %d\n", serverport); - lastupdatemaster = totalmillis ? totalmillis : 1; + if(!masterconnected && lastconnectmaster && totalmillis-lastconnectmaster <= 5*60*1000) return; + if(mastername[0] && allowupdatemaster) requestmasterf("regserv %d\n", serverport); + lastupdatemaster = totalmillis ? totalmillis : 1; } uint totalsecs = 0; void updatetime() { - static int lastsec = 0; - if(totalmillis - lastsec >= 1000) - { - int cursecs = (totalmillis - lastsec) / 1000; - totalsecs += cursecs; - lastsec += cursecs * 1000; - } + static int lastsec = 0; + if(totalmillis - lastsec >= 1000) + { + int cursecs = (totalmillis - lastsec) / 1000; + totalsecs += cursecs; + lastsec += cursecs * 1000; + } } void serverslice(bool dedicated, uint timeout) // main server update, called from main loop in sp, or from below in dedicated server { - if(!serverhost) - { - server::serverupdate(); - server::sendpackets(); - return; - } - - // below is network only - - if(dedicated) - { - int millis = (int)enet_time_get(); - elapsedtime = millis - totalmillis; - static int timeerr = 0; - int scaledtime = server::scaletime(elapsedtime) + timeerr; - curtime = scaledtime/100; - timeerr = scaledtime%100; - if(server::ispaused()) curtime = 0; - lastmillis += curtime; - totalmillis = millis; - updatetime(); - } - server::serverupdate(); - - flushmasteroutput(); - checkserversockets(); - - if(!lastupdatemaster || totalmillis-lastupdatemaster>60*60*1000) // send alive signal to masterserver every hour of uptime - updatemasterserver(); - - if(totalmillis-laststatus>60*1000) // display bandwidth stats, useful for server ops - { - laststatus = totalmillis; - if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) logoutf("status: %d remote clients, %.1f send, %.1f rec (K/sec)", nonlocalclients, serverhost->totalSentData/60.0f/1024, serverhost->totalReceivedData/60.0f/1024); - serverhost->totalSentData = serverhost->totalReceivedData = 0; - } - - ENetEvent event; - bool serviced = false; - while(!serviced) - { - if(enet_host_check_events(serverhost, &event) <= 0) - { - if(enet_host_service(serverhost, &event, timeout) <= 0) break; - serviced = true; - } - switch(event.type) - { - case ENET_EVENT_TYPE_CONNECT: - { - client &c = addclient(ST_TCPIP); - c.peer = event.peer; - c.peer->data = &c; - string hn; - copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); - logoutf("client connected (%s)", c.hostname); - int reason = server::clientconnect(c.num); - if(reason) disconnect_client(c.num, reason); - break; - } - case ENET_EVENT_TYPE_RECEIVE: - { - client *c = (client *)event.peer->data; - if(c) process(event.packet, c->num, event.channelID); - if(event.packet->referenceCount==0) enet_packet_destroy(event.packet); - break; - } - case ENET_EVENT_TYPE_DISCONNECT: - { - client *c = (client *)event.peer->data; - if(!c) break; - logoutf("disconnected client (%s)", c->hostname); - server::clientdisconnect(c->num); - delclient(c); - break; - } - default: - break; - } - } - if(server::sendpackets()) enet_host_flush(serverhost); + if(!serverhost) + { + server::serverupdate(); + server::sendpackets(); + return; + } + + // below is network only + + if(dedicated) + { + int millis = (int)enet_time_get(); + elapsedtime = millis - totalmillis; + static int timeerr = 0; + int scaledtime = server::scaletime(elapsedtime) + timeerr; + curtime = scaledtime/100; + timeerr = scaledtime%100; + if(server::ispaused()) curtime = 0; + lastmillis += curtime; + totalmillis = millis; + updatetime(); + } + server::serverupdate(); + + flushmasteroutput(); + checkserversockets(); + + if(!lastupdatemaster || totalmillis-lastupdatemaster>60*60*1000) // send alive signal to masterserver every hour of uptime + updatemasterserver(); + + if(totalmillis-laststatus>60*1000) // display bandwidth stats, useful for server ops + { + laststatus = totalmillis; + if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) logoutf("status: %d remote clients, %.1f send, %.1f rec (K/sec)", nonlocalclients, serverhost->totalSentData/60.0f/1024, serverhost->totalReceivedData/60.0f/1024); + serverhost->totalSentData = serverhost->totalReceivedData = 0; + } + + ENetEvent event; + bool serviced = false; + while(!serviced) + { + if(enet_host_check_events(serverhost, &event) <= 0) + { + if(enet_host_service(serverhost, &event, timeout) <= 0) break; + serviced = true; + } + switch(event.type) + { + case ENET_EVENT_TYPE_CONNECT: + { + client &c = addclient(ST_TCPIP); + c.peer = event.peer; + c.peer->data = &c; + string hn; + copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); + logoutf("client connected (%s)", c.hostname); + int reason = server::clientconnect(c.num); + if(reason) disconnect_client(c.num, reason); + break; + } + case ENET_EVENT_TYPE_RECEIVE: + { + client *c = (client *)event.peer->data; + if(c) process(event.packet, c->num, event.channelID); + if(event.packet->referenceCount==0) enet_packet_destroy(event.packet); + break; + } + case ENET_EVENT_TYPE_DISCONNECT: + { + client *c = (client *)event.peer->data; + if(!c) break; + logoutf("disconnected client (%s)", c->hostname); + server::clientdisconnect(c->num); + delclient(c); + break; + } + default: + break; + } + } + if(server::sendpackets()) enet_host_flush(serverhost); } void flushserver(bool force) { - if(server::sendpackets(force) && serverhost) enet_host_flush(serverhost); + if(server::sendpackets(force) && serverhost) enet_host_flush(serverhost); } #ifndef STANDALONE void localdisconnect(bool cleanup) { - bool disconnected = false; - loopv(clients) if(clients[i]->type==ST_LOCAL) - { - server::localdisconnect(i); - delclient(clients[i]); - disconnected = true; - } - if(!disconnected) return; - game::gamedisconnect(cleanup); - mainmenu = 1; + bool disconnected = false; + loopv(clients) if(clients[i]->type==ST_LOCAL) + { + server::localdisconnect(i); + delclient(clients[i]); + disconnected = true; + } + if(!disconnected) return; + game::gamedisconnect(cleanup); + mainmenu = 1; } void localconnect() { - if(initing) return; - client &c = addclient(ST_LOCAL); - copystring(c.hostname, "local"); - game::gameconnect(false); - server::localconnect(c.num); + if(initing) return; + client &c = addclient(ST_LOCAL); + copystring(c.hostname, "local"); + game::gameconnect(false); + server::localconnect(c.num); } #endif void logoutfv(const char *fmt, va_list args) { - FILE *f = getlogfile(); - if(f) writelogv(f, fmt, args); + FILE *f = getlogfile(); + if(f) writelogv(f, fmt, args); } static bool dedicatedserver = false; @@ -723,121 +723,121 @@ bool isdedicatedserver() { return dedicatedserver; } void rundedicatedserver() { - dedicatedserver = true; - logoutf("dedicated server started, waiting for clients..."); - for(;;) serverslice(true, 5); - dedicatedserver = false; + dedicatedserver = true; + logoutf("dedicated server started, waiting for clients..."); + for(;;) serverslice(true, 5); + dedicatedserver = false; } bool servererror(bool dedicated, const char *desc) { #ifndef STANDALONE - if(!dedicated) - { - conoutf(CON_ERROR, "%s", desc); - cleanupserver(); - } - else + if(!dedicated) + { + conoutf(CON_ERROR, "%s", desc); + cleanupserver(); + } + else #endif - fatal("%s", desc); - return false; + fatal("%s", desc); + return false; } bool setuplistenserver(bool dedicated) { - ENetAddress address = { ENET_HOST_ANY, enet_uint16(serverport <= 0 ? server::serverport() : serverport) }; - if(*serverip) - { - if(enet_address_set_host(&address, serverip)<0) conoutf(CON_WARN, "WARNING: server ip not resolved"); - else serveraddress.host = address.host; - } - serverhost = enet_host_create(&address, min(maxclients + server::reserveclients(), MAXCLIENTS), server::numchannels(), 0, serveruprate); - if(!serverhost) return servererror(dedicated, "could not create server host"); - serverhost->duplicatePeers = maxdupclients ? maxdupclients : MAXCLIENTS; - address.port = server::serverinfoport(serverport > 0 ? serverport : -1); - pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); - if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0) - { - enet_socket_destroy(pongsock); - pongsock = ENET_SOCKET_NULL; - } - if(pongsock == ENET_SOCKET_NULL) return servererror(dedicated, "could not create server info socket"); - else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1); - address.port = server::laninfoport(); - lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); - if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0)) - { - enet_socket_destroy(lansock); - lansock = ENET_SOCKET_NULL; - } - if(lansock == ENET_SOCKET_NULL) conoutf(CON_WARN, "WARNING: could not create LAN server info socket"); - else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1); - return true; + ENetAddress address = { ENET_HOST_ANY, enet_uint16(serverport <= 0 ? server::serverport() : serverport) }; + if(*serverip) + { + if(enet_address_set_host(&address, serverip)<0) conoutf(CON_WARN, "WARNING: server ip not resolved"); + else serveraddress.host = address.host; + } + serverhost = enet_host_create(&address, min(maxclients + server::reserveclients(), MAXCLIENTS), server::numchannels(), 0, serveruprate); + if(!serverhost) return servererror(dedicated, "could not create server host"); + serverhost->duplicatePeers = maxdupclients ? maxdupclients : MAXCLIENTS; + address.port = server::serverinfoport(serverport > 0 ? serverport : -1); + pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0) + { + enet_socket_destroy(pongsock); + pongsock = ENET_SOCKET_NULL; + } + if(pongsock == ENET_SOCKET_NULL) return servererror(dedicated, "could not create server info socket"); + else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1); + address.port = server::laninfoport(); + lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0)) + { + enet_socket_destroy(lansock); + lansock = ENET_SOCKET_NULL; + } + if(lansock == ENET_SOCKET_NULL) conoutf(CON_WARN, "WARNING: could not create LAN server info socket"); + else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1); + return true; } void initserver(bool listen, bool dedicated) { - execfile("server-init.cfg", false); + execfile("server-init.cfg", false); - if(listen) setuplistenserver(dedicated); + if(listen) setuplistenserver(dedicated); - server::serverinit(); + server::serverinit(); - if(listen) - { - dedicatedserver = dedicated; - updatemasterserver(); - if(dedicated) rundedicatedserver(); // never returns + if(listen) + { + dedicatedserver = dedicated; + updatemasterserver(); + if(dedicated) rundedicatedserver(); // never returns #ifndef STANDALONE - else conoutf("listen server started"); + else conoutf("listen server started"); #endif - } + } } #ifndef STANDALONE void startlistenserver(int *usemaster) { - if(serverhost) { conoutf(CON_ERROR, "listen server is already running"); return; } + if(serverhost) { conoutf(CON_ERROR, "listen server is already running"); return; } - allowupdatemaster = *usemaster>0 ? 1 : 0; + allowupdatemaster = *usemaster>0 ? 1 : 0; - if(!setuplistenserver(false)) return; + if(!setuplistenserver(false)) return; - updatemasterserver(); + updatemasterserver(); - conoutf("listen server started for %d clients%s", maxclients, allowupdatemaster ? " and listed with master server" : ""); + conoutf("listen server started for %d clients%s", maxclients, allowupdatemaster ? " and listed with master server" : ""); } COMMAND(startlistenserver, "i"); void stoplistenserver() { - if(!serverhost) { conoutf(CON_ERROR, "listen server is not running"); return; } + if(!serverhost) { conoutf(CON_ERROR, "listen server is not running"); return; } - kicknonlocalclients(); - enet_host_flush(serverhost); - cleanupserver(); + kicknonlocalclients(); + enet_host_flush(serverhost); + cleanupserver(); - conoutf("listen server stopped"); + conoutf("listen server stopped"); } COMMAND(stoplistenserver, ""); #endif bool serveroption(char *opt) { - switch(opt[1]) - { - case 'u': setvar("serveruprate", atoi(opt+2)); return true; - case 'c': setvar("maxclients", atoi(opt+2)); return true; - case 'i': setsvar("serverip", opt+2); return true; - case 'j': setvar("serverport", atoi(opt+2)); return true; - case 'm': setsvar("mastername", opt+2); setvar("updatemaster", mastername[0] ? 1 : 0); return true; + switch(opt[1]) + { + case 'u': setvar("serveruprate", atoi(opt+2)); return true; + case 'c': setvar("maxclients", atoi(opt+2)); return true; + case 'i': setsvar("serverip", opt+2); return true; + case 'j': setvar("serverport", atoi(opt+2)); return true; + case 'm': setsvar("mastername", opt+2); setvar("updatemaster", mastername[0] ? 1 : 0); return true; #ifdef STANDALONE - case 'q': logoutf("Using home directory: %s", opt); sethomedir(opt+2); return true; - case 'k': logoutf("Adding package directory: %s", opt); addpackagedir(opt+2); return true; - case 'g': logoutf("Setting log file: %s", opt); setlogfile(opt+2); return true; + case 'q': logoutf("Using home directory: %s", opt); sethomedir(opt+2); return true; + case 'k': logoutf("Adding package directory: %s", opt); addpackagedir(opt+2); return true; + case 'g': logoutf("Setting log file: %s", opt); setlogfile(opt+2); return true; #endif - default: return false; - } + default: return false; + } } vector gameargs; @@ -845,13 +845,13 @@ vector gameargs; #ifdef STANDALONE int main(int argc, char **argv) { - setlogfile(NULL); - if(enet_initialize()<0) fatal("Unable to initialise network module"); - atexit(enet_deinitialize); - enet_time_set(0); - for(int i = 1; i resolverthreads; @@ -28,234 +28,234 @@ SDL_cond *querycond, *resultcond; int resolverloop(void * data) { - resolverthread *rt = (resolverthread *)data; - SDL_LockMutex(resolvermutex); - SDL_Thread *thread = rt->thread; - SDL_UnlockMutex(resolvermutex); - if(!thread || SDL_GetThreadID(thread) != SDL_ThreadID()) - return 0; - while(thread == rt->thread) - { - SDL_LockMutex(resolvermutex); - while(resolverqueries.empty()) SDL_CondWait(querycond, resolvermutex); - rt->query = resolverqueries.pop(); - rt->starttime = totalmillis; - SDL_UnlockMutex(resolvermutex); - - ENetAddress address = { ENET_HOST_ANY, ENET_PORT_ANY }; - enet_address_set_host(&address, rt->query); - - SDL_LockMutex(resolvermutex); - if(rt->query && thread == rt->thread) - { - resolverresult &rr = resolverresults.add(); - rr.query = rt->query; - rr.address = address; - rt->query = NULL; - rt->starttime = 0; - SDL_CondSignal(resultcond); - } - SDL_UnlockMutex(resolvermutex); - } - return 0; + resolverthread *rt = (resolverthread *)data; + SDL_LockMutex(resolvermutex); + SDL_Thread *thread = rt->thread; + SDL_UnlockMutex(resolvermutex); + if(!thread || SDL_GetThreadID(thread) != SDL_ThreadID()) + return 0; + while(thread == rt->thread) + { + SDL_LockMutex(resolvermutex); + while(resolverqueries.empty()) SDL_CondWait(querycond, resolvermutex); + rt->query = resolverqueries.pop(); + rt->starttime = totalmillis; + SDL_UnlockMutex(resolvermutex); + + ENetAddress address = { ENET_HOST_ANY, ENET_PORT_ANY }; + enet_address_set_host(&address, rt->query); + + SDL_LockMutex(resolvermutex); + if(rt->query && thread == rt->thread) + { + resolverresult &rr = resolverresults.add(); + rr.query = rt->query; + rr.address = address; + rt->query = NULL; + rt->starttime = 0; + SDL_CondSignal(resultcond); + } + SDL_UnlockMutex(resolvermutex); + } + return 0; } void resolverinit() { - resolvermutex = SDL_CreateMutex(); - querycond = SDL_CreateCond(); - resultcond = SDL_CreateCond(); - - SDL_LockMutex(resolvermutex); - loopi(RESOLVERTHREADS) - { - resolverthread &rt = resolverthreads.add(); - rt.query = NULL; - rt.starttime = 0; - rt.thread = SDL_CreateThread(resolverloop, "resolver", &rt); - } - SDL_UnlockMutex(resolvermutex); + resolvermutex = SDL_CreateMutex(); + querycond = SDL_CreateCond(); + resultcond = SDL_CreateCond(); + + SDL_LockMutex(resolvermutex); + loopi(RESOLVERTHREADS) + { + resolverthread &rt = resolverthreads.add(); + rt.query = NULL; + rt.starttime = 0; + rt.thread = SDL_CreateThread(resolverloop, "resolver", &rt); + } + SDL_UnlockMutex(resolvermutex); } void resolverstop(resolverthread &rt) { - SDL_LockMutex(resolvermutex); - if(rt.query) - { + SDL_LockMutex(resolvermutex); + if(rt.query) + { #if SDL_VERSION_ATLEAST(2, 0, 2) - SDL_DetachThread(rt.thread); + SDL_DetachThread(rt.thread); #endif - rt.thread = SDL_CreateThread(resolverloop, "resolver", &rt); - } - rt.query = NULL; - rt.starttime = 0; - SDL_UnlockMutex(resolvermutex); + rt.thread = SDL_CreateThread(resolverloop, "resolver", &rt); + } + rt.query = NULL; + rt.starttime = 0; + SDL_UnlockMutex(resolvermutex); } void resolverclear() { - if(resolverthreads.empty()) return; - - SDL_LockMutex(resolvermutex); - resolverqueries.shrink(0); - resolverresults.shrink(0); - loopv(resolverthreads) - { - resolverthread &rt = resolverthreads[i]; - resolverstop(rt); - } - SDL_UnlockMutex(resolvermutex); + if(resolverthreads.empty()) return; + + SDL_LockMutex(resolvermutex); + resolverqueries.shrink(0); + resolverresults.shrink(0); + loopv(resolverthreads) + { + resolverthread &rt = resolverthreads[i]; + resolverstop(rt); + } + SDL_UnlockMutex(resolvermutex); } void resolverquery(const char *name) { - if(resolverthreads.empty()) resolverinit(); + if(resolverthreads.empty()) resolverinit(); - SDL_LockMutex(resolvermutex); - resolverqueries.add(name); - SDL_CondSignal(querycond); - SDL_UnlockMutex(resolvermutex); + SDL_LockMutex(resolvermutex); + resolverqueries.add(name); + SDL_CondSignal(querycond); + SDL_UnlockMutex(resolvermutex); } bool resolvercheck(const char **name, ENetAddress *address) { - bool resolved = false; - SDL_LockMutex(resolvermutex); - if(!resolverresults.empty()) - { - resolverresult &rr = resolverresults.pop(); - *name = rr.query; - address->host = rr.address.host; - resolved = true; - } - else loopv(resolverthreads) - { - resolverthread &rt = resolverthreads[i]; - if(rt.query && totalmillis - rt.starttime > RESOLVERLIMIT) - { - resolverstop(rt); - *name = rt.query; - resolved = true; - } - } - SDL_UnlockMutex(resolvermutex); - return resolved; + bool resolved = false; + SDL_LockMutex(resolvermutex); + if(!resolverresults.empty()) + { + resolverresult &rr = resolverresults.pop(); + *name = rr.query; + address->host = rr.address.host; + resolved = true; + } + else loopv(resolverthreads) + { + resolverthread &rt = resolverthreads[i]; + if(rt.query && totalmillis - rt.starttime > RESOLVERLIMIT) + { + resolverstop(rt); + *name = rt.query; + resolved = true; + } + } + SDL_UnlockMutex(resolvermutex); + return resolved; } bool resolverwait(const char *name, ENetAddress *address) { - if(resolverthreads.empty()) resolverinit(); - - defformatstring(text, "resolving %s... (esc to abort)", name); - renderprogress(0, text); - - SDL_LockMutex(resolvermutex); - resolverqueries.add(name); - SDL_CondSignal(querycond); - int starttime = SDL_GetTicks(), timeout = 0; - bool resolved = false; - for(;;) - { - SDL_CondWaitTimeout(resultcond, resolvermutex, 250); - loopv(resolverresults) if(resolverresults[i].query == name) - { - address->host = resolverresults[i].address.host; - resolverresults.remove(i); - resolved = true; - break; - } - if(resolved) break; - - timeout = SDL_GetTicks() - starttime; - renderprogress(min(float(timeout)/RESOLVERLIMIT, 1.0f), text); - if(interceptkey(SDLK_ESCAPE)) timeout = RESOLVERLIMIT + 1; - if(timeout > RESOLVERLIMIT) break; - } - if(!resolved && timeout > RESOLVERLIMIT) - { - loopv(resolverthreads) - { - resolverthread &rt = resolverthreads[i]; - if(rt.query == name) { resolverstop(rt); break; } - } - } - SDL_UnlockMutex(resolvermutex); - return resolved && address->host != ENET_HOST_ANY; + if(resolverthreads.empty()) resolverinit(); + + defformatstring(text, "resolving %s... (esc to abort)", name); + renderprogress(0, text); + + SDL_LockMutex(resolvermutex); + resolverqueries.add(name); + SDL_CondSignal(querycond); + int starttime = SDL_GetTicks(), timeout = 0; + bool resolved = false; + for(;;) + { + SDL_CondWaitTimeout(resultcond, resolvermutex, 250); + loopv(resolverresults) if(resolverresults[i].query == name) + { + address->host = resolverresults[i].address.host; + resolverresults.remove(i); + resolved = true; + break; + } + if(resolved) break; + + timeout = SDL_GetTicks() - starttime; + renderprogress(min(float(timeout)/RESOLVERLIMIT, 1.0f), text); + if(interceptkey(SDLK_ESCAPE)) timeout = RESOLVERLIMIT + 1; + if(timeout > RESOLVERLIMIT) break; + } + if(!resolved && timeout > RESOLVERLIMIT) + { + loopv(resolverthreads) + { + resolverthread &rt = resolverthreads[i]; + if(rt.query == name) { resolverstop(rt); break; } + } + } + SDL_UnlockMutex(resolvermutex); + return resolved && address->host != ENET_HOST_ANY; } #define CONNLIMIT 20000 int connectwithtimeout(ENetSocket sock, const char *hostname, const ENetAddress &address) { - defformatstring(text, "connecting to %s... (esc to abort)", hostname); - renderprogress(0, text); - - ENetSocketSet readset, writeset; - if(!enet_socket_connect(sock, &address)) for(int starttime = SDL_GetTicks(), timeout = 0; timeout <= CONNLIMIT;) - { - ENET_SOCKETSET_EMPTY(readset); - ENET_SOCKETSET_EMPTY(writeset); - ENET_SOCKETSET_ADD(readset, sock); - ENET_SOCKETSET_ADD(writeset, sock); - int result = enet_socketset_select(sock, &readset, &writeset, 250); - if(result < 0) break; - else if(result > 0) - { - if(ENET_SOCKETSET_CHECK(readset, sock) || ENET_SOCKETSET_CHECK(writeset, sock)) - { - int error = 0; - if(enet_socket_get_option(sock, ENET_SOCKOPT_ERROR, &error) < 0 || error) break; - return 0; - } - } - timeout = SDL_GetTicks() - starttime; - renderprogress(min(float(timeout)/CONNLIMIT, 1.0f), text); - if(interceptkey(SDLK_ESCAPE)) break; - } - - return -1; + defformatstring(text, "connecting to %s... (esc to abort)", hostname); + renderprogress(0, text); + + ENetSocketSet readset, writeset; + if(!enet_socket_connect(sock, &address)) for(int starttime = SDL_GetTicks(), timeout = 0; timeout <= CONNLIMIT;) + { + ENET_SOCKETSET_EMPTY(readset); + ENET_SOCKETSET_EMPTY(writeset); + ENET_SOCKETSET_ADD(readset, sock); + ENET_SOCKETSET_ADD(writeset, sock); + int result = enet_socketset_select(sock, &readset, &writeset, 250); + if(result < 0) break; + else if(result > 0) + { + if(ENET_SOCKETSET_CHECK(readset, sock) || ENET_SOCKETSET_CHECK(writeset, sock)) + { + int error = 0; + if(enet_socket_get_option(sock, ENET_SOCKOPT_ERROR, &error) < 0 || error) break; + return 0; + } + } + timeout = SDL_GetTicks() - starttime; + renderprogress(min(float(timeout)/CONNLIMIT, 1.0f), text); + if(interceptkey(SDLK_ESCAPE)) break; + } + + return -1; } struct pingattempts { - enum { MAXATTEMPTS = 2 }; + enum { MAXATTEMPTS = 2 }; - int offset, attempts[MAXATTEMPTS]; + int offset, attempts[MAXATTEMPTS]; - pingattempts() : offset(0) { clearattempts(); } + pingattempts() : offset(0) { clearattempts(); } - void clearattempts() { memset(attempts, 0, sizeof(attempts)); } + void clearattempts() { memset(attempts, 0, sizeof(attempts)); } - void setoffset() { offset = 1 + rnd(0xFFFFFF); } + void setoffset() { offset = 1 + rnd(0xFFFFFF); } - int encodeping(int millis) - { - millis += offset; - return millis ? millis : 1; - } + int encodeping(int millis) + { + millis += offset; + return millis ? millis : 1; + } - int decodeping(int val) - { - return val - offset; - } + int decodeping(int val) + { + return val - offset; + } - int addattempt(int millis) - { - int val = encodeping(millis); - loopk(MAXATTEMPTS-1) attempts[k+1] = attempts[k]; - attempts[0] = val; - return val; - } + int addattempt(int millis) + { + int val = encodeping(millis); + loopk(MAXATTEMPTS-1) attempts[k+1] = attempts[k]; + attempts[0] = val; + return val; + } - bool checkattempt(int val, bool del = true) - { - if(val) loopk(MAXATTEMPTS) if(attempts[k] == val) - { - if(del) attempts[k] = 0; - return true; - } - return false; - } + bool checkattempt(int val, bool del = true) + { + if(val) loopk(MAXATTEMPTS) if(attempts[k] == val) + { + if(del) attempts[k] = 0; + return true; + } + return false; + } }; @@ -263,95 +263,95 @@ enum { UNRESOLVED = 0, RESOLVING, RESOLVED }; struct serverinfo : pingattempts { - enum - { - WAITING = INT_MAX, - - MAXPINGS = 3 - }; - - string name, map, sdesc; - int port, numplayers, resolved, ping, lastping, nextping; - int pings[MAXPINGS]; - vector attr; - ENetAddress address; - bool keep; - const char *password; - - serverinfo() - : port(-1), numplayers(0), resolved(UNRESOLVED), keep(false), password(NULL) - { - name[0] = map[0] = sdesc[0] = '\0'; - clearpings(); - setoffset(); - } - - ~serverinfo() - { - DELETEA(password); - } - - void clearpings() - { - ping = WAITING; - loopk(MAXPINGS) pings[k] = WAITING; - nextping = 0; - lastping = -1; - clearattempts(); - } - - void cleanup() - { - clearpings(); - attr.setsize(0); - numplayers = 0; - } - - void reset() - { - lastping = -1; - } - - void checkdecay(int decay) - { - if(lastping >= 0 && totalmillis - lastping >= decay) - cleanup(); - if(lastping < 0) lastping = totalmillis; - } - - void calcping() - { - int numpings = 0, totalpings = 0; - loopk(MAXPINGS) if(pings[k] != WAITING) { totalpings += pings[k]; numpings++; } - ping = numpings ? totalpings/numpings : WAITING; - } - - void addping(int rtt, int millis) - { - if(millis >= lastping) lastping = -1; - pings[nextping] = rtt; - nextping = (nextping+1)%MAXPINGS; - calcping(); - } - - static bool compare(serverinfo *a, serverinfo *b) - { - bool ac = (a->attr.length() != 0) && (a->attr[0]==PROTOCOL_VERSION); - bool bc = (b->attr.length() != 0) && (b->attr[0]==PROTOCOL_VERSION); - if(ac > bc) return true; - if(bc > ac) return false; - if(a->keep > b->keep) return true; - if(a->keep < b->keep) return false; - if(a->numplayers < b->numplayers) return false; - if(a->numplayers > b->numplayers) return true; - if(a->ping > b->ping) return false; - if(a->ping < b->ping) return true; - int cmp = strcmp(a->name, b->name); - if(cmp != 0) return cmp < 0; - if(a->port < b->port) return true; - if(a->port > b->port) return false; - return false; - } + enum + { + WAITING = INT_MAX, + + MAXPINGS = 3 + }; + + string name, map, sdesc; + int port, numplayers, resolved, ping, lastping, nextping; + int pings[MAXPINGS]; + vector attr; + ENetAddress address; + bool keep; + const char *password; + + serverinfo() + : port(-1), numplayers(0), resolved(UNRESOLVED), keep(false), password(NULL) + { + name[0] = map[0] = sdesc[0] = '\0'; + clearpings(); + setoffset(); + } + + ~serverinfo() + { + DELETEA(password); + } + + void clearpings() + { + ping = WAITING; + loopk(MAXPINGS) pings[k] = WAITING; + nextping = 0; + lastping = -1; + clearattempts(); + } + + void cleanup() + { + clearpings(); + attr.setsize(0); + numplayers = 0; + } + + void reset() + { + lastping = -1; + } + + void checkdecay(int decay) + { + if(lastping >= 0 && totalmillis - lastping >= decay) + cleanup(); + if(lastping < 0) lastping = totalmillis; + } + + void calcping() + { + int numpings = 0, totalpings = 0; + loopk(MAXPINGS) if(pings[k] != WAITING) { totalpings += pings[k]; numpings++; } + ping = numpings ? totalpings/numpings : WAITING; + } + + void addping(int rtt, int millis) + { + if(millis >= lastping) lastping = -1; + pings[nextping] = rtt; + nextping = (nextping+1)%MAXPINGS; + calcping(); + } + + static bool compare(serverinfo *a, serverinfo *b) + { + bool ac = (a->attr.length() != 0) && (a->attr[0]==PROTOCOL_VERSION); + bool bc = (b->attr.length() != 0) && (b->attr[0]==PROTOCOL_VERSION); + if(ac > bc) return true; + if(bc > ac) return false; + if(a->keep > b->keep) return true; + if(a->keep < b->keep) return false; + if(a->numplayers < b->numplayers) return false; + if(a->numplayers > b->numplayers) return true; + if(a->ping > b->ping) return false; + if(a->ping < b->ping) return true; + int cmp = strcmp(a->name, b->name); + if(cmp != 0) return cmp < 0; + if(a->port < b->port) return true; + if(a->port > b->port) return false; + return false; + } }; vector servers; @@ -360,44 +360,44 @@ int lastinfo = 0; static serverinfo *newserver(const char *name, int port, uint ip = ENET_HOST_ANY) { - serverinfo *si = new serverinfo; - si->address.host = ip; - si->address.port = server::serverinfoport(port); - if(ip!=ENET_HOST_ANY) si->resolved = RESOLVED; + serverinfo *si = new serverinfo; + si->address.host = ip; + si->address.port = server::serverinfoport(port); + if(ip!=ENET_HOST_ANY) si->resolved = RESOLVED; - si->port = port; - if(name) copystring(si->name, name); - else if(ip==ENET_HOST_ANY || enet_address_get_host_ip(&si->address, si->name, sizeof(si->name)) < 0) - { - delete si; - return NULL; + si->port = port; + if(name) copystring(si->name, name); + else if(ip==ENET_HOST_ANY || enet_address_get_host_ip(&si->address, si->name, sizeof(si->name)) < 0) + { + delete si; + return NULL; - } + } - servers.add(si); + servers.add(si); - return si; + return si; } void addserver(const char *name, int port, const char *password, bool keep) { - if(port <= 0) port = server::serverport(); - loopv(servers) - { - serverinfo *s = servers[i]; - if(strcmp(s->name, name) || s->port != port) continue; - if(password && (!s->password || strcmp(s->password, password))) - { - DELETEA(s->password); - s->password = newstring(password); - } - if(keep && !s->keep) s->keep = true; - return; - } - serverinfo *s = newserver(name, port); - if(!s) return; - if(password) s->password = newstring(password); - s->keep = keep; + if(port <= 0) port = server::serverport(); + loopv(servers) + { + serverinfo *s = servers[i]; + if(strcmp(s->name, name) || s->port != port) continue; + if(password && (!s->password || strcmp(s->password, password))) + { + DELETEA(s->password); + s->password = newstring(password); + } + if(keep && !s->keep) s->keep = true; + return; + } + serverinfo *s = newserver(name, port); + if(!s) return; + if(password) s->password = newstring(password); + s->keep = keep; } VARP(searchlan, 0, 0, 1); @@ -409,134 +409,134 @@ pingattempts lanpings; template static inline void buildping(ENetBuffer &buf, uchar (&ping)[N], pingattempts &a) { - ucharbuf p(ping, N); - putint(p, a.addattempt(totalmillis)); - buf.data = ping; - buf.dataLength = p.length(); + ucharbuf p(ping, N); + putint(p, a.addattempt(totalmillis)); + buf.data = ping; + buf.dataLength = p.length(); } void pingservers() { - if(pingsock == ENET_SOCKET_NULL) - { - pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); - if(pingsock == ENET_SOCKET_NULL) - { - lastinfo = totalmillis; - return; - } - enet_socket_set_option(pingsock, ENET_SOCKOPT_NONBLOCK, 1); - enet_socket_set_option(pingsock, ENET_SOCKOPT_BROADCAST, 1); - - lanpings.setoffset(); - } - - ENetBuffer buf; - uchar ping[MAXTRANS]; - - static int lastping = 0; - if(lastping >= servers.length()) lastping = 0; - loopi(maxservpings ? min(servers.length(), maxservpings) : servers.length()) - { - serverinfo &si = *servers[lastping]; - if(++lastping >= servers.length()) lastping = 0; - if(si.address.host == ENET_HOST_ANY) continue; - buildping(buf, ping, si); - enet_socket_send(pingsock, &si.address, &buf, 1); - - si.checkdecay(servpingdecay); - } - if(searchlan) - { - ENetAddress address; - address.host = ENET_HOST_BROADCAST; - address.port = server::laninfoport(); - buildping(buf, ping, lanpings); - enet_socket_send(pingsock, &address, &buf, 1); - } - lastinfo = totalmillis; + if(pingsock == ENET_SOCKET_NULL) + { + pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + if(pingsock == ENET_SOCKET_NULL) + { + lastinfo = totalmillis; + return; + } + enet_socket_set_option(pingsock, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option(pingsock, ENET_SOCKOPT_BROADCAST, 1); + + lanpings.setoffset(); + } + + ENetBuffer buf; + uchar ping[MAXTRANS]; + + static int lastping = 0; + if(lastping >= servers.length()) lastping = 0; + loopi(maxservpings ? min(servers.length(), maxservpings) : servers.length()) + { + serverinfo &si = *servers[lastping]; + if(++lastping >= servers.length()) lastping = 0; + if(si.address.host == ENET_HOST_ANY) continue; + buildping(buf, ping, si); + enet_socket_send(pingsock, &si.address, &buf, 1); + + si.checkdecay(servpingdecay); + } + if(searchlan) + { + ENetAddress address; + address.host = ENET_HOST_BROADCAST; + address.port = server::laninfoport(); + buildping(buf, ping, lanpings); + enet_socket_send(pingsock, &address, &buf, 1); + } + lastinfo = totalmillis; } void checkresolver() { - int resolving = 0; - loopv(servers) - { - serverinfo &si = *servers[i]; - if(si.resolved == RESOLVED) continue; - if(si.address.host == ENET_HOST_ANY) - { - if(si.resolved == UNRESOLVED) { si.resolved = RESOLVING; resolverquery(si.name); } - resolving++; - } - } - if(!resolving) return; - - const char *name = NULL; - for(;;) - { - ENetAddress addr = { ENET_HOST_ANY, ENET_PORT_ANY }; - if(!resolvercheck(&name, &addr)) break; - loopv(servers) - { - serverinfo &si = *servers[i]; - if(name == si.name) - { - si.resolved = RESOLVED; - si.address.host = addr.host; - break; - } - } - } + int resolving = 0; + loopv(servers) + { + serverinfo &si = *servers[i]; + if(si.resolved == RESOLVED) continue; + if(si.address.host == ENET_HOST_ANY) + { + if(si.resolved == UNRESOLVED) { si.resolved = RESOLVING; resolverquery(si.name); } + resolving++; + } + } + if(!resolving) return; + + const char *name = NULL; + for(;;) + { + ENetAddress addr = { ENET_HOST_ANY, ENET_PORT_ANY }; + if(!resolvercheck(&name, &addr)) break; + loopv(servers) + { + serverinfo &si = *servers[i]; + if(name == si.name) + { + si.resolved = RESOLVED; + si.address.host = addr.host; + break; + } + } + } } static int lastreset = 0; void checkpings() { - if(pingsock==ENET_SOCKET_NULL) return; - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - ENetBuffer buf; - ENetAddress addr; - uchar ping[MAXTRANS]; - char text[MAXTRANS]; - buf.data = ping; - buf.dataLength = sizeof(ping); - while(enet_socket_wait(pingsock, &events, 0) >= 0 && events) - { - int len = enet_socket_receive(pingsock, &addr, &buf, 1); - if(len <= 0) return; - ucharbuf p(ping, len); - int millis = getint(p); - serverinfo *si = NULL; - loopv(servers) if(addr.host == servers[i]->address.host && addr.port == servers[i]->address.port) { si = servers[i]; break; } - if(si) - { - if(!si->checkattempt(millis)) continue; - millis = si->decodeping(millis); - } - else if(!searchlan || !lanpings.checkattempt(millis, false)) continue; - else - { - si = newserver(NULL, server::serverport(addr.port), addr.host); - millis = lanpings.decodeping(millis); - } - int rtt = clamp(totalmillis - millis, 0, min(servpingdecay, totalmillis)); - if(millis >= lastreset && rtt < servpingdecay) si->addping(rtt, millis); - si->numplayers = getint(p); - int numattr = getint(p); - si->attr.setsize(0); - loopj(numattr) { int attr = getint(p); if(p.overread()) break; si->attr.add(attr); } - getstring(text, p); - filtertext(si->map, text, false); - getstring(text, p); - filtertext(si->sdesc, text, true, true); - } + if(pingsock==ENET_SOCKET_NULL) return; + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + ENetBuffer buf; + ENetAddress addr; + uchar ping[MAXTRANS]; + char text[MAXTRANS]; + buf.data = ping; + buf.dataLength = sizeof(ping); + while(enet_socket_wait(pingsock, &events, 0) >= 0 && events) + { + int len = enet_socket_receive(pingsock, &addr, &buf, 1); + if(len <= 0) return; + ucharbuf p(ping, len); + int millis = getint(p); + serverinfo *si = NULL; + loopv(servers) if(addr.host == servers[i]->address.host && addr.port == servers[i]->address.port) { si = servers[i]; break; } + if(si) + { + if(!si->checkattempt(millis)) continue; + millis = si->decodeping(millis); + } + else if(!searchlan || !lanpings.checkattempt(millis, false)) continue; + else + { + si = newserver(NULL, server::serverport(addr.port), addr.host); + millis = lanpings.decodeping(millis); + } + int rtt = clamp(totalmillis - millis, 0, min(servpingdecay, totalmillis)); + if(millis >= lastreset && rtt < servpingdecay) si->addping(rtt, millis); + si->numplayers = getint(p); + int numattr = getint(p); + si->attr.setsize(0); + loopj(numattr) { int attr = getint(p); if(p.overread()) break; si->attr.add(attr); } + getstring(text, p); + filtertext(si->map, text, false); + getstring(text, p); + filtertext(si->sdesc, text, true, true); + } } void sortservers() { - servers.sort(serverinfo::compare); + servers.sort(serverinfo::compare); } COMMAND(sortservers, ""); @@ -545,173 +545,173 @@ VARP(autoupdateservers, 0, 1, 1); void refreshservers() { - static int lastrefresh = 0; - if(lastrefresh==totalmillis) return; - if(totalmillis - lastrefresh > 1000) - { - loopv(servers) servers[i]->reset(); - lastreset = totalmillis; - } - lastrefresh = totalmillis; - - checkresolver(); - checkpings(); - if(totalmillis - lastinfo >= servpingrate/(maxservpings ? max(1, (servers.length() + maxservpings - 1) / maxservpings) : 1)) pingservers(); - if(autosortservers) sortservers(); + static int lastrefresh = 0; + if(lastrefresh==totalmillis) return; + if(totalmillis - lastrefresh > 1000) + { + loopv(servers) servers[i]->reset(); + lastreset = totalmillis; + } + lastrefresh = totalmillis; + + checkresolver(); + checkpings(); + if(totalmillis - lastinfo >= servpingrate/(maxservpings ? max(1, (servers.length() + maxservpings - 1) / maxservpings) : 1)) pingservers(); + if(autosortservers) sortservers(); } serverinfo *selectedserver = NULL; const char *showservers(g3d_gui *cgui, uint *header, int pagemin, int pagemax) { - refreshservers(); - if(servers.empty()) - { - if(header) execute(header); - return NULL; - } - serverinfo *sc = NULL; - for(int start = 0; start < servers.length();) - { - if(start > 0) cgui->tab(); - if(header) execute(header); - int end = servers.length(); - cgui->pushlist(); - loopi(10) - { - if(!game::serverinfostartcolumn(cgui, i)) break; - for(int j = start; j < end; j++) - { - if(!i && j+1 - start >= pagemin && (j+1 - start >= pagemax || cgui->shouldtab())) { end = j; break; } - serverinfo &si = *servers[j]; - const char *sdesc = si.sdesc; - if(si.address.host == ENET_HOST_ANY) sdesc = "[unknown host]"; - else if(si.ping == serverinfo::WAITING) sdesc = "[waiting for response]"; - if(game::serverinfoentry(cgui, i, si.name, si.port, sdesc, si.map, sdesc == si.sdesc ? si.ping : -1, si.attr, si.numplayers)) - sc = &si; - } - game::serverinfoendcolumn(cgui, i); - } - cgui->poplist(); - start = end; - } - if(selectedserver || !sc) return NULL; - selectedserver = sc; - return "connectselected"; + refreshservers(); + if(servers.empty()) + { + if(header) execute(header); + return NULL; + } + serverinfo *sc = NULL; + for(int start = 0; start < servers.length();) + { + if(start > 0) cgui->tab(); + if(header) execute(header); + int end = servers.length(); + cgui->pushlist(); + loopi(10) + { + if(!game::serverinfostartcolumn(cgui, i)) break; + for(int j = start; j < end; j++) + { + if(!i && j+1 - start >= pagemin && (j+1 - start >= pagemax || cgui->shouldtab())) { end = j; break; } + serverinfo &si = *servers[j]; + const char *sdesc = si.sdesc; + if(si.address.host == ENET_HOST_ANY) sdesc = "[unknown host]"; + else if(si.ping == serverinfo::WAITING) sdesc = "[waiting for response]"; + if(game::serverinfoentry(cgui, i, si.name, si.port, sdesc, si.map, sdesc == si.sdesc ? si.ping : -1, si.attr, si.numplayers)) + sc = &si; + } + game::serverinfoendcolumn(cgui, i); + } + cgui->poplist(); + start = end; + } + if(selectedserver || !sc) return NULL; + selectedserver = sc; + return "connectselected"; } void connectselected() { - if(!selectedserver) return; - connectserv(selectedserver->name, selectedserver->port, selectedserver->password); - selectedserver = NULL; + if(!selectedserver) return; + connectserv(selectedserver->name, selectedserver->port, selectedserver->password); + selectedserver = NULL; } COMMAND(connectselected, ""); void clearservers(bool full = false) { - resolverclear(); - if(full) servers.deletecontents(); - else loopvrev(servers) if(!servers[i]->keep) delete servers.remove(i); - selectedserver = NULL; + resolverclear(); + if(full) servers.deletecontents(); + else loopvrev(servers) if(!servers[i]->keep) delete servers.remove(i); + selectedserver = NULL; } #define RETRIEVELIMIT 20000 void retrieveservers(vector &data) { - ENetSocket sock = connectmaster(true); - if(sock == ENET_SOCKET_NULL) return; - - extern char *mastername; - defformatstring(text, "retrieving servers from %s... (esc to abort)", mastername); - renderprogress(0, text); - - int starttime = SDL_GetTicks(), timeout = 0; - const char *req = "list\n"; - int reqlen = strlen(req); - ENetBuffer buf; - while(reqlen > 0) - { - enet_uint32 events = ENET_SOCKET_WAIT_SEND; - if(enet_socket_wait(sock, &events, 250) >= 0 && events) - { - buf.data = (void *)req; - buf.dataLength = reqlen; - int sent = enet_socket_send(sock, NULL, &buf, 1); - if(sent < 0) break; - req += sent; - reqlen -= sent; - if(reqlen <= 0) break; - } - timeout = SDL_GetTicks() - starttime; - renderprogress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text); - if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1; - if(timeout > RETRIEVELIMIT) break; - } - - if(reqlen <= 0) for(;;) - { - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - if(enet_socket_wait(sock, &events, 250) >= 0 && events) - { - if(data.length() >= data.capacity()) data.reserve(4096); - buf.data = data.getbuf() + data.length(); - buf.dataLength = data.capacity() - data.length(); - int recv = enet_socket_receive(sock, NULL, &buf, 1); - if(recv <= 0) break; - data.advance(recv); - } - timeout = SDL_GetTicks() - starttime; - renderprogress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text); - if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1; - if(timeout > RETRIEVELIMIT) break; - } - - if(data.length()) data.add('\0'); - enet_socket_destroy(sock); + ENetSocket sock = connectmaster(true); + if(sock == ENET_SOCKET_NULL) return; + + extern char *mastername; + defformatstring(text, "retrieving servers from %s... (esc to abort)", mastername); + renderprogress(0, text); + + int starttime = SDL_GetTicks(), timeout = 0; + const char *req = "list\n"; + int reqlen = strlen(req); + ENetBuffer buf; + while(reqlen > 0) + { + enet_uint32 events = ENET_SOCKET_WAIT_SEND; + if(enet_socket_wait(sock, &events, 250) >= 0 && events) + { + buf.data = (void *)req; + buf.dataLength = reqlen; + int sent = enet_socket_send(sock, NULL, &buf, 1); + if(sent < 0) break; + req += sent; + reqlen -= sent; + if(reqlen <= 0) break; + } + timeout = SDL_GetTicks() - starttime; + renderprogress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text); + if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1; + if(timeout > RETRIEVELIMIT) break; + } + + if(reqlen <= 0) for(;;) + { + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + if(enet_socket_wait(sock, &events, 250) >= 0 && events) + { + if(data.length() >= data.capacity()) data.reserve(4096); + buf.data = data.getbuf() + data.length(); + buf.dataLength = data.capacity() - data.length(); + int recv = enet_socket_receive(sock, NULL, &buf, 1); + if(recv <= 0) break; + data.advance(recv); + } + timeout = SDL_GetTicks() - starttime; + renderprogress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text); + if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1; + if(timeout > RETRIEVELIMIT) break; + } + + if(data.length()) data.add('\0'); + enet_socket_destroy(sock); } bool updatedservers = false; void updatefrommaster() { - vector data; - retrieveservers(data); - if(data.empty()) conoutf(CON_ERROR, "master server not replying"); - else - { - clearservers(); - char *line = data.getbuf(); - while(char *end = (char *)memchr(line, '\n', data.length() - (line - data.getbuf()))) - { - *end = '\0'; - - const char *args = line; - while(args < end && !iscubespace(*args)) args++; - int cmdlen = args - line; - while(args < end && iscubespace(*args)) args++; - - if(matchstring(line, cmdlen, "addserver")) - { - string ip; - int port; - if(sscanf(args, "%100s %d", ip, &port) == 2) addserver(ip, port); - } - else if(matchstring(line, cmdlen, "echo")) conoutf("\f1%s", args); - - line = end + 1; - } - } - refreshservers(); - updatedservers = true; + vector data; + retrieveservers(data); + if(data.empty()) conoutf(CON_ERROR, "master server not replying"); + else + { + clearservers(); + char *line = data.getbuf(); + while(char *end = (char *)memchr(line, '\n', data.length() - (line - data.getbuf()))) + { + *end = '\0'; + + const char *args = line; + while(args < end && !iscubespace(*args)) args++; + int cmdlen = args - line; + while(args < end && iscubespace(*args)) args++; + + if(matchstring(line, cmdlen, "addserver")) + { + string ip; + int port; + if(sscanf(args, "%100s %d", ip, &port) == 2) addserver(ip, port); + } + else if(matchstring(line, cmdlen, "echo")) conoutf("\f1%s", args); + + line = end + 1; + } + } + refreshservers(); + updatedservers = true; } void initservers() { - selectedserver = NULL; - if(autoupdateservers && !updatedservers) updatefrommaster(); + selectedserver = NULL; + if(autoupdateservers && !updatedservers) updatefrommaster(); } ICOMMAND(addserver, "sis", (const char *name, int *port, const char *password), addserver(name, *port, password[0] ? password : NULL)); @@ -722,32 +722,32 @@ COMMAND(initservers, ""); void writeservercfg() { - if(!game::savedservers()) return; - stream *f = openutf8file(path(game::savedservers(), true), "w"); - if(!f) return; - int kept = 0; - loopv(servers) - { - serverinfo *s = servers[i]; - if(s->keep) - { - if(!kept) f->printf("// servers that should never be cleared from the server list\n\n"); - if(s->password) f->printf("keepserver %s %d %s\n", escapeid(s->name), s->port, escapestring(s->password)); - else f->printf("keepserver %s %d\n", escapeid(s->name), s->port); - kept++; - } - } - if(kept) f->printf("\n"); - f->printf("// servers connected to are added here automatically\n\n"); - loopv(servers) - { - serverinfo *s = servers[i]; - if(!s->keep) - { - if(s->password) f->printf("addserver %s %d %s\n", escapeid(s->name), s->port, escapestring(s->password)); - else f->printf("addserver %s %d\n", escapeid(s->name), s->port); - } - } - delete f; + if(!game::savedservers()) return; + stream *f = openutf8file(path(game::savedservers(), true), "w"); + if(!f) return; + int kept = 0; + loopv(servers) + { + serverinfo *s = servers[i]; + if(s->keep) + { + if(!kept) f->printf("// servers that should never be cleared from the server list\n\n"); + if(s->password) f->printf("keepserver %s %d %s\n", escapeid(s->name), s->port, escapestring(s->password)); + else f->printf("keepserver %s %d\n", escapeid(s->name), s->port); + kept++; + } + } + if(kept) f->printf("\n"); + f->printf("// servers connected to are added here automatically\n\n"); + loopv(servers) + { + serverinfo *s = servers[i]; + if(!s->keep) + { + if(s->password) f->printf("addserver %s %d %s\n", escapeid(s->name), s->port, escapestring(s->password)); + else f->printf("addserver %s %d\n", escapeid(s->name), s->port); + } + } + delete f; } diff --git a/src/engine/shader.cpp b/src/engine/shader.cpp index da17d4f..f7a740e 100644 --- a/src/engine/shader.cpp +++ b/src/engine/shader.cpp @@ -17,1206 +17,1201 @@ VAR(reservevpparams, 1, 16, 0); VAR(maxvsuniforms, 1, 0, 0); VAR(maxfsuniforms, 1, 0, 0); VAR(maxvaryings, 1, 0, 0); -VAR(dbgshader, 0, 0, 2); void loadshaders() { - standardshaders = true; - execfile("data/glsl.cfg"); - standardshaders = false; + standardshaders = true; + execfile("data/glsl.cfg"); + standardshaders = false; - nullshader = lookupshaderbyname("null"); - hudshader = lookupshaderbyname("hud"); - hudnotextureshader = lookupshaderbyname("hudnotexture"); - stdworldshader = lookupshaderbyname("stdworld"); - if(!nullshader || !hudshader || !hudnotextureshader || !stdworldshader) fatal("cannot find shader definitions"); + nullshader = lookupshaderbyname("null"); + hudshader = lookupshaderbyname("hud"); + hudnotextureshader = lookupshaderbyname("hudnotexture"); + stdworldshader = lookupshaderbyname("stdworld"); + if(!nullshader || !hudshader || !hudnotextureshader || !stdworldshader) fatal("cannot find shader definitions"); - dummyslot.shader = stdworldshader; + dummyslot.shader = stdworldshader; - textureshader = lookupshaderbyname("texture"); - notextureshader = lookupshaderbyname("notexture"); - nocolorshader = lookupshaderbyname("nocolor"); - foggedshader = lookupshaderbyname("fogged"); - foggednotextureshader = lookupshaderbyname("foggednotexture"); - - nullshader->set(); + textureshader = lookupshaderbyname("texture"); + notextureshader = lookupshaderbyname("notexture"); + nocolorshader = lookupshaderbyname("nocolor"); + foggedshader = lookupshaderbyname("fogged"); + foggednotextureshader = lookupshaderbyname("foggednotexture"); - loadedshaders = true; + nullshader->set(); + + loadedshaders = true; } -Shader *lookupshaderbyname(const char *name) -{ - Shader *s = shaders.access(name); - return s && s->loaded() ? s : NULL; +Shader *lookupshaderbyname(const char *name) +{ + Shader *s = shaders.access(name); + return s && s->loaded() ? s : NULL; } Shader *generateshader(const char *name, const char *fmt, ...) { - if(!loadedshaders) return NULL; - Shader *s = name ? lookupshaderbyname(name) : NULL; - if(!s) - { - defvformatstring(cmd, fmt, fmt); - bool wasstandard = standardshaders; - standardshaders = true; - execute(cmd); - standardshaders = wasstandard; - s = name ? lookupshaderbyname(name) : NULL; - if(!s) s = nullshader; - } - return s; + if(!loadedshaders) return NULL; + Shader *s = name ? lookupshaderbyname(name) : NULL; + if(!s) + { + defvformatstring(cmd, fmt, fmt); + bool wasstandard = standardshaders; + standardshaders = true; + execute(cmd); + standardshaders = wasstandard; + s = name ? lookupshaderbyname(name) : NULL; + if(!s) s = nullshader; + } + return s; } static void showglslinfo(GLenum type, GLuint obj, const char *name, const char **parts = NULL, int numparts = 0) { - GLint length = 0; - if(type) glGetShaderiv_(obj, GL_INFO_LOG_LENGTH, &length); - else glGetProgramiv_(obj, GL_INFO_LOG_LENGTH, &length); - if(length > 1) - { - conoutf(CON_ERROR, "GLSL ERROR (%s:%s)", type == GL_VERTEX_SHADER ? "VS" : (type == GL_FRAGMENT_SHADER ? "FS" : "PROG"), name); - FILE *l = getlogfile(); - if(l) - { - GLchar *log = new GLchar[length]; - if(type) glGetShaderInfoLog_(obj, length, &length, log); - else glGetProgramInfoLog_(obj, length, &length, log); - fprintf(l, "%s\n", log); - bool partlines = log[0] != '0'; - int line = 0; - loopi(numparts) - { - const char *part = parts[i]; - int startline = line; - while(*part) - { - const char *next = strchr(part, '\n'); - if(++line > 1000) goto done; - if(partlines) fprintf(l, "%d(%d): ", i, line - startline); else fprintf(l, "%d: ", line); - fwrite(part, 1, next ? next - part + 1 : strlen(part), l); - if(!next) { fputc('\n', l); break; } - part = next + 1; - } - } - done: - delete[] log; - } - } + GLint length = 0; + if(type) glGetShaderiv_(obj, GL_INFO_LOG_LENGTH, &length); + else glGetProgramiv_(obj, GL_INFO_LOG_LENGTH, &length); + if(length > 1) + { + conoutf(CON_ERROR, "GLSL ERROR (%s:%s)", type == GL_VERTEX_SHADER ? "VS" : (type == GL_FRAGMENT_SHADER ? "FS" : "PROG"), name); + FILE *l = getlogfile(); + if(l) + { + GLchar *log = new GLchar[length]; + if(type) glGetShaderInfoLog_(obj, length, &length, log); + else glGetProgramInfoLog_(obj, length, &length, log); + fprintf(l, "%s\n", log); + bool partlines = log[0] != '0'; + int line = 0; + loopi(numparts) + { + const char *part = parts[i]; + int startline = line; + while(*part) + { + const char *next = strchr(part, '\n'); + if(++line > 1000) goto done; + if(partlines) fprintf(l, "%d(%d): ", i, line - startline); else fprintf(l, "%d: ", line); + fwrite(part, 1, next ? next - part + 1 : strlen(part), l); + if(!next) { fputc('\n', l); break; } + part = next + 1; + } + } + done: + delete[] log; + } + } } -static void compileglslshader(GLenum type, GLuint &obj, const char *def, const char *name, bool msg = true) -{ - const char *source = def + strspn(def, " \t\r\n"); - const char *parts[16]; - int numparts = 0; - static const struct { int version; const char * const header; } glslversions[] = - { - { 330, "#version 330\n" }, - { 150, "#version 150\n" }, - { 130, "#version 130\n" }, - { 120, "#version 120\n" } - }; - loopi(sizeof(glslversions)/sizeof(glslversions[0])) if(glslversion >= glslversions[i].version) - { - parts[numparts++] = glslversions[i].header; - break; - } - if(glslversion >= 130) - { - if(type == GL_VERTEX_SHADER) parts[numparts++] = - "#define attribute in\n" - "#define varying out\n"; - else if(type == GL_FRAGMENT_SHADER) - { - parts[numparts++] = "#define varying in\n"; - if(glslversion < 150) parts[numparts++] = "precision highp float;\n"; - if(glversion >= 300) parts[numparts++] = - "out vec4 cube2_FragColor;\n" - "#define gl_FragColor cube2_FragColor\n"; - } - parts[numparts++] = - "#define texture2D(sampler, coords) texture(sampler, coords)\n" - "#define texture2DProj(sampler, coords) textureProj(sampler, coords)\n" - "#define textureCube(sampler, coords) texture(sampler, coords)\n"; - } - parts[numparts++] = source; - - obj = glCreateShader_(type); - glShaderSource_(obj, numparts, (const GLchar **)parts, NULL); - glCompileShader_(obj); - GLint success; - glGetShaderiv_(obj, GL_COMPILE_STATUS, &success); - if(!success) - { - if(msg) showglslinfo(type, obj, name, parts, numparts); - glDeleteShader_(obj); - obj = 0; - } - else if(dbgshader > 1 && msg) showglslinfo(type, obj, name, parts, numparts); -} - -VAR(dbgubo, 0, 0, 1); +static void compileglslshader(GLenum type, GLuint &obj, const char *def, const char *name, bool msg = true) +{ + const char *source = def + strspn(def, " \t\r\n"); + const char *parts[16]; + int numparts = 0; + static const struct { int version; const char * const header; } glslversions[] = + { + { 330, "#version 330\n" }, + { 150, "#version 150\n" }, + { 130, "#version 130\n" }, + { 120, "#version 120\n" } + }; + loopi(sizeof(glslversions)/sizeof(glslversions[0])) if(glslversion >= glslversions[i].version) + { + parts[numparts++] = glslversions[i].header; + break; + } + if(glslversion >= 130) + { + if(type == GL_VERTEX_SHADER) parts[numparts++] = + "#define attribute in\n" + "#define varying out\n"; + else if(type == GL_FRAGMENT_SHADER) + { + parts[numparts++] = "#define varying in\n"; + if(glslversion < 150) parts[numparts++] = "precision highp float;\n"; + if(glversion >= 300) parts[numparts++] = + "out vec4 cube2_FragColor;\n" + "#define gl_FragColor cube2_FragColor\n"; + } + parts[numparts++] = + "#define texture2D(sampler, coords) texture(sampler, coords)\n" + "#define texture2DProj(sampler, coords) textureProj(sampler, coords)\n" + "#define textureCube(sampler, coords) texture(sampler, coords)\n"; + } + parts[numparts++] = source; + + obj = glCreateShader_(type); + glShaderSource_(obj, numparts, (const GLchar **)parts, NULL); + glCompileShader_(obj); + GLint success; + glGetShaderiv_(obj, GL_COMPILE_STATUS, &success); + if(!success) + { + if(msg) showglslinfo(type, obj, name, parts, numparts); + glDeleteShader_(obj); + obj = 0; + } +} static void bindglsluniform(Shader &s, UniformLoc &u) { - u.loc = glGetUniformLocation_(s.program, u.name); - if(!u.blockname || !hasUBO) return; - GLuint bidx = glGetUniformBlockIndex_(s.program, u.blockname); - GLuint uidx = GL_INVALID_INDEX; - glGetUniformIndices_(s.program, 1, &u.name, &uidx); - if(bidx != GL_INVALID_INDEX && uidx != GL_INVALID_INDEX) - { - GLint sizeval = 0, offsetval = 0, strideval = 0; - glGetActiveUniformBlockiv_(s.program, bidx, GL_UNIFORM_BLOCK_DATA_SIZE, &sizeval); - if(sizeval <= 0) return; - glGetActiveUniformsiv_(s.program, 1, &uidx, GL_UNIFORM_OFFSET, &offsetval); - if(u.stride > 0) - { - glGetActiveUniformsiv_(s.program, 1, &uidx, GL_UNIFORM_ARRAY_STRIDE, &strideval); - if(strideval > u.stride) return; - } - u.offset = offsetval; - u.size = sizeval; - glUniformBlockBinding_(s.program, bidx, u.binding); - if(dbgubo) conoutf(CON_DEBUG, "UBO: %s:%s:%d, offset: %d, size: %d, stride: %d", u.name, u.blockname, u.binding, offsetval, sizeval, strideval); - } + u.loc = glGetUniformLocation_(s.program, u.name); + if(!u.blockname || !hasUBO) return; + GLuint bidx = glGetUniformBlockIndex_(s.program, u.blockname); + GLuint uidx = GL_INVALID_INDEX; + glGetUniformIndices_(s.program, 1, &u.name, &uidx); + if(bidx != GL_INVALID_INDEX && uidx != GL_INVALID_INDEX) + { + GLint sizeval = 0, offsetval = 0, strideval = 0; + glGetActiveUniformBlockiv_(s.program, bidx, GL_UNIFORM_BLOCK_DATA_SIZE, &sizeval); + if(sizeval <= 0) return; + glGetActiveUniformsiv_(s.program, 1, &uidx, GL_UNIFORM_OFFSET, &offsetval); + if(u.stride > 0) + { + glGetActiveUniformsiv_(s.program, 1, &uidx, GL_UNIFORM_ARRAY_STRIDE, &strideval); + if(strideval > u.stride) return; + } + u.offset = offsetval; + u.size = sizeval; + glUniformBlockBinding_(s.program, bidx, u.binding); + } } static void linkglslprogram(Shader &s, bool msg = true) { - s.program = s.vsobj && s.psobj ? glCreateProgram_() : 0; - GLint success = 0; - if(s.program) - { - glAttachShader_(s.program, s.vsobj); - glAttachShader_(s.program, s.psobj); - uint attribs = 0; - loopv(s.attriblocs) - { - AttribLoc &a = s.attriblocs[i]; - glBindAttribLocation_(s.program, a.loc, a.name); - attribs |= 1<= 300) - { - glBindFragDataLocation_(s.program, 0, "cube2_FragColor"); - } - glLinkProgram_(s.program); - glGetProgramiv_(s.program, GL_LINK_STATUS, &success); - } - if(success) - { - glUseProgram_(s.program); - loopi(8) - { - static const char * const texnames[8] = { "tex0", "tex1", "tex2", "tex3", "tex4", "tex5", "tex6", "tex7" }; - GLint loc = glGetUniformLocation_(s.program, texnames[i]); - if(loc != -1) glUniform1i_(loc, i); - } - loopv(s.defaultparams) - { - SlotShaderParamState ¶m = s.defaultparams[i]; - param.loc = glGetUniformLocation_(s.program, param.name); - } - loopv(s.uniformlocs) bindglsluniform(s, s.uniformlocs[i]); - glUseProgram_(0); - } - else if(s.program) - { - if(msg) showglslinfo(GL_FALSE, s.program, s.name); - glDeleteProgram_(s.program); - s.program = 0; - } + s.program = s.vsobj && s.psobj ? glCreateProgram_() : 0; + GLint success = 0; + if(s.program) + { + glAttachShader_(s.program, s.vsobj); + glAttachShader_(s.program, s.psobj); + uint attribs = 0; + loopv(s.attriblocs) + { + AttribLoc &a = s.attriblocs[i]; + glBindAttribLocation_(s.program, a.loc, a.name); + attribs |= 1<= 300) + { + glBindFragDataLocation_(s.program, 0, "cube2_FragColor"); + } + glLinkProgram_(s.program); + glGetProgramiv_(s.program, GL_LINK_STATUS, &success); + } + if(success) + { + glUseProgram_(s.program); + loopi(8) + { + static const char * const texnames[8] = { "tex0", "tex1", "tex2", "tex3", "tex4", "tex5", "tex6", "tex7" }; + GLint loc = glGetUniformLocation_(s.program, texnames[i]); + if(loc != -1) glUniform1i_(loc, i); + } + loopv(s.defaultparams) + { + SlotShaderParamState ¶m = s.defaultparams[i]; + param.loc = glGetUniformLocation_(s.program, param.name); + } + loopv(s.uniformlocs) bindglsluniform(s, s.uniformlocs[i]); + glUseProgram_(0); + } + else if(s.program) + { + if(msg) showglslinfo(GL_FALSE, s.program, s.name); + glDeleteProgram_(s.program); + s.program = 0; + } } int getlocalparam(const char *name) { - return localparams.access(name, int(localparams.numelems)); + return localparams.access(name, int(localparams.numelems)); } static int addlocalparam(Shader &s, const char *name, int loc, int size, GLenum format) { - int idx = getlocalparam(name); - if(idx >= s.localparamremap.length()) - { - int n = idx + 1 - s.localparamremap.length(); - memset(s.localparamremap.pad(n), 0xFF, n); - } - s.localparamremap[idx] = s.localparams.length(); - - LocalShaderParamState &l = s.localparams.add(); - l.name = name; - l.loc = loc; - l.size = size; - l.format = format; - return idx; + int idx = getlocalparam(name); + if(idx >= s.localparamremap.length()) + { + int n = idx + 1 - s.localparamremap.length(); + memset(s.localparamremap.pad(n), 0xFF, n); + } + s.localparamremap[idx] = s.localparams.length(); + + LocalShaderParamState &l = s.localparams.add(); + l.name = name; + l.loc = loc; + l.size = size; + l.format = format; + return idx; } GlobalShaderParamState *getglobalparam(const char *name) { - GlobalShaderParamState *param = globalparams.access(name); - if(!param) - { - param = &globalparams[name]; - param->name = name; - memset(param->buf, -1, sizeof(param->buf)); - param->version = -1; - } - return param; + GlobalShaderParamState *param = globalparams.access(name); + if(!param) + { + param = &globalparams[name]; + param->name = name; + memset(param->buf, -1, sizeof(param->buf)); + param->version = -1; + } + return param; } static GlobalShaderParamUse *addglobalparam(Shader &s, GlobalShaderParamState *param, int loc, int size, GLenum format) { - GlobalShaderParamUse &g = s.globalparams.add(); - g.param = param; - g.version = -2; - g.loc = loc; - g.size = size; - g.format = format; - return &g; + GlobalShaderParamUse &g = s.globalparams.add(); + g.param = param; + g.version = -2; + g.loc = loc; + g.size = size; + g.format = format; + return &g; } static void setglsluniformformat(Shader &s, const char *name, GLenum format, int size) { - switch(format) - { - case GL_FLOAT: - case GL_FLOAT_VEC2: - case GL_FLOAT_VEC3: - case GL_FLOAT_VEC4: - case GL_INT: - case GL_INT_VEC2: - case GL_INT_VEC3: - case GL_INT_VEC4: - case GL_BOOL: - case GL_BOOL_VEC2: - case GL_BOOL_VEC3: - case GL_BOOL_VEC4: - case GL_FLOAT_MAT2: - case GL_FLOAT_MAT3: - case GL_FLOAT_MAT4: - break; - default: - return; - } - if(!strncmp(name, "gl_", 3)) return; - - int loc = glGetUniformLocation_(s.program, name); - if(loc < 0) return; - loopvj(s.defaultparams) if(s.defaultparams[j].loc == loc) - { - s.defaultparams[j].format = format; - return; - } - loopvj(s.uniformlocs) if(s.uniformlocs[j].loc == loc) return; - loopvj(s.globalparams) if(s.globalparams[j].loc == loc) return; - loopvj(s.localparams) if(s.localparams[j].loc == loc) return; - - name = getshaderparamname(name); - GlobalShaderParamState *param = globalparams.access(name); - if(param) addglobalparam(s, param, loc, size, format); - else addlocalparam(s, name, loc, size, format); + switch(format) + { + case GL_FLOAT: + case GL_FLOAT_VEC2: + case GL_FLOAT_VEC3: + case GL_FLOAT_VEC4: + case GL_INT: + case GL_INT_VEC2: + case GL_INT_VEC3: + case GL_INT_VEC4: + case GL_BOOL: + case GL_BOOL_VEC2: + case GL_BOOL_VEC3: + case GL_BOOL_VEC4: + case GL_FLOAT_MAT2: + case GL_FLOAT_MAT3: + case GL_FLOAT_MAT4: + break; + default: + return; + } + if(!strncmp(name, "gl_", 3)) return; + + int loc = glGetUniformLocation_(s.program, name); + if(loc < 0) return; + loopvj(s.defaultparams) if(s.defaultparams[j].loc == loc) + { + s.defaultparams[j].format = format; + return; + } + loopvj(s.uniformlocs) if(s.uniformlocs[j].loc == loc) return; + loopvj(s.globalparams) if(s.globalparams[j].loc == loc) return; + loopvj(s.localparams) if(s.localparams[j].loc == loc) return; + + name = getshaderparamname(name); + GlobalShaderParamState *param = globalparams.access(name); + if(param) addglobalparam(s, param, loc, size, format); + else addlocalparam(s, name, loc, size, format); } static void allocglslactiveuniforms(Shader &s) { - GLint numactive = 0; - glGetProgramiv_(s.program, GL_ACTIVE_UNIFORMS, &numactive); - string name; - loopi(numactive) - { - GLsizei namelen = 0; - GLint size = 0; - GLenum format = GL_FLOAT_VEC4; - name[0] = '\0'; - glGetActiveUniform_(s.program, i, sizeof(name)-1, &namelen, &size, &format, name); - if(namelen <= 0 || size <= 0) continue; - name[clamp(int(namelen), 0, (int)sizeof(name)-2)] = '\0'; - char *brak = strchr(name, '['); - if(brak) *brak = '\0'; - setglsluniformformat(s, name, format, size); - } + GLint numactive = 0; + glGetProgramiv_(s.program, GL_ACTIVE_UNIFORMS, &numactive); + string name; + loopi(numactive) + { + GLsizei namelen = 0; + GLint size = 0; + GLenum format = GL_FLOAT_VEC4; + name[0] = '\0'; + glGetActiveUniform_(s.program, i, sizeof(name)-1, &namelen, &size, &format, name); + if(namelen <= 0 || size <= 0) continue; + name[clamp(int(namelen), 0, (int)sizeof(name)-2)] = '\0'; + char *brak = strchr(name, '['); + if(brak) *brak = '\0'; + setglsluniformformat(s, name, format, size); + } } void Shader::allocparams(Slot *slot) { - if(slot) - { + if(slot) + { #define UNIFORMTEX(name, tmu) \ - { \ - loc = glGetUniformLocation_(program, name); \ - int val = tmu; \ - if(loc != -1) glUniform1i_(loc, val); \ - } - int loc, tmu = 2; - if(type & SHADER_NORMALSLMS) - { - UNIFORMTEX("lmcolor", 1); - UNIFORMTEX("lmdir", 2); - tmu++; - } - else UNIFORMTEX("lightmap", 1); - if(type & SHADER_ENVMAP) UNIFORMTEX("envmap", tmu++); - UNIFORMTEX("shadowmap", 7); - int stex = 0; - loopv(slot->sts) - { - Slot::Tex &t = slot->sts[i]; - switch(t.type) - { - case TEX_DIFFUSE: UNIFORMTEX("diffusemap", 0); break; - case TEX_NORMAL: UNIFORMTEX("normalmap", tmu++); break; - case TEX_GLOW: UNIFORMTEX("glowmap", tmu++); break; - case TEX_DECAL: UNIFORMTEX("decal", tmu++); break; - case TEX_SPEC: if(t.combined<0) UNIFORMTEX("specmap", tmu++); break; - case TEX_DEPTH: if(t.combined<0) UNIFORMTEX("depthmap", tmu++); break; - case TEX_ALPHA: if(t.combined<0) UNIFORMTEX("alphamap", tmu++); break; - case TEX_UNKNOWN: - { - defformatstring(sname, "stex%d", stex++); - UNIFORMTEX(sname, tmu++); - break; - } - } - } - } - allocglslactiveuniforms(*this); + { \ + loc = glGetUniformLocation_(program, name); \ + int val = tmu; \ + if(loc != -1) glUniform1i_(loc, val); \ + } + int loc, tmu = 2; + if(type & SHADER_NORMALSLMS) + { + UNIFORMTEX("lmcolor", 1); + UNIFORMTEX("lmdir", 2); + tmu++; + } + else UNIFORMTEX("lightmap", 1); + if(type & SHADER_ENVMAP) UNIFORMTEX("envmap", tmu++); + UNIFORMTEX("shadowmap", 7); + int stex = 0; + loopv(slot->sts) + { + Slot::Tex &t = slot->sts[i]; + switch(t.type) + { + case TEX_DIFFUSE: UNIFORMTEX("diffusemap", 0); break; + case TEX_NORMAL: UNIFORMTEX("normalmap", tmu++); break; + case TEX_GLOW: UNIFORMTEX("glowmap", tmu++); break; + case TEX_DECAL: UNIFORMTEX("decal", tmu++); break; + case TEX_SPEC: if(t.combined<0) UNIFORMTEX("specmap", tmu++); break; + case TEX_DEPTH: if(t.combined<0) UNIFORMTEX("depthmap", tmu++); break; + case TEX_ALPHA: if(t.combined<0) UNIFORMTEX("alphamap", tmu++); break; + case TEX_UNKNOWN: + { + defformatstring(sname, "stex%d", stex++); + UNIFORMTEX(sname, tmu++); + break; + } + } + } + } + allocglslactiveuniforms(*this); } int GlobalShaderParamState::nextversion = 0; void GlobalShaderParamState::resetversions() { - enumerate(shaders, Shader, s, - { - loopv(s.globalparams) - { - GlobalShaderParamUse &u = s.globalparams[i]; - if(u.version != u.param->version) u.version = -2; - } - }); - nextversion = 0; - enumerate(globalparams, GlobalShaderParamState, g, { g.version = ++nextversion; }); - enumerate(shaders, Shader, s, - { - loopv(s.globalparams) - { - GlobalShaderParamUse &u = s.globalparams[i]; - if(u.version >= 0) u.version = u.param->version; - } - }); + enumerate(shaders, Shader, s, + { + loopv(s.globalparams) + { + GlobalShaderParamUse &u = s.globalparams[i]; + if(u.version != u.param->version) u.version = -2; + } + }); + nextversion = 0; + enumerate(globalparams, GlobalShaderParamState, g, { g.version = ++nextversion; }); + enumerate(shaders, Shader, s, + { + loopv(s.globalparams) + { + GlobalShaderParamUse &u = s.globalparams[i]; + if(u.version >= 0) u.version = u.param->version; + } + }); } static float *findslotparam(Slot &s, const char *name, float *noval = NULL) { - loopv(s.params) - { - SlotShaderParam ¶m = s.params[i]; - if(name == param.name) return param.val; - } - loopv(s.shader->defaultparams) - { - SlotShaderParamState ¶m = s.shader->defaultparams[i]; - if(name == param.name) return param.val; - } - return noval; + loopv(s.params) + { + SlotShaderParam ¶m = s.params[i]; + if(name == param.name) return param.val; + } + loopv(s.shader->defaultparams) + { + SlotShaderParamState ¶m = s.shader->defaultparams[i]; + if(name == param.name) return param.val; + } + return noval; } static float *findslotparam(VSlot &s, const char *name, float *noval = NULL) { - loopv(s.params) - { - SlotShaderParam ¶m = s.params[i]; - if(name == param.name) return param.val; - } - return findslotparam(*s.slot, name, noval); + loopv(s.params) + { + SlotShaderParam ¶m = s.params[i]; + if(name == param.name) return param.val; + } + return findslotparam(*s.slot, name, noval); } static inline void setslotparam(SlotShaderParamState &l, const float *val) { - switch(l.format) - { - case GL_BOOL: - case GL_FLOAT: glUniform1fv_(l.loc, 1, val); break; - case GL_BOOL_VEC2: - case GL_FLOAT_VEC2: glUniform2fv_(l.loc, 1, val); break; - case GL_BOOL_VEC3: - case GL_FLOAT_VEC3: glUniform3fv_(l.loc, 1, val); break; - case GL_BOOL_VEC4: - case GL_FLOAT_VEC4: glUniform4fv_(l.loc, 1, val); break; - case GL_INT: glUniform1i_(l.loc, int(val[0])); break; - case GL_INT_VEC2: glUniform2i_(l.loc, int(val[0]), int(val[1])); break; - case GL_INT_VEC3: glUniform3i_(l.loc, int(val[0]), int(val[1]), int(val[2])); break; - case GL_INT_VEC4: glUniform4i_(l.loc, int(val[0]), int(val[1]), int(val[2]), int(val[3])); break; - } + switch(l.format) + { + case GL_BOOL: + case GL_FLOAT: glUniform1fv_(l.loc, 1, val); break; + case GL_BOOL_VEC2: + case GL_FLOAT_VEC2: glUniform2fv_(l.loc, 1, val); break; + case GL_BOOL_VEC3: + case GL_FLOAT_VEC3: glUniform3fv_(l.loc, 1, val); break; + case GL_BOOL_VEC4: + case GL_FLOAT_VEC4: glUniform4fv_(l.loc, 1, val); break; + case GL_INT: glUniform1i_(l.loc, int(val[0])); break; + case GL_INT_VEC2: glUniform2i_(l.loc, int(val[0]), int(val[1])); break; + case GL_INT_VEC3: glUniform3i_(l.loc, int(val[0]), int(val[1]), int(val[2])); break; + case GL_INT_VEC4: glUniform4i_(l.loc, int(val[0]), int(val[1]), int(val[2]), int(val[3])); break; + } } #define SETSLOTPARAM(l, mask, i, val) do { \ - if(!(mask&(1<invalid() ? 0 : reusevs->vsobj; - else compileglslshader(GL_VERTEX_SHADER, vsobj, vsstr, name, dbgshader || !variantshader); - if(!psstr) psobj = !reuseps || reuseps->invalid() ? 0 : reuseps->psobj; - else compileglslshader(GL_FRAGMENT_SHADER, psobj, psstr, name, dbgshader || !variantshader); - linkglslprogram(*this, !variantshader); - return program!=0; + if(!vsstr) vsobj = !reusevs || reusevs->invalid() ? 0 : reusevs->vsobj; + else compileglslshader(GL_VERTEX_SHADER, vsobj, vsstr, name, !variantshader); + if(!psstr) psobj = !reuseps || reuseps->invalid() ? 0 : reuseps->psobj; + else compileglslshader(GL_FRAGMENT_SHADER, psobj, psstr, name, !variantshader); + linkglslprogram(*this, !variantshader); + return program!=0; } void Shader::cleanup(bool invalid) { - detailshader = NULL; - used = false; - if(vsobj) { if(!reusevs) glDeleteShader_(vsobj); vsobj = 0; } - if(psobj) { if(!reuseps) glDeleteShader_(psobj); psobj = 0; } - if(program) { glDeleteProgram_(program); program = 0; } - localparams.setsize(0); - localparamremap.setsize(0); - globalparams.setsize(0); - if(standard || invalid) - { - type = SHADER_INVALID; - DELETEA(vsstr); - DELETEA(psstr); - DELETEA(defer); - variants.setsize(0); - DELETEA(variantrows); - defaultparams.setsize(0); - attriblocs.setsize(0); - uniformlocs.setsize(0); - altshader = NULL; - loopi(MAXSHADERDETAIL) fastshader[i] = this; - reusevs = reuseps = NULL; - } - else loopv(defaultparams) defaultparams[i].loc = -1; - owner = NULL; + detailshader = NULL; + used = false; + if(vsobj) { if(!reusevs) glDeleteShader_(vsobj); vsobj = 0; } + if(psobj) { if(!reuseps) glDeleteShader_(psobj); psobj = 0; } + if(program) { glDeleteProgram_(program); program = 0; } + localparams.setsize(0); + localparamremap.setsize(0); + globalparams.setsize(0); + if(standard || invalid) + { + type = SHADER_INVALID; + DELETEA(vsstr); + DELETEA(psstr); + DELETEA(defer); + variants.setsize(0); + DELETEA(variantrows); + defaultparams.setsize(0); + attriblocs.setsize(0); + uniformlocs.setsize(0); + altshader = NULL; + loopi(MAXSHADERDETAIL) fastshader[i] = this; + reusevs = reuseps = NULL; + } + else loopv(defaultparams) defaultparams[i].loc = -1; + owner = NULL; } bool Shader::isnull(const Shader *s) { return !s; } static void genattriblocs(Shader &s, const char *vs, const char *ps, Shader *reusevs, Shader *reuseps) { - static int len = strlen("//:attrib"); - string name; - int loc; - if(reusevs) s.attriblocs = reusevs->attriblocs; - else while((vs = strstr(vs, "//:attrib"))) - { - if(sscanf(vs, "//:attrib %100s %d", name, &loc) == 2) - s.attriblocs.add(AttribLoc(getshaderparamname(name), loc)); - vs += len; - } + static int len = strlen("//:attrib"); + string name; + int loc; + if(reusevs) s.attriblocs = reusevs->attriblocs; + else while((vs = strstr(vs, "//:attrib"))) + { + if(sscanf(vs, "//:attrib %100s %d", name, &loc) == 2) + s.attriblocs.add(AttribLoc(getshaderparamname(name), loc)); + vs += len; + } } static void genuniformlocs(Shader &s, const char *vs, const char *ps, Shader *reusevs, Shader *reuseps) { - static int len = strlen("//:uniform"); - string name, blockname; - int binding, stride; - if(reusevs) s.uniformlocs = reusevs->uniformlocs; - else while((vs = strstr(vs, "//:uniform"))) - { - int numargs = sscanf(vs, "//:uniform %100s %100s %d %d", name, blockname, &binding, &stride); - if(numargs >= 3) s.uniformlocs.add(UniformLoc(getshaderparamname(name), getshaderparamname(blockname), binding, numargs >= 4 ? stride : 0)); - else if(numargs >= 1) s.uniformlocs.add(UniformLoc(getshaderparamname(name))); - vs += len; - } + static int len = strlen("//:uniform"); + string name, blockname; + int binding, stride; + if(reusevs) s.uniformlocs = reusevs->uniformlocs; + else while((vs = strstr(vs, "//:uniform"))) + { + int numargs = sscanf(vs, "//:uniform %100s %100s %d %d", name, blockname, &binding, &stride); + if(numargs >= 3) s.uniformlocs.add(UniformLoc(getshaderparamname(name), getshaderparamname(blockname), binding, numargs >= 4 ? stride : 0)); + else if(numargs >= 1) s.uniformlocs.add(UniformLoc(getshaderparamname(name))); + vs += len; + } } Shader *newshader(int type, const char *name, const char *vs, const char *ps, Shader *variant = NULL, int row = 0) { - if(Shader::lastshader) - { - glUseProgram_(0); - Shader::lastshader = NULL; - } - - Shader *exists = shaders.access(name); - char *rname = exists ? exists->name : newstring(name); - Shader &s = shaders[rname]; - s.name = rname; - s.vsstr = newstring(vs); - s.psstr = newstring(ps); - DELETEA(s.defer); - s.type = type; - s.variantshader = variant; - s.standard = standardshaders; - if(forceshaders) s.forced = true; - s.reusevs = s.reuseps = NULL; - if(variant) - { - int row = 0, col = 0; - if(!vs[0] || sscanf(vs, "%d , %d", &row, &col) >= 1) - { - DELETEA(s.vsstr); - s.reusevs = !vs[0] ? variant : variant->getvariant(col, row); - } - row = col = 0; - if(!ps[0] || sscanf(ps, "%d , %d", &row, &col) >= 1) - { - DELETEA(s.psstr); - s.reuseps = !ps[0] ? variant : variant->getvariant(col, row); - } - } - if(variant) loopv(variant->defaultparams) s.defaultparams.add(variant->defaultparams[i]); - else loopv(slotparams) s.defaultparams.add(slotparams[i]); - s.attriblocs.setsize(0); - s.uniformlocs.setsize(0); - genattriblocs(s, vs, ps, s.reusevs, s.reuseps); - genuniformlocs(s, vs, ps, s.reusevs, s.reuseps); - if(!s.compile()) - { - s.cleanup(true); - if(variant) shaders.remove(rname); - return NULL; - } - if(variant) variant->addvariant(row, &s); - s.fixdetailshader(); - return &s; + if(Shader::lastshader) + { + glUseProgram_(0); + Shader::lastshader = NULL; + } + + Shader *exists = shaders.access(name); + char *rname = exists ? exists->name : newstring(name); + Shader &s = shaders[rname]; + s.name = rname; + s.vsstr = newstring(vs); + s.psstr = newstring(ps); + DELETEA(s.defer); + s.type = type; + s.variantshader = variant; + s.standard = standardshaders; + if(forceshaders) s.forced = true; + s.reusevs = s.reuseps = NULL; + if(variant) + { + int row = 0, col = 0; + if(!vs[0] || sscanf(vs, "%d , %d", &row, &col) >= 1) + { + DELETEA(s.vsstr); + s.reusevs = !vs[0] ? variant : variant->getvariant(col, row); + } + row = col = 0; + if(!ps[0] || sscanf(ps, "%d , %d", &row, &col) >= 1) + { + DELETEA(s.psstr); + s.reuseps = !ps[0] ? variant : variant->getvariant(col, row); + } + } + if(variant) loopv(variant->defaultparams) s.defaultparams.add(variant->defaultparams[i]); + else loopv(slotparams) s.defaultparams.add(slotparams[i]); + s.attriblocs.setsize(0); + s.uniformlocs.setsize(0); + genattriblocs(s, vs, ps, s.reusevs, s.reuseps); + genuniformlocs(s, vs, ps, s.reusevs, s.reuseps); + if(!s.compile()) + { + s.cleanup(true); + if(variant) shaders.remove(rname); + return NULL; + } + if(variant) variant->addvariant(row, &s); + s.fixdetailshader(); + return &s; } void setupshaders() { - GLint val; - glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &val); - maxvsuniforms = val/4; - glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &val); - maxfsuniforms = val/4; - if(glversion < 300) - { - glGetIntegerv(GL_MAX_VARYING_COMPONENTS, &val); - maxvaryings = val; - } - - standardshaders = true; - nullshader = newshader(0, "null", - "attribute vec4 vvertex;\n" - "void main(void) {\n" - " gl_Position = vvertex;\n" - "}\n", - "void main(void) {\n" - " gl_FragColor = vec4(1.0, 0.0, 1.0, 0.0);\n" - "}\n"); - hudshader = newshader(0, "hud", - "attribute vec4 vvertex, vcolor;\n" - "attribute vec2 vtexcoord0;\n" - "uniform mat4 hudmatrix;\n" - "varying vec2 texcoord0;\n" - "varying vec4 color;\n" - "void main(void) {\n" - " gl_Position = hudmatrix * vvertex;\n" - " texcoord0 = vtexcoord0;\n" - " color = vcolor;\n" - "}\n", - "varying vec2 texcoord0;\n" - "varying vec4 color;\n" - "uniform sampler2D tex0;\n" - "void main(void) {\n" - " gl_FragColor = color * texture2D(tex0, texcoord0);\n" - "}\n"); - hudnotextureshader = newshader(0, "hudnotexture", - "attribute vec4 vvertex, vcolor;\n" - "uniform mat4 hudmatrix;\n" - "varying vec4 color;\n" - "void main(void) {\n" - " gl_Position = hudmatrix * vvertex;\n" - " color = vcolor;\n" - "}\n", - "varying vec4 color;\n" - "void main(void) {\n" - " gl_FragColor = color;\n" - "}\n"); - standardshaders = false; - - if(!nullshader || !hudshader || !hudnotextureshader) fatal("failed to setup shaders"); - - dummyslot.shader = nullshader; + GLint val; + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &val); + maxvsuniforms = val/4; + glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &val); + maxfsuniforms = val/4; + if(glversion < 300) + { + glGetIntegerv(GL_MAX_VARYING_COMPONENTS, &val); + maxvaryings = val; + } + + standardshaders = true; + nullshader = newshader(0, "null", + "attribute vec4 vvertex;\n" + "void main(void) {\n" + " gl_Position = vvertex;\n" + "}\n", + "void main(void) {\n" + " gl_FragColor = vec4(1.0, 0.0, 1.0, 0.0);\n" + "}\n"); + hudshader = newshader(0, "hud", + "attribute vec4 vvertex, vcolor;\n" + "attribute vec2 vtexcoord0;\n" + "uniform mat4 hudmatrix;\n" + "varying vec2 texcoord0;\n" + "varying vec4 color;\n" + "void main(void) {\n" + " gl_Position = hudmatrix * vvertex;\n" + " texcoord0 = vtexcoord0;\n" + " color = vcolor;\n" + "}\n", + "varying vec2 texcoord0;\n" + "varying vec4 color;\n" + "uniform sampler2D tex0;\n" + "void main(void) {\n" + " gl_FragColor = color * texture2D(tex0, texcoord0);\n" + "}\n"); + hudnotextureshader = newshader(0, "hudnotexture", + "attribute vec4 vvertex, vcolor;\n" + "uniform mat4 hudmatrix;\n" + "varying vec4 color;\n" + "void main(void) {\n" + " gl_Position = hudmatrix * vvertex;\n" + " color = vcolor;\n" + "}\n", + "varying vec4 color;\n" + "void main(void) {\n" + " gl_FragColor = color;\n" + "}\n"); + standardshaders = false; + + if(!nullshader || !hudshader || !hudnotextureshader) fatal("failed to setup shaders"); + + dummyslot.shader = nullshader; } static const char *findglslmain(const char *s) { - const char *main = strstr(s, "main"); - if(!main) return NULL; - for(; main >= s; main--) switch(*main) { case '\r': case '\n': case ';': return main + 1; } - return s; + const char *main = strstr(s, "main"); + if(!main) return NULL; + for(; main >= s; main--) switch(*main) { case '\r': case '\n': case ';': return main + 1; } + return s; } static void gengenericvariant(Shader &s, const char *sname, const char *vs, const char *ps, int row = 0) { - int rowoffset = 0; - bool vschanged = false, pschanged = false; - vector vsv, psv; - vsv.put(vs, strlen(vs)+1); - psv.put(ps, strlen(ps)+1); - - static const int len = strlen("//:variant"), olen = strlen("override"); - for(char *vspragma = vsv.getbuf();; vschanged = true) - { - vspragma = strstr(vspragma, "//:variant"); - if(!vspragma) break; - if(sscanf(vspragma + len, "row %d", &rowoffset) == 1) continue; - memset(vspragma, ' ', len); - vspragma += len; - if(!strncmp(vspragma, "override", olen)) - { - memset(vspragma, ' ', olen); - vspragma += olen; - char *end = vspragma + strcspn(vspragma, "\n\r"); - end += strspn(end, "\n\r"); - int endlen = strcspn(end, "\n\r"); - memset(end, ' ', endlen); - } - } - for(char *pspragma = psv.getbuf();; pschanged = true) - { - pspragma = strstr(pspragma, "//:variant"); - if(!pspragma) break; - if(sscanf(pspragma + len, "row %d", &rowoffset) == 1) continue; - memset(pspragma, ' ', len); - pspragma += len; - if(!strncmp(pspragma, "override", olen)) - { - memset(pspragma, ' ', olen); - pspragma += olen; - char *end = pspragma + strcspn(pspragma, "\n\r"); - end += strspn(end, "\n\r"); - int endlen = strcspn(end, "\n\r"); - memset(end, ' ', endlen); - } - } - row += rowoffset; - if(row < 0 || row >= MAXVARIANTROWS) return; - int col = s.numvariants(row); - defformatstring(varname, "%s", col, row, sname); - string reuse; - if(col) formatstring(reuse, "%d", row); - else copystring(reuse, ""); - newshader(s.type, varname, vschanged ? vsv.getbuf() : reuse, pschanged ? psv.getbuf() : reuse, &s, row); + int rowoffset = 0; + bool vschanged = false, pschanged = false; + vector vsv, psv; + vsv.put(vs, strlen(vs)+1); + psv.put(ps, strlen(ps)+1); + + static const int len = strlen("//:variant"), olen = strlen("override"); + for(char *vspragma = vsv.getbuf();; vschanged = true) + { + vspragma = strstr(vspragma, "//:variant"); + if(!vspragma) break; + if(sscanf(vspragma + len, "row %d", &rowoffset) == 1) continue; + memset(vspragma, ' ', len); + vspragma += len; + if(!strncmp(vspragma, "override", olen)) + { + memset(vspragma, ' ', olen); + vspragma += olen; + char *end = vspragma + strcspn(vspragma, "\n\r"); + end += strspn(end, "\n\r"); + int endlen = strcspn(end, "\n\r"); + memset(end, ' ', endlen); + } + } + for(char *pspragma = psv.getbuf();; pschanged = true) + { + pspragma = strstr(pspragma, "//:variant"); + if(!pspragma) break; + if(sscanf(pspragma + len, "row %d", &rowoffset) == 1) continue; + memset(pspragma, ' ', len); + pspragma += len; + if(!strncmp(pspragma, "override", olen)) + { + memset(pspragma, ' ', olen); + pspragma += olen; + char *end = pspragma + strcspn(pspragma, "\n\r"); + end += strspn(end, "\n\r"); + int endlen = strcspn(end, "\n\r"); + memset(end, ' ', endlen); + } + } + row += rowoffset; + if(row < 0 || row >= MAXVARIANTROWS) return; + int col = s.numvariants(row); + defformatstring(varname, "%s", col, row, sname); + string reuse; + if(col) formatstring(reuse, "%d", row); + else copystring(reuse, ""); + newshader(s.type, varname, vschanged ? vsv.getbuf() : reuse, pschanged ? psv.getbuf() : reuse, &s, row); } static bool genwatervariant(Shader &s, const char *sname, const char *vs, const char *ps, int row = 2) { - if(!strstr(vs, "//:water") && !strstr(ps, "//:water")) return false; - - vector vsw, psw; - - const char *vsmain = findglslmain(vs), *vsend = strrchr(vs, '}'); - if(!vsmain || !vsend) return false; - vsw.put(vs, vsmain - vs); - const char *fadeparams = "\nuniform vec4 waterfadeparams;\nvarying float fadedepth;\n"; - vsw.put(fadeparams, strlen(fadeparams)); - vsw.put(vsmain, vsend - vsmain); - const char *fadedef = "\nfadedepth = vvertex.z*waterfadeparams.x + waterfadeparams.y;\n"; - vsw.put(fadedef, strlen(fadedef)); - vsw.put(vsend, strlen(vsend)+1); - - const char *psmain = findglslmain(ps), *psend = strrchr(ps, '}'); - if(!psmain || !psend) return false; - psw.put(ps, psmain - ps); - const char *fadeinterp = "\nvarying float fadedepth;\n"; - psw.put(fadeinterp, strlen(fadeinterp)); - psw.put(psmain, psend - psmain); - const char *fade = "\ngl_FragColor.a = fadedepth;\n"; - psw.put(fade, strlen(fade)); - psw.put(psend, strlen(psend)+1); - - defformatstring(name, "%s", sname); - Shader *variant = newshader(s.type, name, vsw.getbuf(), psw.getbuf(), &s, row); - return variant!=NULL; + if(!strstr(vs, "//:water") && !strstr(ps, "//:water")) return false; + + vector vsw, psw; + + const char *vsmain = findglslmain(vs), *vsend = strrchr(vs, '}'); + if(!vsmain || !vsend) return false; + vsw.put(vs, vsmain - vs); + const char *fadeparams = "\nuniform vec4 waterfadeparams;\nvarying float fadedepth;\n"; + vsw.put(fadeparams, strlen(fadeparams)); + vsw.put(vsmain, vsend - vsmain); + const char *fadedef = "\nfadedepth = vvertex.z*waterfadeparams.x + waterfadeparams.y;\n"; + vsw.put(fadedef, strlen(fadedef)); + vsw.put(vsend, strlen(vsend)+1); + + const char *psmain = findglslmain(ps), *psend = strrchr(ps, '}'); + if(!psmain || !psend) return false; + psw.put(ps, psmain - ps); + const char *fadeinterp = "\nvarying float fadedepth;\n"; + psw.put(fadeinterp, strlen(fadeinterp)); + psw.put(psmain, psend - psmain); + const char *fade = "\ngl_FragColor.a = fadedepth;\n"; + psw.put(fade, strlen(fade)); + psw.put(psend, strlen(psend)+1); + + defformatstring(name, "%s", sname); + Shader *variant = newshader(s.type, name, vsw.getbuf(), psw.getbuf(), &s, row); + return variant!=NULL; } bool minimizedynlighttcusage() { return glversion < 300 && maxvaryings < 48; } static void gendynlightvariant(Shader &s, const char *sname, const char *vs, const char *ps, int row = 0) { - int numlights = minimizedynlighttcusage() ? 1 : MAXDYNLIGHTS; - - const char *vspragma = strstr(vs, "//:dynlight"), *pspragma = strstr(ps, "//:dynlight"); - if(!vspragma || !pspragma) return; - - string pslight; - vspragma += strcspn(vspragma, "\n"); - if(*vspragma) vspragma++; - - if(sscanf(pspragma, "//:dynlight %100s", pslight)!=1) return; - - pspragma += strcspn(pspragma, "\n"); - if(*pspragma) pspragma++; - - const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); - if(vsmain > vspragma) vsmain = vs; - if(psmain > pspragma) psmain = ps; - - vector vsdl, psdl; - loopi(MAXDYNLIGHTS) - { - vsdl.setsize(0); - psdl.setsize(0); - if(vsmain >= vs) vsdl.put(vs, vsmain - vs); - if(psmain >= ps) psdl.put(ps, psmain - ps); - - defformatstring(pos, "uniform vec4 dynlightpos[%d];\n", i+1); - vsdl.put(pos, strlen(pos)); - psdl.put(pos, strlen(pos)); - defformatstring(color, "uniform vec3 dynlightcolor[%d];\n", i+1); - psdl.put(color, strlen(color)); - - loopk(min(i+1, numlights)) - { - defformatstring(dir, "%sdynlight%ddir%s", !k ? "varying vec3 " : " ", k, k==i || k+1==numlights ? ";\n" : ","); - vsdl.put(dir, strlen(dir)); - psdl.put(dir, strlen(dir)); - } - - vsdl.put(vsmain, vspragma-vsmain); - psdl.put(psmain, pspragma-psmain); - - loopk(i+1) - { - defformatstring(tc, - k%s", i+1, sname); - Shader *variant = newshader(s.type, name, vsdl.getbuf(), psdl.getbuf(), &s, row); - if(!variant) return; - if(row < 4) genwatervariant(s, name, vsdl.getbuf(), psdl.getbuf(), row+2); - } + int numlights = minimizedynlighttcusage() ? 1 : MAXDYNLIGHTS; + + const char *vspragma = strstr(vs, "//:dynlight"), *pspragma = strstr(ps, "//:dynlight"); + if(!vspragma || !pspragma) return; + + string pslight; + vspragma += strcspn(vspragma, "\n"); + if(*vspragma) vspragma++; + + if(sscanf(pspragma, "//:dynlight %100s", pslight)!=1) return; + + pspragma += strcspn(pspragma, "\n"); + if(*pspragma) pspragma++; + + const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); + if(vsmain > vspragma) vsmain = vs; + if(psmain > pspragma) psmain = ps; + + vector vsdl, psdl; + loopi(MAXDYNLIGHTS) + { + vsdl.setsize(0); + psdl.setsize(0); + if(vsmain >= vs) vsdl.put(vs, vsmain - vs); + if(psmain >= ps) psdl.put(ps, psmain - ps); + + defformatstring(pos, "uniform vec4 dynlightpos[%d];\n", i+1); + vsdl.put(pos, strlen(pos)); + psdl.put(pos, strlen(pos)); + defformatstring(color, "uniform vec3 dynlightcolor[%d];\n", i+1); + psdl.put(color, strlen(color)); + + loopk(min(i+1, numlights)) + { + defformatstring(dir, "%sdynlight%ddir%s", !k ? "varying vec3 " : " ", k, k==i || k+1==numlights ? ";\n" : ","); + vsdl.put(dir, strlen(dir)); + psdl.put(dir, strlen(dir)); + } + + vsdl.put(vsmain, vspragma-vsmain); + psdl.put(psmain, pspragma-psmain); + + loopk(i+1) + { + defformatstring(tc, + k%s", i+1, sname); + Shader *variant = newshader(s.type, name, vsdl.getbuf(), psdl.getbuf(), &s, row); + if(!variant) return; + if(row < 4) genwatervariant(s, name, vsdl.getbuf(), psdl.getbuf(), row+2); + } } static void genshadowmapvariant(Shader &s, const char *sname, const char *vs, const char *ps, int row = 1) { - const char *vspragma = strstr(vs, "//:shadowmap"), *pspragma = strstr(ps, "//:shadowmap"); - if(!vspragma || !pspragma) return; - - string pslight; - vspragma += strcspn(vspragma, "\n"); - if(*vspragma) vspragma++; - - if(sscanf(pspragma, "//:shadowmap %100s", pslight)!=1) return; - - pspragma += strcspn(pspragma, "\n"); - if(*pspragma) pspragma++; - - const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); - if(vsmain > vspragma) vsmain = vs; - if(psmain > pspragma) psmain = ps; - - vector vssm, pssm; - if(vsmain >= vs) vssm.put(vs, vsmain - vs); - if(psmain >= ps) pssm.put(ps, psmain - ps); - - const char *vsdecl = - "uniform mat4 shadowmapproject;\n" - "varying vec3 shadowmaptc;\n"; - vssm.put(vsdecl, strlen(vsdecl)); - - const char *psdecl = - "uniform sampler2D shadowmap;\n" - "uniform vec4 shadowmapambient;\n" - "varying vec3 shadowmaptc;\n"; - pssm.put(psdecl, strlen(psdecl)); - - vssm.put(vsmain, vspragma-vsmain); - pssm.put(psmain, pspragma-psmain); - - extern int smoothshadowmappeel; - const char *tcgen = - "shadowmaptc = vec3(shadowmapproject * vvertex);\n"; - vssm.put(tcgen, strlen(tcgen)); - const char *sm = - smoothshadowmappeel ? - "vec4 smvals = texture2D(shadowmap, shadowmaptc.xy);\n" - "vec2 smdiff = clamp(smvals.xz - shadowmaptc.zz*smvals.y, 0.0, 1.0);\n" - "float shadowed = clamp((smdiff.x > 0.0 ? smvals.w : 0.0) - 8.0*smdiff.y, 0.0, 1.0);\n" : - - "vec4 smvals = texture2D(shadowmap, shadowmaptc.xy);\n" - "float smtest = shadowmaptc.z*smvals.y;\n" - "float shadowed = smtest < smvals.x && smtest > smvals.z ? smvals.w : 0.0;\n"; - pssm.put(sm, strlen(sm)); - defformatstring(smlight, - "%s.rgb -= shadowed*clamp(%s.rgb - shadowmapambient.rgb, 0.0, 1.0);\n", - pslight, pslight); - pssm.put(smlight, strlen(smlight)); - - vssm.put(vspragma, strlen(vspragma)+1); - pssm.put(pspragma, strlen(pspragma)+1); - - defformatstring(name, "%s", sname); - Shader *variant = newshader(s.type, name, vssm.getbuf(), pssm.getbuf(), &s, row); - if(!variant) return; - genwatervariant(s, name, vssm.getbuf(), pssm.getbuf(), row+2); - - if(strstr(vs, "//:dynlight")) gendynlightvariant(s, name, vssm.getbuf(), pssm.getbuf(), row); + const char *vspragma = strstr(vs, "//:shadowmap"), *pspragma = strstr(ps, "//:shadowmap"); + if(!vspragma || !pspragma) return; + + string pslight; + vspragma += strcspn(vspragma, "\n"); + if(*vspragma) vspragma++; + + if(sscanf(pspragma, "//:shadowmap %100s", pslight)!=1) return; + + pspragma += strcspn(pspragma, "\n"); + if(*pspragma) pspragma++; + + const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); + if(vsmain > vspragma) vsmain = vs; + if(psmain > pspragma) psmain = ps; + + vector vssm, pssm; + if(vsmain >= vs) vssm.put(vs, vsmain - vs); + if(psmain >= ps) pssm.put(ps, psmain - ps); + + const char *vsdecl = + "uniform mat4 shadowmapproject;\n" + "varying vec3 shadowmaptc;\n"; + vssm.put(vsdecl, strlen(vsdecl)); + + const char *psdecl = + "uniform sampler2D shadowmap;\n" + "uniform vec4 shadowmapambient;\n" + "varying vec3 shadowmaptc;\n"; + pssm.put(psdecl, strlen(psdecl)); + + vssm.put(vsmain, vspragma-vsmain); + pssm.put(psmain, pspragma-psmain); + + extern int smoothshadowmappeel; + const char *tcgen = + "shadowmaptc = vec3(shadowmapproject * vvertex);\n"; + vssm.put(tcgen, strlen(tcgen)); + const char *sm = + smoothshadowmappeel ? + "vec4 smvals = texture2D(shadowmap, shadowmaptc.xy);\n" + "vec2 smdiff = clamp(smvals.xz - shadowmaptc.zz*smvals.y, 0.0, 1.0);\n" + "float shadowed = clamp((smdiff.x > 0.0 ? smvals.w : 0.0) - 8.0*smdiff.y, 0.0, 1.0);\n" : + + "vec4 smvals = texture2D(shadowmap, shadowmaptc.xy);\n" + "float smtest = shadowmaptc.z*smvals.y;\n" + "float shadowed = smtest < smvals.x && smtest > smvals.z ? smvals.w : 0.0;\n"; + pssm.put(sm, strlen(sm)); + defformatstring(smlight, + "%s.rgb -= shadowed*clamp(%s.rgb - shadowmapambient.rgb, 0.0, 1.0);\n", + pslight, pslight); + pssm.put(smlight, strlen(smlight)); + + vssm.put(vspragma, strlen(vspragma)+1); + pssm.put(pspragma, strlen(pspragma)+1); + + defformatstring(name, "%s", sname); + Shader *variant = newshader(s.type, name, vssm.getbuf(), pssm.getbuf(), &s, row); + if(!variant) return; + genwatervariant(s, name, vssm.getbuf(), pssm.getbuf(), row+2); + + if(strstr(vs, "//:dynlight")) gendynlightvariant(s, name, vssm.getbuf(), pssm.getbuf(), row); } static void genfogshader(vector &vsbuf, vector &psbuf, const char *vs, const char *ps) { - const char *vspragma = strstr(vs, "//:fog"), *pspragma = strstr(ps, "//:fog"); - if(!vspragma && !pspragma) return; - static const int pragmalen = strlen("//:fog"); - const char *vsmain = findglslmain(vs), *vsend = strrchr(vs, '}'); - if(vsmain && vsend) - { - vsbuf.put(vs, vsmain - vs); - const char *fogparams = "\nuniform vec4 fogplane;\nvarying float fogcoord;\n"; - vsbuf.put(fogparams, strlen(fogparams)); - vsbuf.put(vsmain, vsend - vsmain); - const char *vsfog = "\nfogcoord = dot(fogplane, gl_Position);\n"; - vsbuf.put(vsfog, strlen(vsfog)); - vsbuf.put(vsend, strlen(vsend)+1); - } - const char *psmain = findglslmain(ps), *psend = strrchr(ps, '}'); - if(psmain && psend) - { - psbuf.put(ps, psmain - ps); - const char *fogparams = - "\nuniform vec3 fogcolor;\n" - "uniform vec2 fogparams;\n" - "varying float fogcoord;\n"; - psbuf.put(fogparams, strlen(fogparams)); - psbuf.put(psmain, psend - psmain); - const char *psdef = "\n#define FOG_COLOR "; - const char *psfog = - pspragma && !strncmp(pspragma+pragmalen, "rgba", 4) ? - "\ngl_FragColor = mix((FOG_COLOR), gl_FragColor, clamp(fogcoord*fogparams.x + fogparams.y, 0.0, 1.0));\n" : - "\ngl_FragColor.rgb = mix((FOG_COLOR).rgb, gl_FragColor.rgb, clamp(fogcoord*fogparams.x + fogparams.y, 0.0, 1.0));\n"; - int clen = 0; - if(pspragma) - { - pspragma += pragmalen; - while(iscubealpha(*pspragma)) pspragma++; - while(*pspragma && !iscubespace(*pspragma)) pspragma++; - pspragma += strspn(pspragma, " \t\v\f"); - clen = strcspn(pspragma, "\r\n"); - } - if(clen <= 0) { pspragma = "fogcolor"; clen = strlen(pspragma); } - psbuf.put(psdef, strlen(psdef)); - psbuf.put(pspragma, clen); - psbuf.put(psfog, strlen(psfog)); - psbuf.put(psend, strlen(psend)+1); - } + const char *vspragma = strstr(vs, "//:fog"), *pspragma = strstr(ps, "//:fog"); + if(!vspragma && !pspragma) return; + static const int pragmalen = strlen("//:fog"); + const char *vsmain = findglslmain(vs), *vsend = strrchr(vs, '}'); + if(vsmain && vsend) + { + vsbuf.put(vs, vsmain - vs); + const char *fogparams = "\nuniform vec4 fogplane;\nvarying float fogcoord;\n"; + vsbuf.put(fogparams, strlen(fogparams)); + vsbuf.put(vsmain, vsend - vsmain); + const char *vsfog = "\nfogcoord = dot(fogplane, gl_Position);\n"; + vsbuf.put(vsfog, strlen(vsfog)); + vsbuf.put(vsend, strlen(vsend)+1); + } + const char *psmain = findglslmain(ps), *psend = strrchr(ps, '}'); + if(psmain && psend) + { + psbuf.put(ps, psmain - ps); + const char *fogparams = + "\nuniform vec3 fogcolor;\n" + "uniform vec2 fogparams;\n" + "varying float fogcoord;\n"; + psbuf.put(fogparams, strlen(fogparams)); + psbuf.put(psmain, psend - psmain); + const char *psdef = "\n#define FOG_COLOR "; + const char *psfog = + pspragma && !strncmp(pspragma+pragmalen, "rgba", 4) ? + "\ngl_FragColor = mix((FOG_COLOR), gl_FragColor, clamp(fogcoord*fogparams.x + fogparams.y, 0.0, 1.0));\n" : + "\ngl_FragColor.rgb = mix((FOG_COLOR).rgb, gl_FragColor.rgb, clamp(fogcoord*fogparams.x + fogparams.y, 0.0, 1.0));\n"; + int clen = 0; + if(pspragma) + { + pspragma += pragmalen; + while(iscubealpha(*pspragma)) pspragma++; + while(*pspragma && !iscubespace(*pspragma)) pspragma++; + pspragma += strspn(pspragma, " \t\v\f"); + clen = strcspn(pspragma, "\r\n"); + } + if(clen <= 0) { pspragma = "fogcolor"; clen = strlen(pspragma); } + psbuf.put(psdef, strlen(psdef)); + psbuf.put(pspragma, clen); + psbuf.put(psfog, strlen(psfog)); + psbuf.put(psend, strlen(psend)+1); + } } static void genuniformdefs(vector &vsbuf, vector &psbuf, const char *vs, const char *ps, Shader *variant = NULL) { - if(variant ? variant->defaultparams.empty() : slotparams.empty()) return; - const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); - if(!vsmain || !psmain) return; - vsbuf.put(vs, vsmain - vs); - psbuf.put(ps, psmain - ps); - if(variant) loopv(variant->defaultparams) - { - defformatstring(uni, "\nuniform vec4 %s;\n", variant->defaultparams[i].name); - vsbuf.put(uni, strlen(uni)); - psbuf.put(uni, strlen(uni)); - } - else loopv(slotparams) - { - defformatstring(uni, "\nuniform vec4 %s;\n", slotparams[i].name); - vsbuf.put(uni, strlen(uni)); - psbuf.put(uni, strlen(uni)); - } - vsbuf.put(vsmain, strlen(vsmain)+1); - psbuf.put(psmain, strlen(psmain)+1); + if(variant ? variant->defaultparams.empty() : slotparams.empty()) return; + const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); + if(!vsmain || !psmain) return; + vsbuf.put(vs, vsmain - vs); + psbuf.put(ps, psmain - ps); + if(variant) loopv(variant->defaultparams) + { + defformatstring(uni, "\nuniform vec4 %s;\n", variant->defaultparams[i].name); + vsbuf.put(uni, strlen(uni)); + psbuf.put(uni, strlen(uni)); + } + else loopv(slotparams) + { + defformatstring(uni, "\nuniform vec4 %s;\n", slotparams[i].name); + vsbuf.put(uni, strlen(uni)); + psbuf.put(uni, strlen(uni)); + } + vsbuf.put(vsmain, strlen(vsmain)+1); + psbuf.put(psmain, strlen(psmain)+1); } VAR(defershaders, 0, 1, 1); void defershader(int *type, const char *name, const char *contents) { - Shader *exists = shaders.access(name); - if(exists && !exists->invalid()) return; - if(!defershaders) { execute(contents); return; } - char *rname = exists ? exists->name : newstring(name); - Shader &s = shaders[rname]; - s.name = rname; - DELETEA(s.defer); - s.defer = newstring(contents); - s.type = SHADER_DEFERRED | *type; - s.standard = standardshaders; + Shader *exists = shaders.access(name); + if(exists && !exists->invalid()) return; + if(!defershaders) { execute(contents); return; } + char *rname = exists ? exists->name : newstring(name); + Shader &s = shaders[rname]; + s.name = rname; + DELETEA(s.defer); + s.defer = newstring(contents); + s.type = SHADER_DEFERRED | *type; + s.standard = standardshaders; } void Shader::force() { - if(!deferred() || !defer) return; - - char *cmd = defer; - defer = NULL; - bool wasstandard = standardshaders, wasforcing = forceshaders; - int oldflags = identflags; - standardshaders = standard; - forceshaders = false; - identflags &= ~IDF_PERSIST; - slotparams.shrink(0); - execute(cmd); - identflags = oldflags; - forceshaders = wasforcing; - standardshaders = wasstandard; - delete[] cmd; - - if(deferred()) - { - DELETEA(defer); - type = SHADER_INVALID; - } + if(!deferred() || !defer) return; + + char *cmd = defer; + defer = NULL; + bool wasstandard = standardshaders, wasforcing = forceshaders; + int oldflags = identflags; + standardshaders = standard; + forceshaders = false; + identflags &= ~IDF_PERSIST; + slotparams.shrink(0); + execute(cmd); + identflags = oldflags; + forceshaders = wasforcing; + standardshaders = wasstandard; + delete[] cmd; + + if(deferred()) + { + DELETEA(defer); + type = SHADER_INVALID; + } } void fixshaderdetail() { - // must null out separately because fixdetailshader can recursively set it - enumerate(shaders, Shader, s, { if(!s.forced) s.detailshader = NULL; }); - enumerate(shaders, Shader, s, { if(s.forced) s.fixdetailshader(); }); - linkslotshaders(); + // must null out separately because fixdetailshader can recursively set it + enumerate(shaders, Shader, s, { if(!s.forced) s.detailshader = NULL; }); + enumerate(shaders, Shader, s, { if(s.forced) s.fixdetailshader(); }); + linkslotshaders(); } int Shader::uniformlocversion() { - static int version = 0; - if(++version >= 0) return version; - version = 0; - enumerate(shaders, Shader, s, { loopvj(s.uniformlocs) s.uniformlocs[j].version = -1; }); - return version; + static int version = 0; + if(++version >= 0) return version; + version = 0; + enumerate(shaders, Shader, s, { loopvj(s.uniformlocs) s.uniformlocs[j].version = -1; }); + return version; } VARFP(shaderdetail, 0, MAXSHADERDETAIL, MAXSHADERDETAIL, fixshaderdetail()); void Shader::fixdetailshader(bool shouldforce, bool recurse) { - Shader *alt = this; - detailshader = NULL; - do - { - Shader *cur = shaderdetail < MAXSHADERDETAIL ? alt->fastshader[shaderdetail] : alt; - if(cur->deferred() && shouldforce) cur->force(); - if(!cur->invalid()) - { - if(cur->deferred()) break; - detailshader = cur; - break; - } - alt = alt->altshader; - } while(alt && alt!=this); - - if(recurse && detailshader) loopv(detailshader->variants) detailshader->variants[i]->fixdetailshader(shouldforce, false); + Shader *alt = this; + detailshader = NULL; + do + { + Shader *cur = shaderdetail < MAXSHADERDETAIL ? alt->fastshader[shaderdetail] : alt; + if(cur->deferred() && shouldforce) cur->force(); + if(!cur->invalid()) + { + if(cur->deferred()) break; + detailshader = cur; + break; + } + alt = alt->altshader; + } while(alt && alt!=this); + + if(recurse && detailshader) loopv(detailshader->variants) detailshader->variants[i]->fixdetailshader(shouldforce, false); } Shader *useshaderbyname(const char *name) { - Shader *s = shaders.access(name); - if(!s) return NULL; - if(!s->detailshader) s->fixdetailshader(); - s->forced = true; - return s; + Shader *s = shaders.access(name); + if(!s) return NULL; + if(!s->detailshader) s->fixdetailshader(); + s->forced = true; + return s; } void shader(int *type, char *name, char *vs, char *ps) { - if(lookupshaderbyname(name)) return; - - defformatstring(info, "shader %s", name); - renderprogress(loadprogress, info); + if(lookupshaderbyname(name)) return; + + defformatstring(info, "shader %s", name); + renderprogress(loadprogress, info); - vector vsbuf, psbuf, vsbak, psbak; + vector vsbuf, psbuf, vsbak, psbak; #define GENSHADER(cond, body) \ - if(cond) \ - { \ - if(vsbuf.length()) { vsbak.setsize(0); vsbak.put(vs, strlen(vs)+1); vs = vsbak.getbuf(); vsbuf.setsize(0); } \ - if(psbuf.length()) { psbak.setsize(0); psbak.put(ps, strlen(ps)+1); ps = psbak.getbuf(); psbuf.setsize(0); } \ - body; \ - if(vsbuf.length()) vs = vsbuf.getbuf(); \ - if(psbuf.length()) ps = psbuf.getbuf(); \ - } - GENSHADER(slotparams.length(), genuniformdefs(vsbuf, psbuf, vs, ps)); - GENSHADER(strstr(vs, "//:fog") || strstr(ps, "//:fog"), genfogshader(vsbuf, psbuf, vs, ps)); - Shader *s = newshader(*type, name, vs, ps); - if(s) - { - if(strstr(vs, "//:water")) genwatervariant(*s, s->name, vs, ps); - if(strstr(vs, "//:shadowmap")) genshadowmapvariant(*s, s->name, vs, ps); - if(strstr(vs, "//:dynlight")) gendynlightvariant(*s, s->name, vs, ps); - } - slotparams.shrink(0); + if(cond) \ + { \ + if(vsbuf.length()) { vsbak.setsize(0); vsbak.put(vs, strlen(vs)+1); vs = vsbak.getbuf(); vsbuf.setsize(0); } \ + if(psbuf.length()) { psbak.setsize(0); psbak.put(ps, strlen(ps)+1); ps = psbak.getbuf(); psbuf.setsize(0); } \ + body; \ + if(vsbuf.length()) vs = vsbuf.getbuf(); \ + if(psbuf.length()) ps = psbuf.getbuf(); \ + } + GENSHADER(slotparams.length(), genuniformdefs(vsbuf, psbuf, vs, ps)); + GENSHADER(strstr(vs, "//:fog") || strstr(ps, "//:fog"), genfogshader(vsbuf, psbuf, vs, ps)); + Shader *s = newshader(*type, name, vs, ps); + if(s) + { + if(strstr(vs, "//:water")) genwatervariant(*s, s->name, vs, ps); + if(strstr(vs, "//:shadowmap")) genshadowmapvariant(*s, s->name, vs, ps); + if(strstr(vs, "//:dynlight")) gendynlightvariant(*s, s->name, vs, ps); + } + slotparams.shrink(0); } void variantshader(int *type, char *name, int *row, char *vs, char *ps) { - if(*row < 0) - { - shader(type, name, vs, ps); - return; - } - else if(*row >= MAXVARIANTROWS) return; - - Shader *s = lookupshaderbyname(name); - if(!s) return; - - defformatstring(varname, "%s", s->numvariants(*row), *row, name); - //defformatstring(info, "shader %s", varname); - //renderprogress(loadprogress, info); - vector vsbuf, psbuf, vsbak, psbak; - GENSHADER(s->defaultparams.length(), genuniformdefs(vsbuf, psbuf, vs, ps, s)); - GENSHADER(strstr(vs, "//:fog") || strstr(ps, "//:fog"), genfogshader(vsbuf, psbuf, vs, ps)); - Shader *v = newshader(*type, varname, vs, ps, s, *row); - if(v) - { - if(strstr(vs, "//:dynlight")) gendynlightvariant(*s, varname, vs, ps, *row); - if(strstr(ps, "//:variant") || strstr(vs, "//:variant")) gengenericvariant(*s, varname, vs, ps, *row); - } + if(*row < 0) + { + shader(type, name, vs, ps); + return; + } + else if(*row >= MAXVARIANTROWS) return; + + Shader *s = lookupshaderbyname(name); + if(!s) return; + + defformatstring(varname, "%s", s->numvariants(*row), *row, name); + //defformatstring(info, "shader %s", varname); + //renderprogress(loadprogress, info); + vector vsbuf, psbuf, vsbak, psbak; + GENSHADER(s->defaultparams.length(), genuniformdefs(vsbuf, psbuf, vs, ps, s)); + GENSHADER(strstr(vs, "//:fog") || strstr(ps, "//:fog"), genfogshader(vsbuf, psbuf, vs, ps)); + Shader *v = newshader(*type, varname, vs, ps, s, *row); + if(v) + { + if(strstr(vs, "//:dynlight")) gendynlightvariant(*s, varname, vs, ps, *row); + if(strstr(ps, "//:variant") || strstr(vs, "//:variant")) gengenericvariant(*s, varname, vs, ps, *row); + } } void setshader(char *name) { - slotparams.shrink(0); - Shader *s = shaders.access(name); - if(!s) - { - conoutf(CON_ERROR, "no such shader: %s", name); - } - else slotshader = s; + slotparams.shrink(0); + Shader *s = shaders.access(name); + if(!s) + { + conoutf(CON_ERROR, "no such shader: %s", name); + } + else slotshader = s; } void resetslotshader() { - slotshader = NULL; - slotparams.shrink(0); + slotshader = NULL; + slotparams.shrink(0); } void setslotshader(Slot &s) { - s.shader = slotshader; - if(!s.shader) - { - s.shader = stdworldshader; - return; - } - loopv(slotparams) s.params.add(slotparams[i]); + s.shader = slotshader; + if(!s.shader) + { + s.shader = stdworldshader; + return; + } + loopv(slotparams) s.params.add(slotparams[i]); } static void linkslotshaderparams(vector ¶ms, Shader *sh, bool load) { - if(sh) loopv(params) - { - int loc = -1; - SlotShaderParam ¶m = params[i]; - loopv(sh->defaultparams) - { - SlotShaderParamState &dparam = sh->defaultparams[i]; - if(dparam.name==param.name) - { - if(memcmp(param.val, dparam.val, sizeof(param.val))) loc = i; - break; - } - } - param.loc = loc; - } - else if(load) loopv(params) params[i].loc = -1; + if(sh) loopv(params) + { + int loc = -1; + SlotShaderParam ¶m = params[i]; + loopv(sh->defaultparams) + { + SlotShaderParamState &dparam = sh->defaultparams[i]; + if(dparam.name==param.name) + { + if(memcmp(param.val, dparam.val, sizeof(param.val))) loc = i; + break; + } + } + param.loc = loc; + } + else if(load) loopv(params) params[i].loc = -1; } void linkslotshader(Slot &s, bool load) { - if(!s.shader) return; + if(!s.shader) return; - if(load && !s.shader->detailshader) s.shader->fixdetailshader(); + if(load && !s.shader->detailshader) s.shader->fixdetailshader(); - linkslotshaderparams(s.params, s.shader->detailshader, load); + linkslotshaderparams(s.params, s.shader->detailshader, load); } void linkvslotshader(VSlot &s, bool load) { - if(!s.slot->shader) return; + if(!s.slot->shader) return; - Shader *sh = s.slot->shader->detailshader; - linkslotshaderparams(s.params, sh, load); + Shader *sh = s.slot->shader->detailshader; + linkslotshaderparams(s.params, sh, load); - if(!sh) return; + if(!sh) return; - if(s.slot->texmask&(1<texmask&(1<altshader = alt; - orig->fixdetailshader(false); + Shader *orig = shaders.access(origname), *alt = shaders.access(altname); + if(!orig || !alt) return; + orig->altshader = alt; + orig->fixdetailshader(false); } void fastshader(char *nice, char *fast, int *detail) { - Shader *ns = shaders.access(nice), *fs = shaders.access(fast); - if(!ns || !fs) return; - loopi(min(*detail+1, MAXSHADERDETAIL)) ns->fastshader[i] = fs; - ns->fixdetailshader(false); + Shader *ns = shaders.access(nice), *fs = shaders.access(fast); + if(!ns || !fs) return; + loopi(min(*detail+1, MAXSHADERDETAIL)) ns->fastshader[i] = fs; + ns->fixdetailshader(false); } COMMAND(shader, "isss"); @@ -1233,28 +1228,28 @@ static hashset shaderparamnames(256); const char *getshaderparamname(const char *name, bool insert) { - const char *exists = shaderparamnames.find(name, NULL); - if(exists || !insert) return exists; - return shaderparamnames.add(newstring(name)); + const char *exists = shaderparamnames.find(name, NULL); + if(exists || !insert) return exists; + return shaderparamnames.add(newstring(name)); } void addslotparam(const char *name, float x, float y, float z, float w) { - if(name) name = getshaderparamname(name); - loopv(slotparams) - { - SlotShaderParam ¶m = slotparams[i]; - if(param.name==name) - { - param.val[0] = x; - param.val[1] = y; - param.val[2] = z; - param.val[3] = w; - return; - } - } - SlotShaderParam param = {name, -1, {x, y, z, w}}; - slotparams.add(param); + if(name) name = getshaderparamname(name); + loopv(slotparams) + { + SlotShaderParam ¶m = slotparams[i]; + if(param.name==name) + { + param.val[0] = x; + param.val[1] = y; + param.val[2] = z; + param.val[3] = w; + return; + } + } + SlotShaderParam param = {name, -1, {x, y, z, w}}; + slotparams.add(param); } ICOMMAND(setuniformparam, "sffff", (char *name, float *x, float *y, float *z, float *w), addslotparam(name, *x, *y, *z, *w)); @@ -1265,10 +1260,10 @@ ICOMMAND(defuniformparam, "sffff", (char *name, float *x, float *y, float *z, fl struct postfxtex { - GLuint id; - int scale, used; + GLuint id; + int scale, used; - postfxtex() : id(0), scale(0), used(-1) {} + postfxtex() : id(0), scale(0), used(-1) {} }; vector postfxtexs; int postfxbinds[NUMPOSTFXBINDS]; @@ -1277,246 +1272,246 @@ int postfxw = 0, postfxh = 0; struct postfxpass { - Shader *shader; - vec4 params; - uint inputs, freeinputs; - int outputbind, outputscale; + Shader *shader; + vec4 params; + uint inputs, freeinputs; + int outputbind, outputscale; - postfxpass() : shader(NULL), inputs(1), freeinputs(1), outputbind(0), outputscale(0) {} + postfxpass() : shader(NULL), inputs(1), freeinputs(1), outputbind(0), outputscale(0) {} }; vector postfxpasses; static int allocatepostfxtex(int scale) { - loopv(postfxtexs) - { - postfxtex &t = postfxtexs[i]; - if(t.scale==scale && t.used < 0) return i; - } - postfxtex &t = postfxtexs.add(); - t.scale = scale; - glGenTextures(1, &t.id); - createtexture(t.id, max(screenw>>scale, 1), max(screenh>>scale, 1), NULL, 3, 1, GL_RGB); - return postfxtexs.length()-1; + loopv(postfxtexs) + { + postfxtex &t = postfxtexs[i]; + if(t.scale==scale && t.used < 0) return i; + } + postfxtex &t = postfxtexs.add(); + t.scale = scale; + glGenTextures(1, &t.id); + createtexture(t.id, max(screenw>>scale, 1), max(screenh>>scale, 1), NULL, 3, 1, GL_RGB); + return postfxtexs.length()-1; } void cleanuppostfx(bool fullclean) { - if(fullclean && postfxfb) - { - glDeleteFramebuffers_(1, &postfxfb); - postfxfb = 0; - } + if(fullclean && postfxfb) + { + glDeleteFramebuffers_(1, &postfxfb); + postfxfb = 0; + } - loopv(postfxtexs) glDeleteTextures(1, &postfxtexs[i].id); - postfxtexs.shrink(0); + loopv(postfxtexs) glDeleteTextures(1, &postfxtexs[i].id); + postfxtexs.shrink(0); - postfxw = 0; - postfxh = 0; + postfxw = 0; + postfxh = 0; } void renderpostfx() { - if(postfxpasses.empty()) return; - - if(postfxw != screenw || postfxh != screenh) - { - cleanuppostfx(false); - postfxw = screenw; - postfxh = screenh; - } - - int binds[NUMPOSTFXBINDS]; - loopi(NUMPOSTFXBINDS) binds[i] = -1; - loopv(postfxtexs) postfxtexs[i].used = -1; - - binds[0] = allocatepostfxtex(0); - postfxtexs[binds[0]].used = 0; - glBindTexture(GL_TEXTURE_2D, postfxtexs[binds[0]].id); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, screenw, screenh); - - if(postfxpasses.length() > 1) - { - if(!postfxfb) glGenFramebuffers_(1, &postfxfb); - glBindFramebuffer_(GL_FRAMEBUFFER, postfxfb); - } - - GLOBALPARAMF(millis, lastmillis/1000.0f); - - loopv(postfxpasses) - { - postfxpass &p = postfxpasses[i]; - - int tex = -1; - if(!postfxpasses.inrange(i+1)) - { - if(postfxpasses.length() > 1) glBindFramebuffer_(GL_FRAMEBUFFER, 0); - } - else - { - tex = allocatepostfxtex(p.outputscale); - glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, postfxtexs[tex].id, 0); - } - - int w = tex >= 0 ? max(screenw>>postfxtexs[tex].scale, 1) : screenw, - h = tex >= 0 ? max(screenh>>postfxtexs[tex].scale, 1) : screenh; - glViewport(0, 0, w, h); - p.shader->set(); - LOCALPARAM(params, p.params); - int tw = w, th = h, tmu = 0; - loopj(NUMPOSTFXBINDS) if(p.inputs&(1<= 0) - { - if(!tmu) - { - tw = max(screenw>>postfxtexs[binds[j]].scale, 1); - th = max(screenh>>postfxtexs[binds[j]].scale, 1); - } - else glActiveTexture_(GL_TEXTURE0 + tmu); - glBindTexture(GL_TEXTURE_2D, postfxtexs[binds[j]].id); - ++tmu; - } - if(tmu) glActiveTexture_(GL_TEXTURE0); - LOCALPARAMF(postfxscale, 1.0f/tw, 1.0f/th); - screenquad(1, 1); - - loopj(NUMPOSTFXBINDS) if(p.freeinputs&(1<= 0) - { - postfxtexs[binds[j]].used = -1; - binds[j] = -1; - } - if(tex >= 0) - { - if(binds[p.outputbind] >= 0) postfxtexs[binds[p.outputbind]].used = -1; - binds[p.outputbind] = tex; - postfxtexs[tex].used = p.outputbind; - } - } + if(postfxpasses.empty()) return; + + if(postfxw != screenw || postfxh != screenh) + { + cleanuppostfx(false); + postfxw = screenw; + postfxh = screenh; + } + + int binds[NUMPOSTFXBINDS]; + loopi(NUMPOSTFXBINDS) binds[i] = -1; + loopv(postfxtexs) postfxtexs[i].used = -1; + + binds[0] = allocatepostfxtex(0); + postfxtexs[binds[0]].used = 0; + glBindTexture(GL_TEXTURE_2D, postfxtexs[binds[0]].id); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, screenw, screenh); + + if(postfxpasses.length() > 1) + { + if(!postfxfb) glGenFramebuffers_(1, &postfxfb); + glBindFramebuffer_(GL_FRAMEBUFFER, postfxfb); + } + + GLOBALPARAMF(millis, lastmillis/1000.0f); + + loopv(postfxpasses) + { + postfxpass &p = postfxpasses[i]; + + int tex = -1; + if(!postfxpasses.inrange(i+1)) + { + if(postfxpasses.length() > 1) glBindFramebuffer_(GL_FRAMEBUFFER, 0); + } + else + { + tex = allocatepostfxtex(p.outputscale); + glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, postfxtexs[tex].id, 0); + } + + int w = tex >= 0 ? max(screenw>>postfxtexs[tex].scale, 1) : screenw, + h = tex >= 0 ? max(screenh>>postfxtexs[tex].scale, 1) : screenh; + glViewport(0, 0, w, h); + p.shader->set(); + LOCALPARAM(params, p.params); + int tw = w, th = h, tmu = 0; + loopj(NUMPOSTFXBINDS) if(p.inputs&(1<= 0) + { + if(!tmu) + { + tw = max(screenw>>postfxtexs[binds[j]].scale, 1); + th = max(screenh>>postfxtexs[binds[j]].scale, 1); + } + else glActiveTexture_(GL_TEXTURE0 + tmu); + glBindTexture(GL_TEXTURE_2D, postfxtexs[binds[j]].id); + ++tmu; + } + if(tmu) glActiveTexture_(GL_TEXTURE0); + LOCALPARAMF(postfxscale, 1.0f/tw, 1.0f/th); + screenquad(1, 1); + + loopj(NUMPOSTFXBINDS) if(p.freeinputs&(1<= 0) + { + postfxtexs[binds[j]].used = -1; + binds[j] = -1; + } + if(tex >= 0) + { + if(binds[p.outputbind] >= 0) postfxtexs[binds[p.outputbind]].used = -1; + binds[p.outputbind] = tex; + postfxtexs[tex].used = p.outputbind; + } + } } static bool addpostfx(const char *name, int outputbind, int outputscale, uint inputs, uint freeinputs, const vec4 ¶ms) { - if(!*name) return false; - Shader *s = useshaderbyname(name); - if(!s) - { - conoutf(CON_ERROR, "no such postfx shader: %s", name); - return false; - } - postfxpass &p = postfxpasses.add(); - p.shader = s; - p.outputbind = outputbind; - p.outputscale = outputscale; - p.inputs = inputs; - p.freeinputs = freeinputs; - p.params = params; - return true; + if(!*name) return false; + Shader *s = useshaderbyname(name); + if(!s) + { + conoutf(CON_ERROR, "no such postfx shader: %s", name); + return false; + } + postfxpass &p = postfxpasses.add(); + p.shader = s; + p.outputbind = outputbind; + p.outputscale = outputscale; + p.inputs = inputs; + p.freeinputs = freeinputs; + p.params = params; + return true; } void clearpostfx() { - postfxpasses.shrink(0); - cleanuppostfx(false); + postfxpasses.shrink(0); + cleanuppostfx(false); } COMMAND(clearpostfx, ""); ICOMMAND(addpostfx, "siisffff", (char *name, int *bind, int *scale, char *inputs, float *x, float *y, float *z, float *w), { - int inputmask = inputs[0] ? 0 : 1; - int freemask = inputs[0] ? 0 : 1; - bool freeinputs = true; - for(; *inputs; inputs++) if(isdigit(*inputs)) - { - inputmask |= 1<<(*inputs-'0'); - if(freeinputs) freemask |= 1<<(*inputs-'0'); - } - else if(*inputs=='+') freeinputs = false; - else if(*inputs=='-') freeinputs = true; - inputmask &= (1<reusevs && v->reusevs->invalid()) || - (v->reuseps && v->reuseps->invalid()) || - !v->compile()) - v->cleanup(true); - } - } - if(s.forced && !s.detailshader) s.fixdetailshader(); - }); + identflags &= ~IDF_PERSIST; + loadshaders(); + identflags |= IDF_PERSIST; + linkslotshaders(); + enumerate(shaders, Shader, s, + { + if(!s.standard && !(s.type&(SHADER_DEFERRED|SHADER_INVALID)) && !s.variantshader) + { + defformatstring(info, "shader %s", s.name); + renderprogress(0.0, info); + if(!s.compile()) s.cleanup(true); + loopv(s.variants) + { + Shader *v = s.variants[i]; + if((v->reusevs && v->reusevs->invalid()) || + (v->reuseps && v->reuseps->invalid()) || + !v->compile()) + v->cleanup(true); + } + } + if(s.forced && !s.detailshader) s.fixdetailshader(); + }); } void setupblurkernel(int radius, float sigma, float *weights, float *offsets) { - if(radius<1 || radius>MAXBLURRADIUS) return; - sigma *= 2*radius; - float total = 1.0f/sigma; - weights[0] = total; - offsets[0] = 0; - // rely on bilinear filtering to sample 2 pixels at once - // transforms a*X + b*Y into (u+v)*[X*u/(u+v) + Y*(1 - u/(u+v))] - loopi(radius) - { - float weight1 = exp(-((2*i)*(2*i)) / (2*sigma*sigma)) / sigma, - weight2 = exp(-((2*i+1)*(2*i+1)) / (2*sigma*sigma)) / sigma, - scale = weight1 + weight2, - offset = 2*i+1 + weight2 / scale; - weights[i+1] = scale; - offsets[i+1] = offset; - total += 2*scale; - } - loopi(radius+1) weights[i] /= total; - for(int i = radius+1; i <= MAXBLURRADIUS; i++) weights[i] = offsets[i] = 0; + if(radius<1 || radius>MAXBLURRADIUS) return; + sigma *= 2*radius; + float total = 1.0f/sigma; + weights[0] = total; + offsets[0] = 0; + // rely on bilinear filtering to sample 2 pixels at once + // transforms a*X + b*Y into (u+v)*[X*u/(u+v) + Y*(1 - u/(u+v))] + loopi(radius) + { + float weight1 = exp(-((2*i)*(2*i)) / (2*sigma*sigma)) / sigma, + weight2 = exp(-((2*i+1)*(2*i+1)) / (2*sigma*sigma)) / sigma, + scale = weight1 + weight2, + offset = 2*i+1 + weight2 / scale; + weights[i+1] = scale; + offsets[i+1] = offset; + total += 2*scale; + } + loopi(radius+1) weights[i] /= total; + for(int i = radius+1; i <= MAXBLURRADIUS; i++) weights[i] = offsets[i] = 0; } void setblurshader(int pass, int size, int radius, float *weights, float *offsets) { - if(radius<1 || radius>MAXBLURRADIUS) return; - static Shader *blurshader[7][2] = { { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL } }; - Shader *&s = blurshader[radius-1][pass]; - if(!s) - { - defformatstring(name, "blur%c%d", 'x'+pass, radius); - s = lookupshaderbyname(name); - } - s->set(); - LOCALPARAMV(weights, weights, 8); - float scaledoffsets[8]; - loopk(8) scaledoffsets[k] = offsets[k]/size; - LOCALPARAMV(offsets, scaledoffsets, 8); + if(radius<1 || radius>MAXBLURRADIUS) return; + static Shader *blurshader[7][2] = { { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL } }; + Shader *&s = blurshader[radius-1][pass]; + if(!s) + { + defformatstring(name, "blur%c%d", 'x'+pass, radius); + s = lookupshaderbyname(name); + } + s->set(); + LOCALPARAMV(weights, weights, 8); + float scaledoffsets[8]; + loopk(8) scaledoffsets[k] = offsets[k]/size; + LOCALPARAMV(offsets, scaledoffsets, 8); } diff --git a/src/engine/shadowmap.cpp b/src/engine/shadowmap.cpp index 7b7f894..2216452 100644 --- a/src/engine/shadowmap.cpp +++ b/src/engine/shadowmap.cpp @@ -13,8 +13,8 @@ VARFP(shadowmapprecision, 0, 0, 1, cleanshadowmap()); bvec shadowmapambientcolor(0, 0, 0); HVARFR(shadowmapambient, 0, 0, 0xFFFFFF, { - if(shadowmapambient <= 255) shadowmapambient |= (shadowmapambient<<8) | (shadowmapambient<<16); - shadowmapambientcolor = bvec((shadowmapambient>>16)&0xFF, (shadowmapambient>>8)&0xFF, shadowmapambient&0xFF); + if(shadowmapambient <= 255) shadowmapambient |= (shadowmapambient<<8) | (shadowmapambient<<16); + shadowmapambientcolor = bvec((shadowmapambient>>16)&0xFF, (shadowmapambient>>8)&0xFF, shadowmapambient&0xFF); }); VARP(shadowmapintensity, 0, 40, 100); @@ -29,54 +29,54 @@ float shadowmapmaxz = 0; void setshadowdir(int angle) { - shadowdir = vec(0, SHADOWSKEW, 1); - shadowdir.rotate_around_z(angle*RAD); + shadowdir = vec(0, SHADOWSKEW, 1); + shadowdir.rotate_around_z(angle*RAD); } VARFR(shadowmapangle, 0, 0, 360, setshadowdir(shadowmapangle)); void guessshadowdir() { - if(shadowmapangle) return; - vec dir; - if(!sunlightcolor.iszero()) dir = sunlightdir; - else - { - vec lightpos(0, 0, 0), casterpos(0, 0, 0); - int numlights = 0, numcasters = 0; - const vector &ents = entities::getents(); - loopv(ents) - { - extentity &e = *ents[i]; - switch(e.type) - { - case ET_LIGHT: - if(!e.attr1) { lightpos.add(e.o); numlights++; } - break; - - case ET_MAPMODEL: - casterpos.add(e.o); - numcasters++; - break; - - default: - if(e.type &ents = entities::getents(); + loopv(ents) + { + extentity &e = *ents[i]; + switch(e.type) + { + case ET_LIGHT: + if(!e.attr1) { lightpos.add(e.o); numlights++; } + break; + + case ET_MAPMODEL: + casterpos.add(e.o); + numcasters++; + break; + + default: + if(e.typeyaw*RAD); - - vec dir; - vecfromyawpitch(camera1->yaw, camera1->pitch, 1, 0, dir); - dir.z = 0; - dir.mul(shadowmapradius); - - vec dirx, diry; - vecfromyawpitch(camera1->yaw, 0, 0, 1, dirx); - vecfromyawpitch(camera1->yaw, 0, 1, 0, diry); - shadowoffset.x = -fmod(dirx.dot(camera1->o) - skewdir.x*camera1->o.z, 2.0f*shadowmapradius/vieww); - shadowoffset.y = -fmod(diry.dot(camera1->o) - skewdir.y*camera1->o.z, 2.0f*shadowmapradius/viewh); - - shadowmatrix.ortho(-shadowmapradius, shadowmapradius, -shadowmapradius, shadowmapradius, -shadowmapdist, shadowmapdist); - shadowmatrix.mul(matrix3(vec(1, 0, 0), vec(0, 1, 0), vec(skewdir.x, skewdir.y, 1))); - shadowmatrix.translate(skewdir.x*shadowmapheight + shadowoffset.x, skewdir.y*shadowmapheight + shadowoffset.y + dir.magnitude(), -shadowmapheight); - shadowmatrix.rotate_around_z((camera1->yaw+180)*-RAD); - shadowmatrix.translate(vec(camera1->o).neg()); - GLOBALPARAM(shadowmatrix, shadowmatrix); - - shadowfocus = camera1->o; - shadowfocus.add(dir); - shadowfocus.add(vec(shadowdir).mul(shadowmapheight)); - shadowfocus.add(dirx.mul(shadowoffset.x)); - shadowfocus.add(diry.mul(shadowoffset.y)); - - gle::colorf(0, 0, 0); - - GLOBALPARAMF(shadowmapbias, -shadowmapbias/float(shadowmapdist), 1 - (shadowmapbias + (smoothshadowmappeel ? 0 : shadowmappeelbias))/float(shadowmapdist)); - - shadowmapcasters = 0; - shadowmapmaxz = shadowfocus.z - shadowmapdist; - shadowmapping = true; - rendergame(); - shadowmapping = false; - shadowmapmaxz = min(shadowmapmaxz, shadowfocus.z); - - if(shadowmapcasters && smdepthpeel) - { - int sx, sy, sw, sh; - bool scissoring = rtscissor && scissorblur(sx, sy, sw, sh) && sw > 0 && sh > 0; - if(scissoring) glScissor(sx, sy, sw, sh); - if(!rtscissor || scissoring) rendershadowmapreceivers(); - } - - return shadowmapcasters>0; - } + const GLenum *colorformats() const + { + static const GLenum rgbafmts[] = { GL_RGBA16F, GL_RGBA16, GL_RGBA, GL_RGBA8, GL_FALSE }; + return &rgbafmts[fpshadowmap && hasTF ? 0 : (shadowmapprecision ? 1 : 2)]; + } + + bool swaptexs() const { return true; } + + bool scissorblur(int &x, int &y, int &w, int &h) + { + x = max(int(floor((scissorx1+1)/2*vieww)) - 2*blursize, 2); + y = max(int(floor((scissory1+1)/2*viewh)) - 2*blursize, 2); + w = min(int(ceil((scissorx2+1)/2*vieww)) + 2*blursize, vieww-2) - x; + h = min(int(ceil((scissory2+1)/2*viewh)) + 2*blursize, viewh-2) - y; + return true; + } + + bool scissorrender(int &x, int &y, int &w, int &h) + { + x = y = 2; + w = vieww - 2*2; + h = viewh - 2*2; + return true; + } + + void doclear() + { + glClearColor(0, 0, 0, 0); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + } + + bool dorender() + { + vec skewdir(shadowdir); + skewdir.rotate_around_z(-camera1->yaw*RAD); + + vec dir; + vecfromyawpitch(camera1->yaw, camera1->pitch, 1, 0, dir); + dir.z = 0; + dir.mul(shadowmapradius); + + vec dirx, diry; + vecfromyawpitch(camera1->yaw, 0, 0, 1, dirx); + vecfromyawpitch(camera1->yaw, 0, 1, 0, diry); + shadowoffset.x = -fmod(dirx.dot(camera1->o) - skewdir.x*camera1->o.z, 2.0f*shadowmapradius/vieww); + shadowoffset.y = -fmod(diry.dot(camera1->o) - skewdir.y*camera1->o.z, 2.0f*shadowmapradius/viewh); + + shadowmatrix.ortho(-shadowmapradius, shadowmapradius, -shadowmapradius, shadowmapradius, -shadowmapdist, shadowmapdist); + shadowmatrix.mul(matrix3(vec(1, 0, 0), vec(0, 1, 0), vec(skewdir.x, skewdir.y, 1))); + shadowmatrix.translate(skewdir.x*shadowmapheight + shadowoffset.x, skewdir.y*shadowmapheight + shadowoffset.y + dir.magnitude(), -shadowmapheight); + shadowmatrix.rotate_around_z((camera1->yaw+180)*-RAD); + shadowmatrix.translate(vec(camera1->o).neg()); + GLOBALPARAM(shadowmatrix, shadowmatrix); + + shadowfocus = camera1->o; + shadowfocus.add(dir); + shadowfocus.add(vec(shadowdir).mul(shadowmapheight)); + shadowfocus.add(dirx.mul(shadowoffset.x)); + shadowfocus.add(diry.mul(shadowoffset.y)); + + gle::colorf(0, 0, 0); + + GLOBALPARAMF(shadowmapbias, -shadowmapbias/float(shadowmapdist), 1 - (shadowmapbias + (smoothshadowmappeel ? 0 : shadowmappeelbias))/float(shadowmapdist)); + + shadowmapcasters = 0; + shadowmapmaxz = shadowfocus.z - shadowmapdist; + shadowmapping = true; + rendergame(); + shadowmapping = false; + shadowmapmaxz = min(shadowmapmaxz, shadowfocus.z); + + if(shadowmapcasters && smdepthpeel) + { + int sx, sy, sw, sh; + bool scissoring = rtscissor && scissorblur(sx, sy, sw, sh) && sw > 0 && sh > 0; + if(scissoring) glScissor(sx, sy, sw, sh); + if(!rtscissor || scissoring) rendershadowmapreceivers(); + } + + return shadowmapcasters>0; + } } shadowmaptex; void cleanshadowmap() { - shadowmaptex.cleanup(true); + shadowmaptex.cleanup(true); } void calcshadowmapbb(const vec &o, float xyrad, float zrad, float &x1, float &y1, float &x2, float &y2) { - vec skewdir(shadowdir); - skewdir.rotate_around_z(-camera1->yaw*RAD); - - vec ro(o); - ro.sub(camera1->o); - ro.rotate_around_z(-(camera1->yaw+180)*RAD); - ro.x += ro.z * skewdir.x + shadowoffset.x; - ro.y += ro.z * skewdir.y + shadowmapradius * cosf(camera1->pitch*RAD) + shadowoffset.y; - - vec high(ro), low(ro); - high.x += zrad * skewdir.x; - high.y += zrad * skewdir.y; - low.x -= zrad * skewdir.x; - low.y -= zrad * skewdir.y; - - x1 = (min(high.x, low.x) - xyrad) / shadowmapradius; - y1 = (min(high.y, low.y) - xyrad) / shadowmapradius; - x2 = (max(high.x, low.x) + xyrad) / shadowmapradius; - y2 = (max(high.y, low.y) + xyrad) / shadowmapradius; + vec skewdir(shadowdir); + skewdir.rotate_around_z(-camera1->yaw*RAD); + + vec ro(o); + ro.sub(camera1->o); + ro.rotate_around_z(-(camera1->yaw+180)*RAD); + ro.x += ro.z * skewdir.x + shadowoffset.x; + ro.y += ro.z * skewdir.y + shadowmapradius * cosf(camera1->pitch*RAD) + shadowoffset.y; + + vec high(ro), low(ro); + high.x += zrad * skewdir.x; + high.y += zrad * skewdir.y; + low.x -= zrad * skewdir.x; + low.y -= zrad * skewdir.y; + + x1 = (min(high.x, low.x) - xyrad) / shadowmapradius; + y1 = (min(high.y, low.y) - xyrad) / shadowmapradius; + x2 = (max(high.x, low.x) + xyrad) / shadowmapradius; + y2 = (max(high.y, low.y) + xyrad) / shadowmapradius; } bool addshadowmapcaster(const vec &o, float xyrad, float zrad) { - if(o.z + zrad <= shadowfocus.z - shadowmapdist || o.z - zrad >= shadowfocus.z) return false; + if(o.z + zrad <= shadowfocus.z - shadowmapdist || o.z - zrad >= shadowfocus.z) return false; - shadowmapmaxz = max(shadowmapmaxz, o.z + zrad); + shadowmapmaxz = max(shadowmapmaxz, o.z + zrad); - float x1, y1, x2, y2; - calcshadowmapbb(o, xyrad, zrad, x1, y1, x2, y2); + float x1, y1, x2, y2; + calcshadowmapbb(o, xyrad, zrad, x1, y1, x2, y2); - if(!shadowmaptex.addblurtiles(x1, y1, x2, y2, 2)) return false; + if(!shadowmaptex.addblurtiles(x1, y1, x2, y2, 2)) return false; - shadowmapcasters++; - return true; + shadowmapcasters++; + return true; } bool isshadowmapreceiver(vtxarray *va) { - if(!shadowmap || !shadowmapcasters) return false; + if(!shadowmap || !shadowmapcasters) return false; - if(va->shadowmapmax.z <= shadowfocus.z - shadowmapdist || va->shadowmapmin.z >= shadowmapmaxz) return false; + if(va->shadowmapmax.z <= shadowfocus.z - shadowmapdist || va->shadowmapmin.z >= shadowmapmaxz) return false; - float xyrad = SQRT2*0.5f*max(va->shadowmapmax.x-va->shadowmapmin.x, va->shadowmapmax.y-va->shadowmapmin.y), - zrad = 0.5f*(va->shadowmapmax.z-va->shadowmapmin.z), - x1, y1, x2, y2; - if(xyrad<0 || zrad<0) return false; + float xyrad = SQRT2*0.5f*max(va->shadowmapmax.x-va->shadowmapmin.x, va->shadowmapmax.y-va->shadowmapmin.y), + zrad = 0.5f*(va->shadowmapmax.z-va->shadowmapmin.z), + x1, y1, x2, y2; + if(xyrad<0 || zrad<0) return false; - vec center = vec(va->shadowmapmin).add(vec(va->shadowmapmax)).mul(0.5f); - calcshadowmapbb(center, xyrad, zrad, x1, y1, x2, y2); + vec center = vec(va->shadowmapmin).add(vec(va->shadowmapmax)).mul(0.5f); + calcshadowmapbb(center, xyrad, zrad, x1, y1, x2, y2); - return shadowmaptex.checkblurtiles(x1, y1, x2, y2, 2); + return shadowmaptex.checkblurtiles(x1, y1, x2, y2, 2); #if 0 - // cheaper inexact test - float dz = va->o.z + va->size/2 - shadowfocus.z; - float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y; - float skew = va->size/2*SHADOWSKEW; - if(!shadowmap || !shadowmaptex || - va->o.z + va->size <= shadowfocus.z - shadowmapdist || va->o.z >= shadowmapmaxz || - va->o.x + va->size <= cx - shadowmapradius-skew || va->o.x >= cx + shadowmapradius+skew || - va->o.y + va->size <= cy - shadowmapradius-skew || va->o.y >= cy + shadowmapradius+skew) - return false; - return true; + // cheaper inexact test + float dz = va->o.z + va->size/2 - shadowfocus.z; + float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y; + float skew = va->size/2*SHADOWSKEW; + if(!shadowmap || !shadowmaptex || + va->o.z + va->size <= shadowfocus.z - shadowmapdist || va->o.z >= shadowmapmaxz || + va->o.x + va->size <= cx - shadowmapradius-skew || va->o.x >= cx + shadowmapradius+skew || + va->o.y + va->size <= cy - shadowmapradius-skew || va->o.y >= cy + shadowmapradius+skew) + return false; + return true; #endif } bool isshadowmapcaster(const vec &o, float rad) { - // cheaper inexact test - float dz = o.z - shadowfocus.z; - float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y; - float skew = rad*SHADOWSKEW; - if(!shadowmapping || - o.z + rad <= shadowfocus.z - shadowmapdist || o.z - rad >= shadowfocus.z || - o.x + rad <= cx - shadowmapradius-skew || o.x - rad >= cx + shadowmapradius+skew || - o.y + rad <= cy - shadowmapradius-skew || o.y - rad >= cy + shadowmapradius+skew) - return false; - return true; + // cheaper inexact test + float dz = o.z - shadowfocus.z; + float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y; + float skew = rad*SHADOWSKEW; + if(!shadowmapping || + o.z + rad <= shadowfocus.z - shadowmapdist || o.z - rad >= shadowfocus.z || + o.x + rad <= cx - shadowmapradius-skew || o.x - rad >= cx + shadowmapradius+skew || + o.y + rad <= cy - shadowmapradius-skew || o.y - rad >= cy + shadowmapradius+skew) + return false; + return true; } void pushshadowmap() { - if(!shadowmap || !shadowmaptex.rendertex) return; + if(!shadowmap || !shadowmaptex.rendertex) return; - glActiveTexture_(GL_TEXTURE7); - glBindTexture(GL_TEXTURE_2D, shadowmaptex.rendertex); + glActiveTexture_(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, shadowmaptex.rendertex); - matrix4 m = shadowmatrix; - m.projective(-1, 1-shadowmapbias/float(shadowmapdist)); - GLOBALPARAM(shadowmapproject, m); + matrix4 m = shadowmatrix; + m.projective(-1, 1-shadowmapbias/float(shadowmapdist)); + GLOBALPARAM(shadowmapproject, m); - glActiveTexture_(GL_TEXTURE0); + glActiveTexture_(GL_TEXTURE0); - float r, g, b; + float r, g, b; if(!shadowmapambient) { if(skylightcolor[0] || skylightcolor[1] || skylightcolor[2]) @@ -283,24 +283,24 @@ void pushshadowmap() b = max(25.0f, 0.4f*ambientcolor[2] + 0.6f*max(ambientcolor[2], skylightcolor[2])); } else - { - r = max(25.0f, 2.0f*ambientcolor[0]); - g = max(25.0f, 2.0f*ambientcolor[1]); - b = max(25.0f, 2.0f*ambientcolor[2]); - } - } - else { r = shadowmapambientcolor[0]; g = shadowmapambientcolor[1]; b = shadowmapambientcolor[2]; } - GLOBALPARAMF(shadowmapambient, r/255.0f, g/255.0f, b/255.0f); + { + r = max(25.0f, 2.0f*ambientcolor[0]); + g = max(25.0f, 2.0f*ambientcolor[1]); + b = max(25.0f, 2.0f*ambientcolor[2]); + } + } + else { r = shadowmapambientcolor[0]; g = shadowmapambientcolor[1]; b = shadowmapambientcolor[2]; } + GLOBALPARAMF(shadowmapambient, r/255.0f, g/255.0f, b/255.0f); } void popshadowmap() { - if(!shadowmap || !shadowmaptex.rendertex) return; + if(!shadowmap || !shadowmaptex.rendertex) return; } void rendershadowmap() { - if(!shadowmap) return; + if(!shadowmap) return; - shadowmaptex.render(1< weights[k]) - { - for(int l = min(sorted-1, 2); l >= k; l--) - { - weights[l+1] = weights[l]; - bones[l+1] = bones[l]; - } - weights[k] = weight; - bones[k] = bone; - return sorted<4 ? sorted+1 : sorted; - } - if(sorted>=4) return sorted; - weights[sorted] = weight; - bones[sorted] = bone; - return sorted+1; - } - - void finalize(int sorted) - { - loopj(4-sorted) { weights[sorted+j] = 0; bones[sorted+j] = 0; } - if(sorted <= 0) return; - float total = 0; - loopj(sorted) total += weights[j]; - total = 1.0f/total; - loopj(sorted) weights[j] *= total; - } - - void serialize(vvertw &v) - { - if(interpindex >= 0) - { - v.weights[0] = 255; - loopk(3) v.weights[k+1] = 0; - v.bones[0] = 2*interpindex; - loopk(3) v.bones[k+1] = v.bones[0]; - } - else - { - int total = 0; - loopk(4) total += (v.weights[k] = uchar(0.5f + weights[k]*255)); - while(total > 255) - { - loopk(4) if(v.weights[k] > 0 && total > 255) { v.weights[k]--; total--; } - } - while(total < 255) - { - loopk(4) if(v.weights[k] < 255 && total < 255) { v.weights[k]++; total++; } - } - loopk(4) v.bones[k] = 2*interpbones[k]; - } - } - }; - - - struct animcacheentry - { - animstate as[MAXANIMPARTS]; - float pitch; - int millis; - uchar *partmask; - ragdolldata *ragdoll; - - animcacheentry() : ragdoll(NULL) - { - loopk(MAXANIMPARTS) as[k].cur.fr1 = as[k].prev.fr1 = -1; - } - - bool operator==(const animcacheentry &c) const - { - loopi(MAXANIMPARTS) if(as[i]!=c.as[i]) return false; - return pitch==c.pitch && partmask==c.partmask && ragdoll==c.ragdoll && (!ragdoll || min(millis, c.millis) >= ragdoll->lastmove); - } - }; - - struct vbocacheentry : animcacheentry - { - GLuint vbuf; - int owner; - - vbocacheentry() : vbuf(0), owner(-1) {} - }; - - struct skelcacheentry : animcacheentry - { - dualquat *bdata; - int version; - bool dirty; - - skelcacheentry() : bdata(NULL), version(-1), dirty(false) {} - - void nextversion() - { - version = Shader::uniformlocversion(); - dirty = true; - } - }; - - struct blendcacheentry : skelcacheentry - { - int owner; - - blendcacheentry() : owner(-1) {} - }; - - struct skelmeshgroup; - - struct skelmesh : mesh - { - vert *verts; - bumpvert *bumpverts; - tri *tris; - int numverts, numtris, maxweights; - - int voffset, eoffset, elen; - ushort minvert, maxvert; - - skelmesh() : verts(NULL), bumpverts(NULL), tris(NULL), numverts(0), numtris(0), maxweights(0) - { - } - - virtual ~skelmesh() - { - DELETEA(verts); - DELETEA(bumpverts); - DELETEA(tris); - } - - int addblendcombo(const blendcombo &c) - { - maxweights = max(maxweights, c.size()); - return ((skelmeshgroup *)group)->addblendcombo(c); - } - - void smoothnorms(float limit = 0, bool areaweight = true) - { - mesh::smoothnorms(verts, numverts, tris, numtris, limit, areaweight); - } - - void buildnorms(bool areaweight = true) - { - mesh::buildnorms(verts, numverts, tris, numtris, areaweight); - } - - void calctangents(bool areaweight = true) - { - if(bumpverts) return; - bumpverts = new bumpvert[numverts]; - mesh::calctangents(bumpverts, verts, verts, numverts, tris, numtris, areaweight); - } - - void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) - { - loopj(numverts) - { - vec v = m.transform(verts[j].pos); - loopi(3) - { - bbmin[i] = min(bbmin[i], v[i]); - bbmax[i] = max(bbmax[i], v[i]); - } - } - } - - void genBIH(BIH::mesh &m) - { - m.tris = (const BIH::tri *)tris; - m.numtris = numtris; - m.pos = (const uchar *)&verts->pos; - m.posstride = sizeof(vert); - m.tc = (const uchar *)&verts->tc; - m.tcstride = sizeof(vert); - } - - static inline void assignvert(vvertn &vv, int j, vert &v, blendcombo &c) - { - vv.pos = v.pos; - vv.norm = v.norm; - vv.tc = v.tc; - } - - inline void assignvert(vvertbump &vv, int j, vert &v, blendcombo &c) - { - vv.pos = v.pos; - vv.tc = v.tc; - vv.tangent = bumpverts[j].tangent; - } - - static inline void assignvert(vvertnw &vv, int j, vert &v, blendcombo &c) - { - vv.pos = v.pos; - vv.norm = v.norm; - vv.tc = v.tc; - c.serialize(vv); - } - - inline void assignvert(vvertbumpw &vv, int j, vert &v, blendcombo &c) - { - vv.pos = v.pos; - vv.tc = v.tc; - vv.tangent = bumpverts[j].tangent; - c.serialize(vv); - } - - template - int genvbo(vector &idxs, int offset, vector &vverts) - { - voffset = offset; - eoffset = idxs.length(); - loopi(numverts) - { - vert &v = verts[i]; - assignvert(vverts.add(), i, v, ((skelmeshgroup *)group)->blendcombos[v.blend]); - } - loopi(numtris) loopj(3) idxs.add(voffset + tris[i].vert[j]); - elen = idxs.length()-eoffset; - minvert = voffset; - maxvert = voffset + numverts-1; - return numverts; - } - - template - int genvbo(vector &idxs, int offset, vector &vverts, int *htdata, int htlen) - { - voffset = offset; - eoffset = idxs.length(); - minvert = 0xFFFF; - loopi(numtris) - { - tri &t = tris[i]; - loopj(3) - { - int index = t.vert[j]; - vert &v = verts[index]; - T vv; - assignvert(vv, index, v, ((skelmeshgroup *)group)->blendcombos[v.blend]); - int htidx = hthash(v.pos)&(htlen-1); - loopk(htlen) - { - int &vidx = htdata[(htidx+k)&(htlen-1)]; - if(vidx < 0) { vidx = idxs.add(ushort(vverts.length())); vverts.add(vv); break; } - else if(!memcmp(&vverts[vidx], &vv, sizeof(vv))) { minvert = min(minvert, idxs.add(ushort(vidx))); break; } - } - } - } - elen = idxs.length()-eoffset; - minvert = min(minvert, ushort(voffset)); - maxvert = max(minvert, ushort(vverts.length()-1)); - return vverts.length()-voffset; - } - - int genvbo(vector &idxs, int offset) - { - loopi(numverts) verts[i].interpindex = ((skelmeshgroup *)group)->remapblend(verts[i].blend); - - voffset = offset; - eoffset = idxs.length(); - loopi(numtris) - { - tri &t = tris[i]; - loopj(3) idxs.add(voffset+t.vert[j]); - } - minvert = voffset; - maxvert = voffset + numverts-1; - elen = idxs.length()-eoffset; - return numverts; - } - - template - static inline void fillvert(T &vv, int j, vert &v) - { - vv.tc = v.tc; - } - - template - void fillverts(T *vdata) - { - vdata += voffset; - loopi(numverts) fillvert(vdata[i], i, verts[i]); - } - - void interpverts(const dualquat * RESTRICT bdata1, const dualquat * RESTRICT bdata2, bool tangents, void * RESTRICT vdata, skin &s) - { - const int blendoffset = ((skelmeshgroup *)group)->skel->numgpubones; - bdata2 -= blendoffset; - - #define IPLOOP(type, dosetup, dotransform) \ - loopi(numverts) \ - { \ - const vert &src = verts[i]; \ - type &dst = ((type * RESTRICT)vdata)[i]; \ - dosetup; \ - const dualquat &b = (src.interpindex < blendoffset ? bdata1 : bdata2)[src.interpindex]; \ - dst.pos = b.transform(src.pos); \ - dotransform; \ - } - - if(tangents) - { - IPLOOP(vvertbump, bumpvert &bsrc = bumpverts[i], - { - quat q = b.transform(bsrc.tangent); - fixqtangent(q, bsrc.tangent.w); - dst.tangent = q; - }); - } - else - { - IPLOOP(vvertn, , - { - dst.norm = b.transformnormal(src.norm); - }); - } - - #undef IPLOOP - } - - void setshader(Shader *s) - { - skelmeshgroup *g = (skelmeshgroup *)group; - if(glaring) - { - if(!g->skel->usegpuskel) s->setvariant(0, 1); - else s->setvariant(min(maxweights, g->vweights), 1); - } - else if(!g->skel->usegpuskel) s->set(); - else s->setvariant(min(maxweights, g->vweights)-1, 0); - } - - void render(const animstate *as, skin &s, vbocacheentry &vc) - { - if(!Shader::lastshader) return; - glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((skelmeshgroup *)group)->edata[eoffset]); - glde++; - xtravertsva += numverts; - } - }; - - - struct tag - { - char *name; - int bone; - matrix4x3 matrix; - - tag() : name(NULL) {} - ~tag() { DELETEA(name); } - }; - - struct skelanimspec - { - char *name; - int frame, range; - - skelanimspec() : name(NULL), frame(0), range(0) {} - ~skelanimspec() - { - DELETEA(name); - } - }; - - struct boneinfo - { - const char *name; - int parent, children, next, group, scheduled, interpindex, interpparent, ragdollindex, correctindex; - float pitchscale, pitchoffset, pitchmin, pitchmax; - dualquat base, invbase; - - boneinfo() : name(NULL), parent(-1), children(-1), next(-1), group(INT_MAX), scheduled(-1), interpindex(-1), interpparent(-1), ragdollindex(-1), correctindex(-1), pitchscale(0), pitchoffset(0), pitchmin(0), pitchmax(0) {} - ~boneinfo() - { - DELETEA(name); - } - }; - - struct antipode - { - int parent, child; - - antipode(int parent, int child) : parent(parent), child(child) {} - }; - - struct pitchdep - { - int bone, parent; - dualquat pose; - }; - - struct pitchtarget - { - int bone, frame, corrects, deps; - float pitchmin, pitchmax, deviated; - dualquat pose; - }; - - struct pitchcorrect - { - int bone, target, parent; - float pitchmin, pitchmax, pitchscale, pitchangle, pitchtotal; - - pitchcorrect() : parent(-1), pitchangle(0), pitchtotal(0) {} - }; - - struct skeleton - { - char *name; - int shared; - vector users; - boneinfo *bones; - int numbones, numinterpbones, numgpubones, numframes; - dualquat *framebones; - vector skelanims; - vector tags; - vector antipodes; - ragdollskel *ragdoll; - vector pitchdeps; - vector pitchtargets; - vector pitchcorrects; - - bool usegpuskel; - vector skelcache; - hashtable blendoffsets; - - skeleton() : name(NULL), shared(0), bones(NULL), numbones(0), numinterpbones(0), numgpubones(0), numframes(0), framebones(NULL), ragdoll(NULL), usegpuskel(false), blendoffsets(32) - { - } - - ~skeleton() - { - DELETEA(name); - DELETEA(bones); - DELETEA(framebones); - DELETEP(ragdoll); - loopv(skelcache) - { - DELETEA(skelcache[i].bdata); - } - } - - skelanimspec *findskelanim(const char *name, char sep = '\0') - { - int len = sep ? strlen(name) : 0; - loopv(skelanims) - { - if(skelanims[i].name) - { - if(sep) - { - const char *end = strchr(skelanims[i].name, ':'); - if(end && end - skelanims[i].name == len && !memcmp(name, skelanims[i].name, len)) return &skelanims[i]; - } - if(!strcmp(name, skelanims[i].name)) return &skelanims[i]; - } - } - return NULL; - } - - skelanimspec &addskelanim(const char *name) - { - skelanimspec &sa = skelanims.add(); - sa.name = name ? newstring(name) : NULL; - return sa; - } - - int findbone(const char *name) - { - loopi(numbones) if(bones[i].name && !strcmp(bones[i].name, name)) return i; - return -1; - } - - int findtag(const char *name) - { - loopv(tags) if(!strcmp(tags[i].name, name)) return i; - return -1; - } - - bool addtag(const char *name, int bone, const matrix4x3 &matrix) - { - int idx = findtag(name); - if(idx >= 0) - { - if(!testtags) return false; - tag &t = tags[idx]; - t.bone = bone; - t.matrix = matrix; - } - else - { - tag &t = tags.add(); - t.name = newstring(name); - t.bone = bone; - t.matrix = matrix; - } - return true; - } - - void calcantipodes() - { - antipodes.shrink(0); - vector schedule; - loopi(numbones) - { - if(bones[i].group >= numbones) - { - bones[i].scheduled = schedule.length(); - schedule.add(i); - } - else bones[i].scheduled = -1; - } - loopv(schedule) - { - int bone = schedule[i]; - const boneinfo &info = bones[bone]; - loopj(numbones) if(abs(bones[j].group) == bone && bones[j].scheduled < 0) - { - antipodes.add(antipode(info.interpindex, bones[j].interpindex)); - bones[j].scheduled = schedule.length(); - schedule.add(j); - } - if(i + 1 == schedule.length()) - { - int conflict = INT_MAX; - loopj(numbones) if(bones[j].group < numbones && bones[j].scheduled < 0) conflict = min(conflict, abs(bones[j].group)); - if(conflict < numbones) - { - bones[conflict].scheduled = schedule.length(); - schedule.add(conflict); - } - } - } - } - - void remapbones() - { - loopi(numbones) - { - boneinfo &info = bones[i]; - info.interpindex = -1; - info.ragdollindex = -1; - } - numgpubones = 0; - loopv(users) - { - skelmeshgroup *group = users[i]; - loopvj(group->blendcombos) - { - blendcombo &c = group->blendcombos[j]; - loopk(4) - { - if(!c.weights[k]) { c.interpbones[k] = k > 0 ? c.interpbones[k-1] : 0; continue; } - boneinfo &info = bones[c.bones[k]]; - if(info.interpindex < 0) info.interpindex = numgpubones++; - c.interpbones[k] = info.interpindex; - if(info.group < 0) continue; - loopl(4) - { - if(!c.weights[l]) break; - if(l == k) continue; - int parent = c.bones[l]; - if(info.parent == parent || (info.parent >= 0 && info.parent == bones[parent].parent)) { info.group = -info.parent; break; } - if(info.group <= parent) continue; - int child = c.bones[k]; - while(parent > child) parent = bones[parent].parent; - if(parent != child) info.group = c.bones[l]; - } - } - } - } - numinterpbones = numgpubones; - loopv(tags) - { - boneinfo &info = bones[tags[i].bone]; - if(info.interpindex < 0) info.interpindex = numinterpbones++; - } - if(ragdoll) - { - loopv(ragdoll->joints) - { - boneinfo &info = bones[ragdoll->joints[i].bone]; - if(info.interpindex < 0) info.interpindex = numinterpbones++; - info.ragdollindex = i; - } - } - loopi(numbones) - { - boneinfo &info = bones[i]; - if(info.interpindex < 0) continue; - for(int parent = info.parent; parent >= 0 && bones[parent].interpindex < 0; parent = bones[parent].parent) - bones[parent].interpindex = numinterpbones++; - } - loopi(numbones) - { - boneinfo &info = bones[i]; - if(info.interpindex < 0) continue; - info.interpparent = info.parent >= 0 ? bones[info.parent].interpindex : -1; - } - if(ragdoll) - { - loopi(numbones) - { - boneinfo &info = bones[i]; - if(info.interpindex < 0 || info.ragdollindex >= 0) continue; - for(int parent = info.parent; parent >= 0; parent = bones[parent].parent) - { - if(bones[parent].ragdollindex >= 0) { ragdoll->addreljoint(i, bones[parent].ragdollindex); break; } - } - } - } - calcantipodes(); - } - - - void addpitchdep(int bone, int frame) - { - for(; bone >= 0; bone = bones[bone].parent) - { - int pos = pitchdeps.length(); - loopvj(pitchdeps) if(bone <= pitchdeps[j].bone) - { - if(bone == pitchdeps[j].bone) goto nextbone; - pos = j; - break; - } - { - pitchdep d; - d.bone = bone; - d.parent = -1; - d.pose = framebones[frame*numbones + bone]; - pitchdeps.insert(pos, d); - } - nextbone:; - } - } - - int findpitchdep(int bone) - { - loopv(pitchdeps) if(bone <= pitchdeps[i].bone) return bone == pitchdeps[i].bone ? i : -1; - return -1; - } - - int findpitchcorrect(int bone) - { - loopv(pitchcorrects) if(bone <= pitchcorrects[i].bone) return bone == pitchcorrects[i].bone ? i : -1; - return -1; - } - - void initpitchdeps() - { - pitchdeps.setsize(0); - if(pitchtargets.empty()) return; - loopv(pitchtargets) - { - pitchtarget &t = pitchtargets[i]; - t.deps = -1; - addpitchdep(t.bone, t.frame); - } - loopv(pitchdeps) - { - pitchdep &d = pitchdeps[i]; - int parent = bones[d.bone].parent; - if(parent >= 0) - { - int j = findpitchdep(parent); - if(j >= 0) - { - d.parent = j; - d.pose.mul(pitchdeps[j].pose, dualquat(d.pose)); - } - } - } - loopv(pitchtargets) - { - pitchtarget &t = pitchtargets[i]; - int j = findpitchdep(t.bone); - if(j >= 0) - { - t.deps = j; - t.pose = pitchdeps[j].pose; - } - t.corrects = -1; - for(int parent = t.bone; parent >= 0; parent = bones[parent].parent) - { - t.corrects = findpitchcorrect(parent); - if(t.corrects >= 0) break; - } - } - loopv(pitchcorrects) - { - pitchcorrect &c = pitchcorrects[i]; - bones[c.bone].correctindex = i; - c.parent = -1; - for(int parent = c.bone;;) - { - parent = bones[parent].parent; - if(parent < 0) break; - c.parent = findpitchcorrect(parent); - if(c.parent >= 0) break; - } - } - } - - void optimize() - { - cleanup(); - if(ragdoll) ragdoll->setup(); - remapbones(); - initpitchdeps(); - } - - void expandbonemask(uchar *expansion, int bone, int val) - { - expansion[bone] = val; - bone = bones[bone].children; - while(bone>=0) { expandbonemask(expansion, bone, val); bone = bones[bone].next; } - } - - void applybonemask(ushort *mask, uchar *partmask, int partindex) - { - if(!mask || *mask==BONEMASK_END) return; - uchar *expansion = new uchar[numbones]; - memset(expansion, *mask&BONEMASK_NOT ? 1 : 0, numbones); - while(*mask!=BONEMASK_END) - { - expandbonemask(expansion, *mask&BONEMASK_BONE, *mask&BONEMASK_NOT ? 0 : 1); - mask++; - } - loopi(numbones) if(expansion[i]) partmask[i] = partindex; - delete[] expansion; - } - - void linkchildren() - { - loopi(numbones) - { - boneinfo &b = bones[i]; - b.children = -1; - if(b.parent<0) b.next = -1; - else - { - b.next = bones[b.parent].children; - bones[b.parent].children = i; - } - } - } - - int availgpubones() const { return min(maxvsuniforms - reservevpparams - 10, maxskelanimdata) / 2; } - bool gpuaccelerate() const { return numframes && gpuskel && numgpubones<=availgpubones(); } - - float calcdeviation(const vec &axis, const vec &forward, const dualquat &pose1, const dualquat &pose2) - { - vec forward1 = pose1.transformnormal(forward).project(axis).normalize(), - forward2 = pose2.transformnormal(forward).project(axis).normalize(), - daxis = vec().cross(forward1, forward2); - float dx = clamp(forward1.dot(forward2), -1.0f, 1.0f), dy = clamp(daxis.magnitude(), -1.0f, 1.0f); - if(daxis.dot(axis) < 0) dy = -dy; - return atan2f(dy, dx)/RAD; - } - - void calcpitchcorrects(float pitch, const vec &axis, const vec &forward) - { - loopv(pitchtargets) - { - pitchtarget &t = pitchtargets[i]; - t.deviated = calcdeviation(axis, forward, t.pose, pitchdeps[t.deps].pose); - } - loopv(pitchcorrects) - { - pitchcorrect &c = pitchcorrects[i]; - c.pitchangle = c.pitchtotal = 0; - } - loopvj(pitchtargets) - { - pitchtarget &t = pitchtargets[j]; - float tpitch = pitch - t.deviated; - for(int parent = t.corrects; parent >= 0; parent = pitchcorrects[parent].parent) - tpitch -= pitchcorrects[parent].pitchangle; - if(t.pitchmin || t.pitchmax) tpitch = clamp(tpitch, t.pitchmin, t.pitchmax); - loopv(pitchcorrects) - { - pitchcorrect &c = pitchcorrects[i]; - if(c.target != j) continue; - float total = c.parent >= 0 ? pitchcorrects[c.parent].pitchtotal : 0, - avail = tpitch - total, - used = tpitch*c.pitchscale; - if(c.pitchmin || c.pitchmax) - { - if(used < 0) used = clamp(c.pitchmin, used, 0.0f); - else used = clamp(c.pitchmax, 0.0f, used); - } - if(used < 0) used = clamp(avail, used, 0.0f); - else used = clamp(avail, 0.0f, used); - c.pitchangle = used; - c.pitchtotal = used + total; - } - } - } - - #define INTERPBONE(bone) \ - const animstate &s = as[partmask[bone]]; \ - const framedata &f = partframes[partmask[bone]]; \ - dualquat d; \ - (d = f.fr1[bone]).mul((1-s.cur.t)*s.interp); \ - d.accumulate(f.fr2[bone], s.cur.t*s.interp); \ - if(s.interp<1) \ - { \ - d.accumulate(f.pfr1[bone], (1-s.prev.t)*(1-s.interp)); \ - d.accumulate(f.pfr2[bone], s.prev.t*(1-s.interp)); \ - } - - void interpbones(const animstate *as, float pitch, const vec &axis, const vec &forward, int numanimparts, const uchar *partmask, skelcacheentry &sc) - { - if(!sc.bdata) sc.bdata = new dualquat[numinterpbones]; - sc.nextversion(); - struct framedata - { - const dualquat *fr1, *fr2, *pfr1, *pfr2; - } partframes[MAXANIMPARTS]; - loopi(numanimparts) - { - partframes[i].fr1 = &framebones[as[i].cur.fr1*numbones]; - partframes[i].fr2 = &framebones[as[i].cur.fr2*numbones]; - if(as[i].interp<1) - { - partframes[i].pfr1 = &framebones[as[i].prev.fr1*numbones]; - partframes[i].pfr2 = &framebones[as[i].prev.fr2*numbones]; - } - } - loopv(pitchdeps) - { - pitchdep &p = pitchdeps[i]; - INTERPBONE(p.bone); - d.normalize(); - if(p.parent >= 0) p.pose.mul(pitchdeps[p.parent].pose, d); - else p.pose = d; - } - calcpitchcorrects(pitch, axis, forward); - loopi(numbones) if(bones[i].interpindex>=0) - { - INTERPBONE(i); - const boneinfo &b = bones[i]; - d.normalize(); - if(b.interpparent<0) sc.bdata[b.interpindex] = d; - else sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d); - float angle; - if(b.pitchscale) { angle = b.pitchscale*pitch + b.pitchoffset; if(b.pitchmin || b.pitchmax) angle = clamp(angle, b.pitchmin, b.pitchmax); } - else if(b.correctindex >= 0) angle = pitchcorrects[b.correctindex].pitchangle; - else continue; - if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH)) - angle *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp); - sc.bdata[b.interpindex].mulorient(quat(axis, angle*RAD), b.base); - } - loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]); - } - - void initragdoll(ragdolldata &d, skelcacheentry &sc, part *p) - { - const dualquat *bdata = sc.bdata; - loopv(ragdoll->joints) - { - const ragdollskel::joint &j = ragdoll->joints[i]; - const boneinfo &b = bones[j.bone]; - const dualquat &q = bdata[b.interpindex]; - loopk(3) if(j.vert[k] >= 0) - { - ragdollskel::vert &v = ragdoll->verts[j.vert[k]]; - ragdolldata::vert &dv = d.verts[j.vert[k]]; - dv.pos.add(q.transform(v.pos).mul(v.weight)); - } - } - if(ragdoll->animjoints) loopv(ragdoll->joints) - { - const ragdollskel::joint &j = ragdoll->joints[i]; - const boneinfo &b = bones[j.bone]; - const dualquat &q = bdata[b.interpindex]; - d.calcanimjoint(i, matrix4x3(q)); - } - loopv(ragdoll->verts) - { - ragdolldata::vert &dv = d.verts[i]; - matrixstack[matrixpos].transform(vec(dv.pos).add(p->translate).mul(p->model->scale), dv.pos); - } - loopv(ragdoll->reljoints) - { - const ragdollskel::reljoint &r = ragdoll->reljoints[i]; - const ragdollskel::joint &j = ragdoll->joints[r.parent]; - const boneinfo &br = bones[r.bone], &bj = bones[j.bone]; - d.reljoints[i].mul(dualquat(bdata[bj.interpindex]).invert(), bdata[br.interpindex]); - } - } - - void genragdollbones(ragdolldata &d, skelcacheentry &sc, part *p) - { - if(!sc.bdata) sc.bdata = new dualquat[numinterpbones]; - sc.nextversion(); - loopv(ragdoll->joints) - { - const ragdollskel::joint &j = ragdoll->joints[i]; - const boneinfo &b = bones[j.bone]; - vec pos(0, 0, 0); - loopk(3) if(j.vert[k]>=0) pos.add(d.verts[j.vert[k]].pos); - pos.mul(j.weight/p->model->scale).sub(p->translate); - matrix4x3 m; - m.mul(d.tris[j.tri], pos, d.animjoints ? d.animjoints[i] : j.orient); - sc.bdata[b.interpindex] = dualquat(m); - } - loopv(ragdoll->reljoints) - { - const ragdollskel::reljoint &r = ragdoll->reljoints[i]; - const ragdollskel::joint &j = ragdoll->joints[r.parent]; - const boneinfo &br = bones[r.bone], &bj = bones[j.bone]; - sc.bdata[br.interpindex].mul(sc.bdata[bj.interpindex], d.reljoints[i]); - } - loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]); - } - - void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) - { - matrix4x3 t; - t.mul(bones[tags[i].bone].base, tags[i].matrix); - t.posttranslate(p->translate, p->model->scale); - n.mul(m, t); - } - - void calctags(part *p, skelcacheentry *sc = NULL) - { - loopv(p->links) - { - linkedpart &l = p->links[i]; - tag &t = tags[l.tag]; - dualquat q; - if(sc) q.mul(sc->bdata[bones[t.bone].interpindex], bones[t.bone].base); - else q = bones[t.bone].base; - matrix4x3 m; - m.mul(q, t.matrix); - m.d.add(p->translate).mul(p->model->scale); - l.matrix = m; - } - } - - void cleanup(bool full = true) - { - loopv(skelcache) - { - skelcacheentry &sc = skelcache[i]; - loopj(MAXANIMPARTS) sc.as[j].cur.fr1 = -1; - DELETEA(sc.bdata); - } - skelcache.setsize(0); - blendoffsets.clear(); - if(full) loopv(users) users[i]->cleanup(); - } - - bool canpreload() { return !numframes || gpuaccelerate(); } - - void preload() - { - if(!numframes) return; - if(skelcache.empty()) - { - usegpuskel = gpuaccelerate(); - } - } - - skelcacheentry &checkskelcache(part *p, const animstate *as, float pitch, const vec &axis, const vec &forward, ragdolldata *rdata) - { - if(skelcache.empty()) - { - usegpuskel = gpuaccelerate(); - } - - int numanimparts = ((skelpart *)as->owner)->numanimparts; - uchar *partmask = ((skelpart *)as->owner)->partmask; - skelcacheentry *sc = NULL; - bool match = false; - loopv(skelcache) - { - skelcacheentry &c = skelcache[i]; - loopj(numanimparts) if(c.as[j]!=as[j]) goto mismatch; - if(c.pitch != pitch || c.partmask != partmask || c.ragdoll != rdata || (rdata && c.millis < rdata->lastmove)) goto mismatch; - match = true; - sc = &c; - break; - mismatch: - if(c.millis < lastmillis) { sc = &c; break; } - } - if(!sc) sc = &skelcache.add(); - if(!match) - { - loopi(numanimparts) sc->as[i] = as[i]; - sc->pitch = pitch; - sc->partmask = partmask; - sc->ragdoll = rdata; - if(rdata) genragdollbones(*rdata, *sc, p); - else interpbones(as, pitch, axis, forward, numanimparts, partmask, *sc); - } - sc->millis = lastmillis; - return *sc; - } - - int getblendoffset(UniformLoc &u) - { - int &offset = blendoffsets.access(Shader::lastshader->program, -1); - if(offset < 0) - { - defformatstring(offsetname, "%s[%d]", u.name, 2*numgpubones); - offset = glGetUniformLocation_(Shader::lastshader->program, offsetname); - } - return offset; - } - - void setglslbones(UniformLoc &u, skelcacheentry &sc, skelcacheentry &bc, int count) - { - if(u.version == bc.version && u.data == bc.bdata) return; - glUniform4fv_(u.loc, 2*numgpubones, sc.bdata[0].real.v); - if(count > 0) - { - int offset = getblendoffset(u); - if(offset >= 0) glUniform4fv_(offset, 2*count, bc.bdata[0].real.v); - } - u.version = bc.version; - u.data = bc.bdata; - } - - void setgpubones(skelcacheentry &sc, blendcacheentry *bc, int count) - { - if(!Shader::lastshader) return; - if(Shader::lastshader->uniformlocs.length() < 1) return; - UniformLoc &u = Shader::lastshader->uniformlocs[0]; - setglslbones(u, sc, bc ? *bc : sc, count); - } - - bool shouldcleanup() const - { - return numframes && (skelcache.empty() || gpuaccelerate()!=usegpuskel); - } - }; - - struct skelmeshgroup : meshgroup - { - skeleton *skel; - - vector blendcombos; - int numblends[4]; - - static const int MAXBLENDCACHE = 16; - blendcacheentry blendcache[MAXBLENDCACHE]; - - static const int MAXVBOCACHE = 16; - vbocacheentry vbocache[MAXVBOCACHE]; - - ushort *edata; - GLuint ebuf; - bool vtangents; - int vlen, vertsize, vblends, vweights; - uchar *vdata; - - skelmeshgroup() : skel(NULL), edata(NULL), ebuf(0), vtangents(false), vlen(0), vertsize(0), vblends(0), vweights(0), vdata(NULL) - { - memset(numblends, 0, sizeof(numblends)); - } - - virtual ~skelmeshgroup() - { - if(skel) - { - if(skel->shared) skel->users.removeobj(this); - else DELETEP(skel); - } - if(ebuf) glDeleteBuffers_(1, &ebuf); - loopi(MAXBLENDCACHE) - { - DELETEA(blendcache[i].bdata); - } - loopi(MAXVBOCACHE) - { - if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf); - } - DELETEA(vdata); - } - - void shareskeleton(char *name) - { - if(!name) - { - skel = new skeleton; - skel->users.add(this); - return; - } - - static hashnameset skeletons; - if(skeletons.access(name)) skel = skeletons[name]; - else - { - skel = new skeleton; - skel->name = newstring(name); - skeletons.add(skel); - } - skel->users.add(this); - skel->shared++; - } - - int findtag(const char *name) - { - return skel->findtag(name); - } - - void *animkey() { return skel; } - int totalframes() const { return max(skel->numframes, 1); } - - virtual skelanimspec *loadanim(const char *filename) { return NULL; } - - void genvbo(bool tangents, vbocacheentry &vc) - { - if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf); - if(ebuf) return; - - vector idxs; - - if(tangents) loopv(meshes) ((skelmesh *)meshes[i])->calctangents(); - - vtangents = tangents; - vlen = 0; - vblends = 0; - if(skel->numframes && !skel->usegpuskel) - { - vweights = 1; - loopv(blendcombos) - { - blendcombo &c = blendcombos[i]; - c.interpindex = c.weights[1] ? skel->numgpubones + vblends++ : -1; - } - - vertsize = tangents ? sizeof(vvertbump) : sizeof(vvertn); - loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo(idxs, vlen); - DELETEA(vdata); - vdata = new uchar[vlen*vertsize]; - #define FILLVDATA(type) do { \ - loopv(meshes) ((skelmesh *)meshes[i])->fillverts((type *)vdata); \ - } while(0) - if(tangents) FILLVDATA(vvertbump); - else FILLVDATA(vvertn); - #undef FILLVDATA - } - else - { - if(skel->numframes) - { - vweights = 4; - int availbones = skel->availgpubones() - skel->numgpubones; - while(vweights > 1 && availbones >= numblends[vweights-1]) availbones -= numblends[--vweights]; - loopv(blendcombos) - { - blendcombo &c = blendcombos[i]; - c.interpindex = c.size() > vweights ? skel->numgpubones + vblends++ : -1; - } - } - else - { - vweights = 0; - loopv(blendcombos) blendcombos[i].interpindex = -1; - } - - gle::bindvbo(vc.vbuf); - #define GENVBO(type, args) do { \ - vertsize = sizeof(type); \ - vector vverts; \ - loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo args; \ - glBufferData_(GL_ARRAY_BUFFER, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW); \ - } while(0) - #define GENVBOANIM(type) GENVBO(type, (idxs, vlen, vverts)) - #define GENVBOSTAT(type) GENVBO(type, (idxs, vlen, vverts, htdata, htlen)) - if(skel->numframes) - { - if(tangents) GENVBOANIM(vvertbumpw); - else GENVBOANIM(vvertnw); - } - else - { - int numverts = 0, htlen = 128; - loopv(meshes) numverts += ((skelmesh *)meshes[i])->numverts; - while(htlen < numverts) htlen *= 2; - if(numverts*4 > htlen*3) htlen *= 2; - int *htdata = new int[htlen]; - memset(htdata, -1, htlen*sizeof(int)); - if(tangents) GENVBOSTAT(vvertbump); - else GENVBOSTAT(vvertn); - delete[] htdata; - } - #undef GENVBO - #undef GENVBOANIM - #undef GENVBOSTAT - gle::clearvbo(); - } - - glGenBuffers_(1, &ebuf); - gle::bindebo(ebuf); - glBufferData_(GL_ELEMENT_ARRAY_BUFFER, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW); - gle::clearebo(); - } - - void bindvbo(const animstate *as, vbocacheentry &vc, skelcacheentry *sc = NULL, blendcacheentry *bc = NULL) - { - vvert *vverts = 0; - bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize); - if(as->cur.anim&ANIM_NOSKIN) - { - if(enabletc) disabletc(); - if(enablenormals) disablenormals(); - if(enabletangents) disabletangents(); - } - else - { - if(vtangents) - { - if(enablenormals) disablenormals(); - vvertbump *vvertbumps = 0; - bindtangents(&vvertbumps->tangent, vertsize); - } - else - { - if(enabletangents) disabletangents(); - vvertn *vvertns = 0; - bindnormals(&vvertns->norm, vertsize); - } - - bindtc(&vverts->tc, vertsize); - } - if(!sc || !skel->usegpuskel) - { - if(enablebones) disablebones(); - } - else - { - if(vtangents) - { - vvertbumpw *vvertbumpws = 0; - bindbones(vvertbumpws->weights, vvertbumpws->bones, vertsize); - } - else - { - vvertnw *vvertnws = 0; - bindbones(vvertnws->weights, vvertnws->bones, vertsize); - } - } - } - - void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) - { - skel->concattagtransform(p, i, m, n); - } - - int addblendcombo(const blendcombo &c) - { - loopv(blendcombos) if(blendcombos[i]==c) - { - blendcombos[i].uses += c.uses; - return i; - } - numblends[c.size()-1]++; - blendcombo &a = blendcombos.add(c); - return a.interpindex = blendcombos.length()-1; - } - - void sortblendcombos() - { - blendcombos.sort(blendcombo::sortcmp); - int *remap = new int[blendcombos.length()]; - loopv(blendcombos) remap[blendcombos[i].interpindex] = i; - loopv(meshes) - { - skelmesh *m = (skelmesh *)meshes[i]; - loopj(m->numverts) - { - vert &v = m->verts[j]; - v.blend = remap[v.blend]; - } - } - delete[] remap; - } - - int remapblend(int blend) - { - const blendcombo &c = blendcombos[blend]; - return c.weights[1] ? c.interpindex : c.interpbones[0]; - } - - static inline void blendbones(dualquat &d, const dualquat *bdata, const blendcombo &c) - { - d = bdata[c.interpbones[0]]; - d.mul(c.weights[0]); - d.accumulate(bdata[c.interpbones[1]], c.weights[1]); - if(c.weights[2]) - { - d.accumulate(bdata[c.interpbones[2]], c.weights[2]); - if(c.weights[3]) d.accumulate(bdata[c.interpbones[3]], c.weights[3]); - } - } - - void blendbones(const skelcacheentry &sc, blendcacheentry &bc) - { - bc.nextversion(); - if(!bc.bdata) bc.bdata = new dualquat[vblends]; - dualquat *dst = bc.bdata - skel->numgpubones; - bool normalize = !skel->usegpuskel || vweights<=1; - loopv(blendcombos) - { - const blendcombo &c = blendcombos[i]; - if(c.interpindex<0) break; - dualquat &d = dst[c.interpindex]; - blendbones(d, sc.bdata, c); - if(normalize) d.normalize(); - } - } - - void cleanup() - { - loopi(MAXBLENDCACHE) - { - blendcacheentry &c = blendcache[i]; - DELETEA(c.bdata); - c.owner = -1; - } - loopi(MAXVBOCACHE) - { - vbocacheentry &c = vbocache[i]; - if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; } - c.owner = -1; - } - if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } - if(skel) skel->cleanup(false); - } - - #define SEARCHCACHE(cachesize, cacheentry, cache, reusecheck) \ - loopi(cachesize) \ - { \ - cacheentry &c = cache[i]; \ - if(c.owner==owner) \ - { \ - if(c==sc) return c; \ - else c.owner = -1; \ - break; \ - } \ - } \ - loopi(cachesize-1) \ - { \ - cacheentry &c = cache[i]; \ - if(reusecheck c.owner < 0 || c.millis < lastmillis) \ - return c; \ - } \ - return cache[cachesize-1]; - - vbocacheentry &checkvbocache(skelcacheentry &sc, int owner) - { - SEARCHCACHE(MAXVBOCACHE, vbocacheentry, vbocache, !c.vbuf || ); - } - - blendcacheentry &checkblendcache(skelcacheentry &sc, int owner) - { - SEARCHCACHE(MAXBLENDCACHE, blendcacheentry, blendcache, ) - } - - void preload(part *p) - { - if(!skel->canpreload()) return; - bool tangents = false; - loopv(p->skins) if(p->skins[i].tangents()) tangents = true; - if(skel->shouldcleanup()) skel->cleanup(); - else if(tangents!=vtangents) cleanup(); - skel->preload(); - if(!vbocache->vbuf) genvbo(tangents, *vbocache); - } - - void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) - { - bool tangents = false; - loopv(p->skins) if(p->skins[i].tangents()) tangents = true; - if(skel->shouldcleanup()) { skel->cleanup(); disablevbo(); } - else if(tangents!=vtangents) { cleanup(); disablevbo(); } - - if(!skel->numframes) - { - if(!(as->cur.anim&ANIM_NORENDER)) - { - if(!vbocache->vbuf) genvbo(tangents, *vbocache); - bindvbo(as, *vbocache); - loopv(meshes) - { - skelmesh *m = (skelmesh *)meshes[i]; - p->skins[i].bind(m, as); - m->render(as, p->skins[i], *vbocache); - } - } - skel->calctags(p); - return; - } - - skelcacheentry &sc = skel->checkskelcache(p, as, pitch, axis, forward, as->cur.anim&ANIM_RAGDOLL || !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll ? NULL : d->ragdoll); - if(!(as->cur.anim&ANIM_NORENDER)) - { - int owner = &sc-&skel->skelcache[0]; - vbocacheentry &vc = skel->usegpuskel ? *vbocache : checkvbocache(sc, owner); - vc.millis = lastmillis; - if(!vc.vbuf) genvbo(tangents, vc); - blendcacheentry *bc = NULL; - if(vblends) - { - bc = &checkblendcache(sc, owner); - bc->millis = lastmillis; - if(bc->owner!=owner) - { - bc->owner = owner; - *(animcacheentry *)bc = sc; - blendbones(sc, *bc); - } - } - if(!skel->usegpuskel && vc.owner!=owner) - { - vc.owner = owner; - (animcacheentry &)vc = sc; - loopv(meshes) - { - skelmesh &m = *(skelmesh *)meshes[i]; - m.interpverts(sc.bdata, bc ? bc->bdata : NULL, tangents, vdata + m.voffset*vertsize, p->skins[i]); - } - gle::bindvbo(vc.vbuf); - glBufferData_(GL_ARRAY_BUFFER, vlen*vertsize, vdata, GL_STREAM_DRAW); - } - - bindvbo(as, vc, &sc, bc); - loopv(meshes) - { - skelmesh *m = (skelmesh *)meshes[i]; - p->skins[i].bind(m, as); - if(skel->usegpuskel) skel->setgpubones(sc, bc, vblends); - m->render(as, p->skins[i], vc); - } - } - - skel->calctags(p, &sc); - - if(as->cur.anim&ANIM_RAGDOLL && skel->ragdoll && !d->ragdoll) - { - d->ragdoll = new ragdolldata(skel->ragdoll, p->model->scale); - skel->initragdoll(*d->ragdoll, sc, p); - d->ragdoll->init(d); - } - } - }; - - struct animpartmask - { - animpartmask *next; - int numbones; - uchar bones[1]; - }; - - struct skelpart : part - { - animpartmask *buildingpartmask; - - uchar *partmask; - - skelpart(animmodel *model, int index = 0) : part(model, index), buildingpartmask(NULL), partmask(NULL) - { - } - - virtual ~skelpart() - { - DELETEA(buildingpartmask); - } - - uchar *sharepartmask(animpartmask *o) - { - static animpartmask *partmasks = NULL; - animpartmask *p = partmasks; - for(; p; p = p->next) if(p->numbones==o->numbones && !memcmp(p->bones, o->bones, p->numbones)) - { - delete[] (uchar *)o; - return p->bones; - } - - o->next = p; - partmasks = o; - return o->bones; - } - - animpartmask *newpartmask() - { - animpartmask *p = (animpartmask *)new uchar[sizeof(animpartmask) + ((skelmeshgroup *)meshes)->skel->numbones-1]; - p->numbones = ((skelmeshgroup *)meshes)->skel->numbones; - memset(p->bones, 0, p->numbones); - return p; - } - - void initanimparts() - { - DELETEA(buildingpartmask); - buildingpartmask = newpartmask(); - } - - bool addanimpart(ushort *bonemask) - { - if(!buildingpartmask || numanimparts>=MAXANIMPARTS) return false; - ((skelmeshgroup *)meshes)->skel->applybonemask(bonemask, buildingpartmask->bones, numanimparts); - numanimparts++; - return true; - } - - void endanimparts() - { - if(buildingpartmask) - { - partmask = sharepartmask(buildingpartmask); - buildingpartmask = NULL; - } - - ((skelmeshgroup *)meshes)->skel->optimize(); - } - - void loaded() - { - endanimparts(); - part::loaded(); - } - }; - - skelmodel(const char *name) : animmodel(name) - { - } - - int linktype(animmodel *m) const - { - return type()==m->type() && - ((skelmeshgroup *)parts[0]->meshes)->skel == ((skelmeshgroup *)m->parts[0]->meshes)->skel ? - LINK_REUSE : - LINK_TAG; - } - - bool skeletal() const { return true; } - - skelpart &addpart() - { - flushpart(); - skelpart *p = new skelpart(this, parts.length()); - parts.add(p); - return *p; - } + struct vert { vec pos, norm; vec2 tc; int blend, interpindex; }; + struct vvert { vec pos; vec2 tc; }; + struct vvertn : vvert { vec norm; }; + struct vvertbump : vvert { squat tangent; }; + struct vvertw { uchar weights[4]; uchar bones[4]; }; + struct vvertnw : vvertn, vvertw {}; + struct vvertbumpw : vvertbump, vvertw {}; + struct bumpvert { quat tangent; }; + struct tri { ushort vert[3]; }; + + struct blendcombo + { + int uses, interpindex; + float weights[4]; + uchar bones[4], interpbones[4]; + + blendcombo() : uses(1) + { + } + + bool operator==(const blendcombo &c) const + { + loopk(4) if(bones[k] != c.bones[k]) return false; + loopk(4) if(weights[k] != c.weights[k]) return false; + return true; + } + + int size() const + { + int i = 1; + while(i < 4 && weights[i]) i++; + return i; + } + + static bool sortcmp(const blendcombo &x, const blendcombo &y) + { + loopi(4) + { + if(x.weights[i]) + { + if(!y.weights[i]) return true; + } + else if(y.weights[i]) return false; + else break; + } + return false; + } + + int addweight(int sorted, float weight, int bone) + { + if(weight <= 1e-3f) return sorted; + loopk(sorted) if(weight > weights[k]) + { + for(int l = min(sorted-1, 2); l >= k; l--) + { + weights[l+1] = weights[l]; + bones[l+1] = bones[l]; + } + weights[k] = weight; + bones[k] = bone; + return sorted<4 ? sorted+1 : sorted; + } + if(sorted>=4) return sorted; + weights[sorted] = weight; + bones[sorted] = bone; + return sorted+1; + } + + void finalize(int sorted) + { + loopj(4-sorted) { weights[sorted+j] = 0; bones[sorted+j] = 0; } + if(sorted <= 0) return; + float total = 0; + loopj(sorted) total += weights[j]; + total = 1.0f/total; + loopj(sorted) weights[j] *= total; + } + + void serialize(vvertw &v) + { + if(interpindex >= 0) + { + v.weights[0] = 255; + loopk(3) v.weights[k+1] = 0; + v.bones[0] = 2*interpindex; + loopk(3) v.bones[k+1] = v.bones[0]; + } + else + { + int total = 0; + loopk(4) total += (v.weights[k] = uchar(0.5f + weights[k]*255)); + while(total > 255) + { + loopk(4) if(v.weights[k] > 0 && total > 255) { v.weights[k]--; total--; } + } + while(total < 255) + { + loopk(4) if(v.weights[k] < 255 && total < 255) { v.weights[k]++; total++; } + } + loopk(4) v.bones[k] = 2*interpbones[k]; + } + } + }; + + + struct animcacheentry + { + animstate as[MAXANIMPARTS]; + float pitch; + int millis; + uchar *partmask; + ragdolldata *ragdoll; + + animcacheentry() : ragdoll(NULL) + { + loopk(MAXANIMPARTS) as[k].cur.fr1 = as[k].prev.fr1 = -1; + } + + bool operator==(const animcacheentry &c) const + { + loopi(MAXANIMPARTS) if(as[i]!=c.as[i]) return false; + return pitch==c.pitch && partmask==c.partmask && ragdoll==c.ragdoll && (!ragdoll || min(millis, c.millis) >= ragdoll->lastmove); + } + }; + + struct vbocacheentry : animcacheentry + { + GLuint vbuf; + int owner; + + vbocacheentry() : vbuf(0), owner(-1) {} + }; + + struct skelcacheentry : animcacheentry + { + dualquat *bdata; + int version; + bool dirty; + + skelcacheentry() : bdata(NULL), version(-1), dirty(false) {} + + void nextversion() + { + version = Shader::uniformlocversion(); + dirty = true; + } + }; + + struct blendcacheentry : skelcacheentry + { + int owner; + + blendcacheentry() : owner(-1) {} + }; + + struct skelmeshgroup; + + struct skelmesh : mesh + { + vert *verts; + bumpvert *bumpverts; + tri *tris; + int numverts, numtris, maxweights; + + int voffset, eoffset, elen; + ushort minvert, maxvert; + + skelmesh() : verts(NULL), bumpverts(NULL), tris(NULL), numverts(0), numtris(0), maxweights(0) + { + } + + virtual ~skelmesh() + { + DELETEA(verts); + DELETEA(bumpverts); + DELETEA(tris); + } + + int addblendcombo(const blendcombo &c) + { + maxweights = max(maxweights, c.size()); + return ((skelmeshgroup *)group)->addblendcombo(c); + } + + void smoothnorms(float limit = 0, bool areaweight = true) + { + mesh::smoothnorms(verts, numverts, tris, numtris, limit, areaweight); + } + + void buildnorms(bool areaweight = true) + { + mesh::buildnorms(verts, numverts, tris, numtris, areaweight); + } + + void calctangents(bool areaweight = true) + { + if(bumpverts) return; + bumpverts = new bumpvert[numverts]; + mesh::calctangents(bumpverts, verts, verts, numverts, tris, numtris, areaweight); + } + + void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) + { + loopj(numverts) + { + vec v = m.transform(verts[j].pos); + loopi(3) + { + bbmin[i] = min(bbmin[i], v[i]); + bbmax[i] = max(bbmax[i], v[i]); + } + } + } + + void genBIH(BIH::mesh &m) + { + m.tris = (const BIH::tri *)tris; + m.numtris = numtris; + m.pos = (const uchar *)&verts->pos; + m.posstride = sizeof(vert); + m.tc = (const uchar *)&verts->tc; + m.tcstride = sizeof(vert); + } + + static inline void assignvert(vvertn &vv, int j, vert &v, blendcombo &c) + { + vv.pos = v.pos; + vv.norm = v.norm; + vv.tc = v.tc; + } + + inline void assignvert(vvertbump &vv, int j, vert &v, blendcombo &c) + { + vv.pos = v.pos; + vv.tc = v.tc; + vv.tangent = bumpverts[j].tangent; + } + + static inline void assignvert(vvertnw &vv, int j, vert &v, blendcombo &c) + { + vv.pos = v.pos; + vv.norm = v.norm; + vv.tc = v.tc; + c.serialize(vv); + } + + inline void assignvert(vvertbumpw &vv, int j, vert &v, blendcombo &c) + { + vv.pos = v.pos; + vv.tc = v.tc; + vv.tangent = bumpverts[j].tangent; + c.serialize(vv); + } + + template + int genvbo(vector &idxs, int offset, vector &vverts) + { + voffset = offset; + eoffset = idxs.length(); + loopi(numverts) + { + vert &v = verts[i]; + assignvert(vverts.add(), i, v, ((skelmeshgroup *)group)->blendcombos[v.blend]); + } + loopi(numtris) loopj(3) idxs.add(voffset + tris[i].vert[j]); + elen = idxs.length()-eoffset; + minvert = voffset; + maxvert = voffset + numverts-1; + return numverts; + } + + template + int genvbo(vector &idxs, int offset, vector &vverts, int *htdata, int htlen) + { + voffset = offset; + eoffset = idxs.length(); + minvert = 0xFFFF; + loopi(numtris) + { + tri &t = tris[i]; + loopj(3) + { + int index = t.vert[j]; + vert &v = verts[index]; + T vv; + assignvert(vv, index, v, ((skelmeshgroup *)group)->blendcombos[v.blend]); + int htidx = hthash(v.pos)&(htlen-1); + loopk(htlen) + { + int &vidx = htdata[(htidx+k)&(htlen-1)]; + if(vidx < 0) { vidx = idxs.add(ushort(vverts.length())); vverts.add(vv); break; } + else if(!memcmp(&vverts[vidx], &vv, sizeof(vv))) { minvert = min(minvert, idxs.add(ushort(vidx))); break; } + } + } + } + elen = idxs.length()-eoffset; + minvert = min(minvert, ushort(voffset)); + maxvert = max(minvert, ushort(vverts.length()-1)); + return vverts.length()-voffset; + } + + int genvbo(vector &idxs, int offset) + { + loopi(numverts) verts[i].interpindex = ((skelmeshgroup *)group)->remapblend(verts[i].blend); + + voffset = offset; + eoffset = idxs.length(); + loopi(numtris) + { + tri &t = tris[i]; + loopj(3) idxs.add(voffset+t.vert[j]); + } + minvert = voffset; + maxvert = voffset + numverts-1; + elen = idxs.length()-eoffset; + return numverts; + } + + template + static inline void fillvert(T &vv, int j, vert &v) + { + vv.tc = v.tc; + } + + template + void fillverts(T *vdata) + { + vdata += voffset; + loopi(numverts) fillvert(vdata[i], i, verts[i]); + } + + void interpverts(const dualquat * RESTRICT bdata1, const dualquat * RESTRICT bdata2, bool tangents, void * RESTRICT vdata, skin &s) + { + const int blendoffset = ((skelmeshgroup *)group)->skel->numgpubones; + bdata2 -= blendoffset; + + #define IPLOOP(type, dosetup, dotransform) \ + loopi(numverts) \ + { \ + const vert &src = verts[i]; \ + type &dst = ((type * RESTRICT)vdata)[i]; \ + dosetup; \ + const dualquat &b = (src.interpindex < blendoffset ? bdata1 : bdata2)[src.interpindex]; \ + dst.pos = b.transform(src.pos); \ + dotransform; \ + } + + if(tangents) + { + IPLOOP(vvertbump, bumpvert &bsrc = bumpverts[i], + { + quat q = b.transform(bsrc.tangent); + fixqtangent(q, bsrc.tangent.w); + dst.tangent = q; + }); + } + else + { + IPLOOP(vvertn, , + { + dst.norm = b.transformnormal(src.norm); + }); + } + + #undef IPLOOP + } + + void setshader(Shader *s) + { + skelmeshgroup *g = (skelmeshgroup *)group; + if(glaring) + { + if(!g->skel->usegpuskel) s->setvariant(0, 1); + else s->setvariant(min(maxweights, g->vweights), 1); + } + else if(!g->skel->usegpuskel) s->set(); + else s->setvariant(min(maxweights, g->vweights)-1, 0); + } + + void render(const animstate *as, skin &s, vbocacheentry &vc) + { + if(!Shader::lastshader) return; + glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((skelmeshgroup *)group)->edata[eoffset]); + glde++; + xtravertsva += numverts; + } + }; + + + struct tag + { + char *name; + int bone; + matrix4x3 matrix; + + tag() : name(NULL) {} + ~tag() { DELETEA(name); } + }; + + struct skelanimspec + { + char *name; + int frame, range; + + skelanimspec() : name(NULL), frame(0), range(0) {} + ~skelanimspec() + { + DELETEA(name); + } + }; + + struct boneinfo + { + const char *name; + int parent, children, next, group, scheduled, interpindex, interpparent, ragdollindex, correctindex; + float pitchscale, pitchoffset, pitchmin, pitchmax; + dualquat base, invbase; + + boneinfo() : name(NULL), parent(-1), children(-1), next(-1), group(INT_MAX), scheduled(-1), interpindex(-1), interpparent(-1), ragdollindex(-1), correctindex(-1), pitchscale(0), pitchoffset(0), pitchmin(0), pitchmax(0) {} + ~boneinfo() + { + DELETEA(name); + } + }; + + struct antipode + { + int parent, child; + + antipode(int parent, int child) : parent(parent), child(child) {} + }; + + struct pitchdep + { + int bone, parent; + dualquat pose; + }; + + struct pitchtarget + { + int bone, frame, corrects, deps; + float pitchmin, pitchmax, deviated; + dualquat pose; + }; + + struct pitchcorrect + { + int bone, target, parent; + float pitchmin, pitchmax, pitchscale, pitchangle, pitchtotal; + + pitchcorrect() : parent(-1), pitchangle(0), pitchtotal(0) {} + }; + + struct skeleton + { + char *name; + int shared; + vector users; + boneinfo *bones; + int numbones, numinterpbones, numgpubones, numframes; + dualquat *framebones; + vector skelanims; + vector tags; + vector antipodes; + ragdollskel *ragdoll; + vector pitchdeps; + vector pitchtargets; + vector pitchcorrects; + + bool usegpuskel; + vector skelcache; + hashtable blendoffsets; + + skeleton() : name(NULL), shared(0), bones(NULL), numbones(0), numinterpbones(0), numgpubones(0), numframes(0), framebones(NULL), ragdoll(NULL), usegpuskel(false), blendoffsets(32) + { + } + + ~skeleton() + { + DELETEA(name); + DELETEA(bones); + DELETEA(framebones); + DELETEP(ragdoll); + loopv(skelcache) + { + DELETEA(skelcache[i].bdata); + } + } + + skelanimspec *findskelanim(const char *name, char sep = '\0') + { + int len = sep ? strlen(name) : 0; + loopv(skelanims) + { + if(skelanims[i].name) + { + if(sep) + { + const char *end = strchr(skelanims[i].name, ':'); + if(end && end - skelanims[i].name == len && !memcmp(name, skelanims[i].name, len)) return &skelanims[i]; + } + if(!strcmp(name, skelanims[i].name)) return &skelanims[i]; + } + } + return NULL; + } + + skelanimspec &addskelanim(const char *name) + { + skelanimspec &sa = skelanims.add(); + sa.name = name ? newstring(name) : NULL; + return sa; + } + + int findbone(const char *name) + { + loopi(numbones) if(bones[i].name && !strcmp(bones[i].name, name)) return i; + return -1; + } + + int findtag(const char *name) + { + loopv(tags) if(!strcmp(tags[i].name, name)) return i; + return -1; + } + + bool addtag(const char *name, int bone, const matrix4x3 &matrix) + { + int idx = findtag(name); + if(idx >= 0) + { + if(!testtags) return false; + tag &t = tags[idx]; + t.bone = bone; + t.matrix = matrix; + } + else + { + tag &t = tags.add(); + t.name = newstring(name); + t.bone = bone; + t.matrix = matrix; + } + return true; + } + + void calcantipodes() + { + antipodes.shrink(0); + vector schedule; + loopi(numbones) + { + if(bones[i].group >= numbones) + { + bones[i].scheduled = schedule.length(); + schedule.add(i); + } + else bones[i].scheduled = -1; + } + loopv(schedule) + { + int bone = schedule[i]; + const boneinfo &info = bones[bone]; + loopj(numbones) if(abs(bones[j].group) == bone && bones[j].scheduled < 0) + { + antipodes.add(antipode(info.interpindex, bones[j].interpindex)); + bones[j].scheduled = schedule.length(); + schedule.add(j); + } + if(i + 1 == schedule.length()) + { + int conflict = INT_MAX; + loopj(numbones) if(bones[j].group < numbones && bones[j].scheduled < 0) conflict = min(conflict, abs(bones[j].group)); + if(conflict < numbones) + { + bones[conflict].scheduled = schedule.length(); + schedule.add(conflict); + } + } + } + } + + void remapbones() + { + loopi(numbones) + { + boneinfo &info = bones[i]; + info.interpindex = -1; + info.ragdollindex = -1; + } + numgpubones = 0; + loopv(users) + { + skelmeshgroup *group = users[i]; + loopvj(group->blendcombos) + { + blendcombo &c = group->blendcombos[j]; + loopk(4) + { + if(!c.weights[k]) { c.interpbones[k] = k > 0 ? c.interpbones[k-1] : 0; continue; } + boneinfo &info = bones[c.bones[k]]; + if(info.interpindex < 0) info.interpindex = numgpubones++; + c.interpbones[k] = info.interpindex; + if(info.group < 0) continue; + loopl(4) + { + if(!c.weights[l]) break; + if(l == k) continue; + int parent = c.bones[l]; + if(info.parent == parent || (info.parent >= 0 && info.parent == bones[parent].parent)) { info.group = -info.parent; break; } + if(info.group <= parent) continue; + int child = c.bones[k]; + while(parent > child) parent = bones[parent].parent; + if(parent != child) info.group = c.bones[l]; + } + } + } + } + numinterpbones = numgpubones; + loopv(tags) + { + boneinfo &info = bones[tags[i].bone]; + if(info.interpindex < 0) info.interpindex = numinterpbones++; + } + if(ragdoll) + { + loopv(ragdoll->joints) + { + boneinfo &info = bones[ragdoll->joints[i].bone]; + if(info.interpindex < 0) info.interpindex = numinterpbones++; + info.ragdollindex = i; + } + } + loopi(numbones) + { + boneinfo &info = bones[i]; + if(info.interpindex < 0) continue; + for(int parent = info.parent; parent >= 0 && bones[parent].interpindex < 0; parent = bones[parent].parent) + bones[parent].interpindex = numinterpbones++; + } + loopi(numbones) + { + boneinfo &info = bones[i]; + if(info.interpindex < 0) continue; + info.interpparent = info.parent >= 0 ? bones[info.parent].interpindex : -1; + } + if(ragdoll) + { + loopi(numbones) + { + boneinfo &info = bones[i]; + if(info.interpindex < 0 || info.ragdollindex >= 0) continue; + for(int parent = info.parent; parent >= 0; parent = bones[parent].parent) + { + if(bones[parent].ragdollindex >= 0) { ragdoll->addreljoint(i, bones[parent].ragdollindex); break; } + } + } + } + calcantipodes(); + } + + + void addpitchdep(int bone, int frame) + { + for(; bone >= 0; bone = bones[bone].parent) + { + int pos = pitchdeps.length(); + loopvj(pitchdeps) if(bone <= pitchdeps[j].bone) + { + if(bone == pitchdeps[j].bone) goto nextbone; + pos = j; + break; + } + { + pitchdep d; + d.bone = bone; + d.parent = -1; + d.pose = framebones[frame*numbones + bone]; + pitchdeps.insert(pos, d); + } + nextbone:; + } + } + + int findpitchdep(int bone) + { + loopv(pitchdeps) if(bone <= pitchdeps[i].bone) return bone == pitchdeps[i].bone ? i : -1; + return -1; + } + + int findpitchcorrect(int bone) + { + loopv(pitchcorrects) if(bone <= pitchcorrects[i].bone) return bone == pitchcorrects[i].bone ? i : -1; + return -1; + } + + void initpitchdeps() + { + pitchdeps.setsize(0); + if(pitchtargets.empty()) return; + loopv(pitchtargets) + { + pitchtarget &t = pitchtargets[i]; + t.deps = -1; + addpitchdep(t.bone, t.frame); + } + loopv(pitchdeps) + { + pitchdep &d = pitchdeps[i]; + int parent = bones[d.bone].parent; + if(parent >= 0) + { + int j = findpitchdep(parent); + if(j >= 0) + { + d.parent = j; + d.pose.mul(pitchdeps[j].pose, dualquat(d.pose)); + } + } + } + loopv(pitchtargets) + { + pitchtarget &t = pitchtargets[i]; + int j = findpitchdep(t.bone); + if(j >= 0) + { + t.deps = j; + t.pose = pitchdeps[j].pose; + } + t.corrects = -1; + for(int parent = t.bone; parent >= 0; parent = bones[parent].parent) + { + t.corrects = findpitchcorrect(parent); + if(t.corrects >= 0) break; + } + } + loopv(pitchcorrects) + { + pitchcorrect &c = pitchcorrects[i]; + bones[c.bone].correctindex = i; + c.parent = -1; + for(int parent = c.bone;;) + { + parent = bones[parent].parent; + if(parent < 0) break; + c.parent = findpitchcorrect(parent); + if(c.parent >= 0) break; + } + } + } + + void optimize() + { + cleanup(); + if(ragdoll) ragdoll->setup(); + remapbones(); + initpitchdeps(); + } + + void expandbonemask(uchar *expansion, int bone, int val) + { + expansion[bone] = val; + bone = bones[bone].children; + while(bone>=0) { expandbonemask(expansion, bone, val); bone = bones[bone].next; } + } + + void applybonemask(ushort *mask, uchar *partmask, int partindex) + { + if(!mask || *mask==BONEMASK_END) return; + uchar *expansion = new uchar[numbones]; + memset(expansion, *mask&BONEMASK_NOT ? 1 : 0, numbones); + while(*mask!=BONEMASK_END) + { + expandbonemask(expansion, *mask&BONEMASK_BONE, *mask&BONEMASK_NOT ? 0 : 1); + mask++; + } + loopi(numbones) if(expansion[i]) partmask[i] = partindex; + delete[] expansion; + } + + void linkchildren() + { + loopi(numbones) + { + boneinfo &b = bones[i]; + b.children = -1; + if(b.parent<0) b.next = -1; + else + { + b.next = bones[b.parent].children; + bones[b.parent].children = i; + } + } + } + + int availgpubones() const { return min(maxvsuniforms - reservevpparams - 10, maxskelanimdata) / 2; } + bool gpuaccelerate() const { return numframes && gpuskel && numgpubones<=availgpubones(); } + + float calcdeviation(const vec &axis, const vec &forward, const dualquat &pose1, const dualquat &pose2) + { + vec forward1 = pose1.transformnormal(forward).project(axis).normalize(), + forward2 = pose2.transformnormal(forward).project(axis).normalize(), + daxis = vec().cross(forward1, forward2); + float dx = clamp(forward1.dot(forward2), -1.0f, 1.0f), dy = clamp(daxis.magnitude(), -1.0f, 1.0f); + if(daxis.dot(axis) < 0) dy = -dy; + return atan2f(dy, dx)/RAD; + } + + void calcpitchcorrects(float pitch, const vec &axis, const vec &forward) + { + loopv(pitchtargets) + { + pitchtarget &t = pitchtargets[i]; + t.deviated = calcdeviation(axis, forward, t.pose, pitchdeps[t.deps].pose); + } + loopv(pitchcorrects) + { + pitchcorrect &c = pitchcorrects[i]; + c.pitchangle = c.pitchtotal = 0; + } + loopvj(pitchtargets) + { + pitchtarget &t = pitchtargets[j]; + float tpitch = pitch - t.deviated; + for(int parent = t.corrects; parent >= 0; parent = pitchcorrects[parent].parent) + tpitch -= pitchcorrects[parent].pitchangle; + if(t.pitchmin || t.pitchmax) tpitch = clamp(tpitch, t.pitchmin, t.pitchmax); + loopv(pitchcorrects) + { + pitchcorrect &c = pitchcorrects[i]; + if(c.target != j) continue; + float total = c.parent >= 0 ? pitchcorrects[c.parent].pitchtotal : 0, + avail = tpitch - total, + used = tpitch*c.pitchscale; + if(c.pitchmin || c.pitchmax) + { + if(used < 0) used = clamp(c.pitchmin, used, 0.0f); + else used = clamp(c.pitchmax, 0.0f, used); + } + if(used < 0) used = clamp(avail, used, 0.0f); + else used = clamp(avail, 0.0f, used); + c.pitchangle = used; + c.pitchtotal = used + total; + } + } + } + + #define INTERPBONE(bone) \ + const animstate &s = as[partmask[bone]]; \ + const framedata &f = partframes[partmask[bone]]; \ + dualquat d; \ + (d = f.fr1[bone]).mul((1-s.cur.t)*s.interp); \ + d.accumulate(f.fr2[bone], s.cur.t*s.interp); \ + if(s.interp<1) \ + { \ + d.accumulate(f.pfr1[bone], (1-s.prev.t)*(1-s.interp)); \ + d.accumulate(f.pfr2[bone], s.prev.t*(1-s.interp)); \ + } + + void interpbones(const animstate *as, float pitch, const vec &axis, const vec &forward, int numanimparts, const uchar *partmask, skelcacheentry &sc) + { + if(!sc.bdata) sc.bdata = new dualquat[numinterpbones]; + sc.nextversion(); + struct framedata + { + const dualquat *fr1, *fr2, *pfr1, *pfr2; + } partframes[MAXANIMPARTS]; + loopi(numanimparts) + { + partframes[i].fr1 = &framebones[as[i].cur.fr1*numbones]; + partframes[i].fr2 = &framebones[as[i].cur.fr2*numbones]; + if(as[i].interp<1) + { + partframes[i].pfr1 = &framebones[as[i].prev.fr1*numbones]; + partframes[i].pfr2 = &framebones[as[i].prev.fr2*numbones]; + } + } + loopv(pitchdeps) + { + pitchdep &p = pitchdeps[i]; + INTERPBONE(p.bone); + d.normalize(); + if(p.parent >= 0) p.pose.mul(pitchdeps[p.parent].pose, d); + else p.pose = d; + } + calcpitchcorrects(pitch, axis, forward); + loopi(numbones) if(bones[i].interpindex>=0) + { + INTERPBONE(i); + const boneinfo &b = bones[i]; + d.normalize(); + if(b.interpparent<0) sc.bdata[b.interpindex] = d; + else sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d); + float angle; + if(b.pitchscale) { angle = b.pitchscale*pitch + b.pitchoffset; if(b.pitchmin || b.pitchmax) angle = clamp(angle, b.pitchmin, b.pitchmax); } + else if(b.correctindex >= 0) angle = pitchcorrects[b.correctindex].pitchangle; + else continue; + if(as->cur.anim&ANIM_NOPITCH || (as->interp < 1 && as->prev.anim&ANIM_NOPITCH)) + angle *= (as->cur.anim&ANIM_NOPITCH ? 0 : as->interp) + (as->interp < 1 && as->prev.anim&ANIM_NOPITCH ? 0 : 1-as->interp); + sc.bdata[b.interpindex].mulorient(quat(axis, angle*RAD), b.base); + } + loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]); + } + + void initragdoll(ragdolldata &d, skelcacheentry &sc, part *p) + { + const dualquat *bdata = sc.bdata; + loopv(ragdoll->joints) + { + const ragdollskel::joint &j = ragdoll->joints[i]; + const boneinfo &b = bones[j.bone]; + const dualquat &q = bdata[b.interpindex]; + loopk(3) if(j.vert[k] >= 0) + { + ragdollskel::vert &v = ragdoll->verts[j.vert[k]]; + ragdolldata::vert &dv = d.verts[j.vert[k]]; + dv.pos.add(q.transform(v.pos).mul(v.weight)); + } + } + if(ragdoll->animjoints) loopv(ragdoll->joints) + { + const ragdollskel::joint &j = ragdoll->joints[i]; + const boneinfo &b = bones[j.bone]; + const dualquat &q = bdata[b.interpindex]; + d.calcanimjoint(i, matrix4x3(q)); + } + loopv(ragdoll->verts) + { + ragdolldata::vert &dv = d.verts[i]; + matrixstack[matrixpos].transform(vec(dv.pos).add(p->translate).mul(p->model->scale), dv.pos); + } + loopv(ragdoll->reljoints) + { + const ragdollskel::reljoint &r = ragdoll->reljoints[i]; + const ragdollskel::joint &j = ragdoll->joints[r.parent]; + const boneinfo &br = bones[r.bone], &bj = bones[j.bone]; + d.reljoints[i].mul(dualquat(bdata[bj.interpindex]).invert(), bdata[br.interpindex]); + } + } + + void genragdollbones(ragdolldata &d, skelcacheentry &sc, part *p) + { + if(!sc.bdata) sc.bdata = new dualquat[numinterpbones]; + sc.nextversion(); + loopv(ragdoll->joints) + { + const ragdollskel::joint &j = ragdoll->joints[i]; + const boneinfo &b = bones[j.bone]; + vec pos(0, 0, 0); + loopk(3) if(j.vert[k]>=0) pos.add(d.verts[j.vert[k]].pos); + pos.mul(j.weight/p->model->scale).sub(p->translate); + matrix4x3 m; + m.mul(d.tris[j.tri], pos, d.animjoints ? d.animjoints[i] : j.orient); + sc.bdata[b.interpindex] = dualquat(m); + } + loopv(ragdoll->reljoints) + { + const ragdollskel::reljoint &r = ragdoll->reljoints[i]; + const ragdollskel::joint &j = ragdoll->joints[r.parent]; + const boneinfo &br = bones[r.bone], &bj = bones[j.bone]; + sc.bdata[br.interpindex].mul(sc.bdata[bj.interpindex], d.reljoints[i]); + } + loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]); + } + + void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) + { + matrix4x3 t; + t.mul(bones[tags[i].bone].base, tags[i].matrix); + t.posttranslate(p->translate, p->model->scale); + n.mul(m, t); + } + + void calctags(part *p, skelcacheentry *sc = NULL) + { + loopv(p->links) + { + linkedpart &l = p->links[i]; + tag &t = tags[l.tag]; + dualquat q; + if(sc) q.mul(sc->bdata[bones[t.bone].interpindex], bones[t.bone].base); + else q = bones[t.bone].base; + matrix4x3 m; + m.mul(q, t.matrix); + m.d.add(p->translate).mul(p->model->scale); + l.matrix = m; + } + } + + void cleanup(bool full = true) + { + loopv(skelcache) + { + skelcacheentry &sc = skelcache[i]; + loopj(MAXANIMPARTS) sc.as[j].cur.fr1 = -1; + DELETEA(sc.bdata); + } + skelcache.setsize(0); + blendoffsets.clear(); + if(full) loopv(users) users[i]->cleanup(); + } + + bool canpreload() { return !numframes || gpuaccelerate(); } + + void preload() + { + if(!numframes) return; + if(skelcache.empty()) + { + usegpuskel = gpuaccelerate(); + } + } + + skelcacheentry &checkskelcache(part *p, const animstate *as, float pitch, const vec &axis, const vec &forward, ragdolldata *rdata) + { + if(skelcache.empty()) + { + usegpuskel = gpuaccelerate(); + } + + int numanimparts = ((skelpart *)as->owner)->numanimparts; + uchar *partmask = ((skelpart *)as->owner)->partmask; + skelcacheentry *sc = NULL; + bool match = false; + loopv(skelcache) + { + skelcacheentry &c = skelcache[i]; + loopj(numanimparts) if(c.as[j]!=as[j]) goto mismatch; + if(c.pitch != pitch || c.partmask != partmask || c.ragdoll != rdata || (rdata && c.millis < rdata->lastmove)) goto mismatch; + match = true; + sc = &c; + break; + mismatch: + if(c.millis < lastmillis) { sc = &c; break; } + } + if(!sc) sc = &skelcache.add(); + if(!match) + { + loopi(numanimparts) sc->as[i] = as[i]; + sc->pitch = pitch; + sc->partmask = partmask; + sc->ragdoll = rdata; + if(rdata) genragdollbones(*rdata, *sc, p); + else interpbones(as, pitch, axis, forward, numanimparts, partmask, *sc); + } + sc->millis = lastmillis; + return *sc; + } + + int getblendoffset(UniformLoc &u) + { + int &offset = blendoffsets.access(Shader::lastshader->program, -1); + if(offset < 0) + { + defformatstring(offsetname, "%s[%d]", u.name, 2*numgpubones); + offset = glGetUniformLocation_(Shader::lastshader->program, offsetname); + } + return offset; + } + + void setglslbones(UniformLoc &u, skelcacheentry &sc, skelcacheentry &bc, int count) + { + if(u.version == bc.version && u.data == bc.bdata) return; + glUniform4fv_(u.loc, 2*numgpubones, sc.bdata[0].real.v); + if(count > 0) + { + int offset = getblendoffset(u); + if(offset >= 0) glUniform4fv_(offset, 2*count, bc.bdata[0].real.v); + } + u.version = bc.version; + u.data = bc.bdata; + } + + void setgpubones(skelcacheentry &sc, blendcacheentry *bc, int count) + { + if(!Shader::lastshader) return; + if(Shader::lastshader->uniformlocs.length() < 1) return; + UniformLoc &u = Shader::lastshader->uniformlocs[0]; + setglslbones(u, sc, bc ? *bc : sc, count); + } + + bool shouldcleanup() const + { + return numframes && (skelcache.empty() || gpuaccelerate()!=usegpuskel); + } + }; + + struct skelmeshgroup : meshgroup + { + skeleton *skel; + + vector blendcombos; + int numblends[4]; + + static const int MAXBLENDCACHE = 16; + blendcacheentry blendcache[MAXBLENDCACHE]; + + static const int MAXVBOCACHE = 16; + vbocacheentry vbocache[MAXVBOCACHE]; + + ushort *edata; + GLuint ebuf; + bool vtangents; + int vlen, vertsize, vblends, vweights; + uchar *vdata; + + skelmeshgroup() : skel(NULL), edata(NULL), ebuf(0), vtangents(false), vlen(0), vertsize(0), vblends(0), vweights(0), vdata(NULL) + { + memset(numblends, 0, sizeof(numblends)); + } + + virtual ~skelmeshgroup() + { + if(skel) + { + if(skel->shared) skel->users.removeobj(this); + else DELETEP(skel); + } + if(ebuf) glDeleteBuffers_(1, &ebuf); + loopi(MAXBLENDCACHE) + { + DELETEA(blendcache[i].bdata); + } + loopi(MAXVBOCACHE) + { + if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf); + } + DELETEA(vdata); + } + + void shareskeleton(char *name) + { + if(!name) + { + skel = new skeleton; + skel->users.add(this); + return; + } + + static hashnameset skeletons; + if(skeletons.access(name)) skel = skeletons[name]; + else + { + skel = new skeleton; + skel->name = newstring(name); + skeletons.add(skel); + } + skel->users.add(this); + skel->shared++; + } + + int findtag(const char *name) + { + return skel->findtag(name); + } + + void *animkey() { return skel; } + int totalframes() const { return max(skel->numframes, 1); } + + virtual skelanimspec *loadanim(const char *filename) { (void) filename; return NULL; } + + void genvbo(bool tangents, vbocacheentry &vc) + { + if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf); + if(ebuf) return; + + vector idxs; + + if(tangents) loopv(meshes) ((skelmesh *)meshes[i])->calctangents(); + + vtangents = tangents; + vlen = 0; + vblends = 0; + if(skel->numframes && !skel->usegpuskel) + { + vweights = 1; + loopv(blendcombos) + { + blendcombo &c = blendcombos[i]; + c.interpindex = c.weights[1] ? skel->numgpubones + vblends++ : -1; + } + + vertsize = tangents ? sizeof(vvertbump) : sizeof(vvertn); + loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo(idxs, vlen); + DELETEA(vdata); + vdata = new uchar[vlen*vertsize]; + #define FILLVDATA(type) do { \ + loopv(meshes) ((skelmesh *)meshes[i])->fillverts((type *)vdata); \ + } while(0) + if(tangents) FILLVDATA(vvertbump); + else FILLVDATA(vvertn); + #undef FILLVDATA + } + else + { + if(skel->numframes) + { + vweights = 4; + int availbones = skel->availgpubones() - skel->numgpubones; + while(vweights > 1 && availbones >= numblends[vweights-1]) availbones -= numblends[--vweights]; + loopv(blendcombos) + { + blendcombo &c = blendcombos[i]; + c.interpindex = c.size() > vweights ? skel->numgpubones + vblends++ : -1; + } + } + else + { + vweights = 0; + loopv(blendcombos) blendcombos[i].interpindex = -1; + } + + gle::bindvbo(vc.vbuf); + #define GENVBO(type, args) do { \ + vertsize = sizeof(type); \ + vector vverts; \ + loopv(meshes) vlen += ((skelmesh *)meshes[i])->genvbo args; \ + glBufferData_(GL_ARRAY_BUFFER, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW); \ + } while(0) + #define GENVBOANIM(type) GENVBO(type, (idxs, vlen, vverts)) + #define GENVBOSTAT(type) GENVBO(type, (idxs, vlen, vverts, htdata, htlen)) + if(skel->numframes) + { + if(tangents) GENVBOANIM(vvertbumpw); + else GENVBOANIM(vvertnw); + } + else + { + int numverts = 0, htlen = 128; + loopv(meshes) numverts += ((skelmesh *)meshes[i])->numverts; + while(htlen < numverts) htlen *= 2; + if(numverts*4 > htlen*3) htlen *= 2; + int *htdata = new int[htlen]; + memset(htdata, -1, htlen*sizeof(int)); + if(tangents) GENVBOSTAT(vvertbump); + else GENVBOSTAT(vvertn); + delete[] htdata; + } + #undef GENVBO + #undef GENVBOANIM + #undef GENVBOSTAT + gle::clearvbo(); + } + + glGenBuffers_(1, &ebuf); + gle::bindebo(ebuf); + glBufferData_(GL_ELEMENT_ARRAY_BUFFER, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW); + gle::clearebo(); + } + + void bindvbo(const animstate *as, vbocacheentry &vc, skelcacheentry *sc = NULL, blendcacheentry *bc = NULL) + { + vvert *vverts = 0; + bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize); + if(as->cur.anim&ANIM_NOSKIN) + { + if(enabletc) disabletc(); + if(enablenormals) disablenormals(); + if(enabletangents) disabletangents(); + } + else + { + if(vtangents) + { + if(enablenormals) disablenormals(); + vvertbump *vvertbumps = 0; + bindtangents(&vvertbumps->tangent, vertsize); + } + else + { + if(enabletangents) disabletangents(); + vvertn *vvertns = 0; + bindnormals(&vvertns->norm, vertsize); + } + + bindtc(&vverts->tc, vertsize); + } + if(!sc || !skel->usegpuskel) + { + if(enablebones) disablebones(); + } + else + { + if(vtangents) + { + vvertbumpw *vvertbumpws = 0; + bindbones(vvertbumpws->weights, vvertbumpws->bones, vertsize); + } + else + { + vvertnw *vvertnws = 0; + bindbones(vvertnws->weights, vvertnws->bones, vertsize); + } + } + } + + void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) + { + skel->concattagtransform(p, i, m, n); + } + + int addblendcombo(const blendcombo &c) + { + loopv(blendcombos) if(blendcombos[i]==c) + { + blendcombos[i].uses += c.uses; + return i; + } + numblends[c.size()-1]++; + blendcombo &a = blendcombos.add(c); + return a.interpindex = blendcombos.length()-1; + } + + void sortblendcombos() + { + blendcombos.sort(blendcombo::sortcmp); + int *remap = new int[blendcombos.length()]; + loopv(blendcombos) remap[blendcombos[i].interpindex] = i; + loopv(meshes) + { + skelmesh *m = (skelmesh *)meshes[i]; + loopj(m->numverts) + { + vert &v = m->verts[j]; + v.blend = remap[v.blend]; + } + } + delete[] remap; + } + + int remapblend(int blend) + { + const blendcombo &c = blendcombos[blend]; + return c.weights[1] ? c.interpindex : c.interpbones[0]; + } + + static inline void blendbones(dualquat &d, const dualquat *bdata, const blendcombo &c) + { + d = bdata[c.interpbones[0]]; + d.mul(c.weights[0]); + d.accumulate(bdata[c.interpbones[1]], c.weights[1]); + if(c.weights[2]) + { + d.accumulate(bdata[c.interpbones[2]], c.weights[2]); + if(c.weights[3]) d.accumulate(bdata[c.interpbones[3]], c.weights[3]); + } + } + + void blendbones(const skelcacheentry &sc, blendcacheentry &bc) + { + bc.nextversion(); + if(!bc.bdata) bc.bdata = new dualquat[vblends]; + dualquat *dst = bc.bdata - skel->numgpubones; + bool normalize = !skel->usegpuskel || vweights<=1; + loopv(blendcombos) + { + const blendcombo &c = blendcombos[i]; + if(c.interpindex<0) break; + dualquat &d = dst[c.interpindex]; + blendbones(d, sc.bdata, c); + if(normalize) d.normalize(); + } + } + + void cleanup() + { + loopi(MAXBLENDCACHE) + { + blendcacheentry &c = blendcache[i]; + DELETEA(c.bdata); + c.owner = -1; + } + loopi(MAXVBOCACHE) + { + vbocacheentry &c = vbocache[i]; + if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; } + c.owner = -1; + } + if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } + if(skel) skel->cleanup(false); + } + + #define SEARCHCACHE(cachesize, cacheentry, cache, reusecheck) \ + loopi(cachesize) \ + { \ + cacheentry &c = cache[i]; \ + if(c.owner==owner) \ + { \ + if(c==sc) return c; \ + else c.owner = -1; \ + break; \ + } \ + } \ + loopi(cachesize-1) \ + { \ + cacheentry &c = cache[i]; \ + if(reusecheck c.owner < 0 || c.millis < lastmillis) \ + return c; \ + } \ + return cache[cachesize-1]; + + vbocacheentry &checkvbocache(skelcacheentry &sc, int owner) + { + SEARCHCACHE(MAXVBOCACHE, vbocacheentry, vbocache, !c.vbuf || ); + } + + blendcacheentry &checkblendcache(skelcacheentry &sc, int owner) + { + SEARCHCACHE(MAXBLENDCACHE, blendcacheentry, blendcache, ) + } + + void preload(part *p) + { + if(!skel->canpreload()) return; + bool tangents = false; + loopv(p->skins) if(p->skins[i].tangents()) tangents = true; + if(skel->shouldcleanup()) skel->cleanup(); + else if(tangents!=vtangents) cleanup(); + skel->preload(); + if(!vbocache->vbuf) genvbo(tangents, *vbocache); + } + + void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) + { + bool tangents = false; + loopv(p->skins) if(p->skins[i].tangents()) tangents = true; + if(skel->shouldcleanup()) { skel->cleanup(); disablevbo(); } + else if(tangents!=vtangents) { cleanup(); disablevbo(); } + + if(!skel->numframes) + { + if(!(as->cur.anim&ANIM_NORENDER)) + { + if(!vbocache->vbuf) genvbo(tangents, *vbocache); + bindvbo(as, *vbocache); + loopv(meshes) + { + skelmesh *m = (skelmesh *)meshes[i]; + p->skins[i].bind(m, as); + m->render(as, p->skins[i], *vbocache); + } + } + skel->calctags(p); + return; + } + + skelcacheentry &sc = skel->checkskelcache(p, as, pitch, axis, forward, as->cur.anim&ANIM_RAGDOLL || !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll ? NULL : d->ragdoll); + if(!(as->cur.anim&ANIM_NORENDER)) + { + int owner = &sc-&skel->skelcache[0]; + vbocacheentry &vc = skel->usegpuskel ? *vbocache : checkvbocache(sc, owner); + vc.millis = lastmillis; + if(!vc.vbuf) genvbo(tangents, vc); + blendcacheentry *bc = NULL; + if(vblends) + { + bc = &checkblendcache(sc, owner); + bc->millis = lastmillis; + if(bc->owner!=owner) + { + bc->owner = owner; + *(animcacheentry *)bc = sc; + blendbones(sc, *bc); + } + } + if(!skel->usegpuskel && vc.owner!=owner) + { + vc.owner = owner; + (animcacheentry &)vc = sc; + loopv(meshes) + { + skelmesh &m = *(skelmesh *)meshes[i]; + m.interpverts(sc.bdata, bc ? bc->bdata : NULL, tangents, vdata + m.voffset*vertsize, p->skins[i]); + } + gle::bindvbo(vc.vbuf); + glBufferData_(GL_ARRAY_BUFFER, vlen*vertsize, vdata, GL_STREAM_DRAW); + } + + bindvbo(as, vc, &sc, bc); + loopv(meshes) + { + skelmesh *m = (skelmesh *)meshes[i]; + p->skins[i].bind(m, as); + if(skel->usegpuskel) skel->setgpubones(sc, bc, vblends); + m->render(as, p->skins[i], vc); + } + } + + skel->calctags(p, &sc); + + if(as->cur.anim&ANIM_RAGDOLL && skel->ragdoll && !d->ragdoll) + { + d->ragdoll = new ragdolldata(skel->ragdoll, p->model->scale); + skel->initragdoll(*d->ragdoll, sc, p); + d->ragdoll->init(d); + } + } + }; + + struct animpartmask + { + animpartmask *next; + int numbones; + uchar bones[1]; + }; + + struct skelpart : part + { + animpartmask *buildingpartmask; + + uchar *partmask; + + skelpart(animmodel *model, int index = 0) : part(model, index), buildingpartmask(NULL), partmask(NULL) + { + } + + virtual ~skelpart() + { + DELETEA(buildingpartmask); + } + + uchar *sharepartmask(animpartmask *o) + { + static animpartmask *partmasks = NULL; + animpartmask *p = partmasks; + for(; p; p = p->next) if(p->numbones==o->numbones && !memcmp(p->bones, o->bones, p->numbones)) + { + delete[] (uchar *)o; + return p->bones; + } + + o->next = p; + partmasks = o; + return o->bones; + } + + animpartmask *newpartmask() + { + animpartmask *p = (animpartmask *)new uchar[sizeof(animpartmask) + ((skelmeshgroup *)meshes)->skel->numbones-1]; + p->numbones = ((skelmeshgroup *)meshes)->skel->numbones; + memset(p->bones, 0, p->numbones); + return p; + } + + void initanimparts() + { + DELETEA(buildingpartmask); + buildingpartmask = newpartmask(); + } + + bool addanimpart(ushort *bonemask) + { + if(!buildingpartmask || numanimparts>=MAXANIMPARTS) return false; + ((skelmeshgroup *)meshes)->skel->applybonemask(bonemask, buildingpartmask->bones, numanimparts); + numanimparts++; + return true; + } + + void endanimparts() + { + if(buildingpartmask) + { + partmask = sharepartmask(buildingpartmask); + buildingpartmask = NULL; + } + + ((skelmeshgroup *)meshes)->skel->optimize(); + } + + void loaded() + { + endanimparts(); + part::loaded(); + } + }; + + skelmodel(const char *name) : animmodel(name) + { + } + + int linktype(animmodel *m) const + { + return type()==m->type() && + ((skelmeshgroup *)parts[0]->meshes)->skel == ((skelmeshgroup *)m->parts[0]->meshes)->skel ? + LINK_REUSE : + LINK_TAG; + } + + bool skeletal() const { return true; } + + skelpart &addpart() + { + flushpart(); + skelpart *p = new skelpart(this, parts.length()); + parts.add(p); + return *p; + } }; struct skeladjustment { - float yaw, pitch, roll; - vec translate; - - skeladjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {} - - void adjust(dualquat &dq) - { - if(yaw) dq.mulorient(quat(vec(0, 0, 1), yaw*RAD)); - if(pitch) dq.mulorient(quat(vec(0, -1, 0), pitch*RAD)); - if(roll) dq.mulorient(quat(vec(-1, 0, 0), roll*RAD)); - if(!translate.iszero()) dq.translate(translate); - } + float yaw, pitch, roll; + vec translate; + + skeladjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {} + + void adjust(dualquat &dq) + { + if(yaw) dq.mulorient(quat(vec(0, 0, 1), yaw*RAD)); + if(pitch) dq.mulorient(quat(vec(0, -1, 0), pitch*RAD)); + if(roll) dq.mulorient(quat(vec(-1, 0, 0), roll*RAD)); + if(!translate.iszero()) dq.translate(translate); + } }; template struct skelloader : modelloader { - static vector adjustments; + static vector adjustments; - skelloader(const char *name) : modelloader(name) {} + skelloader(const char *name) : modelloader(name) {} - void flushpart() - { - adjustments.setsize(0); - } + void flushpart() + { + adjustments.setsize(0); + } }; template vector skelloader::adjustments; template struct skelcommands : modelcommands { - typedef modelcommands commands; - typedef struct MDL::skeleton skeleton; - typedef struct MDL::skelmeshgroup meshgroup; - typedef struct MDL::skelpart part; - typedef struct MDL::skin skin; - typedef struct MDL::boneinfo boneinfo; - typedef struct MDL::skelanimspec animspec; - typedef struct MDL::pitchdep pitchdep; - typedef struct MDL::pitchtarget pitchtarget; - typedef struct MDL::pitchcorrect pitchcorrect; - - static void loadpart(char *meshfile, char *skelname, float *smooth) - { - if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - defformatstring(filename, "%s/%s", MDL::dir, meshfile); - part &mdl = MDL::loading->addpart(); - mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; - mdl.meshes = MDL::loading->sharemeshes(path(filename), skelname[0] ? skelname : NULL, double(*smooth > 0 ? cos(clamp(*smooth, 0.0f, 180.0f)*RAD) : 2)); - if(!mdl.meshes) conoutf(CON_ERROR, "could not load %s", filename); - else - { - mdl.initanimparts(); - mdl.initskins(); - } - } - - static void settag(char *name, char *tagname, float *tx, float *ty, float *tz, float *rx, float *ry, float *rz) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - part &mdl = *(part *)MDL::loading->parts.last(); - int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1; - if(i >= 0) - { - float cx = *rx ? cosf(*rx/2*RAD) : 1, sx = *rx ? sinf(*rx/2*RAD) : 0, - cy = *ry ? cosf(*ry/2*RAD) : 1, sy = *ry ? sinf(*ry/2*RAD) : 0, - cz = *rz ? cosf(*rz/2*RAD) : 1, sz = *rz ? sinf(*rz/2*RAD) : 0; - matrix4x3 m(matrix3(quat(sx*cy*cz - cx*sy*sz, cx*sy*cz + sx*cy*sz, cx*cy*sz - sx*sy*cz, cx*cy*cz + sx*sy*sz)), - vec(*tx, *ty, *tz)); - ((meshgroup *)mdl.meshes)->skel->addtag(tagname, i, m); - return; - } - conoutf(CON_ERROR, "could not find bone %s for tag %s", name, tagname); - } - - static void setpitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - part &mdl = *(part *)MDL::loading->parts.last(); - - if(name[0]) - { - int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1; - if(i>=0) - { - boneinfo &b = ((meshgroup *)mdl.meshes)->skel->bones[i]; - b.pitchscale = *pitchscale; - b.pitchoffset = *pitchoffset; - if(*pitchmin || *pitchmax) - { - b.pitchmin = *pitchmin; - b.pitchmax = *pitchmax; - } - else - { - b.pitchmin = -360*fabs(b.pitchscale) + b.pitchoffset; - b.pitchmax = 360*fabs(b.pitchscale) + b.pitchoffset; - } - return; - } - conoutf(CON_ERROR, "could not find bone %s to pitch", name); - return; - } - - mdl.pitchscale = *pitchscale; - mdl.pitchoffset = *pitchoffset; - if(*pitchmin || *pitchmax) - { - mdl.pitchmin = *pitchmin; - mdl.pitchmax = *pitchmax; - } - else - { - mdl.pitchmin = -360*fabs(mdl.pitchscale) + mdl.pitchoffset; - mdl.pitchmax = 360*fabs(mdl.pitchscale) + mdl.pitchoffset; - } - } - - static void setpitchtarget(char *name, char *animfile, int *frameoffset, float *pitchmin, float *pitchmax) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - part &mdl = *(part *)MDL::loading->parts.last(); - if(!mdl.meshes) return; - defformatstring(filename, "%s/%s", MDL::dir, animfile); - animspec *sa = ((meshgroup *)mdl.meshes)->loadanim(path(filename)); - if(!sa) { conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename); return; } - skeleton *skel = ((meshgroup *)mdl.meshes)->skel; - int bone = skel ? skel->findbone(name) : -1; - if(bone < 0) - { - conoutf(CON_ERROR, "could not find bone %s to pitch target", name); - return; - } - loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == bone) return; - pitchtarget &t = skel->pitchtargets.add(); - t.bone = bone; - t.frame = sa->frame + clamp(*frameoffset, 0, sa->range-1); - t.pitchmin = *pitchmin; - t.pitchmax = *pitchmax; - } - - static void setpitchcorrect(char *name, char *targetname, float *scale, float *pitchmin, float *pitchmax) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - part &mdl = *(part *)MDL::loading->parts.last(); - if(!mdl.meshes) return; - skeleton *skel = ((meshgroup *)mdl.meshes)->skel; - int bone = skel ? skel->findbone(name) : -1; - if(bone < 0) - { - conoutf(CON_ERROR, "could not find bone %s to pitch correct", name); - return; - } - if(skel->findpitchcorrect(bone) >= 0) return; - int targetbone = skel->findbone(targetname), target = -1; - if(targetbone >= 0) loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == targetbone) { target = i; break; } - if(target < 0) - { - conoutf(CON_ERROR, "could not find pitch target %s to pitch correct %s", targetname, name); - return; - } - pitchcorrect c; - c.bone = bone; - c.target = target; - c.pitchmin = *pitchmin; - c.pitchmax = *pitchmax; - c.pitchscale = *scale; - int pos = skel->pitchcorrects.length(); - loopv(skel->pitchcorrects) if(bone <= skel->pitchcorrects[i].bone) { pos = i; break; } - skel->pitchcorrects.insert(pos, c); - } - - static void setanim(char *anim, char *animfile, float *speed, int *priority, int *startoffset, int *endoffset) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - - vector anims; - findanims(anim, anims); - if(anims.empty()) conoutf(CON_ERROR, "could not find animation %s", anim); - else - { - part *p = (part *)MDL::loading->parts.last(); - if(!p->meshes) return; - defformatstring(filename, "%s/%s", MDL::dir, animfile); - animspec *sa = ((meshgroup *)p->meshes)->loadanim(path(filename)); - if(!sa) conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename); - else loopv(anims) - { - int start = sa->frame, end = sa->range; - if(*startoffset > 0) start += min(*startoffset, end-1); - else if(*startoffset < 0) start += max(end + *startoffset, 0); - end -= start - sa->frame; - if(*endoffset > 0) end = min(end, *endoffset); - else if(*endoffset < 0) end = max(end + *endoffset, 1); - MDL::loading->parts.last()->setanim(p->numanimparts-1, anims[i], start, end, *speed, *priority); - } - } - } - - static void setanimpart(char *maskstr) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - - part *p = (part *)MDL::loading->parts.last(); - - vector bonestrs; - explodelist(maskstr, bonestrs); - vector bonemask; - loopv(bonestrs) - { - char *bonestr = bonestrs[i]; - int bone = p->meshes ? ((meshgroup *)p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr+1 : bonestr) : -1; - if(bone<0) { conoutf(CON_ERROR, "could not find bone %s for anim part mask [%s]", bonestr, maskstr); bonestrs.deletearrays(); return; } - bonemask.add(bone | (bonestr[0]=='!' ? BONEMASK_NOT : 0)); - } - bonestrs.deletearrays(); - bonemask.sort(); - if(bonemask.length()) bonemask.add(BONEMASK_END); - - if(!p->addanimpart(bonemask.getbuf())) conoutf(CON_ERROR, "too many animation parts"); - } - - static void setadjust(char *name, float *yaw, float *pitch, float *roll, float *tx, float *ty, float *tz) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - part &mdl = *(part *)MDL::loading->parts.last(); - - if(!name[0]) return; - int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1; - if(i < 0) { conoutf(CON_ERROR, "could not find bone %s to adjust", name); return; } - while(!MDL::adjustments.inrange(i)) MDL::adjustments.add(skeladjustment(0, 0, 0, vec(0, 0, 0))); - MDL::adjustments[i] = skeladjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4)); - } - - skelcommands() - { - if(MDL::multiparted()) this->modelcommand(loadpart, "load", "ssf"); - this->modelcommand(settag, "tag", "ssffffff"); - this->modelcommand(setpitch, "pitch", "sffff"); - this->modelcommand(setpitchtarget, "pitchtarget", "ssiff"); - this->modelcommand(setpitchcorrect, "pitchcorrect", "ssfff"); - if(MDL::animated()) - { - this->modelcommand(setanim, "anim", "ssfiii"); - this->modelcommand(setanimpart, "animpart", "s"); - this->modelcommand(setadjust, "adjust", "sffffff"); - } - } + typedef modelcommands commands; + typedef struct MDL::skeleton skeleton; + typedef struct MDL::skelmeshgroup meshgroup; + typedef struct MDL::skelpart part; + typedef struct MDL::skin skin; + typedef struct MDL::boneinfo boneinfo; + typedef struct MDL::skelanimspec animspec; + typedef struct MDL::pitchdep pitchdep; + typedef struct MDL::pitchtarget pitchtarget; + typedef struct MDL::pitchcorrect pitchcorrect; + + static void loadpart(char *meshfile, char *skelname, float *smooth) + { + if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + defformatstring(filename, "%s/%s", MDL::dir, meshfile); + part &mdl = MDL::loading->addpart(); + mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; + mdl.meshes = MDL::loading->sharemeshes(path(filename), skelname[0] ? skelname : NULL, double(*smooth > 0 ? cos(clamp(*smooth, 0.0f, 180.0f)*RAD) : 2)); + if(!mdl.meshes) conoutf(CON_ERROR, "could not load %s", filename); + else + { + mdl.initanimparts(); + mdl.initskins(); + } + } + + static void settag(char *name, char *tagname, float *tx, float *ty, float *tz, float *rx, float *ry, float *rz) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + part &mdl = *(part *)MDL::loading->parts.last(); + int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1; + if(i >= 0) + { + float cx = *rx ? cosf(*rx/2*RAD) : 1, sx = *rx ? sinf(*rx/2*RAD) : 0, + cy = *ry ? cosf(*ry/2*RAD) : 1, sy = *ry ? sinf(*ry/2*RAD) : 0, + cz = *rz ? cosf(*rz/2*RAD) : 1, sz = *rz ? sinf(*rz/2*RAD) : 0; + matrix4x3 m(matrix3(quat(sx*cy*cz - cx*sy*sz, cx*sy*cz + sx*cy*sz, cx*cy*sz - sx*sy*cz, cx*cy*cz + sx*sy*sz)), + vec(*tx, *ty, *tz)); + ((meshgroup *)mdl.meshes)->skel->addtag(tagname, i, m); + return; + } + conoutf(CON_ERROR, "could not find bone %s for tag %s", name, tagname); + } + + static void setpitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + part &mdl = *(part *)MDL::loading->parts.last(); + + if(name[0]) + { + int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1; + if(i>=0) + { + boneinfo &b = ((meshgroup *)mdl.meshes)->skel->bones[i]; + b.pitchscale = *pitchscale; + b.pitchoffset = *pitchoffset; + if(*pitchmin || *pitchmax) + { + b.pitchmin = *pitchmin; + b.pitchmax = *pitchmax; + } + else + { + b.pitchmin = -360*fabs(b.pitchscale) + b.pitchoffset; + b.pitchmax = 360*fabs(b.pitchscale) + b.pitchoffset; + } + return; + } + conoutf(CON_ERROR, "could not find bone %s to pitch", name); + return; + } + + mdl.pitchscale = *pitchscale; + mdl.pitchoffset = *pitchoffset; + if(*pitchmin || *pitchmax) + { + mdl.pitchmin = *pitchmin; + mdl.pitchmax = *pitchmax; + } + else + { + mdl.pitchmin = -360*fabs(mdl.pitchscale) + mdl.pitchoffset; + mdl.pitchmax = 360*fabs(mdl.pitchscale) + mdl.pitchoffset; + } + } + + static void setpitchtarget(char *name, char *animfile, int *frameoffset, float *pitchmin, float *pitchmax) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + part &mdl = *(part *)MDL::loading->parts.last(); + if(!mdl.meshes) return; + defformatstring(filename, "%s/%s", MDL::dir, animfile); + animspec *sa = ((meshgroup *)mdl.meshes)->loadanim(path(filename)); + if(!sa) { conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename); return; } + skeleton *skel = ((meshgroup *)mdl.meshes)->skel; + int bone = skel ? skel->findbone(name) : -1; + if(bone < 0) + { + conoutf(CON_ERROR, "could not find bone %s to pitch target", name); + return; + } + loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == bone) return; + pitchtarget &t = skel->pitchtargets.add(); + t.bone = bone; + t.frame = sa->frame + clamp(*frameoffset, 0, sa->range-1); + t.pitchmin = *pitchmin; + t.pitchmax = *pitchmax; + } + + static void setpitchcorrect(char *name, char *targetname, float *scale, float *pitchmin, float *pitchmax) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + part &mdl = *(part *)MDL::loading->parts.last(); + if(!mdl.meshes) return; + skeleton *skel = ((meshgroup *)mdl.meshes)->skel; + int bone = skel ? skel->findbone(name) : -1; + if(bone < 0) + { + conoutf(CON_ERROR, "could not find bone %s to pitch correct", name); + return; + } + if(skel->findpitchcorrect(bone) >= 0) return; + int targetbone = skel->findbone(targetname), target = -1; + if(targetbone >= 0) loopv(skel->pitchtargets) if(skel->pitchtargets[i].bone == targetbone) { target = i; break; } + if(target < 0) + { + conoutf(CON_ERROR, "could not find pitch target %s to pitch correct %s", targetname, name); + return; + } + pitchcorrect c; + c.bone = bone; + c.target = target; + c.pitchmin = *pitchmin; + c.pitchmax = *pitchmax; + c.pitchscale = *scale; + int pos = skel->pitchcorrects.length(); + loopv(skel->pitchcorrects) if(bone <= skel->pitchcorrects[i].bone) { pos = i; break; } + skel->pitchcorrects.insert(pos, c); + } + + static void setanim(char *anim, char *animfile, float *speed, int *priority, int *startoffset, int *endoffset) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + + vector anims; + findanims(anim, anims); + if(anims.empty()) conoutf(CON_ERROR, "could not find animation %s", anim); + else + { + part *p = (part *)MDL::loading->parts.last(); + if(!p->meshes) return; + defformatstring(filename, "%s/%s", MDL::dir, animfile); + animspec *sa = ((meshgroup *)p->meshes)->loadanim(path(filename)); + if(!sa) conoutf(CON_ERROR, "could not load %s anim file %s", MDL::formatname(), filename); + else loopv(anims) + { + int start = sa->frame, end = sa->range; + if(*startoffset > 0) start += min(*startoffset, end-1); + else if(*startoffset < 0) start += max(end + *startoffset, 0); + end -= start - sa->frame; + if(*endoffset > 0) end = min(end, *endoffset); + else if(*endoffset < 0) end = max(end + *endoffset, 1); + MDL::loading->parts.last()->setanim(p->numanimparts-1, anims[i], start, end, *speed, *priority); + } + } + } + + static void setanimpart(char *maskstr) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + + part *p = (part *)MDL::loading->parts.last(); + + vector bonestrs; + explodelist(maskstr, bonestrs); + vector bonemask; + loopv(bonestrs) + { + char *bonestr = bonestrs[i]; + int bone = p->meshes ? ((meshgroup *)p->meshes)->skel->findbone(bonestr[0]=='!' ? bonestr+1 : bonestr) : -1; + if(bone<0) { conoutf(CON_ERROR, "could not find bone %s for anim part mask [%s]", bonestr, maskstr); bonestrs.deletearrays(); return; } + bonemask.add(bone | (bonestr[0]=='!' ? BONEMASK_NOT : 0)); + } + bonestrs.deletearrays(); + bonemask.sort(); + if(bonemask.length()) bonemask.add(BONEMASK_END); + + if(!p->addanimpart(bonemask.getbuf())) conoutf(CON_ERROR, "too many animation parts"); + } + + static void setadjust(char *name, float *yaw, float *pitch, float *roll, float *tx, float *ty, float *tz) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + part &mdl = *(part *)MDL::loading->parts.last(); + + if(!name[0]) return; + int i = mdl.meshes ? ((meshgroup *)mdl.meshes)->skel->findbone(name) : -1; + if(i < 0) { conoutf(CON_ERROR, "could not find bone %s to adjust", name); return; } + while(!MDL::adjustments.inrange(i)) MDL::adjustments.add(skeladjustment(0, 0, 0, vec(0, 0, 0))); + MDL::adjustments[i] = skeladjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4)); + } + + skelcommands() + { + if(MDL::multiparted()) this->modelcommand(loadpart, "load", "ssf"); + this->modelcommand(settag, "tag", "ssffffff"); + this->modelcommand(setpitch, "pitch", "sffff"); + this->modelcommand(setpitchtarget, "pitchtarget", "ssiff"); + this->modelcommand(setpitchcorrect, "pitchcorrect", "ssfff"); + if(MDL::animated()) + { + this->modelcommand(setanim, "anim", "ssfiii"); + this->modelcommand(setanimpart, "animpart", "s"); + this->modelcommand(setadjust, "adjust", "sffffff"); + } + } }; diff --git a/src/engine/sound.cpp b/src/engine/sound.cpp index 18cc0ab..d10386d 100644 --- a/src/engine/sound.cpp +++ b/src/engine/sound.cpp @@ -7,115 +7,115 @@ bool nosound = true; struct soundsample { - char *name; - Mix_Chunk *chunk; + char *name; + Mix_Chunk *chunk; - soundsample() : name(NULL), chunk(NULL) {} - ~soundsample() { DELETEA(name); } + soundsample() : name(NULL), chunk(NULL) {} + ~soundsample() { DELETEA(name); } - void cleanup() { if(chunk) { Mix_FreeChunk(chunk); chunk = NULL; } } - bool load(bool msg = false); + void cleanup() { if(chunk) { Mix_FreeChunk(chunk); chunk = NULL; } } + bool load(bool msg = false); }; struct soundslot { - soundsample *sample; - int volume; + soundsample *sample; + int volume; }; struct soundconfig { - int slots, numslots; - int maxuses; + int slots, numslots; + int maxuses; - bool hasslot(const soundslot *p, const vector &v) const - { - return p >= v.getbuf() + slots && p < v.getbuf() + slots+numslots && slots+numslots < v.length(); - } + bool hasslot(const soundslot *p, const vector &v) const + { + return p >= v.getbuf() + slots && p < v.getbuf() + slots+numslots && slots+numslots < v.length(); + } - int chooseslot(int flags) const - { - if(flags&SND_NO_ALT || numslots <= 1) return slots; - if(flags&SND_USE_ALT) return slots + 1 + rnd(numslots - 1); - return slots + rnd(numslots); - } + int chooseslot(int flags) const + { + if(flags&SND_NO_ALT || numslots <= 1) return slots; + if(flags&SND_USE_ALT) return slots + 1 + rnd(numslots - 1); + return slots + rnd(numslots); + } }; struct soundchannel { - int id; - bool inuse; - vec loc; - soundslot *slot; - extentity *ent; - int radius, volume, pan, flags; - bool dirty; - - soundchannel(int id) : id(id) { reset(); } - - bool hasloc() const { return loc.x >= -1e15f; } - void clearloc() { loc = vec(-1e16f, -1e16f, -1e16f); } - - void reset() - { - inuse = false; - clearloc(); - slot = NULL; - ent = NULL; - radius = 0; - volume = -1; - pan = -1; - flags = 0; - dirty = false; - } + int id; + bool inuse; + vec loc; + soundslot *slot; + extentity *ent; + int radius, volume, pan, flags; + bool dirty; + + soundchannel(int id) : id(id) { reset(); } + + bool hasloc() const { return loc.x >= -1e15f; } + void clearloc() { loc = vec(-1e16f, -1e16f, -1e16f); } + + void reset() + { + inuse = false; + clearloc(); + slot = NULL; + ent = NULL; + radius = 0; + volume = -1; + pan = -1; + flags = 0; + dirty = false; + } }; vector channels; int maxchannels = 0; soundchannel &newchannel(int n, soundslot *slot, const vec *loc = NULL, extentity *ent = NULL, int flags = 0, int radius = 0) { - if(ent) - { - loc = &ent->o; - ent->flags |= EF_SOUND; - } - while(!channels.inrange(n)) channels.add(channels.length()); - soundchannel &chan = channels[n]; - chan.reset(); - chan.inuse = true; - if(loc) chan.loc = *loc; - chan.slot = slot; - chan.ent = ent; - chan.flags = 0; - chan.radius = radius; - return chan; + if(ent) + { + loc = &ent->o; + ent->flags |= EF_SOUND; + } + while(!channels.inrange(n)) channels.add(channels.length()); + soundchannel &chan = channels[n]; + chan.reset(); + chan.inuse = true; + if(loc) chan.loc = *loc; + chan.slot = slot; + chan.ent = ent; + chan.flags = 0; + chan.radius = radius; + return chan; } void freechannel(int n) { - if(!channels.inrange(n) || !channels[n].inuse) return; - soundchannel &chan = channels[n]; - chan.inuse = false; - if(chan.ent) chan.ent->flags &= ~EF_SOUND; + if(!channels.inrange(n) || !channels[n].inuse) return; + soundchannel &chan = channels[n]; + chan.inuse = false; + if(chan.ent) chan.ent->flags &= ~EF_SOUND; } void syncchannel(soundchannel &chan) { - if(!chan.dirty) return; - if(!Mix_FadingChannel(chan.id)) Mix_Volume(chan.id, chan.volume); - Mix_SetPanning(chan.id, 255-chan.pan, chan.pan); - chan.dirty = false; + if(!chan.dirty) return; + if(!Mix_FadingChannel(chan.id)) Mix_Volume(chan.id, chan.volume); + Mix_SetPanning(chan.id, 255-chan.pan, chan.pan); + chan.dirty = false; } void stopchannels() { - loopv(channels) - { - soundchannel &chan = channels[i]; - if(!chan.inuse) continue; - Mix_HaltChannel(i); - freechannel(i); - } + loopv(channels) + { + soundchannel &chan = channels[i]; + if(!chan.inuse) continue; + Mix_HaltChannel(i); + freechannel(i); + } } void setmusicvol(int musicvol); @@ -123,9 +123,9 @@ extern int musicvol; static int curvol = 0; VARFP(soundvol, 0, 255, 255, { - if(!soundvol) { stopchannels(); setmusicvol(0); } - else if(!curvol) setmusicvol(musicvol); - curvol = soundvol; + if(!soundvol) { stopchannels(); setmusicvol(0); } + else if(!curvol) setmusicvol(musicvol); + curvol = soundvol; }); VARFP(musicvol, 0, 128, 255, setmusicvol(soundvol ? musicvol : 0)); @@ -137,28 +137,27 @@ stream *musicstream = NULL; void setmusicvol(int musicvol) { - if(nosound) return; - if(music) Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255); + if(nosound) return; + if(music) Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255); } void stopmusic() { - if(nosound) return; - DELETEA(musicfile); - DELETEA(musicdonecmd); - if(music) - { - Mix_HaltMusic(); - Mix_FreeMusic(music); - music = NULL; - } - if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; } - DELETEP(musicstream); + if(nosound) return; + DELETEA(musicfile); + DELETEA(musicdonecmd); + if(music) + { + Mix_HaltMusic(); + Mix_FreeMusic(music); + music = NULL; + } + if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; } + DELETEP(musicstream); } -#define AUDIODRIVER "" bool shouldinitaudio = true; -SVARF(audiodriver, AUDIODRIVER, { shouldinitaudio = true; initwarning("sound configuration", INIT_RESET, CHANGE_SOUND); }); +SVARF(audiodriver, "", { shouldinitaudio = true; initwarning("sound configuration", INIT_RESET, CHANGE_SOUND); }); VARF(usesound, 0, 1, 1, { shouldinitaudio = true; initwarning("sound configuration", INIT_RESET, CHANGE_SOUND); }); VARF(soundchans, 1, 32, 128, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND)); VARF(soundfreq, 0, MIX_DEFAULT_FREQUENCY, 48000, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND)); @@ -166,377 +165,377 @@ VARF(soundbufferlen, 128, 1024, 4096, initwarning("sound configuration", INIT_RE bool initaudio() { - static string fallback = ""; - static bool initfallback = true; - static bool restorefallback = false; - if(initfallback) - { - initfallback = false; - if(char *env = SDL_getenv("SDL_AUDIODRIVER")) copystring(fallback, env); - } - if(!fallback[0] && audiodriver[0]) - { - vector drivers; - explodelist(audiodriver, drivers); - loopv(drivers) - { - restorefallback = true; - SDL_setenv("SDL_AUDIODRIVER", drivers[i], 1); - if(SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0) - { - drivers.deletearrays(); - return true; - } - } - drivers.deletearrays(); - } - if(restorefallback) - { - restorefallback = false; - unsetenv("SDL_AUDIODRIVER"); - } - if(SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0) return true; - conoutf(CON_ERROR, "sound init failed: %s", SDL_GetError()); - return false; + static string fallback = ""; + static bool initfallback = true; + static bool restorefallback = false; + if(initfallback) + { + initfallback = false; + if(char *env = SDL_getenv("SDL_AUDIODRIVER")) copystring(fallback, env); + } + if(!fallback[0] && audiodriver[0]) + { + vector drivers; + explodelist(audiodriver, drivers); + loopv(drivers) + { + restorefallback = true; + SDL_setenv("SDL_AUDIODRIVER", drivers[i], 1); + if(SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0) + { + drivers.deletearrays(); + return true; + } + } + drivers.deletearrays(); + } + if(restorefallback) + { + restorefallback = false; + unsetenv("SDL_AUDIODRIVER"); + } + if(SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0) return true; + conoutf(CON_ERROR, "sound init failed: %s", SDL_GetError()); + return false; } void initsound() { - SDL_version version; - SDL_GetVersion(&version); - if(version.major == 2 && version.minor == 0 && version.patch == 6) - { - nosound = true; - if(usesound) conoutf(CON_ERROR, "audio is broken in SDL 2.0.6"); - return; - } - - if(shouldinitaudio) - { - shouldinitaudio = false; - if(SDL_WasInit(SDL_INIT_AUDIO)) SDL_QuitSubSystem(SDL_INIT_AUDIO); - if(!usesound || !initaudio()) - { - nosound = true; - return; - } - } - - if(Mix_OpenAudio(soundfreq, MIX_DEFAULT_FORMAT, 2, soundbufferlen)<0) - { - nosound = true; - conoutf(CON_ERROR, "sound init failed (SDL_mixer): %s", Mix_GetError()); - return; - } + SDL_version version; + SDL_GetVersion(&version); + if(version.major == 2 && version.minor == 0 && version.patch == 6) + { + nosound = true; + if(usesound) conoutf(CON_ERROR, "audio is broken in SDL 2.0.6"); + return; + } + + if(shouldinitaudio) + { + shouldinitaudio = false; + if(SDL_WasInit(SDL_INIT_AUDIO)) SDL_QuitSubSystem(SDL_INIT_AUDIO); + if(!usesound || !initaudio()) + { + nosound = true; + return; + } + } + + if(Mix_OpenAudio(soundfreq, MIX_DEFAULT_FORMAT, 2, soundbufferlen)<0) + { + nosound = true; + conoutf(CON_ERROR, "sound init failed (SDL_mixer): %s", Mix_GetError()); + return; + } Mix_AllocateChannels(soundchans); - maxchannels = soundchans; - nosound = false; + maxchannels = soundchans; + nosound = false; } void musicdone() { - if(music) { Mix_HaltMusic(); Mix_FreeMusic(music); music = NULL; } - if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; } - DELETEP(musicstream); - DELETEA(musicfile); - if(!musicdonecmd) return; - char *cmd = musicdonecmd; - musicdonecmd = NULL; - execute(cmd); - delete[] cmd; + if(music) { Mix_HaltMusic(); Mix_FreeMusic(music); music = NULL; } + if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; } + DELETEP(musicstream); + DELETEA(musicfile); + if(!musicdonecmd) return; + char *cmd = musicdonecmd; + musicdonecmd = NULL; + execute(cmd); + delete[] cmd; } Mix_Music *loadmusic(const char *name) { - if(!musicstream) musicstream = openzipfile(name, "rb"); - if(musicstream) - { - if(!musicrw) musicrw = musicstream->rwops(); - if(!musicrw) DELETEP(musicstream); - } - if(musicrw) music = Mix_LoadMUSType_RW(musicrw, MUS_NONE, 0); - else music = Mix_LoadMUS(findfile(name, "rb")); - if(!music) - { - if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; } - DELETEP(musicstream); - } - return music; + if(!musicstream) musicstream = openzipfile(name, "rb"); + if(musicstream) + { + if(!musicrw) musicrw = musicstream->rwops(); + if(!musicrw) DELETEP(musicstream); + } + if(musicrw) music = Mix_LoadMUSType_RW(musicrw, MUS_NONE, 0); + else music = Mix_LoadMUS(findfile(name, "rb")); + if(!music) + { + if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; } + DELETEP(musicstream); + } + return music; } void startmusic(char *name, char *cmd) { - if(nosound) return; - stopmusic(); - if(soundvol && musicvol && *name) - { - defformatstring(file, "packages/%s", name); - path(file); - if(loadmusic(file)) - { - DELETEA(musicfile); - DELETEA(musicdonecmd); - musicfile = newstring(file); - if(cmd[0]) musicdonecmd = newstring(cmd); - Mix_PlayMusic(music, cmd[0] ? 0 : -1); - Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255); - intret(1); - } - else - { - conoutf(CON_ERROR, "could not play music: %s", file); - intret(0); - } - } + if(nosound) return; + stopmusic(); + if(soundvol && musicvol && *name) + { + defformatstring(file, "packages/%s", name); + path(file); + if(loadmusic(file)) + { + DELETEA(musicfile); + DELETEA(musicdonecmd); + musicfile = newstring(file); + if(cmd[0]) musicdonecmd = newstring(cmd); + Mix_PlayMusic(music, cmd[0] ? 0 : -1); + Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255); + intret(1); + } + else + { + conoutf(CON_ERROR, "could not play music: %s", file); + intret(0); + } + } } COMMANDN(music, startmusic, "ss"); static Mix_Chunk *loadwav(const char *name) { - Mix_Chunk *c = NULL; - stream *z = openzipfile(name, "rb"); - if(z) - { - SDL_RWops *rw = z->rwops(); - if(rw) - { - c = Mix_LoadWAV_RW(rw, 0); - SDL_FreeRW(rw); - } - delete z; - } - if(!c) c = Mix_LoadWAV(findfile(name, "rb")); - return c; + Mix_Chunk *c = NULL; + stream *z = openzipfile(name, "rb"); + if(z) + { + SDL_RWops *rw = z->rwops(); + if(rw) + { + c = Mix_LoadWAV_RW(rw, 0); + SDL_FreeRW(rw); + } + delete z; + } + if(!c) c = Mix_LoadWAV(findfile(name, "rb")); + return c; } template static void scalewav(T* dst, T* src, size_t len, int scale) { - len /= sizeof(T); - const T* end = src + len; - if(scale==2) for(; src < end; src++, dst += scale) - { - T s = src[0]; - dst[0] = s; - dst[1] = s; - } - else if(scale==4) for(; src < end; src++, dst += scale) - { - T s = src[0]; - dst[0] = s; - dst[1] = s; - dst[2] = s; - dst[3] = s; - } - else for(; src < end; src++) - { - T s = src[0]; - loopi(scale) *dst++ = s; - } + len /= sizeof(T); + const T* end = src + len; + if(scale==2) for(; src < end; src++, dst += scale) + { + T s = src[0]; + dst[0] = s; + dst[1] = s; + } + else if(scale==4) for(; src < end; src++, dst += scale) + { + T s = src[0]; + dst[0] = s; + dst[1] = s; + dst[2] = s; + dst[3] = s; + } + else for(; src < end; src++) + { + T s = src[0]; + loopi(scale) *dst++ = s; + } } static Mix_Chunk *loadwavscaled(const char *name) { - int mixerfreq = 0; - Uint16 mixerformat = 0; - int mixerchannels = 0; - if(!Mix_QuerySpec(&mixerfreq, &mixerformat, &mixerchannels)) return NULL; - - SDL_AudioSpec spec; - Uint8 *audiobuf = NULL; - Uint32 audiolen = 0; - stream *z = openzipfile(name, "rb"); - if(z) - { - SDL_RWops *rw = z->rwops(); - if(rw) - { - SDL_LoadWAV_RW(rw, 0, &spec, &audiobuf, &audiolen); - SDL_FreeRW(rw); - } - delete z; - } - if(!audiobuf) SDL_LoadWAV(findfile(name, "rb"), &spec, &audiobuf, &audiolen); - if(!audiobuf) return NULL; - int samplesize = ((spec.format&0xFF)/8) * spec.channels; - int scale = mixerfreq / spec.freq; - if(scale >= 2) - { - Uint8 *scalebuf = (Uint8*)SDL_malloc(audiolen * scale); - if(scalebuf) - { - switch(samplesize) - { - case 1: scalewav((uchar*)scalebuf, (uchar*)audiobuf, audiolen, scale); break; - case 2: scalewav((ushort*)scalebuf, (ushort*)audiobuf, audiolen, scale); break; - case 4: scalewav((uint*)scalebuf, (uint*)audiobuf, audiolen, scale); break; - case 8: scalewav((ullong*)scalebuf, (ullong*)audiobuf, audiolen, scale); break; - default: SDL_free(scalebuf); scalebuf = NULL; break; - } - if(scalebuf) - { - SDL_free(audiobuf); - audiobuf = scalebuf; - audiolen *= scale; - spec.freq *= scale; - } - } - } - if(spec.freq != mixerfreq || spec.format != mixerformat || spec.channels != mixerchannels) - { - SDL_AudioCVT cvt; - if(SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq, mixerformat, mixerchannels, mixerfreq) < 0) - { - SDL_free(audiobuf); - return NULL; - } - if(cvt.filters[0]) - { - cvt.len = audiolen & ~(samplesize-1); - cvt.buf = (Uint8*)SDL_malloc(cvt.len * cvt.len_mult); - if(!cvt.buf) { SDL_free(audiobuf); return NULL; } - SDL_memcpy(cvt.buf, audiobuf, cvt.len); - SDL_free(audiobuf); - if(SDL_ConvertAudio(&cvt) < 0) { SDL_free(cvt.buf); return NULL; } - audiobuf = cvt.buf; - audiolen = cvt.len_cvt; - } - } - Mix_Chunk *c = Mix_QuickLoad_RAW(audiobuf, audiolen); - if(!c) { SDL_free(audiobuf); return NULL; } - c->allocated = 1; - return c; + int mixerfreq = 0; + Uint16 mixerformat = 0; + int mixerchannels = 0; + if(!Mix_QuerySpec(&mixerfreq, &mixerformat, &mixerchannels)) return NULL; + + SDL_AudioSpec spec; + Uint8 *audiobuf = NULL; + Uint32 audiolen = 0; + stream *z = openzipfile(name, "rb"); + if(z) + { + SDL_RWops *rw = z->rwops(); + if(rw) + { + SDL_LoadWAV_RW(rw, 0, &spec, &audiobuf, &audiolen); + SDL_FreeRW(rw); + } + delete z; + } + if(!audiobuf) SDL_LoadWAV(findfile(name, "rb"), &spec, &audiobuf, &audiolen); + if(!audiobuf) return NULL; + int samplesize = ((spec.format&0xFF)/8) * spec.channels; + int scale = mixerfreq / spec.freq; + if(scale >= 2) + { + Uint8 *scalebuf = (Uint8*)SDL_malloc(audiolen * scale); + if(scalebuf) + { + switch(samplesize) + { + case 1: scalewav((uchar*)scalebuf, (uchar*)audiobuf, audiolen, scale); break; + case 2: scalewav((ushort*)scalebuf, (ushort*)audiobuf, audiolen, scale); break; + case 4: scalewav((uint*)scalebuf, (uint*)audiobuf, audiolen, scale); break; + case 8: scalewav((ullong*)scalebuf, (ullong*)audiobuf, audiolen, scale); break; + default: SDL_free(scalebuf); scalebuf = NULL; break; + } + if(scalebuf) + { + SDL_free(audiobuf); + audiobuf = scalebuf; + audiolen *= scale; + spec.freq *= scale; + } + } + } + if(spec.freq != mixerfreq || spec.format != mixerformat || spec.channels != mixerchannels) + { + SDL_AudioCVT cvt; + if(SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq, mixerformat, mixerchannels, mixerfreq) < 0) + { + SDL_free(audiobuf); + return NULL; + } + if(cvt.filters[0]) + { + cvt.len = audiolen & ~(samplesize-1); + cvt.buf = (Uint8*)SDL_malloc(cvt.len * cvt.len_mult); + if(!cvt.buf) { SDL_free(audiobuf); return NULL; } + SDL_memcpy(cvt.buf, audiobuf, cvt.len); + SDL_free(audiobuf); + if(SDL_ConvertAudio(&cvt) < 0) { SDL_free(cvt.buf); return NULL; } + audiobuf = cvt.buf; + audiolen = cvt.len_cvt; + } + } + Mix_Chunk *c = Mix_QuickLoad_RAW(audiobuf, audiolen); + if(!c) { SDL_free(audiobuf); return NULL; } + c->allocated = 1; + return c; } VARFP(fixwav, 0, 1, 1, initwarning("sound configuration", INIT_LOAD, CHANGE_SOUND)); bool soundsample::load(bool msg) { - if(chunk) return true; - if(!name[0]) return false; - - static const char * const exts[] = { "", ".wav", ".ogg" }; - string filename; - loopi(sizeof(exts)/sizeof(exts[0])) - { - formatstring(filename, "packages/sounds/%s%s", name, exts[i]); - if(msg && !i) renderprogress(0, filename); - path(filename); - if(fixwav) - { - size_t len = strlen(filename); - if(len >= 4 && !strcasecmp(filename + len - 4, ".wav")) - { - chunk = loadwavscaled(filename); - if(chunk) return true; - } - } - chunk = loadwav(filename); - if(chunk) return true; - } - - conoutf(CON_ERROR, "failed to load sample: packages/sounds/%s", name); - return false; + if(chunk) return true; + if(!name[0]) return false; + + static const char * const exts[] = { "", ".wav", ".ogg" }; + string filename; + loopi(sizeof(exts)/sizeof(exts[0])) + { + formatstring(filename, "packages/sounds/%s%s", name, exts[i]); + if(msg && !i) renderprogress(0, filename); + path(filename); + if(fixwav) + { + size_t len = strlen(filename); + if(len >= 4 && !strcasecmp(filename + len - 4, ".wav")) + { + chunk = loadwavscaled(filename); + if(chunk) return true; + } + } + chunk = loadwav(filename); + if(chunk) return true; + } + + conoutf(CON_ERROR, "failed to load sample: packages/sounds/%s", name); + return false; } static hashnameset samples; static void cleanupsamples() { - enumerate(samples, soundsample, s, s.cleanup()); + enumerate(samples, soundsample, s, s.cleanup()); } static struct soundtype { - vector slots; - vector configs; - - int findsound(const char *name, int vol) - { - loopv(configs) - { - soundconfig &s = configs[i]; - loopj(s.numslots) - { - soundslot &c = slots[s.slots+j]; - if(!strcmp(c.sample->name, name) && (!vol || c.volume==vol)) return i; - } - } - return -1; - } - - int addslot(const char *name, int vol) - { - soundsample *s = samples.access(name); - if(!s) - { - char *n = newstring(name); - s = &samples[n]; - s->name = n; - s->chunk = NULL; - } - soundslot *oldslots = slots.getbuf(); - int oldlen = slots.length(); - soundslot &slot = slots.add(); - // soundslots.add() may relocate slot pointers - if(slots.getbuf() != oldslots) loopv(channels) - { - soundchannel &chan = channels[i]; - if(chan.inuse && chan.slot >= oldslots && chan.slot < &oldslots[oldlen]) - chan.slot = &slots[chan.slot - oldslots]; - } - slot.sample = s; - slot.volume = vol ? vol : 100; - return oldlen; - } - - int addsound(const char *name, int vol, int maxuses = 0) - { - soundconfig &s = configs.add(); - s.slots = addslot(name, vol); - s.numslots = 1; - s.maxuses = maxuses; - return configs.length()-1; - } - - void addalt(const char *name, int vol) - { - if(configs.empty()) return; - addslot(name, vol); - configs.last().numslots++; - } - - void clear() - { - slots.setsize(0); - configs.setsize(0); - } - - void reset() - { - loopv(channels) - { - soundchannel &chan = channels[i]; - if(chan.inuse && slots.inbuf(chan.slot)) - { - Mix_HaltChannel(i); - freechannel(i); - } - } - clear(); - } - - void preloadsound(int n) - { - if(nosound || !configs.inrange(n)) return; - soundconfig &config = configs[n]; - loopk(config.numslots) slots[config.slots+k].sample->load(true); - } - - bool playing(const soundchannel &chan, const soundconfig &config) const - { - return chan.inuse && config.hasslot(chan.slot, slots); - } + vector slots; + vector configs; + + int findsound(const char *name, int vol) + { + loopv(configs) + { + soundconfig &s = configs[i]; + loopj(s.numslots) + { + soundslot &c = slots[s.slots+j]; + if(!strcmp(c.sample->name, name) && (!vol || c.volume==vol)) return i; + } + } + return -1; + } + + int addslot(const char *name, int vol) + { + soundsample *s = samples.access(name); + if(!s) + { + char *n = newstring(name); + s = &samples[n]; + s->name = n; + s->chunk = NULL; + } + soundslot *oldslots = slots.getbuf(); + int oldlen = slots.length(); + soundslot &slot = slots.add(); + // soundslots.add() may relocate slot pointers + if(slots.getbuf() != oldslots) loopv(channels) + { + soundchannel &chan = channels[i]; + if(chan.inuse && chan.slot >= oldslots && chan.slot < &oldslots[oldlen]) + chan.slot = &slots[chan.slot - oldslots]; + } + slot.sample = s; + slot.volume = vol ? vol : 100; + return oldlen; + } + + int addsound(const char *name, int vol, int maxuses = 0) + { + soundconfig &s = configs.add(); + s.slots = addslot(name, vol); + s.numslots = 1; + s.maxuses = maxuses; + return configs.length()-1; + } + + void addalt(const char *name, int vol) + { + if(configs.empty()) return; + addslot(name, vol); + configs.last().numslots++; + } + + void clear() + { + slots.setsize(0); + configs.setsize(0); + } + + void reset() + { + loopv(channels) + { + soundchannel &chan = channels[i]; + if(chan.inuse && slots.inbuf(chan.slot)) + { + Mix_HaltChannel(i); + freechannel(i); + } + } + clear(); + } + + void preloadsound(int n) + { + if(nosound || !configs.inrange(n)) return; + soundconfig &config = configs[n]; + loopk(config.numslots) slots[config.slots+k].sample->load(true); + } + + bool playing(const soundchannel &chan, const soundconfig &config) const + { + return chan.inuse && config.hasslot(chan.slot, slots); + } } gamesounds, mapsounds; void registersound(char *name, int *vol) { intret(gamesounds.addsound(name, *vol, 0)); } @@ -556,318 +555,313 @@ ICOMMAND(nummapsounds, "", (), intret(mapsounds.configs.length())); void soundreset() { - gamesounds.reset(); + gamesounds.reset(); } COMMAND(soundreset, ""); void mapsoundreset() { - mapsounds.reset(); + mapsounds.reset(); } COMMAND(mapsoundreset, ""); void resetchannels() { - loopv(channels) if(channels[i].inuse) freechannel(i); - channels.shrink(0); + loopv(channels) if(channels[i].inuse) freechannel(i); + channels.shrink(0); } void clear_sound() { - if(nosound) return; - stopmusic(); + if(nosound) return; + stopmusic(); - cleanupsamples(); - gamesounds.clear(); - mapsounds.clear(); - samples.clear(); - Mix_CloseAudio(); - resetchannels(); + cleanupsamples(); + gamesounds.clear(); + mapsounds.clear(); + samples.clear(); + Mix_CloseAudio(); + resetchannels(); } void stopmapsounds() { - loopv(channels) if(channels[i].inuse && channels[i].ent) - { - Mix_HaltChannel(i); - freechannel(i); - } + loopv(channels) if(channels[i].inuse && channels[i].ent) + { + Mix_HaltChannel(i); + freechannel(i); + } } void clearmapsounds() { - stopmapsounds(); - mapsounds.clear(); + stopmapsounds(); + mapsounds.clear(); } void stopmapsound(extentity *e) { - loopv(channels) - { - soundchannel &chan = channels[i]; - if(chan.inuse && chan.ent == e) - { - Mix_HaltChannel(i); - freechannel(i); - } - } + loopv(channels) + { + soundchannel &chan = channels[i]; + if(chan.inuse && chan.ent == e) + { + Mix_HaltChannel(i); + freechannel(i); + } + } } void checkmapsounds() { - const vector &ents = entities::getents(); - loopv(ents) - { - extentity &e = *ents[i]; - if(e.type!=ET_SOUND) continue; - if(camera1->o.dist(e.o) < e.attr2) - { - if(!(e.flags&EF_SOUND)) playsound(e.attr1, NULL, &e, SND_MAP, -1); - } - else if(e.flags&EF_SOUND) stopmapsound(&e); - } + const vector &ents = entities::getents(); + loopv(ents) + { + extentity &e = *ents[i]; + if(e.type!=ET_SOUND) continue; + if(camera1->o.dist(e.o) < e.attr2) + { + if(!(e.flags&EF_SOUND)) playsound(e.attr1, NULL, &e, SND_MAP, -1); + } + else if(e.flags&EF_SOUND) stopmapsound(&e); + } } VAR(stereo, 0, 1, 1); bool updatechannel(soundchannel &chan) { - if(!chan.slot) return false; - int vol = soundvol, pan = 255/2; - if(chan.hasloc()) - { - vec v; - float dist = chan.loc.dist(camera1->o, v); - int rad = 0; - if(chan.ent) - { - rad = chan.ent->attr2; - if(chan.ent->attr3) - { - rad -= chan.ent->attr3; - dist -= chan.ent->attr3; - } - } - else if(chan.radius > 0) rad = chan.radius; - if(rad > 0) vol -= int(clamp(dist/rad, 0.0f, 1.0f)*soundvol); // simple mono distance attenuation - if(stereo && (v.x != 0 || v.y != 0) && dist>0) - { - v.rotate_around_z(-camera1->yaw*RAD); - pan = int(255.9f*(0.5f - 0.5f*v.x/v.magnitude2())); // range is from 0 (left) to 255 (right) - } - } - vol = (vol*MIX_MAX_VOLUME*chan.slot->volume)/255/255; - vol = min(vol, MIX_MAX_VOLUME); - if(vol == chan.volume && pan == chan.pan) return false; - chan.volume = vol; - chan.pan = pan; - chan.dirty = true; - return true; + if(!chan.slot) return false; + int vol = soundvol, pan = 255/2; + if(chan.hasloc()) + { + vec v; + float dist = chan.loc.dist(camera1->o, v); + int rad = 0; + if(chan.ent) + { + rad = chan.ent->attr2; + if(chan.ent->attr3) + { + rad -= chan.ent->attr3; + dist -= chan.ent->attr3; + } + } + else if(chan.radius > 0) rad = chan.radius; + if(rad > 0) vol -= int(clamp(dist/rad, 0.0f, 1.0f)*soundvol); // simple mono distance attenuation + if(stereo && (v.x != 0 || v.y != 0) && dist>0) + { + v.rotate_around_z(-camera1->yaw*RAD); + pan = int(255.9f*(0.5f - 0.5f*v.x/v.magnitude2())); // range is from 0 (left) to 255 (right) + } + } + vol = (vol*MIX_MAX_VOLUME*chan.slot->volume)/255/255; + vol = min(vol, MIX_MAX_VOLUME); + if(vol == chan.volume && pan == chan.pan) return false; + chan.volume = vol; + chan.pan = pan; + chan.dirty = true; + return true; } void reclaimchannels() { - loopv(channels) - { - soundchannel &chan = channels[i]; - if(chan.inuse && !Mix_Playing(i)) freechannel(i); - } + loopv(channels) + { + soundchannel &chan = channels[i]; + if(chan.inuse && !Mix_Playing(i)) freechannel(i); + } } void syncchannels() { - loopv(channels) - { - soundchannel &chan = channels[i]; - if(chan.inuse && chan.hasloc() && updatechannel(chan)) syncchannel(chan); - } + loopv(channels) + { + soundchannel &chan = channels[i]; + if(chan.inuse && chan.hasloc() && updatechannel(chan)) syncchannel(chan); + } } VARP(minimizedsounds, 0, 0, 1); void updatesounds() { - if(nosound) return; - if(minimized && !minimizedsounds) stopsounds(); - else - { - reclaimchannels(); - if(mainmenu) stopmapsounds(); - else checkmapsounds(); - syncchannels(); - } - if(music) - { - if(!Mix_PlayingMusic()) musicdone(); - else if(Mix_PausedMusic()) Mix_ResumeMusic(); - } + if(nosound) return; + if(minimized && !minimizedsounds) stopsounds(); + else + { + reclaimchannels(); + if(mainmenu) stopmapsounds(); + else checkmapsounds(); + syncchannels(); + } + if(music) + { + if(!Mix_PlayingMusic()) musicdone(); + else if(Mix_PausedMusic()) Mix_ResumeMusic(); + } } VARP(maxsoundsatonce, 0, 7, 100); -VAR(dbgsound, 0, 0, 1); - void preloadsound(int n) { - gamesounds.preloadsound(n); + gamesounds.preloadsound(n); } void preloadmapsound(int n) { - mapsounds.preloadsound(n); + mapsounds.preloadsound(n); } void preloadmapsounds() { - const vector &ents = entities::getents(); - loopv(ents) - { - extentity &e = *ents[i]; - if(e.type==ET_SOUND) mapsounds.preloadsound(e.attr1); - } + const vector &ents = entities::getents(); + loopv(ents) + { + extentity &e = *ents[i]; + if(e.type==ET_SOUND) mapsounds.preloadsound(e.attr1); + } } int playsound(int n, const vec *loc, extentity *ent, int flags, int loops, int fade, int chanid, int radius, int expire) { - if(nosound || !soundvol || (minimized && !minimizedsounds)) return -1; - - soundtype &sounds = ent || flags&SND_MAP ? mapsounds : gamesounds; - if(!sounds.configs.inrange(n)) { conoutf(CON_WARN, "unregistered sound: %d", n); return -1; } - soundconfig &config = sounds.configs[n]; - - if(loc) - { - // cull sounds that are unlikely to be heard - int maxrad = game::maxsoundradius(n); - if(radius <= 0 || maxrad < radius) radius = maxrad; - if(camera1->o.dist(*loc) > 1.5f*radius) - { - if(channels.inrange(chanid) && sounds.playing(channels[chanid], config)) - { - Mix_HaltChannel(chanid); - freechannel(chanid); - } - return -1; - } - } - - if(chanid < 0) - { - if(config.maxuses) - { - int uses = 0; - loopv(channels) if(sounds.playing(channels[i], config) && ++uses >= config.maxuses) return -1; - } - - // avoid bursts of sounds with heavy packetloss and in sp - static int soundsatonce = 0, lastsoundmillis = 0; - if(totalmillis == lastsoundmillis) soundsatonce++; else soundsatonce = 1; - lastsoundmillis = totalmillis; - if(maxsoundsatonce && soundsatonce > maxsoundsatonce) return -1; - } - - if(channels.inrange(chanid)) - { - soundchannel &chan = channels[chanid]; - if(sounds.playing(chan, config)) - { - if(loc) chan.loc = *loc; - else if(chan.hasloc()) chan.clearloc(); - return chanid; - } - } - if(fade < 0) return -1; - - soundslot &slot = sounds.slots[config.chooseslot(flags)]; - if(!slot.sample->chunk && !slot.sample->load()) return -1; - - if(dbgsound) conoutf(CON_DEBUG, "sound: %s", slot.sample->name); - - chanid = -1; - loopv(channels) if(!channels[i].inuse) { chanid = i; break; } - if(chanid < 0 && channels.length() < maxchannels) chanid = channels.length(); - if(chanid < 0) loopv(channels) if(!channels[i].volume) { chanid = i; break; } - if(chanid < 0) return -1; - - soundchannel &chan = newchannel(chanid, &slot, loc, ent, flags, radius); - updatechannel(chan); - int playing = -1; - if(fade) - { - Mix_Volume(chanid, chan.volume); - playing = expire >= 0 ? Mix_FadeInChannelTimed(chanid, slot.sample->chunk, loops, fade, expire) : Mix_FadeInChannel(chanid, slot.sample->chunk, loops, fade); - } - else playing = expire >= 0 ? Mix_PlayChannelTimed(chanid, slot.sample->chunk, loops, expire) : Mix_PlayChannel(chanid, slot.sample->chunk, loops); - if(playing >= 0) syncchannel(chan); - else freechannel(chanid); - return playing; + if(nosound || !soundvol || (minimized && !minimizedsounds)) return -1; + + soundtype &sounds = ent || flags&SND_MAP ? mapsounds : gamesounds; + if(!sounds.configs.inrange(n)) { conoutf(CON_WARN, "unregistered sound: %d", n); return -1; } + soundconfig &config = sounds.configs[n]; + + if(loc) + { + // cull sounds that are unlikely to be heard + int maxrad = game::maxsoundradius(n); + if(radius <= 0 || maxrad < radius) radius = maxrad; + if(camera1->o.dist(*loc) > 1.5f*radius) + { + if(channels.inrange(chanid) && sounds.playing(channels[chanid], config)) + { + Mix_HaltChannel(chanid); + freechannel(chanid); + } + return -1; + } + } + + if(chanid < 0) + { + if(config.maxuses) + { + int uses = 0; + loopv(channels) if(sounds.playing(channels[i], config) && ++uses >= config.maxuses) return -1; + } + + // avoid bursts of sounds with heavy packetloss and in sp + static int soundsatonce = 0, lastsoundmillis = 0; + if(totalmillis == lastsoundmillis) soundsatonce++; else soundsatonce = 1; + lastsoundmillis = totalmillis; + if(maxsoundsatonce && soundsatonce > maxsoundsatonce) return -1; + } + + if(channels.inrange(chanid)) + { + soundchannel &chan = channels[chanid]; + if(sounds.playing(chan, config)) + { + if(loc) chan.loc = *loc; + else if(chan.hasloc()) chan.clearloc(); + return chanid; + } + } + if(fade < 0) return -1; + + soundslot &slot = sounds.slots[config.chooseslot(flags)]; + if(!slot.sample->chunk && !slot.sample->load()) return -1; + + chanid = -1; + loopv(channels) if(!channels[i].inuse) { chanid = i; break; } + if(chanid < 0 && channels.length() < maxchannels) chanid = channels.length(); + if(chanid < 0) loopv(channels) if(!channels[i].volume) { chanid = i; break; } + if(chanid < 0) return -1; + + soundchannel &chan = newchannel(chanid, &slot, loc, ent, flags, radius); + updatechannel(chan); + int playing = -1; + if(fade) + { + Mix_Volume(chanid, chan.volume); + playing = expire >= 0 ? Mix_FadeInChannelTimed(chanid, slot.sample->chunk, loops, fade, expire) : Mix_FadeInChannel(chanid, slot.sample->chunk, loops, fade); + } + else playing = expire >= 0 ? Mix_PlayChannelTimed(chanid, slot.sample->chunk, loops, expire) : Mix_PlayChannel(chanid, slot.sample->chunk, loops); + if(playing >= 0) syncchannel(chan); + else freechannel(chanid); + return playing; } void stopsounds() { - loopv(channels) if(channels[i].inuse) - { - Mix_HaltChannel(i); - freechannel(i); - } + loopv(channels) if(channels[i].inuse) + { + Mix_HaltChannel(i); + freechannel(i); + } } bool stopsound(int n, int chanid, int fade) { - if(!gamesounds.configs.inrange(n) || !channels.inrange(chanid) || !channels[chanid].inuse || !gamesounds.playing(channels[chanid], gamesounds.configs[n])) return false; - if(dbgsound) conoutf(CON_DEBUG, "stopsound: %s", channels[chanid].slot->sample->name); - if(!fade || !Mix_FadeOutChannel(chanid, fade)) - { - Mix_HaltChannel(chanid); - freechannel(chanid); - } - return true; + if(!gamesounds.configs.inrange(n) || !channels.inrange(chanid) || !channels[chanid].inuse || !gamesounds.playing(channels[chanid], gamesounds.configs[n])) return false; + if(!fade || !Mix_FadeOutChannel(chanid, fade)) + { + Mix_HaltChannel(chanid); + freechannel(chanid); + } + return true; } int playsoundname(const char *s, const vec *loc, int vol, int flags, int loops, int fade, int chanid, int radius, int expire) { - if(!vol) vol = 100; - int id = gamesounds.findsound(s, vol); - if(id < 0) id = gamesounds.addsound(s, vol); - return playsound(id, loc, NULL, flags, loops, fade, chanid, radius, expire); + if(!vol) vol = 100; + int id = gamesounds.findsound(s, vol); + if(id < 0) id = gamesounds.addsound(s, vol); + return playsound(id, loc, NULL, flags, loops, fade, chanid, radius, expire); } ICOMMAND(sound, "i", (int *n), playsound(*n)); void resetsound() { - clearchanges(CHANGE_SOUND); - if(!nosound) - { - cleanupsamples(); - if(music) - { - Mix_HaltMusic(); - Mix_FreeMusic(music); - } - if(musicstream) musicstream->seek(0, SEEK_SET); - Mix_CloseAudio(); - } - initsound(); - resetchannels(); - if(nosound) - { - DELETEA(musicfile); - DELETEA(musicdonecmd); - music = NULL; - cleanupsamples(); - return; - } - if(music && loadmusic(musicfile)) - { - Mix_PlayMusic(music, musicdonecmd ? 0 : -1); - Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255); - } - else - { - DELETEA(musicfile); - DELETEA(musicdonecmd); - } + clearchanges(CHANGE_SOUND); + if(!nosound) + { + cleanupsamples(); + if(music) + { + Mix_HaltMusic(); + Mix_FreeMusic(music); + } + if(musicstream) musicstream->seek(0, SEEK_SET); + Mix_CloseAudio(); + } + initsound(); + resetchannels(); + if(nosound) + { + DELETEA(musicfile); + DELETEA(musicdonecmd); + music = NULL; + cleanupsamples(); + return; + } + if(music && loadmusic(musicfile)) + { + Mix_PlayMusic(music, musicdonecmd ? 0 : -1); + Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255); + } + else + { + DELETEA(musicfile); + DELETEA(musicdonecmd); + } } COMMAND(resetsound, ""); diff --git a/src/engine/textedit.h b/src/engine/textedit.h index 31f23ed..0dcda1d 100644 --- a/src/engine/textedit.h +++ b/src/engine/textedit.h @@ -1,665 +1,664 @@ - struct editline { - enum { CHUNKSIZE = 256 }; - - char *text; - int len, maxlen; - - editline() : text(NULL), len(0), maxlen(0) {} - editline(const char *init) : text(NULL), len(0), maxlen(0) - { - set(init); - } - - bool empty() { return len <= 0; } - - void clear() - { - DELETEA(text); - len = maxlen = 0; - } - - bool grow(int total, const char *fmt = "", ...) - { - if(total + 1 <= maxlen) return false; - maxlen = (total + CHUNKSIZE) - total%CHUNKSIZE; - char *newtext = new char[maxlen]; - if(fmt) - { - va_list args; - va_start(args, fmt); - vformatstring(newtext, fmt, args, maxlen); - va_end(args); - } - else newtext[0] = '\0'; - DELETEA(text); - text = newtext; - return true; - } - - void set(const char *str, int slen = -1) - { - if(slen < 0) - { - slen = strlen(str); - if(!grow(slen, "%s", str)) memcpy(text, str, slen + 1); - } - else - { - grow(slen); - memcpy(text, str, slen); - text[slen] = '\0'; - } - len = slen; - } - - void prepend(const char *str) - { - int slen = strlen(str); - if(!grow(slen + len, "%s%s", str, text ? text : "")) - { - memmove(&text[slen], text, len + 1); - memcpy(text, str, slen + 1); - } - len += slen; - } - - void append(const char *str) - { - int slen = strlen(str); - if(!grow(len + slen, "%s%s", text ? text : "", str)) memcpy(&text[len], str, slen + 1); - len += slen; - } - - bool read(stream *f, int chop = -1) - { - if(chop < 0) chop = INT_MAX; else chop++; - set(""); - while(len + 1 < chop && f->getline(&text[len], min(maxlen, chop) - len)) - { - len += strlen(&text[len]); - if(len > 0 && text[len-1] == '\n') - { - text[--len] = '\0'; - return true; - } - if(len + 1 >= maxlen && len + 1 < chop) grow(len + CHUNKSIZE, "%s", text); - } - if(len + 1 >= chop) - { - char buf[CHUNKSIZE]; - while(f->getline(buf, sizeof(buf))) - { - int blen = strlen(buf); - if(blen > 0 && buf[blen-1] == '\n') return true; - } - } - return len > 0; - } - - void del(int start, int count) - { - if(!text) return; - if(start < 0) { count += start; start = 0; } - if(count <= 0 || start >= len) return; - if(start + count > len) count = len - start - 1; - memmove(&text[start], &text[start+count], len + 1 - (start + count)); - len -= count; - } - - void chop(int newlen) - { - if(!text) return; - len = clamp(newlen, 0, len); - text[len] = '\0'; - } - - void insert(char *str, int start, int count = 0) - { - if(count <= 0) count = strlen(str); - start = clamp(start, 0, len); - grow(len + count, "%s", text ? text : ""); - memmove(&text[start + count], &text[start], len - start + 1); - memcpy(&text[start], str, count); - len += count; - } - - void combinelines(vector &src) - { - if(src.empty()) set(""); - else loopv(src) - { - if(i) append("\n"); - if(!i) set(src[i].text, src[i].len); - else insert(src[i].text, len, src[i].len); - } - } + enum { CHUNKSIZE = 256 }; + + char *text; + int len, maxlen; + + editline() : text(NULL), len(0), maxlen(0) {} + editline(const char *init) : text(NULL), len(0), maxlen(0) + { + set(init); + } + + bool empty() { return len <= 0; } + + void clear() + { + DELETEA(text); + len = maxlen = 0; + } + + bool grow(int total, const char *fmt = "", ...) + { + if(total + 1 <= maxlen) return false; + maxlen = (total + CHUNKSIZE) - total%CHUNKSIZE; + char *newtext = new char[maxlen]; + if(fmt) + { + va_list args; + va_start(args, fmt); + vformatstring(newtext, fmt, args, maxlen); + va_end(args); + } + else newtext[0] = '\0'; + DELETEA(text); + text = newtext; + return true; + } + + void set(const char *str, int slen = -1) + { + if(slen < 0) + { + slen = strlen(str); + if(!grow(slen, "%s", str)) memcpy(text, str, slen + 1); + } + else + { + grow(slen); + memcpy(text, str, slen); + text[slen] = '\0'; + } + len = slen; + } + + void prepend(const char *str) + { + int slen = strlen(str); + if(!grow(slen + len, "%s%s", str, text ? text : "")) + { + memmove(&text[slen], text, len + 1); + memcpy(text, str, slen + 1); + } + len += slen; + } + + void append(const char *str) + { + int slen = strlen(str); + if(!grow(len + slen, "%s%s", text ? text : "", str)) memcpy(&text[len], str, slen + 1); + len += slen; + } + + bool read(stream *f, int chop = -1) + { + if(chop < 0) chop = INT_MAX; else chop++; + set(""); + while(len + 1 < chop && f->getline(&text[len], min(maxlen, chop) - len)) + { + len += strlen(&text[len]); + if(len > 0 && text[len-1] == '\n') + { + text[--len] = '\0'; + return true; + } + if(len + 1 >= maxlen && len + 1 < chop) grow(len + CHUNKSIZE, "%s", text); + } + if(len + 1 >= chop) + { + char buf[CHUNKSIZE]; + while(f->getline(buf, sizeof(buf))) + { + int blen = strlen(buf); + if(blen > 0 && buf[blen-1] == '\n') return true; + } + } + return len > 0; + } + + void del(int start, int count) + { + if(!text) return; + if(start < 0) { count += start; start = 0; } + if(count <= 0 || start >= len) return; + if(start + count > len) count = len - start - 1; + memmove(&text[start], &text[start+count], len + 1 - (start + count)); + len -= count; + } + + void chop(int newlen) + { + if(!text) return; + len = clamp(newlen, 0, len); + text[len] = '\0'; + } + + void insert(char *str, int start, int count = 0) + { + if(count <= 0) count = strlen(str); + start = clamp(start, 0, len); + grow(len + count, "%s", text ? text : ""); + memmove(&text[start + count], &text[start], len - start + 1); + memcpy(&text[start], str, count); + len += count; + } + + void combinelines(vector &src) + { + if(src.empty()) set(""); + else loopv(src) + { + if(i) append("\n"); + if(!i) set(src[i].text, src[i].len); + else insert(src[i].text, len, src[i].len); + } + } }; struct editor { - int mode; //editor mode - 1= keep while focused, 2= keep while used in gui, 3= keep forever (i.e. until mode changes) - bool active, rendered; - const char *name; - const char *filename; - - int cx, cy; // cursor position - ensured to be valid after a region() or currentline() - int mx, my; // selection mark, mx=-1 if following cursor - avoid direct access, instead use region() - int maxx, maxy; // maxy=-1 if unlimited lines, 1 if single line editor - - int scrolly; // vertical scroll offset - - bool linewrap; - int pixelwidth; // required for up/down/hit/draw/bounds - int pixelheight; // -1 for variable sized, i.e. from bounds() - - vector lines; // MUST always contain at least one line! - - editor(const char *name, int mode, const char *initval) : - mode(mode), active(true), rendered(false), name(newstring(name)), filename(NULL), - cx(0), cy(0), mx(-1), maxx(-1), maxy(-1), scrolly(0), linewrap(false), pixelwidth(-1), pixelheight(-1) - { - //printf("editor %08x '%s'\n", this, name); - lines.add().set(initval ? initval : ""); - } - - ~editor() - { - //printf("~editor %08x '%s'\n", this, name); - DELETEA(name); - DELETEA(filename); - clear(NULL); - } - - void clear(const char *init = "") - { - cx = cy = 0; - mark(false); - loopv(lines) lines[i].clear(); - lines.shrink(0); - if(init) lines.add().set(init); - } - - void setfile(const char *fname) - { - DELETEA(filename); - if(fname) filename = newstring(fname); - } - - void load() - { - if(!filename) return; - clear(NULL); - stream *file = openutf8file(filename, "r"); - if(file) - { - while(lines.add().read(file, maxx) && (maxy < 0 || lines.length() <= maxy)); - lines.pop().clear(); - delete file; - } - if(lines.empty()) lines.add().set(""); - } - - void save() - { - if(!filename) return; - stream *file = openutf8file(filename, "w"); - if(!file) return; - loopv(lines) file->putline(lines[i].text); - delete file; - } - - void mark(bool enable) - { - mx = (enable) ? cx : -1; - my = cy; - } - - void selectall() - { - mx = my = INT_MAX; - cx = cy = 0; - } - - // constrain results to within buffer - s=start, e=end, return true if a selection range - // also ensures that cy is always within lines[] and cx is valid - bool region(int &sx, int &sy, int &ex, int &ey) - { - int n = lines.length(); - ASSERT(n != 0); - if(cy < 0) cy = 0; else if(cy >= n) cy = n-1; - int len = lines[cy].len; - if(cx < 0) cx = 0; else if(cx > len) cx = len; - if(mx >= 0) - { - if(my < 0) my = 0; else if(my >= n) my = n-1; - len = lines[my].len; - if(mx > len) mx = len; - } - sx = (mx >= 0) ? mx : cx; - sy = (mx >= 0) ? my : cy; - ex = cx; - ey = cy; - if(sy > ey) { swap(sy, ey); swap(sx, ex); } - else if(sy==ey && sx > ex) swap(sx, ex); - return (sx != ex) || (sy != ey); - } - - bool region() { int sx, sy, ex, ey; return region(sx, sy, ex, ey); } - - // also ensures that cy is always within lines[] and cx is valid - editline ¤tline() - { - int n = lines.length(); - ASSERT(n != 0); - if(cy < 0) cy = 0; else if(cy >= n) cy = n-1; - if(cx < 0) cx = 0; else if(cx > lines[cy].len) cx = lines[cy].len; - return lines[cy]; - } - - void copyselectionto(editor *b) - { - if(b==this) return; - - b->clear(NULL); - int sx, sy, ex, ey; - region(sx, sy, ex, ey); - loopi(1+ey-sy) - { - if(b->maxy != -1 && b->lines.length() >= b->maxy) break; - int y = sy+i; - char *line = lines[y].text; - int len = lines[y].len; - if(y == sy && y == ey) - { - line += sx; - len = ex - sx; - } - else if(y == sy) line += sx; - else if(y == ey) len = ex; - b->lines.add().set(line, len); - } - if(b->lines.empty()) b->lines.add().set(""); - } - - char *tostring() - { - int len = 0; - loopv(lines) len += lines[i].len + 1; - char *str = newstring(len); - int offset = 0; - loopv(lines) - { - editline &l = lines[i]; - memcpy(&str[offset], l.text, l.len); - offset += l.len; - str[offset++] = '\n'; - } - str[offset] = '\0'; - return str; - } - - char *selectiontostring() - { - vector buf; - int sx, sy, ex, ey; - region(sx, sy, ex, ey); - loopi(1+ey-sy) - { - int y = sy+i; - char *line = lines[y].text; - int len = lines[y].len; - if(y == sy && y == ey) - { - line += sx; - len = ex - sx; - } - else if(y == sy) line += sx; - else if(y == ey) len = ex; - buf.put(line, len); - buf.add('\n'); - } - buf.add('\0'); - return newstring(buf.getbuf(), buf.length()-1); - } - - void removelines(int start, int count) - { - loopi(count) lines[start+i].clear(); - lines.remove(start, count); - } - - bool del() // removes the current selection (if any) - { - int sx, sy, ex, ey; - if(!region(sx, sy, ex, ey)) - { - mark(false); - return false; - } - if(sy == ey) - { - if(sx == 0 && ex == lines[ey].len) removelines(sy, 1); - else lines[sy].del(sx, ex - sx); - } - else - { - if(ey > sy+1) { removelines(sy+1, ey-(sy+1)); ey = sy+1; } - if(ex == lines[ey].len) removelines(ey, 1); else lines[ey].del(0, ex); - if(sx == 0) removelines(sy, 1); else lines[sy].del(sx, lines[sy].len - sx); - } - if(lines.empty()) lines.add().set(""); - mark(false); - cx = sx; - cy = sy; - editline ¤t = currentline(); - if(cx >= current.len && cy < lines.length() - 1) - { - current.append(lines[cy+1].text); - removelines(cy + 1, 1); - } - return true; - } - - void insert(char ch) - { - del(); - editline ¤t = currentline(); - if(ch == '\n') - { - if(maxy == -1 || cy < maxy-1) - { - editline newline(¤t.text[cx]); - current.chop(cx); - cy = min(lines.length(), cy+1); - lines.insert(cy, newline); - } - else current.chop(cx); - cx = 0; - } - else - { - int len = current.len; - if(maxx >= 0 && len > maxx-1) len = maxx-1; - if(cx <= len) current.insert(&ch, cx++, 1); - } - } - - void insert(const char *s) - { - while(*s) insert(*s++); - } - - void insertallfrom(editor *b) - { - if(b==this) return; - - del(); - - if(b->lines.length() == 1 || maxy == 1) - { - editline ¤t = currentline(); - char *str = b->lines[0].text; - int slen = b->lines[0].len; - if(maxx >= 0 && b->lines[0].len + cx > maxx) slen = maxx-cx; - if(slen > 0) - { - int len = current.len; - if(maxx >= 0 && slen + cx + len > maxx) len = max(0, maxx-(cx+slen)); - current.insert(str, cx, slen); - cx += slen; - } - } - else - { - loopv(b->lines) - { - if(!i) - { - lines[cy++].append(b->lines[i].text); - } - else if(i >= b->lines.length()) - { - cx = b->lines[i].len; - lines[cy].prepend(b->lines[i].text); - } - else if(maxy < 0 || lines.length() < maxy) lines.insert(cy++, editline(b->lines[i].text)); - } - } - } - - void key(int code) - { - switch(code) - { - case SDLK_UP: - if(linewrap) - { - int x, y; - char *str = currentline().text; - text_pos(str, cx+1, x, y, pixelwidth); - if(y > 0) { cx = text_visible(str, x, y-FONTH, pixelwidth); break; } - } - cy--; - break; - case SDLK_DOWN: - if(linewrap) - { - int x, y, width, height; - char *str = currentline().text; - text_pos(str, cx, x, y, pixelwidth); - text_bounds(str, width, height, pixelwidth); - y += FONTH; - if(y < height) { cx = text_visible(str, x, y, pixelwidth); break; } - } - cy++; - break; - case -4: - cy--; - break; - case -5: - cy++; - break; - case SDLK_PAGEUP: - cy-=pixelheight/FONTH; - break; - case SDLK_PAGEDOWN: - cy+=pixelheight/FONTH; - break; - case SDLK_HOME: - cx = cy = 0; - break; - case SDLK_END: - cx = cy = INT_MAX; - break; - case SDLK_LEFT: - cx--; - break; - case SDLK_RIGHT: - cx++; - break; - case SDLK_DELETE: - if(!del()) - { - editline ¤t = currentline(); - if(cx < current.len) current.del(cx, 1); - else if(cy < lines.length()-1) - { //combine with next line - current.append(lines[cy+1].text); - removelines(cy+1, 1); - } - } - break; - case SDLK_BACKSPACE: - if(!del()) - { - editline ¤t = currentline(); - if(cx > 0) current.del(--cx, 1); - else if(cy > 0) - { //combine with previous line - cx = lines[cy-1].len; - lines[cy-1].append(current.text); - removelines(cy--, 1); - } - } - break; - case SDLK_LSHIFT: - case SDLK_RSHIFT: - break; - case SDLK_RETURN: - insert('\n'); - break; - case SDLK_TAB: - insert('\t'); - break; - } - } - - void input(const char *str, int len) - { - loopi(len) insert(str[i]); - } - - void hit(int hitx, int hity, bool dragged) - { - int maxwidth = linewrap?pixelwidth:-1; - int h = 0; - for(int i = scrolly; i < lines.length(); i++) - { - int width, height; - text_bounds(lines[i].text, width, height, maxwidth); - if(h + height > pixelheight) break; - - if(hity >= h && hity <= h+height) - { - int x = text_visible(lines[i].text, hitx, hity-h, maxwidth); - if(dragged) { mx = x; my = i; } else { cx = x; cy = i; }; - break; - } - h+=height; - } - } - - int limitscrolly() - { - int maxwidth = linewrap?pixelwidth:-1; - int slines = lines.length(); - for(int ph = pixelheight; slines > 0 && ph > 0;) - { - int width, height; - text_bounds(lines[slines-1].text, width, height, maxwidth); - if(height > ph) break; - ph -= height; - slines--; - } - return slines; - } - - void draw(int x, int y, int color, bool hit) - { - int maxwidth = linewrap?pixelwidth:-1; - - int sx, sy, ex, ey; - bool selection = region(sx, sy, ex, ey); - - // fix scrolly so that is always on screen - if(cy < scrolly) scrolly = cy; - else - { - if(scrolly < 0) scrolly = 0; - int h = 0; - for(int i = cy; i >= scrolly; i--) - { - int width, height; - text_bounds(lines[i].text, width, height, maxwidth); - if(h + height > pixelheight) { scrolly = i+1; break; } - h += height; - } - } - - if(selection) - { - // convert from cursor coords into pixel coords - int psx, psy, pex, pey; - text_pos(lines[sy].text, sx, psx, psy, maxwidth); - text_pos(lines[ey].text, ex, pex, pey, maxwidth); - int maxy = lines.length(); - int h = 0; - for(int i = scrolly; i < maxy; i++) - { - int width, height; - text_bounds(lines[i].text, width, height, maxwidth); - if(h + height > pixelheight) { maxy = i; break; } - if(i == sy) psy += h; - if(i == ey) { pey += h; break; } - h += height; - } - maxy--; - - if(ey >= scrolly && sy <= maxy) - { - // crop top/bottom within window - if(sy < scrolly) { sy = scrolly; psy = 0; psx = 0; } - if(ey > maxy) { ey = maxy; pey = pixelheight - FONTH; pex = pixelwidth; } - - hudnotextureshader->set(); - gle::colorub(0xA0, 0x80, 0x80); - gle::defvertex(2); - gle::begin(GL_QUADS); - if(psy == pey) - { - gle::attribf(x+psx, y+psy); - gle::attribf(x+pex, y+psy); - gle::attribf(x+pex, y+pey+FONTH); - gle::attribf(x+psx, y+pey+FONTH); - } - else - { gle::attribf(x+psx, y+psy); - gle::attribf(x+psx, y+psy+FONTH); - gle::attribf(x+pixelwidth, y+psy+FONTH); - gle::attribf(x+pixelwidth, y+psy); - if(pey-psy > FONTH) - { - gle::attribf(x, y+psy+FONTH); - gle::attribf(x+pixelwidth, y+psy+FONTH); - gle::attribf(x+pixelwidth, y+pey); - gle::attribf(x, y+pey); - } - gle::attribf(x, y+pey); - gle::attribf(x, y+pey+FONTH); - gle::attribf(x+pex, y+pey+FONTH); - gle::attribf(x+pex, y+pey); - } - gle::end(); - hudshader->set(); - } - } - - int h = 0; - for(int i = scrolly; i < lines.length(); i++) - { - int width, height; - text_bounds(lines[i].text, width, height, maxwidth); - if(h + height > pixelheight) break; - - draw_text(lines[i].text, x, y+h, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, hit&&(cy==i)?cx:-1, maxwidth); - if(linewrap && height > FONTH) // line wrap indicator - { - hudnotextureshader->set(); - gle::colorub(0x80, 0xA0, 0x80); - gle::defvertex(2); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x, y+h+FONTH); - gle::attribf(x, y+h+height); - gle::attribf(x-FONTW/2, y+h+FONTH); - gle::attribf(x-FONTW/2, y+h+height); - gle::end(); - hudshader->set(); - } - h+=height; - } - } + int mode; //editor mode - 1= keep while focused, 2= keep while used in gui, 3= keep forever (i.e. until mode changes) + bool active, rendered; + const char *name; + const char *filename; + + int cx, cy; // cursor position - ensured to be valid after a region() or currentline() + int mx, my; // selection mark, mx=-1 if following cursor - avoid direct access, instead use region() + int maxx, maxy; // maxy=-1 if unlimited lines, 1 if single line editor + + int scrolly; // vertical scroll offset + + bool linewrap; + int pixelwidth; // required for up/down/hit/draw/bounds + int pixelheight; // -1 for variable sized, i.e. from bounds() + + vector lines; // MUST always contain at least one line! + + editor(const char *name, int mode, const char *initval) : + mode(mode), active(true), rendered(false), name(newstring(name)), filename(NULL), + cx(0), cy(0), mx(-1), maxx(-1), maxy(-1), scrolly(0), linewrap(false), pixelwidth(-1), pixelheight(-1) + { + //printf("editor %08x '%s'\n", this, name); + lines.add().set(initval ? initval : ""); + } + + ~editor() + { + //printf("~editor %08x '%s'\n", this, name); + DELETEA(name); + DELETEA(filename); + clear(NULL); + } + + void clear(const char *init = "") + { + cx = cy = 0; + mark(false); + loopv(lines) lines[i].clear(); + lines.shrink(0); + if(init) lines.add().set(init); + } + + void setfile(const char *fname) + { + DELETEA(filename); + if(fname) filename = newstring(fname); + } + + void load() + { + if(!filename) return; + clear(NULL); + stream *file = openutf8file(filename, "r"); + if(file) + { + while(lines.add().read(file, maxx) && (maxy < 0 || lines.length() <= maxy)); + lines.pop().clear(); + delete file; + } + if(lines.empty()) lines.add().set(""); + } + + void save() + { + if(!filename) return; + stream *file = openutf8file(filename, "w"); + if(!file) return; + loopv(lines) file->putline(lines[i].text); + delete file; + } + + void mark(bool enable) + { + mx = (enable) ? cx : -1; + my = cy; + } + + void selectall() + { + mx = my = INT_MAX; + cx = cy = 0; + } + + // constrain results to within buffer - s=start, e=end, return true if a selection range + // also ensures that cy is always within lines[] and cx is valid + bool region(int &sx, int &sy, int &ex, int &ey) + { + int n = lines.length(); + ASSERT(n != 0); + if(cy < 0) cy = 0; else if(cy >= n) cy = n-1; + int len = lines[cy].len; + if(cx < 0) cx = 0; else if(cx > len) cx = len; + if(mx >= 0) + { + if(my < 0) my = 0; else if(my >= n) my = n-1; + len = lines[my].len; + if(mx > len) mx = len; + } + sx = (mx >= 0) ? mx : cx; + sy = (mx >= 0) ? my : cy; + ex = cx; + ey = cy; + if(sy > ey) { swap(sy, ey); swap(sx, ex); } + else if(sy==ey && sx > ex) swap(sx, ex); + return (sx != ex) || (sy != ey); + } + + bool region() { int sx, sy, ex, ey; return region(sx, sy, ex, ey); } + + // also ensures that cy is always within lines[] and cx is valid + editline ¤tline() + { + int n = lines.length(); + ASSERT(n != 0); + if(cy < 0) cy = 0; else if(cy >= n) cy = n-1; + if(cx < 0) cx = 0; else if(cx > lines[cy].len) cx = lines[cy].len; + return lines[cy]; + } + + void copyselectionto(editor *b) + { + if(b==this) return; + + b->clear(NULL); + int sx, sy, ex, ey; + region(sx, sy, ex, ey); + loopi(1+ey-sy) + { + if(b->maxy != -1 && b->lines.length() >= b->maxy) break; + int y = sy+i; + char *line = lines[y].text; + int len = lines[y].len; + if(y == sy && y == ey) + { + line += sx; + len = ex - sx; + } + else if(y == sy) line += sx; + else if(y == ey) len = ex; + b->lines.add().set(line, len); + } + if(b->lines.empty()) b->lines.add().set(""); + } + + char *tostring() + { + int len = 0; + loopv(lines) len += lines[i].len + 1; + char *str = newstring(len); + int offset = 0; + loopv(lines) + { + editline &l = lines[i]; + memcpy(&str[offset], l.text, l.len); + offset += l.len; + str[offset++] = '\n'; + } + str[offset] = '\0'; + return str; + } + + char *selectiontostring() + { + vector buf; + int sx, sy, ex, ey; + region(sx, sy, ex, ey); + loopi(1+ey-sy) + { + int y = sy+i; + char *line = lines[y].text; + int len = lines[y].len; + if(y == sy && y == ey) + { + line += sx; + len = ex - sx; + } + else if(y == sy) line += sx; + else if(y == ey) len = ex; + buf.put(line, len); + buf.add('\n'); + } + buf.add('\0'); + return newstring(buf.getbuf(), buf.length()-1); + } + + void removelines(int start, int count) + { + loopi(count) lines[start+i].clear(); + lines.remove(start, count); + } + + bool del() // removes the current selection (if any) + { + int sx, sy, ex, ey; + if(!region(sx, sy, ex, ey)) + { + mark(false); + return false; + } + if(sy == ey) + { + if(sx == 0 && ex == lines[ey].len) removelines(sy, 1); + else lines[sy].del(sx, ex - sx); + } + else + { + if(ey > sy+1) { removelines(sy+1, ey-(sy+1)); ey = sy+1; } + if(ex == lines[ey].len) removelines(ey, 1); else lines[ey].del(0, ex); + if(sx == 0) removelines(sy, 1); else lines[sy].del(sx, lines[sy].len - sx); + } + if(lines.empty()) lines.add().set(""); + mark(false); + cx = sx; + cy = sy; + editline ¤t = currentline(); + if(cx >= current.len && cy < lines.length() - 1) + { + current.append(lines[cy+1].text); + removelines(cy + 1, 1); + } + return true; + } + + void insert(char ch) + { + del(); + editline ¤t = currentline(); + if(ch == '\n') + { + if(maxy == -1 || cy < maxy-1) + { + editline newline(¤t.text[cx]); + current.chop(cx); + cy = min(lines.length(), cy+1); + lines.insert(cy, newline); + } + else current.chop(cx); + cx = 0; + } + else + { + int len = current.len; + if(maxx >= 0 && len > maxx-1) len = maxx-1; + if(cx <= len) current.insert(&ch, cx++, 1); + } + } + + void insert(const char *s) + { + while(*s) insert(*s++); + } + + void insertallfrom(editor *b) + { + if(b==this) return; + + del(); + + if(b->lines.length() == 1 || maxy == 1) + { + editline ¤t = currentline(); + char *str = b->lines[0].text; + int slen = b->lines[0].len; + if(maxx >= 0 && b->lines[0].len + cx > maxx) slen = maxx-cx; + if(slen > 0) + { + int len = current.len; + if(maxx >= 0 && slen + cx + len > maxx) len = max(0, maxx-(cx+slen)); + current.insert(str, cx, slen); + cx += slen; + } + } + else + { + loopv(b->lines) + { + if(!i) + { + lines[cy++].append(b->lines[i].text); + } + else if(i >= b->lines.length()) + { + cx = b->lines[i].len; + lines[cy].prepend(b->lines[i].text); + } + else if(maxy < 0 || lines.length() < maxy) lines.insert(cy++, editline(b->lines[i].text)); + } + } + } + + void key(int code) + { + switch(code) + { + case SDLK_UP: + if(linewrap) + { + int x, y; + char *str = currentline().text; + text_pos(str, cx+1, x, y, pixelwidth); + if(y > 0) { cx = text_visible(str, x, y-FONTH, pixelwidth); break; } + } + cy--; + break; + case SDLK_DOWN: + if(linewrap) + { + int x, y, width, height; + char *str = currentline().text; + text_pos(str, cx, x, y, pixelwidth); + text_bounds(str, width, height, pixelwidth); + y += FONTH; + if(y < height) { cx = text_visible(str, x, y, pixelwidth); break; } + } + cy++; + break; + case -4: + cy--; + break; + case -5: + cy++; + break; + case SDLK_PAGEUP: + cy-=pixelheight/FONTH; + break; + case SDLK_PAGEDOWN: + cy+=pixelheight/FONTH; + break; + case SDLK_HOME: + cx = cy = 0; + break; + case SDLK_END: + cx = cy = INT_MAX; + break; + case SDLK_LEFT: + cx--; + break; + case SDLK_RIGHT: + cx++; + break; + case SDLK_DELETE: + if(!del()) + { + editline ¤t = currentline(); + if(cx < current.len) current.del(cx, 1); + else if(cy < lines.length()-1) + { //combine with next line + current.append(lines[cy+1].text); + removelines(cy+1, 1); + } + } + break; + case SDLK_BACKSPACE: + if(!del()) + { + editline ¤t = currentline(); + if(cx > 0) current.del(--cx, 1); + else if(cy > 0) + { //combine with previous line + cx = lines[cy-1].len; + lines[cy-1].append(current.text); + removelines(cy--, 1); + } + } + break; + case SDLK_LSHIFT: + case SDLK_RSHIFT: + break; + case SDLK_RETURN: + insert('\n'); + break; + case SDLK_TAB: + insert('\t'); + break; + } + } + + void input(const char *str, int len) + { + loopi(len) insert(str[i]); + } + + void hit(int hitx, int hity, bool dragged) + { + int maxwidth = linewrap?pixelwidth:-1; + int h = 0; + for(int i = scrolly; i < lines.length(); i++) + { + int width, height; + text_bounds(lines[i].text, width, height, maxwidth); + if(h + height > pixelheight) break; + + if(hity >= h && hity <= h+height) + { + int x = text_visible(lines[i].text, hitx, hity-h, maxwidth); + if(dragged) { mx = x; my = i; } else { cx = x; cy = i; }; + break; + } + h+=height; + } + } + + int limitscrolly() + { + int maxwidth = linewrap?pixelwidth:-1; + int slines = lines.length(); + for(int ph = pixelheight; slines > 0 && ph > 0;) + { + int width, height; + text_bounds(lines[slines-1].text, width, height, maxwidth); + if(height > ph) break; + ph -= height; + slines--; + } + return slines; + } + + void draw(int x, int y, int color, bool hit) + { + int maxwidth = linewrap?pixelwidth:-1; + + int sx, sy, ex, ey; + bool selection = region(sx, sy, ex, ey); + + // fix scrolly so that is always on screen + if(cy < scrolly) scrolly = cy; + else + { + if(scrolly < 0) scrolly = 0; + int h = 0; + for(int i = cy; i >= scrolly; i--) + { + int width, height; + text_bounds(lines[i].text, width, height, maxwidth); + if(h + height > pixelheight) { scrolly = i+1; break; } + h += height; + } + } + + if(selection) + { + // convert from cursor coords into pixel coords + int psx, psy, pex, pey; + text_pos(lines[sy].text, sx, psx, psy, maxwidth); + text_pos(lines[ey].text, ex, pex, pey, maxwidth); + int maxy = lines.length(); + int h = 0; + for(int i = scrolly; i < maxy; i++) + { + int width, height; + text_bounds(lines[i].text, width, height, maxwidth); + if(h + height > pixelheight) { maxy = i; break; } + if(i == sy) psy += h; + if(i == ey) { pey += h; break; } + h += height; + } + maxy--; + + if(ey >= scrolly && sy <= maxy) + { + // crop top/bottom within window + if(sy < scrolly) { sy = scrolly; psy = 0; psx = 0; } + if(ey > maxy) { ey = maxy; pey = pixelheight - FONTH; pex = pixelwidth; } + + hudnotextureshader->set(); + gle::colorub(0xA0, 0x80, 0x80); + gle::defvertex(2); + gle::begin(GL_QUADS); + if(psy == pey) + { + gle::attribf(x+psx, y+psy); + gle::attribf(x+pex, y+psy); + gle::attribf(x+pex, y+pey+FONTH); + gle::attribf(x+psx, y+pey+FONTH); + } + else + { gle::attribf(x+psx, y+psy); + gle::attribf(x+psx, y+psy+FONTH); + gle::attribf(x+pixelwidth, y+psy+FONTH); + gle::attribf(x+pixelwidth, y+psy); + if(pey-psy > FONTH) + { + gle::attribf(x, y+psy+FONTH); + gle::attribf(x+pixelwidth, y+psy+FONTH); + gle::attribf(x+pixelwidth, y+pey); + gle::attribf(x, y+pey); + } + gle::attribf(x, y+pey); + gle::attribf(x, y+pey+FONTH); + gle::attribf(x+pex, y+pey+FONTH); + gle::attribf(x+pex, y+pey); + } + gle::end(); + hudshader->set(); + } + } + + int h = 0; + for(int i = scrolly; i < lines.length(); i++) + { + int width, height; + text_bounds(lines[i].text, width, height, maxwidth); + if(h + height > pixelheight) break; + + draw_text(lines[i].text, x, y+h, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, hit&&(cy==i)?cx:-1, maxwidth); + if(linewrap && height > FONTH) // line wrap indicator + { + hudnotextureshader->set(); + gle::colorub(0x80, 0xA0, 0x80); + gle::defvertex(2); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x, y+h+FONTH); + gle::attribf(x, y+h+height); + gle::attribf(x-FONTW/2, y+h+FONTH); + gle::attribf(x-FONTW/2, y+h+height); + gle::end(); + hudshader->set(); + } + h+=height; + } + } }; // a 'stack' where the last is the current focused editor @@ -669,85 +668,85 @@ static editor *currentfocus() { return editors.length() ? editors.last() : NULL; static void readyeditors() { - loopv(editors) editors[i]->active = (editors[i]->mode==EDITORFOREVER); + loopv(editors) editors[i]->active = (editors[i]->mode==EDITORFOREVER); } static void flusheditors() { - loopvrev(editors) if(!editors[i]->active) - { - editor *e = editors.remove(i); - DELETEP(e); - } + loopvrev(editors) if(!editors[i]->active) + { + editor *e = editors.remove(i); + DELETEP(e); + } } static editor *useeditor(const char *name, int mode, bool focus, const char *initval = NULL) { - loopv(editors) if(strcmp(editors[i]->name, name) == 0) - { - editor *e = editors[i]; - if(focus) { editors.add(e); editors.remove(i); } // re-position as last - e->active = true; - return e; - } - editor *e = new editor(name, mode, initval); - if(focus) editors.add(e); else editors.insert(0, e); - return e; + loopv(editors) if(strcmp(editors[i]->name, name) == 0) + { + editor *e = editors[i]; + if(focus) { editors.add(e); editors.remove(i); } // re-position as last + e->active = true; + return e; + } + editor *e = new editor(name, mode, initval); + if(focus) editors.add(e); else editors.insert(0, e); + return e; } #define TEXTCOMMAND(f, s, d, body) ICOMMAND(f, s, d,\ - editor *top = currentfocus();\ - if(!top || identflags&IDF_OVERRIDDEN) return;\ - body\ + editor *top = currentfocus();\ + if(!top || identflags&IDF_OVERRIDDEN) return;\ + body\ ) ICOMMAND(textlist, "", (), // @DEBUG return list of all the editors - vector s; - loopv(editors) - { - if(i > 0) s.put(", ", 2); - s.put(editors[i]->name, strlen(editors[i]->name)); - } - s.add('\0'); - result(s.getbuf()); + vector s; + loopv(editors) + { + if(i > 0) s.put(", ", 2); + s.put(editors[i]->name, strlen(editors[i]->name)); + } + s.add('\0'); + result(s.getbuf()); ); TEXTCOMMAND(textshow, "", (), // @DEBUG return the start of the buffer - editline line; - line.combinelines(top->lines); - result(line.text); - line.clear(); + editline line; + line.combinelines(top->lines); + result(line.text); + line.clear(); ); ICOMMAND(textfocus, "si", (char *name, int *mode), // focus on a (or create a persistent) specific editor, else returns current name - if(*name) useeditor(name, *mode<=0 ? EDITORFOREVER : *mode, true); - else if(editors.length() > 0) result(editors.last()->name); + if(*name) useeditor(name, *mode<=0 ? EDITORFOREVER : *mode, true); + else if(editors.length() > 0) result(editors.last()->name); ); TEXTCOMMAND(textprev, "", (), editors.insert(0, top); editors.pop();); // return to the previous editor TEXTCOMMAND(textmode, "i", (int *m), // (1= keep while focused, 2= keep while used in gui, 3= keep forever (i.e. until mode changes)) topmost editor, return current setting if no args - if(*m) top->mode = *m; - else intret(top->mode); + if(*m) top->mode = *m; + else intret(top->mode); ); TEXTCOMMAND(textsave, "s", (char *file), // saves the topmost (filename is optional) - if(*file) top->setfile(path(file, true)); - top->save(); + if(*file) top->setfile(path(file, true)); + top->save(); ); TEXTCOMMAND(textload, "s", (char *file), // loads into the topmost editor, returns filename if no args - if(*file) - { - top->setfile(path(file, true)); - top->load(); - } - else if(top->filename) result(top->filename); + if(*file) + { + top->setfile(path(file, true)); + top->load(); + } + else if(top->filename) result(top->filename); ); TEXTCOMMAND(textinit, "sss", (char *name, char *file, char *initval), // loads into named editor if no file assigned and editor has been rendered { - editor *e = NULL; - loopv(editors) if(!strcmp(editors[i]->name, name)) { e = editors[i]; break; } - if(e && e->rendered && !e->filename && *file && (e->lines.empty() || (e->lines.length() == 1 && !strcmp(e->lines[0].text, initval)))) - { - e->setfile(path(file, true)); - e->load(); - } + editor *e = NULL; + loopv(editors) if(!strcmp(editors[i]->name, name)) { e = editors[i]; break; } + if(e && e->rendered && !e->filename && *file && (e->lines.empty() || (e->lines.length() == 1 && !strcmp(e->lines[0].text, initval)))) + { + e->setfile(path(file, true)); + e->load(); + } }); #define PASTEBUFFER "#pastebuffer" @@ -755,16 +754,16 @@ TEXTCOMMAND(textinit, "sss", (char *name, char *file, char *initval), // loads i TEXTCOMMAND(textcopy, "", (), editor *b = useeditor(PASTEBUFFER, EDITORFOREVER, false); top->copyselectionto(b);); TEXTCOMMAND(textpaste, "", (), editor *b = useeditor(PASTEBUFFER, EDITORFOREVER, false); top->insertallfrom(b);); TEXTCOMMAND(textmark, "i", (int *m), // (1=mark, 2=unmark), return current mark setting if no args - if(*m) top->mark(*m==1); - else intret(top->region() ? 1 : 2); + if(*m) top->mark(*m==1); + else intret(top->region() ? 1 : 2); ); TEXTCOMMAND(textselectall, "", (), top->selectall();); TEXTCOMMAND(textclear, "", (), top->clear();); TEXTCOMMAND(textcurrentline, "", (), result(top->currentline().text);); TEXTCOMMAND(textexec, "i", (int *selected), // execute script commands from the buffer (0=all, 1=selected region only) - char *script = *selected ? top->selectiontostring() : top->tostring(); - execute(script); - delete[] script; + char *script = *selected ? top->selectiontostring() : top->tostring(); + execute(script); + delete[] script; ); diff --git a/src/engine/texture.cpp b/src/engine/texture.cpp index e76b59e..6a0084d 100644 --- a/src/engine/texture.cpp +++ b/src/engine/texture.cpp @@ -5,611 +5,611 @@ #ifndef SDL_IMAGE_VERSION_ATLEAST #define SDL_IMAGE_VERSION_ATLEAST(X, Y, Z) \ - (SDL_VERSIONNUM(SDL_IMAGE_MAJOR_VERSION, SDL_IMAGE_MINOR_VERSION, SDL_IMAGE_PATCHLEVEL) >= SDL_VERSIONNUM(X, Y, Z)) + (SDL_VERSIONNUM(SDL_IMAGE_MAJOR_VERSION, SDL_IMAGE_MINOR_VERSION, SDL_IMAGE_PATCHLEVEL) >= SDL_VERSIONNUM(X, Y, Z)) #endif template static void halvetexture(uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst) { - for(uchar *yend = &src[sh*stride]; src < yend;) - { - for(uchar *xend = &src[sw*BPP], *xsrc = src; xsrc < xend; xsrc += 2*BPP, dst += BPP) - { - loopi(BPP) dst[i] = (uint(xsrc[i]) + uint(xsrc[i+BPP]) + uint(xsrc[stride+i]) + uint(xsrc[stride+i+BPP]))>>2; - } - src += 2*stride; - } + for(uchar *yend = &src[sh*stride]; src < yend;) + { + for(uchar *xend = &src[sw*BPP], *xsrc = src; xsrc < xend; xsrc += 2*BPP, dst += BPP) + { + loopi(BPP) dst[i] = (uint(xsrc[i]) + uint(xsrc[i+BPP]) + uint(xsrc[stride+i]) + uint(xsrc[stride+i+BPP]))>>2; + } + src += 2*stride; + } } template static void shifttexture(uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst, uint dw, uint dh) { - uint wfrac = sw/dw, hfrac = sh/dh, wshift = 0, hshift = 0; - while(dw<> tshift; - } - src += hfrac*stride; - } + uint wfrac = sw/dw, hfrac = sh/dh, wshift = 0, hshift = 0; + while(dw<> tshift; + } + src += hfrac*stride; + } } template static void scaletexture(uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst, uint dw, uint dh) { - uint wfrac = (sw<<12)/dw, hfrac = (sh<<12)/dh, darea = dw*dh, sarea = sw*sh; - int over, under; - for(over = 0; (darea>>over) > sarea; over++); - for(under = 0; (darea<>12, h = (yn>>12) - yi, ylow = ((yn|(-int(h)>>24))&0xFFFU) + 1 - (y&0xFFFU), yhigh = (yn&0xFFFU) + 1; - const uchar *ysrc = &src[yi*stride]; - for(uint x = 0; x < dw; x += wfrac, dst += BPP) - { - const uint xn = x + wfrac - 1, xi = x>>12, w = (xn>>12) - xi, xlow = ((w+0xFFFU)&0x1000U) - (x&0xFFFU), xhigh = (xn&0xFFFU) + 1; - const uchar *xsrc = &ysrc[xi*BPP], *xend = &xsrc[w*BPP]; - uint t[BPP] = {0}; - for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP) - loopi(BPP) t[i] += xcur[i]; - loopi(BPP) t[i] = (ylow*(t[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale; - if(h) - { - xsrc += stride; - xend += stride; - for(uint hcur = h; --hcur; xsrc += stride, xend += stride) - { - uint c[BPP] = {0}; - for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP) - loopi(BPP) c[i] += xcur[i]; - loopi(BPP) t[i] += ((c[i]<<12) + xsrc[i]*xlow + xend[i]*xhigh)>>cscale; - } - uint c[BPP] = {0}; - for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP) - loopi(BPP) c[i] += xcur[i]; - loopi(BPP) t[i] += (yhigh*(c[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale; - } - loopi(BPP) dst[i] = (t[i] * area)>>dscale; - } - } + uint wfrac = (sw<<12)/dw, hfrac = (sh<<12)/dh, darea = dw*dh, sarea = sw*sh; + int over, under; + for(over = 0; (darea>>over) > sarea; over++); + for(under = 0; (darea<>12, h = (yn>>12) - yi, ylow = ((yn|(-int(h)>>24))&0xFFFU) + 1 - (y&0xFFFU), yhigh = (yn&0xFFFU) + 1; + const uchar *ysrc = &src[yi*stride]; + for(uint x = 0; x < dw; x += wfrac, dst += BPP) + { + const uint xn = x + wfrac - 1, xi = x>>12, w = (xn>>12) - xi, xlow = ((w+0xFFFU)&0x1000U) - (x&0xFFFU), xhigh = (xn&0xFFFU) + 1; + const uchar *xsrc = &ysrc[xi*BPP], *xend = &xsrc[w*BPP]; + uint t[BPP] = {0}; + for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP) + loopi(BPP) t[i] += xcur[i]; + loopi(BPP) t[i] = (ylow*(t[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale; + if(h) + { + xsrc += stride; + xend += stride; + for(uint hcur = h; --hcur; xsrc += stride, xend += stride) + { + uint c[BPP] = {0}; + for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP) + loopi(BPP) c[i] += xcur[i]; + loopi(BPP) t[i] += ((c[i]<<12) + xsrc[i]*xlow + xend[i]*xhigh)>>cscale; + } + uint c[BPP] = {0}; + for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP) + loopi(BPP) c[i] += xcur[i]; + loopi(BPP) t[i] += (yhigh*(c[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale; + } + loopi(BPP) dst[i] = (t[i] * area)>>dscale; + } + } } static void scaletexture(uchar * RESTRICT src, uint sw, uint sh, uint bpp, uint pitch, uchar * RESTRICT dst, uint dw, uint dh) { - if(sw == dw*2 && sh == dh*2) - { - switch(bpp) - { - case 1: return halvetexture<1>(src, sw, sh, pitch, dst); - case 2: return halvetexture<2>(src, sw, sh, pitch, dst); - case 3: return halvetexture<3>(src, sw, sh, pitch, dst); - case 4: return halvetexture<4>(src, sw, sh, pitch, dst); - } - } - else if(sw < dw || sh < dh || sw&(sw-1) || sh&(sh-1) || dw&(dw-1) || dh&(dh-1)) - { - switch(bpp) - { - case 1: return scaletexture<1>(src, sw, sh, pitch, dst, dw, dh); - case 2: return scaletexture<2>(src, sw, sh, pitch, dst, dw, dh); - case 3: return scaletexture<3>(src, sw, sh, pitch, dst, dw, dh); - case 4: return scaletexture<4>(src, sw, sh, pitch, dst, dw, dh); - } - } - else - { - switch(bpp) - { - case 1: return shifttexture<1>(src, sw, sh, pitch, dst, dw, dh); - case 2: return shifttexture<2>(src, sw, sh, pitch, dst, dw, dh); - case 3: return shifttexture<3>(src, sw, sh, pitch, dst, dw, dh); - case 4: return shifttexture<4>(src, sw, sh, pitch, dst, dw, dh); - } - } + if(sw == dw*2 && sh == dh*2) + { + switch(bpp) + { + case 1: return halvetexture<1>(src, sw, sh, pitch, dst); + case 2: return halvetexture<2>(src, sw, sh, pitch, dst); + case 3: return halvetexture<3>(src, sw, sh, pitch, dst); + case 4: return halvetexture<4>(src, sw, sh, pitch, dst); + } + } + else if(sw < dw || sh < dh || sw&(sw-1) || sh&(sh-1) || dw&(dw-1) || dh&(dh-1)) + { + switch(bpp) + { + case 1: return scaletexture<1>(src, sw, sh, pitch, dst, dw, dh); + case 2: return scaletexture<2>(src, sw, sh, pitch, dst, dw, dh); + case 3: return scaletexture<3>(src, sw, sh, pitch, dst, dw, dh); + case 4: return scaletexture<4>(src, sw, sh, pitch, dst, dw, dh); + } + } + else + { + switch(bpp) + { + case 1: return shifttexture<1>(src, sw, sh, pitch, dst, dw, dh); + case 2: return shifttexture<2>(src, sw, sh, pitch, dst, dw, dh); + case 3: return shifttexture<3>(src, sw, sh, pitch, dst, dw, dh); + case 4: return shifttexture<4>(src, sw, sh, pitch, dst, dw, dh); + } + } } static void reorientnormals(uchar * RESTRICT src, int sw, int sh, int bpp, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy) { - int stridex = bpp, stridey = bpp; - if(swapxy) stridex *= sh; else stridey *= sw; - if(flipx) { dst += (sw-1)*stridex; stridex = -stridex; } - if(flipy) { dst += (sh-1)*stridey; stridey = -stridey; } - uchar *srcrow = src; - loopi(sh) - { - for(uchar *curdst = dst, *src = srcrow, *end = &srcrow[sw*bpp]; src < end;) - { - uchar nx = *src++, ny = *src++; - if(flipx) nx = 255-nx; - if(flipy) ny = 255-ny; - if(swapxy) swap(nx, ny); - curdst[0] = nx; - curdst[1] = ny; - curdst[2] = *src++; - if(bpp > 3) curdst[3] = *src++; - curdst += stridex; - } - srcrow += stride; - dst += stridey; - } + int stridex = bpp, stridey = bpp; + if(swapxy) stridex *= sh; else stridey *= sw; + if(flipx) { dst += (sw-1)*stridex; stridex = -stridex; } + if(flipy) { dst += (sh-1)*stridey; stridey = -stridey; } + uchar *srcrow = src; + loopi(sh) + { + for(uchar *curdst = dst, *src = srcrow, *end = &srcrow[sw*bpp]; src < end;) + { + uchar nx = *src++, ny = *src++; + if(flipx) nx = 255-nx; + if(flipy) ny = 255-ny; + if(swapxy) swap(nx, ny); + curdst[0] = nx; + curdst[1] = ny; + curdst[2] = *src++; + if(bpp > 3) curdst[3] = *src++; + curdst += stridex; + } + srcrow += stride; + dst += stridey; + } } template static inline void reorienttexture(uchar * RESTRICT src, int sw, int sh, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy) { - int stridex = BPP, stridey = BPP; - if(swapxy) stridex *= sh; else stridey *= sw; - if(flipx) { dst += (sw-1)*stridex; stridex = -stridex; } - if(flipy) { dst += (sh-1)*stridey; stridey = -stridey; } - uchar *srcrow = src; - loopi(sh) - { - for(uchar *curdst = dst, *src = srcrow, *end = &srcrow[sw*BPP]; src < end;) - { - loopk(BPP) curdst[k] = *src++; - curdst += stridex; - } - srcrow += stride; - dst += stridey; - } + int stridex = BPP, stridey = BPP; + if(swapxy) stridex *= sh; else stridey *= sw; + if(flipx) { dst += (sw-1)*stridex; stridex = -stridex; } + if(flipy) { dst += (sh-1)*stridey; stridey = -stridey; } + uchar *srcrow = src; + loopi(sh) + { + for(uchar *curdst = dst, *src = srcrow, *end = &srcrow[sw*BPP]; src < end;) + { + loopk(BPP) curdst[k] = *src++; + curdst += stridex; + } + srcrow += stride; + dst += stridey; + } } static void reorienttexture(uchar * RESTRICT src, int sw, int sh, int bpp, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy) { - switch(bpp) - { - case 1: return reorienttexture<1>(src, sw, sh, stride, dst, flipx, flipy, swapxy); - case 2: return reorienttexture<2>(src, sw, sh, stride, dst, flipx, flipy, swapxy); - case 3: return reorienttexture<3>(src, sw, sh, stride, dst, flipx, flipy, swapxy); - case 4: return reorienttexture<4>(src, sw, sh, stride, dst, flipx, flipy, swapxy); - } + switch(bpp) + { + case 1: return reorienttexture<1>(src, sw, sh, stride, dst, flipx, flipy, swapxy); + case 2: return reorienttexture<2>(src, sw, sh, stride, dst, flipx, flipy, swapxy); + case 3: return reorienttexture<3>(src, sw, sh, stride, dst, flipx, flipy, swapxy); + case 4: return reorienttexture<4>(src, sw, sh, stride, dst, flipx, flipy, swapxy); + } } static void reorients3tc(GLenum format, int blocksize, int w, int h, uchar *src, uchar *dst, bool flipx, bool flipy, bool swapxy, bool normals = false) { - int bx1 = 0, by1 = 0, bx2 = min(w, 4), by2 = min(h, 4), bw = (w+3)/4, bh = (h+3)/4, stridex = blocksize, stridey = blocksize; - if(swapxy) stridex *= bw; else stridey *= bh; - if(flipx) { dst += (bw-1)*stridex; stridex = -stridex; bx1 += 4-bx2; bx2 = 4; } - if(flipy) { dst += (bh-1)*stridey; stridey = -stridey; by1 += 4-by2; by2 = 4; } - loopi(bh) - { - for(uchar *curdst = dst, *end = &src[bw*blocksize]; src < end; src += blocksize, curdst += stridex) - { - if(format == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT) - { - ullong salpha = lilswap(*(const ullong *)src), dalpha = 0; - uint xmask = flipx ? 15 : 0, ymask = flipy ? 15 : 0, xshift = 2, yshift = 4; - if(swapxy) swap(xshift, yshift); - for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) - { - dalpha |= ((salpha&15) << (((xmask^x)<>= 4; - } - *(ullong *)curdst = lilswap(dalpha); - src += 8; - curdst += 8; - } - else if(format == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) - { - uchar alpha1 = src[0], alpha2 = src[1]; - ullong salpha = lilswap(*(const ushort *)&src[2]) + ((ullong)lilswap(*(const uint *)&src[4]) << 16), dalpha = 0; - uint xmask = flipx ? 7 : 0, ymask = flipy ? 7 : 0, xshift = 0, yshift = 2; - if(swapxy) swap(xshift, yshift); - for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) - { - dalpha |= ((salpha&7) << (3*((xmask^x)<>= 3; - } - curdst[0] = alpha1; - curdst[1] = alpha2; - *(ushort *)&curdst[2] = lilswap(ushort(dalpha)); - *(ushort *)&curdst[4] = lilswap(ushort(dalpha>>16)); - *(ushort *)&curdst[6] = lilswap(ushort(dalpha>>32)); - src += 8; - curdst += 8; - } - - ushort color1 = lilswap(*(const ushort *)src), color2 = lilswap(*(const ushort *)&src[2]); - uint sbits = lilswap(*(const uint *)&src[4]); - if(normals) - { - ushort ncolor1 = color1, ncolor2 = color2; - if(flipx) - { - ncolor1 = (ncolor1 & ~0xF800) | (0xF800 - (ncolor1 & 0xF800)); - ncolor2 = (ncolor2 & ~0xF800) | (0xF800 - (ncolor2 & 0xF800)); - } - if(flipy) - { - ncolor1 = (ncolor1 & ~0x7E0) | (0x7E0 - (ncolor1 & 0x7E0)); - ncolor2 = (ncolor2 & ~0x7E0) | (0x7E0 - (ncolor2 & 0x7E0)); - } - if(swapxy) - { - ncolor1 = (ncolor1 & 0x1F) | (((((ncolor1 >> 11) & 0x1F) * 0x3F) / 0x1F) << 5) | (((((ncolor1 >> 5) & 0x3F) * 0x1F) / 0x3F) << 11); - ncolor2 = (ncolor2 & 0x1F) | (((((ncolor2 >> 11) & 0x1F) * 0x3F) / 0x1F) << 5) | (((((ncolor2 >> 5) & 0x3F) * 0x1F) / 0x3F) << 11); - } - if(color1 <= color2 && ncolor1 > ncolor2) { color1 = ncolor2; color2 = ncolor1; } - else { color1 = ncolor1; color2 = ncolor2; } - } - uint dbits = 0, xmask = flipx ? 3 : 0, ymask = flipy ? 3 : 0, xshift = 1, yshift = 3; - if(swapxy) swap(xshift, yshift); - for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) - { - dbits |= ((sbits&3) << (((xmask^x)<>= 2; - } - *(ushort *)curdst = lilswap(color1); - *(ushort *)&curdst[2] = lilswap(color2); - *(uint *)&curdst[4] = lilswap(dbits); - - if(blocksize > 8) { src -= 8; curdst -= 8; } - } - dst += stridey; - } + int bx1 = 0, by1 = 0, bx2 = min(w, 4), by2 = min(h, 4), bw = (w+3)/4, bh = (h+3)/4, stridex = blocksize, stridey = blocksize; + if(swapxy) stridex *= bw; else stridey *= bh; + if(flipx) { dst += (bw-1)*stridex; stridex = -stridex; bx1 += 4-bx2; bx2 = 4; } + if(flipy) { dst += (bh-1)*stridey; stridey = -stridey; by1 += 4-by2; by2 = 4; } + loopi(bh) + { + for(uchar *curdst = dst, *end = &src[bw*blocksize]; src < end; src += blocksize, curdst += stridex) + { + if(format == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT) + { + ullong salpha = lilswap(*(const ullong *)src), dalpha = 0; + uint xmask = flipx ? 15 : 0, ymask = flipy ? 15 : 0, xshift = 2, yshift = 4; + if(swapxy) swap(xshift, yshift); + for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) + { + dalpha |= ((salpha&15) << (((xmask^x)<>= 4; + } + *(ullong *)curdst = lilswap(dalpha); + src += 8; + curdst += 8; + } + else if(format == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + uchar alpha1 = src[0], alpha2 = src[1]; + ullong salpha = lilswap(*(const ushort *)&src[2]) + ((ullong)lilswap(*(const uint *)&src[4]) << 16), dalpha = 0; + uint xmask = flipx ? 7 : 0, ymask = flipy ? 7 : 0, xshift = 0, yshift = 2; + if(swapxy) swap(xshift, yshift); + for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) + { + dalpha |= ((salpha&7) << (3*((xmask^x)<>= 3; + } + curdst[0] = alpha1; + curdst[1] = alpha2; + *(ushort *)&curdst[2] = lilswap(ushort(dalpha)); + *(ushort *)&curdst[4] = lilswap(ushort(dalpha>>16)); + *(ushort *)&curdst[6] = lilswap(ushort(dalpha>>32)); + src += 8; + curdst += 8; + } + + ushort color1 = lilswap(*(const ushort *)src), color2 = lilswap(*(const ushort *)&src[2]); + uint sbits = lilswap(*(const uint *)&src[4]); + if(normals) + { + ushort ncolor1 = color1, ncolor2 = color2; + if(flipx) + { + ncolor1 = (ncolor1 & ~0xF800) | (0xF800 - (ncolor1 & 0xF800)); + ncolor2 = (ncolor2 & ~0xF800) | (0xF800 - (ncolor2 & 0xF800)); + } + if(flipy) + { + ncolor1 = (ncolor1 & ~0x7E0) | (0x7E0 - (ncolor1 & 0x7E0)); + ncolor2 = (ncolor2 & ~0x7E0) | (0x7E0 - (ncolor2 & 0x7E0)); + } + if(swapxy) + { + ncolor1 = (ncolor1 & 0x1F) | (((((ncolor1 >> 11) & 0x1F) * 0x3F) / 0x1F) << 5) | (((((ncolor1 >> 5) & 0x3F) * 0x1F) / 0x3F) << 11); + ncolor2 = (ncolor2 & 0x1F) | (((((ncolor2 >> 11) & 0x1F) * 0x3F) / 0x1F) << 5) | (((((ncolor2 >> 5) & 0x3F) * 0x1F) / 0x3F) << 11); + } + if(color1 <= color2 && ncolor1 > ncolor2) { color1 = ncolor2; color2 = ncolor1; } + else { color1 = ncolor1; color2 = ncolor2; } + } + uint dbits = 0, xmask = flipx ? 3 : 0, ymask = flipy ? 3 : 0, xshift = 1, yshift = 3; + if(swapxy) swap(xshift, yshift); + for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) + { + dbits |= ((sbits&3) << (((xmask^x)<>= 2; + } + *(ushort *)curdst = lilswap(color1); + *(ushort *)&curdst[2] = lilswap(color2); + *(uint *)&curdst[4] = lilswap(dbits); + + if(blocksize > 8) { src -= 8; curdst -= 8; } + } + dst += stridey; + } } static void reorientrgtc(GLenum format, int blocksize, int w, int h, uchar *src, uchar *dst, bool flipx, bool flipy, bool swapxy) { - int bx1 = 0, by1 = 0, bx2 = min(w, 4), by2 = min(h, 4), bw = (w+3)/4, bh = (h+3)/4, stridex = blocksize, stridey = blocksize; - if(swapxy) stridex *= bw; else stridey *= bh; - if(flipx) { dst += (bw-1)*stridex; stridex = -stridex; bx1 += 4-bx2; bx2 = 4; } - if(flipy) { dst += (bh-1)*stridey; stridey = -stridey; by1 += 4-by2; by2 = 4; } - stridex -= blocksize; - loopi(bh) - { - for(uchar *curdst = dst, *end = &src[bw*blocksize]; src < end; curdst += stridex) - { - loopj(blocksize/8) - { - uchar val1 = src[0], val2 = src[1]; - ullong sval = lilswap(*(const ushort *)&src[2]) + ((ullong)lilswap(*(const uint *)&src[4] )<< 16), dval = 0; - uint xmask = flipx ? 7 : 0, ymask = flipy ? 7 : 0, xshift = 0, yshift = 2; - if(swapxy) swap(xshift, yshift); - for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) - { - dval |= ((sval&7) << (3*((xmask^x)<>= 3; - } - curdst[0] = val1; - curdst[1] = val2; - *(ushort *)&curdst[2] = lilswap(ushort(dval)); - *(ushort *)&curdst[4] = lilswap(ushort(dval>>16)); - *(ushort *)&curdst[6] = lilswap(ushort(dval>>32)); - src += 8; - curdst += 8; - } - } - dst += stridey; - } + int bx1 = 0, by1 = 0, bx2 = min(w, 4), by2 = min(h, 4), bw = (w+3)/4, bh = (h+3)/4, stridex = blocksize, stridey = blocksize; + if(swapxy) stridex *= bw; else stridey *= bh; + if(flipx) { dst += (bw-1)*stridex; stridex = -stridex; bx1 += 4-bx2; bx2 = 4; } + if(flipy) { dst += (bh-1)*stridey; stridey = -stridey; by1 += 4-by2; by2 = 4; } + stridex -= blocksize; + loopi(bh) + { + for(uchar *curdst = dst, *end = &src[bw*blocksize]; src < end; curdst += stridex) + { + loopj(blocksize/8) + { + uchar val1 = src[0], val2 = src[1]; + ullong sval = lilswap(*(const ushort *)&src[2]) + ((ullong)lilswap(*(const uint *)&src[4] )<< 16), dval = 0; + uint xmask = flipx ? 7 : 0, ymask = flipy ? 7 : 0, xshift = 0, yshift = 2; + if(swapxy) swap(xshift, yshift); + for(int y = by1; y < by2; y++) for(int x = bx1; x < bx2; x++) + { + dval |= ((sval&7) << (3*((xmask^x)<>= 3; + } + curdst[0] = val1; + curdst[1] = val2; + *(ushort *)&curdst[2] = lilswap(ushort(dval)); + *(ushort *)&curdst[4] = lilswap(ushort(dval>>16)); + *(ushort *)&curdst[6] = lilswap(ushort(dval>>32)); + src += 8; + curdst += 8; + } + } + dst += stridey; + } } #define writetex(t, body) do \ - { \ - uchar *dstrow = t.data; \ - loop(y, t.h) \ - { \ - for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp]; dst < end; dst += t.bpp) \ - { \ - body; \ - } \ - dstrow += t.pitch; \ - } \ - } while(0) + { \ + uchar *dstrow = t.data; \ + loop(y, t.h) \ + { \ + for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp]; dst < end; dst += t.bpp) \ + { \ + body; \ + } \ + dstrow += t.pitch; \ + } \ + } while(0) #define readwritetex(t, s, body) do \ - { \ - uchar *dstrow = t.data, *srcrow = s.data; \ - loop(y, t.h) \ - { \ - for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; dst += t.bpp, src += s.bpp) \ - { \ - body; \ - } \ - dstrow += t.pitch; \ - srcrow += s.pitch; \ - } \ - } while(0) + { \ + uchar *dstrow = t.data, *srcrow = s.data; \ + loop(y, t.h) \ + { \ + for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; dst += t.bpp, src += s.bpp) \ + { \ + body; \ + } \ + dstrow += t.pitch; \ + srcrow += s.pitch; \ + } \ + } while(0) #define read2writetex(t, s1, src1, s2, src2, body) do \ - { \ - uchar *dstrow = t.data, *src1row = s1.data, *src2row = s2.data; \ - loop(y, t.h) \ - { \ - for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp], *src1 = src1row, *src2 = src2row; dst < end; dst += t.bpp, src1 += s1.bpp, src2 += s2.bpp) \ - { \ - body; \ - } \ - dstrow += t.pitch; \ - src1row += s1.pitch; \ - src2row += s2.pitch; \ - } \ - } while(0) + { \ + uchar *dstrow = t.data, *src1row = s1.data, *src2row = s2.data; \ + loop(y, t.h) \ + { \ + for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp], *src1 = src1row, *src2 = src2row; dst < end; dst += t.bpp, src1 += s1.bpp, src2 += s2.bpp) \ + { \ + body; \ + } \ + dstrow += t.pitch; \ + src1row += s1.pitch; \ + src2row += s2.pitch; \ + } \ + } while(0) #define readwritergbtex(t, s, body) \ - { \ - if(t.bpp >= 3) readwritetex(t, s, body); \ - else \ - { \ - ImageData rgb(t.w, t.h, 3); \ - read2writetex(rgb, t, orig, s, src, { dst[0] = dst[1] = dst[2] = orig[0]; body; }); \ - t.replace(rgb); \ - } \ - } + { \ + if(t.bpp >= 3) readwritetex(t, s, body); \ + else \ + { \ + ImageData rgb(t.w, t.h, 3); \ + read2writetex(rgb, t, orig, s, src, { dst[0] = dst[1] = dst[2] = orig[0]; body; }); \ + t.replace(rgb); \ + } \ + } void forcergbimage(ImageData &s) { - if(s.bpp >= 3) return; - ImageData d(s.w, s.h, 3); - readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; }); - s.replace(d); + if(s.bpp >= 3) return; + ImageData d(s.w, s.h, 3); + readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; }); + s.replace(d); } #define readwritergbatex(t, s, body) \ - { \ - if(t.bpp >= 4) { readwritetex(t, s, body); } \ - else \ - { \ - ImageData rgba(t.w, t.h, 4); \ - if(t.bpp==3) read2writetex(rgba, t, orig, s, src, { dst[0] = orig[0]; dst[1] = orig[1]; dst[2] = orig[2]; body; }); \ - else read2writetex(rgba, t, orig, s, src, { dst[0] = dst[1] = dst[2] = orig[0]; body; }); \ - t.replace(rgba); \ - } \ - } + { \ + if(t.bpp >= 4) { readwritetex(t, s, body); } \ + else \ + { \ + ImageData rgba(t.w, t.h, 4); \ + if(t.bpp==3) read2writetex(rgba, t, orig, s, src, { dst[0] = orig[0]; dst[1] = orig[1]; dst[2] = orig[2]; body; }); \ + else read2writetex(rgba, t, orig, s, src, { dst[0] = dst[1] = dst[2] = orig[0]; body; }); \ + t.replace(rgba); \ + } \ + } void forcergbaimage(ImageData &s) { - if(s.bpp >= 4) return; - ImageData d(s.w, s.h, 4); - if(s.bpp==3) readwritetex(d, s, { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; }); - else readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; }); - s.replace(d); + if(s.bpp >= 4) return; + ImageData d(s.w, s.h, 4); + if(s.bpp==3) readwritetex(d, s, { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; }); + else readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; }); + s.replace(d); } void swizzleimage(ImageData &s) { - if(s.bpp==2) - { - ImageData d(s.w, s.h, 4); - readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; dst[3] = src[1]; }); - s.replace(d); - } - else if(s.bpp==1) - { - ImageData d(s.w, s.h, 3); - readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; }); - s.replace(d); - } + if(s.bpp==2) + { + ImageData d(s.w, s.h, 4); + readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; dst[3] = src[1]; }); + s.replace(d); + } + else if(s.bpp==1) + { + ImageData d(s.w, s.h, 3); + readwritetex(d, s, { dst[0] = dst[1] = dst[2] = src[0]; }); + s.replace(d); + } } void texreorient(ImageData &s, bool flipx, bool flipy, bool swapxy, int type = TEX_DIFFUSE) { - ImageData d(swapxy ? s.h : s.w, swapxy ? s.w : s.h, s.bpp, s.levels, s.align, s.compressed); - switch(s.compressed) - { - case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: - [[fallthrough]]; - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - [[fallthrough]]; - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - [[fallthrough]]; - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - { - uchar *dst = d.data, *src = s.data; - loopi(s.levels) - { - reorients3tc(s.compressed, s.bpp, max(s.w>>i, 1), max(s.h>>i, 1), src, dst, flipx, flipy, swapxy, type==TEX_NORMAL); - src += s.calclevelsize(i); - dst += d.calclevelsize(i); - } - break; - } - case GL_COMPRESSED_RED_RGTC1: - [[fallthrough]]; - case GL_COMPRESSED_RG_RGTC2: - [[fallthrough]]; - case GL_COMPRESSED_LUMINANCE_LATC1_EXT: - [[fallthrough]]; - case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: - { - uchar *dst = d.data, *src = s.data; - loopi(s.levels) - { - reorientrgtc(s.compressed, s.bpp, max(s.w>>i, 1), max(s.h>>i, 1), src, dst, flipx, flipy, swapxy); - src += s.calclevelsize(i); - dst += d.calclevelsize(i); - } - break; - } - default: - if(type==TEX_NORMAL && s.bpp >= 3) reorientnormals(s.data, s.w, s.h, s.bpp, s.pitch, d.data, flipx, flipy, swapxy); - else reorienttexture(s.data, s.w, s.h, s.bpp, s.pitch, d.data, flipx, flipy, swapxy); - break; - } - s.replace(d); + ImageData d(swapxy ? s.h : s.w, swapxy ? s.w : s.h, s.bpp, s.levels, s.align, s.compressed); + switch(s.compressed) + { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + [[fallthrough]]; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + [[fallthrough]]; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + [[fallthrough]]; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + { + uchar *dst = d.data, *src = s.data; + loopi(s.levels) + { + reorients3tc(s.compressed, s.bpp, max(s.w>>i, 1), max(s.h>>i, 1), src, dst, flipx, flipy, swapxy, type==TEX_NORMAL); + src += s.calclevelsize(i); + dst += d.calclevelsize(i); + } + break; + } + case GL_COMPRESSED_RED_RGTC1: + [[fallthrough]]; + case GL_COMPRESSED_RG_RGTC2: + [[fallthrough]]; + case GL_COMPRESSED_LUMINANCE_LATC1_EXT: + [[fallthrough]]; + case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: + { + uchar *dst = d.data, *src = s.data; + loopi(s.levels) + { + reorientrgtc(s.compressed, s.bpp, max(s.w>>i, 1), max(s.h>>i, 1), src, dst, flipx, flipy, swapxy); + src += s.calclevelsize(i); + dst += d.calclevelsize(i); + } + break; + } + default: + if(type==TEX_NORMAL && s.bpp >= 3) reorientnormals(s.data, s.w, s.h, s.bpp, s.pitch, d.data, flipx, flipy, swapxy); + else reorienttexture(s.data, s.w, s.h, s.bpp, s.pitch, d.data, flipx, flipy, swapxy); + break; + } + s.replace(d); } extern const texrotation texrotations[8] = { - { false, false, false }, // 0: default - { false, true, true }, // 1: 90 degrees - { true, true, false }, // 2: 180 degrees - { true, false, true }, // 3: 270 degrees - { true, false, false }, // 4: flip X - { false, true, false }, // 5: flip Y - { false, false, true }, // 6: transpose - { true, true, true }, // 7: flipped transpose + { false, false, false }, // 0: default + { false, true, true }, // 1: 90 degrees + { true, true, false }, // 2: 180 degrees + { true, false, true }, // 3: 270 degrees + { true, false, false }, // 4: flip X + { false, true, false }, // 5: flip Y + { false, false, true }, // 6: transpose + { true, true, true }, // 7: flipped transpose }; void texrotate(ImageData &s, int numrots, int type = TEX_DIFFUSE) { - if(numrots>=1 && numrots<=7) - { - const texrotation &r = texrotations[numrots]; - texreorient(s, r.flipx, r.flipy, r.swapxy, type); - } + if(numrots>=1 && numrots<=7) + { + const texrotation &r = texrotations[numrots]; + texreorient(s, r.flipx, r.flipy, r.swapxy, type); + } } void texoffset(ImageData &s, int xoffset, int yoffset) { - xoffset = max(xoffset, 0); - xoffset %= s.w; - yoffset = max(yoffset, 0); - yoffset %= s.h; - if(!xoffset && !yoffset) return; - ImageData d(s.w, s.h, s.bpp); - uchar *src = s.data; - loop(y, s.h) - { - uchar *dst = (uchar *)d.data+((y+yoffset)%d.h)*d.pitch; - memcpy(dst+xoffset*s.bpp, src, (s.w-xoffset)*s.bpp); - memcpy(dst, src+(s.w-xoffset)*s.bpp, xoffset*s.bpp); - src += s.pitch; - } - s.replace(d); + xoffset = max(xoffset, 0); + xoffset %= s.w; + yoffset = max(yoffset, 0); + yoffset %= s.h; + if(!xoffset && !yoffset) return; + ImageData d(s.w, s.h, s.bpp); + uchar *src = s.data; + loop(y, s.h) + { + uchar *dst = (uchar *)d.data+((y+yoffset)%d.h)*d.pitch; + memcpy(dst+xoffset*s.bpp, src, (s.w-xoffset)*s.bpp); + memcpy(dst, src+(s.w-xoffset)*s.bpp, xoffset*s.bpp); + src += s.pitch; + } + s.replace(d); } void texmad(ImageData &s, const vec &mul, const vec &add) { - if(s.bpp < 3 && (mul.x != mul.y || mul.y != mul.z || add.x != add.y || add.y != add.z)) - swizzleimage(s); - int maxk = min(int(s.bpp), 3); - writetex(s, - loopk(maxk) dst[k] = uchar(clamp(dst[k]*mul[k] + 255*add[k], 0.0f, 255.0f)); - ); + if(s.bpp < 3 && (mul.x != mul.y || mul.y != mul.z || add.x != add.y || add.y != add.z)) + swizzleimage(s); + int maxk = min(int(s.bpp), 3); + writetex(s, + loopk(maxk) dst[k] = uchar(clamp(dst[k]*mul[k] + 255*add[k], 0.0f, 255.0f)); + ); } void texcolorify(ImageData &s, const vec &color, vec weights) { - if(s.bpp < 3) return; - if(weights.iszero()) weights = vec(0.21f, 0.72f, 0.07f); - writetex(s, - float lum = dst[0]*weights.x + dst[1]*weights.y + dst[2]*weights.z; - loopk(3) dst[k] = uchar(clamp(lum*color[k], 0.0f, 255.0f)); - ); + if(s.bpp < 3) return; + if(weights.iszero()) weights = vec(0.21f, 0.72f, 0.07f); + writetex(s, + float lum = dst[0]*weights.x + dst[1]*weights.y + dst[2]*weights.z; + loopk(3) dst[k] = uchar(clamp(lum*color[k], 0.0f, 255.0f)); + ); } void texcolormask(ImageData &s, const vec &color1, const vec &color2) { - if(s.bpp < 4) return; - ImageData d(s.w, s.h, 3); - readwritetex(d, s, - vec color; - color.lerp(color2, color1, src[3]/255.0f); - loopk(3) dst[k] = uchar(clamp(color[k]*src[k], 0.0f, 255.0f)); - ); - s.replace(d); + if(s.bpp < 4) return; + ImageData d(s.w, s.h, 3); + readwritetex(d, s, + vec color; + color.lerp(color2, color1, src[3]/255.0f); + loopk(3) dst[k] = uchar(clamp(color[k]*src[k], 0.0f, 255.0f)); + ); + s.replace(d); } void texdup(ImageData &s, int srcchan, int dstchan) { - if(srcchan==dstchan || max(srcchan, dstchan) >= s.bpp) return; - writetex(s, dst[dstchan] = dst[srcchan]); + if(srcchan==dstchan || max(srcchan, dstchan) >= s.bpp) return; + writetex(s, dst[dstchan] = dst[srcchan]); } void texmix(ImageData &s, int c1, int c2, int c3, int c4) { - int numchans = c1 < 0 ? 0 : (c2 < 0 ? 1 : (c3 < 0 ? 2 : (c4 < 0 ? 3 : 4))); - if(numchans <= 0) return; - ImageData d(s.w, s.h, numchans); - readwritetex(d, s, - switch(numchans) - { - case 4: dst[3] = src[c4]; - [[fallthrough]]; - case 3: dst[2] = src[c3]; - [[fallthrough]]; - case 2: dst[1] = src[c2]; - [[fallthrough]]; - case 1: dst[0] = src[c1]; break; - default: break; - } - ); - s.replace(d); + int numchans = c1 < 0 ? 0 : (c2 < 0 ? 1 : (c3 < 0 ? 2 : (c4 < 0 ? 3 : 4))); + if(numchans <= 0) return; + ImageData d(s.w, s.h, numchans); + readwritetex(d, s, + switch(numchans) + { + case 4: dst[3] = src[c4]; + [[fallthrough]]; + case 3: dst[2] = src[c3]; + [[fallthrough]]; + case 2: dst[1] = src[c2]; + [[fallthrough]]; + case 1: dst[0] = src[c1]; break; + default: break; + } + ); + s.replace(d); } void texgrey(ImageData &s) { - if(s.bpp <= 2) return; - ImageData d(s.w, s.h, s.bpp >= 4 ? 2 : 1); - if(s.bpp >= 4) - { - readwritetex(d, s, - dst[0] = src[0]; - dst[1] = src[3]; - ); - } - else - { - readwritetex(d, s, dst[0] = src[0]); - } - s.replace(d); + if(s.bpp <= 2) return; + ImageData d(s.w, s.h, s.bpp >= 4 ? 2 : 1); + if(s.bpp >= 4) + { + readwritetex(d, s, + dst[0] = src[0]; + dst[1] = src[3]; + ); + } + else + { + readwritetex(d, s, dst[0] = src[0]); + } + s.replace(d); } void texpremul(ImageData &s) { - switch(s.bpp) - { - case 2: - writetex(s, - dst[0] = uchar((uint(dst[0])*uint(dst[1]))/255); - ); - break; - case 4: - writetex(s, - uint alpha = dst[3]; - dst[0] = uchar((uint(dst[0])*alpha)/255); - dst[1] = uchar((uint(dst[1])*alpha)/255); - dst[2] = uchar((uint(dst[2])*alpha)/255); - ); - break; - } + switch(s.bpp) + { + case 2: + writetex(s, + dst[0] = uchar((uint(dst[0])*uint(dst[1]))/255); + ); + break; + case 4: + writetex(s, + uint alpha = dst[3]; + dst[0] = uchar((uint(dst[0])*alpha)/255); + dst[1] = uchar((uint(dst[1])*alpha)/255); + dst[2] = uchar((uint(dst[2])*alpha)/255); + ); + break; + } } void texagrad(ImageData &s, float x2, float y2, float x1, float y1) { - if(s.bpp != 2 && s.bpp != 4) return; - y1 = 1 - y1; - y2 = 1 - y2; - float minx = 1, miny = 1, maxx = 1, maxy = 1; - if(x1 != x2) - { - minx = (0 - x1) / (x2 - x1); - maxx = (1 - x1) / (x2 - x1); - } - if(y1 != y2) - { - miny = (0 - y1) / (y2 - y1); - maxy = (1 - y1) / (y2 - y1); - } - float dx = (maxx - minx)/max(s.w-1, 1), - dy = (maxy - miny)/max(s.h-1, 1), - cury = miny; - for(uchar *dstrow = s.data + s.bpp - 1, *endrow = dstrow + s.h*s.pitch; dstrow < endrow; dstrow += s.pitch) - { - float curx = minx; - for(uchar *dst = dstrow, *end = &dstrow[s.w*s.bpp]; dst < end; dst += s.bpp) - { - dst[0] = uchar(dst[0]*clamp(curx, 0.0f, 1.0f)*clamp(cury, 0.0f, 1.0f)); - curx += dx; - } - cury += dy; - } + if(s.bpp != 2 && s.bpp != 4) return; + y1 = 1 - y1; + y2 = 1 - y2; + float minx = 1, miny = 1, maxx = 1, maxy = 1; + if(x1 != x2) + { + minx = (0 - x1) / (x2 - x1); + maxx = (1 - x1) / (x2 - x1); + } + if(y1 != y2) + { + miny = (0 - y1) / (y2 - y1); + maxy = (1 - y1) / (y2 - y1); + } + float dx = (maxx - minx)/max(s.w-1, 1), + dy = (maxy - miny)/max(s.h-1, 1), + cury = miny; + for(uchar *dstrow = s.data + s.bpp - 1, *endrow = dstrow + s.h*s.pitch; dstrow < endrow; dstrow += s.pitch) + { + float curx = minx; + for(uchar *dst = dstrow, *end = &dstrow[s.w*s.bpp]; dst < end; dst += s.bpp) + { + dst[0] = uchar(dst[0]*clamp(curx, 0.0f, 1.0f)*clamp(cury, 0.0f, 1.0f)); + curx += dx; + } + cury += dy; + } } VAR(hwtexsize, 1, 0, 0); @@ -628,384 +628,348 @@ extern int usetexcompress; void setuptexcompress() { - if(!usetexcompress) return; + if(!usetexcompress) return; - GLenum hint = GL_DONT_CARE; - switch(texcompressquality) - { - case 1: hint = GL_NICEST; break; - case 0: hint = GL_FASTEST; break; - } - glHint(GL_TEXTURE_COMPRESSION_HINT, hint); + GLenum hint = GL_DONT_CARE; + switch(texcompressquality) + { + case 1: hint = GL_NICEST; break; + case 0: hint = GL_FASTEST; break; + } + glHint(GL_TEXTURE_COMPRESSION_HINT, hint); } GLenum compressedformat(GLenum format, int w, int h, int force = 0) { - if(usetexcompress && texcompress && force >= 0 && (force || max(w, h) >= texcompress)) switch(format) - { - case GL_RGB5: - case GL_RGB8: - case GL_RGB: return usetexcompress > 1 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB; - case GL_RGB5_A1: return usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA; - case GL_RGBA: return usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA; - case GL_RED: - case GL_R8: return hasRGTC ? (usetexcompress > 1 ? GL_COMPRESSED_RED_RGTC1 : GL_COMPRESSED_RED) : (usetexcompress > 1 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB); - case GL_RG: - case GL_RG8: return hasRGTC ? (usetexcompress > 1 ? GL_COMPRESSED_RG_RGTC2 : GL_COMPRESSED_RG) : (usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA); - case GL_LUMINANCE: - case GL_LUMINANCE8: return hasLATC ? (usetexcompress > 1 ? GL_COMPRESSED_LUMINANCE_LATC1_EXT : GL_COMPRESSED_LUMINANCE) : (usetexcompress > 1 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB); - case GL_LUMINANCE_ALPHA: - case GL_LUMINANCE8_ALPHA8: return hasLATC ? (usetexcompress > 1 ? GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT : GL_COMPRESSED_LUMINANCE_ALPHA) : (usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA); - } - return format; + if(usetexcompress && texcompress && force >= 0 && (force || max(w, h) >= texcompress)) switch(format) + { + case GL_RGB5: + case GL_RGB8: + case GL_RGB: return usetexcompress > 1 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB; + case GL_RGB5_A1: return usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA; + case GL_RGBA: return usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA; + case GL_RED: + case GL_R8: return hasRGTC ? (usetexcompress > 1 ? GL_COMPRESSED_RED_RGTC1 : GL_COMPRESSED_RED) : (usetexcompress > 1 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB); + case GL_RG: + case GL_RG8: return hasRGTC ? (usetexcompress > 1 ? GL_COMPRESSED_RG_RGTC2 : GL_COMPRESSED_RG) : (usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA); + case GL_LUMINANCE: + case GL_LUMINANCE8: return hasLATC ? (usetexcompress > 1 ? GL_COMPRESSED_LUMINANCE_LATC1_EXT : GL_COMPRESSED_LUMINANCE) : (usetexcompress > 1 ? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB); + case GL_LUMINANCE_ALPHA: + case GL_LUMINANCE8_ALPHA8: return hasLATC ? (usetexcompress > 1 ? GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT : GL_COMPRESSED_LUMINANCE_ALPHA) : (usetexcompress > 1 ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA); + } + return format; } GLenum uncompressedformat(GLenum format) { - switch(format) - { - case GL_COMPRESSED_ALPHA: - return GL_ALPHA; - case GL_COMPRESSED_LUMINANCE: - case GL_COMPRESSED_LUMINANCE_LATC1_EXT: - return GL_LUMINANCE; - case GL_COMPRESSED_LUMINANCE_ALPHA: - case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: - return GL_LUMINANCE_ALPHA; - case GL_COMPRESSED_RED: - case GL_COMPRESSED_RED_RGTC1: - return GL_RED; - case GL_COMPRESSED_RG: - case GL_COMPRESSED_RG_RGTC2: - return GL_RG; - case GL_COMPRESSED_RGB: - case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: - return GL_RGB; - case GL_COMPRESSED_RGBA: - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - return GL_RGBA; - } - return GL_FALSE; + switch(format) + { + case GL_COMPRESSED_ALPHA: + return GL_ALPHA; + case GL_COMPRESSED_LUMINANCE: + case GL_COMPRESSED_LUMINANCE_LATC1_EXT: + return GL_LUMINANCE; + case GL_COMPRESSED_LUMINANCE_ALPHA: + case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: + return GL_LUMINANCE_ALPHA; + case GL_COMPRESSED_RED: + case GL_COMPRESSED_RED_RGTC1: + return GL_RED; + case GL_COMPRESSED_RG: + case GL_COMPRESSED_RG_RGTC2: + return GL_RG; + case GL_COMPRESSED_RGB: + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + return GL_RGB; + case GL_COMPRESSED_RGBA: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return GL_RGBA; + } + return GL_FALSE; } int formatsize(GLenum format) { - switch(format) - { - case GL_RED: - case GL_LUMINANCE: - case GL_ALPHA: return 1; - case GL_RG: - case GL_LUMINANCE_ALPHA: return 2; - case GL_RGB: return 3; - case GL_RGBA: return 4; - default: return 4; - } + switch(format) + { + case GL_RED: + case GL_LUMINANCE: + case GL_ALPHA: return 1; + case GL_RG: + case GL_LUMINANCE_ALPHA: return 2; + case GL_RGB: return 3; + case GL_RGBA: return 4; + default: return 4; + } } VARFP(usenp2, 0, 0, 1, initwarning("texture quality", INIT_LOAD)); void resizetexture(int w, int h, bool mipmap, bool canreduce, GLenum target, int compress, int &tw, int &th) { - int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize, - sizelimit = mipmap && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit; - if(compress > 0 && !usetexcompress) - { - w = max(w/compress, 1); - h = max(h/compress, 1); - } - if(canreduce && texreduce) - { - w = max(w>>texreduce, 1); - h = max(h>>texreduce, 1); - } - w = min(w, sizelimit); - h = min(h, sizelimit); - if(!usenp2 && (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; - } -} - -static GLuint mipmapfbo[2] = { 0, 0 }; - -void cleanupmipmaps() -{ - if(mipmapfbo[0]) { glDeleteFramebuffers_(2, mipmapfbo); memset(mipmapfbo, 0, sizeof(mipmapfbo)); } -} - -VARFP(gpumipmap, 0, 0, 1, cleanupmipmaps()); + int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize, + sizelimit = mipmap && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit; + if(compress > 0 && !usetexcompress) + { + w = max(w/compress, 1); + h = max(h/compress, 1); + } + if(canreduce && texreduce) + { + w = max(w>>texreduce, 1); + h = max(h>>texreduce, 1); + } + w = min(w, sizelimit); + h = min(h, sizelimit); + if(!usenp2 && (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(int tnum, GLenum target, GLenum internal, int tw, int th, GLenum format, GLenum type, void *pixels, int pw, int ph, int pitch, bool mipmap) { - int bpp = formatsize(format), row = 0, rowalign = 0; - if(!pitch) pitch = pw*bpp; - uchar *buf = NULL; - if(pw!=tw || ph!=th) - { - buf = new uchar[tw*th*bpp]; - scaletexture((uchar *)pixels, pw, ph, bpp, pitch, buf, tw, th); - } - else if(tw*bpp != pitch) - { - row = pitch/bpp; - rowalign = texalign(pixels, pitch, 1); - while(rowalign > 0 && ((row*bpp + rowalign - 1)/rowalign)*rowalign != pitch) rowalign >>= 1; - if(!rowalign) - { - row = 0; - buf = new uchar[tw*th*bpp]; - loopi(th) memcpy(&buf[i*tw*bpp], &((uchar *)pixels)[i*pitch], tw*bpp); - } - } - bool shouldgpumipmap = pixels && mipmap && max(tw, th) > 1 && gpumipmap && hasFBB && !uncompressedformat(internal); - for(int level = 0, align = 0, mw = tw, mh = th;; level++) - { - uchar *src = buf ? buf : (uchar *)pixels; - if(buf) pitch = mw*bpp; - int srcalign = row > 0 ? rowalign : texalign(src, pitch, 1); - if(align != srcalign) glPixelStorei(GL_UNPACK_ALIGNMENT, align = srcalign); - if(row > 0) glPixelStorei(GL_UNPACK_ROW_LENGTH, row); - glTexImage2D(target, level, internal, mw, mh, 0, format, type, src); - if(row > 0) glPixelStorei(GL_UNPACK_ROW_LENGTH, row = 0); - if(!mipmap || shouldgpumipmap || max(mw, mh) <= 1) break; - int srcw = mw, srch = mh; - if(mw > 1) mw /= 2; - if(mh > 1) mh /= 2; - if(src) - { - if(!buf) buf = new uchar[mw*mh*bpp]; - scaletexture(src, srcw, srch, bpp, pitch, buf, mw, mh); - } - } - if(buf) delete[] buf; - if(shouldgpumipmap) - { - GLint fbo = 0; - if(!inbetweenframes || drawtex) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo); - for(int level = 1, mw = tw, mh = th; max(mw, mh) > 1; level++) - { - if(mw > 1) mw /= 2; - if(mh > 1) mh /= 2; - glTexImage2D(target, level, internal, mw, mh, 0, format, type, NULL); - } - if(!mipmapfbo[0]) glGenFramebuffers_(2, mipmapfbo); - glBindFramebuffer_(GL_READ_FRAMEBUFFER, mipmapfbo[0]); - glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, mipmapfbo[1]); - for(int level = 1, mw = tw, mh = th; max(mw, mh) > 1; level++) - { - int srcw = mw, srch = mh; - if(mw > 1) mw /= 2; - if(mh > 1) mh /= 2; - glFramebufferTexture2D_(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, tnum, level - 1); - glFramebufferTexture2D_(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, tnum, level); - glBlitFramebuffer_(0, 0, srcw, srch, 0, 0, mw, mh, GL_COLOR_BUFFER_BIT, GL_LINEAR); - } - glFramebufferTexture2D_(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, 0, 0); - glFramebufferTexture2D_(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, 0, 0); - glBindFramebuffer_(GL_FRAMEBUFFER, fbo); - } + int bpp = formatsize(format), row = 0, rowalign = 0; + if(!pitch) pitch = pw*bpp; + uchar *buf = NULL; + if(pw!=tw || ph!=th) + { + buf = new uchar[tw*th*bpp]; + scaletexture((uchar *)pixels, pw, ph, bpp, pitch, buf, tw, th); + } + else if(tw*bpp != pitch) + { + row = pitch/bpp; + rowalign = texalign(pixels, pitch, 1); + while(rowalign > 0 && ((row*bpp + rowalign - 1)/rowalign)*rowalign != pitch) rowalign >>= 1; + if(!rowalign) + { + row = 0; + buf = new uchar[tw*th*bpp]; + loopi(th) memcpy(&buf[i*tw*bpp], &((uchar *)pixels)[i*pitch], tw*bpp); + } + } + for(int level = 0, align = 0, mw = tw, mh = th;; level++) + { + uchar *src = buf ? buf : (uchar *)pixels; + if(buf) pitch = mw*bpp; + int srcalign = row > 0 ? rowalign : texalign(src, pitch, 1); + if(align != srcalign) glPixelStorei(GL_UNPACK_ALIGNMENT, align = srcalign); + if(row > 0) glPixelStorei(GL_UNPACK_ROW_LENGTH, row); + glTexImage2D(target, level, internal, mw, mh, 0, format, type, src); + if(row > 0) glPixelStorei(GL_UNPACK_ROW_LENGTH, row = 0); + if(!mipmap || max(mw, mh) <= 1) break; + int srcw = mw, srch = mh; + if(mw > 1) mw /= 2; + if(mh > 1) mh /= 2; + if(src) + { + if(!buf) buf = new uchar[mw*mh*bpp]; + scaletexture(src, srcw, srch, bpp, pitch, buf, mw, mh); + } + } + if(buf) delete[] buf; } void uploadcompressedtexture(GLenum target, GLenum subtarget, GLenum format, int w, int h, uchar *data, int align, int blocksize, int levels, bool mipmap) { - int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize, - sizelimit = levels > 1 && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit; - int level = 0; - loopi(levels) - { - int size = ((w + align-1)/align) * ((h + align-1)/align) * blocksize; - if(w <= sizelimit && h <= sizelimit) - { - glCompressedTexImage2D_(subtarget, level, format, w, h, 0, size, data); - level++; - if(!mipmap) break; - } - if(max(w, h) <= 1) break; - if(w > 1) w /= 2; - if(h > 1) h /= 2; - data += size; - } + int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize, + sizelimit = levels > 1 && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit; + int level = 0; + loopi(levels) + { + int size = ((w + align-1)/align) * ((h + align-1)/align) * blocksize; + if(w <= sizelimit && h <= sizelimit) + { + glCompressedTexImage2D_(subtarget, level, format, w, h, 0, size, data); + level++; + if(!mipmap) break; + } + if(max(w, h) <= 1) break; + if(w > 1) w /= 2; + if(h > 1) h /= 2; + data += size; + } } GLenum textarget(GLenum subtarget) { - switch(subtarget) - { - case GL_TEXTURE_CUBE_MAP_POSITIVE_X: - case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: - case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: - case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: - case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: - case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: - return GL_TEXTURE_CUBE_MAP; - } - return subtarget; + switch(subtarget) + { + case GL_TEXTURE_CUBE_MAP_POSITIVE_X: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: + case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: + case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: + return GL_TEXTURE_CUBE_MAP; + } + return subtarget; } const GLint *swizzlemask(GLenum format) { - static const GLint luminance[4] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - static const GLint luminancealpha[4] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; - switch(format) - { - case GL_RED: return luminance; - case GL_RG: return luminancealpha; - } - return NULL; + static const GLint luminance[4] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + static const GLint luminancealpha[4] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; + switch(format) + { + case GL_RED: return luminance; + case GL_RG: return luminancealpha; + } + return NULL; } void setuptexparameters(int tnum, void *pixels, int clamp, int filter, GLenum format, GLenum target, bool swizzle) { - glBindTexture(target, tnum); - glTexParameteri(target, GL_TEXTURE_WRAP_S, clamp&1 ? GL_CLAMP_TO_EDGE : (clamp&0x100 ? GL_MIRRORED_REPEAT : GL_REPEAT)); - glTexParameteri(target, GL_TEXTURE_WRAP_T, clamp&2 ? GL_CLAMP_TO_EDGE : (clamp&0x200 ? GL_MIRRORED_REPEAT : GL_REPEAT)); - if(target==GL_TEXTURE_2D && hasAF && min(aniso, hwmaxaniso) > 0 && filter > 1) glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(aniso, hwmaxaniso)); - glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter && bilinear ? GL_LINEAR : GL_NEAREST); - glTexParameteri(target, GL_TEXTURE_MIN_FILTER, - filter > 1 ? - (trilinear ? - (bilinear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR) : - (bilinear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST)) : - (filter && bilinear ? GL_LINEAR : GL_NEAREST)); - if(swizzle && hasTRG && hasTSW) - { - const GLint *mask = swizzlemask(format); - if(mask) glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, mask); - } + glBindTexture(target, tnum); + glTexParameteri(target, GL_TEXTURE_WRAP_S, clamp&1 ? GL_CLAMP_TO_EDGE : (clamp&0x100 ? GL_MIRRORED_REPEAT : GL_REPEAT)); + glTexParameteri(target, GL_TEXTURE_WRAP_T, clamp&2 ? GL_CLAMP_TO_EDGE : (clamp&0x200 ? GL_MIRRORED_REPEAT : GL_REPEAT)); + if(target==GL_TEXTURE_2D && hasAF && min(aniso, hwmaxaniso) > 0 && filter > 1) glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(aniso, hwmaxaniso)); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter && bilinear ? GL_LINEAR : GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, + filter > 1 ? + (trilinear ? + (bilinear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR) : + (bilinear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST)) : + (filter && bilinear ? GL_LINEAR : GL_NEAREST)); + if(swizzle && hasTRG && hasTSW) + { + const GLint *mask = swizzlemask(format); + if(mask) glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, mask); + } } static GLenum textype(GLenum &component, GLenum &format) { - GLenum type = GL_UNSIGNED_BYTE; - switch(component) - { - case GL_R16F: - case GL_R32F: - if(!format) format = GL_RED; - type = GL_FLOAT; - break; - - case GL_RG16F: - case GL_RG32F: - if(!format) format = GL_RG; - type = GL_FLOAT; - break; - - case GL_RGB16F: - case GL_RGB32F: - if(!format) format = GL_RGB; - type = GL_FLOAT; - break; - - case GL_RGBA16F: - case GL_RGBA32F: - if(!format) format = GL_RGBA; - type = GL_FLOAT; - break; - - case GL_DEPTH_COMPONENT16: - case GL_DEPTH_COMPONENT24: - case GL_DEPTH_COMPONENT32: - if(!format) format = GL_DEPTH_COMPONENT; - break; - - case GL_RGB5: - case GL_RGB8: - case GL_RGB10: - case GL_RGB16: - case GL_COMPRESSED_RGB: - case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: - if(!format) format = GL_RGB; - break; - - case GL_RGB5_A1: - case GL_RGBA8: - case GL_RGB10_A2: - case GL_RGBA16: - case GL_COMPRESSED_RGBA: - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - if(!format) format = GL_RGBA; - break; - - case GL_DEPTH_STENCIL: - case GL_DEPTH24_STENCIL8: - if(!format) format = GL_DEPTH_STENCIL; - type = GL_UNSIGNED_INT_24_8; - break; - - case GL_R8: - case GL_R16: - case GL_COMPRESSED_RED: - case GL_COMPRESSED_RED_RGTC1: - if(!format) format = GL_RED; - break; - - case GL_RG8: - case GL_RG16: - case GL_COMPRESSED_RG: - case GL_COMPRESSED_RG_RGTC2: - if(!format) format = GL_RG; - break; - - case GL_LUMINANCE8: - case GL_LUMINANCE16: - case GL_COMPRESSED_LUMINANCE: - case GL_COMPRESSED_LUMINANCE_LATC1_EXT: - if(!format) format = GL_LUMINANCE; - break; - - case GL_LUMINANCE8_ALPHA8: - case GL_LUMINANCE16_ALPHA16: - case GL_COMPRESSED_LUMINANCE_ALPHA: - case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: - if(!format) format = GL_LUMINANCE_ALPHA; - break; - - case GL_ALPHA8: - case GL_ALPHA16: - case GL_COMPRESSED_ALPHA: - if(!format) format = GL_ALPHA; - break; - } - if(!format) format = component; - return type; + GLenum type = GL_UNSIGNED_BYTE; + switch(component) + { + case GL_R16F: + case GL_R32F: + if(!format) format = GL_RED; + type = GL_FLOAT; + break; + + case GL_RG16F: + case GL_RG32F: + if(!format) format = GL_RG; + type = GL_FLOAT; + break; + + case GL_RGB16F: + case GL_RGB32F: + if(!format) format = GL_RGB; + type = GL_FLOAT; + break; + + case GL_RGBA16F: + case GL_RGBA32F: + if(!format) format = GL_RGBA; + type = GL_FLOAT; + break; + + case GL_DEPTH_COMPONENT16: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + if(!format) format = GL_DEPTH_COMPONENT; + break; + + case GL_RGB5: + case GL_RGB8: + case GL_RGB10: + case GL_RGB16: + case GL_COMPRESSED_RGB: + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + if(!format) format = GL_RGB; + break; + + case GL_RGB5_A1: + case GL_RGBA8: + case GL_RGB10_A2: + case GL_RGBA16: + case GL_COMPRESSED_RGBA: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + if(!format) format = GL_RGBA; + break; + + case GL_DEPTH_STENCIL: + case GL_DEPTH24_STENCIL8: + if(!format) format = GL_DEPTH_STENCIL; + type = GL_UNSIGNED_INT_24_8; + break; + + case GL_R8: + case GL_R16: + case GL_COMPRESSED_RED: + case GL_COMPRESSED_RED_RGTC1: + if(!format) format = GL_RED; + break; + + case GL_RG8: + case GL_RG16: + case GL_COMPRESSED_RG: + case GL_COMPRESSED_RG_RGTC2: + if(!format) format = GL_RG; + break; + + case GL_LUMINANCE8: + case GL_LUMINANCE16: + case GL_COMPRESSED_LUMINANCE: + case GL_COMPRESSED_LUMINANCE_LATC1_EXT: + if(!format) format = GL_LUMINANCE; + break; + + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE16_ALPHA16: + case GL_COMPRESSED_LUMINANCE_ALPHA: + case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: + if(!format) format = GL_LUMINANCE_ALPHA; + break; + + case GL_ALPHA8: + case GL_ALPHA16: + case GL_COMPRESSED_ALPHA: + if(!format) format = GL_ALPHA; + break; + } + if(!format) format = component; + return type; } void createtexture(int tnum, int w, int h, void *pixels, int clamp, int filter, GLenum component, GLenum subtarget, int pw, int ph, int pitch, bool resize, GLenum format, bool swizzle) { - GLenum target = textarget(subtarget), type = textype(component, format); - if(filter >= 0 && clamp >= 0) setuptexparameters(tnum, pixels, clamp, filter, format, target, swizzle); - if(!pw) pw = w; - if(!ph) ph = h; - int tw = w, th = h; - bool mipmap = filter > 1 && pixels; - if(resize && pixels) - { - resizetexture(w, h, mipmap, false, target, 0, tw, th); - if(mipmap) component = compressedformat(component, tw, th); - } - uploadtexture(tnum, subtarget, component, tw, th, format, type, pixels, pw, ph, pitch, mipmap); + GLenum target = textarget(subtarget), type = textype(component, format); + if(filter >= 0 && clamp >= 0) setuptexparameters(tnum, pixels, clamp, filter, format, target, swizzle); + if(!pw) pw = w; + if(!ph) ph = h; + int tw = w, th = h; + bool mipmap = filter > 1 && pixels; + if(resize && pixels) + { + resizetexture(w, h, mipmap, false, target, 0, tw, th); + if(mipmap) component = compressedformat(component, tw, th); + } + uploadtexture(tnum, subtarget, component, tw, th, format, type, pixels, pw, ph, pitch, mipmap); } void createcompressedtexture(int tnum, int w, int h, uchar *data, int align, int blocksize, int levels, int clamp, int filter, GLenum format, GLenum subtarget, bool swizzle = false) { - GLenum target = textarget(subtarget); - if(filter >= 0 && clamp >= 0) setuptexparameters(tnum, data, clamp, filter, format, target); - uploadcompressedtexture(target, subtarget, format, w, h, data, align, blocksize, levels, filter > 1); + GLenum target = textarget(subtarget); + if(filter >= 0 && clamp >= 0) setuptexparameters(tnum, data, clamp, filter, format, target); + uploadcompressedtexture(target, subtarget, format, w, h, data, align, blocksize, levels, filter > 1); } hashnameset textures; @@ -1014,112 +978,112 @@ Texture *notexture = NULL; // used as default, ensured to be loaded static GLenum texformat(int bpp, bool swizzle = false) { - switch(bpp) - { - case 1: return hasTRG && (hasTSW || !glcompat || !swizzle) ? GL_RED : GL_LUMINANCE; - case 2: return hasTRG && (hasTSW || !glcompat || !swizzle) ? GL_RG : GL_LUMINANCE_ALPHA; - case 3: return GL_RGB; - case 4: return GL_RGBA; - default: return 0; - } + switch(bpp) + { + case 1: return hasTRG && (hasTSW || !glcompat || !swizzle) ? GL_RED : GL_LUMINANCE; + case 2: return hasTRG && (hasTSW || !glcompat || !swizzle) ? GL_RG : GL_LUMINANCE_ALPHA; + case 3: return GL_RGB; + case 4: return GL_RGBA; + default: return 0; + } } static bool alphaformat(GLenum format) { - switch(format) - { - case GL_ALPHA: - case GL_LUMINANCE_ALPHA: - case GL_RG: - case GL_RGBA: - return true; - default: - return false; - } + switch(format) + { + case GL_ALPHA: + case GL_LUMINANCE_ALPHA: + case GL_RG: + case GL_RGBA: + return true; + default: + return false; + } } int texalign(const void *data, int w, int bpp) { - int stride = w*bpp; - if(stride&1) return 1; - if(stride&2) return 2; - return 4; + int stride = w*bpp; + if(stride&1) return 1; + if(stride&2) return 2; + return 4; } static Texture *newtexture(Texture *t, const char *rname, ImageData &s, int clamp = 0, bool mipit = true, bool canreduce = false, bool transient = false, int compress = 0) { - if(!t) - { - char *key = newstring(rname); - t = &textures[key]; - t->name = key; - } - - t->clamp = clamp; - t->mipmap = mipit; - t->type = Texture::IMAGE; - if(transient) t->type |= Texture::TRANSIENT; - if(clamp&0x300) t->type |= Texture::MIRROR; - if(!s.data) - { - t->type |= Texture::STUB; - t->w = t->h = t->xs = t->ys = t->bpp = 0; - return t; - } - - bool swizzle = !(clamp&0x10000); - GLenum format; - if(s.compressed) - { - format = uncompressedformat(s.compressed); - t->bpp = formatsize(format); - t->type |= Texture::COMPRESSED; - } - else - { - format = texformat(s.bpp, swizzle); - t->bpp = s.bpp; - if(swizzle && hasTRG && !hasTSW && swizzlemask(format)) - { - swizzleimage(s); - format = texformat(s.bpp, swizzle); - t->bpp = s.bpp; - } - } - if(alphaformat(format)) t->type |= Texture::ALPHA; - t->w = t->xs = s.w; - t->h = t->ys = s.h; - - int filter = !canreduce || reducefilter ? (mipit ? 2 : 1) : 0; - glGenTextures(1, &t->id); - if(s.compressed) - { - uchar *data = s.data; - int levels = s.levels, level = 0; - if(canreduce && texreduce) loopi(min(texreduce, s.levels-1)) - { - data += s.calclevelsize(level++); - levels--; - if(t->w > 1) t->w /= 2; - if(t->h > 1) t->h /= 2; - } - int sizelimit = mipit && maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize; - while(t->w > sizelimit || t->h > sizelimit) - { - data += s.calclevelsize(level++); - levels--; - if(t->w > 1) t->w /= 2; - if(t->h > 1) t->h /= 2; - } - createcompressedtexture(t->id, t->w, t->h, data, s.align, s.bpp, levels, clamp, filter, s.compressed, GL_TEXTURE_2D, swizzle); - } - else - { - resizetexture(t->w, t->h, mipit, canreduce, GL_TEXTURE_2D, compress, t->w, t->h); - GLenum component = compressedformat(format, t->w, t->h, compress); - createtexture(t->id, t->w, t->h, s.data, clamp, filter, component, GL_TEXTURE_2D, t->xs, t->ys, s.pitch, false, format, swizzle); - } - return t; + if(!t) + { + char *key = newstring(rname); + t = &textures[key]; + t->name = key; + } + + t->clamp = clamp; + t->mipmap = mipit; + t->type = Texture::IMAGE; + if(transient) t->type |= Texture::TRANSIENT; + if(clamp&0x300) t->type |= Texture::MIRROR; + if(!s.data) + { + t->type |= Texture::STUB; + t->w = t->h = t->xs = t->ys = t->bpp = 0; + return t; + } + + bool swizzle = !(clamp&0x10000); + GLenum format; + if(s.compressed) + { + format = uncompressedformat(s.compressed); + t->bpp = formatsize(format); + t->type |= Texture::COMPRESSED; + } + else + { + format = texformat(s.bpp, swizzle); + t->bpp = s.bpp; + if(swizzle && hasTRG && !hasTSW && swizzlemask(format)) + { + swizzleimage(s); + format = texformat(s.bpp, swizzle); + t->bpp = s.bpp; + } + } + if(alphaformat(format)) t->type |= Texture::ALPHA; + t->w = t->xs = s.w; + t->h = t->ys = s.h; + + int filter = !canreduce || reducefilter ? (mipit ? 2 : 1) : 0; + glGenTextures(1, &t->id); + if(s.compressed) + { + uchar *data = s.data; + int levels = s.levels, level = 0; + if(canreduce && texreduce) loopi(min(texreduce, s.levels-1)) + { + data += s.calclevelsize(level++); + levels--; + if(t->w > 1) t->w /= 2; + if(t->h > 1) t->h /= 2; + } + int sizelimit = mipit && maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize; + while(t->w > sizelimit || t->h > sizelimit) + { + data += s.calclevelsize(level++); + levels--; + if(t->w > 1) t->w /= 2; + if(t->h > 1) t->h /= 2; + } + createcompressedtexture(t->id, t->w, t->h, data, s.align, s.bpp, levels, clamp, filter, s.compressed, GL_TEXTURE_2D, swizzle); + } + else + { + resizetexture(t->w, t->h, mipit, canreduce, GL_TEXTURE_2D, compress, t->w, t->h); + GLenum component = compressedformat(format, t->w, t->h, compress); + createtexture(t->id, t->w, t->h, s.data, clamp, filter, component, GL_TEXTURE_2D, t->xs, t->ys, s.pitch, false, format, swizzle); + } + return t; } #if SDL_BYTEORDER == SDL_BIG_ENDIAN @@ -1132,435 +1096,435 @@ static Texture *newtexture(Texture *t, const char *rname, ImageData &s, int clam 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; + 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(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 *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 *creatergbasurface(SDL_Surface *os) { - SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 32, RGBAMASKS); - if(ns) - { - SDL_SetSurfaceBlendMode(os, SDL_BLENDMODE_NONE); - SDL_BlitSurface(os, NULL, ns, NULL); - } - SDL_FreeSurface(os); - return ns; + SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 32, RGBAMASKS); + if(ns) + { + SDL_SetSurfaceBlendMode(os, SDL_BLENDMODE_NONE); + 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 || SDL_GetColorKey(s, NULL) >= 0) 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; + // gray scale images have 256 levels, no colorkey, and the palette is a ramp + if(s->format->palette) + { + if(s->format->palette->ncolors != 256 || SDL_GetColorKey(s, NULL) >= 0) 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; } 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 SDL_GetColorKey(s, NULL) >= 0 ? creatergbasurface(s) : creatergbsurface(s); - break; - case 3: - if(s->format->Rmask != rgbmasks[0] || s->format->Gmask != rgbmasks[1] || s->format->Bmask != rgbmasks[2]) - return creatergbsurface(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 ? creatergbasurface(s) : creatergbsurface(s); - break; - } - return 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 SDL_GetColorKey(s, NULL) >= 0 ? creatergbasurface(s) : creatergbsurface(s); + break; + case 3: + if(s->format->Rmask != rgbmasks[0] || s->format->Gmask != rgbmasks[1] || s->format->Bmask != rgbmasks[2]) + return creatergbsurface(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 ? creatergbasurface(s) : creatergbsurface(s); + break; + } + return s; } void texflip(ImageData &s) { - ImageData d(s.w, s.h, s.bpp); - uchar *dst = d.data, *src = &s.data[s.pitch*s.h]; - loopi(s.h) - { - src -= s.pitch; - memcpy(dst, src, s.bpp*s.w); - dst += d.pitch; - } - s.replace(d); + ImageData d(s.w, s.h, s.bpp); + uchar *dst = d.data, *src = &s.data[s.pitch*s.h]; + loopi(s.h) + { + src -= s.pitch; + memcpy(dst, src, s.bpp*s.w); + dst += d.pitch; + } + s.replace(d); } void texnormal(ImageData &s, int emphasis) { - ImageData d(s.w, s.h, 3); - uchar *src = s.data, *dst = d.data; - loop(y, s.h) loop(x, s.w) - { - vec normal(0.0f, 0.0f, 255.0f/emphasis); - normal.x += src[y*s.pitch + ((x+s.w-1)%s.w)*s.bpp]; - normal.x -= src[y*s.pitch + ((x+1)%s.w)*s.bpp]; - normal.y += src[((y+s.h-1)%s.h)*s.pitch + x*s.bpp]; - normal.y -= src[((y+1)%s.h)*s.pitch + x*s.bpp]; - normal.normalize(); - *dst++ = uchar(127.5f + normal.x*127.5f); - *dst++ = uchar(127.5f + normal.y*127.5f); - *dst++ = uchar(127.5f + normal.z*127.5f); - } - s.replace(d); + ImageData d(s.w, s.h, 3); + uchar *src = s.data, *dst = d.data; + loop(y, s.h) loop(x, s.w) + { + vec normal(0.0f, 0.0f, 255.0f/emphasis); + normal.x += src[y*s.pitch + ((x+s.w-1)%s.w)*s.bpp]; + normal.x -= src[y*s.pitch + ((x+1)%s.w)*s.bpp]; + normal.y += src[((y+s.h-1)%s.h)*s.pitch + x*s.bpp]; + normal.y -= src[((y+1)%s.h)*s.pitch + x*s.bpp]; + normal.normalize(); + *dst++ = uchar(127.5f + normal.x*127.5f); + *dst++ = uchar(127.5f + normal.y*127.5f); + *dst++ = uchar(127.5f + normal.z*127.5f); + } + s.replace(d); } template static void blurtexture(int w, int h, uchar *dst, const uchar *src, int margin) { - static const int weights3x3[9] = - { - 0x10, 0x20, 0x10, - 0x20, 0x40, 0x20, - 0x10, 0x20, 0x10 - }; - static const int weights5x5[25] = - { - 0x05, 0x05, 0x09, 0x05, 0x05, - 0x05, 0x0A, 0x14, 0x0A, 0x05, - 0x09, 0x14, 0x28, 0x14, 0x09, - 0x05, 0x0A, 0x14, 0x0A, 0x05, - 0x05, 0x05, 0x09, 0x05, 0x05 - }; - const int *mat = n > 1 ? weights5x5 : weights3x3; - int mstride = 2*n + 1, - mstartoffset = n*(mstride + 1), - stride = bpp*w, - startoffset = n*bpp, - nextoffset1 = stride + mstride*bpp, - nextoffset2 = stride - mstride*bpp; - src += margin*(stride + bpp); - for(int y = margin; y < h-margin; y++) - { - for(int x = margin; x < w-margin; x++) - { - int dr = 0, dg = 0, db = 0; - const uchar *p = src - startoffset; - const int *m = mat + mstartoffset; - for(int t = y; t >= y-n; t--, p -= nextoffset1, m -= mstride) - { - if(t < 0) p += stride; - int a = 0; - if(n > 1) { a += m[-2]; if(x >= 2) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; } - a += m[-1]; if(x >= 1) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; - int cr = p[0], cg = p[1], cb = p[2]; a += m[0]; dr += cr * a; dg += cg * a; db += cb * a; p += bpp; - if(x+1 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[1]; dg += cg * m[1]; db += cb * m[1]; p += bpp; - if(n > 1) { if(x+2 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[2]; dg += cg * m[2]; db += cb * m[2]; p += bpp; } - } - p = src - startoffset + stride; - m = mat + mstartoffset + mstride; - for(int t = y+1; t <= y+n; t++, p += nextoffset2, m += mstride) - { - if(t >= h) p -= stride; - int a = 0; - if(n > 1) { a += m[-2]; if(x >= 2) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; } - a += m[-1]; if(x >= 1) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; - int cr = p[0], cg = p[1], cb = p[2]; a += m[0]; dr += cr * a; dg += cg * a; db += cb * a; p += bpp; - if(x+1 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[1]; dg += cg * m[1]; db += cb * m[1]; p += bpp; - if(n > 1) { if(x+2 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[2]; dg += cg * m[2]; db += cb * m[2]; p += bpp; } - } - if(normals) - { - vec v(dr-0x7F80, dg-0x7F80, db-0x7F80); - float mag = 127.5f/v.magnitude(); - dst[0] = uchar(v.x*mag + 127.5f); - dst[1] = uchar(v.y*mag + 127.5f); - dst[2] = uchar(v.z*mag + 127.5f); - } - else - { - dst[0] = dr>>8; - dst[1] = dg>>8; - dst[2] = db>>8; - } - if(bpp > 3) dst[3] = src[3]; - dst += bpp; - src += bpp; - } - src += 2*margin*bpp; - } + static const int weights3x3[9] = + { + 0x10, 0x20, 0x10, + 0x20, 0x40, 0x20, + 0x10, 0x20, 0x10 + }; + static const int weights5x5[25] = + { + 0x05, 0x05, 0x09, 0x05, 0x05, + 0x05, 0x0A, 0x14, 0x0A, 0x05, + 0x09, 0x14, 0x28, 0x14, 0x09, + 0x05, 0x0A, 0x14, 0x0A, 0x05, + 0x05, 0x05, 0x09, 0x05, 0x05 + }; + const int *mat = n > 1 ? weights5x5 : weights3x3; + int mstride = 2*n + 1, + mstartoffset = n*(mstride + 1), + stride = bpp*w, + startoffset = n*bpp, + nextoffset1 = stride + mstride*bpp, + nextoffset2 = stride - mstride*bpp; + src += margin*(stride + bpp); + for(int y = margin; y < h-margin; y++) + { + for(int x = margin; x < w-margin; x++) + { + int dr = 0, dg = 0, db = 0; + const uchar *p = src - startoffset; + const int *m = mat + mstartoffset; + for(int t = y; t >= y-n; t--, p -= nextoffset1, m -= mstride) + { + if(t < 0) p += stride; + int a = 0; + if(n > 1) { a += m[-2]; if(x >= 2) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; } + a += m[-1]; if(x >= 1) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; + int cr = p[0], cg = p[1], cb = p[2]; a += m[0]; dr += cr * a; dg += cg * a; db += cb * a; p += bpp; + if(x+1 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[1]; dg += cg * m[1]; db += cb * m[1]; p += bpp; + if(n > 1) { if(x+2 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[2]; dg += cg * m[2]; db += cb * m[2]; p += bpp; } + } + p = src - startoffset + stride; + m = mat + mstartoffset + mstride; + for(int t = y+1; t <= y+n; t++, p += nextoffset2, m += mstride) + { + if(t >= h) p -= stride; + int a = 0; + if(n > 1) { a += m[-2]; if(x >= 2) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; } + a += m[-1]; if(x >= 1) { dr += p[0] * a; dg += p[1] * a; db += p[2] * a; a = 0; } p += bpp; + int cr = p[0], cg = p[1], cb = p[2]; a += m[0]; dr += cr * a; dg += cg * a; db += cb * a; p += bpp; + if(x+1 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[1]; dg += cg * m[1]; db += cb * m[1]; p += bpp; + if(n > 1) { if(x+2 < w) { cr = p[0]; cg = p[1]; cb = p[2]; } dr += cr * m[2]; dg += cg * m[2]; db += cb * m[2]; p += bpp; } + } + if(normals) + { + vec v(dr-0x7F80, dg-0x7F80, db-0x7F80); + float mag = 127.5f/v.magnitude(); + dst[0] = uchar(v.x*mag + 127.5f); + dst[1] = uchar(v.y*mag + 127.5f); + dst[2] = uchar(v.z*mag + 127.5f); + } + else + { + dst[0] = dr>>8; + dst[1] = dg>>8; + dst[2] = db>>8; + } + if(bpp > 3) dst[3] = src[3]; + dst += bpp; + src += bpp; + } + src += 2*margin*bpp; + } } void blurtexture(int n, int bpp, int w, int h, uchar *dst, const uchar *src, int margin) { - switch((clamp(n, 1, 2)<<4) | bpp) - { - case 0x13: blurtexture<1, 3, false>(w, h, dst, src, margin); break; - case 0x23: blurtexture<2, 3, false>(w, h, dst, src, margin); break; - case 0x14: blurtexture<1, 4, false>(w, h, dst, src, margin); break; - case 0x24: blurtexture<2, 4, false>(w, h, dst, src, margin); break; - } + switch((clamp(n, 1, 2)<<4) | bpp) + { + case 0x13: blurtexture<1, 3, false>(w, h, dst, src, margin); break; + case 0x23: blurtexture<2, 3, false>(w, h, dst, src, margin); break; + case 0x14: blurtexture<1, 4, false>(w, h, dst, src, margin); break; + case 0x24: blurtexture<2, 4, false>(w, h, dst, src, margin); break; + } } void blurnormals(int n, int w, int h, bvec *dst, const bvec *src, int margin) { - switch(clamp(n, 1, 2)) - { - case 1: blurtexture<1, 3, true>(w, h, dst->v, src->v, margin); break; - case 2: blurtexture<2, 3, true>(w, h, dst->v, src->v, margin); break; - } + switch(clamp(n, 1, 2)) + { + case 1: blurtexture<1, 3, true>(w, h, dst->v, src->v, margin); break; + case 2: blurtexture<2, 3, true>(w, h, dst->v, src->v, margin); break; + } } void texblur(ImageData &s, int n, int r) { - if(s.bpp < 3) return; - loopi(r) - { - ImageData d(s.w, s.h, s.bpp); - blurtexture(n, s.bpp, s.w, s.h, d.data, s.data); - s.replace(d); - } + if(s.bpp < 3) return; + loopi(r) + { + ImageData d(s.w, s.h, s.bpp); + blurtexture(n, s.bpp, s.w, s.h, d.data, s.data); + s.replace(d); + } } void scaleimage(ImageData &s, int w, int h) { - ImageData d(w, h, s.bpp); - scaletexture(s.data, s.w, s.h, s.bpp, s.pitch, d.data, w, h); - s.replace(d); + ImageData d(w, h, s.bpp); + scaletexture(s.data, s.w, s.h, s.bpp, s.pitch, d.data, w, h); + s.replace(d); } bool canloadsurface(const char *name) { - stream *f = openfile(name, "rb"); - if(!f) return false; - delete f; - return true; + stream *f = openfile(name, "rb"); + if(!f) return false; + delete f; + return true; } SDL_Surface *loadsurface(const char *name) { - SDL_Surface *s = NULL; - stream *z = openzipfile(name, "rb"); - if(z) - { - SDL_RWops *rw = z->rwops(); - if(rw) - { - char *ext = (char *)strrchr(name, '.'); - if(ext) ++ext; - s = IMG_LoadTyped_RW(rw, 0, ext); - SDL_FreeRW(rw); - } - delete z; - } - if(!s) s = IMG_Load(findfile(name, "rb")); - return fixsurfaceformat(s); + SDL_Surface *s = NULL; + stream *z = openzipfile(name, "rb"); + if(z) + { + SDL_RWops *rw = z->rwops(); + if(rw) + { + char *ext = (char *)strrchr(name, '.'); + if(ext) ++ext; + s = IMG_LoadTyped_RW(rw, 0, ext); + SDL_FreeRW(rw); + } + delete z; + } + if(!s) s = IMG_Load(findfile(name, "rb")); + return fixsurfaceformat(s); } static vec parsevec(const char *arg) { - vec v(0, 0, 0); - int i = 0; - for(; arg[0] && (!i || arg[0]=='/') && i<3; arg += strcspn(arg, "/,><"), i++) - { - if(i) arg++; - v[i] = atof(arg); - } - if(i==1) v.y = v.z = v.x; - return v; + vec v(0, 0, 0); + int i = 0; + for(; arg[0] && (!i || arg[0]=='/') && i<3; arg += strcspn(arg, "/,><"), i++) + { + if(i) arg++; + v[i] = atof(arg); + } + if(i==1) v.y = v.z = v.x; + return v; } static bool texturedata(ImageData &d, const char *tname, Slot::Tex *tex = NULL, bool msg = true, int *compress = NULL, int *wrap = NULL) { - const char *cmds = NULL, *file = tname; - - if(!tname) - { - if(!tex) return false; - if(tex->name[0]=='<') - { - cmds = tex->name; - file = strrchr(tex->name, '>'); - if(!file) { if(msg) conoutf(CON_ERROR, "could not load texture packages/%s", tex->name); return false; } - file++; - } - else file = tex->name; - - static string pname; - formatstring(pname, "packages/%s", file); - file = path(pname); - } - else if(tname[0]=='<') - { - cmds = tname; - file = strrchr(tname, '>'); - if(!file) { if(msg) conoutf(CON_ERROR, "could not load texture %s", tname); return false; } - file++; - } - - int flen = strlen(file); - bool raw = !compress, guess = false; - for(const char *pcmds = cmds; pcmds;) - { - #define PARSETEXCOMMANDS(cmds) \ - const char *cmd = NULL, *end = NULL, *arg[4] = { NULL, NULL, NULL, NULL }; \ - cmd = &cmds[1]; \ - end = strchr(cmd, '>'); \ - if(!end) break; \ - cmds = strchr(cmd, '<'); \ - size_t len = strcspn(cmd, ":,><"); \ - loopi(4) \ - { \ - arg[i] = strchr(i ? arg[i-1] : cmd, i ? ',' : ':'); \ - if(!arg[i] || arg[i] >= end) arg[i] = ""; \ - else arg[i]++; \ - } - PARSETEXCOMMANDS(pcmds); - if(matchstring(cmd, len, "thumbnail")) - { - raw = true; - guess = flen >= 4 && !strchr(file+flen-4, '.'); - } - else if(matchstring(cmd, len, "stub")) return canloadsurface(file); - } - - if(msg) renderprogress(loadprogress, file); - - if(!d.data) - { - SDL_Surface *s = NULL; - if(guess) - { - static const char *exts[] = {".jpg", ".png"}; - string ext; - loopi(sizeof(exts)/sizeof(exts[0])) - { - copystring(ext, file); - concatstring(ext, exts[i]); - s = loadsurface(ext); - if(s) break; - } - } - else s = loadsurface(file); - if(!s) { if(msg) conoutf(CON_ERROR, "could not load texture %s", file); return false; } - int bpp = s->format->BitsPerPixel; - if(bpp%8 || !texformat(bpp/8)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture must be 8, 16, 24, or 32 bpp: %s", file); return false; } - if(max(s->w, s->h) > (1<<12)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture size exceeded %dx%d pixels: %s", 1<<12, 1<<12, file); return false; } - d.wrap(s); - } - - while(cmds) - { - PARSETEXCOMMANDS(cmds); - if(d.compressed) goto compressed; - if(matchstring(cmd, len, "mad")) texmad(d, parsevec(arg[0]), parsevec(arg[1])); - else if(matchstring(cmd, len, "colorify")) texcolorify(d, parsevec(arg[0]), parsevec(arg[1])); - else if(matchstring(cmd, len, "colormask")) texcolormask(d, parsevec(arg[0]), *arg[1] ? parsevec(arg[1]) : vec(1, 1, 1)); - else if(matchstring(cmd, len, "normal")) - { - int emphasis = atoi(arg[0]); - texnormal(d, emphasis > 0 ? emphasis : 3); - } - else if(matchstring(cmd, len, "dup")) texdup(d, atoi(arg[0]), atoi(arg[1])); - else if(matchstring(cmd, len, "offset")) texoffset(d, atoi(arg[0]), atoi(arg[1])); - else if(matchstring(cmd, len, "rotate")) texrotate(d, atoi(arg[0]), tex ? tex->type : 0); - else if(matchstring(cmd, len, "reorient")) texreorient(d, atoi(arg[0])>0, atoi(arg[1])>0, atoi(arg[2])>0, tex ? tex->type : TEX_DIFFUSE); - else if(matchstring(cmd, len, "mix")) texmix(d, *arg[0] ? atoi(arg[0]) : -1, *arg[1] ? atoi(arg[1]) : -1, *arg[2] ? atoi(arg[2]) : -1, *arg[3] ? atoi(arg[3]) : -1); - else if(matchstring(cmd, len, "grey")) texgrey(d); - else if(matchstring(cmd, len, "blur")) - { - int emphasis = atoi(arg[0]), repeat = atoi(arg[1]); - texblur(d, emphasis > 0 ? clamp(emphasis, 1, 2) : 1, repeat > 0 ? repeat : 1); - } - else if(matchstring(cmd, len, "premul")) texpremul(d); - else if(matchstring(cmd, len, "agrad")) texagrad(d, atof(arg[0]), atof(arg[1]), atof(arg[2]), atof(arg[3])); - else if(matchstring(cmd, len, "compress")) - { - int scale = atoi(arg[0]); - if(scale <= 0) scale = 2; - if(compress) *compress = scale; - } - else if(matchstring(cmd, len, "nocompress")) - { - if(compress) *compress = -1; - } - else if(matchstring(cmd, len, "thumbnail")) - { - int w = atoi(arg[0]), h = atoi(arg[1]); - if(w <= 0 || w > (1<<12)) w = 64; - if(h <= 0 || h > (1<<12)) h = w; - if(d.w > w || d.h > h) scaleimage(d, w, h); - } - else - compressed: - if(matchstring(cmd, len, "mirror")) - { - if(wrap) *wrap |= 0x300; - } - else if(matchstring(cmd, len, "noswizzle")) - { - if(wrap) *wrap |= 0x10000; - } - } - - return true; + const char *cmds = NULL, *file = tname; + + if(!tname) + { + if(!tex) return false; + if(tex->name[0]=='<') + { + cmds = tex->name; + file = strrchr(tex->name, '>'); + if(!file) { if(msg) conoutf(CON_ERROR, "could not load texture packages/%s", tex->name); return false; } + file++; + } + else file = tex->name; + + static string pname; + formatstring(pname, "packages/%s", file); + file = path(pname); + } + else if(tname[0]=='<') + { + cmds = tname; + file = strrchr(tname, '>'); + if(!file) { if(msg) conoutf(CON_ERROR, "could not load texture %s", tname); return false; } + file++; + } + + int flen = strlen(file); + bool raw = !compress, guess = false; + for(const char *pcmds = cmds; pcmds;) + { + #define PARSETEXCOMMANDS(cmds) \ + const char *cmd = NULL, *end = NULL, *arg[4] = { NULL, NULL, NULL, NULL }; \ + cmd = &cmds[1]; \ + end = strchr(cmd, '>'); \ + if(!end) break; \ + cmds = strchr(cmd, '<'); \ + size_t len = strcspn(cmd, ":,><"); \ + loopi(4) \ + { \ + arg[i] = strchr(i ? arg[i-1] : cmd, i ? ',' : ':'); \ + if(!arg[i] || arg[i] >= end) arg[i] = ""; \ + else arg[i]++; \ + } + PARSETEXCOMMANDS(pcmds); + if(matchstring(cmd, len, "thumbnail")) + { + raw = true; + guess = flen >= 4 && !strchr(file+flen-4, '.'); + } + else if(matchstring(cmd, len, "stub")) return canloadsurface(file); + } + + if(msg) renderprogress(loadprogress, file); + + if(!d.data) + { + SDL_Surface *s = NULL; + if(guess) + { + static const char *exts[] = {".jpg", ".png"}; + string ext; + loopi(sizeof(exts)/sizeof(exts[0])) + { + copystring(ext, file); + concatstring(ext, exts[i]); + s = loadsurface(ext); + if(s) break; + } + } + else s = loadsurface(file); + if(!s) { if(msg) conoutf(CON_ERROR, "could not load texture %s", file); return false; } + int bpp = s->format->BitsPerPixel; + if(bpp%8 || !texformat(bpp/8)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture must be 8, 16, 24, or 32 bpp: %s", file); return false; } + if(max(s->w, s->h) > (1<<12)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture size exceeded %dx%d pixels: %s", 1<<12, 1<<12, file); return false; } + d.wrap(s); + } + + while(cmds) + { + PARSETEXCOMMANDS(cmds); + if(d.compressed) goto compressed; + if(matchstring(cmd, len, "mad")) texmad(d, parsevec(arg[0]), parsevec(arg[1])); + else if(matchstring(cmd, len, "colorify")) texcolorify(d, parsevec(arg[0]), parsevec(arg[1])); + else if(matchstring(cmd, len, "colormask")) texcolormask(d, parsevec(arg[0]), *arg[1] ? parsevec(arg[1]) : vec(1, 1, 1)); + else if(matchstring(cmd, len, "normal")) + { + int emphasis = atoi(arg[0]); + texnormal(d, emphasis > 0 ? emphasis : 3); + } + else if(matchstring(cmd, len, "dup")) texdup(d, atoi(arg[0]), atoi(arg[1])); + else if(matchstring(cmd, len, "offset")) texoffset(d, atoi(arg[0]), atoi(arg[1])); + else if(matchstring(cmd, len, "rotate")) texrotate(d, atoi(arg[0]), tex ? tex->type : 0); + else if(matchstring(cmd, len, "reorient")) texreorient(d, atoi(arg[0])>0, atoi(arg[1])>0, atoi(arg[2])>0, tex ? tex->type : TEX_DIFFUSE); + else if(matchstring(cmd, len, "mix")) texmix(d, *arg[0] ? atoi(arg[0]) : -1, *arg[1] ? atoi(arg[1]) : -1, *arg[2] ? atoi(arg[2]) : -1, *arg[3] ? atoi(arg[3]) : -1); + else if(matchstring(cmd, len, "grey")) texgrey(d); + else if(matchstring(cmd, len, "blur")) + { + int emphasis = atoi(arg[0]), repeat = atoi(arg[1]); + texblur(d, emphasis > 0 ? clamp(emphasis, 1, 2) : 1, repeat > 0 ? repeat : 1); + } + else if(matchstring(cmd, len, "premul")) texpremul(d); + else if(matchstring(cmd, len, "agrad")) texagrad(d, atof(arg[0]), atof(arg[1]), atof(arg[2]), atof(arg[3])); + else if(matchstring(cmd, len, "compress")) + { + int scale = atoi(arg[0]); + if(scale <= 0) scale = 2; + if(compress) *compress = scale; + } + else if(matchstring(cmd, len, "nocompress")) + { + if(compress) *compress = -1; + } + else if(matchstring(cmd, len, "thumbnail")) + { + int w = atoi(arg[0]), h = atoi(arg[1]); + if(w <= 0 || w > (1<<12)) w = 64; + if(h <= 0 || h > (1<<12)) h = w; + if(d.w > w || d.h > h) scaleimage(d, w, h); + } + else + compressed: + if(matchstring(cmd, len, "mirror")) + { + if(wrap) *wrap |= 0x300; + } + else if(matchstring(cmd, len, "noswizzle")) + { + if(wrap) *wrap |= 0x10000; + } + } + + return true; } uchar *loadalphamask(Texture *t) { - if(t->alphamask) return t->alphamask; - if(!(t->type&Texture::ALPHA)) return NULL; - ImageData s; - if(!texturedata(s, t->name, NULL, false) || !s.data || s.compressed) return NULL; - t->alphamask = new uchar[s.h * ((s.w+7)/8)]; - uchar *srcrow = s.data, *dst = t->alphamask-1; - loop(y, s.h) - { - uchar *src = srcrow+s.bpp-1; - loop(x, s.w) - { - int offset = x%8; - if(!offset) *++dst = 0; - if(*src) *dst |= 1<alphamask; + if(t->alphamask) return t->alphamask; + if(!(t->type&Texture::ALPHA)) return NULL; + ImageData s; + if(!texturedata(s, t->name, NULL, false) || !s.data || s.compressed) return NULL; + t->alphamask = new uchar[s.h * ((s.w+7)/8)]; + uchar *srcrow = s.data, *dst = t->alphamask-1; + loop(y, s.h) + { + uchar *src = srcrow+s.bpp-1; + loop(x, s.w) + { + int offset = x%8; + if(!offset) *++dst = 0; + if(*src) *dst |= 1<alphamask; } Texture *textureload(const char *name, int clamp, bool mipit, bool msg) { - string tname; - copystring(tname, name); - Texture *t = textures.access(path(tname)); - if(t) return t; - int compress = 0; - ImageData s; - if(texturedata(s, tname, NULL, msg, &compress, &clamp)) return newtexture(NULL, tname, s, clamp, mipit, false, false, compress); - return notexture; + string tname; + copystring(tname, name); + Texture *t = textures.access(path(tname)); + if(t) return t; + int compress = 0; + ImageData s; + if(texturedata(s, tname, NULL, msg, &compress, &clamp)) return newtexture(NULL, tname, s, clamp, mipit, false, false, compress); + return notexture; } bool settexture(const char *name, int clamp) { - Texture *t = textureload(name, clamp, true, false); - glBindTexture(GL_TEXTURE_2D, t->id); - return t != notexture; + Texture *t = textureload(name, clamp, true, false); + glBindTexture(GL_TEXTURE_2D, t->id); + return t != notexture; } vector vslots; @@ -1571,30 +1535,30 @@ VSlot dummyvslot(&dummyslot); void texturereset(int *n) { - if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; - resetslotshader(); - int limit = clamp(*n, 0, slots.length()); - for(int i = limit; i < slots.length(); i++) - { - Slot *s = slots[i]; - for(VSlot *vs = s->variants; vs; vs = vs->next) vs->slot = &dummyslot; - delete s; - } - slots.setsize(limit); - while(vslots.length()) - { - VSlot *vs = vslots.last(); - if(vs->slot != &dummyslot || vs->changed) break; - delete vslots.pop(); - } + if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; + resetslotshader(); + int limit = clamp(*n, 0, slots.length()); + for(int i = limit; i < slots.length(); i++) + { + Slot *s = slots[i]; + for(VSlot *vs = s->variants; vs; vs = vs->next) vs->slot = &dummyslot; + delete s; + } + slots.setsize(limit); + while(vslots.length()) + { + VSlot *vs = vslots.last(); + if(vs->slot != &dummyslot || vs->changed) break; + delete vslots.pop(); + } } COMMAND(texturereset, "i"); void materialreset() { - if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; - loopi((MATF_VOLUME|MATF_INDEX)+1) materialslots[i].reset(); + if(!(identflags&IDF_OVERRIDDEN) && !game::allowedittoggle()) return; + loopi((MATF_VOLUME|MATF_INDEX)+1) materialslots[i].reset(); } COMMAND(materialreset, ""); @@ -1604,1042 +1568,1005 @@ static bool markingvslots = false; void clearslots() { - resetslotshader(); - slots.deletecontents(); - vslots.deletecontents(); - loopi((MATF_VOLUME|MATF_INDEX)+1) materialslots[i].reset(); - clonedvslots = 0; + resetslotshader(); + slots.deletecontents(); + vslots.deletecontents(); + loopi((MATF_VOLUME|MATF_INDEX)+1) materialslots[i].reset(); + clonedvslots = 0; } static void assignvslot(VSlot &vs); static inline void assignvslotlayer(VSlot &vs) { - if(vs.layer && vslots.inrange(vs.layer)) - { - VSlot &layer = *vslots[vs.layer]; - if(layer.index < 0) assignvslot(layer); - } + if(vs.layer && vslots.inrange(vs.layer)) + { + VSlot &layer = *vslots[vs.layer]; + if(layer.index < 0) assignvslot(layer); + } } static void assignvslot(VSlot &vs) { - vs.index = compactedvslots++; - assignvslotlayer(vs); + vs.index = compactedvslots++; + assignvslotlayer(vs); } void compactvslot(int &index) { - if(vslots.inrange(index)) - { - VSlot &vs = *vslots[index]; - if(vs.index < 0) assignvslot(vs); - if(!markingvslots) index = vs.index; - } + if(vslots.inrange(index)) + { + VSlot &vs = *vslots[index]; + if(vs.index < 0) assignvslot(vs); + if(!markingvslots) index = vs.index; + } } void compactvslot(VSlot &vs) { - if(vs.index < 0) assignvslot(vs); + if(vs.index < 0) assignvslot(vs); } void compactvslots(cube *c, int n) { - if((compactvslotsprogress++&0xFFF)==0) renderprogress(min(float(compactvslotsprogress)/allocnodes, 1.0f), markingvslots ? "marking slots..." : "compacting slots..."); - loopi(n) - { - if(c[i].children) compactvslots(c[i].children); - else loopj(6) if(vslots.inrange(c[i].texture[j])) - { - VSlot &vs = *vslots[c[i].texture[j]]; - if(vs.index < 0) assignvslot(vs); - if(!markingvslots) c[i].texture[j] = vs.index; - } - } + if((compactvslotsprogress++&0xFFF)==0) renderprogress(min(float(compactvslotsprogress)/allocnodes, 1.0f), markingvslots ? "marking slots..." : "compacting slots..."); + loopi(n) + { + if(c[i].children) compactvslots(c[i].children); + else loopj(6) if(vslots.inrange(c[i].texture[j])) + { + VSlot &vs = *vslots[c[i].texture[j]]; + if(vs.index < 0) assignvslot(vs); + if(!markingvslots) c[i].texture[j] = vs.index; + } + } } int compactvslots() { - clonedvslots = 0; - markingvslots = false; - compactedvslots = 0; - compactvslotsprogress = 0; - loopv(vslots) vslots[i]->index = -1; - loopv(slots) slots[i]->variants->index = compactedvslots++; - loopv(slots) assignvslotlayer(*slots[i]->variants); - loopv(vslots) - { - VSlot &vs = *vslots[i]; - if(!vs.changed && vs.index < 0) { markingvslots = true; break; } - } - compactvslots(worldroot); - int total = compactedvslots; - compacteditvslots(); - loopv(vslots) - { - VSlot *vs = vslots[i]; - if(vs->changed) continue; - while(vs->next) - { - if(vs->next->index < 0) vs->next = vs->next->next; - else vs = vs->next; - } - } - if(markingvslots) - { - markingvslots = false; - compactedvslots = 0; - compactvslotsprogress = 0; - int lastdiscard = 0; - loopv(vslots) - { - VSlot &vs = *vslots[i]; - if(vs.changed || (vs.index < 0 && !vs.next)) vs.index = -1; - else - { - while(lastdiscard < i) - { - VSlot &ds = *vslots[lastdiscard++]; - if(!ds.changed && ds.index < 0) ds.index = compactedvslots++; - } - vs.index = compactedvslots++; - } - } - compactvslots(worldroot); - total = compactedvslots; - compacteditvslots(); - } - compactmruvslots(); - loopv(vslots) - { - VSlot &vs = *vslots[i]; - if(vs.index >= 0 && vs.layer && vslots.inrange(vs.layer)) vs.layer = vslots[vs.layer]->index; - } - loopv(vslots) - { - while(vslots[i]->index >= 0 && vslots[i]->index != i) - swap(vslots[i], vslots[vslots[i]->index]); - } - for(int i = compactedvslots; i < vslots.length(); i++) delete vslots[i]; - vslots.setsize(compactedvslots); - return total; + clonedvslots = 0; + markingvslots = false; + compactedvslots = 0; + compactvslotsprogress = 0; + loopv(vslots) vslots[i]->index = -1; + loopv(slots) slots[i]->variants->index = compactedvslots++; + loopv(slots) assignvslotlayer(*slots[i]->variants); + loopv(vslots) + { + VSlot &vs = *vslots[i]; + if(!vs.changed && vs.index < 0) { markingvslots = true; break; } + } + compactvslots(worldroot); + int total = compactedvslots; + compacteditvslots(); + loopv(vslots) + { + VSlot *vs = vslots[i]; + if(vs->changed) continue; + while(vs->next) + { + if(vs->next->index < 0) vs->next = vs->next->next; + else vs = vs->next; + } + } + if(markingvslots) + { + markingvslots = false; + compactedvslots = 0; + compactvslotsprogress = 0; + int lastdiscard = 0; + loopv(vslots) + { + VSlot &vs = *vslots[i]; + if(vs.changed || (vs.index < 0 && !vs.next)) vs.index = -1; + else + { + while(lastdiscard < i) + { + VSlot &ds = *vslots[lastdiscard++]; + if(!ds.changed && ds.index < 0) ds.index = compactedvslots++; + } + vs.index = compactedvslots++; + } + } + compactvslots(worldroot); + total = compactedvslots; + compacteditvslots(); + } + compactmruvslots(); + loopv(vslots) + { + VSlot &vs = *vslots[i]; + if(vs.index >= 0 && vs.layer && vslots.inrange(vs.layer)) vs.layer = vslots[vs.layer]->index; + } + loopv(vslots) + { + while(vslots[i]->index >= 0 && vslots[i]->index != i) + swap(vslots[i], vslots[vslots[i]->index]); + } + for(int i = compactedvslots; i < vslots.length(); i++) delete vslots[i]; + vslots.setsize(compactedvslots); + return total; } ICOMMAND(compactvslots, "", (), { - extern int nompedit; - if(nompedit && multiplayer()) return; - compactvslots(); - allchanged(); + extern int nompedit; + if(nompedit && multiplayer()) return; + compactvslots(); + allchanged(); }); static Slot &loadslot(Slot &s, bool forceload); static void clampvslotoffset(VSlot &dst, Slot *slot = NULL) { - if(!slot) slot = dst.slot; - if(slot && slot->sts.inrange(0)) - { - if(!slot->loaded) loadslot(*slot, false); - Texture *t = slot->sts[0].t; - int xs = t->xs, ys = t->ys; - if(t->type & Texture::MIRROR) { xs *= 2; ys *= 2; } - if(texrotations[dst.rotation].swapxy) swap(xs, ys); - dst.offset.x %= xs; if(dst.offset.x < 0) dst.offset.x += xs; - dst.offset.y %= ys; if(dst.offset.y < 0) dst.offset.y += ys; - } - else dst.offset.max(0); + if(!slot) slot = dst.slot; + if(slot && slot->sts.inrange(0)) + { + if(!slot->loaded) loadslot(*slot, false); + Texture *t = slot->sts[0].t; + int xs = t->xs, ys = t->ys; + if(t->type & Texture::MIRROR) { xs *= 2; ys *= 2; } + if(texrotations[dst.rotation].swapxy) swap(xs, ys); + dst.offset.x %= xs; if(dst.offset.x < 0) dst.offset.x += xs; + dst.offset.y %= ys; if(dst.offset.y < 0) dst.offset.y += ys; + } + else dst.offset.max(0); } static void propagatevslot(VSlot &dst, const VSlot &src, int diff, bool edit = false) { - if(diff & (1<next; vs; vs = vs->next) - { - int diff = changed & ~vs->changed; - if(diff) propagatevslot(*vs, *root, diff); - } + for(VSlot *vs = root->next; vs; vs = vs->next) + { + int diff = changed & ~vs->changed; + if(diff) propagatevslot(*vs, *root, diff); + } } static void mergevslot(VSlot &dst, const VSlot &src, int diff, Slot *slot = NULL) { - if(diff & (1<slot = &owner; - vs->linked = false; - vs = vs->next; - } - return owner.variants; + owner.variants = vs; + while(vs) + { + vs->slot = &owner; + vs->linked = false; + vs = vs->next; + } + return owner.variants; } static VSlot *emptyvslot(Slot &owner) { - int offset = 0; - loopvrev(slots) if(slots[i]->variants) { offset = slots[i]->variants->index + 1; break; } - for(int i = offset; i < vslots.length(); i++) if(!vslots[i]->changed) return reassignvslot(owner, vslots[i]); - return vslots.add(new VSlot(&owner, vslots.length())); + int offset = 0; + loopvrev(slots) if(slots[i]->variants) { offset = slots[i]->variants->index + 1; break; } + for(int i = offset; i < vslots.length(); i++) if(!vslots[i]->changed) return reassignvslot(owner, vslots[i]); + return vslots.add(new VSlot(&owner, vslots.length())); } static bool comparevslot(const VSlot &dst, const VSlot &src, int diff) { - if(diff & (1< &buf, const VSlot &src) { - if(src.changed & (1<changed ? src.layer : 0); - } - if(src.changed & (1<changed ? src.layer : 0); + } + if(src.changed & (1< &buf, int index) { - if(vslots.inrange(index)) packvslot(buf, *vslots[index]); - else buf.put(0xFF); + if(vslots.inrange(index)) packvslot(buf, *vslots[index]); + else buf.put(0xFF); } void packvslot(vector &buf, const VSlot *vs) { - if(vs) packvslot(buf, *vs); - else buf.put(0xFF); + if(vs) packvslot(buf, *vs); + else buf.put(0xFF); } bool unpackvslot(ucharbuf &buf, VSlot &dst, bool delta) { - while(buf.remaining()) - { - int changed = buf.get(); - if(changed >= 0x80) break; - switch(changed) - { - case VSLOT_SHPARAM: - { - string name; - getstring(name, buf); - SlotShaderParam p = { name[0] ? getshaderparamname(name) : NULL, -1, { 0, 0, 0, 0 } }; - loopi(4) p.val[i] = getfloat(buf); - if(p.name) dst.params.add(p); - break; - } - case VSLOT_SCALE: - dst.scale = getfloat(buf); - if(dst.scale <= 0) dst.scale = 1; - else if(!delta) dst.scale = clamp(dst.scale, 1/8.0f, 8.0f); - break; - case VSLOT_ROTATION: - dst.rotation = getint(buf); - if(!delta) dst.rotation = clamp(dst.rotation, 0, 7); - break; - case VSLOT_OFFSET: - dst.offset.x = getint(buf); - dst.offset.y = getint(buf); - if(!delta) dst.offset.max(0); - break; - case VSLOT_SCROLL: - dst.scroll.x = getfloat(buf); - dst.scroll.y = getfloat(buf); - break; - case VSLOT_LAYER: - { - int tex = getuint(buf); - dst.layer = vslots.inrange(tex) ? tex : 0; - break; - } - case VSLOT_ALPHA: - dst.alphafront = clamp(getfloat(buf), 0.0f, 1.0f); - dst.alphaback = clamp(getfloat(buf), 0.0f, 1.0f); - break; - case VSLOT_COLOR: - dst.colorscale.r = clamp(getfloat(buf), 0.0f, 2.0f); - dst.colorscale.g = clamp(getfloat(buf), 0.0f, 2.0f); - dst.colorscale.b = clamp(getfloat(buf), 0.0f, 2.0f); - break; - default: - return false; - } - dst.changed |= 1<= 0x80) break; + switch(changed) + { + case VSLOT_SHPARAM: + { + string name; + getstring(name, buf); + SlotShaderParam p = { name[0] ? getshaderparamname(name) : NULL, -1, { 0, 0, 0, 0 } }; + loopi(4) p.val[i] = getfloat(buf); + if(p.name) dst.params.add(p); + break; + } + case VSLOT_SCALE: + dst.scale = getfloat(buf); + if(dst.scale <= 0) dst.scale = 1; + else if(!delta) dst.scale = clamp(dst.scale, 1/8.0f, 8.0f); + break; + case VSLOT_ROTATION: + dst.rotation = getint(buf); + if(!delta) dst.rotation = clamp(dst.rotation, 0, 7); + break; + case VSLOT_OFFSET: + dst.offset.x = getint(buf); + dst.offset.y = getint(buf); + if(!delta) dst.offset.max(0); + break; + case VSLOT_SCROLL: + dst.scroll.x = getfloat(buf); + dst.scroll.y = getfloat(buf); + break; + case VSLOT_LAYER: + { + int tex = getuint(buf); + dst.layer = vslots.inrange(tex) ? tex : 0; + break; + } + case VSLOT_ALPHA: + dst.alphafront = clamp(getfloat(buf), 0.0f, 1.0f); + dst.alphaback = clamp(getfloat(buf), 0.0f, 1.0f); + break; + case VSLOT_COLOR: + dst.colorscale.r = clamp(getfloat(buf), 0.0f, 2.0f); + dst.colorscale.g = clamp(getfloat(buf), 0.0f, 2.0f); + dst.colorscale.b = clamp(getfloat(buf), 0.0f, 2.0f); + break; + default: + return false; + } + dst.changed |= 1<next) - { - if((!dst->changed || dst->changed == (src.changed | delta.changed)) && - comparevslot(*dst, src, src.changed & ~delta.changed) && - comparevslot(*dst, delta, delta.changed)) - return dst; - } - return NULL; + for(VSlot *dst = slot.variants; dst; dst = dst->next) + { + if((!dst->changed || dst->changed == (src.changed | delta.changed)) && + comparevslot(*dst, src, src.changed & ~delta.changed) && + comparevslot(*dst, delta, delta.changed)) + return dst; + } + return NULL; } static VSlot *clonevslot(const VSlot &src, const VSlot &delta) { - VSlot *dst = vslots.add(new VSlot(src.slot, vslots.length())); - dst->changed = src.changed | delta.changed; - propagatevslot(*dst, src, ((1<changed = src.changed | delta.changed; + propagatevslot(*dst, src, ((1<=0x10000) - { - compactvslots(); - allchanged(); - if(vslots.length()>=0x10000) return NULL; - } - if(autocompactvslots && ++clonedvslots >= autocompactvslots) - { - compactvslots(); - allchanged(); - } - return clonevslot(src, delta); + VSlot *exists = findvslot(*src.slot, src, delta); + if(exists) return exists; + if(vslots.length()>=0x10000) + { + compactvslots(); + allchanged(); + if(vslots.length()>=0x10000) return NULL; + } + if(autocompactvslots && ++clonedvslots >= autocompactvslots) + { + compactvslots(); + allchanged(); + } + return clonevslot(src, delta); } static void fixinsidefaces(cube *c, const ivec &o, int size, int tex) { - loopi(8) - { - ivec co(i, o, size); - if(c[i].children) fixinsidefaces(c[i].children, co, size>>1, tex); - else loopj(6) if(!visibletris(c[i], j, co, size)) - c[i].texture[j] = tex; - } + loopi(8) + { + ivec co(i, o, size); + if(c[i].children) fixinsidefaces(c[i].children, co, size>>1, tex); + else loopj(6) if(!visibletris(c[i], j, co, size)) + c[i].texture[j] = tex; + } } ICOMMAND(fixinsidefaces, "i", (int *tex), { - extern int nompedit; - if(noedit(true) || (nompedit && multiplayer())) return; - fixinsidefaces(worldroot, ivec(0, 0, 0), worldsize>>1, *tex && vslots.inrange(*tex) ? *tex : DEFAULT_GEOM); - allchanged(); + extern int nompedit; + if(noedit(true) || (nompedit && multiplayer())) return; + fixinsidefaces(worldroot, ivec(0, 0, 0), worldsize>>1, *tex && vslots.inrange(*tex) ? *tex : DEFAULT_GEOM); + allchanged(); }); const struct slottex { - const char *name; - int id; + const char *name; + int id; } slottexs[] = { - {"c", TEX_DIFFUSE}, - {"u", TEX_UNKNOWN}, - {"d", TEX_DECAL}, - {"n", TEX_NORMAL}, - {"g", TEX_GLOW}, - {"s", TEX_SPEC}, - {"z", TEX_DEPTH}, - {"a", TEX_ALPHA}, - {"e", TEX_ENVMAP} + {"c", TEX_DIFFUSE}, + {"u", TEX_UNKNOWN}, + {"d", TEX_DECAL}, + {"n", TEX_NORMAL}, + {"g", TEX_GLOW}, + {"s", TEX_SPEC}, + {"z", TEX_DEPTH}, + {"a", TEX_ALPHA}, + {"e", TEX_ENVMAP} }; int findslottex(const char *name) { - loopi(sizeof(slottexs)/sizeof(slottex)) - { - if(!strcmp(slottexs[i].name, name)) return slottexs[i].id; - } - return -1; + loopi(sizeof(slottexs)/sizeof(slottex)) + { + if(!strcmp(slottexs[i].name, name)) return slottexs[i].id; + } + return -1; } void texture(char *type, char *name, int *rot, int *xoffset, int *yoffset, float *scale) { - if(slots.length()>=0x10000) return; - static int lastmatslot = -1; - int tnum = findslottex(type), matslot = findmaterial(type); - if(tnum<0) tnum = atoi(type); - if(tnum==TEX_DIFFUSE) lastmatslot = matslot; - else if(lastmatslot>=0) matslot = lastmatslot; - else if(slots.empty()) return; - Slot &s = matslot>=0 ? materialslots[matslot] : *(tnum!=TEX_DIFFUSE ? slots.last() : slots.add(new Slot(slots.length()))); - s.loaded = false; - s.texmask |= 1<=8) conoutf(CON_WARN, "warning: too many textures in slot %d", slots.length()-1); - Slot::Tex &st = s.sts.add(); - st.type = tnum; - st.combined = -1; - st.t = NULL; - copystring(st.name, name); - path(st.name); - if(tnum==TEX_DIFFUSE) - { - setslotshader(s); - VSlot &vs = matslot >= 0 ? materialslots[matslot] : *emptyvslot(s); - vs.reset(); - vs.rotation = clamp(*rot, 0, 7); - vs.offset = ivec2(*xoffset, *yoffset).max(0); - vs.scale = *scale <= 0 ? 1 : *scale; - propagatevslot(&vs, (1<=0x10000) return; + static int lastmatslot = -1; + int tnum = findslottex(type), matslot = findmaterial(type); + if(tnum<0) tnum = atoi(type); + if(tnum==TEX_DIFFUSE) lastmatslot = matslot; + else if(lastmatslot>=0) matslot = lastmatslot; + else if(slots.empty()) return; + Slot &s = matslot>=0 ? materialslots[matslot] : *(tnum!=TEX_DIFFUSE ? slots.last() : slots.add(new Slot(slots.length()))); + s.loaded = false; + s.texmask |= 1<=8) conoutf(CON_WARN, "warning: too many textures in slot %d", slots.length()-1); + Slot::Tex &st = s.sts.add(); + st.type = tnum; + st.combined = -1; + st.t = NULL; + copystring(st.name, name); + path(st.name); + if(tnum==TEX_DIFFUSE) + { + setslotshader(s); + VSlot &vs = matslot >= 0 ? materialslots[matslot] : *emptyvslot(s); + vs.reset(); + vs.rotation = clamp(*rot, 0, 7); + vs.offset = ivec2(*xoffset, *yoffset).max(0); + vs.scale = *scale <= 0 ? 1 : *scale; + propagatevslot(&vs, (1<scroll = vec2(*scrollS, *scrollT).div(1000.0f); - propagatevslot(s.variants, 1<scroll = vec2(*scrollS, *scrollT).div(1000.0f); + propagatevslot(s.variants, 1<offset = ivec2(*xoffset, *yoffset).max(0); - propagatevslot(s.variants, 1<offset = ivec2(*xoffset, *yoffset).max(0); + propagatevslot(s.variants, 1<rotation = clamp(*rot, 0, 7); - propagatevslot(s.variants, 1<rotation = clamp(*rot, 0, 7); + propagatevslot(s.variants, 1<scale = *scale <= 0 ? 1 : *scale; - propagatevslot(s.variants, 1<scale = *scale <= 0 ? 1 : *scale; + propagatevslot(s.variants, 1<layer = *layer < 0 ? max(slots.length()-1+*layer, 0) : *layer; - s.layermaskname = name[0] ? newstring(path(makerelpath("packages", name))) : NULL; - s.layermaskmode = *mode; - s.layermaskscale = *scale <= 0 ? 1 : *scale; - propagatevslot(s.variants, 1<layer = *layer < 0 ? max(slots.length()-1+*layer, 0) : *layer; + s.layermaskname = name[0] ? newstring(path(makerelpath("packages", name))) : NULL; + s.layermaskmode = *mode; + s.layermaskscale = *scale <= 0 ? 1 : *scale; + propagatevslot(s.variants, 1<alphafront = clamp(*front, 0.0f, 1.0f); - s.variants->alphaback = clamp(*back, 0.0f, 1.0f); - propagatevslot(s.variants, 1<alphafront = clamp(*front, 0.0f, 1.0f); + s.variants->alphaback = clamp(*back, 0.0f, 1.0f); + propagatevslot(s.variants, 1<colorscale = vec(clamp(*r, 0.0f, 1.0f), clamp(*g, 0.0f, 1.0f), clamp(*b, 0.0f, 1.0f)); - propagatevslot(s.variants, 1<colorscale = vec(clamp(*r, 0.0f, 1.0f), clamp(*g, 0.0f, 1.0f), clamp(*b, 0.0f, 1.0f)); + propagatevslot(s.variants, 1< &key, Slot &slot, Slot::Tex &t, bool combined = false, const char *prefix = NULL) { - if(combined) key.add('&'); - if(prefix) { while(*prefix) key.add(*prefix++); } - defformatstring(tname, "packages/%s", t.name); - for(const char *s = path(tname); *s; key.add(*s++)); + if(combined) key.add('&'); + if(prefix) { while(*prefix) key.add(*prefix++); } + defformatstring(tname, "packages/%s", t.name); + for(const char *s = path(tname); *s; key.add(*s++)); } static void texcombine(Slot &s, int index, Slot::Tex &t, bool forceload = false) { - vector key; - addname(key, s, t); - int texmask = 0; - if(!forceload) switch(t.type) - { - case TEX_DIFFUSE: - case TEX_NORMAL: - { - int i = findtextype(s, t.type==TEX_DIFFUSE ? (s.texmask&(1< key; + addname(key, s, t); + int texmask = 0; + if(!forceload) switch(t.type) + { + case TEX_DIFFUSE: + case TEX_NORMAL: + { + int i = findtextype(s, t.type==TEX_DIFFUSE ? (s.texmask&(1<= 0) continue; - switch(t.type) - { - case TEX_ENVMAP: - t.t = cubemapload(t.name); - break; - - default: - texcombine(s, i, t, forceload); - break; - } - } - s.loaded = true; - return s; + linkslotshader(s); + loopv(s.sts) + { + Slot::Tex &t = s.sts[i]; + if(t.combined >= 0) continue; + switch(t.type) + { + case TEX_ENVMAP: + t.t = cubemapload(t.name); + break; + + default: + texcombine(s, i, t, forceload); + break; + } + } + s.loaded = true; + return s; } MSlot &lookupmaterialslot(int index, bool load) { - if(materialslots[index].sts.empty() && index&MATF_INDEX) index &= ~MATF_INDEX; - MSlot &s = materialslots[index]; - if(load && !s.linked) - { - if(!s.loaded) loadslot(s, true); - linkvslotshader(s); - s.linked = true; - } - return s; + if(materialslots[index].sts.empty() && index&MATF_INDEX) index &= ~MATF_INDEX; + MSlot &s = materialslots[index]; + if(load && !s.linked) + { + if(!s.loaded) loadslot(s, true); + linkvslotshader(s); + s.linked = true; + } + return s; } Slot &lookupslot(int index, bool load) { - Slot &s = slots.inrange(index) ? *slots[index] : (slots.inrange(DEFAULT_GEOM) ? *slots[DEFAULT_GEOM] : dummyslot); - return s.loaded || !load ? s : loadslot(s, false); + Slot &s = slots.inrange(index) ? *slots[index] : (slots.inrange(DEFAULT_GEOM) ? *slots[DEFAULT_GEOM] : dummyslot); + return s.loaded || !load ? s : loadslot(s, false); } VSlot &lookupvslot(int index, bool load) { - VSlot &s = vslots.inrange(index) && vslots[index]->slot ? *vslots[index] : (slots.inrange(DEFAULT_GEOM) && slots[DEFAULT_GEOM]->variants ? *slots[DEFAULT_GEOM]->variants : dummyvslot); - if(load && !s.linked) - { - if(!s.slot->loaded) loadslot(*s.slot, false); - linkvslotshader(s); - s.linked = true; - } - return s; + VSlot &s = vslots.inrange(index) && vslots[index]->slot ? *vslots[index] : (slots.inrange(DEFAULT_GEOM) && slots[DEFAULT_GEOM]->variants ? *slots[DEFAULT_GEOM]->variants : dummyvslot); + if(load && !s.linked) + { + if(!s.slot->loaded) loadslot(*s.slot, false); + linkvslotshader(s); + s.linked = true; + } + return s; } void linkslotshaders() { - loopv(slots) if(slots[i]->loaded) linkslotshader(*slots[i]); - loopv(vslots) if(vslots[i]->linked) linkvslotshader(*vslots[i]); - loopi((MATF_VOLUME|MATF_INDEX)+1) if(materialslots[i].loaded) - { - linkslotshader(materialslots[i]); - linkvslotshader(materialslots[i]); - } + loopv(slots) if(slots[i]->loaded) linkslotshader(*slots[i]); + loopv(vslots) if(vslots[i]->linked) linkvslotshader(*vslots[i]); + loopi((MATF_VOLUME|MATF_INDEX)+1) if(materialslots[i].loaded) + { + linkslotshader(materialslots[i]); + linkvslotshader(materialslots[i]); + } } Texture *loadthumbnail(Slot &slot) { - if(slot.thumbnail) return slot.thumbnail; - if(!slot.variants) - { - slot.thumbnail = notexture; - return slot.thumbnail; - } - VSlot &vslot = *slot.variants; - linkslotshader(slot, false); - linkvslotshader(vslot, false); - vector name; - if(vslot.colorscale == vec(1, 1, 1)) addname(name, slot, slot.sts[0], false, ""); - else - { - defformatstring(prefix, "", vslot.colorscale.x, vslot.colorscale.y, vslot.colorscale.z); - addname(name, slot, slot.sts[0], false, prefix); - } - int glow = -1; - if(slot.texmask&(1<= 0) - { - defformatstring(prefix, "", vslot.glowcolor.x, vslot.glowcolor.y, vslot.glowcolor.z); - addname(name, slot, slot.sts[glow], true, prefix); - } - } - VSlot *layer = vslot.layer ? &lookupvslot(vslot.layer, false) : NULL; - if(layer) - { - if(layer->colorscale == vec(1, 1, 1)) addname(name, *layer->slot, layer->slot->sts[0], true, ""); - else - { - defformatstring(prefix, "", vslot.colorscale.x, vslot.colorscale.y, vslot.colorscale.z); - addname(name, *layer->slot, layer->slot->sts[0], true, prefix); - } - } - name.add('\0'); - Texture *t = textures.access(path(name.getbuf())); - if(t) slot.thumbnail = t; - else - { - ImageData s, g, l; - texturedata(s, NULL, &slot.sts[0], false); - if(glow >= 0) texturedata(g, NULL, &slot.sts[glow], false); - if(layer) texturedata(l, NULL, &layer->slot->sts[0], false); - if(!s.data) t = slot.thumbnail = notexture; - else - { - if(vslot.colorscale != vec(1, 1, 1)) texmad(s, vslot.colorscale, vec(0, 0, 0)); - int xs = s.w, ys = s.h; - if(s.w > 64 || s.h > 64) scaleimage(s, min(s.w, 64), min(s.h, 64)); - if(g.data) - { - if(g.w != s.w || g.h != s.h) scaleimage(g, s.w, s.h); - addglow(s, g, vslot.glowcolor); - } - if(l.data) - { - if(layer->colorscale != vec(1, 1, 1)) texmad(l, layer->colorscale, vec(0, 0, 0)); - if(l.w != s.w/2 || l.h != s.h/2) scaleimage(l, s.w/2, s.h/2); - forcergbimage(s); - forcergbimage(l); - uchar *dstrow = &s.data[s.pitch*l.h + s.bpp*l.w], *srcrow = l.data; - loop(y, l.h) - { - for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[l.w*l.bpp]; src < end; dst += s.bpp, src += l.bpp) - loopk(3) dst[k] = src[k]; - dstrow += s.pitch; - srcrow += l.pitch; - } - } - if(s.bpp < 3) forcergbimage(s); - t = newtexture(NULL, name.getbuf(), s, 0, false, false, true); - t->xs = xs; - t->ys = ys; - slot.thumbnail = t; - } - } - return t; + if(slot.thumbnail) return slot.thumbnail; + if(!slot.variants) + { + slot.thumbnail = notexture; + return slot.thumbnail; + } + VSlot &vslot = *slot.variants; + linkslotshader(slot, false); + linkvslotshader(vslot, false); + vector name; + if(vslot.colorscale == vec(1, 1, 1)) addname(name, slot, slot.sts[0], false, ""); + else + { + defformatstring(prefix, "", vslot.colorscale.x, vslot.colorscale.y, vslot.colorscale.z); + addname(name, slot, slot.sts[0], false, prefix); + } + int glow = -1; + if(slot.texmask&(1<= 0) + { + defformatstring(prefix, "", vslot.glowcolor.x, vslot.glowcolor.y, vslot.glowcolor.z); + addname(name, slot, slot.sts[glow], true, prefix); + } + } + VSlot *layer = vslot.layer ? &lookupvslot(vslot.layer, false) : NULL; + if(layer) + { + if(layer->colorscale == vec(1, 1, 1)) addname(name, *layer->slot, layer->slot->sts[0], true, ""); + else + { + defformatstring(prefix, "", vslot.colorscale.x, vslot.colorscale.y, vslot.colorscale.z); + addname(name, *layer->slot, layer->slot->sts[0], true, prefix); + } + } + name.add('\0'); + Texture *t = textures.access(path(name.getbuf())); + if(t) slot.thumbnail = t; + else + { + ImageData s, g, l; + texturedata(s, NULL, &slot.sts[0], false); + if(glow >= 0) texturedata(g, NULL, &slot.sts[glow], false); + if(layer) texturedata(l, NULL, &layer->slot->sts[0], false); + if(!s.data) t = slot.thumbnail = notexture; + else + { + if(vslot.colorscale != vec(1, 1, 1)) texmad(s, vslot.colorscale, vec(0, 0, 0)); + int xs = s.w, ys = s.h; + if(s.w > 64 || s.h > 64) scaleimage(s, min(s.w, 64), min(s.h, 64)); + if(g.data) + { + if(g.w != s.w || g.h != s.h) scaleimage(g, s.w, s.h); + addglow(s, g, vslot.glowcolor); + } + if(l.data) + { + if(layer->colorscale != vec(1, 1, 1)) texmad(l, layer->colorscale, vec(0, 0, 0)); + if(l.w != s.w/2 || l.h != s.h/2) scaleimage(l, s.w/2, s.h/2); + forcergbimage(s); + forcergbimage(l); + uchar *dstrow = &s.data[s.pitch*l.h + s.bpp*l.w], *srcrow = l.data; + loop(y, l.h) + { + for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[l.w*l.bpp]; src < end; dst += s.bpp, src += l.bpp) + loopk(3) dst[k] = src[k]; + dstrow += s.pitch; + srcrow += l.pitch; + } + } + if(s.bpp < 3) forcergbimage(s); + t = newtexture(NULL, name.getbuf(), s, 0, false, false, true); + t->xs = xs; + t->ys = ys; + slot.thumbnail = t; + } + } + return t; } void loadlayermasks() { - loopv(slots) - { - Slot &slot = *slots[i]; - if(slot.loaded && slot.layermaskname && !slot.layermask) - { - slot.layermask = new ImageData; - texturedata(*slot.layermask, slot.layermaskname); - if(!slot.layermask->data) DELETEP(slot.layermask); - } - } + loopv(slots) + { + Slot &slot = *slots[i]; + if(slot.loaded && slot.layermaskname && !slot.layermask) + { + slot.layermask = new ImageData; + texturedata(*slot.layermask, slot.layermaskname); + if(!slot.layermask->data) DELETEP(slot.layermask); + } + } } // environment mapped reflections void forcecubemapload(GLuint tex) { - extern int ati_cubemap_bug; - if(!ati_cubemap_bug || !tex) return; - - SETSHADER(cubemap); - GLenum depthtest = glIsEnabled(GL_DEPTH_TEST), blend = glIsEnabled(GL_BLEND); - if(depthtest) glDisable(GL_DEPTH_TEST); - glBindTexture(GL_TEXTURE_CUBE_MAP, tex); - if(!blend) glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - gle::defvertex(2); - gle::deftexcoord0(3); - gle::defcolor(4); - gle::begin(GL_LINES); - loopi(2) - { - gle::attribf(i*1e-3f, 0); - gle::attribf(0, 0, 1); - gle::attribf(1, 1, 1, 0); - } - gle::end(); - if(!blend) glDisable(GL_BLEND); - if(depthtest) glEnable(GL_DEPTH_TEST); + extern int ati_cubemap_bug; + if(!ati_cubemap_bug || !tex) return; + + SETSHADER(cubemap); + GLenum depthtest = glIsEnabled(GL_DEPTH_TEST), blend = glIsEnabled(GL_BLEND); + if(depthtest) glDisable(GL_DEPTH_TEST); + glBindTexture(GL_TEXTURE_CUBE_MAP, tex); + if(!blend) glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gle::defvertex(2); + gle::deftexcoord0(3); + gle::defcolor(4); + gle::begin(GL_LINES); + loopi(2) + { + gle::attribf(i*1e-3f, 0); + gle::attribf(0, 0, 1); + gle::attribf(1, 1, 1, 0); + } + gle::end(); + if(!blend) glDisable(GL_BLEND); + if(depthtest) glEnable(GL_DEPTH_TEST); } extern const cubemapside cubemapsides[6] = { - { GL_TEXTURE_CUBE_MAP_NEGATIVE_X, "lf", true, true, true }, - { GL_TEXTURE_CUBE_MAP_POSITIVE_X, "rt", false, false, true }, - { GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, "ft", true, false, false }, - { GL_TEXTURE_CUBE_MAP_POSITIVE_Y, "bk", false, true, false }, - { GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, "dn", false, false, true }, - { GL_TEXTURE_CUBE_MAP_POSITIVE_Z, "up", false, false, true }, + { GL_TEXTURE_CUBE_MAP_NEGATIVE_X, "lf", true, true, true }, + { GL_TEXTURE_CUBE_MAP_POSITIVE_X, "rt", false, false, true }, + { GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, "ft", true, false, false }, + { GL_TEXTURE_CUBE_MAP_POSITIVE_Y, "bk", false, true, false }, + { GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, "dn", false, false, true }, + { GL_TEXTURE_CUBE_MAP_POSITIVE_Z, "up", false, false, true }, }; VARFP(envmapsize, 4, 7, 10, setupmaterials()); Texture *cubemaploadwildcard(Texture *t, const char *name, bool mipit, bool msg, bool transient = false) { - string tname; - if(!name) copystring(tname, t->name); - else - { - copystring(tname, name); - t = textures.access(path(tname)); - if(t) - { - if(!transient && t->type&Texture::TRANSIENT) t->type &= ~Texture::TRANSIENT; - return t; - } - } - char *wildcard = strchr(tname, '*'); - ImageData surface[6]; - string sname; - if(!wildcard) copystring(sname, tname); - int tsize = 0, compress = 0; - loopi(6) - { - if(wildcard) - { - copystring(sname, stringslice(tname, wildcard)); - concatstring(sname, cubemapsides[i].name); - concatstring(sname, wildcard+1); - } - ImageData &s = surface[i]; - texturedata(s, sname, NULL, msg, &compress); - if(!s.data) return NULL; - if(s.w != s.h) - { - if(msg) conoutf(CON_ERROR, "cubemap texture %s does not have square size", sname); - return NULL; - } - if(s.compressed ? s.compressed!=surface[0].compressed || s.w!=surface[0].w || s.h!=surface[0].h || s.levels!=surface[0].levels : surface[0].compressed || s.bpp!=surface[0].bpp) - { - if(msg) conoutf(CON_ERROR, "cubemap texture %s doesn't match other sides' format", sname); - return NULL; - } - tsize = max(tsize, max(s.w, s.h)); - } - if(name) - { - char *key = newstring(tname); - t = &textures[key]; - t->name = key; - } - t->type = Texture::CUBEMAP; - if(transient) t->type |= Texture::TRANSIENT; - GLenum format; - if(surface[0].compressed) - { - format = uncompressedformat(surface[0].compressed); - t->bpp = formatsize(format); - t->type |= Texture::COMPRESSED; - } - else - { - format = texformat(surface[0].bpp, true); - t->bpp = surface[0].bpp; - if(hasTRG && !hasTSW && swizzlemask(format)) - { - loopi(6) swizzleimage(surface[i]); - format = texformat(surface[0].bpp, true); - t->bpp = surface[0].bpp; - } - } - if(alphaformat(format)) t->type |= Texture::ALPHA; - t->mipmap = mipit; - t->clamp = 3; - t->xs = t->ys = tsize; - t->w = t->h = min(1<w, t->h, mipit, false, GL_TEXTURE_CUBE_MAP, compress, t->w, t->h); - GLenum component = format; - if(!surface[0].compressed) - { - component = compressedformat(format, t->w, t->h, compress); - switch(component) - { - case GL_RGB: component = GL_RGB5; break; - } - } - glGenTextures(1, &t->id); - loopi(6) - { - ImageData &s = surface[i]; - const cubemapside &side = cubemapsides[i]; - texreorient(s, side.flipx, side.flipy, side.swapxy); - if(s.compressed) - { - int w = s.w, h = s.h, levels = s.levels, level = 0; - uchar *data = s.data; - while(levels > 1 && (w > t->w || h > t->h)) - { - data += s.calclevelsize(level++); - levels--; - if(w > 1) w /= 2; - if(h > 1) h /= 2; - } - createcompressedtexture(t->id, w, h, data, s.align, s.bpp, levels, i ? -1 : 3, mipit ? 2 : 1, s.compressed, side.target, true); - } - else - { - createtexture(t->id, t->w, t->h, s.data, i ? -1 : 3, mipit ? 2 : 1, component, side.target, s.w, s.h, s.pitch, false, format, true); - } - } - forcecubemapload(t->id); - return t; + string tname; + if(!name) copystring(tname, t->name); + else + { + copystring(tname, name); + t = textures.access(path(tname)); + if(t) + { + if(!transient && t->type&Texture::TRANSIENT) t->type &= ~Texture::TRANSIENT; + return t; + } + } + char *wildcard = strchr(tname, '*'); + ImageData surface[6]; + string sname; + if(!wildcard) copystring(sname, tname); + int tsize = 0, compress = 0; + loopi(6) + { + if(wildcard) + { + copystring(sname, stringslice(tname, wildcard)); + concatstring(sname, cubemapsides[i].name); + concatstring(sname, wildcard+1); + } + ImageData &s = surface[i]; + texturedata(s, sname, NULL, msg, &compress); + if(!s.data) return NULL; + if(s.w != s.h) + { + if(msg) conoutf(CON_ERROR, "cubemap texture %s does not have square size", sname); + return NULL; + } + if(s.compressed ? s.compressed!=surface[0].compressed || s.w!=surface[0].w || s.h!=surface[0].h || s.levels!=surface[0].levels : surface[0].compressed || s.bpp!=surface[0].bpp) + { + if(msg) conoutf(CON_ERROR, "cubemap texture %s doesn't match other sides' format", sname); + return NULL; + } + tsize = max(tsize, max(s.w, s.h)); + } + if(name) + { + char *key = newstring(tname); + t = &textures[key]; + t->name = key; + } + t->type = Texture::CUBEMAP; + if(transient) t->type |= Texture::TRANSIENT; + GLenum format; + if(surface[0].compressed) + { + format = uncompressedformat(surface[0].compressed); + t->bpp = formatsize(format); + t->type |= Texture::COMPRESSED; + } + else + { + format = texformat(surface[0].bpp, true); + t->bpp = surface[0].bpp; + if(hasTRG && !hasTSW && swizzlemask(format)) + { + loopi(6) swizzleimage(surface[i]); + format = texformat(surface[0].bpp, true); + t->bpp = surface[0].bpp; + } + } + if(alphaformat(format)) t->type |= Texture::ALPHA; + t->mipmap = mipit; + t->clamp = 3; + t->xs = t->ys = tsize; + t->w = t->h = min(1<w, t->h, mipit, false, GL_TEXTURE_CUBE_MAP, compress, t->w, t->h); + GLenum component = format; + if(!surface[0].compressed) + { + component = compressedformat(format, t->w, t->h, compress); + switch(component) + { + case GL_RGB: component = GL_RGB5; break; + } + } + glGenTextures(1, &t->id); + loopi(6) + { + ImageData &s = surface[i]; + const cubemapside &side = cubemapsides[i]; + texreorient(s, side.flipx, side.flipy, side.swapxy); + if(s.compressed) + { + int w = s.w, h = s.h, levels = s.levels, level = 0; + uchar *data = s.data; + while(levels > 1 && (w > t->w || h > t->h)) + { + data += s.calclevelsize(level++); + levels--; + if(w > 1) w /= 2; + if(h > 1) h /= 2; + } + createcompressedtexture(t->id, w, h, data, s.align, s.bpp, levels, i ? -1 : 3, mipit ? 2 : 1, s.compressed, side.target, true); + } + else + { + createtexture(t->id, t->w, t->h, s.data, i ? -1 : 3, mipit ? 2 : 1, component, side.target, s.w, s.h, s.pitch, false, format, true); + } + } + forcecubemapload(t->id); + return t; } Texture *cubemapload(const char *name, bool mipit, bool msg, bool transient) { - string pname; - copystring(pname, makerelpath("packages", name)); - path(pname); - Texture *t = NULL; - if(!strchr(pname, '*')) - { - defformatstring(jpgname, "%s_*.jpg", pname); - t = cubemaploadwildcard(NULL, jpgname, mipit, false, transient); - if(!t) - { - defformatstring(pngname, "%s_*.png", pname); - t = cubemaploadwildcard(NULL, pngname, mipit, false, transient); - if(!t && msg) conoutf(CON_ERROR, "could not load envmap %s", name); - } - } - else t = cubemaploadwildcard(NULL, pname, mipit, msg, transient); - return t; + string pname; + copystring(pname, makerelpath("packages", name)); + path(pname); + Texture *t = NULL; + if(!strchr(pname, '*')) + { + defformatstring(jpgname, "%s_*.jpg", pname); + t = cubemaploadwildcard(NULL, jpgname, mipit, false, transient); + if(!t) + { + defformatstring(pngname, "%s_*.png", pname); + t = cubemaploadwildcard(NULL, pngname, mipit, false, transient); + if(!t && msg) conoutf(CON_ERROR, "could not load envmap %s", name); + } + } + else t = cubemaploadwildcard(NULL, pname, mipit, msg, transient); + return t; } VARR(envmapradius, 0, 128, 10000); @@ -2647,16 +2574,16 @@ VARR(envmapbb, 0, 0, 1); struct envmap { - int radius, size, blur; - vec o; - GLuint tex; + int radius, size, blur; + vec o; + GLuint tex; - envmap() : radius(-1), size(0), blur(0), o(0, 0, 0), tex(0) {} + envmap() : radius(-1), size(0), blur(0), o(0, 0, 0), tex(0) {} - void clear() - { - if(tex) { glDeleteTextures(1, &tex); tex = 0; } - } + void clear() + { + if(tex) { glDeleteTextures(1, &tex); tex = 0; } + } }; static vector envmaps; @@ -2664,581 +2591,455 @@ static Texture *skyenvmap = NULL; void clearenvmaps() { - if(skyenvmap) - { - if(skyenvmap->type&Texture::TRANSIENT) cleanuptexture(skyenvmap); - skyenvmap = NULL; - } - loopv(envmaps) envmaps[i].clear(); - envmaps.shrink(0); + if(skyenvmap) + { + if(skyenvmap->type&Texture::TRANSIENT) cleanuptexture(skyenvmap); + skyenvmap = NULL; + } + loopv(envmaps) envmaps[i].clear(); + envmaps.shrink(0); } VAR(aaenvmap, 0, 2, 4); GLuint genenvmap(const vec &o, int envmapsize, int blur, bool onlysky) { - int rendersize = 1<<(envmapsize+aaenvmap), sizelimit = min(hwcubetexsize, min(screenw, screenh)); - if(maxtexsize) sizelimit = min(sizelimit, maxtexsize); - while(rendersize > sizelimit) rendersize /= 2; - int texsize = min(rendersize, 1< texsize) - { - scaletexture(src, rendersize, rendersize, 3, 3*rendersize, dst, texsize, texsize); - swap(src, dst); - } - if(blur > 0) - { - blurtexture(blur, 3, texsize, texsize, dst, src); - swap(src, dst); - } - createtexture(tex, texsize, texsize, src, 3, 2, GL_RGB5, side.target); - } - glFrontFace(GL_CW); - delete[] pixels; - glViewport(0, 0, screenw, screenh); - clientkeepalive(); - forcecubemapload(tex); - return tex; + int rendersize = 1<<(envmapsize+aaenvmap), sizelimit = min(hwcubetexsize, min(screenw, screenh)); + if(maxtexsize) sizelimit = min(sizelimit, maxtexsize); + while(rendersize > sizelimit) rendersize /= 2; + int texsize = min(rendersize, 1< texsize) + { + scaletexture(src, rendersize, rendersize, 3, 3*rendersize, dst, texsize, texsize); + swap(src, dst); + } + if(blur > 0) + { + blurtexture(blur, 3, texsize, texsize, dst, src); + swap(src, dst); + } + createtexture(tex, texsize, texsize, src, 3, 2, GL_RGB5, side.target); + } + glFrontFace(GL_CW); + delete[] pixels; + glViewport(0, 0, screenw, screenh); + clientkeepalive(); + forcecubemapload(tex); + return tex; } void initenvmaps() { - clearenvmaps(); - skyenvmap = NULL; - if(shouldrenderskyenvmap()) envmaps.add().size = 1; - else if(skybox[0]) skyenvmap = cubemapload(skybox, true, false, true); - const vector &ents = entities::getents(); - loopv(ents) - { - const extentity &ent = *ents[i]; - if(ent.type != ET_ENVMAP) continue; - envmap &em = envmaps.add(); - em.radius = ent.attr1 ? clamp(int(ent.attr1), 0, 10000) : envmapradius; - em.size = ent.attr2 ? clamp(int(ent.attr2), 4, 9) : 0; - em.blur = ent.attr3 ? clamp(int(ent.attr3), 1, 2) : 0; - em.o = ent.o; - } + clearenvmaps(); + skyenvmap = NULL; + if(shouldrenderskyenvmap()) envmaps.add().size = 1; + else if(skybox[0]) skyenvmap = cubemapload(skybox, true, false, true); + const vector &ents = entities::getents(); + loopv(ents) + { + const extentity &ent = *ents[i]; + if(ent.type != ET_ENVMAP) continue; + envmap &em = envmaps.add(); + em.radius = ent.attr1 ? clamp(int(ent.attr1), 0, 10000) : envmapradius; + em.size = ent.attr2 ? clamp(int(ent.attr2), 4, 9) : 0; + em.blur = ent.attr3 ? clamp(int(ent.attr3), 1, 2) : 0; + em.o = ent.o; + } } void genenvmaps() { - if(envmaps.empty()) return; - renderprogress(0, "generating environment maps..."); - int lastprogress = SDL_GetTicks(); - loopv(envmaps) - { - envmap &em = envmaps[i]; - em.tex = genenvmap(em.o, em.size ? min(em.size, envmapsize) : envmapsize, em.blur, em.radius < 0); - if(renderedframe) continue; - int millis = SDL_GetTicks(); - if(millis - lastprogress >= 250) - { - renderprogress(float(i+1)/envmaps.length(), "generating environment maps...", 0, true); - lastprogress = millis; - } - } + if(envmaps.empty()) return; + renderprogress(0, "generating environment maps..."); + int lastprogress = SDL_GetTicks(); + loopv(envmaps) + { + envmap &em = envmaps[i]; + em.tex = genenvmap(em.o, em.size ? min(em.size, envmapsize) : envmapsize, em.blur, em.radius < 0); + if(renderedframe) continue; + int millis = SDL_GetTicks(); + if(millis - lastprogress >= 250) + { + renderprogress(float(i+1)/envmaps.length(), "generating environment maps...", 0, true); + lastprogress = millis; + } + } } ushort closestenvmap(const vec &o) { - ushort minemid = EMID_SKY; - float mindist = 1e16f; - loopv(envmaps) - { - envmap &em = envmaps[i]; - float dist; - if(envmapbb) - { - if(!o.insidebb(vec(em.o).sub(em.radius), vec(em.o).add(em.radius))) continue; - dist = em.o.dist(o); - } - else - { - dist = em.o.dist(o); - if(dist > em.radius) continue; - } - if(dist < mindist) - { - minemid = EMID_RESERVED + i; - mindist = dist; - } - } - return minemid; + ushort minemid = EMID_SKY; + float mindist = 1e16f; + loopv(envmaps) + { + envmap &em = envmaps[i]; + float dist; + if(envmapbb) + { + if(!o.insidebb(vec(em.o).sub(em.radius), vec(em.o).add(em.radius))) continue; + dist = em.o.dist(o); + } + else + { + dist = em.o.dist(o); + if(dist > em.radius) continue; + } + if(dist < mindist) + { + minemid = EMID_RESERVED + i; + mindist = dist; + } + } + return minemid; } ushort closestenvmap(int orient, const ivec &co, int size) { - vec loc(co); - int dim = dimension(orient); - if(dimcoord(orient)) loc[dim] += size; - loc[R[dim]] += size/2; - loc[C[dim]] += size/2; - return closestenvmap(loc); + vec loc(co); + int dim = dimension(orient); + if(dimcoord(orient)) loc[dim] += size; + loc[R[dim]] += size/2; + loc[C[dim]] += size/2; + return closestenvmap(loc); } static inline GLuint lookupskyenvmap() { - return envmaps.length() && envmaps[0].radius < 0 ? envmaps[0].tex : (skyenvmap ? skyenvmap->id : 0); + return envmaps.length() && envmaps[0].radius < 0 ? envmaps[0].tex : (skyenvmap ? skyenvmap->id : 0); } GLuint lookupenvmap(Slot &slot) { - loopv(slot.sts) if(slot.sts[i].type==TEX_ENVMAP && slot.sts[i].t) return slot.sts[i].t->id; - return lookupskyenvmap(); + loopv(slot.sts) if(slot.sts[i].type==TEX_ENVMAP && slot.sts[i].t) return slot.sts[i].t->id; + return lookupskyenvmap(); } GLuint lookupenvmap(ushort emid) { - if(emid==EMID_SKY || emid==EMID_CUSTOM) return skyenvmap ? skyenvmap->id : 0; - if(emid==EMID_NONE || !envmaps.inrange(emid-EMID_RESERVED)) return 0; - GLuint tex = envmaps[emid-EMID_RESERVED].tex; - return tex ? tex : lookupskyenvmap(); + if(emid==EMID_SKY || emid==EMID_CUSTOM) return skyenvmap ? skyenvmap->id : 0; + if(emid==EMID_NONE || !envmaps.inrange(emid-EMID_RESERVED)) return 0; + GLuint tex = envmaps[emid-EMID_RESERVED].tex; + return tex ? tex : lookupskyenvmap(); } void cleanuptexture(Texture *t) { - DELETEA(t->alphamask); - if(t->id) { glDeleteTextures(1, &t->id); t->id = 0; } - if(t->type&Texture::TRANSIENT) textures.remove(t->name); + DELETEA(t->alphamask); + if(t->id) { glDeleteTextures(1, &t->id); t->id = 0; } + if(t->type&Texture::TRANSIENT) textures.remove(t->name); } void cleanuptextures() { - cleanupmipmaps(); - clearenvmaps(); - loopv(slots) slots[i]->cleanup(); - loopv(vslots) vslots[i]->cleanup(); - loopi((MATF_VOLUME|MATF_INDEX)+1) materialslots[i].cleanup(); - enumerate(textures, Texture, tex, cleanuptexture(&tex)); + clearenvmaps(); + loopv(slots) slots[i]->cleanup(); + loopv(vslots) vslots[i]->cleanup(); + loopi((MATF_VOLUME|MATF_INDEX)+1) materialslots[i].cleanup(); + enumerate(textures, Texture, tex, cleanuptexture(&tex)); } bool reloadtexture(const char *name) { - Texture *t = textures.access(path(name, true)); - if(t) return reloadtexture(*t); - return true; + Texture *t = textures.access(path(name, true)); + if(t) return reloadtexture(*t); + return true; } bool reloadtexture(Texture &tex) { - if(tex.id) return true; - switch(tex.type&Texture::TYPE) - { - case Texture::IMAGE: - { - int compress = 0; - ImageData s; - if(!texturedata(s, tex.name, NULL, true, &compress) || !newtexture(&tex, NULL, s, tex.clamp, tex.mipmap, false, false, compress)) return false; - break; - } + if(tex.id) return true; + switch(tex.type&Texture::TYPE) + { + case Texture::IMAGE: + { + int compress = 0; + ImageData s; + if(!texturedata(s, tex.name, NULL, true, &compress) || !newtexture(&tex, NULL, s, tex.clamp, tex.mipmap, false, false, compress)) return false; + break; + } - case Texture::CUBEMAP: - if(!cubemaploadwildcard(&tex, NULL, tex.mipmap, true)) return false; - break; - } - return true; + case Texture::CUBEMAP: + if(!cubemaploadwildcard(&tex, NULL, tex.mipmap, true)) return false; + break; + } + return true; } void reloadtex(char *name) { - Texture *t = textures.access(path(name, true)); - if(!t) { conoutf(CON_ERROR, "texture %s is not loaded", name); return; } - if(t->type&Texture::TRANSIENT) { conoutf(CON_ERROR, "can't reload transient texture %s", name); return; } - DELETEA(t->alphamask); - Texture oldtex = *t; - t->id = 0; - if(!reloadtexture(*t)) - { - if(t->id) glDeleteTextures(1, &t->id); - *t = oldtex; - conoutf(CON_ERROR, "failed to reload texture %s", name); - } + Texture *t = textures.access(path(name, true)); + if(!t) { conoutf(CON_ERROR, "texture %s is not loaded", name); return; } + if(t->type&Texture::TRANSIENT) { conoutf(CON_ERROR, "can't reload transient texture %s", name); return; } + DELETEA(t->alphamask); + Texture oldtex = *t; + t->id = 0; + if(!reloadtexture(*t)) + { + if(t->id) glDeleteTextures(1, &t->id); + *t = oldtex; + conoutf(CON_ERROR, "failed to reload texture %s", name); + } } COMMAND(reloadtex, "s"); void reloadtextures() { - int reloaded = 0; - enumerate(textures, Texture, tex, - { - loadprogress = float(++reloaded)/textures.numelems; - reloadtexture(tex); - }); - loadprogress = 0; + int reloaded = 0; + enumerate(textures, Texture, tex, + { + loadprogress = float(++reloaded)/textures.numelems; + reloadtexture(tex); + }); + loadprogress = 0; } void writepngchunk(stream *f, const char *type, uchar *data = NULL, uint len = 0) { - f->putbig(len); - f->write(type, 4); - f->write(data, len); + f->putbig(len); + f->write(type, 4); + f->write(data, len); - uint crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, (const Bytef *)type, 4); - if(data) crc = crc32(crc, data, len); - f->putbig(crc); + uint crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, (const Bytef *)type, 4); + if(data) crc = crc32(crc, data, len); + f->putbig(crc); } VARP(compresspng, 0, 9, 9); void savepng(const char *filename, ImageData &image, bool flip) { - uchar ctype = 0; - switch(image.bpp) - { - case 1: ctype = 0; break; - case 2: ctype = 4; break; - case 3: ctype = 2; break; - case 4: ctype = 6; break; - default: conoutf(CON_ERROR, "failed saving png to %s", filename); return; - } - stream *f = openfile(filename, "wb"); - if(!f) { conoutf(CON_ERROR, "could not write to %s", filename); return; } - - uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; - f->write(signature, sizeof(signature)); - - struct pngihdr - { - uint width, height; - uchar bitdepth, colortype, compress, filter, interlace; - } ihdr = { bigswap(image.w), bigswap(image.h), 8, ctype, 0, 0, 0 }; - writepngchunk(f, "IHDR", (uchar *)&ihdr, 13); - - stream::offset idat = f->tell(); - uint len = 0; - f->write("\0\0\0\0IDAT", 8); - uint crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, (const Bytef *)"IDAT", 4); - - z_stream z; - z.zalloc = NULL; - z.zfree = NULL; - z.opaque = NULL; - - if(deflateInit(&z, compresspng) != Z_OK) - goto error; - - uchar buf[1<<12]; - z.next_out = (Bytef *)buf; - z.avail_out = sizeof(buf); - - loopi(image.h) - { - uchar filter = 0; - loopj(2) - { - z.next_in = j ? (Bytef *)image.data + (flip ? image.h-i-1 : i)*image.pitch : (Bytef *)&filter; - z.avail_in = j ? image.w*image.bpp : 1; - while(z.avail_in > 0) - { - if(deflate(&z, Z_NO_FLUSH) != Z_OK) goto cleanuperror; - #define FLUSHZ do { \ - int flush = sizeof(buf) - z.avail_out; \ - crc = crc32(crc, buf, flush); \ - len += flush; \ - f->write(buf, flush); \ - z.next_out = (Bytef *)buf; \ - z.avail_out = sizeof(buf); \ - } while(0) - FLUSHZ; - } - } - } - - for(;;) - { - int err = deflate(&z, Z_FINISH); - if(err != Z_OK && err != Z_STREAM_END) goto cleanuperror; - FLUSHZ; - if(err == Z_STREAM_END) break; - } - - deflateEnd(&z); - - f->seek(idat, SEEK_SET); - f->putbig(len); - f->seek(0, SEEK_END); - f->putbig(crc); - - writepngchunk(f, "IEND"); - - delete f; - return; + uchar ctype = 0; + switch(image.bpp) + { + case 1: ctype = 0; break; + case 2: ctype = 4; break; + case 3: ctype = 2; break; + case 4: ctype = 6; break; + default: conoutf(CON_ERROR, "failed saving png to %s", filename); return; + } + stream *f = openfile(filename, "wb"); + if(!f) { conoutf(CON_ERROR, "could not write to %s", filename); return; } + + uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + f->write(signature, sizeof(signature)); + + struct pngihdr + { + uint width, height; + uchar bitdepth, colortype, compress, filter, interlace; + } ihdr = { bigswap(image.w), bigswap(image.h), 8, ctype, 0, 0, 0 }; + writepngchunk(f, "IHDR", (uchar *)&ihdr, 13); + + stream::offset idat = f->tell(); + uint len = 0; + f->write("\0\0\0\0IDAT", 8); + uint crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, (const Bytef *)"IDAT", 4); + + z_stream z; + z.zalloc = NULL; + z.zfree = NULL; + z.opaque = NULL; + + if(deflateInit(&z, compresspng) != Z_OK) + goto error; + + uchar buf[1<<12]; + z.next_out = (Bytef *)buf; + z.avail_out = sizeof(buf); + + loopi(image.h) + { + uchar filter = 0; + loopj(2) + { + z.next_in = j ? (Bytef *)image.data + (flip ? image.h-i-1 : i)*image.pitch : (Bytef *)&filter; + z.avail_in = j ? image.w*image.bpp : 1; + while(z.avail_in > 0) + { + if(deflate(&z, Z_NO_FLUSH) != Z_OK) goto cleanuperror; + #define FLUSHZ do { \ + int flush = sizeof(buf) - z.avail_out; \ + crc = crc32(crc, buf, flush); \ + len += flush; \ + f->write(buf, flush); \ + z.next_out = (Bytef *)buf; \ + z.avail_out = sizeof(buf); \ + } while(0) + FLUSHZ; + } + } + } + + for(;;) + { + int err = deflate(&z, Z_FINISH); + if(err != Z_OK && err != Z_STREAM_END) goto cleanuperror; + FLUSHZ; + if(err == Z_STREAM_END) break; + } + + deflateEnd(&z); + + f->seek(idat, SEEK_SET); + f->putbig(len); + f->seek(0, SEEK_END); + f->putbig(crc); + + writepngchunk(f, "IEND"); + + delete f; + return; cleanuperror: - deflateEnd(&z); + deflateEnd(&z); error: - delete f; + delete f; - conoutf(CON_ERROR, "failed saving png to %s", filename); + conoutf(CON_ERROR, "failed saving png to %s", filename); } -struct tgaheader -{ - uchar identsize; - uchar cmaptype; - uchar imagetype; - uchar cmaporigin[2]; - uchar cmapsize[2]; - uchar cmapentrysize; - uchar xorigin[2]; - uchar yorigin[2]; - uchar width[2]; - uchar height[2]; - uchar pixelsize; - uchar descbyte; -}; - -VARP(compresstga, 0, 1, 1); - -void savetga(const char *filename, ImageData &image, bool flip) -{ - switch(image.bpp) - { - case 3: case 4: break; - default: conoutf(CON_ERROR, "failed saving tga to %s", filename); return; - } - - stream *f = openfile(filename, "wb"); - if(!f) { conoutf(CON_ERROR, "could not write to %s", filename); return; } - - tgaheader hdr; - memset(&hdr, 0, sizeof(hdr)); - hdr.pixelsize = image.bpp*8; - hdr.width[0] = image.w&0xFF; - hdr.width[1] = (image.w>>8)&0xFF; - hdr.height[0] = image.h&0xFF; - hdr.height[1] = (image.h>>8)&0xFF; - hdr.imagetype = compresstga ? 10 : 2; - f->write(&hdr, sizeof(hdr)); - - uchar buf[128*4]; - loopi(image.h) - { - uchar *src = image.data + (flip ? i : image.h - i - 1)*image.pitch; - for(int remaining = image.w; remaining > 0;) - { - int raw = 1; - if(compresstga) - { - int run = 1; - for(uchar *scan = src; run < min(remaining, 128); run++) - { - scan += image.bpp; - if(src[0]!=scan[0] || src[1]!=scan[1] || src[2]!=scan[2] || (image.bpp==4 && src[3]!=scan[3])) break; - } - if(run > 1) - { - f->putchar(0x80 | (run-1)); - f->putchar(src[2]); f->putchar(src[1]); f->putchar(src[0]); - if(image.bpp==4) f->putchar(src[3]); - src += run*image.bpp; - remaining -= run; - if(remaining <= 0) break; - } - for(uchar *scan = src; raw < min(remaining, 128); raw++) - { - scan += image.bpp; - if(src[0]==scan[0] && src[1]==scan[1] && src[2]==scan[2] && (image.bpp!=4 || src[3]==scan[3])) break; - } - f->putchar(raw - 1); - } - else raw = min(remaining, 128); - uchar *dst = buf; - loopj(raw) - { - dst[0] = src[2]; - dst[1] = src[1]; - dst[2] = src[0]; - if(image.bpp==4) dst[3] = src[3]; - dst += image.bpp; - src += image.bpp; - } - f->write(buf, raw*image.bpp); - remaining -= raw; - } - } - - delete f; -} - -enum -{ - IMG_BMP = 0, - IMG_TGA = 1, - IMG_PNG = 2, - IMG_JPG = 3, - NUMIMG -}; +enum { IMG_BMP, IMG_PNG, IMG_JPG, NUMIMG }; VARP(screenshotquality, 0, 97, 100); VARP(screenshotformat, 0, IMG_PNG, NUMIMG-1); -const char *imageexts[NUMIMG] = { ".bmp", ".tga", ".png", ".jpg" }; +const char *imageexts[NUMIMG] = { ".bmp", ".png", ".jpg" }; int guessimageformat(const char *filename, int format = IMG_BMP) { - int len = strlen(filename); - loopi(NUMIMG) - { - int extlen = strlen(imageexts[i]); - if(len >= extlen && !strcasecmp(&filename[len-extlen], imageexts[i])) return i; - } - return format; + int len = strlen(filename); + loopi(NUMIMG) + { + int extlen = strlen(imageexts[i]); + if(len >= extlen && !strcasecmp(&filename[len-extlen], imageexts[i])) return i; + } + return format; } void saveimage(const char *filename, int format, ImageData &image, bool flip = false) { - switch(format) - { - case IMG_PNG: savepng(filename, image, flip); break; - case IMG_TGA: savetga(filename, image, flip); break; - default: - { - ImageData flipped(image.w, image.h, image.bpp, image.data); - if(flip) texflip(flipped); - SDL_Surface *s = wrapsurface(flipped.data, flipped.w, flipped.h, flipped.bpp); - if(!s) break; - stream *f = openfile(filename, "wb"); - if(f) - { - switch(format) { - case IMG_JPG: + switch(format) + { + case IMG_PNG: savepng(filename, image, flip); break; + default: + { + ImageData flipped(image.w, image.h, image.bpp, image.data); + if(flip) texflip(flipped); + SDL_Surface *s = wrapsurface(flipped.data, flipped.w, flipped.h, flipped.bpp); + if(!s) break; + stream *f = openfile(filename, "wb"); + if(f) + { + switch(format) { + case IMG_JPG: #if SDL_IMAGE_VERSION_ATLEAST(2, 0, 2) - IMG_SaveJPG_RW(s, f->rwops(), 1, screenshotquality); + IMG_SaveJPG_RW(s, f->rwops(), 1, screenshotquality); #else - conoutf(CON_ERROR, "JPG screenshot support requires SDL_image 2.0.2"); + conoutf(CON_ERROR, "JPG screenshot support requires SDL_image 2.0.2"); #endif - break; - default: SDL_SaveBMP_RW(s, f->rwops(), 1); break; - } - delete f; - } - SDL_FreeSurface(s); - break; - } - } + break; + default: SDL_SaveBMP_RW(s, f->rwops(), 1); break; + } + delete f; + } + SDL_FreeSurface(s); + break; + } + } } bool loadimage(const char *filename, ImageData &image) { - SDL_Surface *s = loadsurface(path(filename, true)); - if(!s) return false; - image.wrap(s); - return true; + SDL_Surface *s = loadsurface(path(filename, true)); + if(!s) return false; + image.wrap(s); + return true; } SVARP(screenshotdir, "screenshot"); void screenshot(char *filename) { - static string buf; - int format = -1, dirlen = 0; - copystring(buf, screenshotdir); - if(screenshotdir[0]) - { - dirlen = strlen(buf); - if(buf[dirlen] != '/' && buf[dirlen] != '\\' && dirlen+1 < (int)sizeof(buf)) { buf[dirlen++] = '/'; buf[dirlen] = '\0'; } - const char *dir = findfile(buf, "w"); - if(!fileexists(dir, "w")) createdir(dir); - } - if(filename[0]) - { - concatstring(buf, filename); - format = guessimageformat(buf, -1); - } - else - { - string sstime; - time_t t = time(NULL); - size_t len = strftime(sstime, sizeof(sstime), "%Y-%m-%d_%H.%M.%S", localtime(&t)); - sstime[min(len, sizeof(sstime)-1)] = '\0'; - concatstring(buf, sstime); - - const char *map = game::getclientmap(), *ssinfo = game::getscreenshotinfo(); - if(map && map[0]) - { - concatstring(buf, "_"); - concatstring(buf, map); - } - if(ssinfo && ssinfo[0]) - { - concatstring(buf, "_"); - concatstring(buf, ssinfo); - } - - for(char *s = &buf[dirlen]; *s; s++) if(iscubespace(*s) || *s == '/' || *s == '\\') *s = '-'; - } - if(format < 0) - { - format = screenshotformat; - concatstring(buf, imageexts[format]); - } - - ImageData image(screenw, screenh, 3); - glPixelStorei(GL_PACK_ALIGNMENT, texalign(image.data, screenw, 3)); - glReadPixels(0, 0, screenw, screenh, GL_RGB, GL_UNSIGNED_BYTE, image.data); - saveimage(path(buf), format, image, true); + static string buf; + int format = -1, dirlen = 0; + copystring(buf, screenshotdir); + if(screenshotdir[0]) + { + dirlen = strlen(buf); + if(buf[dirlen] != '/' && buf[dirlen] != '\\' && dirlen+1 < (int)sizeof(buf)) { buf[dirlen++] = '/'; buf[dirlen] = '\0'; } + const char *dir = findfile(buf, "w"); + if(!fileexists(dir, "w")) createdir(dir); + } + if(filename[0]) + { + concatstring(buf, filename); + format = guessimageformat(buf, -1); + } + else + { + string sstime; + time_t t = time(NULL); + size_t len = strftime(sstime, sizeof(sstime), "%Y-%m-%d_%H.%M.%S", localtime(&t)); + sstime[min(len, sizeof(sstime)-1)] = '\0'; + concatstring(buf, sstime); + + const char *map = game::getclientmap(), *ssinfo = game::getscreenshotinfo(); + if(map && map[0]) + { + concatstring(buf, "_"); + concatstring(buf, map); + } + if(ssinfo && ssinfo[0]) + { + concatstring(buf, "_"); + concatstring(buf, ssinfo); + } + + for(char *s = &buf[dirlen]; *s; s++) if(iscubespace(*s) || *s == '/' || *s == '\\') *s = '-'; + } + if(format < 0) + { + format = screenshotformat; + concatstring(buf, imageexts[format]); + } + + ImageData image(screenw, screenh, 3); + glPixelStorei(GL_PACK_ALIGNMENT, texalign(image.data, screenw, 3)); + glReadPixels(0, 0, screenw, screenh, GL_RGB, GL_UNSIGNED_BYTE, image.data); + saveimage(path(buf), format, image, true); } COMMAND(screenshot, "s"); - -void flipnormalmapy(char *destfile, char *normalfile) // jpg/png /tga-> tga -{ - ImageData ns; - if(!loadimage(normalfile, ns)) return; - ImageData d(ns.w, ns.h, 3); - readwritetex(d, ns, - dst[0] = src[0]; - dst[1] = 255 - src[1]; - dst[2] = src[2]; - ); - saveimage(destfile, guessimageformat(destfile, IMG_TGA), d); -} - -void mergenormalmaps(char *heightfile, char *normalfile) // jpg/png/tga + tga -> tga -{ - ImageData hs, ns; - if(!loadimage(heightfile, hs) || !loadimage(normalfile, ns) || hs.w != ns.w || hs.h != ns.h) return; - ImageData d(ns.w, ns.h, 3); - read2writetex(d, hs, srch, ns, srcn, - *(bvec *)dst = bvec(((bvec *)srcn)->tonormal().mul(2).add(((bvec *)srch)->tonormal()).normalize()); - ); - saveimage(normalfile, guessimageformat(normalfile, IMG_TGA), d); -} - -COMMAND(flipnormalmapy, "ss"); -COMMAND(mergenormalmaps, "ss"); - diff --git a/src/engine/texture.h b/src/engine/texture.h index b39ba80..0b1335a 100644 --- a/src/engine/texture.h +++ b/src/engine/texture.h @@ -1,99 +1,99 @@ struct GlobalShaderParamState { - const char *name; - union - { - float fval[32]; - int ival[32]; - uint uval[32]; - uchar buf[32*sizeof(float)]; - }; - int version; - - static int nextversion; - - void resetversions(); - - void changed() - { - if(++nextversion < 0) resetversions(); - version = nextversion; - } + const char *name; + union + { + float fval[32]; + int ival[32]; + uint uval[32]; + uchar buf[32*sizeof(float)]; + }; + int version; + + static int nextversion; + + void resetversions(); + + void changed() + { + if(++nextversion < 0) resetversions(); + version = nextversion; + } }; struct ShaderParamBinding { - int loc, size; - GLenum format; + int loc, size; + GLenum format; }; struct GlobalShaderParamUse : ShaderParamBinding { - GlobalShaderParamState *param; - int version; - - void flush() - { - if(version == param->version) return; - switch(format) - { - case GL_BOOL: - case GL_FLOAT: glUniform1fv_(loc, size, param->fval); break; - case GL_BOOL_VEC2: - case GL_FLOAT_VEC2: glUniform2fv_(loc, size, param->fval); break; - case GL_BOOL_VEC3: - case GL_FLOAT_VEC3: glUniform3fv_(loc, size, param->fval); break; - case GL_BOOL_VEC4: - case GL_FLOAT_VEC4: glUniform4fv_(loc, size, param->fval); break; - case GL_INT: glUniform1iv_(loc, size, param->ival); break; - case GL_INT_VEC2: glUniform2iv_(loc, size, param->ival); break; - case GL_INT_VEC3: glUniform3iv_(loc, size, param->ival); break; - case GL_INT_VEC4: glUniform4iv_(loc, size, param->ival); break; - case GL_FLOAT_MAT2: glUniformMatrix2fv_(loc, 1, GL_FALSE, param->fval); break; - case GL_FLOAT_MAT3: glUniformMatrix3fv_(loc, 1, GL_FALSE, param->fval); break; - case GL_FLOAT_MAT4: glUniformMatrix4fv_(loc, 1, GL_FALSE, param->fval); break; - } - version = param->version; - } + GlobalShaderParamState *param; + int version; + + void flush() + { + if(version == param->version) return; + switch(format) + { + case GL_BOOL: + case GL_FLOAT: glUniform1fv_(loc, size, param->fval); break; + case GL_BOOL_VEC2: + case GL_FLOAT_VEC2: glUniform2fv_(loc, size, param->fval); break; + case GL_BOOL_VEC3: + case GL_FLOAT_VEC3: glUniform3fv_(loc, size, param->fval); break; + case GL_BOOL_VEC4: + case GL_FLOAT_VEC4: glUniform4fv_(loc, size, param->fval); break; + case GL_INT: glUniform1iv_(loc, size, param->ival); break; + case GL_INT_VEC2: glUniform2iv_(loc, size, param->ival); break; + case GL_INT_VEC3: glUniform3iv_(loc, size, param->ival); break; + case GL_INT_VEC4: glUniform4iv_(loc, size, param->ival); break; + case GL_FLOAT_MAT2: glUniformMatrix2fv_(loc, 1, GL_FALSE, param->fval); break; + case GL_FLOAT_MAT3: glUniformMatrix3fv_(loc, 1, GL_FALSE, param->fval); break; + case GL_FLOAT_MAT4: glUniformMatrix4fv_(loc, 1, GL_FALSE, param->fval); break; + } + version = param->version; + } }; struct LocalShaderParamState : ShaderParamBinding { - const char *name; + const char *name; }; struct SlotShaderParam { - const char *name; - int loc; - float val[4]; + const char *name; + int loc; + float val[4]; }; struct SlotShaderParamState : LocalShaderParamState { - float val[4]; - - SlotShaderParamState() {} - SlotShaderParamState(const SlotShaderParam &p) - { - name = p.name; - loc = -1; - size = 1; - format = GL_FLOAT_VEC4; - memcpy(val, p.val, sizeof(val)); - } + float val[4]; + + SlotShaderParamState() {} + SlotShaderParamState(const SlotShaderParam &p) + { + name = p.name; + loc = -1; + size = 1; + format = GL_FLOAT_VEC4; + memcpy(val, p.val, sizeof(val)); + } }; enum { - SHADER_DEFAULT = 0, - SHADER_NORMALSLMS = 1<<0, - SHADER_ENVMAP = 1<<1, - SHADER_OPTION = 1<<3, + SHADER_DEFAULT = 0, + SHADER_NORMALSLMS = 1<<0, + SHADER_ENVMAP = 1<<1, + SHADER_OPTION = 1<<3, - SHADER_INVALID = 1<<8, - SHADER_DEFERRED = 1<<9 + SHADER_INVALID = 1<<8, + SHADER_DEFERRED = 1<<9 }; #define MAXSHADERDETAIL 3 @@ -106,319 +106,319 @@ struct VSlot; struct UniformLoc { - const char *name, *blockname; - int loc, version, binding, stride, offset, size; - void *data; - UniformLoc(const char *name = NULL, const char *blockname = NULL, int binding = -1, int stride = -1) : name(name), blockname(blockname), loc(-1), version(-1), binding(binding), stride(stride), offset(-1), size(-1), data(NULL) {} + const char *name, *blockname; + int loc, version, binding, stride, offset, size; + void *data; + UniformLoc(const char *name = NULL, const char *blockname = NULL, int binding = -1, int stride = -1) : name(name), blockname(blockname), loc(-1), version(-1), binding(binding), stride(stride), offset(-1), size(-1), data(NULL) {} }; struct AttribLoc { - const char *name; - int loc; - AttribLoc(const char *name = NULL, int loc = -1) : name(name), loc(loc) {} + const char *name; + int loc; + AttribLoc(const char *name = NULL, int loc = -1) : name(name), loc(loc) {} }; struct Shader { - static Shader *lastshader; - - char *name, *vsstr, *psstr, *defer; - int type; - GLuint program, vsobj, psobj; - vector defaultparams; - vector globalparams; - vector localparams; - vector localparamremap; - Shader *detailshader, *variantshader, *altshader, *fastshader[MAXSHADERDETAIL]; - vector variants; - ushort *variantrows; - bool standard, forced, used; - Shader *reusevs, *reuseps; - vector uniformlocs; - vector attriblocs; - const void *owner; - - Shader() : name(NULL), vsstr(NULL), psstr(NULL), defer(NULL), type(SHADER_DEFAULT), program(0), vsobj(0), psobj(0), detailshader(NULL), variantshader(NULL), altshader(NULL), variantrows(NULL), standard(false), forced(false), used(false), reusevs(NULL), reuseps(NULL), owner(NULL) - { - loopi(MAXSHADERDETAIL) fastshader[i] = this; - } - - ~Shader() - { - DELETEA(name); - DELETEA(vsstr); - DELETEA(psstr); - DELETEA(defer); - DELETEA(variantrows); - } - - void fixdetailshader(bool force = true, bool recurse = true); - void allocparams(Slot *slot = NULL); - void setslotparams(Slot &slot); - void setslotparams(Slot &slot, VSlot &vslot); - void bindprograms(); - - void flushparams(Slot *slot = NULL) - { - if(!used) { allocparams(slot); used = true; } - loopv(globalparams) globalparams[i].flush(); - } - - void force(); - - bool invalid() const { return (type&SHADER_INVALID)!=0; } - bool deferred() const { return (type&SHADER_DEFERRED)!=0; } - bool loaded() const { return detailshader!=NULL; } - - static bool isnull(const Shader *s); - - bool isnull() const { return isnull(this); } - - int numvariants(int row) const - { - if(row < 0 || row >= MAXVARIANTROWS || !variantrows) return 0; - return variantrows[row+1] - variantrows[row]; - } - - Shader *getvariant(int col, int row) const - { - if(row < 0 || row >= MAXVARIANTROWS || col < 0 || !variantrows) return NULL; - int start = variantrows[row], end = variantrows[row+1]; - return col < end - start ? variants[start + col] : NULL; - } - - bool hasoption(int row) - { - if(detailshader) - { - Shader *s = getvariant(0, row); - if(s) return (s->type&SHADER_OPTION)!=0; - } - return false; - } - - void addvariant(int row, Shader *s) - { - if(row < 0 || row >= MAXVARIANTROWS || variants.length() >= USHRT_MAX) return; - if(!variantrows) { variantrows = new ushort[MAXVARIANTROWS+1]; memset(variantrows, 0, (MAXVARIANTROWS+1)*sizeof(ushort)); } - variants.insert(variantrows[row+1], s); - for(int i = row+1; i <= MAXVARIANTROWS; ++i) ++variantrows[i]; - } - - void setvariant_(int col, int row, Shader *fallbackshader) - { - Shader *s = fallbackshader; - if(detailshader->variantrows) - { - int start = detailshader->variantrows[row], end = detailshader->variantrows[row+1]; - for(col = min(start + col, end-1); col >= start; --col) - { - if(!detailshader->variants[col]->invalid()) { s = detailshader->variants[col]; break; } - } - } - if(lastshader!=s) s->bindprograms(); - } - - void setvariant(int col, int row, Shader *fallbackshader) - { - if(isnull() || !loaded()) return; - setvariant_(col, row, fallbackshader); - lastshader->flushparams(); - } - - void setvariant(int col, int row) - { - if(isnull() || !loaded()) return; - setvariant_(col, row, detailshader); - lastshader->flushparams(); - } - - void setvariant(int col, int row, Slot &slot, VSlot &vslot, Shader *fallbackshader) - { - if(isnull() || !loaded()) return; - setvariant_(col, row, fallbackshader); - lastshader->flushparams(&slot); - lastshader->setslotparams(slot, vslot); - } - - void setvariant(int col, int row, Slot &slot, VSlot &vslot) - { - if(isnull() || !loaded()) return; - setvariant_(col, row, detailshader); - lastshader->flushparams(&slot); - lastshader->setslotparams(slot, vslot); - } - - void set_() - { - if(lastshader!=detailshader) detailshader->bindprograms(); - } - - void set() - { - if(isnull() || !loaded()) return; - set_(); - lastshader->flushparams(); - } - - void set(Slot &slot, VSlot &vslot) - { - if(isnull() || !loaded()) return; - set_(); - lastshader->flushparams(&slot); - lastshader->setslotparams(slot, vslot); - } - - bool compile(); - void cleanup(bool invalid = false); - - static int uniformlocversion(); + static Shader *lastshader; + + char *name, *vsstr, *psstr, *defer; + int type; + GLuint program, vsobj, psobj; + vector defaultparams; + vector globalparams; + vector localparams; + vector localparamremap; + Shader *detailshader, *variantshader, *altshader, *fastshader[MAXSHADERDETAIL]; + vector variants; + ushort *variantrows; + bool standard, forced, used; + Shader *reusevs, *reuseps; + vector uniformlocs; + vector attriblocs; + const void *owner; + + Shader() : name(NULL), vsstr(NULL), psstr(NULL), defer(NULL), type(SHADER_DEFAULT), program(0), vsobj(0), psobj(0), detailshader(NULL), variantshader(NULL), altshader(NULL), variantrows(NULL), standard(false), forced(false), used(false), reusevs(NULL), reuseps(NULL), owner(NULL) + { + loopi(MAXSHADERDETAIL) fastshader[i] = this; + } + + ~Shader() + { + DELETEA(name); + DELETEA(vsstr); + DELETEA(psstr); + DELETEA(defer); + DELETEA(variantrows); + } + + void fixdetailshader(bool force = true, bool recurse = true); + void allocparams(Slot *slot = NULL); + void setslotparams(Slot &slot); + void setslotparams(Slot &slot, VSlot &vslot); + void bindprograms(); + + void flushparams(Slot *slot = NULL) + { + if(!used) { allocparams(slot); used = true; } + loopv(globalparams) globalparams[i].flush(); + } + + void force(); + + bool invalid() const { return (type&SHADER_INVALID)!=0; } + bool deferred() const { return (type&SHADER_DEFERRED)!=0; } + bool loaded() const { return detailshader!=NULL; } + + static bool isnull(const Shader *s); + + bool isnull() const { return isnull(this); } + + int numvariants(int row) const + { + if(row < 0 || row >= MAXVARIANTROWS || !variantrows) return 0; + return variantrows[row+1] - variantrows[row]; + } + + Shader *getvariant(int col, int row) const + { + if(row < 0 || row >= MAXVARIANTROWS || col < 0 || !variantrows) return NULL; + int start = variantrows[row], end = variantrows[row+1]; + return col < end - start ? variants[start + col] : NULL; + } + + bool hasoption(int row) + { + if(detailshader) + { + Shader *s = getvariant(0, row); + if(s) return (s->type&SHADER_OPTION)!=0; + } + return false; + } + + void addvariant(int row, Shader *s) + { + if(row < 0 || row >= MAXVARIANTROWS || variants.length() >= USHRT_MAX) return; + if(!variantrows) { variantrows = new ushort[MAXVARIANTROWS+1]; memset(variantrows, 0, (MAXVARIANTROWS+1)*sizeof(ushort)); } + variants.insert(variantrows[row+1], s); + for(int i = row+1; i <= MAXVARIANTROWS; ++i) ++variantrows[i]; + } + + void setvariant_(int col, int row, Shader *fallbackshader) + { + Shader *s = fallbackshader; + if(detailshader->variantrows) + { + int start = detailshader->variantrows[row], end = detailshader->variantrows[row+1]; + for(col = min(start + col, end-1); col >= start; --col) + { + if(!detailshader->variants[col]->invalid()) { s = detailshader->variants[col]; break; } + } + } + if(lastshader!=s) s->bindprograms(); + } + + void setvariant(int col, int row, Shader *fallbackshader) + { + if(isnull() || !loaded()) return; + setvariant_(col, row, fallbackshader); + lastshader->flushparams(); + } + + void setvariant(int col, int row) + { + if(isnull() || !loaded()) return; + setvariant_(col, row, detailshader); + lastshader->flushparams(); + } + + void setvariant(int col, int row, Slot &slot, VSlot &vslot, Shader *fallbackshader) + { + if(isnull() || !loaded()) return; + setvariant_(col, row, fallbackshader); + lastshader->flushparams(&slot); + lastshader->setslotparams(slot, vslot); + } + + void setvariant(int col, int row, Slot &slot, VSlot &vslot) + { + if(isnull() || !loaded()) return; + setvariant_(col, row, detailshader); + lastshader->flushparams(&slot); + lastshader->setslotparams(slot, vslot); + } + + void set_() + { + if(lastshader!=detailshader) detailshader->bindprograms(); + } + + void set() + { + if(isnull() || !loaded()) return; + set_(); + lastshader->flushparams(); + } + + void set(Slot &slot, VSlot &vslot) + { + if(isnull() || !loaded()) return; + set_(); + lastshader->flushparams(&slot); + lastshader->setslotparams(slot, vslot); + } + + bool compile(); + void cleanup(bool invalid = false); + + static int uniformlocversion(); }; struct GlobalShaderParam { - const char *name; - GlobalShaderParamState *param; - - GlobalShaderParam(const char *name) : name(name), param(NULL) {} - - GlobalShaderParamState *resolve() - { - extern GlobalShaderParamState *getglobalparam(const char *name); - if(!param) param = getglobalparam(name); - param->changed(); - return param; - } - - void setf(float x = 0, float y = 0, float z = 0, float w = 0) - { - GlobalShaderParamState *g = resolve(); - g->fval[0] = x; - g->fval[1] = y; - g->fval[2] = z; - g->fval[3] = w; - } - void set(const vec &v, float w = 0) { setf(v.x, v.y, v.z, w); } - void set(const vec2 &v, float z = 0, float w = 0) { setf(v.x, v.y, z, w); } - void set(const vec4 &v) { setf(v.x, v.y, v.z, v.w); } - void set(const plane &p) { setf(p.x, p.y, p.z, p.offset); } - void set(const matrix2 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); } - void set(const matrix3 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); } - void set(const matrix4 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); } - - template - void setv(const T *v, int n = 1) { memcpy(resolve()->buf, v, n*sizeof(T)); } - - void seti(int x = 0, int y = 0, int z = 0, int w = 0) - { - GlobalShaderParamState *g = resolve(); - g->ival[0] = x; - g->ival[1] = y; - g->ival[2] = z; - g->ival[3] = w; - } - void set(const ivec &v, int w = 0) { seti(v.x, v.y, v.z, w); } - void set(const ivec2 &v, int z = 0, int w = 0) { seti(v.x, v.y, z, w); } - void set(const ivec4 &v) { seti(v.x, v.y, v.z, v.w); } - - void setu(uint x = 0, uint y = 0, uint z = 0, uint w = 0) - { - GlobalShaderParamState *g = resolve(); - g->uval[0] = x; - g->uval[1] = y; - g->uval[2] = z; - g->uval[3] = w; - } - - template - T *reserve(int n = 1) { return (T *)resolve()->buf; } + const char *name; + GlobalShaderParamState *param; + + GlobalShaderParam(const char *name) : name(name), param(NULL) {} + + GlobalShaderParamState *resolve() + { + extern GlobalShaderParamState *getglobalparam(const char *name); + if(!param) param = getglobalparam(name); + param->changed(); + return param; + } + + void setf(float x = 0, float y = 0, float z = 0, float w = 0) + { + GlobalShaderParamState *g = resolve(); + g->fval[0] = x; + g->fval[1] = y; + g->fval[2] = z; + g->fval[3] = w; + } + void set(const vec &v, float w = 0) { setf(v.x, v.y, v.z, w); } + void set(const vec2 &v, float z = 0, float w = 0) { setf(v.x, v.y, z, w); } + void set(const vec4 &v) { setf(v.x, v.y, v.z, v.w); } + void set(const plane &p) { setf(p.x, p.y, p.z, p.offset); } + void set(const matrix2 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); } + void set(const matrix3 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); } + void set(const matrix4 &m) { memcpy(resolve()->fval, m.a.v, sizeof(m)); } + + template + void setv(const T *v, int n = 1) { memcpy(resolve()->buf, v, n*sizeof(T)); } + + void seti(int x = 0, int y = 0, int z = 0, int w = 0) + { + GlobalShaderParamState *g = resolve(); + g->ival[0] = x; + g->ival[1] = y; + g->ival[2] = z; + g->ival[3] = w; + } + void set(const ivec &v, int w = 0) { seti(v.x, v.y, v.z, w); } + void set(const ivec2 &v, int z = 0, int w = 0) { seti(v.x, v.y, z, w); } + void set(const ivec4 &v) { seti(v.x, v.y, v.z, v.w); } + + void setu(uint x = 0, uint y = 0, uint z = 0, uint w = 0) + { + GlobalShaderParamState *g = resolve(); + g->uval[0] = x; + g->uval[1] = y; + g->uval[2] = z; + g->uval[3] = w; + } + + template + T *reserve(int n = 1) { return (T *)resolve()->buf; } }; struct LocalShaderParam { - const char *name; - int loc; - - LocalShaderParam(const char *name) : name(name), loc(-1) {} - - LocalShaderParamState *resolve() - { - Shader *s = Shader::lastshader; - if(!s) return NULL; - if(!s->localparamremap.inrange(loc)) - { - extern int getlocalparam(const char *name); - if(loc == -1) loc = getlocalparam(name); - if(!s->localparamremap.inrange(loc)) return NULL; - } - uchar remap = s->localparamremap[loc]; - return s->localparams.inrange(remap) ? &s->localparams[remap] : NULL; - } - - void setf(float x = 0, float y = 0, float z = 0, float w = 0) - { - ShaderParamBinding *b = resolve(); - if(b) switch(b->format) - { - case GL_BOOL: - case GL_FLOAT: glUniform1f_(b->loc, x); break; - case GL_BOOL_VEC2: - case GL_FLOAT_VEC2: glUniform2f_(b->loc, x, y); break; - case GL_BOOL_VEC3: - case GL_FLOAT_VEC3: glUniform3f_(b->loc, x, y, z); break; - case GL_BOOL_VEC4: - case GL_FLOAT_VEC4: glUniform4f_(b->loc, x, y, z, w); break; - case GL_INT: glUniform1i_(b->loc, int(x)); break; - case GL_INT_VEC2: glUniform2i_(b->loc, int(x), int(y)); break; - case GL_INT_VEC3: glUniform3i_(b->loc, int(x), int(y), int(z)); break; - case GL_INT_VEC4: glUniform4i_(b->loc, int(x), int(y), int(z), int(w)); break; - } - } - void set(const vec &v, float w = 0) { setf(v.x, v.y, v.z, w); } - void set(const vec2 &v, float z = 0, float w = 0) { setf(v.x, v.y, z, w); } - void set(const vec4 &v) { setf(v.x, v.y, v.z, v.w); } - void set(const plane &p) { setf(p.x, p.y, p.z, p.offset); } - void setv(const float *f, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform1fv_(b->loc, n, f); } - void setv(const vec *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform3fv_(b->loc, n, v->v); } - void setv(const vec2 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform2fv_(b->loc, n, v->v); } - void setv(const vec4 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4fv_(b->loc, n, v->v); } - void setv(const plane *p, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4fv_(b->loc, n, p->v); } - void setv(const matrix2 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix2fv_(b->loc, n, GL_FALSE, m->a.v); } - void setv(const matrix3 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix3fv_(b->loc, n, GL_FALSE, m->a.v); } - void setv(const matrix4 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix4fv_(b->loc, n, GL_FALSE, m->a.v); } - void set(const matrix2 &m) { setv(&m); } - void set(const matrix3 &m) { setv(&m); } - void set(const matrix4 &m) { setv(&m); } - - template - void sett(T x, T y, T z, T w) - { - ShaderParamBinding *b = resolve(); - if(b) switch(b->format) - { - case GL_FLOAT: glUniform1f_(b->loc, x); break; - case GL_FLOAT_VEC2: glUniform2f_(b->loc, x, y); break; - case GL_FLOAT_VEC3: glUniform3f_(b->loc, x, y, z); break; - case GL_FLOAT_VEC4: glUniform4f_(b->loc, x, y, z, w); break; - case GL_BOOL: - case GL_INT: glUniform1i_(b->loc, x); break; - case GL_BOOL_VEC2: - case GL_INT_VEC2: glUniform2i_(b->loc, x, y); break; - case GL_BOOL_VEC3: - case GL_INT_VEC3: glUniform3i_(b->loc, x, y, z); break; - case GL_BOOL_VEC4: - case GL_INT_VEC4: glUniform4i_(b->loc, x, y, z, w); break; - } - } - void seti(int x = 0, int y = 0, int z = 0, int w = 0) { sett(x, y, z, w); } - void set(const ivec &v, int w = 0) { seti(v.x, v.y, v.z, w); } - void set(const ivec2 &v, int z = 0, int w = 0) { seti(v.x, v.y, z, w); } - void set(const ivec4 &v) { seti(v.x, v.y, v.z, v.w); } - void setv(const int *i, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform1iv_(b->loc, n, i); } - void setv(const ivec *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform3iv_(b->loc, n, v->v); } - void setv(const ivec2 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform2iv_(b->loc, n, v->v); } - void setv(const ivec4 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4iv_(b->loc, n, v->v); } + const char *name; + int loc; + + LocalShaderParam(const char *name) : name(name), loc(-1) {} + + LocalShaderParamState *resolve() + { + Shader *s = Shader::lastshader; + if(!s) return NULL; + if(!s->localparamremap.inrange(loc)) + { + extern int getlocalparam(const char *name); + if(loc == -1) loc = getlocalparam(name); + if(!s->localparamremap.inrange(loc)) return NULL; + } + uchar remap = s->localparamremap[loc]; + return s->localparams.inrange(remap) ? &s->localparams[remap] : NULL; + } + + void setf(float x = 0, float y = 0, float z = 0, float w = 0) + { + ShaderParamBinding *b = resolve(); + if(b) switch(b->format) + { + case GL_BOOL: + case GL_FLOAT: glUniform1f_(b->loc, x); break; + case GL_BOOL_VEC2: + case GL_FLOAT_VEC2: glUniform2f_(b->loc, x, y); break; + case GL_BOOL_VEC3: + case GL_FLOAT_VEC3: glUniform3f_(b->loc, x, y, z); break; + case GL_BOOL_VEC4: + case GL_FLOAT_VEC4: glUniform4f_(b->loc, x, y, z, w); break; + case GL_INT: glUniform1i_(b->loc, int(x)); break; + case GL_INT_VEC2: glUniform2i_(b->loc, int(x), int(y)); break; + case GL_INT_VEC3: glUniform3i_(b->loc, int(x), int(y), int(z)); break; + case GL_INT_VEC4: glUniform4i_(b->loc, int(x), int(y), int(z), int(w)); break; + } + } + void set(const vec &v, float w = 0) { setf(v.x, v.y, v.z, w); } + void set(const vec2 &v, float z = 0, float w = 0) { setf(v.x, v.y, z, w); } + void set(const vec4 &v) { setf(v.x, v.y, v.z, v.w); } + void set(const plane &p) { setf(p.x, p.y, p.z, p.offset); } + void setv(const float *f, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform1fv_(b->loc, n, f); } + void setv(const vec *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform3fv_(b->loc, n, v->v); } + void setv(const vec2 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform2fv_(b->loc, n, v->v); } + void setv(const vec4 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4fv_(b->loc, n, v->v); } + void setv(const plane *p, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4fv_(b->loc, n, p->v); } + void setv(const matrix2 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix2fv_(b->loc, n, GL_FALSE, m->a.v); } + void setv(const matrix3 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix3fv_(b->loc, n, GL_FALSE, m->a.v); } + void setv(const matrix4 *m, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniformMatrix4fv_(b->loc, n, GL_FALSE, m->a.v); } + void set(const matrix2 &m) { setv(&m); } + void set(const matrix3 &m) { setv(&m); } + void set(const matrix4 &m) { setv(&m); } + + template + void sett(T x, T y, T z, T w) + { + ShaderParamBinding *b = resolve(); + if(b) switch(b->format) + { + case GL_FLOAT: glUniform1f_(b->loc, x); break; + case GL_FLOAT_VEC2: glUniform2f_(b->loc, x, y); break; + case GL_FLOAT_VEC3: glUniform3f_(b->loc, x, y, z); break; + case GL_FLOAT_VEC4: glUniform4f_(b->loc, x, y, z, w); break; + case GL_BOOL: + case GL_INT: glUniform1i_(b->loc, x); break; + case GL_BOOL_VEC2: + case GL_INT_VEC2: glUniform2i_(b->loc, x, y); break; + case GL_BOOL_VEC3: + case GL_INT_VEC3: glUniform3i_(b->loc, x, y, z); break; + case GL_BOOL_VEC4: + case GL_INT_VEC4: glUniform4i_(b->loc, x, y, z, w); break; + } + } + void seti(int x = 0, int y = 0, int z = 0, int w = 0) { sett(x, y, z, w); } + void set(const ivec &v, int w = 0) { seti(v.x, v.y, v.z, w); } + void set(const ivec2 &v, int z = 0, int w = 0) { seti(v.x, v.y, z, w); } + void set(const ivec4 &v) { seti(v.x, v.y, v.z, v.w); } + void setv(const int *i, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform1iv_(b->loc, n, i); } + void setv(const ivec *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform3iv_(b->loc, n, v->v); } + void setv(const ivec2 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform2iv_(b->loc, n, v->v); } + void setv(const ivec4 *v, int n = 1) { ShaderParamBinding *b = resolve(); if(b) glUniform4iv_(b->loc, n, v->v); } }; #define LOCALPARAM(name, vals) do { static LocalShaderParam param( #name ); param.set(vals); } while(0) @@ -431,106 +431,106 @@ struct LocalShaderParam #define GLOBALPARAMV(name, vals, num) do { static GlobalShaderParam param( #name ); param.setv(vals, num); } while(0) #define SETSHADER(name, ...) \ - do { \ - static Shader *name##shader = NULL; \ - if(!name##shader) name##shader = lookupshaderbyname(#name); \ - name##shader->set(__VA_ARGS__); \ - } while(0) + do { \ + static Shader *name##shader = NULL; \ + if(!name##shader) name##shader = lookupshaderbyname(#name); \ + name##shader->set(__VA_ARGS__); \ + } while(0) #define SETVARIANT(name, ...) \ - do { \ - static Shader *name##shader = NULL; \ - if(!name##shader) name##shader = lookupshaderbyname(#name); \ - name##shader->setvariant(__VA_ARGS__); \ - } while(0) + do { \ + static Shader *name##shader = NULL; \ + if(!name##shader) name##shader = lookupshaderbyname(#name); \ + name##shader->setvariant(__VA_ARGS__); \ + } while(0) struct ImageData { - int w, h, bpp, levels, align, pitch; - GLenum compressed; - uchar *data; - void *owner; - void (*freefunc)(void *); - - ImageData() - : data(NULL), owner(NULL), freefunc(NULL) - {} - - - ImageData(int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE) - { - setdata(NULL, nw, nh, nbpp, nlevels, nalign, ncompressed); - } - - ImageData(int nw, int nh, int nbpp, uchar *data) - : owner(NULL), freefunc(NULL) - { - setdata(data, nw, nh, nbpp); - } - - ImageData(SDL_Surface *s) { wrap(s); } - ~ImageData() { cleanup(); } - - void setdata(uchar *ndata, int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE) - { - w = nw; - h = nh; - bpp = nbpp; - levels = nlevels; - align = nalign; - pitch = align ? 0 : w*bpp; - compressed = ncompressed; - data = ndata ? ndata : new uchar[calcsize()]; - if(!ndata) { owner = this; freefunc = NULL; } - } - - int calclevelsize(int level) const { return ((max(w>>level, 1)+align-1)/align)*((max(h>>level, 1)+align-1)/align)*bpp; } - - int calcsize() const - { - if(!align) return w*h*bpp; - int lw = w, lh = h, - size = 0; - loopi(levels) - { - if(lw<=0) lw = 1; - if(lh<=0) lh = 1; - size += ((lw+align-1)/align)*((lh+align-1)/align)*bpp; - if(lw*lh==1) break; - lw >>= 1; - lh >>= 1; - } - return size; - } - - void disown() - { - data = NULL; - owner = NULL; - freefunc = NULL; - } - - void cleanup() - { - if(owner==this) delete[] data; - else if(freefunc) (*freefunc)(owner); - disown(); - } - - void replace(ImageData &d) - { - cleanup(); - *this = d; - if(owner == &d) owner = this; - d.disown(); - } - - void wrap(SDL_Surface *s) - { - setdata((uchar *)s->pixels, s->w, s->h, s->format->BytesPerPixel); - pitch = s->pitch; - owner = s; - freefunc = (void (*)(void *))SDL_FreeSurface; - } + int w, h, bpp, levels, align, pitch; + GLenum compressed; + uchar *data; + void *owner; + void (*freefunc)(void *); + + ImageData() + : data(NULL), owner(NULL), freefunc(NULL) + {} + + + ImageData(int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE) + { + setdata(NULL, nw, nh, nbpp, nlevels, nalign, ncompressed); + } + + ImageData(int nw, int nh, int nbpp, uchar *data) + : owner(NULL), freefunc(NULL) + { + setdata(data, nw, nh, nbpp); + } + + ImageData(SDL_Surface *s) { wrap(s); } + ~ImageData() { cleanup(); } + + void setdata(uchar *ndata, int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE) + { + w = nw; + h = nh; + bpp = nbpp; + levels = nlevels; + align = nalign; + pitch = align ? 0 : w*bpp; + compressed = ncompressed; + data = ndata ? ndata : new uchar[calcsize()]; + if(!ndata) { owner = this; freefunc = NULL; } + } + + int calclevelsize(int level) const { return ((max(w>>level, 1)+align-1)/align)*((max(h>>level, 1)+align-1)/align)*bpp; } + + int calcsize() const + { + if(!align) return w*h*bpp; + int lw = w, lh = h, + size = 0; + loopi(levels) + { + if(lw<=0) lw = 1; + if(lh<=0) lh = 1; + size += ((lw+align-1)/align)*((lh+align-1)/align)*bpp; + if(lw*lh==1) break; + lw >>= 1; + lh >>= 1; + } + return size; + } + + void disown() + { + data = NULL; + owner = NULL; + freefunc = NULL; + } + + void cleanup() + { + if(owner==this) delete[] data; + else if(freefunc) (*freefunc)(owner); + disown(); + } + + void replace(ImageData &d) + { + cleanup(); + *this = d; + if(owner == &d) owner = this; + d.disown(); + } + + void wrap(SDL_Surface *s) + { + setdata((uchar *)s->pixels, s->w, s->h, s->format->BytesPerPixel); + pitch = s->pitch; + owner = s; + freefunc = (void (*)(void *))SDL_FreeSurface; + } }; // management of texture slots @@ -539,190 +539,190 @@ struct ImageData struct Texture { - enum - { - IMAGE = 0, - CUBEMAP = 1, - TYPE = 0xFF, - - STUB = 1<<8, - TRANSIENT = 1<<9, - COMPRESSED = 1<<10, - ALPHA = 1<<11, - MIRROR = 1<<12, - FLAGS = 0xFF00 - }; - - char *name; - int type, w, h, xs, ys, bpp, clamp; - bool mipmap, canreduce; - GLuint id; - uchar *alphamask; - - Texture() : alphamask(NULL) {} + enum + { + IMAGE = 0, + CUBEMAP = 1, + TYPE = 0xFF, + + STUB = 1<<8, + TRANSIENT = 1<<9, + COMPRESSED = 1<<10, + ALPHA = 1<<11, + MIRROR = 1<<12, + FLAGS = 0xFF00 + }; + + char *name; + int type, w, h, xs, ys, bpp, clamp; + bool mipmap, canreduce; + GLuint id; + uchar *alphamask; + + Texture() : alphamask(NULL) {} }; enum { - TEX_DIFFUSE = 0, - TEX_UNKNOWN, - TEX_DECAL, - TEX_NORMAL, - TEX_GLOW, - TEX_SPEC, - TEX_DEPTH, - TEX_ALPHA, - TEX_ENVMAP + TEX_DIFFUSE = 0, + TEX_UNKNOWN, + TEX_DECAL, + TEX_NORMAL, + TEX_GLOW, + TEX_SPEC, + TEX_DEPTH, + TEX_ALPHA, + TEX_ENVMAP }; enum { - VSLOT_SHPARAM = 0, - VSLOT_SCALE, - VSLOT_ROTATION, - VSLOT_OFFSET, - VSLOT_SCROLL, - VSLOT_LAYER, - VSLOT_ALPHA, - VSLOT_COLOR, - VSLOT_NUM + VSLOT_SHPARAM = 0, + VSLOT_SCALE, + VSLOT_ROTATION, + VSLOT_OFFSET, + VSLOT_SCROLL, + VSLOT_LAYER, + VSLOT_ALPHA, + VSLOT_COLOR, + VSLOT_NUM }; struct VSlot { - Slot *slot; - VSlot *next; - int index, changed; - vector params; - bool linked; - float scale; - int rotation; - ivec2 offset; - vec2 scroll; - int layer; - float alphafront, alphaback; - vec colorscale; - vec glowcolor; - - VSlot(Slot *slot = NULL, int index = -1) : slot(slot), next(NULL), index(index), changed(0) - { - reset(); - if(slot) addvariant(slot); - } - - void addvariant(Slot *slot); - - void reset() - { - params.shrink(0); - linked = false; - scale = 1; - rotation = 0; - offset = ivec2(0, 0); - scroll = vec2(0, 0); - layer = 0; - alphafront = 0.5f; - alphaback = 0; - colorscale = vec(1, 1, 1); - glowcolor = vec(1, 1, 1); - } - - void cleanup() - { - linked = false; - } + Slot *slot; + VSlot *next; + int index, changed; + vector params; + bool linked; + float scale; + int rotation; + ivec2 offset; + vec2 scroll; + int layer; + float alphafront, alphaback; + vec colorscale; + vec glowcolor; + + VSlot(Slot *slot = NULL, int index = -1) : slot(slot), next(NULL), index(index), changed(0) + { + reset(); + if(slot) addvariant(slot); + } + + void addvariant(Slot *slot); + + void reset() + { + params.shrink(0); + linked = false; + scale = 1; + rotation = 0; + offset = ivec2(0, 0); + scroll = vec2(0, 0); + layer = 0; + alphafront = 0.5f; + alphaback = 0; + colorscale = vec(1, 1, 1); + glowcolor = vec(1, 1, 1); + } + + void cleanup() + { + linked = false; + } }; struct Slot { - struct Tex - { - int type; - Texture *t; - string name; - int combined; - }; - - int index; - vector sts; - Shader *shader; - vector params; - VSlot *variants; - bool loaded; - uint texmask; - Texture *thumbnail; - char *layermaskname; - int layermaskmode; - float layermaskscale; - ImageData *layermask; - - Slot(int index = -1) : index(index), variants(NULL), layermaskname(NULL), layermask(NULL) { reset(); } - - void reset() - { - sts.shrink(0); - shader = NULL; - params.shrink(0); - loaded = false; - texmask = 0; - thumbnail = NULL; - DELETEA(layermaskname); - layermaskmode = 0; - layermaskscale = 1; - if(layermask) DELETEP(layermask); - } - - void cleanup() - { - loaded = false; - thumbnail = NULL; - loopv(sts) - { - Tex &t = sts[i]; - t.t = NULL; - t.combined = -1; - } - } + struct Tex + { + int type; + Texture *t; + string name; + int combined; + }; + + int index; + vector sts; + Shader *shader; + vector params; + VSlot *variants; + bool loaded; + uint texmask; + Texture *thumbnail; + char *layermaskname; + int layermaskmode; + float layermaskscale; + ImageData *layermask; + + Slot(int index = -1) : index(index), variants(NULL), layermaskname(NULL), layermask(NULL) { reset(); } + + void reset() + { + sts.shrink(0); + shader = NULL; + params.shrink(0); + loaded = false; + texmask = 0; + thumbnail = NULL; + DELETEA(layermaskname); + layermaskmode = 0; + layermaskscale = 1; + if(layermask) DELETEP(layermask); + } + + void cleanup() + { + loaded = false; + thumbnail = NULL; + loopv(sts) + { + Tex &t = sts[i]; + t.t = NULL; + t.combined = -1; + } + } }; inline void VSlot::addvariant(Slot *slot) { - if(!slot->variants) slot->variants = this; - else - { - VSlot *prev = slot->variants; - while(prev->next) prev = prev->next; - prev->next = this; - } + if(!slot->variants) slot->variants = this; + else + { + VSlot *prev = slot->variants; + while(prev->next) prev = prev->next; + prev->next = this; + } } struct MSlot : Slot, VSlot { - MSlot() : VSlot(this) {} - - void reset() - { - Slot::reset(); - VSlot::reset(); - } - - void cleanup() - { - Slot::cleanup(); - VSlot::cleanup(); - } + MSlot() : VSlot(this) {} + + void reset() + { + Slot::reset(); + VSlot::reset(); + } + + void cleanup() + { + Slot::cleanup(); + VSlot::cleanup(); + } }; struct texrotation { - bool flipx, flipy, swapxy; + bool flipx, flipy, swapxy; }; struct cubemapside { - GLenum target; - const char *name; - bool flipx, flipy, swapxy; + GLenum target; + const char *name; + bool flipx, flipy, swapxy; }; extern const texrotation texrotations[8]; @@ -755,7 +755,6 @@ extern void setupblurkernel(int radius, float sigma, float *weights, float *offs extern void setblurshader(int pass, int size, int radius, float *weights, float *offsets); extern void savepng(const char *filename, ImageData &image, bool flip = false); -extern void savetga(const char *filename, ImageData &image, bool flip = false); extern bool loadimage(const char *filename, ImageData &image); extern MSlot &lookupmaterialslot(int slot, bool load = true); diff --git a/src/engine/vertmodel.h b/src/engine/vertmodel.h index eb09001..e9af1b0 100644 --- a/src/engine/vertmodel.h +++ b/src/engine/vertmodel.h @@ -1,490 +1,490 @@ struct vertmodel : animmodel { - struct vert { vec pos, norm; }; - struct vvert { vec pos; vec2 tc; }; - struct vvertn : vvert { vec norm; }; - struct vvertbump : vvert { squat tangent; }; - struct tcvert { vec2 tc; }; - struct bumpvert { vec4 tangent; }; - struct tri { ushort vert[3]; }; - - struct vbocacheentry - { - GLuint vbuf; - animstate as; - int millis; - - vbocacheentry() : vbuf(0) { as.cur.fr1 = as.prev.fr1 = -1; } - }; - - struct vertmesh : mesh - { - vert *verts; - tcvert *tcverts; - bumpvert *bumpverts; - tri *tris; - int numverts, numtris; - - int voffset, eoffset, elen; - ushort minvert, maxvert; - - vertmesh() : verts(0), tcverts(0), bumpverts(0), tris(0) - { - } - - virtual ~vertmesh() - { - DELETEA(verts); - DELETEA(tcverts); - DELETEA(bumpverts); - DELETEA(tris); - } - - void smoothnorms(float limit = 0, bool areaweight = true) - { - if(((vertmeshgroup *)group)->numframes == 1) mesh::smoothnorms(verts, numverts, tris, numtris, limit, areaweight); - else buildnorms(areaweight); - } - - void buildnorms(bool areaweight = true) - { - mesh::buildnorms(verts, numverts, tris, numtris, areaweight, ((vertmeshgroup *)group)->numframes); - } - - void calctangents(bool areaweight = true) - { - if(bumpverts) return; - bumpverts = new bumpvert[((vertmeshgroup *)group)->numframes*numverts]; - mesh::calctangents(bumpverts, verts, tcverts, numverts, tris, numtris, areaweight, ((vertmeshgroup *)group)->numframes); - } - - void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) - { - loopj(numverts) - { - vec v = m.transform(verts[j].pos); - loopi(3) - { - bbmin[i] = min(bbmin[i], v[i]); - bbmax[i] = max(bbmax[i], v[i]); - } - } - } - - void genBIH(BIH::mesh &m) - { - m.tris = (const BIH::tri *)tris; - m.numtris = numtris; - m.pos = (const uchar *)&verts->pos; - m.posstride = sizeof(vert); - m.tc = (const uchar *)&tcverts->tc; - m.tcstride = sizeof(tcvert); - } - - static inline void assignvert(vvertn &vv, int j, tcvert &tc, vert &v) - { - vv.pos = v.pos; - vv.norm = v.norm; - vv.tc = tc.tc; - } - - inline void assignvert(vvertbump &vv, int j, tcvert &tc, vert &v) - { - vv.pos = v.pos; - vv.tc = tc.tc; - vv.tangent = bumpverts[j].tangent; - } - - template - int genvbo(vector &idxs, int offset, vector &vverts, int *htdata, int htlen) - { - voffset = offset; - eoffset = idxs.length(); - minvert = 0xFFFF; - loopi(numtris) - { - tri &t = tris[i]; - loopj(3) - { - int index = t.vert[j]; - tcvert &tc = tcverts[index]; - vert &v = verts[index]; - T vv; - assignvert(vv, index, tc, v); - int htidx = hthash(v.pos)&(htlen-1); - loopk(htlen) - { - int &vidx = htdata[(htidx+k)&(htlen-1)]; - if(vidx < 0) { vidx = idxs.add(ushort(vverts.length())); vverts.add(vv); break; } - else if(!memcmp(&vverts[vidx], &vv, sizeof(vv))) { minvert = min(minvert, idxs.add(ushort(vidx))); break; } - } - } - } - minvert = min(minvert, ushort(voffset)); - maxvert = max(minvert, ushort(vverts.length()-1)); - elen = idxs.length()-eoffset; - return vverts.length()-voffset; - } - - int genvbo(vector &idxs, int offset) - { - voffset = offset; - eoffset = idxs.length(); - loopi(numtris) - { - tri &t = tris[i]; - loopj(3) idxs.add(voffset+t.vert[j]); - } - minvert = voffset; - maxvert = voffset + numverts-1; - elen = idxs.length()-eoffset; - return numverts; - } - - template - static inline void fillvert(T &vv, int j, tcvert &tc, vert &v) - { - vv.tc = tc.tc; - } - - template - void fillverts(T *vdata) - { - vdata += voffset; - loopi(numverts) fillvert(vdata[i], i, tcverts[i], verts[i]); - } - - void interpverts(const animstate &as, bool tangents, void * RESTRICT vdata, skin &s) - { - const vert * RESTRICT vert1 = &verts[as.cur.fr1 * numverts], - * RESTRICT vert2 = &verts[as.cur.fr2 * numverts], - * RESTRICT pvert1 = as.interp<1 ? &verts[as.prev.fr1 * numverts] : NULL, - * RESTRICT pvert2 = as.interp<1 ? &verts[as.prev.fr2 * numverts] : NULL; - #define ipvert(attrib) v.attrib.lerp(vert1[i].attrib, vert2[i].attrib, as.cur.t) - #define ipbvert(attrib, type) v.attrib.lerp(bvert1[i].attrib, bvert2[i].attrib, as.cur.t) - #define ipvertp(attrib) v.attrib.lerp(pvert1[i].attrib, pvert2[i].attrib, as.prev.t).lerp(vec().lerp(vert1[i].attrib, vert2[i].attrib, as.cur.t), as.interp) - #define ipbvertp(attrib, type) v.attrib.lerp(type().lerp(bpvert1[i].attrib, bpvert2[i].attrib, as.prev.t), type().lerp(bvert1[i].attrib, bvert2[i].attrib, as.cur.t), as.interp) - #define iploop(type, body) \ - loopi(numverts) \ - { \ - type &v = ((type * RESTRICT)vdata)[i]; \ - body; \ - } - if(tangents) - { - const bumpvert * RESTRICT bvert1 = &bumpverts[as.cur.fr1 * numverts], - * RESTRICT bvert2 = &bumpverts[as.cur.fr2 * numverts], - * RESTRICT bpvert1 = as.interp<1 ? &bumpverts[as.prev.fr1 * numverts] : NULL, - * RESTRICT bpvert2 = as.interp<1 ? &bumpverts[as.prev.fr2 * numverts] : NULL; - if(as.interp<1) iploop(vvertbump, { ipvertp(pos); ipbvertp(tangent, vec4); }) - else iploop(vvertbump, { ipvert(pos); ipbvert(tangent, vec4); }) - } - else - { - if(as.interp<1) iploop(vvertn, { ipvertp(pos); ipvertp(norm); }) - else iploop(vvertn, { ipvert(pos); ipvert(norm); }) - } - #undef iploop - #undef ipvert - #undef ipbvert - #undef ipvertp - #undef ipbvertp - } - - void render(const animstate *as, skin &s, vbocacheentry &vc) - { - if(!Shader::lastshader) return; - glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((vertmeshgroup *)group)->edata[eoffset]); - glde++; - xtravertsva += numverts; - } - }; - - struct tag - { - char *name; - matrix4x3 transform; - - tag() : name(NULL) {} - ~tag() { DELETEA(name); } - }; - - struct vertmeshgroup : meshgroup - { - int numframes; - tag *tags; - int numtags; - - static const int MAXVBOCACHE = 16; - vbocacheentry vbocache[MAXVBOCACHE]; - - ushort *edata; - GLuint ebuf; - bool vtangents; - int vlen, vertsize; - uchar *vdata; - - vertmeshgroup() : numframes(0), tags(NULL), numtags(0), edata(NULL), ebuf(0), vtangents(false), vlen(0), vertsize(0), vdata(NULL) - { - } - - virtual ~vertmeshgroup() - { - DELETEA(tags); - if(ebuf) glDeleteBuffers_(1, &ebuf); - loopi(MAXVBOCACHE) - { - if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf); - } - DELETEA(vdata); - } - - int findtag(const char *name) - { - loopi(numtags) if(!strcmp(tags[i].name, name)) return i; - return -1; - } - - int totalframes() const { return numframes; } - - void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) - { - n.mul(m, tags[numtags + i].transform); - n.posttranslate(m.transformnormal(p->translate), p->model->scale); - } - - void calctagmatrix(part *p, int i, const animstate &as, matrix4 &matrix) - { - const matrix4x3 &tag1 = tags[as.cur.fr1*numtags + i].transform, - &tag2 = tags[as.cur.fr2*numtags + i].transform; - matrix4x3 tag; - tag.lerp(tag1, tag2, as.cur.t); - if(as.interp<1) - { - const matrix4x3 &tag1p = tags[as.prev.fr1*numtags + i].transform, - &tag2p = tags[as.prev.fr2*numtags + i].transform; - matrix4x3 tagp; - tagp.lerp(tag1p, tag2p, as.prev.t); - tag.lerp(tagp, tag, as.interp); - } - tag.d.add(p->translate).mul(p->model->scale); - matrix = matrix4(tag); - } - - void genvbo(bool tangents, vbocacheentry &vc) - { - if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf); - if(ebuf) return; - - vector idxs; - - if(tangents) loopv(meshes) ((vertmesh *)meshes[i])->calctangents(); - - vtangents = tangents; - vertsize = tangents ? sizeof(vvertbump) : sizeof(vvertn); - vlen = 0; - if(numframes>1) - { - loopv(meshes) vlen += ((vertmesh *)meshes[i])->genvbo(idxs, vlen); - DELETEA(vdata); - vdata = new uchar[vlen*vertsize]; - #define FILLVDATA(type) do { \ - loopv(meshes) ((vertmesh *)meshes[i])->fillverts((type *)vdata); \ - } while(0) - if(tangents) FILLVDATA(vvertbump); - else FILLVDATA(vvertn); - #undef FILLVDATA - } - else - { - gle::bindvbo(vc.vbuf); - #define GENVBO(type) do { \ - vector vverts; \ - loopv(meshes) vlen += ((vertmesh *)meshes[i])->genvbo(idxs, vlen, vverts, htdata, htlen); \ - glBufferData_(GL_ARRAY_BUFFER, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW); \ - } while(0) - int numverts = 0, htlen = 128; - loopv(meshes) numverts += ((vertmesh *)meshes[i])->numverts; - while(htlen < numverts) htlen *= 2; - if(numverts*4 > htlen*3) htlen *= 2; - int *htdata = new int[htlen]; - memset(htdata, -1, htlen*sizeof(int)); - if(tangents) GENVBO(vvertbump); - else GENVBO(vvertn); - delete[] htdata; - #undef GENVBO - gle::clearvbo(); - } - - glGenBuffers_(1, &ebuf); - gle::bindebo(ebuf); - glBufferData_(GL_ELEMENT_ARRAY_BUFFER, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW); - gle::clearebo(); - } - - void bindvbo(const animstate *as, vbocacheentry &vc) - { - vvert *vverts = 0; - bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize); - if(as->cur.anim&ANIM_NOSKIN) - { - if(enabletc) disabletc(); - if(enablenormals) disablenormals(); - if(enabletangents) disabletangents(); - } - else - { - if(vtangents) - { - if(enablenormals) disablenormals(); - vvertbump *vvertbumps = 0; - bindtangents(&vvertbumps->tangent, vertsize); - } - else - { - if(enabletangents) disabletangents(); - vvertn *vvertns = 0; - bindnormals(&vvertns->norm, vertsize); - } - - bindtc(&vverts->tc, vertsize); - } - if(enablebones) disablebones(); - } - - void cleanup() - { - loopi(MAXVBOCACHE) - { - vbocacheentry &c = vbocache[i]; - if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; } - c.as.cur.fr1 = -1; - } - if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } - } - - void preload(part *p) - { - if(numframes > 1) return; - bool tangents = p->tangents(); - if(tangents!=vtangents) cleanup(); - if(!vbocache->vbuf) genvbo(tangents, *vbocache); - } - - void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) - { - if(as->cur.anim&ANIM_NORENDER) - { - loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix); - return; - } - - bool tangents = p->tangents(); - if(tangents!=vtangents) { cleanup(); disablevbo(); } - vbocacheentry *vc = NULL; - if(numframes<=1) vc = vbocache; - else - { - loopi(MAXVBOCACHE) - { - vbocacheentry &c = vbocache[i]; - if(!c.vbuf) continue; - if(c.as==*as) { vc = &c; break; } - } - if(!vc) loopi(MAXVBOCACHE) { vc = &vbocache[i]; if(!vc->vbuf || vc->millis < lastmillis) break; } - } - if(!vc->vbuf) genvbo(tangents, *vc); - if(numframes>1) - { - if(vc->as!=*as) - { - vc->as = *as; - vc->millis = lastmillis; - loopv(meshes) - { - vertmesh &m = *(vertmesh *)meshes[i]; - m.interpverts(*as, tangents, vdata + m.voffset*vertsize, p->skins[i]); - } - gle::bindvbo(vc->vbuf); - glBufferData_(GL_ARRAY_BUFFER, vlen*vertsize, vdata, GL_STREAM_DRAW); - } - vc->millis = lastmillis; - } - - bindvbo(as, *vc); - loopv(meshes) - { - vertmesh *m = (vertmesh *)meshes[i]; - p->skins[i].bind(m, as); - m->render(as, p->skins[i], *vc); - } - - loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix); - } - }; - - vertmodel(const char *name) : animmodel(name) - { - } + struct vert { vec pos, norm; }; + struct vvert { vec pos; vec2 tc; }; + struct vvertn : vvert { vec norm; }; + struct vvertbump : vvert { squat tangent; }; + struct tcvert { vec2 tc; }; + struct bumpvert { vec4 tangent; }; + struct tri { ushort vert[3]; }; + + struct vbocacheentry + { + GLuint vbuf; + animstate as; + int millis; + + vbocacheentry() : vbuf(0) { as.cur.fr1 = as.prev.fr1 = -1; } + }; + + struct vertmesh : mesh + { + vert *verts; + tcvert *tcverts; + bumpvert *bumpverts; + tri *tris; + int numverts, numtris; + + int voffset, eoffset, elen; + ushort minvert, maxvert; + + vertmesh() : verts(0), tcverts(0), bumpverts(0), tris(0) + { + } + + virtual ~vertmesh() + { + DELETEA(verts); + DELETEA(tcverts); + DELETEA(bumpverts); + DELETEA(tris); + } + + void smoothnorms(float limit = 0, bool areaweight = true) + { + if(((vertmeshgroup *)group)->numframes == 1) mesh::smoothnorms(verts, numverts, tris, numtris, limit, areaweight); + else buildnorms(areaweight); + } + + void buildnorms(bool areaweight = true) + { + mesh::buildnorms(verts, numverts, tris, numtris, areaweight, ((vertmeshgroup *)group)->numframes); + } + + void calctangents(bool areaweight = true) + { + if(bumpverts) return; + bumpverts = new bumpvert[((vertmeshgroup *)group)->numframes*numverts]; + mesh::calctangents(bumpverts, verts, tcverts, numverts, tris, numtris, areaweight, ((vertmeshgroup *)group)->numframes); + } + + void calcbb(vec &bbmin, vec &bbmax, const matrix4x3 &m) + { + loopj(numverts) + { + vec v = m.transform(verts[j].pos); + loopi(3) + { + bbmin[i] = min(bbmin[i], v[i]); + bbmax[i] = max(bbmax[i], v[i]); + } + } + } + + void genBIH(BIH::mesh &m) + { + m.tris = (const BIH::tri *)tris; + m.numtris = numtris; + m.pos = (const uchar *)&verts->pos; + m.posstride = sizeof(vert); + m.tc = (const uchar *)&tcverts->tc; + m.tcstride = sizeof(tcvert); + } + + static inline void assignvert(vvertn &vv, int j, tcvert &tc, vert &v) + { + vv.pos = v.pos; + vv.norm = v.norm; + vv.tc = tc.tc; + } + + inline void assignvert(vvertbump &vv, int j, tcvert &tc, vert &v) + { + vv.pos = v.pos; + vv.tc = tc.tc; + vv.tangent = bumpverts[j].tangent; + } + + template + int genvbo(vector &idxs, int offset, vector &vverts, int *htdata, int htlen) + { + voffset = offset; + eoffset = idxs.length(); + minvert = 0xFFFF; + loopi(numtris) + { + tri &t = tris[i]; + loopj(3) + { + int index = t.vert[j]; + tcvert &tc = tcverts[index]; + vert &v = verts[index]; + T vv; + assignvert(vv, index, tc, v); + int htidx = hthash(v.pos)&(htlen-1); + loopk(htlen) + { + int &vidx = htdata[(htidx+k)&(htlen-1)]; + if(vidx < 0) { vidx = idxs.add(ushort(vverts.length())); vverts.add(vv); break; } + else if(!memcmp(&vverts[vidx], &vv, sizeof(vv))) { minvert = min(minvert, idxs.add(ushort(vidx))); break; } + } + } + } + minvert = min(minvert, ushort(voffset)); + maxvert = max(minvert, ushort(vverts.length()-1)); + elen = idxs.length()-eoffset; + return vverts.length()-voffset; + } + + int genvbo(vector &idxs, int offset) + { + voffset = offset; + eoffset = idxs.length(); + loopi(numtris) + { + tri &t = tris[i]; + loopj(3) idxs.add(voffset+t.vert[j]); + } + minvert = voffset; + maxvert = voffset + numverts-1; + elen = idxs.length()-eoffset; + return numverts; + } + + template + static inline void fillvert(T &vv, int j, tcvert &tc, vert &v) + { + vv.tc = tc.tc; + } + + template + void fillverts(T *vdata) + { + vdata += voffset; + loopi(numverts) fillvert(vdata[i], i, tcverts[i], verts[i]); + } + + void interpverts(const animstate &as, bool tangents, void * RESTRICT vdata, skin &s) + { + const vert * RESTRICT vert1 = &verts[as.cur.fr1 * numverts], + * RESTRICT vert2 = &verts[as.cur.fr2 * numverts], + * RESTRICT pvert1 = as.interp<1 ? &verts[as.prev.fr1 * numverts] : NULL, + * RESTRICT pvert2 = as.interp<1 ? &verts[as.prev.fr2 * numverts] : NULL; + #define ipvert(attrib) v.attrib.lerp(vert1[i].attrib, vert2[i].attrib, as.cur.t) + #define ipbvert(attrib, type) v.attrib.lerp(bvert1[i].attrib, bvert2[i].attrib, as.cur.t) + #define ipvertp(attrib) v.attrib.lerp(pvert1[i].attrib, pvert2[i].attrib, as.prev.t).lerp(vec().lerp(vert1[i].attrib, vert2[i].attrib, as.cur.t), as.interp) + #define ipbvertp(attrib, type) v.attrib.lerp(type().lerp(bpvert1[i].attrib, bpvert2[i].attrib, as.prev.t), type().lerp(bvert1[i].attrib, bvert2[i].attrib, as.cur.t), as.interp) + #define iploop(type, body) \ + loopi(numverts) \ + { \ + type &v = ((type * RESTRICT)vdata)[i]; \ + body; \ + } + if(tangents) + { + const bumpvert * RESTRICT bvert1 = &bumpverts[as.cur.fr1 * numverts], + * RESTRICT bvert2 = &bumpverts[as.cur.fr2 * numverts], + * RESTRICT bpvert1 = as.interp<1 ? &bumpverts[as.prev.fr1 * numverts] : NULL, + * RESTRICT bpvert2 = as.interp<1 ? &bumpverts[as.prev.fr2 * numverts] : NULL; + if(as.interp<1) iploop(vvertbump, { ipvertp(pos); ipbvertp(tangent, vec4); }) + else iploop(vvertbump, { ipvert(pos); ipbvert(tangent, vec4); }) + } + else + { + if(as.interp<1) iploop(vvertn, { ipvertp(pos); ipvertp(norm); }) + else iploop(vvertn, { ipvert(pos); ipvert(norm); }) + } + #undef iploop + #undef ipvert + #undef ipbvert + #undef ipvertp + #undef ipbvertp + } + + void render(const animstate *as, skin &s, vbocacheentry &vc) + { + if(!Shader::lastshader) return; + glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((vertmeshgroup *)group)->edata[eoffset]); + glde++; + xtravertsva += numverts; + } + }; + + struct tag + { + char *name; + matrix4x3 transform; + + tag() : name(NULL) {} + ~tag() { DELETEA(name); } + }; + + struct vertmeshgroup : meshgroup + { + int numframes; + tag *tags; + int numtags; + + static const int MAXVBOCACHE = 16; + vbocacheentry vbocache[MAXVBOCACHE]; + + ushort *edata; + GLuint ebuf; + bool vtangents; + int vlen, vertsize; + uchar *vdata; + + vertmeshgroup() : numframes(0), tags(NULL), numtags(0), edata(NULL), ebuf(0), vtangents(false), vlen(0), vertsize(0), vdata(NULL) + { + } + + virtual ~vertmeshgroup() + { + DELETEA(tags); + if(ebuf) glDeleteBuffers_(1, &ebuf); + loopi(MAXVBOCACHE) + { + if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf); + } + DELETEA(vdata); + } + + int findtag(const char *name) + { + loopi(numtags) if(!strcmp(tags[i].name, name)) return i; + return -1; + } + + int totalframes() const { return numframes; } + + void concattagtransform(part *p, int i, const matrix4x3 &m, matrix4x3 &n) + { + n.mul(m, tags[numtags + i].transform); + n.posttranslate(m.transformnormal(p->translate), p->model->scale); + } + + void calctagmatrix(part *p, int i, const animstate &as, matrix4 &matrix) + { + const matrix4x3 &tag1 = tags[as.cur.fr1*numtags + i].transform, + &tag2 = tags[as.cur.fr2*numtags + i].transform; + matrix4x3 tag; + tag.lerp(tag1, tag2, as.cur.t); + if(as.interp<1) + { + const matrix4x3 &tag1p = tags[as.prev.fr1*numtags + i].transform, + &tag2p = tags[as.prev.fr2*numtags + i].transform; + matrix4x3 tagp; + tagp.lerp(tag1p, tag2p, as.prev.t); + tag.lerp(tagp, tag, as.interp); + } + tag.d.add(p->translate).mul(p->model->scale); + matrix = matrix4(tag); + } + + void genvbo(bool tangents, vbocacheentry &vc) + { + if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf); + if(ebuf) return; + + vector idxs; + + if(tangents) loopv(meshes) ((vertmesh *)meshes[i])->calctangents(); + + vtangents = tangents; + vertsize = tangents ? sizeof(vvertbump) : sizeof(vvertn); + vlen = 0; + if(numframes>1) + { + loopv(meshes) vlen += ((vertmesh *)meshes[i])->genvbo(idxs, vlen); + DELETEA(vdata); + vdata = new uchar[vlen*vertsize]; + #define FILLVDATA(type) do { \ + loopv(meshes) ((vertmesh *)meshes[i])->fillverts((type *)vdata); \ + } while(0) + if(tangents) FILLVDATA(vvertbump); + else FILLVDATA(vvertn); + #undef FILLVDATA + } + else + { + gle::bindvbo(vc.vbuf); + #define GENVBO(type) do { \ + vector vverts; \ + loopv(meshes) vlen += ((vertmesh *)meshes[i])->genvbo(idxs, vlen, vverts, htdata, htlen); \ + glBufferData_(GL_ARRAY_BUFFER, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW); \ + } while(0) + int numverts = 0, htlen = 128; + loopv(meshes) numverts += ((vertmesh *)meshes[i])->numverts; + while(htlen < numverts) htlen *= 2; + if(numverts*4 > htlen*3) htlen *= 2; + int *htdata = new int[htlen]; + memset(htdata, -1, htlen*sizeof(int)); + if(tangents) GENVBO(vvertbump); + else GENVBO(vvertn); + delete[] htdata; + #undef GENVBO + gle::clearvbo(); + } + + glGenBuffers_(1, &ebuf); + gle::bindebo(ebuf); + glBufferData_(GL_ELEMENT_ARRAY_BUFFER, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW); + gle::clearebo(); + } + + void bindvbo(const animstate *as, vbocacheentry &vc) + { + vvert *vverts = 0; + bindpos(ebuf, vc.vbuf, &vverts->pos, vertsize); + if(as->cur.anim&ANIM_NOSKIN) + { + if(enabletc) disabletc(); + if(enablenormals) disablenormals(); + if(enabletangents) disabletangents(); + } + else + { + if(vtangents) + { + if(enablenormals) disablenormals(); + vvertbump *vvertbumps = 0; + bindtangents(&vvertbumps->tangent, vertsize); + } + else + { + if(enabletangents) disabletangents(); + vvertn *vvertns = 0; + bindnormals(&vvertns->norm, vertsize); + } + + bindtc(&vverts->tc, vertsize); + } + if(enablebones) disablebones(); + } + + void cleanup() + { + loopi(MAXVBOCACHE) + { + vbocacheentry &c = vbocache[i]; + if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; } + c.as.cur.fr1 = -1; + } + if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } + } + + void preload(part *p) + { + if(numframes > 1) return; + bool tangents = p->tangents(); + if(tangents!=vtangents) cleanup(); + if(!vbocache->vbuf) genvbo(tangents, *vbocache); + } + + void render(const animstate *as, float pitch, const vec &axis, const vec &forward, dynent *d, part *p) + { + if(as->cur.anim&ANIM_NORENDER) + { + loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix); + return; + } + + bool tangents = p->tangents(); + if(tangents!=vtangents) { cleanup(); disablevbo(); } + vbocacheentry *vc = NULL; + if(numframes<=1) vc = vbocache; + else + { + loopi(MAXVBOCACHE) + { + vbocacheentry &c = vbocache[i]; + if(!c.vbuf) continue; + if(c.as==*as) { vc = &c; break; } + } + if(!vc) loopi(MAXVBOCACHE) { vc = &vbocache[i]; if(!vc->vbuf || vc->millis < lastmillis) break; } + } + if(!vc->vbuf) genvbo(tangents, *vc); + if(numframes>1) + { + if(vc->as!=*as) + { + vc->as = *as; + vc->millis = lastmillis; + loopv(meshes) + { + vertmesh &m = *(vertmesh *)meshes[i]; + m.interpverts(*as, tangents, vdata + m.voffset*vertsize, p->skins[i]); + } + gle::bindvbo(vc->vbuf); + glBufferData_(GL_ARRAY_BUFFER, vlen*vertsize, vdata, GL_STREAM_DRAW); + } + vc->millis = lastmillis; + } + + bindvbo(as, *vc); + loopv(meshes) + { + vertmesh *m = (vertmesh *)meshes[i]; + p->skins[i].bind(m, as); + m->render(as, p->skins[i], *vc); + } + + loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix); + } + }; + + vertmodel(const char *name) : animmodel(name) + { + } }; template struct vertloader : modelloader { - vertloader(const char *name) : modelloader(name) {} + vertloader(const char *name) : modelloader(name) {} }; template struct vertcommands : modelcommands { - typedef struct MDL::part part; - typedef struct MDL::skin skin; - - static void loadpart(char *model, float *smooth) - { - if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - defformatstring(filename, "%s/%s", MDL::dir, model); - part &mdl = MDL::loading->addpart(); - if(mdl.index) mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; - mdl.meshes = MDL::loading->sharemeshes(path(filename), double(*smooth > 0 ? cos(clamp(*smooth, 0.0f, 180.0f)*RAD) : 2)); - if(!mdl.meshes) conoutf(CON_ERROR, "could not load %s", filename); - else mdl.initskins(); - } - - static void setpitch(float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - part &mdl = *MDL::loading->parts.last(); - - mdl.pitchscale = *pitchscale; - mdl.pitchoffset = *pitchoffset; - if(*pitchmin || *pitchmax) - { - mdl.pitchmin = *pitchmin; - mdl.pitchmax = *pitchmax; - } - else - { - mdl.pitchmin = -360*fabs(mdl.pitchscale) + mdl.pitchoffset; - mdl.pitchmax = 360*fabs(mdl.pitchscale) + mdl.pitchoffset; - } - } - - static void setanim(char *anim, int *frame, int *range, float *speed, int *priority) - { - if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } - vector anims; - findanims(anim, anims); - if(anims.empty()) conoutf(CON_ERROR, "could not find animation %s", anim); - else loopv(anims) - { - MDL::loading->parts.last()->setanim(0, anims[i], *frame, *range, *speed, *priority); - } - } - - vertcommands() - { - if(MDL::multiparted()) this->modelcommand(loadpart, "load", "sf"); - this->modelcommand(setpitch, "pitch", "ffff"); - if(MDL::animated()) this->modelcommand(setanim, "anim", "siiff"); - } + typedef struct MDL::part part; + typedef struct MDL::skin skin; + + static void loadpart(char *model, float *smooth) + { + if(!MDL::loading) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + defformatstring(filename, "%s/%s", MDL::dir, model); + part &mdl = MDL::loading->addpart(); + if(mdl.index) mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0; + mdl.meshes = MDL::loading->sharemeshes(path(filename), double(*smooth > 0 ? cos(clamp(*smooth, 0.0f, 180.0f)*RAD) : 2)); + if(!mdl.meshes) conoutf(CON_ERROR, "could not load %s", filename); + else mdl.initskins(); + } + + static void setpitch(float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + part &mdl = *MDL::loading->parts.last(); + + mdl.pitchscale = *pitchscale; + mdl.pitchoffset = *pitchoffset; + if(*pitchmin || *pitchmax) + { + mdl.pitchmin = *pitchmin; + mdl.pitchmax = *pitchmax; + } + else + { + mdl.pitchmin = -360*fabs(mdl.pitchscale) + mdl.pitchoffset; + mdl.pitchmax = 360*fabs(mdl.pitchscale) + mdl.pitchoffset; + } + } + + static void setanim(char *anim, int *frame, int *range, float *speed, int *priority) + { + if(!MDL::loading || MDL::loading->parts.empty()) { conoutf(CON_ERROR, "not loading an %s", MDL::formatname()); return; } + vector anims; + findanims(anim, anims); + if(anims.empty()) conoutf(CON_ERROR, "could not find animation %s", anim); + else loopv(anims) + { + MDL::loading->parts.last()->setanim(0, anims[i], *frame, *range, *speed, *priority); + } + } + + vertcommands() + { + if(MDL::multiparted()) this->modelcommand(loadpart, "load", "sf"); + this->modelcommand(setpitch, "pitch", "ffff"); + if(MDL::animated()) this->modelcommand(setanim, "anim", "siiff"); + } }; diff --git a/src/engine/water.cpp b/src/engine/water.cpp index ff184cc..06c6e36 100644 --- a/src/engine/water.cpp +++ b/src/engine/water.cpp @@ -14,236 +14,236 @@ static float whoffset, whphase; static inline float vertwangle(int v1, int v2) { - static const float whscale = 59.0f/23.0f/(2*M_PI); - v1 &= wsize-1; - v2 &= wsize-1; - return v1*v2*whscale+whoffset; + static const float whscale = 59.0f/23.0f/(2*M_PI); + v1 &= wsize-1; + v2 &= wsize-1; + return v1*v2*whscale+whoffset; } static inline float vertwphase(float angle) { - float s = angle - int(angle) - 0.5f; - s *= 8 - fabs(s)*16; - return WATER_AMPLITUDE*s-WATER_OFFSET; + float s = angle - int(angle) - 0.5f; + s *= 8 - fabs(s)*16; + return WATER_AMPLITUDE*s-WATER_OFFSET; } static inline void vertw(int v1, int v2, int v3) { - float h = vertwphase(vertwangle(v1, v2)); - gle::attribf(v1, v2, v3+h); + float h = vertwphase(vertwangle(v1, v2)); + gle::attribf(v1, v2, v3+h); } static inline void vertwq(float v1, float v2, float v3) { - gle::attribf(v1, v2, v3+whphase); + gle::attribf(v1, v2, v3+whphase); } static inline void vertwn(float v1, float v2, float v3) { - float h = -WATER_OFFSET; - gle::attribf(v1, v2, v3+h); + float h = -WATER_OFFSET; + gle::attribf(v1, v2, v3+h); } struct waterstrip { - int x1, y1, x2, y2, z; - ushort size, subdiv; - - int numverts() const { return 2*((y2-y1)/subdiv + 1)*((x2-x1)/subdiv); } - - void save() - { - x1 = wx1; - y1 = wy1; - x2 = wx2; - y2 = wy2; - z = wz; - size = wsize; - subdiv = wsubdiv; - } - - void restore() - { - wx1 = x1; - wy1 = y1; - wx2 = x2; - wy2 = y2; - wz = z; - wsize = size; - wsubdiv = subdiv; - } + int x1, y1, x2, y2, z; + ushort size, subdiv; + + int numverts() const { return 2*((y2-y1)/subdiv + 1)*((x2-x1)/subdiv); } + + void save() + { + x1 = wx1; + y1 = wy1; + x2 = wx2; + y2 = wy2; + z = wz; + size = wsize; + subdiv = wsubdiv; + } + + void restore() + { + wx1 = x1; + wy1 = y1; + wx2 = x2; + wy2 = y2; + wz = z; + wsize = size; + wsubdiv = subdiv; + } }; vector waterstrips; void flushwaterstrips() { - if(gle::attribbuf.length()) xtraverts += gle::end(); - gle::defvertex(); - int numverts = 0; - loopv(waterstrips) numverts += waterstrips[i].numverts(); - gle::begin(GL_TRIANGLE_STRIP, numverts); - loopv(waterstrips) - { - waterstrips[i].restore(); - for(int x = wx1; x < wx2; x += wsubdiv) - { - for(int y = wy1; y <= wy2; y += wsubdiv) - { - vertw(x, y, wz); - vertw(x+wsubdiv, y, wz); - } - x += wsubdiv; - if(x >= wx2) break; - for(int y = wy2; y >= wy1; y -= wsubdiv) - { - vertw(x, y, wz); - vertw(x+wsubdiv, y, wz); - } - } - gle::multidraw(); - } - waterstrips.setsize(0); - wsize = 0; - xtraverts += gle::end(); + if(gle::attribbuf.length()) xtraverts += gle::end(); + gle::defvertex(); + int numverts = 0; + loopv(waterstrips) numverts += waterstrips[i].numverts(); + gle::begin(GL_TRIANGLE_STRIP, numverts); + loopv(waterstrips) + { + waterstrips[i].restore(); + for(int x = wx1; x < wx2; x += wsubdiv) + { + for(int y = wy1; y <= wy2; y += wsubdiv) + { + vertw(x, y, wz); + vertw(x+wsubdiv, y, wz); + } + x += wsubdiv; + if(x >= wx2) break; + for(int y = wy2; y >= wy1; y -= wsubdiv) + { + vertw(x, y, wz); + vertw(x+wsubdiv, y, wz); + } + } + gle::multidraw(); + } + waterstrips.setsize(0); + wsize = 0; + xtraverts += gle::end(); } void flushwater(int mat = MAT_WATER, bool force = true) { - if(wsize) - { - if(wsubdiv >= wsize) - { - if(gle::attribbuf.empty()) { gle::defvertex(); gle::begin(GL_QUADS); } - vertwq(wx1, wy1, wz); - vertwq(wx2, wy1, wz); - vertwq(wx2, wy2, wz); - vertwq(wx1, wy2, wz); - } - else waterstrips.add().save(); - wsize = 0; - } - - if(force) - { - if(gle::attribbuf.length()) xtraverts += gle::end(); - if(waterstrips.length()) flushwaterstrips(); - } + if(wsize) + { + if(wsubdiv >= wsize) + { + if(gle::attribbuf.empty()) { gle::defvertex(); gle::begin(GL_QUADS); } + vertwq(wx1, wy1, wz); + vertwq(wx2, wy1, wz); + vertwq(wx2, wy2, wz); + vertwq(wx1, wy2, wz); + } + else waterstrips.add().save(); + wsize = 0; + } + + if(force) + { + if(gle::attribbuf.length()) xtraverts += gle::end(); + if(waterstrips.length()) flushwaterstrips(); + } } void rendervertwater(int subdiv, int xo, int yo, int z, int size, int mat) { - if(wsize == size && wsubdiv == subdiv && wz == z) - { - if(wx2 == xo) - { - if(wy1 == yo && wy2 == yo + size) { wx2 += size; return; } - } - else if(wy2 == yo && wx1 == xo && wx2 == xo + size) { wy2 += size; return; } - } - - flushwater(mat, false); - - wx1 = xo; - wy1 = yo; - wx2 = xo + size, - wy2 = yo + size; - wz = z; - wsize = size; - wsubdiv = subdiv; - - ASSERT((wx1 & (subdiv - 1)) == 0); - ASSERT((wy1 & (subdiv - 1)) == 0); + if(wsize == size && wsubdiv == subdiv && wz == z) + { + if(wx2 == xo) + { + if(wy1 == yo && wy2 == yo + size) { wx2 += size; return; } + } + else if(wy2 == yo && wx1 == xo && wx2 == xo + size) { wy2 += size; return; } + } + + flushwater(mat, false); + + wx1 = xo; + wy1 = yo; + wx2 = xo + size, + wy2 = yo + size; + wz = z; + wsize = size; + wsubdiv = subdiv; + + ASSERT((wx1 & (subdiv - 1)) == 0); + ASSERT((wy1 & (subdiv - 1)) == 0); } int calcwatersubdiv(int x, int y, int z, int size) { - float dist; - if(camera1->o.x >= x && camera1->o.x < x + size && - camera1->o.y >= y && camera1->o.y < y + size) - dist = fabs(camera1->o.z - float(z)); - else - dist = vec(x + size/2, y + size/2, z + size/2).dist(camera1->o) - size*1.42f/2; - int subdiv = watersubdiv + int(dist) / (32 << waterlod); - return subdiv >= 31 ? INT_MAX : 1<o.x >= x && camera1->o.x < x + size && + camera1->o.y >= y && camera1->o.y < y + size) + dist = fabs(camera1->o.z - float(z)); + else + dist = vec(x + size/2, y + size/2, z + size/2).dist(camera1->o) - size*1.42f/2; + int subdiv = watersubdiv + int(dist) / (32 << waterlod); + return subdiv >= 31 ? INT_MAX : 1<= size) - { - if(subdiv < size * 2) rendervertwater(size, x, y, z, size, mat); - return subdiv; - } - int childsize = size / 2, - subdiv1 = renderwaterlod(x, y, z, childsize, mat), - subdiv2 = renderwaterlod(x + childsize, y, z, childsize, mat), - subdiv3 = renderwaterlod(x + childsize, y + childsize, z, childsize, mat), - subdiv4 = renderwaterlod(x, y + childsize, z, childsize, mat), - minsubdiv = subdiv1; - minsubdiv = min(minsubdiv, subdiv2); - minsubdiv = min(minsubdiv, subdiv3); - minsubdiv = min(minsubdiv, subdiv4); - if(minsubdiv < size * 2) - { - if(minsubdiv >= size) rendervertwater(size, x, y, z, size, mat); - else - { - if(subdiv1 >= size) rendervertwater(childsize, x, y, z, childsize, mat); - if(subdiv2 >= size) rendervertwater(childsize, x + childsize, y, z, childsize, mat); - if(subdiv3 >= size) rendervertwater(childsize, x + childsize, y + childsize, z, childsize, mat); - if(subdiv4 >= size) rendervertwater(childsize, x, y + childsize, z, childsize, mat); - } - } - return minsubdiv; - } + if(size <= (32 << waterlod)) + { + int subdiv = calcwatersubdiv(x, y, z, size); + if(subdiv < size * 2) rendervertwater(min(subdiv, size), x, y, z, size, mat); + return subdiv; + } + else + { + int subdiv = calcwatersubdiv(x, y, z, size); + if(subdiv >= size) + { + if(subdiv < size * 2) rendervertwater(size, x, y, z, size, mat); + return subdiv; + } + int childsize = size / 2, + subdiv1 = renderwaterlod(x, y, z, childsize, mat), + subdiv2 = renderwaterlod(x + childsize, y, z, childsize, mat), + subdiv3 = renderwaterlod(x + childsize, y + childsize, z, childsize, mat), + subdiv4 = renderwaterlod(x, y + childsize, z, childsize, mat), + minsubdiv = subdiv1; + minsubdiv = min(minsubdiv, subdiv2); + minsubdiv = min(minsubdiv, subdiv3); + minsubdiv = min(minsubdiv, subdiv4); + if(minsubdiv < size * 2) + { + if(minsubdiv >= size) rendervertwater(size, x, y, z, size, mat); + else + { + if(subdiv1 >= size) rendervertwater(childsize, x, y, z, childsize, mat); + if(subdiv2 >= size) rendervertwater(childsize, x + childsize, y, z, childsize, mat); + if(subdiv3 >= size) rendervertwater(childsize, x + childsize, y + childsize, z, childsize, mat); + if(subdiv4 >= size) rendervertwater(childsize, x, y + childsize, z, childsize, mat); + } + } + return minsubdiv; + } } void renderflatwater(int x, int y, int z, int rsize, int csize, int mat) { - if(gle::attribbuf.empty()) { gle::defvertex(); gle::begin(GL_QUADS); } - vertwn(x, y, z); - vertwn(x+rsize, y, z); - vertwn(x+rsize, y+csize, z); - vertwn(x, y+csize, z); + if(gle::attribbuf.empty()) { gle::defvertex(); gle::begin(GL_QUADS); } + vertwn(x, y, z); + vertwn(x+rsize, y, z); + vertwn(x+rsize, y+csize, z); + vertwn(x, y+csize, z); } VARFP(vertwater, 0, 1, 1, allchanged()); static inline void renderwater(const materialsurface &m, int mat = MAT_WATER) { - if(!vertwater || drawtex == DRAWTEX_MINIMAP) renderflatwater(m.o.x, m.o.y, m.o.z, m.rsize, m.csize, mat); - else if(renderwaterlod(m.o.x, m.o.y, m.o.z, m.csize, mat) >= int(m.csize) * 2) - rendervertwater(m.csize, m.o.x, m.o.y, m.o.z, m.csize, mat); + if(!vertwater || drawtex == DRAWTEX_MINIMAP) renderflatwater(m.o.x, m.o.y, m.o.z, m.rsize, m.csize, mat); + else if(renderwaterlod(m.o.x, m.o.y, m.o.z, m.csize, mat) >= int(m.csize) * 2) + rendervertwater(m.csize, m.o.x, m.o.y, m.o.z, m.csize, mat); } void setuplava(Texture *tex, float scale) { - float xk = TEX_SCALE/(tex->xs*scale); - float yk = TEX_SCALE/(tex->ys*scale); - float scroll = lastmillis/1000.0f; - LOCALPARAMF(lavatexgen, xk, yk, scroll, scroll); - gle::normal(vec(0, 0, 1)); - whoffset = fmod(float(lastmillis/2000.0f/(2*M_PI)), 1.0f); - whphase = vertwphase(whoffset); + float xk = TEX_SCALE/(tex->xs*scale); + float yk = TEX_SCALE/(tex->ys*scale); + float scroll = lastmillis/1000.0f; + LOCALPARAMF(lavatexgen, xk, yk, scroll, scroll); + gle::normal(vec(0, 0, 1)); + whoffset = fmod(float(lastmillis/2000.0f/(2*M_PI)), 1.0f); + whphase = vertwphase(whoffset); } void renderlava(const materialsurface &m) { - renderwater(m, MAT_LAVA); + renderwater(m, MAT_LAVA); } void flushlava() { - flushwater(MAT_LAVA); + flushwater(MAT_LAVA); } /* reflective/refractive water */ @@ -252,32 +252,32 @@ void flushlava() struct Reflection { - GLuint tex, refracttex; - int material, height, depth, age; - bool init; - matrix4 projmat; - occludequery *query, *prevquery; - vector matsurfs; - - Reflection() : tex(0), refracttex(0), material(-1), height(-1), depth(0), age(0), init(false), query(NULL), prevquery(NULL) - {} + GLuint tex, refracttex; + int material, height, depth, age; + bool init; + matrix4 projmat; + occludequery *query, *prevquery; + vector matsurfs; + + Reflection() : tex(0), refracttex(0), material(-1), height(-1), depth(0), age(0), init(false), query(NULL), prevquery(NULL) + {} }; VARP(reflectdist, 0, 2000, 10000); #define WATERVARS(name) \ - bvec name##color(0x14, 0x46, 0x50), name##fallcolor(0, 0, 0); \ - HVARFR(name##colour, 0, 0x144650, 0xFFFFFF, \ - { \ - if(!name##colour) name##colour = 0x144650; \ - name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \ - }); \ - VARR(name##fog, 0, 150, 10000); \ - VARR(name##spec, 0, 150, 1000); \ - HVARFR(name##fallcolour, 0, 0, 0xFFFFFF, \ - { \ - name##fallcolor = bvec((name##fallcolour>>16)&0xFF, (name##fallcolour>>8)&0xFF, name##fallcolour&0xFF); \ - }); + bvec name##color(0x14, 0x46, 0x50), name##fallcolor(0, 0, 0); \ + HVARFR(name##colour, 0, 0x144650, 0xFFFFFF, \ + { \ + if(!name##colour) name##colour = 0x144650; \ + name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \ + }); \ + VARR(name##fog, 0, 150, 10000); \ + VARR(name##spec, 0, 150, 1000); \ + HVARFR(name##fallcolour, 0, 0, 0xFFFFFF, \ + { \ + name##fallcolor = bvec((name##fallcolour>>16)&0xFF, (name##fallcolour>>8)&0xFF, name##fallcolour&0xFF); \ + }); WATERVARS(water) WATERVARS(water2) @@ -292,13 +292,13 @@ GETMATIDXVAR(water, fog, int) GETMATIDXVAR(water, spec, int) #define LAVAVARS(name) \ - bvec name##color(0xFF, 0x40, 0x00); \ - HVARFR(name##colour, 0, 0xFF4000, 0xFFFFFF, \ - { \ - if(!name##colour) name##colour = 0xFF4000; \ - name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \ - }); \ - VARR(name##fog, 0, 50, 10000); + bvec name##color(0xFF, 0x40, 0x00); \ + HVARFR(name##colour, 0, 0xFF4000, 0xFFFFFF, \ + { \ + if(!name##colour) name##colour = 0xFF4000; \ + name##color = bvec((name##colour>>16)&0xFF, (name##colour>>8)&0xFF, name##colour&0xFF); \ + }); \ + VARR(name##fog, 0, 50, 10000); LAVAVARS(lava) LAVAVARS(lava2) @@ -311,13 +311,13 @@ GETMATIDXVAR(lava, fog, int) void setprojtexmatrix(Reflection &ref) { - if(ref.init) - { - ref.init = false; - (ref.projmat = camprojmatrix).projective(); - } + if(ref.init) + { + ref.init = false; + (ref.projmat = camprojmatrix).projective(); + } - LOCALPARAM(watermatrix, ref.projmat); + LOCALPARAM(watermatrix, ref.projmat); } Reflection reflections[MAXREFLECTIONS]; @@ -331,379 +331,379 @@ VARFP(waterfade, 0, 1, 1, { cleanreflections(); preloadwatershaders(); }); void preloadwatershaders(bool force) { - static bool needwater = false; - if(force) needwater = true; - if(!needwater) return; + static bool needwater = false; + if(force) needwater = true; + if(!needwater) return; - useshaderbyname("waterglare"); + useshaderbyname("waterglare"); - if(waterenvmap && !waterreflect) - useshaderbyname(waterrefract ? (waterfade ? "waterenvfade" : "waterenvrefract") : "waterenv"); - else useshaderbyname(waterrefract ? (waterfade ? "waterfade" : "waterrefract") : (waterreflect ? "waterreflect" : "water")); + if(waterenvmap && !waterreflect) + useshaderbyname(waterrefract ? (waterfade ? "waterenvfade" : "waterenvrefract") : "waterenv"); + else useshaderbyname(waterrefract ? (waterfade ? "waterfade" : "waterrefract") : (waterreflect ? "waterreflect" : "water")); - useshaderbyname(waterrefract ? (waterfade ? "underwaterfade" : "underwaterrefract") : "underwater"); + useshaderbyname(waterrefract ? (waterfade ? "underwaterfade" : "underwaterrefract") : "underwater"); - extern int waterfallenv; - useshaderbyname(waterfallenv ? "waterfallenv" : "waterfall"); - if(waterfallrefract) useshaderbyname(waterfallenv ? "waterfallenvrefract" : "waterfallrefract"); + extern int waterfallenv; + useshaderbyname(waterfallenv ? "waterfallenv" : "waterfall"); + if(waterfallrefract) useshaderbyname(waterfallenv ? "waterfallenvrefract" : "waterfallrefract"); } void renderwater() { - if(editmode && showmat && !drawtex) return; - if(!rplanes) return; - - glDisable(GL_CULL_FACE); - - if(!glaring && drawtex != DRAWTEX_MINIMAP) - { - if(waterrefract) - { - if(waterfade) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - } - else - { - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_SRC_ALPHA); - } - } - - GLOBALPARAM(camera, camera1->o); - GLOBALPARAMF(millis, lastmillis/1000.0f); - - #define SETWATERSHADER(which, name) \ - do { \ - static Shader *name##shader = NULL; \ - if(!name##shader) name##shader = lookupshaderbyname(#name); \ - which##shader = name##shader; \ - } while(0) - - Shader *aboveshader = NULL; - if(glaring) SETWATERSHADER(above, waterglare); - else if(drawtex == DRAWTEX_MINIMAP) aboveshader = notextureshader; - else if(waterenvmap && !waterreflect) - { - if(waterrefract) - { - if(waterfade) SETWATERSHADER(above, waterenvfade); - else SETWATERSHADER(above, waterenvrefract); - } - else SETWATERSHADER(above, waterenv); - } - else if(waterrefract) - { - if(waterfade) SETWATERSHADER(above, waterfade); - else SETWATERSHADER(above, waterrefract); - } - else if(waterreflect) SETWATERSHADER(above, waterreflect); - else SETWATERSHADER(above, water); - - Shader *belowshader = NULL; - if(!glaring && drawtex != DRAWTEX_MINIMAP) - { - if(waterrefract) - { - if(waterfade) SETWATERSHADER(below, underwaterfade); - else SETWATERSHADER(below, underwaterrefract); - } - else SETWATERSHADER(below, underwater); - } - - vec ambient(max(skylightcolor[0], ambientcolor[0]), max(skylightcolor[1], ambientcolor[1]), max(skylightcolor[2], ambientcolor[2])); - float offset = -WATER_OFFSET; - loopi(MAXREFLECTIONS) - { - Reflection &ref = reflections[i]; - if(ref.height<0 || ref.age || ref.matsurfs.empty()) continue; - if(!glaring && oqfrags && oqwater && ref.query && ref.query->owner==&ref) - { - if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery)) - { - if(checkquery(ref.query)) continue; - } - } - - bool below = camera1->o.z < ref.height+offset; - if(below) - { - if(!belowshader) continue; - belowshader->set(); - } - else aboveshader->set(); - - if(!glaring && drawtex != DRAWTEX_MINIMAP) - { - if(waterreflect || waterrefract) - { - if(waterreflect || !waterenvmap) glBindTexture(GL_TEXTURE_2D, waterreflect ? ref.tex : ref.refracttex); - setprojtexmatrix(ref); - } - - if(waterrefract) - { - glActiveTexture_(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_2D, ref.refracttex); - if(waterfade) - { - float fadeheight = ref.height+offset+(below ? -2 : 2); - LOCALPARAMF(waterheight, fadeheight); - } - } - } - - MSlot &mslot = lookupmaterialslot(ref.material); - glActiveTexture_(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, mslot.sts.inrange(2) ? mslot.sts[2].t->id : notexture->id); - glActiveTexture_(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, mslot.sts.inrange(3) ? mslot.sts[3].t->id : notexture->id); - glActiveTexture_(GL_TEXTURE0); - if(!glaring && waterenvmap && !waterreflect && drawtex != DRAWTEX_MINIMAP) - { - glBindTexture(GL_TEXTURE_CUBE_MAP, lookupenvmap(mslot)); - } - - whoffset = fmod(float(lastmillis/600.0f/(2*M_PI)), 1.0f); - whphase = vertwphase(whoffset); - - gle::color(getwatercolor(ref.material)); - int wfog = getwaterfog(ref.material), wspec = getwaterspec(ref.material); - - const entity *lastlight = (const entity *)-1; - int lastdepth = -1; - loopvj(ref.matsurfs) - { - materialsurface &m = *ref.matsurfs[j]; - - entity *light = (m.light && m.light->type==ET_LIGHT ? m.light : NULL); - if(light!=lastlight) - { - flushwater(); - vec lightpos = light ? light->o : vec(worldsize/2, worldsize/2, worldsize); - float lightrad = light && light->attr1 ? light->attr1 : worldsize*8.0f; - vec lightcol = (light ? vec(light->attr2, light->attr3, light->attr4) : vec(ambient)).div(255.0f).mul(wspec/100.0f); - LOCALPARAM(lightpos, lightpos); - LOCALPARAM(lightcolor, lightcol); - LOCALPARAMF(lightradius, lightrad); - lastlight = light; - } - - if(!glaring && !waterrefract && m.depth!=lastdepth) - { - flushwater(); - float depth = !wfog ? 1.0f : min(0.75f*m.depth/wfog, 0.95f); - depth = max(depth, !below && (waterreflect || waterenvmap) ? 0.3f : 0.6f); - LOCALPARAMF(depth, depth, 1.0f-depth); - lastdepth = m.depth; - } - - renderwater(m); - } - flushwater(); - } - - if(!glaring && drawtex != DRAWTEX_MINIMAP) - { - if(waterrefract) - { - if(waterfade) glDisable(GL_BLEND); - } - else - { - glDepthMask(GL_TRUE); - glDisable(GL_BLEND); - } - } - - glEnable(GL_CULL_FACE); + if(editmode && showmat && !drawtex) return; + if(!rplanes) return; + + glDisable(GL_CULL_FACE); + + if(!glaring && drawtex != DRAWTEX_MINIMAP) + { + if(waterrefract) + { + if(waterfade) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + } + else + { + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_SRC_ALPHA); + } + } + + GLOBALPARAM(camera, camera1->o); + GLOBALPARAMF(millis, lastmillis/1000.0f); + + #define SETWATERSHADER(which, name) \ + do { \ + static Shader *name##shader = NULL; \ + if(!name##shader) name##shader = lookupshaderbyname(#name); \ + which##shader = name##shader; \ + } while(0) + + Shader *aboveshader = NULL; + if(glaring) SETWATERSHADER(above, waterglare); + else if(drawtex == DRAWTEX_MINIMAP) aboveshader = notextureshader; + else if(waterenvmap && !waterreflect) + { + if(waterrefract) + { + if(waterfade) SETWATERSHADER(above, waterenvfade); + else SETWATERSHADER(above, waterenvrefract); + } + else SETWATERSHADER(above, waterenv); + } + else if(waterrefract) + { + if(waterfade) SETWATERSHADER(above, waterfade); + else SETWATERSHADER(above, waterrefract); + } + else if(waterreflect) SETWATERSHADER(above, waterreflect); + else SETWATERSHADER(above, water); + + Shader *belowshader = NULL; + if(!glaring && drawtex != DRAWTEX_MINIMAP) + { + if(waterrefract) + { + if(waterfade) SETWATERSHADER(below, underwaterfade); + else SETWATERSHADER(below, underwaterrefract); + } + else SETWATERSHADER(below, underwater); + } + + vec ambient(max(skylightcolor[0], ambientcolor[0]), max(skylightcolor[1], ambientcolor[1]), max(skylightcolor[2], ambientcolor[2])); + float offset = -WATER_OFFSET; + loopi(MAXREFLECTIONS) + { + Reflection &ref = reflections[i]; + if(ref.height<0 || ref.age || ref.matsurfs.empty()) continue; + if(!glaring && oqfrags && oqwater && ref.query && ref.query->owner==&ref) + { + if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery)) + { + if(checkquery(ref.query)) continue; + } + } + + bool below = camera1->o.z < ref.height+offset; + if(below) + { + if(!belowshader) continue; + belowshader->set(); + } + else aboveshader->set(); + + if(!glaring && drawtex != DRAWTEX_MINIMAP) + { + if(waterreflect || waterrefract) + { + if(waterreflect || !waterenvmap) glBindTexture(GL_TEXTURE_2D, waterreflect ? ref.tex : ref.refracttex); + setprojtexmatrix(ref); + } + + if(waterrefract) + { + glActiveTexture_(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, ref.refracttex); + if(waterfade) + { + float fadeheight = ref.height+offset+(below ? -2 : 2); + LOCALPARAMF(waterheight, fadeheight); + } + } + } + + MSlot &mslot = lookupmaterialslot(ref.material); + glActiveTexture_(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, mslot.sts.inrange(2) ? mslot.sts[2].t->id : notexture->id); + glActiveTexture_(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, mslot.sts.inrange(3) ? mslot.sts[3].t->id : notexture->id); + glActiveTexture_(GL_TEXTURE0); + if(!glaring && waterenvmap && !waterreflect && drawtex != DRAWTEX_MINIMAP) + { + glBindTexture(GL_TEXTURE_CUBE_MAP, lookupenvmap(mslot)); + } + + whoffset = fmod(float(lastmillis/600.0f/(2*M_PI)), 1.0f); + whphase = vertwphase(whoffset); + + gle::color(getwatercolor(ref.material)); + int wfog = getwaterfog(ref.material), wspec = getwaterspec(ref.material); + + const entity *lastlight = (const entity *)-1; + int lastdepth = -1; + loopvj(ref.matsurfs) + { + materialsurface &m = *ref.matsurfs[j]; + + entity *light = (m.light && m.light->type==ET_LIGHT ? m.light : NULL); + if(light!=lastlight) + { + flushwater(); + vec lightpos = light ? light->o : vec(worldsize/2, worldsize/2, worldsize); + float lightrad = light && light->attr1 ? light->attr1 : worldsize*8.0f; + vec lightcol = (light ? vec(light->attr2, light->attr3, light->attr4) : vec(ambient)).div(255.0f).mul(wspec/100.0f); + LOCALPARAM(lightpos, lightpos); + LOCALPARAM(lightcolor, lightcol); + LOCALPARAMF(lightradius, lightrad); + lastlight = light; + } + + if(!glaring && !waterrefract && m.depth!=lastdepth) + { + flushwater(); + float depth = !wfog ? 1.0f : min(0.75f*m.depth/wfog, 0.95f); + depth = max(depth, !below && (waterreflect || waterenvmap) ? 0.3f : 0.6f); + LOCALPARAMF(depth, depth, 1.0f-depth); + lastdepth = m.depth; + } + + renderwater(m); + } + flushwater(); + } + + if(!glaring && drawtex != DRAWTEX_MINIMAP) + { + if(waterrefract) + { + if(waterfade) glDisable(GL_BLEND); + } + else + { + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + } + } + + glEnable(GL_CULL_FACE); } void setupwaterfallrefract() { - glBindTexture(GL_TEXTURE_2D, waterfallrefraction.refracttex ? waterfallrefraction.refracttex : notexture->id); - setprojtexmatrix(waterfallrefraction); + glBindTexture(GL_TEXTURE_2D, waterfallrefraction.refracttex ? waterfallrefraction.refracttex : notexture->id); + setprojtexmatrix(waterfallrefraction); } void cleanreflection(Reflection &ref) { - ref.material = -1; - ref.height = -1; - ref.init = false; - ref.query = ref.prevquery = NULL; - ref.matsurfs.setsize(0); - if(ref.tex) - { - glDeleteTextures(1, &ref.tex); - ref.tex = 0; - } - if(ref.refracttex) - { - glDeleteTextures(1, &ref.refracttex); - ref.refracttex = 0; - } + ref.material = -1; + ref.height = -1; + ref.init = false; + ref.query = ref.prevquery = NULL; + ref.matsurfs.setsize(0); + if(ref.tex) + { + glDeleteTextures(1, &ref.tex); + ref.tex = 0; + } + if(ref.refracttex) + { + glDeleteTextures(1, &ref.refracttex); + ref.refracttex = 0; + } } void cleanreflections() { - loopi(MAXREFLECTIONS) cleanreflection(reflections[i]); - cleanreflection(waterfallrefraction); - if(reflectionfb) - { - glDeleteFramebuffers_(1, &reflectionfb); - reflectionfb = 0; - } - if(reflectiondb) - { - glDeleteRenderbuffers_(1, &reflectiondb); - reflectiondb = 0; - } + loopi(MAXREFLECTIONS) cleanreflection(reflections[i]); + cleanreflection(waterfallrefraction); + if(reflectionfb) + { + glDeleteFramebuffers_(1, &reflectionfb); + reflectionfb = 0; + } + if(reflectiondb) + { + glDeleteRenderbuffers_(1, &reflectiondb); + reflectiondb = 0; + } } VARFP(reflectsize, 6, 8, 11, cleanreflections()); void genwatertex(GLuint &tex, GLuint &fb, GLuint &db, bool refract = false) { - static const GLenum colorfmts[] = { GL_RGBA, GL_RGBA8, GL_RGB, GL_RGB8, GL_FALSE }, - depthfmts[] = { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT32, GL_FALSE }; - static GLenum reflectfmt = GL_FALSE, refractfmt = GL_FALSE, depthfmt = GL_FALSE; - static bool usingalpha = false; - bool needsalpha = refract && waterrefract && waterfade; - if(refract && usingalpha!=needsalpha) - { - usingalpha = needsalpha; - refractfmt = GL_FALSE; - } - int size = 1<hwtexsize) size /= 2; - - glGenTextures(1, &tex); - char *buf = new char[size*size*4]; - memset(buf, 0, size*size*4); - - GLenum &colorfmt = refract ? refractfmt : reflectfmt; - if(colorfmt && fb && db) - { - createtexture(tex, size, size, buf, 3, 1, colorfmt); - delete[] buf; - return; - } - - if(!fb) glGenFramebuffers_(1, &fb); - int find = needsalpha ? 0 : 2; - do - { - createtexture(tex, size, size, buf, 3, 1, colorfmt ? colorfmt : colorfmts[find]); - glBindFramebuffer_(GL_FRAMEBUFFER, fb); - glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); - if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; - } - while(!colorfmt && colorfmts[++find]); - if(!colorfmt) colorfmt = colorfmts[find]; - - delete[] buf; - - if(!db) { glGenRenderbuffers_(1, &db); depthfmt = GL_FALSE; } - if(!depthfmt) glBindRenderbuffer_(GL_RENDERBUFFER, db); - find = 0; - do - { - if(!depthfmt) glRenderbufferStorage_(GL_RENDERBUFFER, depthfmts[find], size, size); - glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, db); - if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; - } - while(!depthfmt && depthfmts[++find]); - if(!depthfmt) - { - glBindRenderbuffer_(GL_RENDERBUFFER, 0); - depthfmt = depthfmts[find]; - } - - glBindFramebuffer_(GL_FRAMEBUFFER, 0); + static const GLenum colorfmts[] = { GL_RGBA, GL_RGBA8, GL_RGB, GL_RGB8, GL_FALSE }, + depthfmts[] = { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT32, GL_FALSE }; + static GLenum reflectfmt = GL_FALSE, refractfmt = GL_FALSE, depthfmt = GL_FALSE; + static bool usingalpha = false; + bool needsalpha = refract && waterrefract && waterfade; + if(refract && usingalpha!=needsalpha) + { + usingalpha = needsalpha; + refractfmt = GL_FALSE; + } + int size = 1<hwtexsize) size /= 2; + + glGenTextures(1, &tex); + char *buf = new char[size*size*4]; + memset(buf, 0, size*size*4); + + GLenum &colorfmt = refract ? refractfmt : reflectfmt; + if(colorfmt && fb && db) + { + createtexture(tex, size, size, buf, 3, 1, colorfmt); + delete[] buf; + return; + } + + if(!fb) glGenFramebuffers_(1, &fb); + int find = needsalpha ? 0 : 2; + do + { + createtexture(tex, size, size, buf, 3, 1, colorfmt ? colorfmt : colorfmts[find]); + glBindFramebuffer_(GL_FRAMEBUFFER, fb); + glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; + } + while(!colorfmt && colorfmts[++find]); + if(!colorfmt) colorfmt = colorfmts[find]; + + delete[] buf; + + if(!db) { glGenRenderbuffers_(1, &db); depthfmt = GL_FALSE; } + if(!depthfmt) glBindRenderbuffer_(GL_RENDERBUFFER, db); + find = 0; + do + { + if(!depthfmt) glRenderbufferStorage_(GL_RENDERBUFFER, depthfmts[find], size, size); + glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, db); + if(glCheckFramebufferStatus_(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE) break; + } + while(!depthfmt && depthfmts[++find]); + if(!depthfmt) + { + glBindRenderbuffer_(GL_RENDERBUFFER, 0); + depthfmt = depthfmts[find]; + } + + glBindFramebuffer_(GL_FRAMEBUFFER, 0); } void addwaterfallrefraction(materialsurface &m) { - Reflection &ref = waterfallrefraction; - if(ref.age>=0) - { - ref.age = -1; - ref.init = false; - ref.matsurfs.setsize(0); - ref.material = MAT_WATER; - ref.height = INT_MAX; - } - ref.matsurfs.add(&m); - - if(!ref.refracttex) genwatertex(ref.refracttex, reflectionfb, reflectiondb); + Reflection &ref = waterfallrefraction; + if(ref.age>=0) + { + ref.age = -1; + ref.init = false; + ref.matsurfs.setsize(0); + ref.material = MAT_WATER; + ref.height = INT_MAX; + } + ref.matsurfs.add(&m); + + if(!ref.refracttex) genwatertex(ref.refracttex, reflectionfb, reflectiondb); } void addreflection(materialsurface &m) { - int mat = m.material, height = m.o.z; - Reflection *ref = NULL, *oldest = NULL; - loopi(MAXREFLECTIONS) - { - Reflection &r = reflections[i]; - if(r.height<0) - { - if(!ref) ref = &r; - } - else if(r.height==height && r.material==mat) - { - r.matsurfs.add(&m); - r.depth = max(r.depth, int(m.depth)); - if(r.age<0) return; - ref = &r; - break; - } - else if(!oldest || r.age>oldest->age) oldest = &r; - } - if(!ref) - { - if(!oldest || oldest->age<0) return; - ref = oldest; - } - if(ref->height!=height || ref->material!=mat) - { - ref->material = mat; - ref->height = height; - ref->prevquery = NULL; - } - rplanes++; - ref->age = -1; - ref->init = false; - ref->matsurfs.setsize(0); - ref->matsurfs.add(&m); - ref->depth = m.depth; - if(drawtex == DRAWTEX_MINIMAP) return; - - if(waterreflect && !ref->tex) genwatertex(ref->tex, reflectionfb, reflectiondb); - if(waterrefract && !ref->refracttex) genwatertex(ref->refracttex, reflectionfb, reflectiondb, true); + int mat = m.material, height = m.o.z; + Reflection *ref = NULL, *oldest = NULL; + loopi(MAXREFLECTIONS) + { + Reflection &r = reflections[i]; + if(r.height<0) + { + if(!ref) ref = &r; + } + else if(r.height==height && r.material==mat) + { + r.matsurfs.add(&m); + r.depth = max(r.depth, int(m.depth)); + if(r.age<0) return; + ref = &r; + break; + } + else if(!oldest || r.age>oldest->age) oldest = &r; + } + if(!ref) + { + if(!oldest || oldest->age<0) return; + ref = oldest; + } + if(ref->height!=height || ref->material!=mat) + { + ref->material = mat; + ref->height = height; + ref->prevquery = NULL; + } + rplanes++; + ref->age = -1; + ref->init = false; + ref->matsurfs.setsize(0); + ref->matsurfs.add(&m); + ref->depth = m.depth; + if(drawtex == DRAWTEX_MINIMAP) return; + + if(waterreflect && !ref->tex) genwatertex(ref->tex, reflectionfb, reflectiondb); + if(waterrefract && !ref->refracttex) genwatertex(ref->refracttex, reflectionfb, reflectiondb, true); } static void drawmaterialquery(const materialsurface &m, float offset, float border = 0, float reflect = -1) { - if(gle::attribbuf.empty()) - { - gle::defvertex(); - gle::begin(GL_QUADS); - } - float x = m.o.x, y = m.o.y, z = m.o.z, csize = m.csize + border, rsize = m.rsize + border; - if(reflect >= 0) z = 2*reflect - z; - switch(m.orient) - { + if(gle::attribbuf.empty()) + { + gle::defvertex(); + gle::begin(GL_QUADS); + } + float x = m.o.x, y = m.o.y, z = m.o.z, csize = m.csize + border, rsize = m.rsize + border; + if(reflect >= 0) z = 2*reflect - z; + switch(m.orient) + { #define GENFACEORIENT(orient, v0, v1, v2, v3) \ - case orient: v0 v1 v2 v3 break; + case orient: v0 v1 v2 v3 break; #define GENFACEVERT(orient, vert, mx,my,mz, sx,sy,sz) \ - gle::attribf(mx sx, my sy, mz sz); - GENFACEVERTS(x, x, y, y, z, z, - border, + csize, - border, + rsize, + offset, - offset) + gle::attribf(mx sx, my sy, mz sz); + GENFACEVERTS(x, x, y, y, z, z, - border, + csize, - border, + rsize, + offset, - offset) #undef GENFACEORIENT #undef GENFACEVERT - } + } } extern void drawreflection(float z, bool refract, int fogdepth = -1, const bvec &col = bvec(0, 0, 0)); @@ -712,96 +712,96 @@ int rplanes = 0; void queryreflection(Reflection &ref, bool init) { - if(init) - { - nocolorshader->set(); - glDepthMask(GL_FALSE); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glDisable(GL_CULL_FACE); - } - startquery(ref.query); - loopvj(ref.matsurfs) - { - materialsurface &m = *ref.matsurfs[j]; - float offset = 0.1f; - if(m.orient==O_TOP) - { - offset = WATER_OFFSET + - (vertwater ? WATER_AMPLITUDE*(camera1->pitch > 0 || m.depth < WATER_AMPLITUDE+0.5f ? -1 : 1) : 0); - if(fabs(m.o.z-offset - camera1->o.z) < 0.5f && m.depth > WATER_AMPLITUDE+1.5f) - offset += camera1->pitch > 0 ? -1 : 1; - } - drawmaterialquery(m, offset); - } - xtraverts += gle::end(); - endquery(ref.query); + if(init) + { + nocolorshader->set(); + glDepthMask(GL_FALSE); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glDisable(GL_CULL_FACE); + } + startquery(ref.query); + loopvj(ref.matsurfs) + { + materialsurface &m = *ref.matsurfs[j]; + float offset = 0.1f; + if(m.orient==O_TOP) + { + offset = WATER_OFFSET + + (vertwater ? WATER_AMPLITUDE*(camera1->pitch > 0 || m.depth < WATER_AMPLITUDE+0.5f ? -1 : 1) : 0); + if(fabs(m.o.z-offset - camera1->o.z) < 0.5f && m.depth > WATER_AMPLITUDE+1.5f) + offset += camera1->pitch > 0 ? -1 : 1; + } + drawmaterialquery(m, offset); + } + xtraverts += gle::end(); + endquery(ref.query); } void queryreflections() { - rplanes = 0; - - static int lastsize = 0; - int size = 1<hwtexsize) size /= 2; - if(size!=lastsize) { if(lastsize) cleanreflections(); lastsize = size; } - - for(vtxarray *va = visibleva; va; va = va->next) - { - if(!va->matsurfs || va->occluded >= OCCLUDE_BB || va->curvfc >= VFC_FOGGED) continue; - int lastmat = -1; - loopi(va->matsurfs) - { - materialsurface &m = va->matbuf[i]; - if(m.material != lastmat) - { - if((m.material&MATF_VOLUME) != MAT_WATER || m.orient == O_BOTTOM) { i += m.skip; continue; } - if(m.orient != O_TOP) - { - if(!waterfallrefract || !getwaterfog(m.material)) { i += m.skip; continue; } - } - lastmat = m.material; - } - if(m.orient==O_TOP) addreflection(m); - else addwaterfallrefraction(m); - } - } - - loopi(MAXREFLECTIONS) - { - Reflection &ref = reflections[i]; - ++ref.age; - } - if(waterfallrefract) - { - Reflection &ref = waterfallrefraction; - ++ref.age; - } - - if((editmode && showmat && !drawtex) || !oqfrags || !oqwater || drawtex == DRAWTEX_MINIMAP) return; - - int refs = 0; - if(waterreflect || waterrefract) loopi(MAXREFLECTIONS) - { - Reflection &ref = reflections[i]; - ref.prevquery = oqwater > 1 ? ref.query : NULL; - ref.query = ref.height>=0 && !ref.age && ref.matsurfs.length() ? newquery(&ref) : NULL; - if(ref.query) queryreflection(ref, !refs++); - } - if(waterfallrefract) - { - Reflection &ref = waterfallrefraction; - ref.prevquery = oqwater > 1 ? ref.query : NULL; - ref.query = ref.height>=0 && !ref.age && ref.matsurfs.length() ? newquery(&ref) : NULL; - if(ref.query) queryreflection(ref, !refs++); - } - - if(refs) - { - glDepthMask(GL_TRUE); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glEnable(GL_CULL_FACE); - } + rplanes = 0; + + static int lastsize = 0; + int size = 1<hwtexsize) size /= 2; + if(size!=lastsize) { if(lastsize) cleanreflections(); lastsize = size; } + + for(vtxarray *va = visibleva; va; va = va->next) + { + if(!va->matsurfs || va->occluded >= OCCLUDE_BB || va->curvfc >= VFC_FOGGED) continue; + int lastmat = -1; + loopi(va->matsurfs) + { + materialsurface &m = va->matbuf[i]; + if(m.material != lastmat) + { + if((m.material&MATF_VOLUME) != MAT_WATER || m.orient == O_BOTTOM) { i += m.skip; continue; } + if(m.orient != O_TOP) + { + if(!waterfallrefract || !getwaterfog(m.material)) { i += m.skip; continue; } + } + lastmat = m.material; + } + if(m.orient==O_TOP) addreflection(m); + else addwaterfallrefraction(m); + } + } + + loopi(MAXREFLECTIONS) + { + Reflection &ref = reflections[i]; + ++ref.age; + } + if(waterfallrefract) + { + Reflection &ref = waterfallrefraction; + ++ref.age; + } + + if((editmode && showmat && !drawtex) || !oqfrags || !oqwater || drawtex == DRAWTEX_MINIMAP) return; + + int refs = 0; + if(waterreflect || waterrefract) loopi(MAXREFLECTIONS) + { + Reflection &ref = reflections[i]; + ref.prevquery = oqwater > 1 ? ref.query : NULL; + ref.query = ref.height>=0 && !ref.age && ref.matsurfs.length() ? newquery(&ref) : NULL; + if(ref.query) queryreflection(ref, !refs++); + } + if(waterfallrefract) + { + Reflection &ref = waterfallrefraction; + ref.prevquery = oqwater > 1 ? ref.query : NULL; + ref.query = ref.height>=0 && !ref.age && ref.matsurfs.length() ? newquery(&ref) : NULL; + if(ref.query) queryreflection(ref, !refs++); + } + + if(refs) + { + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glEnable(GL_CULL_FACE); + } glFlush(); } @@ -817,41 +817,41 @@ VAR(maskreflect, 0, 2, 16); void maskreflection(Reflection &ref, float offset, bool reflect, bool clear = false) { - const bvec &wcol = getwatercolor(ref.material); - vec color = wcol.tocolor(); - if(!maskreflect) - { - if(clear) glClearColor(color.r, color.g, color.b, 1); - glClear(GL_DEPTH_BUFFER_BIT | (clear ? GL_COLOR_BUFFER_BIT : 0)); - return; - } - glClearDepth(0); - glClear(GL_DEPTH_BUFFER_BIT); - glClearDepth(1); - glDepthRange(1, 1); - glDepthFunc(GL_ALWAYS); - glDisable(GL_CULL_FACE); - if(clear) - { - notextureshader->set(); - gle::color(color); - } - else - { - nocolorshader->set(); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - } - float reflectheight = reflect ? ref.height + offset : -1; - loopv(ref.matsurfs) - { - materialsurface &m = *ref.matsurfs[i]; - drawmaterialquery(m, -offset, maskreflect, reflectheight); - } - xtraverts += gle::end(); - if(!clear) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glEnable(GL_CULL_FACE); - glDepthFunc(GL_LESS); - glDepthRange(0, 1); + const bvec &wcol = getwatercolor(ref.material); + vec color = wcol.tocolor(); + if(!maskreflect) + { + if(clear) glClearColor(color.r, color.g, color.b, 1); + glClear(GL_DEPTH_BUFFER_BIT | (clear ? GL_COLOR_BUFFER_BIT : 0)); + return; + } + glClearDepth(0); + glClear(GL_DEPTH_BUFFER_BIT); + glClearDepth(1); + glDepthRange(1, 1); + glDepthFunc(GL_ALWAYS); + glDisable(GL_CULL_FACE); + if(clear) + { + notextureshader->set(); + gle::color(color); + } + else + { + nocolorshader->set(); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + } + float reflectheight = reflect ? ref.height + offset : -1; + loopv(ref.matsurfs) + { + materialsurface &m = *ref.matsurfs[i]; + drawmaterialquery(m, -offset, maskreflect, reflectheight); + } + xtraverts += gle::end(); + if(!clear) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glEnable(GL_CULL_FACE); + glDepthFunc(GL_LESS); + glDepthRange(0, 1); } VAR(reflectscissor, 0, 1, 1); @@ -859,195 +859,195 @@ VAR(reflectvfc, 0, 1, 1); static bool calcscissorbox(Reflection &ref, int size, vec &clipmin, vec &clipmax, int &sx, int &sy, int &sw, int &sh) { - materialsurface &m0 = *ref.matsurfs[0]; - int dim = dimension(m0.orient), r = R[dim], c = C[dim]; - ivec bbmin = m0.o, bbmax = bbmin; - bbmax[r] += m0.rsize; - bbmax[c] += m0.csize; - loopvj(ref.matsurfs) - { - materialsurface &m = *ref.matsurfs[j]; - bbmin[r] = min(bbmin[r], m.o[r]); - bbmin[c] = min(bbmin[c], m.o[c]); - bbmax[r] = max(bbmax[r], m.o[r] + m.rsize); - bbmax[c] = max(bbmax[c], m.o[c] + m.csize); - bbmin[dim] = min(bbmin[dim], m.o[dim]); - bbmax[dim] = max(bbmax[dim], m.o[dim]); - } - - vec4 v[8]; - float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1; - loopi(8) - { - vec4 &p = v[i]; - camprojmatrix.transform(vec(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, (i&4 ? bbmax.z + WATER_AMPLITUDE : bbmin.z - WATER_AMPLITUDE) - WATER_OFFSET), p); - if(p.z >= -p.w) - { - float x = p.x / p.w, y = p.y / p.w; - sx1 = min(sx1, x); - sy1 = min(sy1, y); - sx2 = max(sx2, x); - sy2 = max(sy2, y); - } - } - if(sx1 >= sx2 || sy1 >= sy2) return false; - loopi(8) - { - const vec4 &p = v[i]; - if(p.z >= -p.w) continue; - loopj(3) - { - const vec4 &o = v[i^(1<= 1 && sy2 >= 1) return false; - sx1 = max(sx1, -1.0f); - sy1 = max(sy1, -1.0f); - sx2 = min(sx2, 1.0f); - sy2 = min(sy2, 1.0f); - if(reflectvfc) - { - clipmin.x = clamp(clipmin.x, sx1, sx2); - clipmin.y = clamp(clipmin.y, sy1, sy2); - clipmax.x = clamp(clipmax.x, sx1, sx2); - clipmax.y = clamp(clipmax.y, sy1, sy2); - } - sx = int(floor((sx1+1)*0.5f*size)); - sy = int(floor((sy1+1)*0.5f*size)); - sw = max(int(ceil((sx2+1)*0.5f*size)) - sx, 0); - sh = max(int(ceil((sy2+1)*0.5f*size)) - sy, 0); - return true; + materialsurface &m0 = *ref.matsurfs[0]; + int dim = dimension(m0.orient), r = R[dim], c = C[dim]; + ivec bbmin = m0.o, bbmax = bbmin; + bbmax[r] += m0.rsize; + bbmax[c] += m0.csize; + loopvj(ref.matsurfs) + { + materialsurface &m = *ref.matsurfs[j]; + bbmin[r] = min(bbmin[r], m.o[r]); + bbmin[c] = min(bbmin[c], m.o[c]); + bbmax[r] = max(bbmax[r], m.o[r] + m.rsize); + bbmax[c] = max(bbmax[c], m.o[c] + m.csize); + bbmin[dim] = min(bbmin[dim], m.o[dim]); + bbmax[dim] = max(bbmax[dim], m.o[dim]); + } + + vec4 v[8]; + float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1; + loopi(8) + { + vec4 &p = v[i]; + camprojmatrix.transform(vec(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, (i&4 ? bbmax.z + WATER_AMPLITUDE : bbmin.z - WATER_AMPLITUDE) - WATER_OFFSET), p); + if(p.z >= -p.w) + { + float x = p.x / p.w, y = p.y / p.w; + sx1 = min(sx1, x); + sy1 = min(sy1, y); + sx2 = max(sx2, x); + sy2 = max(sy2, y); + } + } + if(sx1 >= sx2 || sy1 >= sy2) return false; + loopi(8) + { + const vec4 &p = v[i]; + if(p.z >= -p.w) continue; + loopj(3) + { + const vec4 &o = v[i^(1<= 1 && sy2 >= 1) return false; + sx1 = max(sx1, -1.0f); + sy1 = max(sy1, -1.0f); + sx2 = min(sx2, 1.0f); + sy2 = min(sy2, 1.0f); + if(reflectvfc) + { + clipmin.x = clamp(clipmin.x, sx1, sx2); + clipmin.y = clamp(clipmin.y, sy1, sy2); + clipmax.x = clamp(clipmax.x, sx1, sx2); + clipmax.y = clamp(clipmax.y, sy1, sy2); + } + sx = int(floor((sx1+1)*0.5f*size)); + sy = int(floor((sy1+1)*0.5f*size)); + sw = max(int(ceil((sx2+1)*0.5f*size)) - sx, 0); + sh = max(int(ceil((sy2+1)*0.5f*size)) - sy, 0); + return true; } VARR(refractclear, 0, 0, 1); void drawreflections() { - if((editmode && showmat && !drawtex) || drawtex == DRAWTEX_MINIMAP) return; - - static int lastdrawn = 0; - int refs = 0, n = lastdrawn; - float offset = -WATER_OFFSET; - int size = 1<hwtexsize) size /= 2; - - if(waterreflect || waterrefract) loopi(MAXREFLECTIONS) - { - Reflection &ref = reflections[++n%MAXREFLECTIONS]; - if(ref.height<0 || ref.age || ref.matsurfs.empty()) continue; - if(oqfrags && oqwater && ref.query && ref.query->owner==&ref) - { - if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery)) - { - if(checkquery(ref.query)) continue; - } - } - - if(!refs) - { - glViewport(0, 0, size, size); - glBindFramebuffer_(GL_FRAMEBUFFER, reflectionfb); - } - refs++; - ref.init = true; - lastdrawn = n; - - vec clipmin(-1, -1, -1), clipmax(1, 1, 1); - int sx, sy, sw, sh; - bool scissor = reflectscissor && calcscissorbox(ref, size, clipmin, clipmax, sx, sy, sw, sh); - if(scissor) glScissor(sx, sy, sw, sh); - else - { - sx = sy = 0; - sw = sh = size; - } - - const bvec &wcol = getwatercolor(ref.material); - int wfog = getwaterfog(ref.material); - - if(waterreflect && ref.tex && camera1->o.z >= ref.height+offset) - { - glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.tex, 0); - if(scissor) glEnable(GL_SCISSOR_TEST); - maskreflection(ref, offset, true); - savevfcP(); - setvfcP(ref.height+offset, clipmin, clipmax); - drawreflection(ref.height+offset, false); - restorevfcP(); - if(scissor) glDisable(GL_SCISSOR_TEST); - } - - if(waterrefract && ref.refracttex) - { - glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.refracttex, 0); - if(scissor) glEnable(GL_SCISSOR_TEST); - maskreflection(ref, offset, false, refractclear || !wfog || (ref.depth>=10000 && camera1->o.z >= ref.height + offset)); - if(wfog || waterfade) - { - savevfcP(); - setvfcP(-1, clipmin, clipmax); - drawreflection(ref.height+offset, true, wfog, wcol); - restorevfcP(); - } - if(scissor) glDisable(GL_SCISSOR_TEST); - } - - if(refs>=maxreflect) break; - } - - if(waterfallrefract && waterfallrefraction.refracttex) - { - Reflection &ref = waterfallrefraction; - - if(ref.height<0 || ref.age || ref.matsurfs.empty()) goto nowaterfall; - if(oqfrags && oqwater && ref.query && ref.query->owner==&ref) - { - if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery)) - { - if(checkquery(ref.query)) goto nowaterfall; - } - } - - if(!refs) - { - glViewport(0, 0, size, size); - glBindFramebuffer_(GL_FRAMEBUFFER, reflectionfb); - } - refs++; - ref.init = true; - - vec clipmin(-1, -1, -1), clipmax(1, 1, 1); - int sx, sy, sw, sh; - bool scissor = reflectscissor && calcscissorbox(ref, size, clipmin, clipmax, sx, sy, sw, sh); - if(scissor) glScissor(sx, sy, sw, sh); - else - { - sx = sy = 0; - sw = sh = size; - } - - glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.refracttex, 0); - if(scissor) glEnable(GL_SCISSOR_TEST); - maskreflection(ref, -0.1f, false); - savevfcP(); - setvfcP(-1, clipmin, clipmax); - drawreflection(-1, true); - restorevfcP(); - if(scissor) glDisable(GL_SCISSOR_TEST); - } + if((editmode && showmat && !drawtex) || drawtex == DRAWTEX_MINIMAP) return; + + static int lastdrawn = 0; + int refs = 0, n = lastdrawn; + float offset = -WATER_OFFSET; + int size = 1<hwtexsize) size /= 2; + + if(waterreflect || waterrefract) loopi(MAXREFLECTIONS) + { + Reflection &ref = reflections[++n%MAXREFLECTIONS]; + if(ref.height<0 || ref.age || ref.matsurfs.empty()) continue; + if(oqfrags && oqwater && ref.query && ref.query->owner==&ref) + { + if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery)) + { + if(checkquery(ref.query)) continue; + } + } + + if(!refs) + { + glViewport(0, 0, size, size); + glBindFramebuffer_(GL_FRAMEBUFFER, reflectionfb); + } + refs++; + ref.init = true; + lastdrawn = n; + + vec clipmin(-1, -1, -1), clipmax(1, 1, 1); + int sx, sy, sw, sh; + bool scissor = reflectscissor && calcscissorbox(ref, size, clipmin, clipmax, sx, sy, sw, sh); + if(scissor) glScissor(sx, sy, sw, sh); + else + { + sx = sy = 0; + sw = sh = size; + } + + const bvec &wcol = getwatercolor(ref.material); + int wfog = getwaterfog(ref.material); + + if(waterreflect && ref.tex && camera1->o.z >= ref.height+offset) + { + glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.tex, 0); + if(scissor) glEnable(GL_SCISSOR_TEST); + maskreflection(ref, offset, true); + savevfcP(); + setvfcP(ref.height+offset, clipmin, clipmax); + drawreflection(ref.height+offset, false); + restorevfcP(); + if(scissor) glDisable(GL_SCISSOR_TEST); + } + + if(waterrefract && ref.refracttex) + { + glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.refracttex, 0); + if(scissor) glEnable(GL_SCISSOR_TEST); + maskreflection(ref, offset, false, refractclear || !wfog || (ref.depth>=10000 && camera1->o.z >= ref.height + offset)); + if(wfog || waterfade) + { + savevfcP(); + setvfcP(-1, clipmin, clipmax); + drawreflection(ref.height+offset, true, wfog, wcol); + restorevfcP(); + } + if(scissor) glDisable(GL_SCISSOR_TEST); + } + + if(refs>=maxreflect) break; + } + + if(waterfallrefract && waterfallrefraction.refracttex) + { + Reflection &ref = waterfallrefraction; + + if(ref.height<0 || ref.age || ref.matsurfs.empty()) goto nowaterfall; + if(oqfrags && oqwater && ref.query && ref.query->owner==&ref) + { + if(!ref.prevquery || ref.prevquery->owner!=&ref || checkquery(ref.prevquery)) + { + if(checkquery(ref.query)) goto nowaterfall; + } + } + + if(!refs) + { + glViewport(0, 0, size, size); + glBindFramebuffer_(GL_FRAMEBUFFER, reflectionfb); + } + refs++; + ref.init = true; + + vec clipmin(-1, -1, -1), clipmax(1, 1, 1); + int sx, sy, sw, sh; + bool scissor = reflectscissor && calcscissorbox(ref, size, clipmin, clipmax, sx, sy, sw, sh); + if(scissor) glScissor(sx, sy, sw, sh); + else + { + sx = sy = 0; + sw = sh = size; + } + + glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ref.refracttex, 0); + if(scissor) glEnable(GL_SCISSOR_TEST); + maskreflection(ref, -0.1f, false); + savevfcP(); + setvfcP(-1, clipmin, clipmax); + drawreflection(-1, true); + restorevfcP(); + if(scissor) glDisable(GL_SCISSOR_TEST); + } nowaterfall: - if(!refs) return; - glViewport(0, 0, screenw, screenh); - glBindFramebuffer_(GL_FRAMEBUFFER, 0); + if(!refs) return; + glViewport(0, 0, screenw, screenh); + glBindFramebuffer_(GL_FRAMEBUFFER, 0); } diff --git a/src/engine/world.cpp b/src/engine/world.cpp index 7f5baed..af998c3 100644 --- a/src/engine/world.cpp +++ b/src/engine/world.cpp @@ -12,261 +12,261 @@ VAR(entselradius, 0, 2, 10); static inline void mmboundbox(const entity &e, model *m, vec ¢er, vec &radius) { - m->boundbox(center, radius); - rotatebb(center, radius, e.attr1); + m->boundbox(center, radius); + rotatebb(center, radius, e.attr1); } static inline void mmcollisionbox(const entity &e, model *m, vec ¢er, vec &radius) { - m->collisionbox(center, radius); - rotatebb(center, radius, e.attr1); + m->collisionbox(center, radius); + rotatebb(center, radius, e.attr1); } bool getentboundingbox(const extentity &e, ivec &o, ivec &r) { - switch(e.type) - { - case ET_EMPTY: - return false; - case ET_MAPMODEL: - { - model *m = loadmapmodel(e.attr2); - if(m) - { - vec center, radius; - mmboundbox(e, m, center, radius); - center.add(e.o); - radius.max(entselradius); - o = ivec(vec(center).sub(radius)); - r = ivec(vec(center).add(radius).add(1)); - break; - } - } - [[fallthrough]]; - // invisible mapmodels use entselradius - default: - o = ivec(vec(e.o).sub(entselradius)); - r = ivec(vec(e.o).add(entselradius+1)); - break; - } - return true; + switch(e.type) + { + case ET_EMPTY: + return false; + case ET_MAPMODEL: + { + model *m = loadmapmodel(e.attr2); + if(m) + { + vec center, radius; + mmboundbox(e, m, center, radius); + center.add(e.o); + radius.max(entselradius); + o = ivec(vec(center).sub(radius)); + r = ivec(vec(center).add(radius).add(1)); + break; + } + } + [[fallthrough]]; + // invisible mapmodels use entselradius + default: + o = ivec(vec(e.o).sub(entselradius)); + r = ivec(vec(e.o).add(entselradius+1)); + break; + } + return true; } enum { - MODOE_ADD = 1<<0, - MODOE_UPDATEBB = 1<<1, - MODOE_LIGHTENT = 1<<2 + MODOE_ADD = 1<<0, + MODOE_UPDATEBB = 1<<1, + MODOE_LIGHTENT = 1<<2 }; void modifyoctaentity(int flags, int id, extentity &e, cube *c, const ivec &cor, int size, const ivec &bo, const ivec &br, int leafsize, vtxarray *lastva = NULL) { - loopoctabox(cor, size, bo, br) - { - ivec o(i, cor, size); - vtxarray *va = c[i].ext && c[i].ext->va ? c[i].ext->va : lastva; - if(c[i].children != NULL && size > leafsize) - modifyoctaentity(flags, id, e, c[i].children, o, size>>1, bo, br, leafsize, va); - else if(flags&MODOE_ADD) - { - if(!c[i].ext || !c[i].ext->ents) ext(c[i]).ents = new octaentities(o, size); - octaentities &oe = *c[i].ext->ents; - switch(e.type) - { - case ET_MAPMODEL: - if(loadmapmodel(e.attr2)) - { - if(va) - { - va->bbmin.x = -1; - if(oe.mapmodels.empty()) va->mapmodels.add(&oe); - } - oe.mapmodels.add(id); - oe.bbmin.min(bo).max(oe.o); - oe.bbmax.max(br).min(ivec(oe.o).add(oe.size)); - break; - } - // invisible mapmodel - [[fallthrough]]; - default: - oe.other.add(id); - break; - } - - } - else if(c[i].ext && c[i].ext->ents) - { - octaentities &oe = *c[i].ext->ents; - switch(e.type) - { - case ET_MAPMODEL: - if(loadmapmodel(e.attr2)) - { - oe.mapmodels.removeobj(id); - if(va) - { - va->bbmin.x = -1; - if(oe.mapmodels.empty()) va->mapmodels.removeobj(&oe); - } - oe.bbmin = oe.bbmax = oe.o; - oe.bbmin.add(oe.size); - loopvj(oe.mapmodels) - { - extentity &e = *entities::getents()[oe.mapmodels[j]]; - ivec eo, er; - if(getentboundingbox(e, eo, er)) - { - oe.bbmin.min(eo); - oe.bbmax.max(er); - } - } - oe.bbmin.max(oe.o); - oe.bbmax.min(ivec(oe.o).add(oe.size)); - break; - } - [[fallthrough]]; - // invisible mapmodel - default: - oe.other.removeobj(id); - break; - } - if(oe.mapmodels.empty() && oe.other.empty()) - freeoctaentities(c[i]); - } - if(c[i].ext && c[i].ext->ents) c[i].ext->ents->query = NULL; - if(va && va!=lastva) - { - if(lastva) - { - if(va->bbmin.x < 0) lastva->bbmin.x = -1; - } - else if(flags&MODOE_UPDATEBB) updatevabb(va); - } - } + loopoctabox(cor, size, bo, br) + { + ivec o(i, cor, size); + vtxarray *va = c[i].ext && c[i].ext->va ? c[i].ext->va : lastva; + if(c[i].children != NULL && size > leafsize) + modifyoctaentity(flags, id, e, c[i].children, o, size>>1, bo, br, leafsize, va); + else if(flags&MODOE_ADD) + { + if(!c[i].ext || !c[i].ext->ents) ext(c[i]).ents = new octaentities(o, size); + octaentities &oe = *c[i].ext->ents; + switch(e.type) + { + case ET_MAPMODEL: + if(loadmapmodel(e.attr2)) + { + if(va) + { + va->bbmin.x = -1; + if(oe.mapmodels.empty()) va->mapmodels.add(&oe); + } + oe.mapmodels.add(id); + oe.bbmin.min(bo).max(oe.o); + oe.bbmax.max(br).min(ivec(oe.o).add(oe.size)); + break; + } + // invisible mapmodel + [[fallthrough]]; + default: + oe.other.add(id); + break; + } + + } + else if(c[i].ext && c[i].ext->ents) + { + octaentities &oe = *c[i].ext->ents; + switch(e.type) + { + case ET_MAPMODEL: + if(loadmapmodel(e.attr2)) + { + oe.mapmodels.removeobj(id); + if(va) + { + va->bbmin.x = -1; + if(oe.mapmodels.empty()) va->mapmodels.removeobj(&oe); + } + oe.bbmin = oe.bbmax = oe.o; + oe.bbmin.add(oe.size); + loopvj(oe.mapmodels) + { + extentity &e = *entities::getents()[oe.mapmodels[j]]; + ivec eo, er; + if(getentboundingbox(e, eo, er)) + { + oe.bbmin.min(eo); + oe.bbmax.max(er); + } + } + oe.bbmin.max(oe.o); + oe.bbmax.min(ivec(oe.o).add(oe.size)); + break; + } + [[fallthrough]]; + // invisible mapmodel + default: + oe.other.removeobj(id); + break; + } + if(oe.mapmodels.empty() && oe.other.empty()) + freeoctaentities(c[i]); + } + if(c[i].ext && c[i].ext->ents) c[i].ext->ents->query = NULL; + if(va && va!=lastva) + { + if(lastva) + { + if(va->bbmin.x < 0) lastva->bbmin.x = -1; + } + else if(flags&MODOE_UPDATEBB) updatevabb(va); + } + } } vector outsideents; static bool modifyoctaent(int flags, int id, extentity &e) { - if(flags&MODOE_ADD ? e.flags&EF_OCTA : !(e.flags&EF_OCTA)) return false; - - ivec o, r; - if(!getentboundingbox(e, o, r)) return false; - - if(!insideworld(e.o)) - { - int idx = outsideents.find(id); - if(flags&MODOE_ADD) - { - if(idx < 0) outsideents.add(id); - } - else if(idx >= 0) outsideents.removeunordered(idx); - } - else - { - int leafsize = octaentsize, limit = max(r.x - o.x, max(r.y - o.y, r.z - o.z)); - while(leafsize < limit) leafsize *= 2; - int diff = ~(leafsize-1) & ((o.x^r.x)|(o.y^r.y)|(o.z^r.z)); - if(diff && (limit > octaentsize/2 || diff < leafsize*2)) leafsize *= 2; - modifyoctaentity(flags, id, e, worldroot, ivec(0, 0, 0), worldsize>>1, o, r, leafsize); - } - e.flags ^= EF_OCTA; - if(e.type == ET_LIGHT) clearlightcache(id); - else if(e.type == ET_PARTICLES) clearparticleemitters(); - else if(flags&MODOE_LIGHTENT) lightent(e); - return true; + if(flags&MODOE_ADD ? e.flags&EF_OCTA : !(e.flags&EF_OCTA)) return false; + + ivec o, r; + if(!getentboundingbox(e, o, r)) return false; + + if(!insideworld(e.o)) + { + int idx = outsideents.find(id); + if(flags&MODOE_ADD) + { + if(idx < 0) outsideents.add(id); + } + else if(idx >= 0) outsideents.removeunordered(idx); + } + else + { + int leafsize = octaentsize, limit = max(r.x - o.x, max(r.y - o.y, r.z - o.z)); + while(leafsize < limit) leafsize *= 2; + int diff = ~(leafsize-1) & ((o.x^r.x)|(o.y^r.y)|(o.z^r.z)); + if(diff && (limit > octaentsize/2 || diff < leafsize*2)) leafsize *= 2; + modifyoctaentity(flags, id, e, worldroot, ivec(0, 0, 0), worldsize>>1, o, r, leafsize); + } + e.flags ^= EF_OCTA; + if(e.type == ET_LIGHT) clearlightcache(id); + else if(e.type == ET_PARTICLES) clearparticleemitters(); + else if(flags&MODOE_LIGHTENT) lightent(e); + return true; } static inline bool modifyoctaent(int flags, int id) { - vector &ents = entities::getents(); - return ents.inrange(id) && modifyoctaent(flags, id, *ents[id]); + vector &ents = entities::getents(); + return ents.inrange(id) && modifyoctaent(flags, id, *ents[id]); } -static inline void addentity(int id) { modifyoctaent(MODOE_ADD|MODOE_UPDATEBB|MODOE_LIGHTENT, id); } +static inline void addentity(int id) { modifyoctaent(MODOE_ADD|MODOE_UPDATEBB|MODOE_LIGHTENT, id); } static inline void removeentity(int id) { modifyoctaent(MODOE_UPDATEBB, id); } void freeoctaentities(cube &c) { - if(!c.ext) return; - if(entities::getents().length()) - { - while(c.ext->ents && !c.ext->ents->mapmodels.empty()) removeentity(c.ext->ents->mapmodels.pop()); - while(c.ext->ents && !c.ext->ents->other.empty()) removeentity(c.ext->ents->other.pop()); - } - if(c.ext->ents) - { - delete c.ext->ents; - c.ext->ents = NULL; - } + if(!c.ext) return; + if(entities::getents().length()) + { + while(c.ext->ents && !c.ext->ents->mapmodels.empty()) removeentity(c.ext->ents->mapmodels.pop()); + while(c.ext->ents && !c.ext->ents->other.empty()) removeentity(c.ext->ents->other.pop()); + } + if(c.ext->ents) + { + delete c.ext->ents; + c.ext->ents = NULL; + } } void entitiesinoctanodes() { - vector &ents = entities::getents(); - loopv(ents) modifyoctaent(MODOE_ADD, i, *ents[i]); + vector &ents = entities::getents(); + loopv(ents) modifyoctaent(MODOE_ADD, i, *ents[i]); } static inline void findents(octaentities &oe, int low, int high, bool notspawned, const vec &pos, const vec &invradius, vector &found) { - vector &ents = entities::getents(); - loopv(oe.other) - { - int id = oe.other[i]; - extentity &e = *ents[id]; - if(e.type >= low && e.type <= high && (e.spawned() || notspawned) && vec(e.o).sub(pos).mul(invradius).squaredlen() <= 1) found.add(id); - } + vector &ents = entities::getents(); + loopv(oe.other) + { + int id = oe.other[i]; + extentity &e = *ents[id]; + if(e.type >= low && e.type <= high && (e.spawned() || notspawned) && vec(e.o).sub(pos).mul(invradius).squaredlen() <= 1) found.add(id); + } } static inline void findents(cube *c, const ivec &o, int size, const ivec &bo, const ivec &br, int low, int high, bool notspawned, const vec &pos, const vec &invradius, vector &found) { - loopoctabox(o, size, bo, br) - { - if(c[i].ext && c[i].ext->ents) findents(*c[i].ext->ents, low, high, notspawned, pos, invradius, found); - if(c[i].children && size > octaentsize) - { - ivec co(i, o, size); - findents(c[i].children, co, size>>1, bo, br, low, high, notspawned, pos, invradius, found); - } - } + loopoctabox(o, size, bo, br) + { + if(c[i].ext && c[i].ext->ents) findents(*c[i].ext->ents, low, high, notspawned, pos, invradius, found); + if(c[i].children && size > octaentsize) + { + ivec co(i, o, size); + findents(c[i].children, co, size>>1, bo, br, low, high, notspawned, pos, invradius, found); + } + } } void findents(int low, int high, bool notspawned, const vec &pos, const vec &radius, vector &found) { - vec invradius(1/radius.x, 1/radius.y, 1/radius.z); - ivec bo(vec(pos).sub(radius).sub(1)), - br(vec(pos).add(radius).add(1)); - int diff = (bo.x^br.x) | (bo.y^br.y) | (bo.z^br.z) | octaentsize, - scale = worldscale-1; - if(diff&~((1<= uint(worldsize)) - { - findents(worldroot, ivec(0, 0, 0), 1<ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found); - scale--; - while(c->children && !(diff&(1<children[octastep(bo.x, bo.y, bo.z, scale)]; - if(c->ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found); - scale--; - } - if(c->children && 1<= octaentsize) findents(c->children, ivec(bo).mask(~((2<= uint(worldsize)) + { + findents(worldroot, ivec(0, 0, 0), 1<ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found); + scale--; + while(c->children && !(diff&(1<children[octastep(bo.x, bo.y, bo.z, scale)]; + if(c->ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found); + scale--; + } + if(c->children && 1<= octaentsize) findents(c->children, ivec(bo).mask(~((2<= sel.o.x - && o.y <= sel.o.y+sel.s.y*sel.grid - && o.y >= sel.o.y - && o.z <= sel.o.z+sel.s.z*sel.grid - && o.z >= sel.o.z); + return(o.x <= sel.o.x+sel.s.x*sel.grid + && o.x >= sel.o.x + && o.y <= sel.o.y+sel.s.y*sel.grid + && o.y >= sel.o.y + && o.z <= sel.o.z+sel.s.z*sel.grid + && o.z >= sel.o.z); } vector entgroup; bool haveselent() { - return entgroup.length() > 0; + return entgroup.length() > 0; } void entcancel() { - entgroup.shrink(0); + entgroup.shrink(0); } void entadd(int id) { - undonext = true; - entgroup.add(id); + undonext = true; + entgroup.add(id); } undoblock *newundoent() { - int numents = entgroup.length(); - if(numents <= 0) return NULL; - undoblock *u = (undoblock *)new uchar[sizeof(undoblock) + numents*sizeof(undoent)]; - u->numents = numents; - undoent *e = (undoent *)(u + 1); - loopv(entgroup) - { - e->i = entgroup[i]; - e->e = *entities::getents()[entgroup[i]]; - e++; - } - return u; + int numents = entgroup.length(); + if(numents <= 0) return NULL; + undoblock *u = (undoblock *)new uchar[sizeof(undoblock) + numents*sizeof(undoent)]; + u->numents = numents; + undoent *e = (undoent *)(u + 1); + loopv(entgroup) + { + e->i = entgroup[i]; + e->e = *entities::getents()[entgroup[i]]; + e++; + } + return u; } void makeundoent() { - if(!undonext) return; - undonext = false; - oldhover = enthover; - undoblock *u = newundoent(); - if(u) addundo(u); + if(!undonext) return; + undonext = false; + oldhover = enthover; + undoblock *u = newundoent(); + if(u) addundo(u); } void detachentity(extentity &e) { - if(!e.attached) return; - e.attached->attached = NULL; - e.attached = NULL; + if(!e.attached) return; + e.attached->attached = NULL; + e.attached = NULL; } VAR(attachradius, 1, 100, 1000); void attachentity(extentity &e) { - switch(e.type) - { - case ET_SPOTLIGHT: - break; - - default: - if(e.type &ents = entities::getents(); - int closest = -1; - float closedist = 1e10f; - loopv(ents) - { - extentity *a = ents[i]; - if(a->attached) continue; - switch(e.type) - { - case ET_SPOTLIGHT: - if(a->type!=ET_LIGHT) continue; - break; - - default: - if(e.typeo); - if(dist < closedist) - { - closest = i; - closedist = dist; - } - } - if(closedist>attachradius) return; - e.attached = ents[closest]; - ents[closest]->attached = &e; + switch(e.type) + { + case ET_SPOTLIGHT: + break; + + default: + if(e.type &ents = entities::getents(); + int closest = -1; + float closedist = 1e10f; + loopv(ents) + { + extentity *a = ents[i]; + if(a->attached) continue; + switch(e.type) + { + case ET_SPOTLIGHT: + if(a->type!=ET_LIGHT) continue; + break; + + default: + if(e.typeo); + if(dist < closedist) + { + closest = i; + closedist = dist; + } + } + if(closedist>attachradius) return; + e.attached = ents[closest]; + ents[closest]->attached = &e; } void attachentities() { - vector &ents = entities::getents(); - loopv(ents) attachentity(*ents[i]); + vector &ents = entities::getents(); + loopv(ents) attachentity(*ents[i]); } // convenience macros implicitly define: -// e entity, currently edited ent -// n int, index to currently edited ent -#define addimplicit(f) { if(entgroup.empty() && enthover>=0) { entadd(enthover); undonext = (enthover != oldhover); f; entgroup.drop(); } else f; } +// e entity, currently edited ent +// n int, index to currently edited ent +#define addimplicit(f) { if(entgroup.empty() && enthover>=0) { entadd(enthover); undonext = (enthover != oldhover); f; entgroup.drop(); } else f; } #define entfocusv(i, f, v){ int n = efocus = (i); if(n>=0) { extentity &e = *v[n]; f; } } -#define entfocus(i, f) entfocusv(i, f, entities::getents()) +#define entfocus(i, f) entfocusv(i, f, entities::getents()) #define enteditv(i, f, v) \ { \ - entfocusv(i, \ - { \ - int oldtype = e.type; \ - removeentity(n); \ - f; \ - if(oldtype!=e.type) detachentity(e); \ - if(e.type!=ET_EMPTY) { addentity(n); if(oldtype!=e.type) attachentity(e); } \ - entities::editent(n, true); \ - }, v); \ + entfocusv(i, \ + { \ + int oldtype = e.type; \ + removeentity(n); \ + f; \ + if(oldtype!=e.type) detachentity(e); \ + if(e.type!=ET_EMPTY) { addentity(n); if(oldtype!=e.type) attachentity(e); } \ + entities::editent(n, true); \ + }, v); \ } #define entedit(i, f) enteditv(i, f, entities::getents()) #define addgroup(exp) { vector &ents = entities::getents(); loopv(ents) entfocusv(i, if(exp) entadd(n), ents); } @@ -418,93 +418,93 @@ void attachentities() #define groupeditloop(f){ vector &ents = entities::getents(); entlooplevel++; int _ = efocus; loopv(entgroup) enteditv(entgroup[i], f, ents); efocus = _; entlooplevel--; } #define groupeditpure(f){ if(entlooplevel>0) { entedit(efocus, f); } else groupeditloop(f); } #define groupeditundo(f){ makeundoent(); groupeditpure(f); } -#define groupedit(f) { addimplicit(groupeditundo(f)); } +#define groupedit(f) { addimplicit(groupeditundo(f)); } vec getselpos() { - vector &ents = entities::getents(); - if(entgroup.length() && ents.inrange(entgroup[0])) return ents[entgroup[0]]->o; - if(ents.inrange(enthover)) return ents[enthover]->o; - return vec(sel.o); + vector &ents = entities::getents(); + if(entgroup.length() && ents.inrange(entgroup[0])) return ents[entgroup[0]]->o; + if(ents.inrange(enthover)) return ents[enthover]->o; + return vec(sel.o); } undoblock *copyundoents(undoblock *u) { - entcancel(); - undoent *e = u->ents(); - loopi(u->numents) - entadd(e[i].i); - undoblock *c = newundoent(); + entcancel(); + undoent *e = u->ents(); + loopi(u->numents) + entadd(e[i].i); + undoblock *c = newundoent(); loopi(u->numents) if(e[i].e.type==ET_EMPTY) entgroup.removeobj(e[i].i); - return c; + return c; } void pasteundoent(int idx, const entity &ue) { - if(idx < 0 || idx >= MAXENTS) return; - vector &ents = entities::getents(); - while(ents.length() < idx) ents.add(entities::newentity())->type = ET_EMPTY; - int efocus = -1; - entedit(idx, (entity &)e = ue); + if(idx < 0 || idx >= MAXENTS) return; + vector &ents = entities::getents(); + while(ents.length() < idx) ents.add(entities::newentity())->type = ET_EMPTY; + int efocus = -1; + entedit(idx, (entity &)e = ue); } void pasteundoents(undoblock *u) { - undoent *ue = u->ents(); - loopi(u->numents) - entedit(ue[i].i, (entity &)e = ue[i].e); + undoent *ue = u->ents(); + loopi(u->numents) + entedit(ue[i].i, (entity &)e = ue[i].e); } void entflip() { - if(noentedit()) return; - int d = dimension(sel.orient); - float mid = sel.s[d]*sel.grid/2+sel.o[d]; - groupeditundo(e.o[d] -= (e.o[d]-mid)*2); + if(noentedit()) return; + int d = dimension(sel.orient); + float mid = sel.s[d]*sel.grid/2+sel.o[d]; + groupeditundo(e.o[d] -= (e.o[d]-mid)*2); } void entrotate(int *cw) { - if(noentedit()) return; - int d = dimension(sel.orient); - int dd = (*cw<0) == dimcoord(sel.orient) ? R[d] : C[d]; - float mid = sel.s[dd]*sel.grid/2+sel.o[dd]; - vec s(sel.o.v); - groupeditundo( - e.o[dd] -= (e.o[dd]-mid)*2; - e.o.sub(s); - swap(e.o[R[d]], e.o[C[d]]); - e.o.add(s); - ); + if(noentedit()) return; + int d = dimension(sel.orient); + int dd = (*cw<0) == dimcoord(sel.orient) ? R[d] : C[d]; + float mid = sel.s[dd]*sel.grid/2+sel.o[dd]; + vec s(sel.o.v); + groupeditundo( + e.o[dd] -= (e.o[dd]-mid)*2; + e.o.sub(s); + swap(e.o[R[d]], e.o[C[d]]); + e.o.add(s); + ); } void entselectionbox(const entity &e, vec &eo, vec &es) { - model *m = NULL; - const char *mname = entities::entmodel(e); - if(mname && (m = loadmodel(mname))) - { - m->collisionbox(eo, es); - if(es.x > es.y) es.y = es.x; else es.x = es.y; // square - es.z = (es.z + eo.z + 1 + entselradius)/2; // enclose ent radius box and model box - eo.x += e.o.x; - eo.y += e.o.y; - eo.z = e.o.z - entselradius + es.z; - } - else if(e.type == ET_MAPMODEL && (m = loadmapmodel(e.attr2))) - { - mmcollisionbox(e, m, eo, es); - es.max(entselradius); - eo.add(e.o); - } - else - { - es = vec(entselradius); - eo = e.o; - } - eo.sub(es); - es.mul(2); + model *m = NULL; + const char *mname = entities::entmodel(e); + if(mname && (m = loadmodel(mname))) + { + m->collisionbox(eo, es); + if(es.x > es.y) es.y = es.x; else es.x = es.y; // square + es.z = (es.z + eo.z + 1 + entselradius)/2; // enclose ent radius box and model box + eo.x += e.o.x; + eo.y += e.o.y; + eo.z = e.o.z - entselradius + es.z; + } + else if(e.type == ET_MAPMODEL && (m = loadmapmodel(e.attr2))) + { + mmcollisionbox(e, m, eo, es); + es.max(entselradius); + eo.add(e.o); + } + else + { + es = vec(entselradius); + eo = e.o; + } + eo.sub(es); + es.mul(2); } VAR(entselsnap, 0, 0, 1); @@ -519,327 +519,327 @@ int entmoving = 0; void entdrag(const vec &ray) { - if(noentedit() || !haveselent()) return; + if(noentedit() || !haveselent()) return; - float r = 0, c = 0; - static vec v, handle; - vec eo, es; - int d = dimension(entorient), - dc= dimcoord(entorient); + float r = 0, c = 0; + static vec v, handle; + vec eo, es; + int d = dimension(entorient), + dc= dimcoord(entorient); - entfocus(entgroup.last(), - entselectionbox(e, eo, es); + entfocus(entgroup.last(), + entselectionbox(e, eo, es); - if(!editmoveplane(e.o, ray, d, eo[d] + (dc ? es[d] : 0), handle, v, entmoving==1)) - return; + if(!editmoveplane(e.o, ray, d, eo[d] + (dc ? es[d] : 0), handle, v, entmoving==1)) + return; - ivec g(v); - int z = g[d]&(~(sel.grid-1)); - g.add(sel.grid/2).mask(~(sel.grid-1)); - g[d] = z; + ivec g(v); + int z = g[d]&(~(sel.grid-1)); + g.add(sel.grid/2).mask(~(sel.grid-1)); + g[d] = z; - r = (entselsnap ? g[R[d]] : v[R[d]]) - e.o[R[d]]; - c = (entselsnap ? g[C[d]] : v[C[d]]) - e.o[C[d]]; - ); + r = (entselsnap ? g[R[d]] : v[R[d]]) - e.o[R[d]]; + c = (entselsnap ? g[C[d]] : v[C[d]]) - e.o[C[d]]; + ); - if(entmoving==1) makeundoent(); - groupeditpure(e.o[R[d]] += r; e.o[C[d]] += c); - entmoving = 2; + if(entmoving==1) makeundoent(); + groupeditpure(e.o[R[d]] += r; e.o[C[d]] += c); + entmoving = 2; } VAR(showentradius, 0, 1, 1); void renderentring(const extentity &e, float radius, int axis) { - if(radius <= 0) return; - gle::defvertex(); - gle::begin(GL_LINE_LOOP); - loopi(15) - { - vec p(e.o); - const vec2 &sc = sincos360[i*(360/15)]; - p[axis>=2 ? 1 : 0] += radius*sc.x; - p[axis>=1 ? 2 : 1] += radius*sc.y; - gle::attrib(p); - } - xtraverts += gle::end(); + if(radius <= 0) return; + gle::defvertex(); + gle::begin(GL_LINE_LOOP); + loopi(15) + { + vec p(e.o); + const vec2 &sc = sincos360[i*(360/15)]; + p[axis>=2 ? 1 : 0] += radius*sc.x; + p[axis>=1 ? 2 : 1] += radius*sc.y; + gle::attrib(p); + } + xtraverts += gle::end(); } void renderentsphere(const extentity &e, float radius) { - if(radius <= 0) return; - loopk(3) renderentring(e, radius, k); + if(radius <= 0) return; + loopk(3) renderentring(e, radius, k); } void renderentattachment(const extentity &e) { - if(!e.attached) return; - gle::defvertex(); - gle::begin(GL_LINES); - gle::attrib(e.o); - gle::attrib(e.attached->o); - xtraverts += gle::end(); + if(!e.attached) return; + gle::defvertex(); + gle::begin(GL_LINES); + gle::attrib(e.o); + gle::attrib(e.attached->o); + xtraverts += gle::end(); } void renderentarrow(const extentity &e, const vec &dir, float radius) { - if(radius <= 0) return; - float arrowsize = min(radius/8, 0.5f); - vec target = vec(dir).mul(radius).add(e.o), arrowbase = vec(dir).mul(radius - arrowsize).add(e.o), spoke; - spoke.orthogonal(dir); - spoke.normalize(); - spoke.mul(arrowsize); + if(radius <= 0) return; + float arrowsize = min(radius/8, 0.5f); + vec target = vec(dir).mul(radius).add(e.o), arrowbase = vec(dir).mul(radius - arrowsize).add(e.o), spoke; + spoke.orthogonal(dir); + spoke.normalize(); + spoke.mul(arrowsize); - gle::defvertex(); + gle::defvertex(); - gle::begin(GL_LINES); - gle::attrib(e.o); - gle::attrib(target); - xtraverts += gle::end(); + gle::begin(GL_LINES); + gle::attrib(e.o); + gle::attrib(target); + xtraverts += gle::end(); - gle::begin(GL_TRIANGLE_FAN); - gle::attrib(target); - loopi(5) gle::attrib(vec(spoke).rotate(2*M_PI*i/4.0f, dir).add(arrowbase)); - xtraverts += gle::end(); + gle::begin(GL_TRIANGLE_FAN); + gle::attrib(target); + loopi(5) gle::attrib(vec(spoke).rotate(2*M_PI*i/4.0f, dir).add(arrowbase)); + xtraverts += gle::end(); } void renderentcone(const extentity &e, const vec &dir, float radius, float angle) { - if(radius <= 0) return; - vec spot = vec(dir).mul(radius*cosf(angle*RAD)).add(e.o), spoke; - spoke.orthogonal(dir); - spoke.normalize(); - spoke.mul(radius*sinf(angle*RAD)); + if(radius <= 0) return; + vec spot = vec(dir).mul(radius*cosf(angle*RAD)).add(e.o), spoke; + spoke.orthogonal(dir); + spoke.normalize(); + spoke.mul(radius*sinf(angle*RAD)); - gle::defvertex(); + gle::defvertex(); - gle::begin(GL_LINES); - loopi(8) - { - gle::attrib(e.o); - gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot)); - } - xtraverts += gle::end(); + gle::begin(GL_LINES); + loopi(8) + { + gle::attrib(e.o); + gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot)); + } + xtraverts += gle::end(); - gle::begin(GL_LINE_LOOP); - loopi(8) gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot)); - xtraverts += gle::end(); + gle::begin(GL_LINE_LOOP); + loopi(8) gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot)); + xtraverts += gle::end(); } void renderentradius(extentity &e, bool color) { - switch(e.type) - { - case ET_LIGHT: - if(color) gle::colorf(e.attr2/255.0f, e.attr3/255.0f, e.attr4/255.0f); - renderentsphere(e, e.attr1); - break; - - case ET_SPOTLIGHT: - if(e.attached) - { - if(color) gle::colorf(0, 1, 1); - float radius = e.attached->attr1; - if(!radius) radius = 2*e.o.dist(e.attached->o); - vec dir = vec(e.o).sub(e.attached->o).normalize(); - float angle = clamp(int(e.attr1), 1, 89); - renderentattachment(e); - renderentcone(*e.attached, dir, radius, angle); - } - break; - - case ET_SOUND: - if(color) gle::colorf(0, 1, 1); - renderentsphere(e, e.attr2); - break; - - case ET_ENVMAP: - { - extern int envmapradius; - if(color) gle::colorf(0, 1, 1); - renderentsphere(e, e.attr1 ? max(0, min(10000, int(e.attr1))) : envmapradius); - break; - } - - case ET_MAPMODEL: - case ET_PLAYERSTART: - { - if(color) gle::colorf(0, 1, 1); - entities::entradius(e, color); - vec dir; - vecfromyawpitch(e.attr1, 0, 1, 0, dir); - renderentarrow(e, dir, 4); - break; - } - - default: - if(e.type>=ET_GAMESPECIFIC) - { - if(color) gle::colorf(0, 1, 1); - entities::entradius(e, color); - } - break; - } + switch(e.type) + { + case ET_LIGHT: + if(color) gle::colorf(e.attr2/255.0f, e.attr3/255.0f, e.attr4/255.0f); + renderentsphere(e, e.attr1); + break; + + case ET_SPOTLIGHT: + if(e.attached) + { + if(color) gle::colorf(0, 1, 1); + float radius = e.attached->attr1; + if(!radius) radius = 2*e.o.dist(e.attached->o); + vec dir = vec(e.o).sub(e.attached->o).normalize(); + float angle = clamp(int(e.attr1), 1, 89); + renderentattachment(e); + renderentcone(*e.attached, dir, radius, angle); + } + break; + + case ET_SOUND: + if(color) gle::colorf(0, 1, 1); + renderentsphere(e, e.attr2); + break; + + case ET_ENVMAP: + { + extern int envmapradius; + if(color) gle::colorf(0, 1, 1); + renderentsphere(e, e.attr1 ? max(0, min(10000, int(e.attr1))) : envmapradius); + break; + } + + case ET_MAPMODEL: + case ET_PLAYERSTART: + { + if(color) gle::colorf(0, 1, 1); + entities::entradius(e, color); + vec dir; + vecfromyawpitch(e.attr1, 0, 1, 0, dir); + renderentarrow(e, dir, 4); + break; + } + + default: + if(e.type>=ET_GAMESPECIFIC) + { + if(color) gle::colorf(0, 1, 1); + entities::entradius(e, color); + } + break; + } } static void renderentbox(const vec &eo, vec es) { - es.add(eo); + es.add(eo); - // bottom quad - gle::attrib(eo.x, eo.y, eo.z); gle::attrib(es.x, eo.y, eo.z); - gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, es.y, eo.z); - gle::attrib(es.x, es.y, eo.z); gle::attrib(eo.x, es.y, eo.z); - gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, eo.y, eo.z); + // bottom quad + gle::attrib(eo.x, eo.y, eo.z); gle::attrib(es.x, eo.y, eo.z); + gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, es.y, eo.z); + gle::attrib(es.x, es.y, eo.z); gle::attrib(eo.x, es.y, eo.z); + gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, eo.y, eo.z); - // top quad - gle::attrib(eo.x, eo.y, es.z); gle::attrib(es.x, eo.y, es.z); - gle::attrib(es.x, eo.y, es.z); gle::attrib(es.x, es.y, es.z); - gle::attrib(es.x, es.y, es.z); gle::attrib(eo.x, es.y, es.z); - gle::attrib(eo.x, es.y, es.z); gle::attrib(eo.x, eo.y, es.z); + // top quad + gle::attrib(eo.x, eo.y, es.z); gle::attrib(es.x, eo.y, es.z); + gle::attrib(es.x, eo.y, es.z); gle::attrib(es.x, es.y, es.z); + gle::attrib(es.x, es.y, es.z); gle::attrib(eo.x, es.y, es.z); + gle::attrib(eo.x, es.y, es.z); gle::attrib(eo.x, eo.y, es.z); - // sides - gle::attrib(eo.x, eo.y, eo.z); gle::attrib(eo.x, eo.y, es.z); - gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, eo.y, es.z); - gle::attrib(es.x, es.y, eo.z); gle::attrib(es.x, es.y, es.z); - gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, es.y, es.z); + // sides + gle::attrib(eo.x, eo.y, eo.z); gle::attrib(eo.x, eo.y, es.z); + gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, eo.y, es.z); + gle::attrib(es.x, es.y, eo.z); gle::attrib(es.x, es.y, es.z); + gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, es.y, es.z); } void renderentselection(const vec &o, const vec &ray, bool entmoving) { - if(noentedit()) return; - vec eo, es; - - if(entgroup.length()) - { - gle::colorub(0, 40, 0); - gle::defvertex(); - gle::begin(GL_LINES, entgroup.length()*24); - loopv(entgroup) entfocus(entgroup[i], - entselectionbox(e, eo, es); - renderentbox(eo, es); - ); - xtraverts += gle::end(); - } - - if(enthover >= 0) - { - gle::colorub(0, 40, 0); - entfocus(enthover, entselectionbox(e, eo, es)); // also ensures enthover is back in focus - boxs3D(eo, es, 1); - if(entmoving && entmovingshadow==1) - { - vec a, b; - gle::colorub(20, 20, 20); - (a = eo).x = eo.x - fmod(eo.x, worldsize); (b = es).x = a.x + worldsize; boxs3D(a, b, 1); - (a = eo).y = eo.y - fmod(eo.y, worldsize); (b = es).y = a.x + worldsize; boxs3D(a, b, 1); - (a = eo).z = eo.z - fmod(eo.z, worldsize); (b = es).z = a.x + worldsize; boxs3D(a, b, 1); - } - gle::colorub(150,0,0); - boxs(entorient, eo, es); - boxs(entorient, eo, es, clamp(0.015f*camera1->o.dist(eo)*tan(fovy*0.5f*RAD), 0.1f, 1.0f)); - } - - if(showentradius && (entgroup.length() || enthover >= 0)) - { - glDepthFunc(GL_GREATER); - gle::colorf(0.25f, 0.25f, 0.25f); - loopv(entgroup) entfocus(entgroup[i], renderentradius(e, false)); - if(enthover>=0) entfocus(enthover, renderentradius(e, false)); - glDepthFunc(GL_LESS); - loopv(entgroup) entfocus(entgroup[i], renderentradius(e, true)); - if(enthover>=0) entfocus(enthover, renderentradius(e, true)); - } + if(noentedit()) return; + vec eo, es; + + if(entgroup.length()) + { + gle::colorub(0, 40, 0); + gle::defvertex(); + gle::begin(GL_LINES, entgroup.length()*24); + loopv(entgroup) entfocus(entgroup[i], + entselectionbox(e, eo, es); + renderentbox(eo, es); + ); + xtraverts += gle::end(); + } + + if(enthover >= 0) + { + gle::colorub(0, 40, 0); + entfocus(enthover, entselectionbox(e, eo, es)); // also ensures enthover is back in focus + boxs3D(eo, es, 1); + if(entmoving && entmovingshadow==1) + { + vec a, b; + gle::colorub(20, 20, 20); + (a = eo).x = eo.x - fmod(eo.x, worldsize); (b = es).x = a.x + worldsize; boxs3D(a, b, 1); + (a = eo).y = eo.y - fmod(eo.y, worldsize); (b = es).y = a.x + worldsize; boxs3D(a, b, 1); + (a = eo).z = eo.z - fmod(eo.z, worldsize); (b = es).z = a.x + worldsize; boxs3D(a, b, 1); + } + gle::colorub(150,0,0); + boxs(entorient, eo, es); + boxs(entorient, eo, es, clamp(0.015f*camera1->o.dist(eo)*tan(fovy*0.5f*RAD), 0.1f, 1.0f)); + } + + if(showentradius && (entgroup.length() || enthover >= 0)) + { + glDepthFunc(GL_GREATER); + gle::colorf(0.25f, 0.25f, 0.25f); + loopv(entgroup) entfocus(entgroup[i], renderentradius(e, false)); + if(enthover>=0) entfocus(enthover, renderentradius(e, false)); + glDepthFunc(GL_LESS); + loopv(entgroup) entfocus(entgroup[i], renderentradius(e, true)); + if(enthover>=0) entfocus(enthover, renderentradius(e, true)); + } } bool enttoggle(int id) { - undonext = true; - int i = entgroup.find(id); - if(i < 0) - entadd(id); - else - entgroup.remove(i); - return i < 0; + undonext = true; + int i = entgroup.find(id); + if(i < 0) + entadd(id); + else + entgroup.remove(i); + return i < 0; } bool hoveringonent(int ent, int orient) { - if(noentedit()) return false; - entorient = orient; - if((efocus = enthover = ent) >= 0) - return true; - efocus = entgroup.empty() ? -1 : entgroup.last(); - enthover = -1; - return false; + if(noentedit()) return false; + entorient = orient; + if((efocus = enthover = ent) >= 0) + return true; + efocus = entgroup.empty() ? -1 : entgroup.last(); + enthover = -1; + return false; } VAR(entitysurf, 0, 0, 1); ICOMMAND(entadd, "", (), { - if(enthover >= 0 && !noentedit()) - { - if(entgroup.find(enthover) < 0) entadd(enthover); - if(entmoving > 1) entmoving = 1; - } + if(enthover >= 0 && !noentedit()) + { + if(entgroup.find(enthover) < 0) entadd(enthover); + if(entmoving > 1) entmoving = 1; + } }); ICOMMAND(enttoggle, "", (), { - if(enthover < 0 || noentedit() || !enttoggle(enthover)) { entmoving = 0; intret(0); } - else { if(entmoving > 1) entmoving = 1; intret(1); } + if(enthover < 0 || noentedit() || !enttoggle(enthover)) { entmoving = 0; intret(0); } + else { if(entmoving > 1) entmoving = 1; intret(1); } }); ICOMMAND(entmoving, "b", (int *n), { - if(*n >= 0) - { - if(!*n || enthover < 0 || noentedit()) entmoving = 0; - else - { - if(entgroup.find(enthover) < 0) { entadd(enthover); entmoving = 1; } - else if(!entmoving) entmoving = 1; - } - } - intret(entmoving); + if(*n >= 0) + { + if(!*n || enthover < 0 || noentedit()) entmoving = 0; + else + { + if(entgroup.find(enthover) < 0) { entadd(enthover); entmoving = 1; } + else if(!entmoving) entmoving = 1; + } + } + intret(entmoving); }); void entpush(int *dir) { - if(noentedit()) return; - int d = dimension(entorient); - int s = dimcoord(entorient) ? -*dir : *dir; - if(entmoving) - { - groupeditpure(e.o[d] += float(s*sel.grid)); // editdrag supplies the undo - } - else - groupedit(e.o[d] += float(s*sel.grid)); - if(entitysurf==1) - { - player->o[d] += float(s*sel.grid); - player->resetinterp(); - } + if(noentedit()) return; + int d = dimension(entorient); + int s = dimcoord(entorient) ? -*dir : *dir; + if(entmoving) + { + groupeditpure(e.o[d] += float(s*sel.grid)); // editdrag supplies the undo + } + else + groupedit(e.o[d] += float(s*sel.grid)); + if(entitysurf==1) + { + player->o[d] += float(s*sel.grid); + player->resetinterp(); + } } VAR(entautoviewdist, 0, 25, 100); void entautoview(int *dir) { - if(!haveselent()) return; - static int s = 0; - vec v(player->o); - v.sub(worldpos); - v.normalize(); - v.mul(entautoviewdist); - int t = s + *dir; - s = abs(t) % entgroup.length(); - if(t<0 && s>0) s = entgroup.length() - s; - entfocus(entgroup[s], - v.add(e.o); - player->o = v; - player->resetinterp(); - ); + if(!haveselent()) return; + static int s = 0; + vec v(player->o); + v.sub(worldpos); + v.normalize(); + v.mul(entautoviewdist); + int t = s + *dir; + s = abs(t) % entgroup.length(); + if(t<0 && s>0) s = entgroup.length() - s; + entfocus(entgroup[s], + v.add(e.o); + player->o = v; + player->resetinterp(); + ); } COMMAND(entautoview, "i"); @@ -849,76 +849,76 @@ COMMAND(entpush, "i"); void delent() { - if(noentedit()) return; - groupedit(e.type = ET_EMPTY;); - entcancel(); + if(noentedit()) return; + groupedit(e.type = ET_EMPTY;); + entcancel(); } int findtype(char *what) { - for(int i = 0; *entities::entname(i); i++) if(strcmp(what, entities::entname(i))==0) return i; - conoutf(CON_ERROR, "unknown entity type \"%s\"", what); - return ET_EMPTY; + for(int i = 0; *entities::entname(i); i++) if(strcmp(what, entities::entname(i))==0) return i; + conoutf(CON_ERROR, "unknown entity type \"%s\"", what); + return ET_EMPTY; } VAR(entdrop, 0, 2, 3); bool dropentity(entity &e, int drop = -1) { - vec radius(4.0f, 4.0f, 4.0f); - if(drop<0) drop = entdrop; - if(e.type == ET_MAPMODEL) - { - model *m = loadmapmodel(e.attr2); - if(m) - { - vec center; - mmboundbox(e, m, center, radius); - radius.x += fabs(center.x); - radius.y += fabs(center.y); - } - radius.z = 0.0f; - } - switch(drop) - { - case 1: - if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT) - dropenttofloor(&e); - break; - case 2: - case 3: - int cx = 0, cy = 0; - if(sel.cxs == 1 && sel.cys == 1) - { - cx = (sel.cx ? 1 : -1) * sel.grid / 2; - cy = (sel.cy ? 1 : -1) * sel.grid / 2; - } - e.o = vec(sel.o); - int d = dimension(sel.orient), dc = dimcoord(sel.orient); - e.o[R[d]] += sel.grid / 2 + cx; - e.o[C[d]] += sel.grid / 2 + cy; - if(!dc) - e.o[D[d]] -= radius[D[d]]; - else - e.o[D[d]] += sel.grid + radius[D[d]]; - - if(drop == 3) - dropenttofloor(&e); - break; - } - return true; + vec radius(4.0f, 4.0f, 4.0f); + if(drop<0) drop = entdrop; + if(e.type == ET_MAPMODEL) + { + model *m = loadmapmodel(e.attr2); + if(m) + { + vec center; + mmboundbox(e, m, center, radius); + radius.x += fabs(center.x); + radius.y += fabs(center.y); + } + radius.z = 0.0f; + } + switch(drop) + { + case 1: + if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT) + dropenttofloor(&e); + break; + case 2: + case 3: + int cx = 0, cy = 0; + if(sel.cxs == 1 && sel.cys == 1) + { + cx = (sel.cx ? 1 : -1) * sel.grid / 2; + cy = (sel.cy ? 1 : -1) * sel.grid / 2; + } + e.o = vec(sel.o); + int d = dimension(sel.orient), dc = dimcoord(sel.orient); + e.o[R[d]] += sel.grid / 2 + cx; + e.o[C[d]] += sel.grid / 2 + cy; + if(!dc) + e.o[D[d]] -= radius[D[d]]; + else + e.o[D[d]] += sel.grid + radius[D[d]]; + + if(drop == 3) + dropenttofloor(&e); + break; + } + return true; } void dropent() { - if(noentedit()) return; - groupedit(dropentity(e)); + if(noentedit()) return; + groupedit(dropentity(e)); } void attachent() { - if(noentedit()) return; - groupedit(attachentity(e)); + if(noentedit()) return; + groupedit(attachentity(e)); } COMMAND(attachent, ""); @@ -929,63 +929,63 @@ static int keepents = 0; extentity *newentity(bool local, const vec &o, int type, int v1, int v2, int v3, int v4, int v5, int &idx) { - vector &ents = entities::getents(); - if(local) - { - idx = -1; - for(int i = keepents; i < ents.length(); i++) if(ents[i]->type == ET_EMPTY) { idx = i; break; } - if(idx < 0 && ents.length() >= MAXENTS) { conoutf(CON_ERROR, "too many entities"); return NULL; } - } - else while(ents.length() < idx) ents.add(entities::newentity())->type = ET_EMPTY; - extentity &e = *entities::newentity(); - e.o = o; - e.attr1 = v1; - e.attr2 = v2; - e.attr3 = v3; - e.attr4 = v4; - e.attr5 = v5; - e.type = type; - e.reserved = 0; - e.light.color = vec(1, 1, 1); - e.light.dir = vec(0, 0, 1); - if(local) - { - if(entcamdir) switch(type) - { - case ET_MAPMODEL: - case ET_PLAYERSTART: - e.attr5 = e.attr4; - e.attr4 = e.attr3; - e.attr3 = e.attr2; - e.attr2 = e.attr1; - e.attr1 = (int)camera1->yaw; - break; - } - entities::fixentity(e); - } - if(ents.inrange(idx)) { entities::deleteentity(ents[idx]); ents[idx] = &e; } - else { idx = ents.length(); ents.add(&e); } - return &e; + vector &ents = entities::getents(); + if(local) + { + idx = -1; + for(int i = keepents; i < ents.length(); i++) if(ents[i]->type == ET_EMPTY) { idx = i; break; } + if(idx < 0 && ents.length() >= MAXENTS) { conoutf(CON_ERROR, "too many entities"); return NULL; } + } + else while(ents.length() < idx) ents.add(entities::newentity())->type = ET_EMPTY; + extentity &e = *entities::newentity(); + e.o = o; + e.attr1 = v1; + e.attr2 = v2; + e.attr3 = v3; + e.attr4 = v4; + e.attr5 = v5; + e.type = type; + e.reserved = 0; + e.light.color = vec(1, 1, 1); + e.light.dir = vec(0, 0, 1); + if(local) + { + if(entcamdir) switch(type) + { + case ET_MAPMODEL: + case ET_PLAYERSTART: + e.attr5 = e.attr4; + e.attr4 = e.attr3; + e.attr3 = e.attr2; + e.attr2 = e.attr1; + e.attr1 = (int)camera1->yaw; + break; + } + entities::fixentity(e); + } + if(ents.inrange(idx)) { entities::deleteentity(ents[idx]); ents[idx] = &e; } + else { idx = ents.length(); ents.add(&e); } + return &e; } void newentity(int type, int a1, int a2, int a3, int a4, int a5) { - int idx; - extentity *t = newentity(true, player->o, type, a1, a2, a3, a4, a5, idx); - if(!t) return; - dropentity(*t); - t->type = ET_EMPTY; - enttoggle(idx); - makeundoent(); - entedit(idx, e.type = type); + int idx; + extentity *t = newentity(true, player->o, type, a1, a2, a3, a4, a5, idx); + if(!t) return; + dropentity(*t); + t->type = ET_EMPTY; + enttoggle(idx); + makeundoent(); + entedit(idx, e.type = type); } void newent(char *what, int *a1, int *a2, int *a3, int *a4, int *a5) { - if(noentedit()) return; - int type = findtype(what); - if(type != ET_EMPTY) - newentity(type, *a1, *a2, *a3, *a4, *a5); + if(noentedit()) return; + int type = findtype(what); + if(type != ET_EMPTY) + newentity(type, *a1, *a2, *a3, *a4, *a5); } int entcopygrid; @@ -993,33 +993,33 @@ vector entcopybuf; void entcopy() { - if(noentedit()) return; - entcopygrid = sel.grid; - entcopybuf.shrink(0); - loopv(entgroup) - entfocus(entgroup[i], entcopybuf.add(e).o.sub(vec(sel.o))); + if(noentedit()) return; + entcopygrid = sel.grid; + entcopybuf.shrink(0); + loopv(entgroup) + entfocus(entgroup[i], entcopybuf.add(e).o.sub(vec(sel.o))); } void entpaste() { - if(noentedit()) return; - if(entcopybuf.length()==0) return; - entcancel(); - float m = float(sel.grid)/float(entcopygrid); - loopv(entcopybuf) - { - entity &c = entcopybuf[i]; - vec o(c.o); - o.mul(m).add(vec(sel.o)); - int idx; - extentity *e = newentity(true, o, ET_EMPTY, c.attr1, c.attr2, c.attr3, c.attr4, c.attr5, idx); - if(!e) continue; - entadd(idx); - keepents = max(keepents, idx+1); - } - keepents = 0; - int j = 0; - groupeditundo(e.type = entcopybuf[j++].type;); + if(noentedit()) return; + if(entcopybuf.length()==0) return; + entcancel(); + float m = float(sel.grid)/float(entcopygrid); + loopv(entcopybuf) + { + entity &c = entcopybuf[i]; + vec o(c.o); + o.mul(m).add(vec(sel.o)); + int idx; + extentity *e = newentity(true, o, ET_EMPTY, c.attr1, c.attr2, c.attr3, c.attr4, c.attr5, idx); + if(!e) continue; + entadd(idx); + keepents = max(keepents, idx+1); + } + keepents = 0; + int j = 0; + groupeditundo(e.type = entcopybuf[j++].type;); } COMMAND(newent, "siiiii"); @@ -1030,101 +1030,101 @@ COMMAND(entpaste, ""); void entset(char *what, int *a1, int *a2, int *a3, int *a4, int *a5) { - if(noentedit()) return; - int type = findtype(what); - if(type != ET_EMPTY) - groupedit(e.type=type; - e.attr1=*a1; - e.attr2=*a2; - e.attr3=*a3; - e.attr4=*a4; - e.attr5=*a5); + if(noentedit()) return; + int type = findtype(what); + if(type != ET_EMPTY) + groupedit(e.type=type; + e.attr1=*a1; + e.attr2=*a2; + e.attr3=*a3; + e.attr4=*a4; + e.attr5=*a5); } void printent(extentity &e, char *buf, int len) { - switch(e.type) - { - case ET_PARTICLES: - if(printparticles(e, buf, len)) return; - break; + switch(e.type) + { + case ET_PARTICLES: + if(printparticles(e, buf, len)) return; + break; - default: - if(e.type >= ET_GAMESPECIFIC && entities::printent(e, buf, len)) return; - break; - } - nformatstring(buf, len, "%s %d %d %d %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); + default: + if(e.type >= ET_GAMESPECIFIC && entities::printent(e, buf, len)) return; + break; + } + nformatstring(buf, len, "%s %d %d %d %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); } void nearestent() { - if(noentedit()) return; - int closest = -1; - float closedist = 1e16f; - vector &ents = entities::getents(); - loopv(ents) - { - extentity &e = *ents[i]; - if(e.type == ET_EMPTY) continue; - float dist = e.o.dist(player->o); - if(dist < closedist) - { - closest = i; - closedist = dist; - } - } - if(closest >= 0 && entgroup.find(closest) < 0) entadd(closest); + if(noentedit()) return; + int closest = -1; + float closedist = 1e16f; + vector &ents = entities::getents(); + loopv(ents) + { + extentity &e = *ents[i]; + if(e.type == ET_EMPTY) continue; + float dist = e.o.dist(player->o); + if(dist < closedist) + { + closest = i; + closedist = dist; + } + } + if(closest >= 0 && entgroup.find(closest) < 0) entadd(closest); } ICOMMAND(enthavesel,"", (), addimplicit(intret(entgroup.length()))); ICOMMAND(entselect, "e", (uint *body), if(!noentedit()) addgroup(e.type != ET_EMPTY && entgroup.find(n)<0 && executebool(body))); ICOMMAND(entloop, "e", (uint *body), if(!noentedit()) addimplicit(groupeditloop(((void)e, execute(body))))); -ICOMMAND(insel, "", (), entfocus(efocus, intret(pointinsel(sel, e.o)))); -ICOMMAND(entget, "", (), entfocus(efocus, string s; printent(e, s, sizeof(s)); result(s))); +ICOMMAND(insel, "", (), entfocus(efocus, intret(pointinsel(sel, e.o)))); +ICOMMAND(entget, "", (), entfocus(efocus, string s; printent(e, s, sizeof(s)); result(s))); ICOMMAND(entindex, "", (), intret(efocus)); COMMAND(entset, "siiiii"); COMMAND(nearestent, ""); void enttype(char *type, int *numargs) { - if(*numargs >= 1) - { - int typeidx = findtype(type); - if(typeidx != ET_EMPTY) groupedit(e.type = typeidx); - } - else entfocus(efocus, - { - result(entities::entname(e.type)); - }) + if(*numargs >= 1) + { + int typeidx = findtype(type); + if(typeidx != ET_EMPTY) groupedit(e.type = typeidx); + } + else entfocus(efocus, + { + result(entities::entname(e.type)); + }) } void entattr(int *attr, int *val, int *numargs) { - if(*numargs >= 2) - { - if(*attr >= 0 && *attr <= 4) - groupedit( - switch(*attr) - { - case 0: e.attr1 = *val; break; - case 1: e.attr2 = *val; break; - case 2: e.attr3 = *val; break; - case 3: e.attr4 = *val; break; - case 4: e.attr5 = *val; break; - } - ); - } - else entfocus(efocus, - { - switch(*attr) - { - case 0: intret(e.attr1); break; - case 1: intret(e.attr2); break; - case 2: intret(e.attr3); break; - case 3: intret(e.attr4); break; - case 4: intret(e.attr5); break; - } - }); + if(*numargs >= 2) + { + if(*attr >= 0 && *attr <= 4) + groupedit( + switch(*attr) + { + case 0: e.attr1 = *val; break; + case 1: e.attr2 = *val; break; + case 2: e.attr3 = *val; break; + case 3: e.attr4 = *val; break; + case 4: e.attr5 = *val; break; + } + ); + } + else entfocus(efocus, + { + switch(*attr) + { + case 0: intret(e.attr1); break; + case 1: intret(e.attr2); break; + case 2: intret(e.attr3); break; + case 3: intret(e.attr4); break; + case 4: intret(e.attr5); break; + } + }); } COMMAND(enttype, "sN"); @@ -1132,21 +1132,21 @@ COMMAND(entattr, "iiN"); int findentity(int type, int index, int attr1, int attr2) { - const vector &ents = entities::getents(); - if(index > ents.length()) index = ents.length(); - else for(int i = index; i &ents = entities::getents(); + if(index > ents.length()) index = ents.length(); + else for(int i = index; i &spawninfos) { - const vector &ents = entities::getents(); - float total = 0.0f; - loopv(ents) - { - const extentity &e = *ents[i]; - if(e.type != ET_PLAYERSTART || e.attr2 != tag) continue; - spawninfo &s = spawninfos.add(); - s.e = &e; - s.weight = game::ratespawn(d, e); - total += s.weight; - } - return total; + const vector &ents = entities::getents(); + float total = 0.0f; + loopv(ents) + { + const extentity &e = *ents[i]; + if(e.type != ET_PLAYERSTART || e.attr2 != tag) continue; + spawninfo &s = spawninfos.add(); + s.e = &e; + s.weight = game::ratespawn(d, e); + total += s.weight; + } + return total; } // Randomly picks a weighted spawn from the provided vector and removes it. @@ -1174,179 +1174,178 @@ float gatherspawninfos(dynent *d, int tag, vector &spawninfos) // If all weights are zero, the index is picked uniformly. static const extentity *poprandomspawn(vector &spawninfos, float &total) { - if(spawninfos.empty()) return NULL; - int index = 0; - if(total > 0.0f) - { - float x = rndscale(total); - do x -= spawninfos[index].weight; while(x > 0 && ++index < spawninfos.length()-1); - } - else index = rnd(spawninfos.length()); - spawninfo s = spawninfos.removeunordered(index); - total -= s.weight; - return s.e; + if(spawninfos.empty()) return NULL; + int index = 0; + if(total > 0.0f) + { + float x = rndscale(total); + do x -= spawninfos[index].weight; while(x > 0 && ++index < spawninfos.length()-1); + } + else index = rnd(spawninfos.length()); + spawninfo s = spawninfos.removeunordered(index); + total -= s.weight; + return s.e; } static inline bool tryspawn(dynent *d, const extentity &e) { - d->o = e.o; - d->yaw = e.attr1; - return entinmap(d, true); + d->o = e.o; + d->yaw = e.attr1; + return entinmap(d, true); } void findplayerspawn(dynent *d, int forceent, int tag) { - const vector &ents = entities::getents(); - d->pitch = 0; - d->roll = 0; - if(ents.inrange(forceent) && tryspawn(d, *ents[forceent])) return; - vector spawninfos; - float total = gatherspawninfos(d, tag, spawninfos); - while(const extentity *e = poprandomspawn(spawninfos, total)) if(tryspawn(d, *e)) return; - d->o = vec(0.5f * worldsize).addz(1); - d->yaw = 0; - entinmap(d); + const vector &ents = entities::getents(); + d->pitch = 0; + d->roll = 0; + if(ents.inrange(forceent) && tryspawn(d, *ents[forceent])) return; + vector spawninfos; + float total = gatherspawninfos(d, tag, spawninfos); + while(const extentity *e = poprandomspawn(spawninfos, total)) if(tryspawn(d, *e)) return; + d->o = vec(0.5f * worldsize).addz(1); + d->yaw = 0; + entinmap(d); } void splitocta(cube *c, int size) { - if(size <= 0x1000) return; - loopi(8) - { - if(!c[i].children) c[i].children = newcubes(isempty(c[i]) ? F_EMPTY : F_SOLID); - splitocta(c[i].children, size>>1); - } + if(size <= 0x1000) return; + loopi(8) + { + if(!c[i].children) c[i].children = newcubes(isempty(c[i]) ? F_EMPTY : F_SOLID); + splitocta(c[i].children, size>>1); + } } void resetmap() { - clearoverrides(); - clearmapsounds(); - cleanreflections(); - resetblendmap(); - resetlightmaps(); - clearslots(); - clearparticles(); - cleardecals(); - cleardamagescreen(); - clearsleep(); - cancelsel(); - pruneundos(); - clearmapcrc(); + clearoverrides(); + clearmapsounds(); + cleanreflections(); + resetblendmap(); + resetlightmaps(); + clearslots(); + clearparticles(); + cleardecals(); + clearsleep(); + cancelsel(); + pruneundos(); + clearmapcrc(); - entities::clearents(); - outsideents.setsize(0); + entities::clearents(); + outsideents.setsize(0); } void startmap(const char *name) { - game::startmap(name); + game::startmap(name); } -bool emptymap(int scale, bool force, const char *mname, bool usecfg) // main empty world creation routine +bool emptymap(int scale, bool force, const char *mname, bool usecfg) // main empty world creation routine { - if(!force && !editmode) - { - conoutf(CON_ERROR, "newmap only allowed in edit mode"); - return false; - } + if(!force && !editmode) + { + conoutf(CON_ERROR, "newmap only allowed in edit mode"); + return false; + } - resetmap(); + resetmap(); - setvar("mapscale", scale<10 ? 10 : (scale>16 ? 16 : scale), true, false); - setvar("mapsize", 1<16 ? 16 : scale), true, false); + setvar("mapsize", 1< 0x1000) splitocta(worldroot, worldsize>>1); + if(worldsize > 0x1000) splitocta(worldroot, worldsize>>1); - clearmainmenu(); + clearmainmenu(); - if(usecfg) - { - identflags |= IDF_OVERRIDDEN; - execfile("data/default_map.cfg", false); - identflags &= ~IDF_OVERRIDDEN; - } + if(usecfg) + { + identflags |= IDF_OVERRIDDEN; + execfile("data/default_map.cfg", false); + identflags &= ~IDF_OVERRIDDEN; + } - initlights(); - allchanged(true); + initlights(); + allchanged(true); - startmap(mname); + startmap(mname); - return true; + return true; } bool enlargemap(bool force) { - if(!force && !editmode) - { - conoutf(CON_ERROR, "mapenlarge only allowed in edit mode"); - return false; - } - if(worldsize >= 1<<16) return false; + if(!force && !editmode) + { + conoutf(CON_ERROR, "mapenlarge only allowed in edit mode"); + return false; + } + if(worldsize >= 1<<16) return false; - while(outsideents.length()) removeentity(outsideents.pop()); + while(outsideents.length()) removeentity(outsideents.pop()); - worldscale++; - worldsize *= 2; - cube *c = newcubes(F_EMPTY); - c[0].children = worldroot; - loopi(3) solidfaces(c[i+1]); - worldroot = c; + worldscale++; + worldsize *= 2; + cube *c = newcubes(F_EMPTY); + c[0].children = worldroot; + loopi(3) solidfaces(c[i+1]); + worldroot = c; - if(worldsize > 0x1000) splitocta(worldroot, worldsize>>1); + if(worldsize > 0x1000) splitocta(worldroot, worldsize>>1); - enlargeblendmap(); + enlargeblendmap(); - allchanged(); + allchanged(); - return true; + return true; } static bool isallempty(cube &c) { - if(!c.children) return isempty(c); - loopi(8) if(!isallempty(c.children[i])) return false; - return true; + if(!c.children) return isempty(c); + loopi(8) if(!isallempty(c.children[i])) return false; + return true; } void shrinkmap() { - extern int nompedit; - if(noedit(true) || (nompedit && multiplayer())) return; - if(worldsize <= 1<<10) return; + extern int nompedit; + if(noedit(true) || (nompedit && multiplayer())) return; + if(worldsize <= 1<<10) return; - int octant = -1; - loopi(8) if(!isallempty(worldroot[i])) - { - if(octant >= 0) return; - octant = i; - } - if(octant < 0) return; + int octant = -1; + loopi(8) if(!isallempty(worldroot[i])) + { + if(octant >= 0) return; + octant = i; + } + if(octant < 0) return; - while(outsideents.length()) removeentity(outsideents.pop()); + while(outsideents.length()) removeentity(outsideents.pop()); - if(!worldroot[octant].children) subdividecube(worldroot[octant], false, false); - cube *root = worldroot[octant].children; - worldroot[octant].children = NULL; - freeocta(worldroot); - worldroot = root; - worldscale--; - worldsize /= 2; + if(!worldroot[octant].children) subdividecube(worldroot[octant], false, false); + cube *root = worldroot[octant].children; + worldroot[octant].children = NULL; + freeocta(worldroot); + worldroot = root; + worldscale--; + worldsize /= 2; - ivec offset(octant, ivec(0, 0, 0), worldsize); - vector &ents = entities::getents(); - loopv(ents) ents[i]->o.sub(vec(offset)); + ivec offset(octant, ivec(0, 0, 0), worldsize); + vector &ents = entities::getents(); + loopv(ents) ents[i]->o.sub(vec(offset)); - shrinkblendmap(octant); + shrinkblendmap(octant); - allchanged(); + allchanged(); - conoutf("shrunk map to size %d", worldscale); + conoutf("shrunk map to size %d", worldscale); } void newmap(int *i) { bool force = !isconnected(); if(force) game::forceedit(""); if(emptymap(*i, force, NULL)) game::newmap(max(*i, 0)); } @@ -1357,35 +1356,35 @@ COMMAND(shrinkmap, ""); void mapname() { - result(game::getclientmap()); + result(game::getclientmap()); } COMMAND(mapname, ""); void mpeditent(int i, const vec &o, int type, int attr1, int attr2, int attr3, int attr4, int attr5, bool local) { - if(i < 0 || i >= MAXENTS) return; - vector &ents = entities::getents(); - if(ents.length()<=i) - { - extentity *e = newentity(local, o, type, attr1, attr2, attr3, attr4, attr5, i); - if(!e) return; - addentity(i); - attachentity(*e); - } - else - { - extentity &e = *ents[i]; - removeentity(i); - int oldtype = e.type; - if(oldtype!=type) detachentity(e); - e.type = type; - e.o = o; - e.attr1 = attr1; e.attr2 = attr2; e.attr3 = attr3; e.attr4 = attr4; e.attr5 = attr5; - addentity(i); - if(oldtype!=type) attachentity(e); - } - entities::editent(i, local); + if(i < 0 || i >= MAXENTS) return; + vector &ents = entities::getents(); + if(ents.length()<=i) + { + extentity *e = newentity(local, o, type, attr1, attr2, attr3, attr4, attr5, i); + if(!e) return; + addentity(i); + attachentity(*e); + } + else + { + extentity &e = *ents[i]; + removeentity(i); + int oldtype = e.type; + if(oldtype!=type) detachentity(e); + e.type = type; + e.o = o; + e.attr1 = attr1; e.attr2 = attr2; e.attr3 = attr3; e.attr4 = attr4; e.attr5 = attr5; + addentity(i); + if(oldtype!=type) attachentity(e); + } + entities::editent(i, local); } int getworldsize() { return worldsize; } diff --git a/src/engine/world.h b/src/engine/world.h index 9c8c502..ef4a5e6 100644 --- a/src/engine/world.h +++ b/src/engine/world.h @@ -1,46 +1,46 @@ -enum // hardcoded texture numbers +enum // hardcoded texture numbers { - DEFAULT_SKY = 0, - DEFAULT_GEOM + DEFAULT_SKY = 0, + DEFAULT_GEOM }; -#define MAPVERSION 33 // bump if map format changes, see worldio.cpp +#define MAPVERSION 34 // bump if map format changes, see worldio.cpp struct octaheader { - char magic[4]; // "OCTA" - int version; // any >8bit quantity is little endian - int headersize; // sizeof(header) - int worldsize; - int numents; - int numpvs; - int lightmaps; - int blendmap; - int numvars; - int numvslots; + char magic[4]; // "OCTA" + int version; // any >8bit quantity is little endian + int headersize; // sizeof(header) + int worldsize; + int numents; + int numpvs; + int lightmaps; + int blendmap; + int numvars; + int numvslots; }; -struct compatheader // map file format header +struct compatheader // map file format header { - char magic[4]; // "OCTA" - int version; // any >8bit quantity is little endian - int headersize; // sizeof(header) - int worldsize; - int numents; - int numpvs; - int lightmaps; - int lightprecision, lighterror, lightlod; - uchar ambient; - uchar watercolour[3]; - uchar blendmap; - uchar lerpangle, lerpsubdiv, lerpsubdivsize; - uchar bumperror; - uchar skylight[3]; - uchar lavacolour[3]; - uchar waterfallcolour[3]; - uchar reserved[10]; - char maptitle[128]; + char magic[4]; // "OCTA" + int version; // any >8bit quantity is little endian + int headersize; // sizeof(header) + int worldsize; + int numents; + int numpvs; + int lightmaps; + int lightprecision, lighterror, lightlod; + uchar ambient; + uchar watercolour[3]; + uchar blendmap; + uchar lerpangle, lerpsubdiv, lerpsubdivsize; + uchar bumperror; + uchar skylight[3]; + uchar lavacolour[3]; + uchar waterfallcolour[3]; + uchar reserved[10]; + char maptitle[128]; }; #define WATER_AMPLITUDE 0.4f @@ -48,9 +48,9 @@ struct compatheader // map file format header enum { - MATSURF_NOT_VISIBLE = 0, - MATSURF_VISIBLE, - MATSURF_EDIT_ONLY + MATSURF_NOT_VISIBLE = 0, + MATSURF_VISIBLE, + MATSURF_EDIT_ONLY }; #define TEX_SCALE 8.0f diff --git a/src/engine/worldio.cpp b/src/engine/worldio.cpp index d1fd52f..34823fa 100644 --- a/src/engine/worldio.cpp +++ b/src/engine/worldio.cpp @@ -4,169 +4,169 @@ void validmapname(char *dst, const char *src, const char *prefix = NULL, const char *alt = "untitled", size_t maxlen = 100) { - if(prefix) while(*prefix) *dst++ = *prefix++; - const char *start = dst; - if(src) loopi(maxlen) - { - char c = *src++; - if(iscubealnum(c) || c == '_' || c == '-' || c == '/' || c == '\\') *dst++ = c; - else break; - } - if(dst > start) *dst = '\0'; - else if(dst != alt) copystring(dst, alt, maxlen); + if(prefix) while(*prefix) *dst++ = *prefix++; + const char *start = dst; + if(src) loopi(maxlen) + { + char c = *src++; + if(iscubealnum(c) || c == '_' || c == '-' || c == '/' || c == '\\') *dst++ = c; + else break; + } + if(dst > start) *dst = '\0'; + else if(dst != alt) copystring(dst, alt, maxlen); } void fixmapname(char *name) { - validmapname(name, name, NULL, ""); + validmapname(name, name, NULL, ""); } void getmapfilenames(const char *fname, const char *cname, char *pakname, char *mapname, char *cfgname) { - if(!cname) cname = fname; - string name; - validmapname(name, cname); - char *slash = strpbrk(name, "/\\"); - if(slash) - { - copystring(pakname, name, slash-name+1); - copystring(cfgname, slash+1, MAXSTRLEN); - } - else - { - copystring(pakname, "maps", MAXSTRLEN); - copystring(cfgname, name, MAXSTRLEN); - } - validmapname(mapname, fname, strpbrk(fname, "/\\") ? NULL : "maps/"); + if(!cname) cname = fname; + string name; + validmapname(name, cname); + char *slash = strpbrk(name, "/\\"); + if(slash) + { + copystring(pakname, name, slash-name+1); + copystring(cfgname, slash+1, MAXSTRLEN); + } + else + { + copystring(pakname, "maps", MAXSTRLEN); + copystring(cfgname, name, MAXSTRLEN); + } + validmapname(mapname, fname, strpbrk(fname, "/\\") ? NULL : "maps/"); } static void fixent(entity &e, int version) { - if(version <= 10 && e.type >= 7) e.type++; - if(version <= 12 && e.type >= 8) e.type++; - if(version <= 14 && e.type >= ET_MAPMODEL && e.type <= 16) - { - if(e.type == 16) e.type = ET_MAPMODEL; - else e.type++; - } - if(version <= 20 && e.type >= ET_ENVMAP) e.type++; - if(version <= 21 && e.type >= ET_PARTICLES) e.type++; - if(version <= 22 && e.type >= ET_SOUND) e.type++; - if(version <= 23 && e.type >= ET_SPOTLIGHT) e.type++; - if(version <= 30 && (e.type == ET_MAPMODEL || e.type == ET_PLAYERSTART)) e.attr1 = (int(e.attr1)+180)%360; - if(version <= 31 && e.type == ET_MAPMODEL) { int yaw = (int(e.attr1)%360 + 360)%360 + 7; e.attr1 = yaw - yaw%15; } + if(version <= 10 && e.type >= 7) e.type++; + if(version <= 12 && e.type >= 8) e.type++; + if(version <= 14 && e.type >= ET_MAPMODEL && e.type <= 16) + { + if(e.type == 16) e.type = ET_MAPMODEL; + else e.type++; + } + if(version <= 20 && e.type >= ET_ENVMAP) e.type++; + if(version <= 21 && e.type >= ET_PARTICLES) e.type++; + if(version <= 22 && e.type >= ET_SOUND) e.type++; + if(version <= 23 && e.type >= ET_SPOTLIGHT) e.type++; + if(version <= 30 && (e.type == ET_MAPMODEL || e.type == ET_PLAYERSTART)) e.attr1 = (int(e.attr1)+180)%360; + if(version <= 31 && e.type == ET_MAPMODEL) { int yaw = (int(e.attr1)%360 + 360)%360 + 7; e.attr1 = yaw - yaw%15; } } bool loadents(const char *fname, vector &ents, uint *crc) { - string pakname, mapname, mcfgname, ogzname; - getmapfilenames(fname, NULL, pakname, mapname, mcfgname); - formatstring(ogzname, "packages/%s.ogz", mapname); - path(ogzname); - stream *f = opengzfile(ogzname, "rb"); - if(!f) return false; - octaheader hdr; - if(f->read(&hdr, 7*sizeof(int)) != 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - lilswap(&hdr.version, 6); - if(memcmp(hdr.magic, "OCTA", 4) || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of Cube 2: Sauerbraten", ogzname); delete f; return false; } - compatheader chdr; - if(hdr.version <= 28) - { - if(f->read(&chdr.lightprecision, sizeof(chdr) - 7*sizeof(int)) != sizeof(chdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - } - else - { - int extra = 0; - if(hdr.version <= 29) extra++; - if(f->read(&hdr.blendmap, sizeof(hdr) - (7+extra)*sizeof(int)) != sizeof(hdr) - (7+extra)*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - } - - if(hdr.version <= 28) - { - lilswap(&chdr.lightprecision, 3); - hdr.blendmap = chdr.blendmap; - hdr.numvars = 0; - hdr.numvslots = 0; - } - else - { - lilswap(&hdr.blendmap, 2); - if(hdr.version <= 29) hdr.numvslots = 0; - else lilswap(&hdr.numvslots, 1); - } - - loopi(hdr.numvars) - { - int type = f->getchar(), ilen = f->getlil(); - f->seek(ilen, SEEK_CUR); - switch(type) - { - case ID_VAR: f->getlil(); break; - case ID_FVAR: f->getlil(); break; - case ID_SVAR: { int slen = f->getlil(); f->seek(slen, SEEK_CUR); break; } - } - } - - string gametype; - copystring(gametype, "fps"); - bool samegame = true; - int eif = 0; - if(hdr.version>=16) - { - int len = f->getchar(); - f->read(gametype, len+1); - } - if(strcmp(gametype, game::gameident())) - { - samegame = false; - conoutf(CON_WARN, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels", gametype); - } - if(hdr.version>=16) - { - eif = f->getlil(); - int extrasize = f->getlil(); - f->seek(extrasize, SEEK_CUR); - } - - if(hdr.version<14) - { - f->seek(256, SEEK_CUR); - } - else - { - ushort nummru = f->getlil(); - f->seek(nummru*sizeof(ushort), SEEK_CUR); - } - - loopi(min(hdr.numents, MAXENTS)) - { - entity &e = ents.add(); - f->read(&e, sizeof(entity)); - lilswap(&e.o.x, 3); - lilswap(&e.attr1, 5); - fixent(e, hdr.version); - if(eif > 0) f->seek(eif, SEEK_CUR); - if(samegame) - { - entities::readent(e, NULL, hdr.version); - } - else if(e.type>=ET_GAMESPECIFIC || hdr.version<=14) - { - ents.pop(); - continue; - } - } - - if(crc) - { - f->seek(0, SEEK_END); - *crc = f->getcrc(); - } - - delete f; - - return true; + string pakname, mapname, mcfgname, ogzname; + getmapfilenames(fname, NULL, pakname, mapname, mcfgname); + formatstring(ogzname, "packages/%s.ogz", mapname); + path(ogzname); + stream *f = opengzfile(ogzname, "rb"); + if(!f) return false; + octaheader hdr; + if(f->read(&hdr, 7*sizeof(int)) != 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + lilswap(&hdr.version, 6); + if(memcmp(hdr.magic, "OCTA", 4) || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of Cube 2: Sauerbraten", ogzname); delete f; return false; } + compatheader chdr; + if(hdr.version <= 28) + { + if(f->read(&chdr.lightprecision, sizeof(chdr) - 7*sizeof(int)) != sizeof(chdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + } + else + { + int extra = 0; + if(hdr.version <= 29) extra++; + if(f->read(&hdr.blendmap, sizeof(hdr) - (7+extra)*sizeof(int)) != sizeof(hdr) - (7+extra)*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + } + + if(hdr.version <= 28) + { + lilswap(&chdr.lightprecision, 3); + hdr.blendmap = chdr.blendmap; + hdr.numvars = 0; + hdr.numvslots = 0; + } + else + { + lilswap(&hdr.blendmap, 2); + if(hdr.version <= 29) hdr.numvslots = 0; + else lilswap(&hdr.numvslots, 1); + } + + loopi(hdr.numvars) + { + int type = f->getchar(), ilen = f->getlil(); + f->seek(ilen, SEEK_CUR); + switch(type) + { + case ID_VAR: f->getlil(); break; + case ID_FVAR: f->getlil(); break; + case ID_SVAR: { int slen = f->getlil(); f->seek(slen, SEEK_CUR); break; } + } + } + + string gametype; + copystring(gametype, "fps"); + bool samegame = true; + int eif = 0; + if(hdr.version>=16) + { + int len = f->getchar(); + f->read(gametype, len+1); + } + if(strcmp(gametype, game::gameident())) + { + samegame = false; + conoutf(CON_WARN, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels", gametype); + } + if(hdr.version>=16) + { + eif = f->getlil(); + int extrasize = f->getlil(); + f->seek(extrasize, SEEK_CUR); + } + + if(hdr.version<14) + { + f->seek(256, SEEK_CUR); + } + else + { + ushort nummru = f->getlil(); + f->seek(nummru*sizeof(ushort), SEEK_CUR); + } + + loopi(min(hdr.numents, MAXENTS)) + { + entity &e = ents.add(); + f->read(&e, sizeof(entity)); + lilswap(&e.o.x, 3); + lilswap(&e.attr1, 5); + fixent(e, hdr.version); + if(eif > 0) f->seek(eif, SEEK_CUR); + if(samegame) + { + entities::readent(e, NULL, hdr.version); + } + else if(e.type>=ET_GAMESPECIFIC || hdr.version<=14) + { + ents.pop(); + continue; + } + } + + if(crc) + { + f->seek(0, SEEK_END); + *crc = f->getcrc(); + } + + delete f; + + return true; } #ifndef STANDALONE @@ -176,39 +176,39 @@ VARP(savebak, 0, 2, 2); void setmapfilenames(const char *fname, const char *cname = NULL) { - string pakname, mapname, mcfgname; - getmapfilenames(fname, cname, pakname, mapname, mcfgname); - - formatstring(ogzname, "packages/%s.ogz", mapname); - if(savebak==1) formatstring(bakname, "packages/%s.BAK", mapname); - else formatstring(bakname, "packages/%s_%d.BAK", mapname, totalmillis); - formatstring(cfgname, "packages/%s/%s.cfg", pakname, mcfgname); - formatstring(picname, "packages/%s.png", mapname); - - path(ogzname); - path(bakname); - path(cfgname); - path(picname); + string pakname, mapname, mcfgname; + getmapfilenames(fname, cname, pakname, mapname, mcfgname); + + formatstring(ogzname, "packages/%s.ogz", mapname); + if(savebak==1) formatstring(bakname, "packages/%s.BAK", mapname); + else formatstring(bakname, "packages/%s_%d.BAK", mapname, totalmillis); + formatstring(cfgname, "packages/%s/%s.cfg", pakname, mcfgname); + formatstring(picname, "packages/%s.png", mapname); + + path(ogzname); + path(bakname); + path(cfgname); + path(picname); } void mapcfgname() { - const char *mname = game::getclientmap(); - string pakname, mapname, mcfgname; - getmapfilenames(mname, NULL, pakname, mapname, mcfgname); - defformatstring(cfgname, "packages/%s/%s.cfg", pakname, mcfgname); - path(cfgname); - result(cfgname); + const char *mname = game::getclientmap(); + string pakname, mapname, mcfgname; + getmapfilenames(mname, NULL, pakname, mapname, mcfgname); + defformatstring(cfgname, "packages/%s/%s.cfg", pakname, mcfgname); + path(cfgname); + result(cfgname); } COMMAND(mapcfgname, ""); void backup(char *name, char *backupname) { - string backupfile; - copystring(backupfile, findfile(backupname, "wb")); - remove(backupfile); - rename(findfile(name, "wb"), backupfile); + string backupfile; + copystring(backupfile, findfile(backupname, "wb")); + remove(backupfile); + rename(findfile(name, "wb"), backupfile); } enum { OCTSAV_CHILDREN = 0, OCTSAV_EMPTY, OCTSAV_SOLID, OCTSAV_NORMAL, OCTSAV_LODCUBE }; @@ -217,773 +217,773 @@ static int savemapprogress = 0; void savec(cube *c, const ivec &o, int size, stream *f, bool nolms) { - if((savemapprogress++&0xFFF)==0) renderprogress(float(savemapprogress)/allocnodes, "saving octree..."); - - loopi(8) - { - ivec co(i, o, size); - if(c[i].children) - { - f->putchar(OCTSAV_CHILDREN); - savec(c[i].children, co, size>>1, f, nolms); - } - else - { - int oflags = 0, surfmask = 0, totalverts = 0; - if(c[i].material!=MAT_AIR) oflags |= 0x40; - if(isempty(c[i])) f->putchar(oflags | OCTSAV_EMPTY); - else - { - if(!nolms) - { - if(c[i].merged) oflags |= 0x80; - if(c[i].ext) loopj(6) - { - const surfaceinfo &surf = c[i].ext->surfaces[j]; - if(!surf.used()) continue; - oflags |= 0x20; - surfmask |= 1<putchar(oflags | OCTSAV_SOLID); - else - { - f->putchar(oflags | OCTSAV_NORMAL); - f->write(c[i].edges, 12); - } - } - - loopj(6) f->putlil(c[i].texture[j]); - - if(oflags&0x40) f->putlil(c[i].material); - if(oflags&0x80) f->putchar(c[i].merged); - if(oflags&0x20) - { - f->putchar(surfmask); - f->putchar(totalverts); - loopj(6) if(surfmask&(1<surfaces[j]; - vertinfo *verts = c[i].ext->verts() + surf.verts; - int layerverts = surf.numverts&MAXFACEVERTS, numverts = surf.totalverts(), - vertmask = 0, vertorder = 0, uvorder = 0, - dim = dimension(j), vc = C[dim], vr = R[dim]; - if(numverts) - { - if(c[i].merged&(1<write(&surf, sizeof(surfaceinfo)); - bool hasxyz = (vertmask&0x04)!=0, hasuv = (vertmask&0x40)!=0, hasnorm = (vertmask&0x80)!=0; - if(layerverts == 4) - { - if(hasxyz && vertmask&0x01) - { - ivec v0 = verts[vertorder].getxyz(), v2 = verts[(vertorder+2)&3].getxyz(); - f->putlil(v0[vc]); f->putlil(v0[vr]); - f->putlil(v2[vc]); f->putlil(v2[vr]); - hasxyz = false; - } - if(hasuv && vertmask&0x02) - { - const vertinfo &v0 = verts[uvorder], &v2 = verts[(uvorder+2)&3]; - f->putlil(v0.u); f->putlil(v0.v); - f->putlil(v2.u); f->putlil(v2.v); - if(surf.numverts&LAYER_DUP) - { - const vertinfo &b0 = verts[4+uvorder], &b2 = verts[4+((uvorder+2)&3)]; - f->putlil(b0.u); f->putlil(b0.v); - f->putlil(b2.u); f->putlil(b2.v); - } - hasuv = false; - } - } - if(hasnorm && vertmask&0x08) { f->putlil(verts[0].norm); hasnorm = false; } - if(hasxyz || hasuv || hasnorm) loopk(layerverts) - { - const vertinfo &v = verts[(k+vertorder)%layerverts]; - if(hasxyz) - { - ivec xyz = v.getxyz(); - f->putlil(xyz[vc]); f->putlil(xyz[vr]); - } - if(hasuv) { f->putlil(v.u); f->putlil(v.v); } - if(hasnorm) f->putlil(v.norm); - } - if(surf.numverts&LAYER_DUP) loopk(layerverts) - { - const vertinfo &v = verts[layerverts + (k+vertorder)%layerverts]; - if(hasuv) { f->putlil(v.u); f->putlil(v.v); } - } - } - } - } - } + if((savemapprogress++&0xFFF)==0) renderprogress(float(savemapprogress)/allocnodes, "saving octree..."); + + loopi(8) + { + ivec co(i, o, size); + if(c[i].children) + { + f->putchar(OCTSAV_CHILDREN); + savec(c[i].children, co, size>>1, f, nolms); + } + else + { + int oflags = 0, surfmask = 0, totalverts = 0; + if(c[i].material!=MAT_AIR) oflags |= 0x40; + if(isempty(c[i])) f->putchar(oflags | OCTSAV_EMPTY); + else + { + if(!nolms) + { + if(c[i].merged) oflags |= 0x80; + if(c[i].ext) loopj(6) + { + const surfaceinfo &surf = c[i].ext->surfaces[j]; + if(!surf.used()) continue; + oflags |= 0x20; + surfmask |= 1<putchar(oflags | OCTSAV_SOLID); + else + { + f->putchar(oflags | OCTSAV_NORMAL); + f->write(c[i].edges, 12); + } + } + + loopj(6) f->putlil(c[i].texture[j]); + + if(oflags&0x40) f->putlil(c[i].material); + if(oflags&0x80) f->putchar(c[i].merged); + if(oflags&0x20) + { + f->putchar(surfmask); + f->putchar(totalverts); + loopj(6) if(surfmask&(1<surfaces[j]; + vertinfo *verts = c[i].ext->verts() + surf.verts; + int layerverts = surf.numverts&MAXFACEVERTS, numverts = surf.totalverts(), + vertmask = 0, vertorder = 0, uvorder = 0, + dim = dimension(j), vc = C[dim], vr = R[dim]; + if(numverts) + { + if(c[i].merged&(1<write(&surf, sizeof(surfaceinfo)); + bool hasxyz = (vertmask&0x04)!=0, hasuv = (vertmask&0x40)!=0, hasnorm = (vertmask&0x80)!=0; + if(layerverts == 4) + { + if(hasxyz && vertmask&0x01) + { + ivec v0 = verts[vertorder].getxyz(), v2 = verts[(vertorder+2)&3].getxyz(); + f->putlil(v0[vc]); f->putlil(v0[vr]); + f->putlil(v2[vc]); f->putlil(v2[vr]); + hasxyz = false; + } + if(hasuv && vertmask&0x02) + { + const vertinfo &v0 = verts[uvorder], &v2 = verts[(uvorder+2)&3]; + f->putlil(v0.u); f->putlil(v0.v); + f->putlil(v2.u); f->putlil(v2.v); + if(surf.numverts&LAYER_DUP) + { + const vertinfo &b0 = verts[4+uvorder], &b2 = verts[4+((uvorder+2)&3)]; + f->putlil(b0.u); f->putlil(b0.v); + f->putlil(b2.u); f->putlil(b2.v); + } + hasuv = false; + } + } + if(hasnorm && vertmask&0x08) { f->putlil(verts[0].norm); hasnorm = false; } + if(hasxyz || hasuv || hasnorm) loopk(layerverts) + { + const vertinfo &v = verts[(k+vertorder)%layerverts]; + if(hasxyz) + { + ivec xyz = v.getxyz(); + f->putlil(xyz[vc]); f->putlil(xyz[vr]); + } + if(hasuv) { f->putlil(v.u); f->putlil(v.v); } + if(hasnorm) f->putlil(v.norm); + } + if(surf.numverts&LAYER_DUP) loopk(layerverts) + { + const vertinfo &v = verts[layerverts + (k+vertorder)%layerverts]; + if(hasuv) { f->putlil(v.u); f->putlil(v.v); } + } + } + } + } + } } struct surfacecompat { - uchar texcoords[8]; - uchar w, h; - ushort x, y; - uchar lmid, layer; + uchar texcoords[8]; + uchar w, h; + ushort x, y; + uchar lmid, layer; }; struct normalscompat { - bvec normals[4]; + bvec normals[4]; }; struct mergecompat { - ushort u1, u2, v1, v2; + ushort u1, u2, v1, v2; }; cube *loadchildren(stream *f, const ivec &co, int size, bool &failed); void convertoldsurfaces(cube &c, const ivec &co, int size, surfacecompat *srcsurfs, int hassurfs, normalscompat *normals, int hasnorms, mergecompat *merges, int hasmerges) { - surfaceinfo dstsurfs[6]; - vertinfo verts[6*2*MAXFACEVERTS]; - int totalverts = 0, numsurfs = 6; - memset(dstsurfs, 0, sizeof(dstsurfs)); - loopi(6) if((hassurfs|hasnorms|hasmerges)&(1<layer&2) - { - blend = &srcsurfs[numsurfs++]; - dst.lmid[0] = src->lmid; - dst.lmid[1] = blend->lmid; - dst.numverts |= LAYER_BLEND; - if(blend->lmid >= LMID_RESERVED && (src->x != blend->x || src->y != blend->y || src->w != blend->w || src->h != blend->h || memcmp(src->texcoords, blend->texcoords, sizeof(src->texcoords)))) - dst.numverts |= LAYER_DUP; - } - else if(src->layer == 1) { dst.lmid[1] = src->lmid; dst.numverts |= LAYER_BOTTOM; } - else { dst.lmid[0] = src->lmid; dst.numverts |= LAYER_TOP; } - } - else dst.numverts |= LAYER_TOP; - bool uselms = hassurfs&(1<= LMID_RESERVED || dst.lmid[1] >= LMID_RESERVED || dst.numverts&~LAYER_TOP), - usemerges = hasmerges&(1< 0 && (pos[k] == pos[0] || pos[k] == pos[k-1])) continue; - vertinfo &dv = curverts[numverts++]; - dv.setxyz(pos[k]); - if(uselms) - { - float u = src->x + (src->texcoords[k*2] / 255.0f) * (src->w - 1), - v = src->y + (src->texcoords[k*2+1] / 255.0f) * (src->h - 1); - dv.u = ushort(floor(clamp((u) * float(USHRT_MAX+1)/LM_PACKW + 0.5f, 0.0f, float(USHRT_MAX)))); - dv.v = ushort(floor(clamp((v) * float(USHRT_MAX+1)/LM_PACKH + 0.5f, 0.0f, float(USHRT_MAX)))); - } - else dv.u = dv.v = 0; - dv.norm = usenorms && normals[i].normals[k] != bvec(128, 128, 128) ? encodenormal(normals[i].normals[k].tonormal().normalize()) : 0; - } - dst.verts = totalverts; - dst.numverts |= numverts; - totalverts += numverts; - if(dst.numverts&LAYER_DUP) loopk(4) - { - if(k > 0 && (pos[k] == pos[0] || pos[k] == pos[k-1])) continue; - vertinfo &bv = verts[totalverts++]; - bv.setxyz(pos[k]); - bv.u = ushort(floor(clamp((blend->x + (blend->texcoords[k*2] / 255.0f) * (blend->w - 1)) * float(USHRT_MAX+1)/LM_PACKW, 0.0f, float(USHRT_MAX)))); - bv.v = ushort(floor(clamp((blend->y + (blend->texcoords[k*2+1] / 255.0f) * (blend->h - 1)) * float(USHRT_MAX+1)/LM_PACKH, 0.0f, float(USHRT_MAX)))); - bv.norm = usenorms && normals[i].normals[k] != bvec(128, 128, 128) ? encodenormal(normals[i].normals[k].tonormal().normalize()) : 0; - } - } - } - setsurfaces(c, dstsurfs, verts, totalverts); + surfaceinfo dstsurfs[6]; + vertinfo verts[6*2*MAXFACEVERTS]; + int totalverts = 0, numsurfs = 6; + memset(dstsurfs, 0, sizeof(dstsurfs)); + loopi(6) if((hassurfs|hasnorms|hasmerges)&(1<layer&2) + { + blend = &srcsurfs[numsurfs++]; + dst.lmid[0] = src->lmid; + dst.lmid[1] = blend->lmid; + dst.numverts |= LAYER_BLEND; + if(blend->lmid >= LMID_RESERVED && (src->x != blend->x || src->y != blend->y || src->w != blend->w || src->h != blend->h || memcmp(src->texcoords, blend->texcoords, sizeof(src->texcoords)))) + dst.numverts |= LAYER_DUP; + } + else if(src->layer == 1) { dst.lmid[1] = src->lmid; dst.numverts |= LAYER_BOTTOM; } + else { dst.lmid[0] = src->lmid; dst.numverts |= LAYER_TOP; } + } + else dst.numverts |= LAYER_TOP; + bool uselms = hassurfs&(1<= LMID_RESERVED || dst.lmid[1] >= LMID_RESERVED || dst.numverts&~LAYER_TOP), + usemerges = hasmerges&(1< 0 && (pos[k] == pos[0] || pos[k] == pos[k-1])) continue; + vertinfo &dv = curverts[numverts++]; + dv.setxyz(pos[k]); + if(uselms) + { + float u = src->x + (src->texcoords[k*2] / 255.0f) * (src->w - 1), + v = src->y + (src->texcoords[k*2+1] / 255.0f) * (src->h - 1); + dv.u = ushort(floor(clamp((u) * float(USHRT_MAX+1)/LM_PACKW + 0.5f, 0.0f, float(USHRT_MAX)))); + dv.v = ushort(floor(clamp((v) * float(USHRT_MAX+1)/LM_PACKH + 0.5f, 0.0f, float(USHRT_MAX)))); + } + else dv.u = dv.v = 0; + dv.norm = usenorms && normals[i].normals[k] != bvec(128, 128, 128) ? encodenormal(normals[i].normals[k].tonormal().normalize()) : 0; + } + dst.verts = totalverts; + dst.numverts |= numverts; + totalverts += numverts; + if(dst.numverts&LAYER_DUP) loopk(4) + { + if(k > 0 && (pos[k] == pos[0] || pos[k] == pos[k-1])) continue; + vertinfo &bv = verts[totalverts++]; + bv.setxyz(pos[k]); + bv.u = ushort(floor(clamp((blend->x + (blend->texcoords[k*2] / 255.0f) * (blend->w - 1)) * float(USHRT_MAX+1)/LM_PACKW, 0.0f, float(USHRT_MAX)))); + bv.v = ushort(floor(clamp((blend->y + (blend->texcoords[k*2+1] / 255.0f) * (blend->h - 1)) * float(USHRT_MAX+1)/LM_PACKH, 0.0f, float(USHRT_MAX)))); + bv.norm = usenorms && normals[i].normals[k] != bvec(128, 128, 128) ? encodenormal(normals[i].normals[k].tonormal().normalize()) : 0; + } + } + } + setsurfaces(c, dstsurfs, verts, totalverts); } static inline int convertoldmaterial(int mat) { - return ((mat&7)<>3)&3)<>5)&7)<>3)&3)<>5)&7)<getchar(); - switch(octsav&0x7) - { - case OCTSAV_CHILDREN: - c.children = loadchildren(f, co, size>>1, failed); - return; - - case OCTSAV_LODCUBE: haschildren = true; break; - case OCTSAV_EMPTY: emptyfaces(c); break; - case OCTSAV_SOLID: solidfaces(c); break; - case OCTSAV_NORMAL: f->read(c.edges, 12); break; - default: failed = true; return; - } - loopi(6) c.texture[i] = mapversion<14 ? f->getchar() : f->getlil(); - if(mapversion < 7) f->seek(3, SEEK_CUR); - else if(mapversion <= 31) - { - uchar mask = f->getchar(); - if(mask & 0x80) - { - int mat = f->getchar(); - if(mapversion < 27) - { - static const ushort matconv[] = { MAT_AIR, MAT_WATER, MAT_CLIP, MAT_GLASS|MAT_CLIP, MAT_NOCLIP, MAT_LAVA|MAT_DEATH, MAT_GAMECLIP, MAT_DEATH }; - c.material = size_t(mat) < sizeof(matconv)/sizeof(matconv[0]) ? (int) matconv[mat] : (int) MAT_AIR; - } - else c.material = convertoldmaterial(mat); - } - surfacecompat surfaces[12]; - normalscompat normals[6]; - mergecompat merges[6]; - int hassurfs = 0, hasnorms = 0, hasmerges = 0; - if(mask & 0x3F) - { - int numsurfs = 6; - loopi(numsurfs) - { - if(i >= 6 || mask & (1 << i)) - { - f->read(&surfaces[i], sizeof(surfacecompat)); - lilswap(&surfaces[i].x, 2); - if(mapversion < 10) ++surfaces[i].lmid; - if(mapversion < 18) - { - if(surfaces[i].lmid >= LMID_AMBIENT1) ++surfaces[i].lmid; - if(surfaces[i].lmid >= LMID_BRIGHT1) ++surfaces[i].lmid; - } - if(mapversion < 19) - { - if(surfaces[i].lmid >= LMID_DARK) surfaces[i].lmid += 2; - } - if(i < 6) - { - if(mask & 0x40) { hasnorms |= 1<read(&normals[i], sizeof(normalscompat)); } - if(surfaces[i].layer != 0 || surfaces[i].lmid != LMID_AMBIENT) - hassurfs |= 1<>4) | ((hassurfs&0x03)<<4); - } - } - if(mapversion >= 20) - { - if(octsav&0x80) - { - int merged = f->getchar(); - c.merged = merged&0x3F; - if(merged&0x80) - { - int mask = f->getchar(); - if(mask) - { - hasmerges = mask&0x3F; - loopi(6) if(mask&(1<read(m, sizeof(mergecompat)); - lilswap(&m->u1, 4); - if(mapversion <= 25) - { - int uorigin = m->u1 & 0xE000, vorigin = m->v1 & 0xE000; - m->u1 = (m->u1 - uorigin) << 2; - m->u2 = (m->u2 - uorigin) << 2; - m->v1 = (m->v1 - vorigin) << 2; - m->v2 = (m->v2 - vorigin) << 2; - } - } - } - } - } - } - if(hassurfs || hasnorms || hasmerges) - convertoldsurfaces(c, co, size, surfaces, hassurfs, normals, hasnorms, merges, hasmerges); - } - else - { - if(octsav&0x40) - { - if(mapversion <= 32) - { - int mat = f->getchar(); - c.material = convertoldmaterial(mat); - } - else c.material = f->getlil(); - } - if(octsav&0x80) c.merged = f->getchar(); - if(octsav&0x20) - { - int surfmask, totalverts; - surfmask = f->getchar(); - totalverts = max(f->getchar(), 0); - newcubeext(c, totalverts, false); - memset(c.ext->surfaces, 0, sizeof(c.ext->surfaces)); - memset(c.ext->verts(), 0, totalverts*sizeof(vertinfo)); - int offset = 0; - loopi(6) if(surfmask&(1<surfaces[i]; - f->read(&surf, sizeof(surfaceinfo)); - int vertmask = surf.verts, numverts = surf.totalverts(); - if(!numverts) { surf.verts = 0; continue; } - surf.verts = offset; - vertinfo *verts = c.ext->verts() + offset; - offset += numverts; - ivec v[4], n, vo = ivec(co).mask(0xFFF).shl(3); - int layerverts = surf.numverts&MAXFACEVERTS, dim = dimension(i), vc = C[dim], vr = R[dim], bias = 0; - genfaceverts(c, i, v); - bool hasxyz = (vertmask&0x04)!=0, hasuv = (vertmask&0x40)!=0, hasnorm = (vertmask&0x80)!=0; - if(hasxyz) - { - ivec e1, e2, e3; - n.cross((e1 = v[1]).sub(v[0]), (e2 = v[2]).sub(v[0])); - if(n.iszero()) n.cross(e2, (e3 = v[3]).sub(v[0])); - bias = -n.dot(ivec(v[0]).mul(size).add(vo)); - } - else - { - int vis = layerverts < 4 ? (vertmask&0x02 ? 2 : 1) : 3, order = vertmask&0x01 ? 1 : 0, k = 0; - verts[k++].setxyz(v[order].mul(size).add(vo)); - if(vis&1) verts[k++].setxyz(v[order+1].mul(size).add(vo)); - verts[k++].setxyz(v[order+2].mul(size).add(vo)); - if(vis&2) verts[k++].setxyz(v[(order+3)&3].mul(size).add(vo)); - } - if(layerverts == 4) - { - if(hasxyz && vertmask&0x01) - { - ushort c1 = f->getlil(), r1 = f->getlil(), c2 = f->getlil(), r2 = f->getlil(); - ivec xyz; - xyz[vc] = c1; xyz[vr] = r1; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; - verts[0].setxyz(xyz); - xyz[vc] = c1; xyz[vr] = r2; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; - verts[1].setxyz(xyz); - xyz[vc] = c2; xyz[vr] = r2; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; - verts[2].setxyz(xyz); - xyz[vc] = c2; xyz[vr] = r1; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; - verts[3].setxyz(xyz); - hasxyz = false; - } - if(hasuv && vertmask&0x02) - { - int uvorder = (vertmask&0x30)>>4; - vertinfo &v0 = verts[uvorder], &v1 = verts[(uvorder+1)&3], &v2 = verts[(uvorder+2)&3], &v3 = verts[(uvorder+3)&3]; - v0.u = f->getlil(); v0.v = f->getlil(); - v2.u = f->getlil(); v2.v = f->getlil(); - v1.u = v0.u; v1.v = v2.v; - v3.u = v2.u; v3.v = v0.v; - if(surf.numverts&LAYER_DUP) - { - vertinfo &b0 = verts[4+uvorder], &b1 = verts[4+((uvorder+1)&3)], &b2 = verts[4+((uvorder+2)&3)], &b3 = verts[4+((uvorder+3)&3)]; - b0.u = f->getlil(); b0.v = f->getlil(); - b2.u = f->getlil(); b2.v = f->getlil(); - b1.u = b0.u; b1.v = b2.v; - b3.u = b2.u; b3.v = b0.v; - } - hasuv = false; - } - } - if(hasnorm && vertmask&0x08) - { - ushort norm = f->getlil(); - loopk(layerverts) verts[k].norm = norm; - hasnorm = false; - } - if(hasxyz || hasuv || hasnorm) loopk(layerverts) - { - vertinfo &v = verts[k]; - if(hasxyz) - { - ivec xyz; - xyz[vc] = f->getlil(); xyz[vr] = f->getlil(); - xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; - v.setxyz(xyz); - } - if(hasuv) { v.u = f->getlil(); v.v = f->getlil(); } - if(hasnorm) v.norm = f->getlil(); - } - if(surf.numverts&LAYER_DUP) loopk(layerverts) - { - vertinfo &v = verts[k+layerverts], &t = verts[k]; - v.setxyz(t.x, t.y, t.z); - if(hasuv) { v.u = f->getlil(); v.v = f->getlil(); } - v.norm = t.norm; - } - } - } - } - - c.children = (haschildren ? loadchildren(f, co, size>>1, failed) : NULL); + bool haschildren = false; + int octsav = f->getchar(); + switch(octsav&0x7) + { + case OCTSAV_CHILDREN: + c.children = loadchildren(f, co, size>>1, failed); + return; + + case OCTSAV_LODCUBE: haschildren = true; break; + case OCTSAV_EMPTY: emptyfaces(c); break; + case OCTSAV_SOLID: solidfaces(c); break; + case OCTSAV_NORMAL: f->read(c.edges, 12); break; + default: failed = true; return; + } + loopi(6) c.texture[i] = mapversion<14 ? f->getchar() : f->getlil(); + if(mapversion < 7) f->seek(3, SEEK_CUR); + else if(mapversion <= 31) + { + uchar mask = f->getchar(); + if(mask & 0x80) + { + int mat = f->getchar(); + if(mapversion < 27) + { + static const ushort matconv[] = { MAT_AIR, MAT_WATER, MAT_CLIP, MAT_GLASS|MAT_CLIP, MAT_NOCLIP, MAT_LAVA|MAT_DEATH, MAT_GAMECLIP, MAT_DEATH }; + c.material = size_t(mat) < sizeof(matconv)/sizeof(matconv[0]) ? (int) matconv[mat] : (int) MAT_AIR; + } + else c.material = convertoldmaterial(mat); + } + surfacecompat surfaces[12]; + normalscompat normals[6]; + mergecompat merges[6]; + int hassurfs = 0, hasnorms = 0, hasmerges = 0; + if(mask & 0x3F) + { + int numsurfs = 6; + loopi(numsurfs) + { + if(i >= 6 || mask & (1 << i)) + { + f->read(&surfaces[i], sizeof(surfacecompat)); + lilswap(&surfaces[i].x, 2); + if(mapversion < 10) ++surfaces[i].lmid; + if(mapversion < 18) + { + if(surfaces[i].lmid >= LMID_AMBIENT1) ++surfaces[i].lmid; + if(surfaces[i].lmid >= LMID_BRIGHT1) ++surfaces[i].lmid; + } + if(mapversion < 19) + { + if(surfaces[i].lmid >= LMID_DARK) surfaces[i].lmid += 2; + } + if(i < 6) + { + if(mask & 0x40) { hasnorms |= 1<read(&normals[i], sizeof(normalscompat)); } + if(surfaces[i].layer != 0 || surfaces[i].lmid != LMID_AMBIENT) + hassurfs |= 1<>4) | ((hassurfs&0x03)<<4); + } + } + if(mapversion >= 20) + { + if(octsav&0x80) + { + int merged = f->getchar(); + c.merged = merged&0x3F; + if(merged&0x80) + { + int mask = f->getchar(); + if(mask) + { + hasmerges = mask&0x3F; + loopi(6) if(mask&(1<read(m, sizeof(mergecompat)); + lilswap(&m->u1, 4); + if(mapversion <= 25) + { + int uorigin = m->u1 & 0xE000, vorigin = m->v1 & 0xE000; + m->u1 = (m->u1 - uorigin) << 2; + m->u2 = (m->u2 - uorigin) << 2; + m->v1 = (m->v1 - vorigin) << 2; + m->v2 = (m->v2 - vorigin) << 2; + } + } + } + } + } + } + if(hassurfs || hasnorms || hasmerges) + convertoldsurfaces(c, co, size, surfaces, hassurfs, normals, hasnorms, merges, hasmerges); + } + else + { + if(octsav&0x40) + { + if(mapversion <= 32) + { + int mat = f->getchar(); + c.material = convertoldmaterial(mat); + } + else c.material = f->getlil(); + } + if(octsav&0x80) c.merged = f->getchar(); + if(octsav&0x20) + { + int surfmask, totalverts; + surfmask = f->getchar(); + totalverts = max(f->getchar(), 0); + newcubeext(c, totalverts, false); + memset(c.ext->surfaces, 0, sizeof(c.ext->surfaces)); + memset(c.ext->verts(), 0, totalverts*sizeof(vertinfo)); + int offset = 0; + loopi(6) if(surfmask&(1<surfaces[i]; + f->read(&surf, sizeof(surfaceinfo)); + int vertmask = surf.verts, numverts = surf.totalverts(); + if(!numverts) { surf.verts = 0; continue; } + surf.verts = offset; + vertinfo *verts = c.ext->verts() + offset; + offset += numverts; + ivec v[4], n, vo = ivec(co).mask(0xFFF).shl(3); + int layerverts = surf.numverts&MAXFACEVERTS, dim = dimension(i), vc = C[dim], vr = R[dim], bias = 0; + genfaceverts(c, i, v); + bool hasxyz = (vertmask&0x04)!=0, hasuv = (vertmask&0x40)!=0, hasnorm = (vertmask&0x80)!=0; + if(hasxyz) + { + ivec e1, e2, e3; + n.cross((e1 = v[1]).sub(v[0]), (e2 = v[2]).sub(v[0])); + if(n.iszero()) n.cross(e2, (e3 = v[3]).sub(v[0])); + bias = -n.dot(ivec(v[0]).mul(size).add(vo)); + } + else + { + int vis = layerverts < 4 ? (vertmask&0x02 ? 2 : 1) : 3, order = vertmask&0x01 ? 1 : 0, k = 0; + verts[k++].setxyz(v[order].mul(size).add(vo)); + if(vis&1) verts[k++].setxyz(v[order+1].mul(size).add(vo)); + verts[k++].setxyz(v[order+2].mul(size).add(vo)); + if(vis&2) verts[k++].setxyz(v[(order+3)&3].mul(size).add(vo)); + } + if(layerverts == 4) + { + if(hasxyz && vertmask&0x01) + { + ushort c1 = f->getlil(), r1 = f->getlil(), c2 = f->getlil(), r2 = f->getlil(); + ivec xyz; + xyz[vc] = c1; xyz[vr] = r1; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; + verts[0].setxyz(xyz); + xyz[vc] = c1; xyz[vr] = r2; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; + verts[1].setxyz(xyz); + xyz[vc] = c2; xyz[vr] = r2; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; + verts[2].setxyz(xyz); + xyz[vc] = c2; xyz[vr] = r1; xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; + verts[3].setxyz(xyz); + hasxyz = false; + } + if(hasuv && vertmask&0x02) + { + int uvorder = (vertmask&0x30)>>4; + vertinfo &v0 = verts[uvorder], &v1 = verts[(uvorder+1)&3], &v2 = verts[(uvorder+2)&3], &v3 = verts[(uvorder+3)&3]; + v0.u = f->getlil(); v0.v = f->getlil(); + v2.u = f->getlil(); v2.v = f->getlil(); + v1.u = v0.u; v1.v = v2.v; + v3.u = v2.u; v3.v = v0.v; + if(surf.numverts&LAYER_DUP) + { + vertinfo &b0 = verts[4+uvorder], &b1 = verts[4+((uvorder+1)&3)], &b2 = verts[4+((uvorder+2)&3)], &b3 = verts[4+((uvorder+3)&3)]; + b0.u = f->getlil(); b0.v = f->getlil(); + b2.u = f->getlil(); b2.v = f->getlil(); + b1.u = b0.u; b1.v = b2.v; + b3.u = b2.u; b3.v = b0.v; + } + hasuv = false; + } + } + if(hasnorm && vertmask&0x08) + { + ushort norm = f->getlil(); + loopk(layerverts) verts[k].norm = norm; + hasnorm = false; + } + if(hasxyz || hasuv || hasnorm) loopk(layerverts) + { + vertinfo &v = verts[k]; + if(hasxyz) + { + ivec xyz; + xyz[vc] = f->getlil(); xyz[vr] = f->getlil(); + xyz[dim] = n[dim] ? -(bias + n[vc]*xyz[vc] + n[vr]*xyz[vr])/n[dim] : vo[dim]; + v.setxyz(xyz); + } + if(hasuv) { v.u = f->getlil(); v.v = f->getlil(); } + if(hasnorm) v.norm = f->getlil(); + } + if(surf.numverts&LAYER_DUP) loopk(layerverts) + { + vertinfo &v = verts[k+layerverts], &t = verts[k]; + v.setxyz(t.x, t.y, t.z); + if(hasuv) { v.u = f->getlil(); v.v = f->getlil(); } + v.norm = t.norm; + } + } + } + } + + c.children = (haschildren ? loadchildren(f, co, size>>1, failed) : NULL); } cube *loadchildren(stream *f, const ivec &co, int size, bool &failed) { - cube *c = newcubes(); - loopi(8) - { - loadc(f, c[i], ivec(i, co, size), size, failed); - if(failed) break; - } - return c; + cube *c = newcubes(); + loopi(8) + { + loadc(f, c[i], ivec(i, co, size), size, failed); + if(failed) break; + } + return c; } void savevslot(stream *f, VSlot &vs, int prev) { - f->putlil(vs.changed); - f->putlil(prev); - if(vs.changed & (1<putlil(vs.params.length()); - loopv(vs.params) - { - SlotShaderParam &p = vs.params[i]; - f->putlil(strlen(p.name)); - f->write(p.name, strlen(p.name)); - loopk(4) f->putlil(p.val[k]); - } - } - if(vs.changed & (1<putlil(vs.scale); - if(vs.changed & (1<putlil(vs.rotation); - if(vs.changed & (1<putlil(vs.offset.x); - f->putlil(vs.offset.y); - } - if(vs.changed & (1<putlil(vs.scroll.x); - f->putlil(vs.scroll.y); - } - if(vs.changed & (1<putlil(vs.layer); - if(vs.changed & (1<putlil(vs.alphafront); - f->putlil(vs.alphaback); - } - if(vs.changed & (1<putlil(vs.colorscale[k]); - } + f->putlil(vs.changed); + f->putlil(prev); + if(vs.changed & (1<putlil(vs.params.length()); + loopv(vs.params) + { + SlotShaderParam &p = vs.params[i]; + f->putlil(strlen(p.name)); + f->write(p.name, strlen(p.name)); + loopk(4) f->putlil(p.val[k]); + } + } + if(vs.changed & (1<putlil(vs.scale); + if(vs.changed & (1<putlil(vs.rotation); + if(vs.changed & (1<putlil(vs.offset.x); + f->putlil(vs.offset.y); + } + if(vs.changed & (1<putlil(vs.scroll.x); + f->putlil(vs.scroll.y); + } + if(vs.changed & (1<putlil(vs.layer); + if(vs.changed & (1<putlil(vs.alphafront); + f->putlil(vs.alphaback); + } + if(vs.changed & (1<putlil(vs.colorscale[k]); + } } void savevslots(stream *f, int numvslots) { - if(vslots.empty()) return; - int *prev = new int[numvslots]; - for(int i=0;ichanged) continue; - for(;;) - { - VSlot *cur = vs; - do vs = vs->next; while(vs && vs->index >= numvslots); - if(!vs) break; - prev[vs->index] = cur->index; - } - } - int lastroot = 0; - loopi(numvslots) - { - VSlot &vs = *vslots[i]; - if(!vs.changed) continue; - if(lastroot < i) f->putlil(-(i - lastroot)); - savevslot(f, vs, prev[i]); - lastroot = i+1; - } - if(lastroot < numvslots) f->putlil(-(numvslots - lastroot)); - delete[] prev; + if(vslots.empty()) return; + int *prev = new int[numvslots]; + for(int i=0;ichanged) continue; + for(;;) + { + VSlot *cur = vs; + do vs = vs->next; while(vs && vs->index >= numvslots); + if(!vs) break; + prev[vs->index] = cur->index; + } + } + int lastroot = 0; + loopi(numvslots) + { + VSlot &vs = *vslots[i]; + if(!vs.changed) continue; + if(lastroot < i) f->putlil(-(i - lastroot)); + savevslot(f, vs, prev[i]); + lastroot = i+1; + } + if(lastroot < numvslots) f->putlil(-(numvslots - lastroot)); + delete[] prev; } void loadvslot(stream *f, VSlot &vs, int changed) { - vs.changed = changed; - if(vs.changed & (1<getlil(); - string name; - loopi(numparams) - { - SlotShaderParam &p = vs.params.add(); - int nlen = f->getlil(); - f->read(name, min(nlen, MAXSTRLEN-1)); - name[min(nlen, MAXSTRLEN-1)] = '\0'; - if(nlen >= MAXSTRLEN) f->seek(nlen - (MAXSTRLEN-1), SEEK_CUR); - p.name = getshaderparamname(name); - p.loc = -1; - loopk(4) p.val[k] = f->getlil(); - } - } - if(vs.changed & (1<getlil(); - if(vs.changed & (1<getlil(), 0, 7); - if(vs.changed & (1<getlil(); - vs.offset.y = f->getlil(); - } - if(vs.changed & (1<getlil(); - vs.scroll.y = f->getlil(); - } - if(vs.changed & (1<getlil(); - if(vs.changed & (1<getlil(); - vs.alphaback = f->getlil(); - } - if(vs.changed & (1<getlil(); - } + vs.changed = changed; + if(vs.changed & (1<getlil(); + string name; + loopi(numparams) + { + SlotShaderParam &p = vs.params.add(); + int nlen = f->getlil(); + f->read(name, min(nlen, MAXSTRLEN-1)); + name[min(nlen, MAXSTRLEN-1)] = '\0'; + if(nlen >= MAXSTRLEN) f->seek(nlen - (MAXSTRLEN-1), SEEK_CUR); + p.name = getshaderparamname(name); + p.loc = -1; + loopk(4) p.val[k] = f->getlil(); + } + } + if(vs.changed & (1<getlil(); + if(vs.changed & (1<getlil(), 0, 7); + if(vs.changed & (1<getlil(); + vs.offset.y = f->getlil(); + } + if(vs.changed & (1<getlil(); + vs.scroll.y = f->getlil(); + } + if(vs.changed & (1<getlil(); + if(vs.changed & (1<getlil(); + vs.alphaback = f->getlil(); + } + if(vs.changed & (1<getlil(); + } } void loadvslots(stream *f, int numvslots) { - int *prev = new (false) int[numvslots]; - if(!prev) return; - for(int i=0;i 0) - { - int changed = f->getlil(); - if(changed < 0) - { - loopi(-changed) vslots.add(new VSlot(NULL, vslots.length())); - numvslots += changed; - } - else - { - prev[vslots.length()] = f->getlil(); - loadvslot(f, *vslots.add(new VSlot(NULL, vslots.length())), changed); - numvslots--; - } - } - loopv(vslots) if(vslots.inrange(prev[i])) vslots[prev[i]]->next = vslots[i]; - delete[] prev; + int *prev = new (false) int[numvslots]; + if(!prev) return; + for(int i=0;i 0) + { + int changed = f->getlil(); + if(changed < 0) + { + loopi(-changed) vslots.add(new VSlot(NULL, vslots.length())); + numvslots += changed; + } + else + { + prev[vslots.length()] = f->getlil(); + loadvslot(f, *vslots.add(new VSlot(NULL, vslots.length())), changed); + numvslots--; + } + } + loopv(vslots) if(vslots.inrange(prev[i])) vslots[prev[i]]->next = vslots[i]; + delete[] prev; } bool save_world(const char *mname, bool nolms) { - if(!*mname) mname = game::getclientmap(); - setmapfilenames(mname); - if(savebak) backup(ogzname, bakname); - stream *f = opengzfile(ogzname, "wb"); - if(!f) { conoutf(CON_WARN, "could not write map to %s", ogzname); return false; } - - int numvslots = vslots.length(); - if(!nolms && !multiplayer(false)) - { - numvslots = compactvslots(); - allchanged(); - } - - savemapprogress = 0; - renderprogress(0, "saving map..."); - - octaheader hdr; - memcpy(hdr.magic, "OCTA", 4); - hdr.version = MAPVERSION; - hdr.headersize = sizeof(hdr); - hdr.worldsize = worldsize; - hdr.numents = 0; - const vector &ents = entities::getents(); - loopv(ents) if(ents[i]->type!=ET_EMPTY || nolms) hdr.numents++; - hdr.numpvs = 0; - hdr.lightmaps = nolms ? 0 : lightmaps.length(); - hdr.blendmap = shouldsaveblendmap(); - hdr.numvars = 0; - hdr.numvslots = numvslots; - enumerate(idents, ident, id, - { - if((id.type == ID_VAR || id.type == ID_FVAR || id.type == ID_SVAR) && id.flags&IDF_OVERRIDE && !(id.flags&IDF_READONLY) && id.flags&IDF_OVERRIDDEN) hdr.numvars++; - }); - lilswap(&hdr.version, 9); - f->write(&hdr, sizeof(hdr)); - - enumerate(idents, ident, id, - { - if((id.type!=ID_VAR && id.type!=ID_FVAR && id.type!=ID_SVAR) || !(id.flags&IDF_OVERRIDE) || id.flags&IDF_READONLY || !(id.flags&IDF_OVERRIDDEN)) continue; - f->putchar(id.type); - f->putlil(strlen(id.name)); - f->write(id.name, strlen(id.name)); - switch(id.type) - { - case ID_VAR: - f->putlil(*id.storage.i); - break; - - case ID_FVAR: - f->putlil(*id.storage.f); - break; - - case ID_SVAR: - f->putlil(strlen(*id.storage.s)); - f->write(*id.storage.s, strlen(*id.storage.s)); - break; - } - }); - - f->putchar((int)strlen(game::gameident())); - f->write(game::gameident(), (int)strlen(game::gameident())+1); - f->putlil(entities::extraentinfosize()); - vector extras; - game::writegamedata(extras); - f->putlil(extras.length()); - f->write(extras.getbuf(), extras.length()); - - f->putlil(texmru.length()); - loopv(texmru) f->putlil(texmru[i]); - char *ebuf = new char[entities::extraentinfosize()]; - loopv(ents) - { - if(ents[i]->type!=ET_EMPTY || nolms) - { - entity tmp = *ents[i]; - lilswap(&tmp.o.x, 3); - lilswap(&tmp.attr1, 5); - f->write(&tmp, sizeof(entity)); - entities::writeent(*ents[i], ebuf); - if(entities::extraentinfosize()) f->write(ebuf, entities::extraentinfosize()); - } - } - delete[] ebuf; - - savevslots(f, numvslots); - - renderprogress(0, "saving octree..."); - savec(worldroot, ivec(0, 0, 0), worldsize>>1, f, nolms); - - if(!nolms) - { - if(lightmaps.length()) renderprogress(0, "saving lightmaps..."); - loopv(lightmaps) - { - LightMap &lm = lightmaps[i]; - f->putchar(lm.type | (lm.unlitx>=0 ? 0x80 : 0)); - if(lm.unlitx>=0) - { - f->putlil(ushort(lm.unlitx)); - f->putlil(ushort(lm.unlity)); - } - f->write(lm.data, lm.bpp*LM_PACKW*LM_PACKH); - renderprogress(float(i+1)/lightmaps.length(), "saving lightmaps..."); - } - } - if(shouldsaveblendmap()) { renderprogress(0, "saving blendmap..."); saveblendmap(f); } - - delete f; - conoutf("wrote map file %s", ogzname); - return true; + if(!*mname) mname = game::getclientmap(); + setmapfilenames(mname); + if(savebak) backup(ogzname, bakname); + stream *f = opengzfile(ogzname, "wb"); + if(!f) { conoutf(CON_WARN, "could not write map to %s", ogzname); return false; } + + int numvslots = vslots.length(); + if(!nolms && !multiplayer(false)) + { + numvslots = compactvslots(); + allchanged(); + } + + savemapprogress = 0; + renderprogress(0, "saving map..."); + + octaheader hdr; + memcpy(hdr.magic, "OCTA", 4); + hdr.version = MAPVERSION; + hdr.headersize = sizeof(hdr); + hdr.worldsize = worldsize; + hdr.numents = 0; + const vector &ents = entities::getents(); + loopv(ents) if(ents[i]->type!=ET_EMPTY || nolms) hdr.numents++; + hdr.numpvs = 0; + hdr.lightmaps = nolms ? 0 : lightmaps.length(); + hdr.blendmap = shouldsaveblendmap(); + hdr.numvars = 0; + hdr.numvslots = numvslots; + enumerate(idents, ident, id, + { + if((id.type == ID_VAR || id.type == ID_FVAR || id.type == ID_SVAR) && id.flags&IDF_OVERRIDE && !(id.flags&IDF_READONLY) && id.flags&IDF_OVERRIDDEN) hdr.numvars++; + }); + lilswap(&hdr.version, 9); + f->write(&hdr, sizeof(hdr)); + + enumerate(idents, ident, id, + { + if((id.type!=ID_VAR && id.type!=ID_FVAR && id.type!=ID_SVAR) || !(id.flags&IDF_OVERRIDE) || id.flags&IDF_READONLY || !(id.flags&IDF_OVERRIDDEN)) continue; + f->putchar(id.type); + f->putlil(strlen(id.name)); + f->write(id.name, strlen(id.name)); + switch(id.type) + { + case ID_VAR: + f->putlil(*id.storage.i); + break; + + case ID_FVAR: + f->putlil(*id.storage.f); + break; + + case ID_SVAR: + f->putlil(strlen(*id.storage.s)); + f->write(*id.storage.s, strlen(*id.storage.s)); + break; + } + }); + + f->putchar((int)strlen(game::gameident())); + f->write(game::gameident(), (int)strlen(game::gameident())+1); + f->putlil(entities::extraentinfosize()); + vector extras; + game::writegamedata(extras); + f->putlil(extras.length()); + f->write(extras.getbuf(), extras.length()); + + f->putlil(texmru.length()); + loopv(texmru) f->putlil(texmru[i]); + char *ebuf = new char[entities::extraentinfosize()]; + loopv(ents) + { + if(ents[i]->type!=ET_EMPTY || nolms) + { + entity tmp = *ents[i]; + lilswap(&tmp.o.x, 3); + lilswap(&tmp.attr1, 5); + f->write(&tmp, sizeof(entity)); + entities::writeent(*ents[i], ebuf); + if(entities::extraentinfosize()) f->write(ebuf, entities::extraentinfosize()); + } + } + delete[] ebuf; + + savevslots(f, numvslots); + + renderprogress(0, "saving octree..."); + savec(worldroot, ivec(0, 0, 0), worldsize>>1, f, nolms); + + if(!nolms) + { + if(lightmaps.length()) renderprogress(0, "saving lightmaps..."); + loopv(lightmaps) + { + LightMap &lm = lightmaps[i]; + f->putchar(lm.type | (lm.unlitx>=0 ? 0x80 : 0)); + if(lm.unlitx>=0) + { + f->putlil(ushort(lm.unlitx)); + f->putlil(ushort(lm.unlity)); + } + f->write(lm.data, lm.bpp*LM_PACKW*LM_PACKH); + renderprogress(float(i+1)/lightmaps.length(), "saving lightmaps..."); + } + } + if(shouldsaveblendmap()) { renderprogress(0, "saving blendmap..."); saveblendmap(f); } + + delete f; + conoutf("wrote map file %s", ogzname); + return true; } static uint mapcrc = 0; @@ -991,276 +991,271 @@ static uint mapcrc = 0; uint getmapcrc() { return mapcrc; } void clearmapcrc() { mapcrc = 0; } -bool load_world(const char *mname, const char *cname) // still supports all map formats that have existed since the earliest cube betas! +bool load_world(const char *mname, const char *cname) // still supports all map formats that have existed since the earliest cube betas! { - int loadingstart = SDL_GetTicks(); - setmapfilenames(mname, cname); - stream *f = opengzfile(ogzname, "rb"); - if(!f) { conoutf(CON_ERROR, "could not read map %s", ogzname); return false; } - octaheader hdr; - if(f->read(&hdr, 7*sizeof(int)) != 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - lilswap(&hdr.version, 6); - if(memcmp(hdr.magic, "OCTA", 4) || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of Cube 2: Sauerbraten", ogzname); delete f; return false; } - compatheader chdr; - if(hdr.version <= 28) - { - if(f->read(&chdr.lightprecision, sizeof(chdr) - 7*sizeof(int)) != sizeof(chdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - } - else - { - int extra = 0; - if(hdr.version <= 29) extra++; - if(f->read(&hdr.blendmap, sizeof(hdr) - (7+extra)*sizeof(int)) != sizeof(hdr) - (7+extra)*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } - } - - resetmap(); - - Texture *mapshot = textureload(picname, 3, true, false); - renderbackground("loading...", mapshot, mname, game::getmapinfo()); - - game::loadingmap(cname ? cname : mname); - - setvar("mapversion", hdr.version, true, false); - - if(hdr.version <= 28) - { - lilswap(&chdr.lightprecision, 3); - if(chdr.lightprecision) setvar("lightprecision", chdr.lightprecision); - if(chdr.lighterror) setvar("lighterror", chdr.lighterror); - if(chdr.bumperror) setvar("bumperror", chdr.bumperror); - setvar("lightlod", chdr.lightlod); - if(chdr.ambient) setvar("ambient", chdr.ambient); - setvar("skylight", (int(chdr.skylight[0])<<16) | (int(chdr.skylight[1])<<8) | int(chdr.skylight[2])); - setvar("watercolour", (int(chdr.watercolour[0])<<16) | (int(chdr.watercolour[1])<<8) | int(chdr.watercolour[2]), true); - setvar("waterfallcolour", (int(chdr.waterfallcolour[0])<<16) | (int(chdr.waterfallcolour[1])<<8) | int(chdr.waterfallcolour[2])); - setvar("lavacolour", (int(chdr.lavacolour[0])<<16) | (int(chdr.lavacolour[1])<<8) | int(chdr.lavacolour[2])); - setvar("fullbright", 0, true); - if(chdr.lerpsubdivsize || chdr.lerpangle) setvar("lerpangle", chdr.lerpangle); - if(chdr.lerpsubdivsize) - { - setvar("lerpsubdiv", chdr.lerpsubdiv); - setvar("lerpsubdivsize", chdr.lerpsubdivsize); - } - setsvar("maptitle", chdr.maptitle); - hdr.blendmap = chdr.blendmap; - hdr.numvars = 0; - hdr.numvslots = 0; - } - else - { - lilswap(&hdr.blendmap, 2); - if(hdr.version <= 29) hdr.numvslots = 0; - else lilswap(&hdr.numvslots, 1); - } - - renderprogress(0, "clearing world..."); - - freeocta(worldroot); - worldroot = NULL; - - int worldscale = 0; - while(1<getchar(), ilen = f->getlil(); - string name; - f->read(name, min(ilen, MAXSTRLEN-1)); - name[min(ilen, MAXSTRLEN-1)] = '\0'; - if(ilen >= MAXSTRLEN) f->seek(ilen - (MAXSTRLEN-1), SEEK_CUR); - ident *id = getident(name); - bool exists = id && id->type == type && id->flags&IDF_OVERRIDE; - switch(type) - { - case ID_VAR: - { - int val = f->getlil(); - if(exists && id->minval <= id->maxval) setvar(name, val); - break; - } - - case ID_FVAR: - { - float val = f->getlil(); - if(exists && id->minvalf <= id->maxvalf) setfvar(name, val); - break; - } - - case ID_SVAR: - { - int slen = f->getlil(); - string val; - f->read(val, min(slen, MAXSTRLEN-1)); - val[min(slen, MAXSTRLEN-1)] = '\0'; - if(slen >= MAXSTRLEN) f->seek(slen - (MAXSTRLEN-1), SEEK_CUR); - if(exists) setsvar(name, val); - break; - } - } - } - - string gametype; - copystring(gametype, "fps"); - bool samegame = true; - int eif = 0; - if(hdr.version>=16) - { - int len = f->getchar(); - f->read(gametype, len+1); - } - if(strcmp(gametype, game::gameident())!=0) - { - samegame = false; - conoutf(CON_WARN, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels", gametype); - } - if(hdr.version>=16) - { - eif = f->getlil(); - int extrasize = f->getlil(); - vector extras; - f->read(extras.pad(extrasize), extrasize); - if(samegame) game::readgamedata(extras); - } - - texmru.shrink(0); - if(hdr.version<14) - { - uchar oldtl[256]; - f->read(oldtl, sizeof(oldtl)); - loopi(256) texmru.add(oldtl[i]); - } - else - { - ushort nummru = f->getlil(); - loopi(nummru) texmru.add(f->getlil()); - } - - renderprogress(0, "loading entities..."); - - vector &ents = entities::getents(); - int einfosize = entities::extraentinfosize(); - char *ebuf = einfosize > 0 ? new char[einfosize] : NULL; - loopi(min(hdr.numents, MAXENTS)) - { - extentity &e = *entities::newentity(); - ents.add(&e); - f->read(&e, sizeof(entity)); - lilswap(&e.o.x, 3); - lilswap(&e.attr1, 5); - fixent(e, hdr.version); - if(samegame) - { - if(einfosize > 0) f->read(ebuf, einfosize); - entities::readent(e, ebuf, mapversion); - } - else - { - if(eif > 0) f->seek(eif, SEEK_CUR); - if(e.type>=ET_GAMESPECIFIC || hdr.version<=14) - { - entities::deleteentity(ents.pop()); - continue; - } - } - if(!insideworld(e.o)) - { - if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT) - { - conoutf(CON_WARN, "warning: ent outside of world: enttype[%s] index %d (%f, %f, %f)", entities::entname(e.type), i, e.o.x, e.o.y, e.o.z); - } - } - if(hdr.version <= 14 && e.type == ET_MAPMODEL) - { - e.o.z += e.attr3; - if(e.attr4) conoutf(CON_WARN, "warning: mapmodel ent (index %d) uses texture slot %d", i, e.attr4); - e.attr3 = e.attr4 = 0; - } - } - if(ebuf) delete[] ebuf; - - if(hdr.numents > MAXENTS) - { - conoutf(CON_WARN, "warning: map has %d entities", hdr.numents); - f->seek((hdr.numents-MAXENTS)*(samegame ? sizeof(entity) + einfosize : eif), SEEK_CUR); - } - - renderprogress(0, "loading slots..."); - loadvslots(f, hdr.numvslots); - - renderprogress(0, "loading octree..."); - bool failed = false; - worldroot = loadchildren(f, ivec(0, 0, 0), hdr.worldsize>>1, failed); - if(failed) conoutf(CON_ERROR, "garbage in map"); - - renderprogress(0, "validating..."); - validatec(worldroot, hdr.worldsize>>1); - - if(!failed) - { - if(hdr.version >= 7) loopi(hdr.lightmaps) - { - renderprogress(i/(float)hdr.lightmaps, "loading lightmaps..."); - LightMap &lm = lightmaps.add(); - if(hdr.version >= 17) - { - int type = f->getchar(); - lm.type = type&0x7F; - if(hdr.version >= 20 && type&0x80) - { - lm.unlitx = f->getlil(); - lm.unlity = f->getlil(); - } - } - if(lm.type&LM_ALPHA && (lm.type&LM_TYPE)!=LM_BUMPMAP1) lm.bpp = 4; - lm.data = new uchar[lm.bpp*LM_PACKW*LM_PACKH]; - f->read(lm.data, lm.bpp * LM_PACKW * LM_PACKH); - lm.finalize(); - } - - if(hdr.version >= 28 && hdr.blendmap) loadblendmap(f, hdr.blendmap); - } - - mapcrc = f->getcrc(); - delete f; - - conoutf("read map %s (%.1f seconds)", ogzname, (SDL_GetTicks()-loadingstart)/1000.0f); - - clearmainmenu(); - - identflags |= IDF_OVERRIDDEN; - execfile("data/default_map.cfg", false); - execfile(cfgname, false); - identflags &= ~IDF_OVERRIDDEN; - - extern void fixlightmapnormals(); - if(hdr.version <= 25) fixlightmapnormals(); - extern void fixrotatedlightmaps(); - if(hdr.version <= 31) fixrotatedlightmaps(); - - preloadusedmapmodels(true); - - game::preload(); - flushpreloadedmodels(); - - preloadmapsounds(); - - entitiesinoctanodes(); - attachentities(); - initlights(); - allchanged(true); - - renderbackground("loading...", mapshot, mname, game::getmapinfo()); - - if(maptitle[0] && strcmp(maptitle, "Untitled Map by Unknown")) conoutf(CON_ECHO, "%s", maptitle); - - startmap(cname ? cname : mname); - - return true; + int loadingstart = SDL_GetTicks(); + setmapfilenames(mname, cname); + stream *f = opengzfile(ogzname, "rb"); + if(!f) { conoutf(CON_ERROR, "could not read map %s", ogzname); return false; } + octaheader hdr; + if(f->read(&hdr, 7*sizeof(int)) != 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + lilswap(&hdr.version, 6); + if(memcmp(hdr.magic, "OCTA", 4) || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of Cube 2: Sauerbraten", ogzname); delete f; return false; } + compatheader chdr; + if(hdr.version <= 28) + { + if(f->read(&chdr.lightprecision, sizeof(chdr) - 7*sizeof(int)) != sizeof(chdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + } + else + { + int extra = 0; + if(hdr.version <= 29) extra++; + if(f->read(&hdr.blendmap, sizeof(hdr) - (7+extra)*sizeof(int)) != sizeof(hdr) - (7+extra)*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; } + } + + resetmap(); + + Texture *mapshot = textureload(picname, 3, true, false); + renderbackground("loading...", mapshot, mname, game::getmapinfo()); + + game::loadingmap(cname ? cname : mname); + + setvar("mapversion", hdr.version, true, false); + + if(hdr.version <= 28) + { + lilswap(&chdr.lightprecision, 3); + if(chdr.lightprecision) setvar("lightprecision", chdr.lightprecision); + if(chdr.lighterror) setvar("lighterror", chdr.lighterror); + if(chdr.bumperror) setvar("bumperror", chdr.bumperror); + setvar("lightlod", chdr.lightlod); + if(chdr.ambient) setvar("ambient", chdr.ambient); + setvar("skylight", (int(chdr.skylight[0])<<16) | (int(chdr.skylight[1])<<8) | int(chdr.skylight[2])); + setvar("watercolour", (int(chdr.watercolour[0])<<16) | (int(chdr.watercolour[1])<<8) | int(chdr.watercolour[2]), true); + setvar("waterfallcolour", (int(chdr.waterfallcolour[0])<<16) | (int(chdr.waterfallcolour[1])<<8) | int(chdr.waterfallcolour[2])); + setvar("lavacolour", (int(chdr.lavacolour[0])<<16) | (int(chdr.lavacolour[1])<<8) | int(chdr.lavacolour[2])); + setvar("fullbright", 0, true); + if(chdr.lerpsubdivsize || chdr.lerpangle) setvar("lerpangle", chdr.lerpangle); + if(chdr.lerpsubdivsize) + { + setvar("lerpsubdiv", chdr.lerpsubdiv); + setvar("lerpsubdivsize", chdr.lerpsubdivsize); + } + setsvar("maptitle", chdr.maptitle); + hdr.blendmap = chdr.blendmap; + hdr.numvars = 0; + hdr.numvslots = 0; + } + else + { + lilswap(&hdr.blendmap, 2); + if(hdr.version <= 29) hdr.numvslots = 0; + else lilswap(&hdr.numvslots, 1); + } + + renderprogress(0, "clearing world..."); + + freeocta(worldroot); + worldroot = NULL; + + int worldscale = 0; + while(1<getchar(), ilen = f->getlil(); + string name; + f->read(name, min(ilen, MAXSTRLEN-1)); + name[min(ilen, MAXSTRLEN-1)] = '\0'; + if(ilen >= MAXSTRLEN) f->seek(ilen - (MAXSTRLEN-1), SEEK_CUR); + ident *id = getident(name); + bool exists = id && id->type == type && id->flags&IDF_OVERRIDE; + switch(type) + { + case ID_VAR: + { + int val = f->getlil(); + if(exists && id->minval <= id->maxval) setvar(name, val); + break; + } + + case ID_FVAR: + { + float val = f->getlil(); + if(exists && id->minvalf <= id->maxvalf) setfvar(name, val); + break; + } + + case ID_SVAR: + { + int slen = f->getlil(); + string val; + f->read(val, min(slen, MAXSTRLEN-1)); + val[min(slen, MAXSTRLEN-1)] = '\0'; + if(slen >= MAXSTRLEN) f->seek(slen - (MAXSTRLEN-1), SEEK_CUR); + if(exists) setsvar(name, val); + break; + } + } + } + + string gametype; + copystring(gametype, "fps"); + bool samegame = true; + int eif = 0; + if(hdr.version>=16) + { + int len = f->getchar(); + f->read(gametype, len+1); + } + if(strcmp(gametype, game::gameident())!=0) + { + samegame = false; + conoutf(CON_WARN, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels", gametype); + } + if(hdr.version>=16) + { + eif = f->getlil(); + int extrasize = f->getlil(); + vector extras; + f->read(extras.pad(extrasize), extrasize); + if(samegame) game::readgamedata(extras); + } + + texmru.shrink(0); + if(hdr.version<14) + { + uchar oldtl[256]; + f->read(oldtl, sizeof(oldtl)); + loopi(256) texmru.add(oldtl[i]); + } + else + { + ushort nummru = f->getlil(); + loopi(nummru) texmru.add(f->getlil()); + } + + renderprogress(0, "loading entities..."); + + vector &ents = entities::getents(); + int einfosize = entities::extraentinfosize(); + char *ebuf = einfosize > 0 ? new char[einfosize] : NULL; + loopi(min(hdr.numents, MAXENTS)) + { + extentity &e = *entities::newentity(); + ents.add(&e); + f->read(&e, sizeof(entity)); + lilswap(&e.o.x, 3); + lilswap(&e.attr1, 5); + fixent(e, hdr.version); + if(samegame) + { + if(einfosize > 0) f->read(ebuf, einfosize); + entities::readent(e, ebuf, mapversion); + } + else + { + if(eif > 0) f->seek(eif, SEEK_CUR); + if(e.type>=ET_GAMESPECIFIC || hdr.version<=14) + { + entities::deleteentity(ents.pop()); + continue; + } + } + if(!insideworld(e.o)) + { + if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT) + { + conoutf(CON_WARN, "warning: ent outside of world: enttype[%s] index %d (%f, %f, %f)", entities::entname(e.type), i, e.o.x, e.o.y, e.o.z); + } + } + if(hdr.version <= 14 && e.type == ET_MAPMODEL) + { + e.o.z += e.attr3; + if(e.attr4) conoutf(CON_WARN, "warning: mapmodel ent (index %d) uses texture slot %d", i, e.attr4); + e.attr3 = e.attr4 = 0; + } + } + if(ebuf) delete[] ebuf; + + if(hdr.numents > MAXENTS) + { + conoutf(CON_WARN, "warning: map has %d entities", hdr.numents); + f->seek((hdr.numents-MAXENTS)*(samegame ? sizeof(entity) + einfosize : eif), SEEK_CUR); + } + + renderprogress(0, "loading slots..."); + loadvslots(f, hdr.numvslots); + + renderprogress(0, "loading octree..."); + bool failed = false; + worldroot = loadchildren(f, ivec(0, 0, 0), hdr.worldsize>>1, failed); + if(failed) conoutf(CON_ERROR, "garbage in map"); + + renderprogress(0, "validating..."); + validatec(worldroot, hdr.worldsize>>1); + + if(!failed) + { + if(hdr.version >= 7) loopi(hdr.lightmaps) + { + renderprogress(i/(float)hdr.lightmaps, "loading lightmaps..."); + LightMap &lm = lightmaps.add(); + if(hdr.version >= 17) + { + int type = f->getchar(); + lm.type = type&0x7F; + if(hdr.version >= 20 && type&0x80) + { + lm.unlitx = f->getlil(); + lm.unlity = f->getlil(); + } + } + if(lm.type&LM_ALPHA && (lm.type&LM_TYPE)!=LM_BUMPMAP1) lm.bpp = 4; + lm.data = new uchar[lm.bpp*LM_PACKW*LM_PACKH]; + f->read(lm.data, lm.bpp * LM_PACKW * LM_PACKH); + lm.finalize(); + } + + if(hdr.version >= 28 && hdr.blendmap) loadblendmap(f, hdr.blendmap); + } + + mapcrc = f->getcrc(); + delete f; + + conoutf("read map %s (%.1f seconds)", ogzname, (SDL_GetTicks()-loadingstart)/1000.0f); + + clearmainmenu(); + + identflags |= IDF_OVERRIDDEN; + execfile("data/default_map.cfg", false); + execfile(cfgname, false); + identflags &= ~IDF_OVERRIDDEN; + + preloadusedmapmodels(true); + + game::preload(); + flushpreloadedmodels(); + + preloadmapsounds(); + + entitiesinoctanodes(); + attachentities(); + initlights(); + allchanged(true); + + renderbackground("loading...", mapshot, mname, game::getmapinfo()); + + if(maptitle[0] && strcmp(maptitle, "Untitled Map by Unknown")) conoutf(CON_ECHO, "%s", maptitle); + + startmap(cname ? cname : mname); + + return true; } void savecurrentmap() { save_world(game::getclientmap()); } diff --git a/src/fpsgame/ai.cpp b/src/fpsgame/ai.cpp index b8c3daa..60b33ca 100644 --- a/src/fpsgame/ai.cpp +++ b/src/fpsgame/ai.cpp @@ -4,881 +4,881 @@ extern int fog; namespace ai { - using namespace game; - - avoidset obstacles; - int updatemillis = 0, iteration = 0, itermillis = 0, forcegun = -1; - vec aitarget(0, 0, 0); - - VAR(aidebug, 0, 0, 6); - VAR(aiforcegun, -1, -1, NUMGUNS-1); - - ICOMMAND(addbot, "s", (char *s), addmsg(N_ADDBOT, "ri", *s ? clamp(parseint(s), 1, 101) : -1)); - ICOMMAND(delbot, "", (), addmsg(N_DELBOT, "r")); - ICOMMAND(botlimit, "i", (int *n), addmsg(N_BOTLIMIT, "ri", *n)); - ICOMMAND(botbalance, "i", (int *n), addmsg(N_BOTBALANCE, "ri", *n)); - - float viewdist(int x) - { - return x <= 100 ? clamp((SIGHTMIN+(SIGHTMAX-SIGHTMIN))/100.f*float(x), float(SIGHTMIN), float(fog)) : float(fog); - } - - float viewfieldx(int x) - { - return x <= 100 ? clamp((VIEWMIN+(VIEWMAX-VIEWMIN))/100.f*float(x), float(VIEWMIN), float(VIEWMAX)) : float(VIEWMAX); - } - - float viewfieldy(int x) - { - return viewfieldx(x)*3.f/4.f; - } - - bool canmove(fpsent *d) - { - return d->state != CS_DEAD && !intermission; - } - - float weapmindist(int weap) - { - return max(int(guns[weap].exprad), 2); - } - - float weapmaxdist(int weap) - { - return guns[weap].range + 4; - } - - bool weaprange(fpsent *d, int weap, float dist) - { - float mindist = weapmindist(weap), maxdist = weapmaxdist(weap); - return dist >= mindist*mindist && dist <= maxdist*maxdist; - } - - bool targetable(fpsent *d, fpsent *e) - { - if(d == e || !canmove(d)) return false; - return e->state == CS_ALIVE && !isteam(d->team, e->team); - } - - bool getsight(vec &o, float yaw, float pitch, vec &q, vec &v, float mdist, float fovx, float fovy) - { - float dist = o.dist(q); - - if(dist <= mdist) - { - float x = fmod(fabs(asin((q.z-o.z)/dist)/RAD-pitch), 360); - float y = fmod(fabs(-atan2(q.x-o.x, q.y-o.y)/RAD-yaw), 360); - if(min(x, 360-x) <= fovx && min(y, 360-y) <= fovy) return raycubelos(o, q, v); - } - return false; - } - - bool cansee(fpsent *d, vec &x, vec &y, vec &targ) - { - aistate &b = d->ai->getstate(); - if(canmove(d) && b.type != AI_S_WAIT) - return getsight(x, d->yaw, d->pitch, y, targ, d->ai->views[2], d->ai->views[0], d->ai->views[1]); - return false; - } - - bool canshoot(fpsent *d, fpsent *e) - { - if(weaprange(d, d->gunselect, e->o.squaredist(d->o)) && targetable(d, e)) - return d->ammo[d->gunselect] > 0 && lastmillis - d->lastaction >= d->gunwait; - return false; - } - - bool canshoot(fpsent *d) - { - return !d->ai->becareful && d->ammo[d->gunselect] > 0 && lastmillis - d->lastaction >= d->gunwait; - } + using namespace game; + + avoidset obstacles; + int updatemillis = 0, iteration = 0, itermillis = 0, forcegun = -1; + vec aitarget(0, 0, 0); + + VAR(aidebug, 0, 0, 6); + VAR(aiforcegun, -1, -1, NUMGUNS-1); + + ICOMMAND(addbot, "s", (char *s), addmsg(N_ADDBOT, "ri", *s ? clamp(parseint(s), 1, 101) : -1)); + ICOMMAND(delbot, "", (), addmsg(N_DELBOT, "r")); + ICOMMAND(botlimit, "i", (int *n), addmsg(N_BOTLIMIT, "ri", *n)); + ICOMMAND(botbalance, "i", (int *n), addmsg(N_BOTBALANCE, "ri", *n)); + + float viewdist(int x) + { + return x <= 100 ? clamp((SIGHTMIN+(SIGHTMAX-SIGHTMIN))/100.f*float(x), float(SIGHTMIN), float(fog)) : float(fog); + } + + float viewfieldx(int x) + { + return x <= 100 ? clamp((VIEWMIN+(VIEWMAX-VIEWMIN))/100.f*float(x), float(VIEWMIN), float(VIEWMAX)) : float(VIEWMAX); + } + + float viewfieldy(int x) + { + return viewfieldx(x)*3.f/4.f; + } + + bool canmove(fpsent *d) + { + return d->state != CS_DEAD && !intermission; + } + + float weapmindist(int weap) + { + return max(int(guns[weap].exprad), 2); + } + + float weapmaxdist(int weap) + { + return guns[weap].range + 4; + } + + bool weaprange(fpsent *d, int weap, float dist) + { + float mindist = weapmindist(weap), maxdist = weapmaxdist(weap); + return dist >= mindist*mindist && dist <= maxdist*maxdist; + } + + bool targetable(fpsent *d, fpsent *e) + { + if(d == e || !canmove(d)) return false; + return e->state == CS_ALIVE && !isteam(d->team, e->team); + } + + bool getsight(vec &o, float yaw, float pitch, vec &q, vec &v, float mdist, float fovx, float fovy) + { + float dist = o.dist(q); + + if(dist <= mdist) + { + float x = fmod(fabs(asin((q.z-o.z)/dist)/RAD-pitch), 360); + float y = fmod(fabs(-atan2(q.x-o.x, q.y-o.y)/RAD-yaw), 360); + if(min(x, 360-x) <= fovx && min(y, 360-y) <= fovy) return raycubelos(o, q, v); + } + return false; + } + + bool cansee(fpsent *d, vec &x, vec &y, vec &targ) + { + aistate &b = d->ai->getstate(); + if(canmove(d) && b.type != AI_S_WAIT) + return getsight(x, d->yaw, d->pitch, y, targ, d->ai->views[2], d->ai->views[0], d->ai->views[1]); + return false; + } + + bool canshoot(fpsent *d, fpsent *e) + { + if(weaprange(d, d->gunselect, e->o.squaredist(d->o)) && targetable(d, e)) + return d->ammo[d->gunselect] > 0 && lastmillis - d->lastaction >= d->gunwait; + return false; + } + + bool canshoot(fpsent *d) + { + return !d->ai->becareful && d->ammo[d->gunselect] > 0 && lastmillis - d->lastaction >= d->gunwait; + } bool hastarget(fpsent *d, aistate &b, fpsent *e, float yaw, float pitch, float dist) { // add margins of error - if(weaprange(d, d->gunselect, dist) || (d->skill <= 100 && !rnd(d->skill))) - { - if(d->gunselect == GUN_FIST) return true; - float skew = clamp(float(lastmillis-d->ai->enemymillis)/float((d->skill*guns[d->gunselect].attackdelay/200.f)), 0.f, guns[d->gunselect].projspeed ? 0.25f : 1e16f), - offy = yaw-d->yaw, offp = pitch-d->pitch; - if(offy > 180) offy -= 360; - else if(offy < -180) offy += 360; - if(fabs(offy) <= d->ai->views[0]*skew && fabs(offp) <= d->ai->views[1]*skew) return true; - } - return false; - } - - vec getaimpos(fpsent *d, fpsent *e) - { - vec o = e->o; - if(d->gunselect == GUN_RL) o.z += (e->aboveeye*0.2f)-(0.8f*d->eyeheight); - else if(d->gunselect != GUN_GL) o.z += (e->aboveeye-e->eyeheight)*0.5f; - if(d->skill <= 100) - { - if(lastmillis >= d->ai->lastaimrnd) - { - const int aiskew[NUMGUNS] = { 1, 10, 50, 5, 20, 1, 100 }; - #define rndaioffset(r) ((rnd(int(r*aiskew[d->gunselect]*2)+1)-(r*aiskew[d->gunselect]))*(1.f/float(max(d->skill, 1)))) - loopk(3) d->ai->aimrnd[k] = rndaioffset(e->radius); - int dur = (d->skill+10)*10; - d->ai->lastaimrnd = lastmillis+dur+rnd(dur); - } - loopk(3) o[k] += d->ai->aimrnd[k]; - } - return o; - } - - void create(fpsent *d) - { - if(!d->ai) d->ai = new aiinfo; - } - - void destroy(fpsent *d) - { - if(d->ai) DELETEP(d->ai); - } - - void init(fpsent *d, int at, int ocn, int sk, int bn, int pm, const char *name, const char *team) - { - loadwaypoints(); - - fpsent *o = newclient(ocn); - - d->aitype = at; - - bool resetthisguy = false; - if(!d->name[0]) - { - if(aidebug) conoutf(CON_DEBUG, "%s assigned to %s at skill %d", colorname(d, name), o ? colorname(o) : "?", sk); - else conoutf("\f0join:\f7 %s", colorname(d, name)); - resetthisguy = true; - } - else - { - if(d->ownernum != ocn) - { - if(aidebug) conoutf(CON_DEBUG, "%s reassigned to %s", colorname(d, name), o ? colorname(o) : "?"); - resetthisguy = true; - } - if(d->skill != sk && aidebug) conoutf(CON_DEBUG, "%s changed skill to %d", colorname(d, name), sk); - } - - copystring(d->name, name, MAXNAMELEN+1); - copystring(d->team, team, MAXTEAMLEN+1); - d->ownernum = ocn; - d->plag = 0; - d->skill = sk; - d->playermodel = 0; - - if(resetthisguy) removeweapons(d); - if(d->ownernum >= 0 && player1->clientnum == d->ownernum) - { - create(d); - if(d->ai) - { - d->ai->views[0] = viewfieldx(d->skill); - d->ai->views[1] = viewfieldy(d->skill); - d->ai->views[2] = viewdist(d->skill); - } - } - else if(d->ai) destroy(d); - } - - void update() - { - if(intermission) { loopv(players) if(players[i]->ai) players[i]->stopmoving(); } - else // fixed rate logic done out-of-sequence at 1 frame per second for each ai - { - if(totalmillis-updatemillis > 1000) - { - avoid(); - forcegun = multiplayer(false) ? -1 : aiforcegun; - updatemillis = totalmillis; - } - if(!iteration && totalmillis-itermillis > 1000) - { - iteration = 1; - itermillis = totalmillis; - } - int count = 0; - loopv(players) if(players[i]->ai) think(players[i], ++count == iteration ? true : false); - if(++iteration > count) iteration = 0; - } - } - - bool checkothers(vector &targets, fpsent *d, int state, int targtype, int target, bool teams, int *members) - { // checks the states of other ai for a match - targets.setsize(0); - loopv(players) - { - fpsent *e = players[i]; - if(targets.find(e->clientnum) >= 0) continue; - if(teams && d && !isteam(d->team, e->team)) continue; - if(members) (*members)++; - if(e == d || !e->ai || e->state != CS_ALIVE) continue; - aistate &b = e->ai->getstate(); - if(state >= 0 && b.type != state) continue; - if(target >= 0 && b.target != target) continue; - if(targtype >=0 && b.targtype != targtype) continue; - targets.add(e->clientnum); - } - return !targets.empty(); - } - - bool makeroute(fpsent *d, aistate &b, int node, bool changed, int retries) - { - if(!iswaypoint(d->lastnode)) return false; + if(weaprange(d, d->gunselect, dist) || (d->skill <= 100 && !rnd(d->skill))) + { + if(d->gunselect == GUN_FIST) return true; + float skew = clamp(float(lastmillis-d->ai->enemymillis)/float((d->skill*guns[d->gunselect].attackdelay/200.f)), 0.f, guns[d->gunselect].projspeed ? 0.25f : 1e16f), + offy = yaw-d->yaw, offp = pitch-d->pitch; + if(offy > 180) offy -= 360; + else if(offy < -180) offy += 360; + if(fabs(offy) <= d->ai->views[0]*skew && fabs(offp) <= d->ai->views[1]*skew) return true; + } + return false; + } + + vec getaimpos(fpsent *d, fpsent *e) + { + vec o = e->o; + if(d->gunselect == GUN_RL) o.z += (e->aboveeye*0.2f)-(0.8f*d->eyeheight); + else if(d->gunselect != GUN_GL) o.z += (e->aboveeye-e->eyeheight)*0.5f; + if(d->skill <= 100) + { + if(lastmillis >= d->ai->lastaimrnd) + { + const int aiskew[NUMGUNS] = { 1, 10, 50, 5, 20, 1, 100 }; + #define rndaioffset(r) ((rnd(int(r*aiskew[d->gunselect]*2)+1)-(r*aiskew[d->gunselect]))*(1.f/float(max(d->skill, 1)))) + loopk(3) d->ai->aimrnd[k] = rndaioffset(e->radius); + int dur = (d->skill+10)*10; + d->ai->lastaimrnd = lastmillis+dur+rnd(dur); + } + loopk(3) o[k] += d->ai->aimrnd[k]; + } + return o; + } + + void create(fpsent *d) + { + if(!d->ai) d->ai = new aiinfo; + } + + void destroy(fpsent *d) + { + if(d->ai) DELETEP(d->ai); + } + + void init(fpsent *d, int at, int ocn, int sk, int bn, int pm, const char *name, const char *team) + { + loadwaypoints(); + + fpsent *o = newclient(ocn); + + d->aitype = at; + + bool resetthisguy = false; + if(!d->name[0]) + { + if(aidebug) conoutf(CON_DEBUG, "%s assigned to %s at skill %d", colorname(d, name), o ? colorname(o) : "?", sk); + else conoutf("\f0join:\f7 %s", colorname(d, name)); + resetthisguy = true; + } + else + { + if(d->ownernum != ocn) + { + if(aidebug) conoutf(CON_DEBUG, "%s reassigned to %s", colorname(d, name), o ? colorname(o) : "?"); + resetthisguy = true; + } + if(d->skill != sk && aidebug) conoutf(CON_DEBUG, "%s changed skill to %d", colorname(d, name), sk); + } + + copystring(d->name, name, MAXNAMELEN+1); + copystring(d->team, team, MAXTEAMLEN+1); + d->ownernum = ocn; + d->plag = 0; + d->skill = sk; + d->playermodel = 0; + + if(resetthisguy) removeweapons(d); + if(d->ownernum >= 0 && player1->clientnum == d->ownernum) + { + create(d); + if(d->ai) + { + d->ai->views[0] = viewfieldx(d->skill); + d->ai->views[1] = viewfieldy(d->skill); + d->ai->views[2] = viewdist(d->skill); + } + } + else if(d->ai) destroy(d); + } + + void update() + { + if(intermission) { loopv(players) if(players[i]->ai) players[i]->stopmoving(); } + else // fixed rate logic done out-of-sequence at 1 frame per second for each ai + { + if(totalmillis-updatemillis > 1000) + { + avoid(); + forcegun = multiplayer(false) ? -1 : aiforcegun; + updatemillis = totalmillis; + } + if(!iteration && totalmillis-itermillis > 1000) + { + iteration = 1; + itermillis = totalmillis; + } + int count = 0; + loopv(players) if(players[i]->ai) think(players[i], ++count == iteration ? true : false); + if(++iteration > count) iteration = 0; + } + } + + bool checkothers(vector &targets, fpsent *d, int state, int targtype, int target, bool teams, int *members) + { // checks the states of other ai for a match + targets.setsize(0); + loopv(players) + { + fpsent *e = players[i]; + if(targets.find(e->clientnum) >= 0) continue; + if(teams && d && !isteam(d->team, e->team)) continue; + if(members) (*members)++; + if(e == d || !e->ai || e->state != CS_ALIVE) continue; + aistate &b = e->ai->getstate(); + if(state >= 0 && b.type != state) continue; + if(target >= 0 && b.target != target) continue; + if(targtype >=0 && b.targtype != targtype) continue; + targets.add(e->clientnum); + } + return !targets.empty(); + } + + bool makeroute(fpsent *d, aistate &b, int node, bool changed, int retries) + { + if(!iswaypoint(d->lastnode)) return false; if(changed && d->ai->route.length() > 1 && d->ai->route[0] == node) return true; if(route(d, d->lastnode, node, d->ai->route, obstacles, retries)) { - b.override = false; - return true; + b.override = false; + return true; + } + // retry fails: 0 = first attempt, 1 = try ignoring obstacles, 2 = try ignoring prevnodes too + if(retries <= 1) return makeroute(d, b, node, false, retries+1); + return false; + } + + bool makeroute(fpsent *d, aistate &b, const vec &pos, bool changed, int retries) + { + int node = closestwaypoint(pos, SIGHTMIN, true); + return makeroute(d, b, node, changed, retries); + } + + bool randomnode(fpsent *d, aistate &b, const vec &pos, float guard, float wander) + { + static vector candidates; + candidates.setsize(0); + findwaypointswithin(pos, guard, wander, candidates); + + while(!candidates.empty()) + { + int w = rnd(candidates.length()), n = candidates.removeunordered(w); + if(n != d->lastnode && !d->ai->hasprevnode(n) && !obstacles.find(n, d) && makeroute(d, b, n)) return true; + } + return false; + } + + bool randomnode(fpsent *d, aistate &b, float guard, float wander) + { + return randomnode(d, b, d->feetpos(), guard, wander); + } + + bool badhealth(fpsent *d) + { + if(d->skill <= 100) return d->health <= (111-d->skill)/4; + return false; + } + + bool enemy(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, int pursue = 0) + { + fpsent *t = NULL; + vec dp = d->headpos(); + float mindist = guard*guard, bestdist = 1e16f; + loopv(players) + { + fpsent *e = players[i]; + if(e == d || !targetable(d, e)) continue; + vec ep = getaimpos(d, e); + float dist = ep.squaredist(dp); + if(dist < bestdist && (cansee(d, dp, ep) || dist <= mindist)) + { + t = e; + bestdist = dist; + } + } + if(t && violence(d, b, t, pursue)) return true; + return false; + } + + bool patrol(fpsent *d, aistate &b, const vec &pos, float guard, float wander, int walk, bool retry) + { + vec feet = d->feetpos(); + if(walk == 2 || b.override || (walk && feet.squaredist(pos) <= guard*guard) || !makeroute(d, b, pos)) + { // run away and back to keep ourselves busy + if(!b.override && randomnode(d, b, pos, guard, wander)) + { + b.override = true; + return true; + } + else if(d->ai->route.empty()) + { + if(!retry) + { + b.override = false; + return patrol(d, b, pos, guard, wander, walk, true); + } + b.override = false; + return false; + } + } + b.override = false; + return true; + } + + bool defend(fpsent *d, aistate &b, const vec &pos, float guard, float wander, int walk) + { + bool hasenemy = enemy(d, b, pos, wander, d->gunselect == GUN_FIST ? 1 : 0); + if(!walk) + { + if(d->feetpos().squaredist(pos) <= guard*guard) + { + b.idle = hasenemy ? 2 : 1; + return true; + } + walk++; + } + return patrol(d, b, pos, guard, wander, walk); + } + + bool violence(fpsent *d, aistate &b, fpsent *e, int pursue) + { + if(e && targetable(d, e)) + { + if(pursue) + { + if((b.targtype != AI_T_AFFINITY || !(pursue%2)) && makeroute(d, b, e->lastnode)) + d->ai->switchstate(b, AI_S_PURSUE, AI_T_PLAYER, e->clientnum); + else if(pursue >= 3) return false; // can't pursue + } + if(d->ai->enemy != e->clientnum) + { + d->ai->enemyseen = d->ai->enemymillis = lastmillis; + d->ai->enemy = e->clientnum; + } + return true; + } + return false; + } + + bool target(fpsent *d, aistate &b, int pursue = 0, bool force = false, float mindist = 0.f) + { + static vector hastried; hastried.setsize(0); + vec dp = d->headpos(); + while(true) + { + float dist = 1e16f; + fpsent *t = NULL; + loopv(players) + { + fpsent *e = players[i]; + if(e == d || hastried.find(e) >= 0 || !targetable(d, e)) continue; + vec ep = getaimpos(d, e); + float v = ep.squaredist(dp); + if((!t || v < dist) && (mindist <= 0 || v <= mindist) && (force || cansee(d, dp, ep))) + { + t = e; + dist = v; + } + } + if(t) + { + if(violence(d, b, t, pursue)) return true; + hastried.add(t); + } + else break; + } + return false; + } + + int isgoodammo(int gun) { return gun >= GUN_SG && gun <= GUN_GL; } + + bool hasgoodammo(fpsent *d) + { + static const int goodguns[] = { GUN_CG, GUN_RL, GUN_SG, GUN_RIFLE }; + loopi(sizeof(goodguns)/sizeof(goodguns[0])) if(d->hasammo(goodguns[0])) return true; + if(d->ammo[GUN_GL] > 5) return true; + return false; + } + + void assist(fpsent *d, aistate &b, vector &interests, bool all, bool force) + { + loopv(players) + { + fpsent *e = players[i]; + if(e == d || (!all && e->aitype != AI_NONE) || !isteam(d->team, e->team)) continue; + interest &n = interests.add(); + n.state = AI_S_DEFEND; + n.node = e->lastnode; + n.target = e->clientnum; + n.targtype = AI_T_PLAYER; + n.score = e->o.squaredist(d->o)/(hasgoodammo(d) ? 1e8f : (force ? 1e4f : 1e2f)); + } + } + + static void tryitem(fpsent *d, extentity &e, int id, aistate &b, vector &interests, bool force = false) + { + float score = 0; + switch(e.type) + { + case I_HEALTH: + if(d->health < min(d->skill, 75)) score = 1e3f; + break; + case I_QUAD: score = 1e3f; break; + case I_BOOST: score = 1e2f; break; + case I_GREENARMOUR: case I_YELLOWARMOUR: + { + int atype = A_GREEN + e.type - I_GREENARMOUR; + if(atype > d->armourtype) score = atype == A_YELLOW ? 1e2f : 1e1f; + else if(d->armour < 50) score = 1e1f; + break; + } + default: + { + if(e.type >= I_SHELLS && e.type <= I_CARTRIDGES && !d->hasmaxammo(e.type)) + { + int gun = e.type - I_SHELLS + GUN_SG; + // go get a weapon upgrade + if(gun == d->ai->weappref) score = 1e8f; + else if(isgoodammo(gun)) score = hasgoodammo(d) ? 1e2f : 1e4f; + } + break; + } + } + if(score != 0) + { + interest &n = interests.add(); + n.state = AI_S_INTEREST; + n.node = closestwaypoint(e.o, SIGHTMIN, true); + n.target = id; + n.targtype = AI_T_ENTITY; + n.score = d->feetpos().squaredist(e.o)/(force ? -1 : score); + } + } + + void items(fpsent *d, aistate &b, vector &interests, bool force = false) + { + loopv(entities::ents) + { + extentity &e = *(extentity *)entities::ents[i]; + if(!e.spawned() || e.nopickup() || !d->canpickup(e.type)) continue; + tryitem(d, e, i, b, interests, force); + } + } + + static vector targets; + + bool parseinterests(fpsent *d, aistate &b, vector &interests, bool override, bool ignore) + { + while(!interests.empty()) + { + int q = interests.length()-1; + loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i; + interest n = interests.removeunordered(q); + bool proceed = true; + if(!ignore) switch(n.state) + { + case AI_S_DEFEND: // don't get into herds + { + int members = 0; + proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true, &members) && members > 1; + break; + } + default: break; + } + if(proceed && makeroute(d, b, n.node)) + { + d->ai->switchstate(b, n.state, n.targtype, n.target); + return true; + } + } + return false; + } + + bool find(fpsent *d, aistate &b, bool override = false) + { + static vector interests; + interests.setsize(0); + if(!m_noitems) + { + if((!m_noammo && !hasgoodammo(d)) || d->health < min(d->skill - 15, 75)) + items(d, b, interests); + else + { + static vector nearby; + nearby.setsize(0); + findents(I_SHELLS, I_QUAD, false, d->feetpos(), vec(32, 32, 24), nearby); + loopv(nearby) + { + int id = nearby[i]; + extentity &e = *(extentity *)entities::ents[id]; + if(d->canpickup(e.type)) tryitem(d, e, id, b, interests); + } + } + } + if(m_teammode) assist(d, b, interests); + return parseinterests(d, b, interests, override); + } + + bool findassist(fpsent *d, aistate &b, bool override = false) + { + static vector interests; + interests.setsize(0); + assist(d, b, interests); + while(!interests.empty()) + { + int q = interests.length()-1; + loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i; + interest n = interests.removeunordered(q); + bool proceed = true; + switch(n.state) + { + case AI_S_DEFEND: // don't get into herds + { + int members = 0; + proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true, &members) && members > 1; + break; + } + default: break; + } + if(proceed && makeroute(d, b, n.node)) + { + d->ai->switchstate(b, n.state, n.targtype, n.target); + return true; + } + } + return false; + } + + void damaged(fpsent *d, fpsent *e) + { + if(d->ai && canmove(d) && targetable(d, e)) // see if this ai is interested in a grudge + { + aistate &b = d->ai->getstate(); + if(violence(d, b, e, d->gunselect == GUN_FIST ? 1 : 0)) return; + } + if(checkothers(targets, d, AI_S_DEFEND, AI_T_PLAYER, d->clientnum, true)) + { + loopv(targets) + { + fpsent *t = getclient(targets[i]); + if(!t->ai || !canmove(t) || !targetable(t, e)) continue; + aistate &c = t->ai->getstate(); + if(violence(t, c, e, d->gunselect == GUN_FIST ? 1 : 0)) return; + } + } + } + + void findorientation(vec &o, float yaw, float pitch, vec &pos) + { + vec dir; + vecfromyawpitch(yaw, pitch, 1, 0, dir); + if(raycubepos(o, dir, pos, 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1) + pos = dir.mul(2*getworldsize()).add(o); + } + + void setup(fpsent *d) + { + d->ai->clearsetup(); + d->ai->reset(true); + d->ai->lastrun = lastmillis; + if(m_insta) d->ai->weappref = GUN_RIFLE; + else + { + if(forcegun >= 0 && forcegun < NUMGUNS) d->ai->weappref = forcegun; + else if(m_noammo) d->ai->weappref = -1; + else d->ai->weappref = rnd(GUN_GL-GUN_SG+1)+GUN_SG; + } + vec dp = d->headpos(); + findorientation(dp, d->yaw, d->pitch, d->ai->target); + } + + void spawned(fpsent *d) + { + if(d->ai) setup(d); + } + + void killed(fpsent *d, fpsent *e) + { + if(d->ai) d->ai->reset(); + } + + void itemspawned(int ent) + { + if(entities::ents.inrange(ent) && entities::ents[ent]->type >= I_SHELLS && entities::ents[ent]->type <= I_QUAD) + { + loopv(players) if(players[i] && players[i]->ai && players[i]->aitype == AI_BOT && players[i]->canpickup(entities::ents[ent]->type)) + { + fpsent *d = players[i]; + bool wantsitem = false; + switch(entities::ents[ent]->type) + { + case I_BOOST: + + case I_HEALTH: wantsitem = badhealth(d); break; + case I_GREENARMOUR: + + case I_YELLOWARMOUR: + + case I_QUAD: break; + default: + { + itemstat &is = itemstats[entities::ents[ent]->type-I_SHELLS]; + wantsitem = isgoodammo(is.info) && d->ammo[is.info] <= (d->ai->weappref == is.info ? is.add : is.add/2); + break; + } + } + if(wantsitem) + { + aistate &b = d->ai->getstate(); + if(b.targtype == AI_T_AFFINITY) continue; + if(b.type == AI_S_INTEREST && b.targtype == AI_T_ENTITY) + { + if(entities::ents.inrange(b.target)) + { + if(d->o.squaredist(entities::ents[ent]->o) < d->o.squaredist(entities::ents[b.target]->o)) + d->ai->switchstate(b, AI_S_INTEREST, AI_T_ENTITY, ent); + } + continue; + } + d->ai->switchstate(b, AI_S_INTEREST, AI_T_ENTITY, ent); + } + } + } + } + + int dowait(fpsent *d, aistate &b) + { + d->ai->clear(true); // ensure they're clean + if(find(d, b)) return 1; + if(target(d, b, 4, false)) return 1; + if(target(d, b, 4, true)) return 1; + if(randomnode(d, b, SIGHTMIN, 1e16f)) + { + d->ai->switchstate(b, AI_S_INTEREST, AI_T_NODE, d->ai->route[0]); + return 1; + } + return 0; // but don't pop the state + } + + int dodefend(fpsent *d, aistate &b) + { + if(d->state == CS_ALIVE) + { + switch(b.targtype) + { + case AI_T_NODE: + if(iswaypoint(b.target)) return defend(d, b, waypoints[b.target].o) ? 1 : 0; + break; + case AI_T_ENTITY: + if(entities::ents.inrange(b.target)) return defend(d, b, entities::ents[b.target]->o) ? 1 : 0; + break; + case AI_T_PLAYER: + { + fpsent *e = getclient(b.target); + if(e && e->state == CS_ALIVE) return defend(d, b, e->feetpos()) ? 1 : 0; + break; + } + default: break; + } + } + return 0; + } + + int dointerest(fpsent *d, aistate &b) + { + if(d->state != CS_ALIVE) return 0; + switch(b.targtype) + { + case AI_T_NODE: // this is like a wait state without sitting still.. + if(find(d, b)) return 1; + if(target(d, b, 4, true)) return 1; + if(iswaypoint(b.target) && vec(waypoints[b.target].o).sub(d->feetpos()).magnitude() > CLOSEDIST) + return makeroute(d, b, waypoints[b.target].o) ? 1 : 0; + break; + case AI_T_ENTITY: + if(entities::ents.inrange(b.target)) + { + extentity &e = *(extentity *)entities::ents[b.target]; + if(!e.spawned() || e.nopickup() || e.type < I_SHELLS || e.type > I_CARTRIDGES || d->hasmaxammo(e.type)) return 0; + //if(d->feetpos().squaredist(e.o) <= CLOSEDIST*CLOSEDIST) + //{ + // b.idle = 1; + // return true; + //} + return makeroute(d, b, e.o) ? 1 : 0; + } + break; + } + return 0; + } + + int dopursue(fpsent *d, aistate &b) + { + if(d->state == CS_ALIVE) + { + switch(b.targtype) + { + case AI_T_NODE: + { + if(iswaypoint(b.target)) + return defend(d, b, waypoints[b.target].o) ? 1 : 0; + break; + } + + case AI_T_PLAYER: + { + fpsent *e = getclient(b.target); + if(e && e->state == CS_ALIVE) + { + float guard = SIGHTMIN, wander = guns[d->gunselect].range; + if(d->gunselect == GUN_FIST) guard = 0.f; + return patrol(d, b, e->feetpos(), guard, wander) ? 1 : 0; + } + break; + } + default: break; + } + } + return 0; + } + + int closenode(fpsent *d) + { + vec pos = d->feetpos(); + int node1 = -1, node2 = -1; + float mindist1 = CLOSEDIST*CLOSEDIST, mindist2 = CLOSEDIST*CLOSEDIST; + loopv(d->ai->route) if(iswaypoint(d->ai->route[i])) + { + vec epos = waypoints[d->ai->route[i]].o; + float dist = epos.squaredist(pos); + if(dist > FARDIST*FARDIST) continue; + int entid = obstacles.remap(d, d->ai->route[i], epos); + if(entid >= 0) + { + if(entid != i) dist = epos.squaredist(pos); + if(dist < mindist1) { node1 = i; mindist1 = dist; } + } + else if(dist < mindist2) { node2 = i; mindist2 = dist; } + } + return node1 >= 0 ? node1 : node2; + } + + int wpspot(fpsent *d, int n, bool check = false) + { + if(iswaypoint(n)) loopk(2) + { + vec epos = waypoints[n].o; + int entid = obstacles.remap(d, n, epos, k!=0); + if(iswaypoint(entid)) + { + d->ai->spot = epos; + d->ai->targnode = entid; + return !check || d->feetpos().squaredist(epos) > MINWPDIST*MINWPDIST ? 1 : 2; + } + } + return 0; + } + + int randomlink(fpsent *d, int n) + { + if(iswaypoint(n) && waypoints[n].haslinks()) + { + waypoint &w = waypoints[n]; + static vector linkmap; linkmap.setsize(0); + loopi(MAXWAYPOINTLINKS) + { + if(!w.links[i]) break; + if(iswaypoint(w.links[i]) && !d->ai->hasprevnode(w.links[i]) && d->ai->route.find(w.links[i]) < 0) + linkmap.add(w.links[i]); + } + if(!linkmap.empty()) return linkmap[rnd(linkmap.length())]; + } + return -1; + } + + bool anynode(fpsent *d, aistate &b, int len = NUMPREVNODES) + { + if(iswaypoint(d->lastnode)) loopk(2) + { + d->ai->clear(k ? true : false); + int n = randomlink(d, d->lastnode); + if(wpspot(d, n)) + { + d->ai->route.add(n); + d->ai->route.add(d->lastnode); + loopi(len) + { + n = randomlink(d, n); + if(iswaypoint(n)) d->ai->route.insert(0, n); + else break; + } + return true; + } } - // retry fails: 0 = first attempt, 1 = try ignoring obstacles, 2 = try ignoring prevnodes too - if(retries <= 1) return makeroute(d, b, node, false, retries+1); return false; - } - - bool makeroute(fpsent *d, aistate &b, const vec &pos, bool changed, int retries) - { - int node = closestwaypoint(pos, SIGHTMIN, true); - return makeroute(d, b, node, changed, retries); - } - - bool randomnode(fpsent *d, aistate &b, const vec &pos, float guard, float wander) - { - static vector candidates; - candidates.setsize(0); - findwaypointswithin(pos, guard, wander, candidates); - - while(!candidates.empty()) - { - int w = rnd(candidates.length()), n = candidates.removeunordered(w); - if(n != d->lastnode && !d->ai->hasprevnode(n) && !obstacles.find(n, d) && makeroute(d, b, n)) return true; - } - return false; - } - - bool randomnode(fpsent *d, aistate &b, float guard, float wander) - { - return randomnode(d, b, d->feetpos(), guard, wander); - } - - bool badhealth(fpsent *d) - { - if(d->skill <= 100) return d->health <= (111-d->skill)/4; - return false; - } - - bool enemy(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, int pursue = 0) - { - fpsent *t = NULL; - vec dp = d->headpos(); - float mindist = guard*guard, bestdist = 1e16f; - loopv(players) - { - fpsent *e = players[i]; - if(e == d || !targetable(d, e)) continue; - vec ep = getaimpos(d, e); - float dist = ep.squaredist(dp); - if(dist < bestdist && (cansee(d, dp, ep) || dist <= mindist)) - { - t = e; - bestdist = dist; - } - } - if(t && violence(d, b, t, pursue)) return true; - return false; - } - - bool patrol(fpsent *d, aistate &b, const vec &pos, float guard, float wander, int walk, bool retry) - { - vec feet = d->feetpos(); - if(walk == 2 || b.override || (walk && feet.squaredist(pos) <= guard*guard) || !makeroute(d, b, pos)) - { // run away and back to keep ourselves busy - if(!b.override && randomnode(d, b, pos, guard, wander)) - { - b.override = true; - return true; - } - else if(d->ai->route.empty()) - { - if(!retry) - { - b.override = false; - return patrol(d, b, pos, guard, wander, walk, true); - } - b.override = false; - return false; - } - } - b.override = false; - return true; - } - - bool defend(fpsent *d, aistate &b, const vec &pos, float guard, float wander, int walk) - { - bool hasenemy = enemy(d, b, pos, wander, d->gunselect == GUN_FIST ? 1 : 0); - if(!walk) + } + + bool checkroute(fpsent *d, int n) + { + if(d->ai->route.empty() || !d->ai->route.inrange(n)) return false; + int last = d->ai->lastcheck ? lastmillis-d->ai->lastcheck : 0; + if(last < 500 || n < 3) return false; // route length is too short + d->ai->lastcheck = lastmillis; + int w = iswaypoint(d->lastnode) ? d->lastnode : d->ai->route[n], c = min(n-1, NUMPREVNODES); + loopj(c) // check ahead to see if we need to go around something { - if(d->feetpos().squaredist(pos) <= guard*guard) - { - b.idle = hasenemy ? 2 : 1; - return true; - } - walk++; - } - return patrol(d, b, pos, guard, wander, walk); - } - - bool violence(fpsent *d, aistate &b, fpsent *e, int pursue) - { - if(e && targetable(d, e)) - { - if(pursue) - { - if((b.targtype != AI_T_AFFINITY || !(pursue%2)) && makeroute(d, b, e->lastnode)) - d->ai->switchstate(b, AI_S_PURSUE, AI_T_PLAYER, e->clientnum); - else if(pursue >= 3) return false; // can't pursue - } - if(d->ai->enemy != e->clientnum) - { - d->ai->enemyseen = d->ai->enemymillis = lastmillis; - d->ai->enemy = e->clientnum; - } - return true; - } - return false; - } - - bool target(fpsent *d, aistate &b, int pursue = 0, bool force = false, float mindist = 0.f) - { - static vector hastried; hastried.setsize(0); - vec dp = d->headpos(); - while(true) - { - float dist = 1e16f; - fpsent *t = NULL; - loopv(players) - { - fpsent *e = players[i]; - if(e == d || hastried.find(e) >= 0 || !targetable(d, e)) continue; - vec ep = getaimpos(d, e); - float v = ep.squaredist(dp); - if((!t || v < dist) && (mindist <= 0 || v <= mindist) && (force || cansee(d, dp, ep))) - { - t = e; - dist = v; - } - } - if(t) - { - if(violence(d, b, t, pursue)) return true; - hastried.add(t); - } - else break; - } - return false; - } - - int isgoodammo(int gun) { return gun >= GUN_SG && gun <= GUN_GL; } - - bool hasgoodammo(fpsent *d) - { - static const int goodguns[] = { GUN_CG, GUN_RL, GUN_SG, GUN_RIFLE }; - loopi(sizeof(goodguns)/sizeof(goodguns[0])) if(d->hasammo(goodguns[0])) return true; - if(d->ammo[GUN_GL] > 5) return true; - return false; - } - - void assist(fpsent *d, aistate &b, vector &interests, bool all, bool force) - { - loopv(players) - { - fpsent *e = players[i]; - if(e == d || (!all && e->aitype != AI_NONE) || !isteam(d->team, e->team)) continue; - interest &n = interests.add(); - n.state = AI_S_DEFEND; - n.node = e->lastnode; - n.target = e->clientnum; - n.targtype = AI_T_PLAYER; - n.score = e->o.squaredist(d->o)/(hasgoodammo(d) ? 1e8f : (force ? 1e4f : 1e2f)); - } - } - - static void tryitem(fpsent *d, extentity &e, int id, aistate &b, vector &interests, bool force = false) - { - float score = 0; - switch(e.type) - { - case I_HEALTH: - if(d->health < min(d->skill, 75)) score = 1e3f; - break; - case I_QUAD: score = 1e3f; break; - case I_BOOST: score = 1e2f; break; - case I_GREENARMOUR: case I_YELLOWARMOUR: - { - int atype = A_GREEN + e.type - I_GREENARMOUR; - if(atype > d->armourtype) score = atype == A_YELLOW ? 1e2f : 1e1f; - else if(d->armour < 50) score = 1e1f; - break; - } - default: - { - if(e.type >= I_SHELLS && e.type <= I_CARTRIDGES && !d->hasmaxammo(e.type)) - { - int gun = e.type - I_SHELLS + GUN_SG; - // go get a weapon upgrade - if(gun == d->ai->weappref) score = 1e8f; - else if(isgoodammo(gun)) score = hasgoodammo(d) ? 1e2f : 1e4f; - } - break; - } - } - if(score != 0) - { - interest &n = interests.add(); - n.state = AI_S_INTEREST; - n.node = closestwaypoint(e.o, SIGHTMIN, true); - n.target = id; - n.targtype = AI_T_ENTITY; - n.score = d->feetpos().squaredist(e.o)/(force ? -1 : score); - } - } - - void items(fpsent *d, aistate &b, vector &interests, bool force = false) - { - loopv(entities::ents) - { - extentity &e = *(extentity *)entities::ents[i]; - if(!e.spawned() || e.nopickup() || !d->canpickup(e.type)) continue; - tryitem(d, e, i, b, interests, force); - } - } - - static vector targets; - - bool parseinterests(fpsent *d, aistate &b, vector &interests, bool override, bool ignore) - { - while(!interests.empty()) - { - int q = interests.length()-1; - loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i; - interest n = interests.removeunordered(q); - bool proceed = true; - if(!ignore) switch(n.state) - { - case AI_S_DEFEND: // don't get into herds - { - int members = 0; - proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true, &members) && members > 1; - break; - } - default: break; - } - if(proceed && makeroute(d, b, n.node)) - { - d->ai->switchstate(b, n.state, n.targtype, n.target); - return true; - } - } - return false; - } - - bool find(fpsent *d, aistate &b, bool override = false) - { - static vector interests; - interests.setsize(0); - if(!m_noitems) - { - if((!m_noammo && !hasgoodammo(d)) || d->health < min(d->skill - 15, 75)) - items(d, b, interests); - else - { - static vector nearby; - nearby.setsize(0); - findents(I_SHELLS, I_QUAD, false, d->feetpos(), vec(32, 32, 24), nearby); - loopv(nearby) - { - int id = nearby[i]; - extentity &e = *(extentity *)entities::ents[id]; - if(d->canpickup(e.type)) tryitem(d, e, id, b, interests); - } - } - } - if(m_teammode) assist(d, b, interests); - return parseinterests(d, b, interests, override); - } - - bool findassist(fpsent *d, aistate &b, bool override = false) - { - static vector interests; - interests.setsize(0); - assist(d, b, interests); - while(!interests.empty()) - { - int q = interests.length()-1; - loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i; - interest n = interests.removeunordered(q); - bool proceed = true; - switch(n.state) - { - case AI_S_DEFEND: // don't get into herds - { - int members = 0; - proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true, &members) && members > 1; - break; - } - default: break; - } - if(proceed && makeroute(d, b, n.node)) - { - d->ai->switchstate(b, n.state, n.targtype, n.target); - return true; - } - } - return false; - } - - void damaged(fpsent *d, fpsent *e) - { - if(d->ai && canmove(d) && targetable(d, e)) // see if this ai is interested in a grudge - { - aistate &b = d->ai->getstate(); - if(violence(d, b, e, d->gunselect == GUN_FIST ? 1 : 0)) return; - } - if(checkothers(targets, d, AI_S_DEFEND, AI_T_PLAYER, d->clientnum, true)) - { - loopv(targets) - { - fpsent *t = getclient(targets[i]); - if(!t->ai || !canmove(t) || !targetable(t, e)) continue; - aistate &c = t->ai->getstate(); - if(violence(t, c, e, d->gunselect == GUN_FIST ? 1 : 0)) return; - } - } - } - - void findorientation(vec &o, float yaw, float pitch, vec &pos) - { - vec dir; - vecfromyawpitch(yaw, pitch, 1, 0, dir); - if(raycubepos(o, dir, pos, 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1) - pos = dir.mul(2*getworldsize()).add(o); - } - - void setup(fpsent *d) - { - d->ai->clearsetup(); - d->ai->reset(true); - d->ai->lastrun = lastmillis; - if(m_insta) d->ai->weappref = GUN_RIFLE; - else - { - if(forcegun >= 0 && forcegun < NUMGUNS) d->ai->weappref = forcegun; - else if(m_noammo) d->ai->weappref = -1; - else d->ai->weappref = rnd(GUN_GL-GUN_SG+1)+GUN_SG; - } - vec dp = d->headpos(); - findorientation(dp, d->yaw, d->pitch, d->ai->target); - } - - void spawned(fpsent *d) - { - if(d->ai) setup(d); - } - - void killed(fpsent *d, fpsent *e) - { - if(d->ai) d->ai->reset(); - } - - void itemspawned(int ent) - { - if(entities::ents.inrange(ent) && entities::ents[ent]->type >= I_SHELLS && entities::ents[ent]->type <= I_QUAD) - { - loopv(players) if(players[i] && players[i]->ai && players[i]->aitype == AI_BOT && players[i]->canpickup(entities::ents[ent]->type)) - { - fpsent *d = players[i]; - bool wantsitem = false; - switch(entities::ents[ent]->type) - { - case I_BOOST: - - case I_HEALTH: wantsitem = badhealth(d); break; - case I_GREENARMOUR: - - case I_YELLOWARMOUR: - - case I_QUAD: break; - default: - { - itemstat &is = itemstats[entities::ents[ent]->type-I_SHELLS]; - wantsitem = isgoodammo(is.info) && d->ammo[is.info] <= (d->ai->weappref == is.info ? is.add : is.add/2); - break; - } - } - if(wantsitem) - { - aistate &b = d->ai->getstate(); - if(b.targtype == AI_T_AFFINITY) continue; - if(b.type == AI_S_INTEREST && b.targtype == AI_T_ENTITY) - { - if(entities::ents.inrange(b.target)) - { - if(d->o.squaredist(entities::ents[ent]->o) < d->o.squaredist(entities::ents[b.target]->o)) - d->ai->switchstate(b, AI_S_INTEREST, AI_T_ENTITY, ent); - } - continue; - } - d->ai->switchstate(b, AI_S_INTEREST, AI_T_ENTITY, ent); - } - } - } - } - - int dowait(fpsent *d, aistate &b) - { - d->ai->clear(true); // ensure they're clean - if(find(d, b)) return 1; - if(target(d, b, 4, false)) return 1; - if(target(d, b, 4, true)) return 1; - if(randomnode(d, b, SIGHTMIN, 1e16f)) - { - d->ai->switchstate(b, AI_S_INTEREST, AI_T_NODE, d->ai->route[0]); - return 1; - } - return 0; // but don't pop the state - } - - int dodefend(fpsent *d, aistate &b) - { - if(d->state == CS_ALIVE) - { - switch(b.targtype) - { - case AI_T_NODE: - if(iswaypoint(b.target)) return defend(d, b, waypoints[b.target].o) ? 1 : 0; - break; - case AI_T_ENTITY: - if(entities::ents.inrange(b.target)) return defend(d, b, entities::ents[b.target]->o) ? 1 : 0; - break; - case AI_T_PLAYER: - { - fpsent *e = getclient(b.target); - if(e && e->state == CS_ALIVE) return defend(d, b, e->feetpos()) ? 1 : 0; - break; - } - default: break; - } - } - return 0; - } - - int dointerest(fpsent *d, aistate &b) - { - if(d->state != CS_ALIVE) return 0; - switch(b.targtype) - { - case AI_T_NODE: // this is like a wait state without sitting still.. - if(find(d, b)) return 1; - if(target(d, b, 4, true)) return 1; - if(iswaypoint(b.target) && vec(waypoints[b.target].o).sub(d->feetpos()).magnitude() > CLOSEDIST) - return makeroute(d, b, waypoints[b.target].o) ? 1 : 0; - break; - case AI_T_ENTITY: - if(entities::ents.inrange(b.target)) - { - extentity &e = *(extentity *)entities::ents[b.target]; - if(!e.spawned() || e.nopickup() || e.type < I_SHELLS || e.type > I_CARTRIDGES || d->hasmaxammo(e.type)) return 0; - //if(d->feetpos().squaredist(e.o) <= CLOSEDIST*CLOSEDIST) - //{ - // b.idle = 1; - // return true; - //} - return makeroute(d, b, e.o) ? 1 : 0; - } - break; - } - return 0; - } - - int dopursue(fpsent *d, aistate &b) - { - if(d->state == CS_ALIVE) - { - switch(b.targtype) - { - case AI_T_NODE: - { - if(iswaypoint(b.target)) - return defend(d, b, waypoints[b.target].o) ? 1 : 0; - break; - } - - case AI_T_PLAYER: - { - fpsent *e = getclient(b.target); - if(e && e->state == CS_ALIVE) - { - float guard = SIGHTMIN, wander = guns[d->gunselect].range; - if(d->gunselect == GUN_FIST) guard = 0.f; - return patrol(d, b, e->feetpos(), guard, wander) ? 1 : 0; - } - break; - } - default: break; - } - } - return 0; - } - - int closenode(fpsent *d) - { - vec pos = d->feetpos(); - int node1 = -1, node2 = -1; - float mindist1 = CLOSEDIST*CLOSEDIST, mindist2 = CLOSEDIST*CLOSEDIST; - loopv(d->ai->route) if(iswaypoint(d->ai->route[i])) - { - vec epos = waypoints[d->ai->route[i]].o; - float dist = epos.squaredist(pos); - if(dist > FARDIST*FARDIST) continue; - int entid = obstacles.remap(d, d->ai->route[i], epos); - if(entid >= 0) - { - if(entid != i) dist = epos.squaredist(pos); - if(dist < mindist1) { node1 = i; mindist1 = dist; } - } - else if(dist < mindist2) { node2 = i; mindist2 = dist; } - } - return node1 >= 0 ? node1 : node2; - } - - int wpspot(fpsent *d, int n, bool check = false) - { - if(iswaypoint(n)) loopk(2) - { - vec epos = waypoints[n].o; - int entid = obstacles.remap(d, n, epos, k!=0); - if(iswaypoint(entid)) - { - d->ai->spot = epos; - d->ai->targnode = entid; - return !check || d->feetpos().squaredist(epos) > MINWPDIST*MINWPDIST ? 1 : 2; - } - } - return 0; - } - - int randomlink(fpsent *d, int n) - { - if(iswaypoint(n) && waypoints[n].haslinks()) - { - waypoint &w = waypoints[n]; - static vector linkmap; linkmap.setsize(0); - loopi(MAXWAYPOINTLINKS) - { - if(!w.links[i]) break; - if(iswaypoint(w.links[i]) && !d->ai->hasprevnode(w.links[i]) && d->ai->route.find(w.links[i]) < 0) - linkmap.add(w.links[i]); - } - if(!linkmap.empty()) return linkmap[rnd(linkmap.length())]; - } - return -1; - } - - bool anynode(fpsent *d, aistate &b, int len = NUMPREVNODES) - { - if(iswaypoint(d->lastnode)) loopk(2) - { - d->ai->clear(k ? true : false); - int n = randomlink(d, d->lastnode); - if(wpspot(d, n)) - { - d->ai->route.add(n); - d->ai->route.add(d->lastnode); - loopi(len) - { - n = randomlink(d, n); - if(iswaypoint(n)) d->ai->route.insert(0, n); - else break; - } - return true; - } - } - return false; - } - - bool checkroute(fpsent *d, int n) - { - if(d->ai->route.empty() || !d->ai->route.inrange(n)) return false; - int last = d->ai->lastcheck ? lastmillis-d->ai->lastcheck : 0; - if(last < 500 || n < 3) return false; // route length is too short - d->ai->lastcheck = lastmillis; - int w = iswaypoint(d->lastnode) ? d->lastnode : d->ai->route[n], c = min(n-1, NUMPREVNODES); - loopj(c) // check ahead to see if we need to go around something - { - int p = n-j-1, v = d->ai->route[p]; - if(d->ai->hasprevnode(v) || obstacles.find(v, d)) // something is in the way, try to remap around it - { - int m = p-1; - if(m < 3) return false; // route length is too short from this point - loopirev(m) - { - int t = d->ai->route[i]; - if(!d->ai->hasprevnode(t) && !obstacles.find(t, d)) - { - static vector remap; remap.setsize(0); - if(route(d, w, t, remap, obstacles)) - { // kill what we don't want and put the remap in - while(d->ai->route.length() > i) d->ai->route.pop(); - loopvk(remap) d->ai->route.add(remap[k]); - return true; - } - return false; // we failed - } - } - return false; - } - } - return false; - } - - bool hunt(fpsent *d, aistate &b) - { - if(!d->ai->route.empty()) - { - int n = closenode(d); - if(d->ai->route.inrange(n) && checkroute(d, n)) n = closenode(d); - if(d->ai->route.inrange(n)) - { - if(!n) - { - switch(wpspot(d, d->ai->route[n], true)) - { - case 2: d->ai->clear(false); - [[fallthrough]]; - - case 1: return true; // not close enough to pop it yet - default: break; - } - } - else - { - while(d->ai->route.length() > n+1) d->ai->route.pop(); // waka-waka-waka-waka - int m = n-1; // next, please! - if(d->ai->route.inrange(m) && wpspot(d, d->ai->route[m])) return true; - } - } - } - b.override = false; - return anynode(d, b); - } - - void jumpto(fpsent *d, aistate &b, const vec &pos) - { + int p = n-j-1, v = d->ai->route[p]; + if(d->ai->hasprevnode(v) || obstacles.find(v, d)) // something is in the way, try to remap around it + { + int m = p-1; + if(m < 3) return false; // route length is too short from this point + loopirev(m) + { + int t = d->ai->route[i]; + if(!d->ai->hasprevnode(t) && !obstacles.find(t, d)) + { + static vector remap; remap.setsize(0); + if(route(d, w, t, remap, obstacles)) + { // kill what we don't want and put the remap in + while(d->ai->route.length() > i) d->ai->route.pop(); + loopvk(remap) d->ai->route.add(remap[k]); + return true; + } + return false; // we failed + } + } + return false; + } + } + return false; + } + + bool hunt(fpsent *d, aistate &b) + { + if(!d->ai->route.empty()) + { + int n = closenode(d); + if(d->ai->route.inrange(n) && checkroute(d, n)) n = closenode(d); + if(d->ai->route.inrange(n)) + { + if(!n) + { + switch(wpspot(d, d->ai->route[n], true)) + { + case 2: d->ai->clear(false); + [[fallthrough]]; + + case 1: return true; // not close enough to pop it yet + default: break; + } + } + else + { + while(d->ai->route.length() > n+1) d->ai->route.pop(); // waka-waka-waka-waka + int m = n-1; // next, please! + if(d->ai->route.inrange(m) && wpspot(d, d->ai->route[m])) return true; + } + } + } + b.override = false; + return anynode(d, b); + } + + void jumpto(fpsent *d, aistate &b, const vec &pos) + { vec off = vec(pos).sub(d->feetpos()), dir(off.x, off.y, 0); - bool sequenced = d->ai->blockseq || d->ai->targseq, offground = d->timeinair && !d->inwater, - jump = !offground && lastmillis >= d->ai->jumpseed && (sequenced || off.z >= JUMPMIN || lastmillis >= d->ai->jumprand); + bool sequenced = d->ai->blockseq || d->ai->targseq, offground = d->timeinair && !d->inwater, + jump = !offground && lastmillis >= d->ai->jumpseed && (sequenced || off.z >= JUMPMIN || lastmillis >= d->ai->jumprand); if(jump) { vec old = d->o; @@ -903,551 +903,551 @@ namespace ai seed *= b.idle ? 50 : 25; d->ai->jumprand = lastmillis+seed+rnd(seed); } - } - - void fixfullrange(float &yaw, float &pitch, float &roll, bool full) - { - if(full) - { - while(pitch < -180.0f) pitch += 360.0f; - while(pitch >= 180.0f) pitch -= 360.0f; - while(roll < -180.0f) roll += 360.0f; - while(roll >= 180.0f) roll -= 360.0f; - } - else - { - if(pitch > 89.9f) pitch = 89.9f; - if(pitch < -89.9f) pitch = -89.9f; - if(roll > 89.9f) roll = 89.9f; - if(roll < -89.9f) roll = -89.9f; - } - while(yaw < 0.0f) yaw += 360.0f; - while(yaw >= 360.0f) yaw -= 360.0f; - } - - void fixrange(float &yaw, float &pitch) - { - float r = 0.f; - fixfullrange(yaw, pitch, r, false); - } - - void getyawpitch(const vec &from, const vec &pos, float &yaw, float &pitch) - { - float dist = from.dist(pos); - yaw = -atan2(pos.x-from.x, pos.y-from.y)/RAD; - pitch = asin((pos.z-from.z)/dist)/RAD; - } - - void scaleyawpitch(float &yaw, float &pitch, float targyaw, float targpitch, float frame, float scale) - { - if(yaw < targyaw-180.0f) yaw += 360.0f; - if(yaw > targyaw+180.0f) yaw -= 360.0f; - float offyaw = fabs(targyaw-yaw)*frame, offpitch = fabs(targpitch-pitch)*frame*scale; - if(targyaw > yaw) - { - yaw += offyaw; - if(targyaw < yaw) yaw = targyaw; - } - else if(targyaw < yaw) - { - yaw -= offyaw; - if(targyaw > yaw) yaw = targyaw; - } - if(targpitch > pitch) - { - pitch += offpitch; - if(targpitch < pitch) pitch = targpitch; - } - else if(targpitch < pitch) - { - pitch -= offpitch; - if(targpitch > pitch) pitch = targpitch; - } - fixrange(yaw, pitch); - } - - bool lockon(fpsent *d, fpsent *e, float maxdist) - { - if(d->gunselect == GUN_FIST && !d->blocked && !d->timeinair) - { - vec dir = vec(e->o).sub(d->o); - float xydist = dir.x*dir.x+dir.y*dir.y, zdist = dir.z*dir.z, mdist = maxdist*maxdist, ddist = d->radius*d->radius+e->radius*e->radius; - if(zdist <= ddist && xydist >= ddist+4 && xydist <= mdist+ddist) return true; - } - return false; - } - - int process(fpsent *d, aistate &b) - { - int result = 0, stupify = d->skill <= 10+rnd(15) ? rnd(d->skill*1000) : 0, skmod = 101-d->skill; - float frame = d->skill <= 100 ? float(lastmillis-d->ai->lastrun)/float(max(skmod,1)*10) : 1; - vec dp = d->headpos(); - - bool idle = b.idle == 1 || (stupify && stupify <= skmod); - d->ai->dontmove = false; - if(idle) - { - d->ai->lastaction = d->ai->lasthunt = lastmillis; - d->ai->dontmove = true; - d->ai->spot = vec(0, 0, 0); - } - else if(hunt(d, b)) - { - getyawpitch(dp, vec(d->ai->spot).add(vec(0, 0, d->eyeheight)), d->ai->targyaw, d->ai->targpitch); - d->ai->lasthunt = lastmillis; - } - else - { - idle = d->ai->dontmove = true; - d->ai->spot = vec(0, 0, 0); - } + } + + void fixfullrange(float &yaw, float &pitch, float &roll, bool full) + { + if(full) + { + while(pitch < -180.0f) pitch += 360.0f; + while(pitch >= 180.0f) pitch -= 360.0f; + while(roll < -180.0f) roll += 360.0f; + while(roll >= 180.0f) roll -= 360.0f; + } + else + { + if(pitch > 89.9f) pitch = 89.9f; + if(pitch < -89.9f) pitch = -89.9f; + if(roll > 89.9f) roll = 89.9f; + if(roll < -89.9f) roll = -89.9f; + } + while(yaw < 0.0f) yaw += 360.0f; + while(yaw >= 360.0f) yaw -= 360.0f; + } + + void fixrange(float &yaw, float &pitch) + { + float r = 0.f; + fixfullrange(yaw, pitch, r, false); + } + + void getyawpitch(const vec &from, const vec &pos, float &yaw, float &pitch) + { + float dist = from.dist(pos); + yaw = -atan2(pos.x-from.x, pos.y-from.y)/RAD; + pitch = asin((pos.z-from.z)/dist)/RAD; + } + + void scaleyawpitch(float &yaw, float &pitch, float targyaw, float targpitch, float frame, float scale) + { + if(yaw < targyaw-180.0f) yaw += 360.0f; + if(yaw > targyaw+180.0f) yaw -= 360.0f; + float offyaw = fabs(targyaw-yaw)*frame, offpitch = fabs(targpitch-pitch)*frame*scale; + if(targyaw > yaw) + { + yaw += offyaw; + if(targyaw < yaw) yaw = targyaw; + } + else if(targyaw < yaw) + { + yaw -= offyaw; + if(targyaw > yaw) yaw = targyaw; + } + if(targpitch > pitch) + { + pitch += offpitch; + if(targpitch < pitch) pitch = targpitch; + } + else if(targpitch < pitch) + { + pitch -= offpitch; + if(targpitch > pitch) pitch = targpitch; + } + fixrange(yaw, pitch); + } + + bool lockon(fpsent *d, fpsent *e, float maxdist) + { + if(d->gunselect == GUN_FIST && !d->blocked && !d->timeinair) + { + vec dir = vec(e->o).sub(d->o); + float xydist = dir.x*dir.x+dir.y*dir.y, zdist = dir.z*dir.z, mdist = maxdist*maxdist, ddist = d->radius*d->radius+e->radius*e->radius; + if(zdist <= ddist && xydist >= ddist+4 && xydist <= mdist+ddist) return true; + } + return false; + } + + int process(fpsent *d, aistate &b) + { + int result = 0, stupify = d->skill <= 10+rnd(15) ? rnd(d->skill*1000) : 0, skmod = 101-d->skill; + float frame = d->skill <= 100 ? float(lastmillis-d->ai->lastrun)/float(max(skmod,1)*10) : 1; + vec dp = d->headpos(); + + bool idle = b.idle == 1 || (stupify && stupify <= skmod); + d->ai->dontmove = false; + if(idle) + { + d->ai->lastaction = d->ai->lasthunt = lastmillis; + d->ai->dontmove = true; + d->ai->spot = vec(0, 0, 0); + } + else if(hunt(d, b)) + { + getyawpitch(dp, vec(d->ai->spot).add(vec(0, 0, d->eyeheight)), d->ai->targyaw, d->ai->targpitch); + d->ai->lasthunt = lastmillis; + } + else + { + idle = d->ai->dontmove = true; + d->ai->spot = vec(0, 0, 0); + } if(!d->ai->dontmove) jumpto(d, b, d->ai->spot); - fpsent *e = getclient(d->ai->enemy); - bool enemyok = e && targetable(d, e); - if(!enemyok || d->skill >= 50) - { - fpsent *f = (fpsent *)intersectclosest(dp, d->ai->target, d); - if(f) - { - if(targetable(d, f)) - { - if(!enemyok) violence(d, b, f, d->gunselect == GUN_FIST ? 1 : 0); - enemyok = true; - e = f; - } - else enemyok = false; - } - else if(!enemyok && target(d, b, d->gunselect == GUN_FIST ? 1 : 0, false, SIGHTMIN)) - enemyok = (e = getclient(d->ai->enemy)) != NULL; - } - if(enemyok) - { - vec ep = getaimpos(d, e); - float yaw, pitch; - getyawpitch(dp, ep, yaw, pitch); - fixrange(yaw, pitch); - bool insight = cansee(d, dp, ep), hasseen = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->skill*10)+3000, - quick = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->gunselect == GUN_CG ? 300 : skmod)+30; - if(insight) d->ai->enemyseen = lastmillis; - if(idle || insight || hasseen || quick) - { - float sskew = insight || d->skill > 100 ? 1.5f : (hasseen ? 1.f : 0.5f); - if(insight && lockon(d, e, 16)) - { - d->ai->targyaw = yaw; - d->ai->targpitch = pitch; - if(!idle) frame *= 2; - d->ai->becareful = false; - } - scaleyawpitch(d->yaw, d->pitch, yaw, pitch, frame, sskew); - if(insight || quick) - { - if(canshoot(d, e) && hastarget(d, b, e, yaw, pitch, dp.squaredist(ep))) - { - d->attacking = true; - d->ai->lastaction = lastmillis; - result = 3; - } - else result = 2; - } - else result = 1; - } - else - { - if(!d->ai->enemyseen || lastmillis-d->ai->enemyseen > (d->skill*50)+3000) - { - d->ai->enemy = -1; - d->ai->enemyseen = d->ai->enemymillis = 0; - } - enemyok = false; - result = 0; - } - } - else - { - if(!enemyok) - { - d->ai->enemy = -1; - d->ai->enemyseen = d->ai->enemymillis = 0; - } - enemyok = false; - result = 0; - } - - fixrange(d->ai->targyaw, d->ai->targpitch); - if(!result) scaleyawpitch(d->yaw, d->pitch, d->ai->targyaw, d->ai->targpitch, frame*0.25f, 1.f); - - if(d->ai->becareful && d->physstate == PHYS_FALL) - { - float offyaw, offpitch; - vectoyawpitch(d->vel, offyaw, offpitch); - offyaw -= d->yaw; offpitch -= d->pitch; - if(fabs(offyaw)+fabs(offpitch) >= 135) d->ai->becareful = false; - else if(d->ai->becareful) d->ai->dontmove = true; - } - else d->ai->becareful = false; - - if(d->ai->dontmove) d->move = d->strafe = 0; - else - { // our guys move one way.. but turn another?! :) - const struct aimdir { int move, strafe, offset; } aimdirs[8] = - { - { 1, 0, 0 }, - { 1, -1, 45 }, - { 0, -1, 90 }, - { -1, -1, 135 }, - { -1, 0, 180 }, - { -1, 1, 225 }, - { 0, 1, 270 }, - { 1, 1, 315 } - }; - float yaw = d->ai->targyaw-d->yaw; - while(yaw < 0.0f) yaw += 360.0f; - while(yaw >= 360.0f) yaw -= 360.0f; - int r = clamp(((int)floor((yaw+22.5f)/45.0f))&7, 0, 7); - const aimdir &ad = aimdirs[r]; - d->move = ad.move; - d->strafe = ad.strafe; - } - findorientation(dp, d->yaw, d->pitch, d->ai->target); - return result; - } - - bool hasrange(fpsent *d, fpsent *e, int weap) - { - if(!e) return true; - if(targetable(d, e)) - { - vec ep = getaimpos(d, e); - float dist = ep.squaredist(d->headpos()); - if(weaprange(d, weap, dist)) return true; - } - return false; - } - - bool request(fpsent *d, aistate &b) - { - fpsent *e = getclient(d->ai->enemy); - if(!d->hasammo(d->gunselect) || !hasrange(d, e, d->gunselect) || (d->gunselect != d->ai->weappref && (!isgoodammo(d->gunselect) || d->hasammo(d->ai->weappref)))) - { - static const int gunprefs[] = { GUN_CG, GUN_RL, GUN_SG, GUN_RIFLE, GUN_GL, GUN_PISTOL, GUN_FIST }; - int gun = -1; - if(d->hasammo(d->ai->weappref) && hasrange(d, e, d->ai->weappref)) gun = d->ai->weappref; - else - { - loopi(sizeof(gunprefs)/sizeof(gunprefs[0])) if(d->hasammo(gunprefs[i]) && hasrange(d, e, gunprefs[i])) - { - gun = gunprefs[i]; - break; - } - } - if(gun >= 0 && gun != d->gunselect) gunselect(gun, d); - } - return process(d, b) >= 2; - } + fpsent *e = getclient(d->ai->enemy); + bool enemyok = e && targetable(d, e); + if(!enemyok || d->skill >= 50) + { + fpsent *f = (fpsent *)intersectclosest(dp, d->ai->target, d); + if(f) + { + if(targetable(d, f)) + { + if(!enemyok) violence(d, b, f, d->gunselect == GUN_FIST ? 1 : 0); + enemyok = true; + e = f; + } + else enemyok = false; + } + else if(!enemyok && target(d, b, d->gunselect == GUN_FIST ? 1 : 0, false, SIGHTMIN)) + enemyok = (e = getclient(d->ai->enemy)) != NULL; + } + if(enemyok) + { + vec ep = getaimpos(d, e); + float yaw, pitch; + getyawpitch(dp, ep, yaw, pitch); + fixrange(yaw, pitch); + bool insight = cansee(d, dp, ep), hasseen = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->skill*10)+3000, + quick = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->gunselect == GUN_CG ? 300 : skmod)+30; + if(insight) d->ai->enemyseen = lastmillis; + if(idle || insight || hasseen || quick) + { + float sskew = insight || d->skill > 100 ? 1.5f : (hasseen ? 1.f : 0.5f); + if(insight && lockon(d, e, 16)) + { + d->ai->targyaw = yaw; + d->ai->targpitch = pitch; + if(!idle) frame *= 2; + d->ai->becareful = false; + } + scaleyawpitch(d->yaw, d->pitch, yaw, pitch, frame, sskew); + if(insight || quick) + { + if(canshoot(d, e) && hastarget(d, b, e, yaw, pitch, dp.squaredist(ep))) + { + d->attacking = true; + d->ai->lastaction = lastmillis; + result = 3; + } + else result = 2; + } + else result = 1; + } + else + { + if(!d->ai->enemyseen || lastmillis-d->ai->enemyseen > (d->skill*50)+3000) + { + d->ai->enemy = -1; + d->ai->enemyseen = d->ai->enemymillis = 0; + } + enemyok = false; + result = 0; + } + } + else + { + if(!enemyok) + { + d->ai->enemy = -1; + d->ai->enemyseen = d->ai->enemymillis = 0; + } + enemyok = false; + result = 0; + } + + fixrange(d->ai->targyaw, d->ai->targpitch); + if(!result) scaleyawpitch(d->yaw, d->pitch, d->ai->targyaw, d->ai->targpitch, frame*0.25f, 1.f); + + if(d->ai->becareful && d->physstate == PHYS_FALL) + { + float offyaw, offpitch; + vectoyawpitch(d->vel, offyaw, offpitch); + offyaw -= d->yaw; offpitch -= d->pitch; + if(fabs(offyaw)+fabs(offpitch) >= 135) d->ai->becareful = false; + else if(d->ai->becareful) d->ai->dontmove = true; + } + else d->ai->becareful = false; + + if(d->ai->dontmove) d->move = d->strafe = 0; + else + { // our guys move one way.. but turn another?! :) + const struct aimdir { int move, strafe, offset; } aimdirs[8] = + { + { 1, 0, 0 }, + { 1, -1, 45 }, + { 0, -1, 90 }, + { -1, -1, 135 }, + { -1, 0, 180 }, + { -1, 1, 225 }, + { 0, 1, 270 }, + { 1, 1, 315 } + }; + float yaw = d->ai->targyaw-d->yaw; + while(yaw < 0.0f) yaw += 360.0f; + while(yaw >= 360.0f) yaw -= 360.0f; + int r = clamp(((int)floor((yaw+22.5f)/45.0f))&7, 0, 7); + const aimdir &ad = aimdirs[r]; + d->move = ad.move; + d->strafe = ad.strafe; + } + findorientation(dp, d->yaw, d->pitch, d->ai->target); + return result; + } + + bool hasrange(fpsent *d, fpsent *e, int weap) + { + if(!e) return true; + if(targetable(d, e)) + { + vec ep = getaimpos(d, e); + float dist = ep.squaredist(d->headpos()); + if(weaprange(d, weap, dist)) return true; + } + return false; + } + + bool request(fpsent *d, aistate &b) + { + fpsent *e = getclient(d->ai->enemy); + if(!d->hasammo(d->gunselect) || !hasrange(d, e, d->gunselect) || (d->gunselect != d->ai->weappref && (!isgoodammo(d->gunselect) || d->hasammo(d->ai->weappref)))) + { + static const int gunprefs[] = { GUN_CG, GUN_RL, GUN_SG, GUN_RIFLE, GUN_GL, GUN_PISTOL, GUN_FIST }; + int gun = -1; + if(d->hasammo(d->ai->weappref) && hasrange(d, e, d->ai->weappref)) gun = d->ai->weappref; + else + { + loopi(sizeof(gunprefs)/sizeof(gunprefs[0])) if(d->hasammo(gunprefs[i]) && hasrange(d, e, gunprefs[i])) + { + gun = gunprefs[i]; + break; + } + } + if(gun >= 0 && gun != d->gunselect) gunselect(gun, d); + } + return process(d, b) >= 2; + } void timeouts(fpsent *d, aistate &b) { - if(d->blocked) - { - d->ai->blocktime += lastmillis-d->ai->lastrun; - if(d->ai->blocktime > (d->ai->blockseq+1)*1000) - { - d->ai->blockseq++; - switch(d->ai->blockseq) - { - case 1: case 2: case 3: - if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode); - d->ai->clear(false); - break; - case 4: d->ai->reset(true); break; - case 5: d->ai->reset(false); break; - case 6: default: suicide(d); return; break; // this is our last resort.. - } - } - } - else d->ai->blocktime = d->ai->blockseq = 0; - - if(d->ai->targnode == d->ai->targlast) - { - d->ai->targtime += lastmillis-d->ai->lastrun; - if(d->ai->targtime > (d->ai->targseq+1)*1000) - { - d->ai->targseq++; - switch(d->ai->targseq) - { - case 1: case 2: case 3: - if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode); - d->ai->clear(false); - break; - case 4: d->ai->reset(true); break; - case 5: d->ai->reset(false); break; - case 6: default: suicide(d); return; break; // this is our last resort.. - } - } - } - else - { - d->ai->targtime = d->ai->targseq = 0; - d->ai->targlast = d->ai->targnode; - } - - if(d->ai->lasthunt) - { - int millis = lastmillis-d->ai->lasthunt; - if(millis <= 1000) { d->ai->tryreset = false; d->ai->huntseq = 0; } - else if(millis > (d->ai->huntseq+1)*1000) - { - d->ai->huntseq++; - switch(d->ai->huntseq) - { - case 1: d->ai->reset(true); break; - case 2: d->ai->reset(false); break; - case 3: default: suicide(d); return; break; // this is our last resort.. - } - } - } - } - - void logic(fpsent *d, aistate &b, bool run) - { - bool allowmove = canmove(d) && b.type != AI_S_WAIT; - if(d->state != CS_ALIVE || !allowmove) d->stopmoving(); - if(d->state == CS_ALIVE) - { - if(allowmove) - { - if(!request(d, b)) target(d, b, d->gunselect == GUN_FIST ? 1 : 0, b.idle ? true : false); - shoot(d, d->ai->target); - } - if(!intermission) - { - if(d->ragdoll) cleanragdoll(d); - moveplayer(d, 10, true); - if(allowmove && !b.idle) timeouts(d, b); - if(d->quadmillis) entities::checkquad(curtime, d); - entities::checkitems(d); - } - } - else if(d->state == CS_DEAD) - { - if(d->ragdoll) moveragdoll(d); - else if(lastmillis-d->lastpain<2000) - { - d->move = d->strafe = 0; - moveplayer(d, 10, false); - } - } - d->attacking = d->jumping = false; - } + if(d->blocked) + { + d->ai->blocktime += lastmillis-d->ai->lastrun; + if(d->ai->blocktime > (d->ai->blockseq+1)*1000) + { + d->ai->blockseq++; + switch(d->ai->blockseq) + { + case 1: case 2: case 3: + if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode); + d->ai->clear(false); + break; + case 4: d->ai->reset(true); break; + case 5: d->ai->reset(false); break; + case 6: default: suicide(d); return; break; // this is our last resort.. + } + } + } + else d->ai->blocktime = d->ai->blockseq = 0; + + if(d->ai->targnode == d->ai->targlast) + { + d->ai->targtime += lastmillis-d->ai->lastrun; + if(d->ai->targtime > (d->ai->targseq+1)*1000) + { + d->ai->targseq++; + switch(d->ai->targseq) + { + case 1: case 2: case 3: + if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode); + d->ai->clear(false); + break; + case 4: d->ai->reset(true); break; + case 5: d->ai->reset(false); break; + case 6: default: suicide(d); return; break; // this is our last resort.. + } + } + } + else + { + d->ai->targtime = d->ai->targseq = 0; + d->ai->targlast = d->ai->targnode; + } + + if(d->ai->lasthunt) + { + int millis = lastmillis-d->ai->lasthunt; + if(millis <= 1000) { d->ai->tryreset = false; d->ai->huntseq = 0; } + else if(millis > (d->ai->huntseq+1)*1000) + { + d->ai->huntseq++; + switch(d->ai->huntseq) + { + case 1: d->ai->reset(true); break; + case 2: d->ai->reset(false); break; + case 3: default: suicide(d); return; break; // this is our last resort.. + } + } + } + } + + void logic(fpsent *d, aistate &b, bool run) + { + bool allowmove = canmove(d) && b.type != AI_S_WAIT; + if(d->state != CS_ALIVE || !allowmove) d->stopmoving(); + if(d->state == CS_ALIVE) + { + if(allowmove) + { + if(!request(d, b)) target(d, b, d->gunselect == GUN_FIST ? 1 : 0, b.idle ? true : false); + shoot(d, d->ai->target); + } + if(!intermission) + { + if(d->ragdoll) cleanragdoll(d); + moveplayer(d, 10, true); + if(allowmove && !b.idle) timeouts(d, b); + if(d->quadmillis) entities::checkquad(curtime, d); + entities::checkitems(d); + } + } + else if(d->state == CS_DEAD) + { + if(d->ragdoll) moveragdoll(d); + else if(lastmillis-d->lastpain<2000) + { + d->move = d->strafe = 0; + moveplayer(d, 10, false); + } + } + d->attacking = d->jumping = false; + } void avoid() - { - // guess as to the radius of ai and other critters relying on the avoid set for now - float guessradius = player1->radius; - obstacles.clear(); - loopv(players) - { - dynent *d = players[i]; - if(d->state != CS_ALIVE) continue; - obstacles.avoidnear(d, d->o.z + d->aboveeye + 1, d->feetpos(), guessradius + d->radius); - } + { + // guess as to the radius of ai and other critters relying on the avoid set for now + float guessradius = player1->radius; + obstacles.clear(); + loopv(players) + { + dynent *d = players[i]; + if(d->state != CS_ALIVE) continue; + obstacles.avoidnear(d, d->o.z + d->aboveeye + 1, d->feetpos(), guessradius + d->radius); + } extern avoidset wpavoid; obstacles.add(wpavoid); avoidweapons(obstacles, guessradius); - } - - void think(fpsent *d, bool run) - { - // the state stack works like a chain of commands, certain commands simply replace each other - // others spawn new commands to the stack the ai reads the top command from the stack and executes - // it or pops the stack and goes back along the history until it finds a suitable command to execute - bool cleannext = false; - if(d->ai->state.empty()) d->ai->addstate(AI_S_WAIT); - loopvrev(d->ai->state) - { - aistate &c = d->ai->state[i]; - if(cleannext) - { - c.millis = lastmillis; - c.override = false; - cleannext = false; - } - if(d->state == CS_DEAD && d->respawned!=d->lifesequence && lastmillis - d->lastpain >= 500) - { - addmsg(N_TRYSPAWN, "rc", d); - d->respawned = d->lifesequence; - } - else if(d->state == CS_ALIVE && run) - { - int result = 0; - c.idle = 0; - switch(c.type) - { - case AI_S_WAIT: result = dowait(d, c); break; - case AI_S_DEFEND: result = dodefend(d, c); break; - case AI_S_PURSUE: result = dopursue(d, c); break; - case AI_S_INTEREST: result = dointerest(d, c); break; - default: result = 0; break; - } - if(result <= 0) - { - if(c.type != AI_S_WAIT) - { - switch(result) - { - case 0: default: d->ai->removestate(i); cleannext = true; break; - case -1: i = d->ai->state.length()-1; break; - } - continue; // shouldn't interfere - } - } - } - logic(d, c, run); - break; - } - if(d->ai->trywipe) d->ai->wipe(); - d->ai->lastrun = lastmillis; - } - - void drawroute(fpsent *d, float amt = 1.f) - { - int last = -1; - loopvrev(d->ai->route) - { - if(d->ai->route.inrange(last)) - { - int index = d->ai->route[i], prev = d->ai->route[last]; - if(iswaypoint(index) && iswaypoint(prev)) - { - waypoint &e = waypoints[index], &f = waypoints[prev]; - vec fr = f.o, dr = e.o; - fr.z += amt; dr.z += amt; - particle_flare(fr, dr, 1, PART_STREAK, 0xFFFFFF); - } - } - last = i; - } - if(aidebug >= 5) - { - vec pos = d->feetpos(); - if(d->ai->spot != vec(0, 0, 0)) particle_flare(pos, d->ai->spot, 1, PART_LIGHTNING, 0x00FFFF); - if(iswaypoint(d->ai->targnode)) - particle_flare(pos, waypoints[d->ai->targnode].o, 1, PART_LIGHTNING, 0xFF00FF); - if(iswaypoint(d->lastnode)) - particle_flare(pos, waypoints[d->lastnode].o, 1, PART_LIGHTNING, 0xFFFF00); - loopi(NUMPREVNODES) if(iswaypoint(d->ai->prevnodes[i])) - { - particle_flare(pos, waypoints[d->ai->prevnodes[i]].o, 1, PART_LIGHTNING, 0x884400); - pos = waypoints[d->ai->prevnodes[i]].o; - } - } - } - - VAR(showwaypoints, 0, 0, 1); - VAR(showwaypointsradius, 0, 200, 10000); - - const char *stnames[AI_S_MAX] = { - "wait", "defend", "pursue", "interest" - }, *sttypes[AI_T_MAX+1] = { - "none", "node", "player", "affinity", "entity" - }; - void render() - { - if(aidebug > 1) - { - int total = 0, alive = 0; - loopv(players) if(players[i]->ai) total++; - loopv(players) if(players[i]->state == CS_ALIVE && players[i]->ai) - { - fpsent *d = players[i]; - vec pos = d->abovehead(); - pos.z += 3; - alive++; - if(aidebug >= 4) drawroute(d, 4.f*(float(alive)/float(total))); - if(aidebug >= 3) - { - defformatstring(q, "node: %d route: %d (%d)", - d->lastnode, - !d->ai->route.empty() ? d->ai->route[0] : -1, - d->ai->route.length() - ); - particle_textcopy(pos, q, PART_TEXT, 1); - pos.z += 2; - } - bool top = true; - loopvrev(d->ai->state) - { - aistate &b = d->ai->state[i]; - defformatstring(s, "%s%s (%d ms) %s:%d", - top ? "\fg" : "\fy", - stnames[b.type], - lastmillis-b.millis, - sttypes[b.targtype+1], b.target - ); - particle_textcopy(pos, s, PART_TEXT, 1); - pos.z += 2; - if(top) - { - if(aidebug >= 3) top = false; - else break; - } - } - if(aidebug >= 3) - { - if(d->ai->weappref >= 0 && d->ai->weappref < NUMGUNS) - { - particle_textcopy(pos, guns[d->ai->weappref].name, PART_TEXT, 1); - pos.z += 2; - } - fpsent *e = getclient(d->ai->enemy); - if(e) - { - particle_textcopy(pos, colorname(e), PART_TEXT, 1); - pos.z += 2; - } - } - } - if(aidebug >= 4) - { - int cur = 0; - loopv(obstacles.obstacles) - { - const avoidset::obstacle &ob = obstacles.obstacles[i]; - int next = cur + ob.numwaypoints; - for(; cur < next; cur++) - { - int ent = obstacles.waypoints[cur]; - if(iswaypoint(ent)) - regular_particle_splash(PART_EDIT, 2, 40, waypoints[ent].o, 0xFF6600, 1.5f); - } - cur = next; - } - } - } - if(showwaypoints || aidebug >= 6) - { - vector close; - int len = waypoints.length(); - if(showwaypointsradius) - { - findwaypointswithin(camera1->o, 0, showwaypointsradius, close); - len = close.length(); - } - loopi(len) - { - waypoint &w = waypoints[showwaypointsradius ? close[i] : i]; - loopj(MAXWAYPOINTLINKS) - { - int link = w.links[j]; - if(!link) break; - particle_flare(w.o, waypoints[link].o, 1, PART_STREAK, 0x0000FF); - } - } - - } - } + } + + void think(fpsent *d, bool run) + { + // the state stack works like a chain of commands, certain commands simply replace each other + // others spawn new commands to the stack the ai reads the top command from the stack and executes + // it or pops the stack and goes back along the history until it finds a suitable command to execute + bool cleannext = false; + if(d->ai->state.empty()) d->ai->addstate(AI_S_WAIT); + loopvrev(d->ai->state) + { + aistate &c = d->ai->state[i]; + if(cleannext) + { + c.millis = lastmillis; + c.override = false; + cleannext = false; + } + if(d->state == CS_DEAD && d->respawned!=d->lifesequence && lastmillis - d->lastpain >= 500) + { + addmsg(N_TRYSPAWN, "rc", d); + d->respawned = d->lifesequence; + } + else if(d->state == CS_ALIVE && run) + { + int result = 0; + c.idle = 0; + switch(c.type) + { + case AI_S_WAIT: result = dowait(d, c); break; + case AI_S_DEFEND: result = dodefend(d, c); break; + case AI_S_PURSUE: result = dopursue(d, c); break; + case AI_S_INTEREST: result = dointerest(d, c); break; + default: result = 0; break; + } + if(result <= 0) + { + if(c.type != AI_S_WAIT) + { + switch(result) + { + case 0: default: d->ai->removestate(i); cleannext = true; break; + case -1: i = d->ai->state.length()-1; break; + } + continue; // shouldn't interfere + } + } + } + logic(d, c, run); + break; + } + if(d->ai->trywipe) d->ai->wipe(); + d->ai->lastrun = lastmillis; + } + + void drawroute(fpsent *d, float amt = 1.f) + { + int last = -1; + loopvrev(d->ai->route) + { + if(d->ai->route.inrange(last)) + { + int index = d->ai->route[i], prev = d->ai->route[last]; + if(iswaypoint(index) && iswaypoint(prev)) + { + waypoint &e = waypoints[index], &f = waypoints[prev]; + vec fr = f.o, dr = e.o; + fr.z += amt; dr.z += amt; + particle_flare(fr, dr, 1, PART_STREAK, 0xFFFFFF); + } + } + last = i; + } + if(aidebug >= 5) + { + vec pos = d->feetpos(); + if(d->ai->spot != vec(0, 0, 0)) particle_flare(pos, d->ai->spot, 1, PART_LIGHTNING, 0x00FFFF); + if(iswaypoint(d->ai->targnode)) + particle_flare(pos, waypoints[d->ai->targnode].o, 1, PART_LIGHTNING, 0xFF00FF); + if(iswaypoint(d->lastnode)) + particle_flare(pos, waypoints[d->lastnode].o, 1, PART_LIGHTNING, 0xFFFF00); + loopi(NUMPREVNODES) if(iswaypoint(d->ai->prevnodes[i])) + { + particle_flare(pos, waypoints[d->ai->prevnodes[i]].o, 1, PART_LIGHTNING, 0x884400); + pos = waypoints[d->ai->prevnodes[i]].o; + } + } + } + + VAR(showwaypoints, 0, 0, 1); + VAR(showwaypointsradius, 0, 200, 10000); + + const char *stnames[AI_S_MAX] = { + "wait", "defend", "pursue", "interest" + }, *sttypes[AI_T_MAX+1] = { + "none", "node", "player", "affinity", "entity" + }; + void render() + { + if(aidebug > 1) + { + int total = 0, alive = 0; + loopv(players) if(players[i]->ai) total++; + loopv(players) if(players[i]->state == CS_ALIVE && players[i]->ai) + { + fpsent *d = players[i]; + vec pos = d->abovehead(); + pos.z += 3; + alive++; + if(aidebug >= 4) drawroute(d, 4.f*(float(alive)/float(total))); + if(aidebug >= 3) + { + defformatstring(q, "node: %d route: %d (%d)", + d->lastnode, + !d->ai->route.empty() ? d->ai->route[0] : -1, + d->ai->route.length() + ); + particle_textcopy(pos, q, PART_TEXT, 1); + pos.z += 2; + } + bool top = true; + loopvrev(d->ai->state) + { + aistate &b = d->ai->state[i]; + defformatstring(s, "%s%s (%d ms) %s:%d", + top ? "\fg" : "\fy", + stnames[b.type], + lastmillis-b.millis, + sttypes[b.targtype+1], b.target + ); + particle_textcopy(pos, s, PART_TEXT, 1); + pos.z += 2; + if(top) + { + if(aidebug >= 3) top = false; + else break; + } + } + if(aidebug >= 3) + { + if(d->ai->weappref >= 0 && d->ai->weappref < NUMGUNS) + { + particle_textcopy(pos, guns[d->ai->weappref].name, PART_TEXT, 1); + pos.z += 2; + } + fpsent *e = getclient(d->ai->enemy); + if(e) + { + particle_textcopy(pos, colorname(e), PART_TEXT, 1); + pos.z += 2; + } + } + } + if(aidebug >= 4) + { + int cur = 0; + loopv(obstacles.obstacles) + { + const avoidset::obstacle &ob = obstacles.obstacles[i]; + int next = cur + ob.numwaypoints; + for(; cur < next; cur++) + { + int ent = obstacles.waypoints[cur]; + if(iswaypoint(ent)) + regular_particle_splash(PART_EDIT, 2, 40, waypoints[ent].o, 0xFF6600, 1.5f); + } + cur = next; + } + } + } + if(showwaypoints || aidebug >= 6) + { + vector close; + int len = waypoints.length(); + if(showwaypointsradius) + { + findwaypointswithin(camera1->o, 0, showwaypointsradius, close); + len = close.length(); + } + loopi(len) + { + waypoint &w = waypoints[showwaypointsradius ? close[i] : i]; + loopj(MAXWAYPOINTLINKS) + { + int link = w.links[j]; + if(!link) break; + particle_flare(w.o, waypoints[link].o, 1, PART_STREAK, 0x0000FF); + } + } + + } + } } diff --git a/src/fpsgame/ai.h b/src/fpsgame/ai.h index 43efde9..9ea7e41 100644 --- a/src/fpsgame/ai.h +++ b/src/fpsgame/ai.h @@ -7,84 +7,84 @@ enum { AI_NONE = 0, AI_BOT, AI_MAX }; namespace ai { - const int MAXWAYPOINTS = USHRT_MAX - 2; - const int MAXWAYPOINTLINKS = 6; - const int WAYPOINTRADIUS = 16; - - const float MINWPDIST = 4.f; // is on top of - const float CLOSEDIST = 32.f; // is close - const float FARDIST = 128.f; // too far to remap close - const float JUMPMIN = 4.f; // decides to jump - const float JUMPMAX = 32.f; // max jump - const float SIGHTMIN = 64.f; // minimum line of sight - const float SIGHTMAX = 1024.f; // maximum line of sight - const float VIEWMIN = 90.f; // minimum field of view - const float VIEWMAX = 180.f; // maximum field of view - - struct waypoint - { - vec o; - float curscore, estscore; + const int MAXWAYPOINTS = USHRT_MAX - 2; + const int MAXWAYPOINTLINKS = 6; + const int WAYPOINTRADIUS = 16; + + const float MINWPDIST = 4.f; // is on top of + const float CLOSEDIST = 32.f; // is close + const float FARDIST = 128.f; // too far to remap close + const float JUMPMIN = 4.f; // decides to jump + const float JUMPMAX = 32.f; // max jump + const float SIGHTMIN = 64.f; // minimum line of sight + const float SIGHTMAX = 1024.f; // maximum line of sight + const float VIEWMIN = 90.f; // minimum field of view + const float VIEWMAX = 180.f; // maximum field of view + + struct waypoint + { + vec o; + float curscore, estscore; int weight; - ushort route, prev; - ushort links[MAXWAYPOINTLINKS]; + ushort route, prev; + ushort links[MAXWAYPOINTLINKS]; - waypoint() {} - waypoint(const vec &o, int weight = 0) : o(o), weight(weight), route(0) { memset(links, 0, sizeof(links)); } + waypoint() {} + waypoint(const vec &o, int weight = 0) : o(o), weight(weight), route(0) { memset(links, 0, sizeof(links)); } - int score() const { return int(curscore) + int(estscore); } + int score() const { return int(curscore) + int(estscore); } - int find(int wp) + int find(int wp) { loopi(MAXWAYPOINTLINKS) if(links[i] == wp) return i; return -1; } bool haslinks() { return links[0]!=0; } - }; - extern vector waypoints; + }; + extern vector waypoints; - static inline bool iswaypoint(int n) - { - return n > 0 && n < waypoints.length(); - } + static inline bool iswaypoint(int n) + { + return n > 0 && n < waypoints.length(); + } - extern int showwaypoints, dropwaypoints; - extern int closestwaypoint(const vec &pos, float mindist, bool links, fpsent *d = NULL); - extern void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector &results); + extern int showwaypoints, dropwaypoints; + extern int closestwaypoint(const vec &pos, float mindist, bool links, fpsent *d = NULL); + extern void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector &results); extern void inferwaypoints(fpsent *d, const vec &o, const vec &v, float mindist = ai::CLOSEDIST); - struct avoidset - { - struct obstacle - { - void *owner; - int numwaypoints; - float above; - - obstacle(void *owner, float above = -1) : owner(owner), numwaypoints(0), above(above) {} - }; - - vector obstacles; - vector waypoints; - - void clear() - { - obstacles.setsize(0); - waypoints.setsize(0); - } - - void add(void *owner, float above) - { - obstacles.add(obstacle(owner, above)); - } - - void add(void *owner, float above, int wp) - { - if(obstacles.empty() || owner != obstacles.last().owner) add(owner, above); - obstacles.last().numwaypoints++; - waypoints.add(wp); - } + struct avoidset + { + struct obstacle + { + void *owner; + int numwaypoints; + float above; + + obstacle(void *owner, float above = -1) : owner(owner), numwaypoints(0), above(above) {} + }; + + vector obstacles; + vector waypoints; + + void clear() + { + obstacles.setsize(0); + waypoints.setsize(0); + } + + void add(void *owner, float above) + { + obstacles.add(obstacle(owner, above)); + } + + void add(void *owner, float above, int wp) + { + if(obstacles.empty() || owner != obstacles.last().owner) add(owner, above); + obstacles.last().numwaypoints++; + waypoints.add(wp); + } void add(avoidset &avoid) { @@ -97,175 +97,175 @@ namespace ai } } - void avoidnear(void *owner, float above, const vec &pos, float limit); - - #define loopavoid(v, d, body) \ - if(!(v).obstacles.empty()) \ - { \ - int cur = 0; \ - loopv((v).obstacles) \ - { \ - const ai::avoidset::obstacle &ob = (v).obstacles[i]; \ - int next = cur + ob.numwaypoints; \ - if(ob.owner != d) \ - { \ - for(; cur < next; cur++) \ - { \ - int wp = (v).waypoints[cur]; \ - body; \ - } \ - } \ - cur = next; \ - } \ - } - - bool find(int n, fpsent *d) const - { - loopavoid(*this, d, { if(wp == n) return true; }); - return false; - } - - int remap(fpsent *d, int n, vec &pos, bool retry = false); - }; - - extern bool route(fpsent *d, int node, int goal, vector &route, const avoidset &obstacles, int retries = 0); - extern void navigate(); - extern void clearwaypoints(bool full = false); - extern void seedwaypoints(); - extern void loadwaypoints(bool force = false, const char *mname = NULL); - extern void savewaypoints(bool force = false, const char *mname = NULL); - - // ai state information for the owner client - enum - { - AI_S_WAIT = 0, // waiting for next command - AI_S_DEFEND, // defend goal target - AI_S_PURSUE, // pursue goal target - AI_S_INTEREST, // interest in goal entity - AI_S_MAX - }; - - enum - { - AI_T_NODE, - AI_T_PLAYER, - AI_T_AFFINITY, - AI_T_ENTITY, - AI_T_MAX - }; - - struct interest - { - int state, node, target, targtype; - float score; - interest() : state(-1), node(-1), target(-1), targtype(-1), score(0.f) {} - ~interest() {} - }; - - struct aistate - { - int type, millis, targtype, target, idle; - bool override; - - aistate(int m, int t, int r = -1, int v = -1) : type(t), millis(m), targtype(r), target(v) - { - reset(); - } - ~aistate() {} - - void reset() - { - idle = 0; - override = false; - } - }; - - const int NUMPREVNODES = 6; - - struct aiinfo - { - vector state; - vector route; - vec target, spot; - int enemy, enemyseen, enemymillis, weappref, prevnodes[NUMPREVNODES], targnode, targlast, targtime, targseq, - lastrun, lasthunt, lastaction, lastcheck, jumpseed, jumprand, blocktime, huntseq, blockseq, lastaimrnd; - float targyaw, targpitch, views[3], aimrnd[3]; - bool dontmove, becareful, tryreset, trywipe; - - aiinfo() - { - clearsetup(); - reset(); - loopk(3) views[k] = 0.f; - } - ~aiinfo() {} + void avoidnear(void *owner, float above, const vec &pos, float limit); + + #define loopavoid(v, d, body) \ + if(!(v).obstacles.empty()) \ + { \ + int cur = 0; \ + loopv((v).obstacles) \ + { \ + const ai::avoidset::obstacle &ob = (v).obstacles[i]; \ + int next = cur + ob.numwaypoints; \ + if(ob.owner != d) \ + { \ + for(; cur < next; cur++) \ + { \ + int wp = (v).waypoints[cur]; \ + body; \ + } \ + } \ + cur = next; \ + } \ + } + + bool find(int n, fpsent *d) const + { + loopavoid(*this, d, { if(wp == n) return true; }); + return false; + } + + int remap(fpsent *d, int n, vec &pos, bool retry = false); + }; + + extern bool route(fpsent *d, int node, int goal, vector &route, const avoidset &obstacles, int retries = 0); + extern void navigate(); + extern void clearwaypoints(bool full = false); + extern void seedwaypoints(); + extern void loadwaypoints(bool force = false, const char *mname = NULL); + extern void savewaypoints(bool force = false, const char *mname = NULL); + + // ai state information for the owner client + enum + { + AI_S_WAIT = 0, // waiting for next command + AI_S_DEFEND, // defend goal target + AI_S_PURSUE, // pursue goal target + AI_S_INTEREST, // interest in goal entity + AI_S_MAX + }; + + enum + { + AI_T_NODE, + AI_T_PLAYER, + AI_T_AFFINITY, + AI_T_ENTITY, + AI_T_MAX + }; + + struct interest + { + int state, node, target, targtype; + float score; + interest() : state(-1), node(-1), target(-1), targtype(-1), score(0.f) {} + ~interest() {} + }; + + struct aistate + { + int type, millis, targtype, target, idle; + bool override; + + aistate(int m, int t, int r = -1, int v = -1) : type(t), millis(m), targtype(r), target(v) + { + reset(); + } + ~aistate() {} + + void reset() + { + idle = 0; + override = false; + } + }; + + const int NUMPREVNODES = 6; + + struct aiinfo + { + vector state; + vector route; + vec target, spot; + int enemy, enemyseen, enemymillis, weappref, prevnodes[NUMPREVNODES], targnode, targlast, targtime, targseq, + lastrun, lasthunt, lastaction, lastcheck, jumpseed, jumprand, blocktime, huntseq, blockseq, lastaimrnd; + float targyaw, targpitch, views[3], aimrnd[3]; + bool dontmove, becareful, tryreset, trywipe; + + aiinfo() + { + clearsetup(); + reset(); + loopk(3) views[k] = 0.f; + } + ~aiinfo() {} void clearsetup() { - weappref = GUN_PISTOL; - spot = target = vec(0, 0, 0); - lastaction = lasthunt = lastcheck = enemyseen = enemymillis = blocktime = huntseq = blockseq = targtime = targseq = lastaimrnd = 0; - lastrun = jumpseed = lastmillis; - jumprand = lastmillis+5000; - targnode = targlast = enemy = -1; + weappref = GUN_PISTOL; + spot = target = vec(0, 0, 0); + lastaction = lasthunt = lastcheck = enemyseen = enemymillis = blocktime = huntseq = blockseq = targtime = targseq = lastaimrnd = 0; + lastrun = jumpseed = lastmillis; + jumprand = lastmillis+5000; + targnode = targlast = enemy = -1; } void clear(bool prev = false) { - if(prev) memset(prevnodes, -1, sizeof(prevnodes)); - route.setsize(0); + if(prev) memset(prevnodes, -1, sizeof(prevnodes)); + route.setsize(0); } - void wipe(bool prev = false) - { - clear(prev); - state.setsize(0); - addstate(AI_S_WAIT); - trywipe = false; - } - - void clean(bool tryit = false) - { - if(!tryit) becareful = dontmove = false; - targyaw = rnd(360); - targpitch = 0.f; - tryreset = tryit; - } - - void reset(bool tryit = false) { wipe(); clean(tryit); } - - bool hasprevnode(int n) const - { - loopi(NUMPREVNODES) if(prevnodes[i] == n) return true; - return false; - } - - void addprevnode(int n) - { - if(prevnodes[0] != n) - { - memmove(&prevnodes[1], prevnodes, sizeof(prevnodes) - sizeof(prevnodes[0])); - prevnodes[0] = n; - } - } - - aistate &addstate(int t, int r = -1, int v = -1) - { - return state.add(aistate(lastmillis, t, r, v)); - } - - void removestate(int index = -1) - { - if(index < 0) state.pop(); - else if(state.inrange(index)) state.remove(index); - if(!state.length()) addstate(AI_S_WAIT); - } - - aistate &getstate(int idx = -1) - { - if(state.inrange(idx)) return state[idx]; - return state.last(); - } + void wipe(bool prev = false) + { + clear(prev); + state.setsize(0); + addstate(AI_S_WAIT); + trywipe = false; + } + + void clean(bool tryit = false) + { + if(!tryit) becareful = dontmove = false; + targyaw = rnd(360); + targpitch = 0.f; + tryreset = tryit; + } + + void reset(bool tryit = false) { wipe(); clean(tryit); } + + bool hasprevnode(int n) const + { + loopi(NUMPREVNODES) if(prevnodes[i] == n) return true; + return false; + } + + void addprevnode(int n) + { + if(prevnodes[0] != n) + { + memmove(&prevnodes[1], prevnodes, sizeof(prevnodes) - sizeof(prevnodes[0])); + prevnodes[0] = n; + } + } + + aistate &addstate(int t, int r = -1, int v = -1) + { + return state.add(aistate(lastmillis, t, r, v)); + } + + void removestate(int index = -1) + { + if(index < 0) state.pop(); + else if(state.inrange(index)) state.remove(index); + if(!state.length()) addstate(AI_S_WAIT); + } + + aistate &getstate(int idx = -1) + { + if(state.inrange(idx)) return state[idx]; + return state.last(); + } aistate &switchstate(aistate &b, int t, int r = -1, int v = -1) { @@ -278,40 +278,40 @@ namespace ai } return addstate(t, r, v); } - }; + }; extern avoidset obstacles; - extern vec aitarget; - - extern float viewdist(int x = 101); - extern float viewfieldx(int x = 101); - extern float viewfieldy(int x = 101); - extern bool targetable(fpsent *d, fpsent *e); - extern bool cansee(fpsent *d, vec &x, vec &y, vec &targ = aitarget); - - extern void init(fpsent *d, int at, int on, int sk, int bn, int pm, const char *name, const char *team); - extern void update(); - extern void avoid(); - extern void think(fpsent *d, bool run); - - extern bool badhealth(fpsent *d); - extern bool checkothers(vector &targets, fpsent *d = NULL, int state = -1, int targtype = -1, int target = -1, bool teams = false, int *members = NULL); - extern bool makeroute(fpsent *d, aistate &b, int node, bool changed = true, int retries = 0); - extern bool makeroute(fpsent *d, aistate &b, const vec &pos, bool changed = true, int retries = 0); - extern bool randomnode(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX); - extern bool randomnode(fpsent *d, aistate &b, float guard = SIGHTMIN, float wander = SIGHTMAX); - extern bool violence(fpsent *d, aistate &b, fpsent *e, int pursue = 0); - extern bool patrol(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX, int walk = 1, bool retry = false); - extern bool defend(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX, int walk = 1); - extern void assist(fpsent *d, aistate &b, vector &interests, bool all = false, bool force = false); - extern bool parseinterests(fpsent *d, aistate &b, vector &interests, bool override = false, bool ignore = false); + extern vec aitarget; + + extern float viewdist(int x = 101); + extern float viewfieldx(int x = 101); + extern float viewfieldy(int x = 101); + extern bool targetable(fpsent *d, fpsent *e); + extern bool cansee(fpsent *d, vec &x, vec &y, vec &targ = aitarget); + + extern void init(fpsent *d, int at, int on, int sk, int bn, int pm, const char *name, const char *team); + extern void update(); + extern void avoid(); + extern void think(fpsent *d, bool run); + + extern bool badhealth(fpsent *d); + extern bool checkothers(vector &targets, fpsent *d = NULL, int state = -1, int targtype = -1, int target = -1, bool teams = false, int *members = NULL); + extern bool makeroute(fpsent *d, aistate &b, int node, bool changed = true, int retries = 0); + extern bool makeroute(fpsent *d, aistate &b, const vec &pos, bool changed = true, int retries = 0); + extern bool randomnode(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX); + extern bool randomnode(fpsent *d, aistate &b, float guard = SIGHTMIN, float wander = SIGHTMAX); + extern bool violence(fpsent *d, aistate &b, fpsent *e, int pursue = 0); + extern bool patrol(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX, int walk = 1, bool retry = false); + extern bool defend(fpsent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX, int walk = 1); + extern void assist(fpsent *d, aistate &b, vector &interests, bool all = false, bool force = false); + extern bool parseinterests(fpsent *d, aistate &b, vector &interests, bool override = false, bool ignore = false); extern void spawned(fpsent *d); extern void damaged(fpsent *d, fpsent *e); extern void killed(fpsent *d, fpsent *e); extern void itemspawned(int ent); - extern void render(); + extern void render(); } diff --git a/src/fpsgame/aiman.h b/src/fpsgame/aiman.h index 9c1598c..940f13e 100644 --- a/src/fpsgame/aiman.h +++ b/src/fpsgame/aiman.h @@ -1,146 +1,146 @@ // server-side ai manager namespace aiman { - bool dorefresh = false, botbalance = true; - VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS); - VAR(serverbotbalance, 0, 1, 1); + bool dorefresh = false, botbalance = true; + VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS); + VAR(serverbotbalance, 0, 1, 1); - void calcteams(vector &teams) - { - static const char * const defaults[2] = { "good", "evil" }; - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue; - teamscore *t = NULL; - loopvj(teams) if(!strcmp(teams[j].team, ci->team)) { t = &teams[j]; break; } - if(t) t->score++; - else teams.add(teamscore(ci->team, 1)); - } - teams.sort(teamscore::compare); - if(teams.length() < int(sizeof(defaults)/sizeof(defaults[0]))) - { - loopi(sizeof(defaults)/sizeof(defaults[0])) if(teams.htfind(defaults[i]) < 0) teams.add(teamscore(defaults[i], 0)); - } - } + void calcteams(vector &teams) + { + static const char * const defaults[2] = { "good", "evil" }; + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue; + teamscore *t = NULL; + loopvj(teams) if(!strcmp(teams[j].team, ci->team)) { t = &teams[j]; break; } + if(t) t->score++; + else teams.add(teamscore(ci->team, 1)); + } + teams.sort(teamscore::compare); + if(teams.length() < int(sizeof(defaults)/sizeof(defaults[0]))) + { + loopi(sizeof(defaults)/sizeof(defaults[0])) if(teams.htfind(defaults[i]) < 0) teams.add(teamscore(defaults[i], 0)); + } + } - void balanceteams() - { - vector teams; - calcteams(teams); - vector reassign; - loopv(bots) if(bots[i]) reassign.add(bots[i]); - while(reassign.length() && teams.length() && teams[0].score > teams.last().score + 1) - { - teamscore &t = teams.last(); - clientinfo *bot = NULL; - loopv(reassign) if(reassign[i] && !strcmp(reassign[i]->team, teams[0].team)) - { - bot = reassign.removeunordered(i); - teams[0].score--; - t.score++; - for(int j = teams.length() - 2; j >= 0; j--) - { - if(teams[j].score >= teams[j+1].score) break; - swap(teams[j], teams[j+1]); - } - break; - } - if(bot) - { - copystring(bot->team, t.team, MAXTEAMLEN+1); - sendf(-1, 1, "riisi", N_SETTEAM, bot->clientnum, bot->team, 0); - } - else teams.remove(0, 1); - } - } + void balanceteams() + { + vector teams; + calcteams(teams); + vector reassign; + loopv(bots) if(bots[i]) reassign.add(bots[i]); + while(reassign.length() && teams.length() && teams[0].score > teams.last().score + 1) + { + teamscore &t = teams.last(); + clientinfo *bot = NULL; + loopv(reassign) if(reassign[i] && !strcmp(reassign[i]->team, teams[0].team)) + { + bot = reassign.removeunordered(i); + teams[0].score--; + t.score++; + for(int j = teams.length() - 2; j >= 0; j--) + { + if(teams[j].score >= teams[j+1].score) break; + swap(teams[j], teams[j+1]); + } + break; + } + if(bot) + { + copystring(bot->team, t.team, MAXTEAMLEN+1); + sendf(-1, 1, "riisi", N_SETTEAM, bot->clientnum, bot->team, 0); + } + else teams.remove(0, 1); + } + } - const char *chooseteam() - { - vector teams; - calcteams(teams); - return teams.length() ? teams.last().team : ""; - } + const char *chooseteam() + { + vector teams; + calcteams(teams); + return teams.length() ? teams.last().team : ""; + } - static inline bool validaiclient(clientinfo *ci) - { - return ci->clientnum >= 0 && ci->state.aitype == AI_NONE && (ci->state.state!=CS_SPECTATOR || ci->local || (ci->privilege && !ci->warned)); - } + static inline bool validaiclient(clientinfo *ci) + { + return ci->clientnum >= 0 && ci->state.aitype == AI_NONE && (ci->state.state!=CS_SPECTATOR || ci->local || (ci->privilege && !ci->warned)); + } clientinfo *findaiclient(clientinfo *exclude = NULL) { - clientinfo *least = NULL; + clientinfo *least = NULL; loopv(clients) { clientinfo *ci = clients[i]; if(!validaiclient(ci) || ci==exclude) continue; - if(!least || ci->bots.length() < least->bots.length()) least = ci; + if(!least || ci->bots.length() < least->bots.length()) least = ci; } - return least; + return least; } bool addai(int skill, int limit) { int numai = 0, cn = -1, maxai = limit >= 0 ? min(limit, MAXBOTS) : MAXBOTS; loopv(bots) - { - clientinfo *ci = bots[i]; - if(!ci || ci->ownernum < 0) { if(cn < 0) cn = i; continue; } + { + clientinfo *ci = bots[i]; + if(!ci || ci->ownernum < 0) { if(cn < 0) cn = i; continue; } numai++; } if(numai >= maxai) return false; - if(bots.inrange(cn)) - { - clientinfo *ci = bots[cn]; - if(ci) - { // reuse a slot that was going to removed + if(bots.inrange(cn)) + { + clientinfo *ci = bots[cn]; + if(ci) + { // reuse a slot that was going to removed - clientinfo *owner = findaiclient(); - ci->ownernum = owner ? owner->clientnum : -1; - if(owner) owner->bots.add(ci); - ci->aireinit = 2; - dorefresh = true; - return true; - } - } - else { cn = bots.length(); bots.add(NULL); } - const char *team = m_teammode ? chooseteam() : ""; - if(!bots[cn]) bots[cn] = new clientinfo; - clientinfo *ci = bots[cn]; + clientinfo *owner = findaiclient(); + ci->ownernum = owner ? owner->clientnum : -1; + if(owner) owner->bots.add(ci); + ci->aireinit = 2; + dorefresh = true; + return true; + } + } + else { cn = bots.length(); bots.add(NULL); } + const char *team = m_teammode ? chooseteam() : ""; + if(!bots[cn]) bots[cn] = new clientinfo; + clientinfo *ci = bots[cn]; ci->clientnum = MAXCLIENTS + cn; ci->state.aitype = AI_BOT; - clientinfo *owner = findaiclient(); + clientinfo *owner = findaiclient(); ci->ownernum = owner ? owner->clientnum : -1; - if(owner) owner->bots.add(ci); - ci->state.skill = skill <= 0 ? rnd(50) + 51 : clamp(skill, 1, 101); - clients.add(ci); + if(owner) owner->bots.add(ci); + ci->state.skill = skill <= 0 ? rnd(50) + 51 : clamp(skill, 1, 101); + clients.add(ci); ci->state.lasttimeplayed = lastmillis; copystring(ci->name, "bot", MAXNAMELEN+1); ci->state.state = CS_DEAD; - copystring(ci->team, team, MAXTEAMLEN+1); - ci->playermodel = 0; + copystring(ci->team, team, MAXTEAMLEN+1); + ci->playermodel = 0; ci->aireinit = 2; ci->connected = true; - dorefresh = true; + dorefresh = true; return true; } void deleteai(clientinfo *ci) { - int cn = ci->clientnum - MAXCLIENTS; - if(!bots.inrange(cn)) return; - sendf(-1, 1, "ri2", N_CDIS, ci->clientnum); - clientinfo *owner = (clientinfo *)getclientinfo(ci->ownernum); - if(owner) owner->bots.removeobj(ci); - clients.removeobj(ci); - DELETEP(bots[cn]); + int cn = ci->clientnum - MAXCLIENTS; + if(!bots.inrange(cn)) return; + sendf(-1, 1, "ri2", N_CDIS, ci->clientnum); + clientinfo *owner = (clientinfo *)getclientinfo(ci->ownernum); + if(owner) owner->bots.removeobj(ci); + clients.removeobj(ci); + DELETEP(bots[cn]); dorefresh = true; } bool deleteai() { - loopvrev(bots) if(bots[i] && bots[i]->ownernum >= 0) - { + loopvrev(bots) if(bots[i] && bots[i]->ownernum >= 0) + { deleteai(bots[i]); return true; } @@ -154,22 +154,22 @@ namespace aiman { sendf(-1, 1, "ri6ss", N_INITAI, ci->clientnum, ci->ownernum, ci->state.aitype, ci->state.skill, ci->playermodel, ci->name, ci->team); if(ci->aireinit == 2) - { - ci->reassign(); - if(ci->state.state==CS_ALIVE) sendspawn(ci); - else sendresume(ci); - } + { + ci->reassign(); + if(ci->state.state==CS_ALIVE) sendspawn(ci); + else sendresume(ci); + } ci->aireinit = 0; } } void shiftai(clientinfo *ci, clientinfo *owner = NULL) { - clientinfo *prevowner = (clientinfo *)getclientinfo(ci->ownernum); - if(prevowner) prevowner->bots.removeobj(ci); + clientinfo *prevowner = (clientinfo *)getclientinfo(ci->ownernum); + if(prevowner) prevowner->bots.removeobj(ci); if(!owner) { ci->aireinit = 0; ci->ownernum = -1; } else if(ci->ownernum != owner->clientnum) { ci->aireinit = 2; ci->ownernum = owner->clientnum; owner->bots.add(ci); } - dorefresh = true; + dorefresh = true; } void removeai(clientinfo *ci) @@ -180,13 +180,13 @@ namespace aiman bool reassignai() { - clientinfo *hi = NULL, *lo = NULL; + clientinfo *hi = NULL, *lo = NULL; loopv(clients) { clientinfo *ci = clients[i]; if(!validaiclient(ci)) continue; - if(!lo || ci->bots.length() < lo->bots.length()) lo = ci; - if(!hi || ci->bots.length() > hi->bots.length()) hi = ci; + if(!lo || ci->bots.length() < lo->bots.length()) lo = ci; + if(!hi || ci->bots.length() > hi->bots.length()) hi = ci; } if(hi && lo && hi->bots.length() - lo->bots.length() > 1) { @@ -202,20 +202,20 @@ namespace aiman void checksetup() { - if(m_teammode && botbalance) balanceteams(); + if(m_teammode && botbalance) balanceteams(); loopvrev(bots) if(bots[i]) reinitai(bots[i]); } void clearai() { // clear and remove all ai immediately - loopvrev(bots) if(bots[i]) deleteai(bots[i]); + loopvrev(bots) if(bots[i]) deleteai(bots[i]); } void checkai() { - if(!dorefresh) return; - dorefresh = false; - if(m_botmode && numclients(-1, false, true)) + if(!dorefresh) return; + dorefresh = false; + if(m_botmode && numclients(-1, false, true)) { checksetup(); while(reassignai()); @@ -225,49 +225,49 @@ namespace aiman void reqadd(clientinfo *ci, int skill) { - if(!ci->local && !ci->privilege) return; - if(!addai(skill, !ci->local && ci->privilege < PRIV_ADMIN ? botlimit : -1)) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to create or assign bot"); + if(!ci->local && !ci->privilege) return; + if(!addai(skill, !ci->local && ci->privilege < PRIV_ADMIN ? botlimit : -1)) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to create or assign bot"); } void reqdel(clientinfo *ci) { - if(!ci->local && !ci->privilege) return; - if(!deleteai()) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to remove any bots"); + if(!ci->local && !ci->privilege) return; + if(!deleteai()) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to remove any bots"); } - void setbotlimit(clientinfo *ci, int limit) - { - if(ci && !ci->local && ci->privilege < PRIV_ADMIN) return; - botlimit = clamp(limit, 0, MAXBOTS); - dorefresh = true; - defformatstring(msg, "bot limit is now %d", botlimit); - sendservmsg(msg); - } + void setbotlimit(clientinfo *ci, int limit) + { + if(ci && !ci->local && ci->privilege < PRIV_ADMIN) return; + botlimit = clamp(limit, 0, MAXBOTS); + dorefresh = true; + defformatstring(msg, "bot limit is now %d", botlimit); + sendservmsg(msg); + } - void setbotbalance(clientinfo *ci, bool balance) - { - if(ci && !ci->local && !ci->privilege) return; - botbalance = balance ? 1 : 0; - dorefresh = true; - defformatstring(msg, "bot team balancing is now %s", botbalance ? "enabled" : "disabled"); - sendservmsg(msg); - } + void setbotbalance(clientinfo *ci, bool balance) + { + if(ci && !ci->local && !ci->privilege) return; + botbalance = balance ? 1 : 0; + dorefresh = true; + defformatstring(msg, "bot team balancing is now %s", botbalance ? "enabled" : "disabled"); + sendservmsg(msg); + } - void changemap() - { - dorefresh = true; - loopv(clients) if(clients[i]->local || clients[i]->privilege) return; - if(botbalance != (serverbotbalance != 0)) setbotbalance(NULL, serverbotbalance != 0); - } + void changemap() + { + dorefresh = true; + loopv(clients) if(clients[i]->local || clients[i]->privilege) return; + if(botbalance != (serverbotbalance != 0)) setbotbalance(NULL, serverbotbalance != 0); + } - void addclient(clientinfo *ci) - { - if(ci->state.aitype == AI_NONE) dorefresh = true; - } + void addclient(clientinfo *ci) + { + if(ci->state.aitype == AI_NONE) dorefresh = true; + } - void changeteam(clientinfo *ci) - { - if(ci->state.aitype == AI_NONE) dorefresh = true; - } + void changeteam(clientinfo *ci) + { + if(ci->state.aitype == AI_NONE) dorefresh = true; + } } diff --git a/src/fpsgame/client.cpp b/src/fpsgame/client.cpp index 00a0c7e..7155c00 100644 --- a/src/fpsgame/client.cpp +++ b/src/fpsgame/client.cpp @@ -2,2010 +2,2010 @@ namespace game { - bool senditemstoserver = false, sendcrc = false; // after a map change, since server doesn't have map data - int lastping = 0; - - bool connected = false, remote = false, demoplayback = false, gamepaused = false; - int sessionid = 0, mastermode = MM_OPEN, gamespeed = 100; - string servinfo = "", servauth = "", connectpass = ""; - - VARP(deadpush, 1, 2, 20); - - void switchname(const char *name) - { - filtertext(player1->name, name, false, false, MAXNAMELEN); - if(!player1->name[0]) copystring(player1->name, "Anonymous"); - addmsg(N_SWITCHNAME, "rs", player1->name); - } - void printname() - { - conoutf("your name is: %s", colorname(player1)); - } - ICOMMAND(name, "sN", (char *s, int *numargs), - { - if(*numargs > 0) switchname(s); - else if(!*numargs) printname(); - else result(colorname(player1)); - }); - ICOMMAND(getname, "", (), result(player1->name)); - - void switchteam(const char *team) - { - if(player1->clientnum < 0) filtertext(player1->team, team, false, false, MAXTEAMLEN); - else addmsg(N_SWITCHTEAM, "rs", team); - } - void printteam() - { - conoutf("your team is: %s", player1->team); - } - ICOMMAND(team, "sN", (char *s, int *numargs), - { - if(*numargs > 0) switchteam(s); - else if(!*numargs) printteam(); - else result(player1->team); - }); - ICOMMAND(getteam, "", (), result(player1->team)); - - struct authkey - { - char *name, *key, *desc; - int lastauth; - - authkey(const char *name, const char *key, const char *desc) - : name(newstring(name)), key(newstring(key)), desc(newstring(desc)), - lastauth(0) - { - } - - ~authkey() - { - DELETEA(name); - DELETEA(key); - DELETEA(desc); - } - }; - vector authkeys; - - authkey *findauthkey(const char *desc = "") - { - loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcasecmp(authkeys[i]->name, player1->name)) return authkeys[i]; - loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc)) return authkeys[i]; - return NULL; - } - - VARP(autoauth, 0, 1, 1); - - void addauthkey(const char *name, const char *key, const char *desc) - { - loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) delete authkeys.remove(i); - if(name[0] && key[0]) authkeys.add(new authkey(name, key, desc)); - } - ICOMMAND(authkey, "sss", (char *name, char *key, char *desc), addauthkey(name, key, desc)); - - bool hasauthkey(const char *name, const char *desc) - { - if(!name[0] && !desc[0]) return authkeys.length() > 0; - loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) return true; - return false; - } - - ICOMMAND(hasauthkey, "ss", (char *name, char *desc), intret(hasauthkey(name, desc) ? 1 : 0)); - - void genauthkey(const char *secret) - { - if(!secret[0]) { conoutf(CON_ERROR, "you must specify a secret password"); return; } - vector privkey, pubkey; - genprivkey(secret, privkey, pubkey); - conoutf("private key: %s", privkey.getbuf()); - conoutf("public key: %s", pubkey.getbuf()); - result(privkey.getbuf()); - } - COMMAND(genauthkey, "s"); - - void getpubkey(const char *desc) - { - authkey *k = findauthkey(desc); - if(!k) { if(desc[0]) conoutf(CON_ERROR, "no authkey found: %s", desc); else conoutf(CON_ERROR, "no global authkey found"); return; } - vector pubkey; - if(!calcpubkey(k->key, pubkey)) { conoutf(CON_ERROR, "failed calculating pubkey"); return; } - result(pubkey.getbuf()); - } - COMMAND(getpubkey, "s"); - - void saveauthkeys() - { - stream *f = openfile("auth.cfg", "w"); - if(!f) { conoutf(CON_ERROR, "failed to open auth.cfg for writing"); return; } - loopv(authkeys) - { - authkey *a = authkeys[i]; - f->printf("authkey %s %s %s\n", escapestring(a->name), escapestring(a->key), escapestring(a->desc)); - } - conoutf("saved authkeys to auth.cfg"); - delete f; - } - COMMAND(saveauthkeys, ""); - - void sendmapinfo() - { - if(!connected) return; - sendcrc = true; - if(player1->state!=CS_SPECTATOR || player1->privilege || !remote) senditemstoserver = true; - } - - void writeclientinfo(stream *f) - { - f->printf("name %s\n", escapestring(player1->name)); - } - - bool allowedittoggle() - { - if(editmode) return true; - if(isconnected() && multiplayer(false) && !m_edit) - { - conoutf(CON_ERROR, "editing in multiplayer requires coop edit mode (1)"); - return false; - } - return execidentbool("allowedittoggle", true); - } - - void edittoggled(bool on) - { - addmsg(N_EDITMODE, "ri", on ? 1 : 0); - if(player1->state==CS_DEAD) deathstate(player1, true); - else if(player1->state==CS_EDITING && player1->editstate==CS_DEAD) showscores(false); - disablezoom(); - player1->suicided = player1->respawned = -2; - } - - const char *getclientname(int cn) - { - fpsent *d = getclient(cn); - return d ? d->name : ""; - } - ICOMMAND(getclientname, "i", (int *cn), result(getclientname(*cn))); - - const char *getclientteam(int cn) - { - fpsent *d = getclient(cn); - return d ? d->team : ""; - } - ICOMMAND(getclientteam, "i", (int *cn), result(getclientteam(*cn))); - - const char *getclienticon(int cn) - { - fpsent *d = getclient(cn); - if(!d || d->state==CS_SPECTATOR) return "spectator"; - const playermodelinfo &mdl = getplayermodelinfo(d); - return m_teammode ? (isteam(player1->team, d->team) ? mdl.blueicon : mdl.redicon) : mdl.ffaicon; - } - ICOMMAND(getclienticon, "i", (int *cn), result(getclienticon(*cn))); - - bool ismaster(int cn) - { - fpsent *d = getclient(cn); - return d && d->privilege >= PRIV_MASTER; - } - ICOMMAND(ismaster, "i", (int *cn), intret(ismaster(*cn) ? 1 : 0)); - - bool isauth(int cn) - { - fpsent *d = getclient(cn); - return d && d->privilege >= PRIV_AUTH; - } - ICOMMAND(isauth, "i", (int *cn), intret(isauth(*cn) ? 1 : 0)); - - bool isadmin(int cn) - { - fpsent *d = getclient(cn); - return d && d->privilege >= PRIV_ADMIN; - } - ICOMMAND(isadmin, "i", (int *cn), intret(isadmin(*cn) ? 1 : 0)); - - ICOMMAND(getmastermode, "", (), intret(mastermode)); - ICOMMAND(mastermodename, "i", (int *mm), result(server::mastermodename(*mm, ""))); - - bool isspectator(int cn) - { - fpsent *d = getclient(cn); - return d && d->state==CS_SPECTATOR; - } - ICOMMAND(isspectator, "i", (int *cn), intret(isspectator(*cn) ? 1 : 0)); - - bool isai(int cn, int type) - { - fpsent *d = getclient(cn); - int aitype = type > 0 && type < AI_MAX ? type : AI_BOT; - return d && d->aitype==aitype; - } - ICOMMAND(isai, "ii", (int *cn, int *type), intret(isai(*cn, *type) ? 1 : 0)); - - int parseplayer(const char *arg) - { - char *end; - int n = strtol(arg, &end, 10); - if(*arg && !*end) - { - if(n!=player1->clientnum && !clients.inrange(n)) return -1; - return n; - } - // try case sensitive first - loopv(players) - { - fpsent *o = players[i]; - if(!strcmp(arg, o->name)) return o->clientnum; - } - // nothing found, try case insensitive - loopv(players) - { - fpsent *o = players[i]; - if(!strcasecmp(arg, o->name)) return o->clientnum; - } - return -1; - } - ICOMMAND(getclientnum, "s", (char *name), intret(name[0] ? parseplayer(name) : player1->clientnum)); - - void listclients(bool local, bool bots) - { - vector buf; - string cn; - int numclients = 0; - if(local && connected) - { - formatstring(cn, "%d", player1->clientnum); - buf.put(cn, strlen(cn)); - numclients++; - } - loopv(clients) if(clients[i] && (bots || clients[i]->aitype == AI_NONE)) - { - formatstring(cn, "%d", clients[i]->clientnum); - if(numclients++) buf.add(' '); - buf.put(cn, strlen(cn)); - } - buf.add('\0'); - result(buf.getbuf()); - } - ICOMMAND(listclients, "bb", (int *local, int *bots), listclients(*local>0, *bots!=0)); - - void clearbans() - { - addmsg(N_CLEARBANS, "r"); - } - COMMAND(clearbans, ""); - - void kick(const char *victim, const char *reason) - { - int vn = parseplayer(victim); - if(vn>=0 && vn!=player1->clientnum) addmsg(N_KICK, "ris", vn, reason); - } - COMMAND(kick, "ss"); - - void authkick(const char *desc, const char *victim, const char *reason) - { - authkey *a = findauthkey(desc); - int vn = parseplayer(victim); - if(a && vn>=0 && vn!=player1->clientnum) - { - a->lastauth = lastmillis; - addmsg(N_AUTHKICK, "rssis", a->desc, a->name, vn, reason); - } - } - ICOMMAND(authkick, "ss", (const char *victim, const char *reason), authkick("", victim, reason)); - ICOMMAND(sauthkick, "ss", (const char *victim, const char *reason), if(servauth[0]) authkick(servauth, victim, reason)); - ICOMMAND(dauthkick, "sss", (const char *desc, const char *victim, const char *reason), if(desc[0]) authkick(desc, victim, reason)); - - vector ignores; - - void ignore(int cn) - { - fpsent *d = getclient(cn); - if(!d || d == player1) return; - conoutf("ignoring %s", d->name); - if(ignores.find(cn) < 0) ignores.add(cn); - } - - void unignore(int cn) - { - if(ignores.find(cn) < 0) return; - fpsent *d = getclient(cn); - if(d) conoutf("stopped ignoring %s", d->name); - ignores.removeobj(cn); - } - - bool isignored(int cn) { return ignores.find(cn) >= 0; } - - ICOMMAND(ignore, "s", (char *arg), ignore(parseplayer(arg))); - ICOMMAND(unignore, "s", (char *arg), unignore(parseplayer(arg))); - ICOMMAND(isignored, "s", (char *arg), intret(isignored(parseplayer(arg)) ? 1 : 0)); - - void setteam(const char *arg1, const char *arg2) - { - int i = parseplayer(arg1); - if(i>=0) addmsg(N_SETTEAM, "ris", i, arg2); - } - COMMAND(setteam, "ss"); - - void hashpwd(const char *pwd) - { - if(player1->clientnum<0) return; - string hash; - server::hashpassword(player1->clientnum, sessionid, pwd, hash); - result(hash); - } - COMMAND(hashpwd, "s"); - - void setmaster(const char *arg, const char *who) - { - if(!arg[0]) return; - int val = 1, cn = player1->clientnum; - if(who[0]) - { - cn = parseplayer(who); - if(cn < 0) return; - } - string hash = ""; - if(!arg[1] && isdigit(arg[0])) val = parseint(arg); - else - { - if(cn != player1->clientnum) return; - server::hashpassword(player1->clientnum, sessionid, arg, hash); - } - addmsg(N_SETMASTER, "riis", cn, val, hash); - } - COMMAND(setmaster, "ss"); - ICOMMAND(mastermode, "i", (int *val), addmsg(N_MASTERMODE, "ri", *val)); - - bool tryauth(const char *desc) - { - authkey *a = findauthkey(desc); - if(!a) return false; - a->lastauth = lastmillis; - addmsg(N_AUTHTRY, "rss", a->desc, a->name); - return true; - } - ICOMMAND(auth, "s", (char *desc), tryauth(desc)); - ICOMMAND(sauth, "", (), if(servauth[0]) tryauth(servauth)); - ICOMMAND(dauth, "s", (char *desc), if(desc[0]) tryauth(desc)); - - ICOMMAND(getservauth, "", (), result(servauth)); - - void togglespectator(int val, const char *who) - { - int i = who[0] ? parseplayer(who) : player1->clientnum; - if(i>=0) addmsg(N_SPECTATOR, "rii", i, val); - } - ICOMMAND(spectator, "is", (int *val, char *who), togglespectator(*val, who)); - - ICOMMAND(checkmaps, "", (), addmsg(N_CHECKMAPS, "r")); - - int gamemode = INT_MAX, nextmode = INT_MAX; - string clientmap = ""; - - void changemapserv(const char *name, int mode) // forced map change from the server - { - if(multiplayer(false) && !m_mp(mode)) - { - conoutf(CON_ERROR, "mode %s (%d) not supported in multiplayer", server::modename(gamemode), gamemode); - loopi(NUMGAMEMODES) if(m_mp(STARTGAMEMODE + i)) { mode = STARTGAMEMODE + i; break; } - } - - gamemode = mode; - nextmode = mode; - if(editmode) toggleedit(); - if(m_demo) { entities::resetspawns(); return; } - if((m_edit && !name[0]) || !load_world(name)) - { - emptymap(0, true, name); - senditemstoserver = false; - } - startgame(); - } - - void setmode(int mode) - { - if(multiplayer(false) && !m_mp(mode)) - { - conoutf(CON_ERROR, "mode %s (%d) not supported in multiplayer", server::modename(mode), mode); - intret(0); - return; - } - nextmode = mode; - intret(1); - } - ICOMMAND(mode, "i", (int *val), setmode(*val)); - ICOMMAND(getmode, "", (), intret(gamemode)); - ICOMMAND(timeremaining, "i", (int *formatted), - { - int val = max(maplimit - lastmillis + 999, 0)/1000; - if(*formatted) - { - defformatstring(str, "%d:%02d", val/60, val%60); - result(str); - } - else intret(val); - }); - ICOMMANDS("m_noitems", "i", (int *mode), { int gamemode = *mode; intret(m_noitems); }); - ICOMMANDS("m_noammo", "i", (int *mode), { int gamemode = *mode; intret(m_noammo); }); - ICOMMANDS("m_insta", "i", (int *mode), { int gamemode = *mode; intret(m_insta); }); - ICOMMANDS("m_efficiency", "i", (int *mode), { int gamemode = *mode; intret(m_efficiency); }); - ICOMMANDS("m_teammode", "i", (int *mode), { int gamemode = *mode; intret(m_teammode); }); - ICOMMANDS("m_demo", "i", (int *mode), { int gamemode = *mode; intret(m_demo); }); - ICOMMANDS("m_edit", "i", (int *mode), { int gamemode = *mode; intret(m_edit); }); - ICOMMANDS("m_lobby", "i", (int *mode), { int gamemode = *mode; intret(m_lobby); }); - - void changemap(const char *name, int mode) // request map change, server may ignore - { - if(!remote) - { - server::forcemap(name, mode); - if(!isconnected()) localconnect(); - } - else if(player1->state!=CS_SPECTATOR || player1->privilege) addmsg(N_MAPVOTE, "rsi", name, mode); - } - void changemap(const char *name) - { - changemap(name, m_valid(nextmode) ? nextmode : (remote ? 0 : 1)); - } - ICOMMAND(map, "s", (char *name), changemap(name)); - - void forceintermission() - { - if(!remote && !hasnonlocalclients()) server::startintermission(); - else addmsg(N_FORCEINTERMISSION, "r"); - } - - void forceedit(const char *name) - { - changemap(name, 1); - } - - void newmap(int size) - { - addmsg(N_NEWMAP, "ri", size); - } - - int needclipboard = -1; - - void sendclipboard() - { - uchar *outbuf = NULL; - int inlen = 0, outlen = 0; - if(!packeditinfo(localedit, inlen, outbuf, outlen)) - { - outbuf = NULL; - inlen = outlen = 0; - } - packetbuf p(16 + outlen, ENET_PACKET_FLAG_RELIABLE); - putint(p, N_CLIPBOARD); - putint(p, inlen); - putint(p, outlen); - if(outlen > 0) p.put(outbuf, outlen); - sendclientpacket(p.finalize(), 1); - needclipboard = -1; - } - - void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3, const VSlot *vs) - { - if(m_edit) switch(op) - { - case EDIT_FLIP: - case EDIT_COPY: - case EDIT_PASTE: - case EDIT_DELCUBE: - { - switch(op) - { - case EDIT_COPY: needclipboard = 0; break; - case EDIT_PASTE: - if(needclipboard > 0) - { - c2sinfo(true); - sendclipboard(); - } - break; - } - addmsg(N_EDITF + op, "ri9i4", - sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, - sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner); - break; - } - case EDIT_ROTATE: - { - addmsg(N_EDITF + op, "ri9i5", - sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, - sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, - arg1); - break; - } - case EDIT_MAT: - case EDIT_FACE: - { - addmsg(N_EDITF + op, "ri9i6", - sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, - sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, - arg1, arg2); - break; - } - case EDIT_TEX: - { - int tex1 = shouldpacktex(arg1); - if(addmsg(N_EDITF + op, "ri9i6", - sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, - sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, - tex1 ? tex1 : arg1, arg2)) - { - messages.pad(2); - int offset = messages.length(); - if(tex1) packvslot(messages, arg1); - *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); - } - break; - } - case EDIT_REPLACE: - { - int tex1 = shouldpacktex(arg1), tex2 = shouldpacktex(arg2); - if(addmsg(N_EDITF + op, "ri9i7", - sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, - sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, - tex1 ? tex1 : arg1, tex2 ? tex2 : arg2, arg3)) - { - messages.pad(2); - int offset = messages.length(); - if(tex1) packvslot(messages, arg1); - if(tex2) packvslot(messages, arg2); - *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); - } - break; - } - case EDIT_REMIP: - { - addmsg(N_EDITF + op, "r"); - break; - } - case EDIT_VSLOT: - { - if(addmsg(N_EDITF + op, "ri9i6", - sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, - sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, - arg1, arg2)) - { - messages.pad(2); - int offset = messages.length(); - packvslot(messages, vs); - *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); - } - break; - } - case EDIT_UNDO: - case EDIT_REDO: - { - uchar *outbuf = NULL; - int inlen = 0, outlen = 0; - if(packundo(op, inlen, outbuf, outlen)) - { - if(addmsg(N_EDITF + op, "ri2", inlen, outlen)) messages.put(outbuf, outlen); - delete[] outbuf; - } - break; - } - } - } - - void printvar(fpsent *d, ident *id) - { - if(id) switch(id->type) - { - case ID_VAR: - { - int val = *id->storage.i; - string str; - if(val < 0) - formatstring(str, "%d", val); - else if(id->flags&IDF_HEX && id->maxval==0xFFFFFF) - formatstring(str, "0x%.6X (%d, %d, %d)", val, (val>>16)&0xFF, (val>>8)&0xFF, val&0xFF); - else - formatstring(str, id->flags&IDF_HEX ? "0x%X" : "%d", val); - conoutf(CON_INFO, id->index, "%s set map var \"%s\" to %s", colorname(d), id->name, str); - break; - } - case ID_FVAR: - conoutf(CON_INFO, id->index, "%s set map var \"%s\" to %s", colorname(d), id->name, floatstr(*id->storage.f)); - break; - case ID_SVAR: - conoutf(CON_INFO, id->index, "%s set map var \"%s\" to \"%s\"", colorname(d), id->name, *id->storage.s); - break; - } - } - - void vartrigger(ident *id) - { - if(!m_edit) return; - switch(id->type) - { - case ID_VAR: - addmsg(N_EDITVAR, "risi", ID_VAR, id->name, *id->storage.i); - break; - - case ID_FVAR: - addmsg(N_EDITVAR, "risf", ID_FVAR, id->name, *id->storage.f); - break; - - case ID_SVAR: - addmsg(N_EDITVAR, "riss", ID_SVAR, id->name, *id->storage.s); - break; - default: return; - } - printvar(player1, id); - } - - void pausegame(bool val) - { - if(!connected) return; - if(!remote) server::forcepaused(val); - else addmsg(N_PAUSEGAME, "ri", val ? 1 : 0); - } - ICOMMAND(pausegame, "i", (int *val), pausegame(*val > 0)); - ICOMMAND(paused, "iN$", (int *val, int *numargs, ident *id), - { - if(*numargs > 0) pausegame(clampvar(id, *val, 0, 1) > 0); - else if(*numargs < 0) intret(gamepaused ? 1 : 0); - else printvar(id, gamepaused ? 1 : 0); - }); - - bool ispaused() { return gamepaused; } - - bool allowmouselook() { return !gamepaused || !remote || m_edit; } - - void changegamespeed(int val) - { - if(!connected) return; - if(!remote) server::forcegamespeed(val); - else addmsg(N_GAMESPEED, "ri", val); - } - ICOMMAND(gamespeed, "iN$", (int *val, int *numargs, ident *id), - { - if(*numargs > 0) changegamespeed(clampvar(id, *val, 10, 1000)); - else if(*numargs < 0) intret(gamespeed); - else printvar(id, gamespeed); - }); - - int scaletime(int t) { return t*gamespeed; } - - // collect c2s messages conveniently - vector messages; - int messagecn = -1, messagereliable = false; - - bool addmsg(int type, const char *fmt, ...) - { - if(!connected) return false; - static uchar buf[MAXTRANS]; - ucharbuf p(buf, sizeof(buf)); - putint(p, type); - int numi = 1, numf = 0, nums = 0, mcn = -1; - bool reliable = false; - if(fmt) - { - va_list args; - va_start(args, fmt); - while(*fmt) switch(*fmt++) - { - case 'r': reliable = true; break; - case 'c': - { - fpsent *d = va_arg(args, fpsent *); - mcn = !d || d == player1 ? -1 : d->clientnum; - break; - } - case 'v': - { - int n = va_arg(args, int); - int *v = va_arg(args, int *); - loopi(n) putint(p, v[i]); - numi += n; - break; - } - - case 'i': - { - int n = isdigit(*fmt) ? *fmt++-'0' : 1; - loopi(n) putint(p, va_arg(args, int)); - numi += n; - break; - } - case 'f': - { - int n = isdigit(*fmt) ? *fmt++-'0' : 1; - loopi(n) putfloat(p, (float)va_arg(args, double)); - numf += n; - break; - } - case 's': sendstring(va_arg(args, const char *), p); nums++; break; - } - va_end(args); - } - int num = nums || numf ? 0 : numi, msgsize = server::msgsizelookup(type); - if(msgsize && num!=msgsize) { fatal("inconsistent msg size for %d (%d != %d)", type, num, msgsize); } - if(reliable) messagereliable = true; - if(mcn != messagecn) - { - static uchar mbuf[16]; - ucharbuf m(mbuf, sizeof(mbuf)); - putint(m, N_FROMAI); - putint(m, mcn); - messages.put(mbuf, m.length()); - messagecn = mcn; - } - messages.put(buf, p.length()); - return true; - } - - void connectattempt(const char *name, const char *password, const ENetAddress &address) - { - copystring(connectpass, password); - } - - void connectfail() - { - memset(connectpass, 0, sizeof(connectpass)); - } - - void gameconnect(bool _remote) - { - remote = _remote; - if(editmode) toggleedit(); - } - - void gamedisconnect(bool cleanup) - { - if(remote) stopfollowing(); - ignores.setsize(0); - connected = remote = false; - player1->clientnum = -1; - sessionid = 0; - mastermode = MM_OPEN; - messages.setsize(0); - messagereliable = false; - messagecn = -1; - player1->respawn(); - player1->lifesequence = 0; - player1->state = CS_ALIVE; - player1->privilege = PRIV_NONE; - sendcrc = senditemstoserver = false; - demoplayback = false; - gamepaused = false; - gamespeed = 100; - clearclients(false); - if(cleanup) - { - nextmode = gamemode = INT_MAX; - clientmap[0] = '\0'; - } - } - - VARP(teamcolorchat, 0, 1, 1); - const char *chatcolorname(fpsent *d) { return teamcolorchat ? teamcolorname(d, NULL) : colorname(d); } - - void toserver(char *text) { conoutf(CON_CHAT, "%s:\f0 %s", chatcolorname(player1), text); addmsg(N_TEXT, "rcs", player1, text); } - COMMANDN(say, toserver, "C"); - - void sayteam(char *text) { conoutf(CON_TEAMCHAT, "\fs\f8[team]\fr %s: \f8%s", chatcolorname(player1), text); addmsg(N_SAYTEAM, "rcs", player1, text); } - COMMAND(sayteam, "C"); - - ICOMMAND(servcmd, "C", (char *cmd), addmsg(N_SERVCMD, "rs", cmd)); - - static void sendposition(fpsent *d, packetbuf &q) - { - putint(q, N_POS); - putuint(q, d->clientnum); - // 3 bits phys state, 1 bit life sequence, 2 bits move, 2 bits strafe - uchar physstate = d->physstate | ((d->lifesequence&1)<<3) | ((d->move&3)<<4) | ((d->strafe&3)<<6); - q.put(physstate); - ivec o = ivec(vec(d->o.x, d->o.y, d->o.z-d->eyeheight).mul(DMF)); - uint vel = min(int(d->vel.magnitude()*DVELF), 0xFFFF), fall = min(int(d->falling.magnitude()*DVELF), 0xFFFF); - // 3 bits position, 1 bit velocity, 3 bits falling, 1 bit material - uint flags = 0; - if(o.x < 0 || o.x > 0xFFFF) flags |= 1<<0; - if(o.y < 0 || o.y > 0xFFFF) flags |= 1<<1; - if(o.z < 0 || o.z > 0xFFFF) flags |= 1<<2; - if(vel > 0xFF) flags |= 1<<3; - if(fall > 0) - { - flags |= 1<<4; - if(fall > 0xFF) flags |= 1<<5; - if(d->falling.x || d->falling.y || d->falling.z > 0) flags |= 1<<6; - } - if((lookupmaterial(d->feetpos())&MATF_CLIP) == MAT_GAMECLIP) flags |= 1<<7; - putuint(q, flags); - loopk(3) - { - q.put(o[k]&0xFF); - q.put((o[k]>>8)&0xFF); - if(o[k] < 0 || o[k] > 0xFFFF) q.put((o[k]>>16)&0xFF); - } - uint dir = (d->yaw < 0 ? 360 + int(d->yaw)%360 : int(d->yaw)%360) + clamp(int(d->pitch+90), 0, 180)*360; - q.put(dir&0xFF); - q.put((dir>>8)&0xFF); - q.put(clamp(int(d->roll+90), 0, 180)); - q.put(vel&0xFF); - if(vel > 0xFF) q.put((vel>>8)&0xFF); - float velyaw, velpitch; - vectoyawpitch(d->vel, velyaw, velpitch); - uint veldir = (velyaw < 0 ? 360 + int(velyaw)%360 : int(velyaw)%360) + clamp(int(velpitch+90), 0, 180)*360; - q.put(veldir&0xFF); - q.put((veldir>>8)&0xFF); - if(fall > 0) - { - q.put(fall&0xFF); - if(fall > 0xFF) q.put((fall>>8)&0xFF); - if(d->falling.x || d->falling.y || d->falling.z > 0) - { - float fallyaw, fallpitch; - vectoyawpitch(d->falling, fallyaw, fallpitch); - uint falldir = (fallyaw < 0 ? 360 + int(fallyaw)%360 : int(fallyaw)%360) + clamp(int(fallpitch+90), 0, 180)*360; - q.put(falldir&0xFF); - q.put((falldir>>8)&0xFF); - } - } - } - - void sendposition(fpsent *d, bool reliable) - { - if(d->state != CS_ALIVE && d->state != CS_EDITING) return; - packetbuf q(100, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); - sendposition(d, q); - sendclientpacket(q.finalize(), 0); - } - - void sendpositions() - { - loopv(players) - { - fpsent *d = players[i]; - if((d == player1 || d->ai) && (d->state == CS_ALIVE || d->state == CS_EDITING)) - { - packetbuf q(100); - sendposition(d, q); - for(int j = i+1; j < players.length(); j++) - { - fpsent *d = players[j]; - if((d == player1 || d->ai) && (d->state == CS_ALIVE || d->state == CS_EDITING)) - sendposition(d, q); - } - sendclientpacket(q.finalize(), 0); - break; - } - } - } - - void sendmessages() - { - packetbuf p(MAXTRANS); - if(sendcrc) - { - p.reliable(); - sendcrc = false; - const char *mname = getclientmap(); - putint(p, N_MAPCRC); - sendstring(mname, p); - putint(p, mname[0] ? getmapcrc() : 0); - } - if(senditemstoserver) - { - if(!m_noitems) p.reliable(); - if(!m_noitems) entities::putitems(p); - senditemstoserver = false; - } - if(messages.length()) - { - p.put(messages.getbuf(), messages.length()); - messages.setsize(0); - if(messagereliable) p.reliable(); - messagereliable = false; - messagecn = -1; - } - if(totalmillis-lastping>250) - { - putint(p, N_PING); - putint(p, totalmillis); - lastping = totalmillis; - } - sendclientpacket(p.finalize(), 1); - } - - void c2sinfo(bool force) // send update to the server - { - static int lastupdate = -1000; - if(totalmillis - lastupdate < 33 && !force) return; // don't update faster than 30fps - lastupdate = totalmillis; - sendpositions(); - sendmessages(); - flushclient(); - } - - void sendintro() - { - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - putint(p, N_CONNECT); - sendstring(player1->name, p); - putint(p, player1->playermodel); - string hash = ""; - if(connectpass[0]) - { - server::hashpassword(player1->clientnum, sessionid, connectpass, hash); - memset(connectpass, 0, sizeof(connectpass)); - } - sendstring(hash, p); - authkey *a = servauth[0] && autoauth ? findauthkey(servauth) : NULL; - if(a) - { - a->lastauth = lastmillis; - sendstring(a->desc, p); - sendstring(a->name, p); - } - else - { - sendstring("", p); - sendstring("", p); - } - sendclientpacket(p.finalize(), 1); - } - - void updatepos(fpsent *d) - { - // update the position of other clients in the game in our world - // don't care if he's in the scenery or other players, - // just don't overlap with our client - - const float r = player1->radius+d->radius; - const float dx = player1->o.x-d->o.x; - const float dy = player1->o.y-d->o.y; - const float dz = player1->o.z-d->o.z; - const float rz = player1->aboveeye+d->eyeheight; - const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); - if(fxstate!=CS_SPECTATOR && d->state!=CS_DEAD) - { - if(fxo.y += dy<0 ? r-fy : -(r-fy); // push aside - else d->o.x += dx<0 ? r-fx : -(r-fx); - } - int lagtime = totalmillis-d->lastupdate; - if(lagtime) - { - if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6; - d->lastupdate = totalmillis; - } - } - - void parsepositions(ucharbuf &p) - { - int type; - while(p.remaining()) switch(type = getint(p)) - { - case N_DEMOPACKET: break; - case N_POS: // position of another client - { - int cn = getuint(p), physstate = p.get(), flags = getuint(p); - vec o, vel, falling; - float yaw, pitch, roll; - loopk(3) - { - int n = p.get(); n |= p.get()<<8; if(flags&(1<>3)&1; - fpsent *d = getclient(cn); - if(!d || d->lifesequence < 0 || seqcolor!=(d->lifesequence&1) || d->state==CS_DEAD) continue; - float oldyaw = d->yaw, oldpitch = d->pitch, oldroll = d->roll; - d->yaw = yaw; - d->pitch = pitch; - d->roll = roll; - d->move = (physstate>>4)&2 ? -1 : (physstate>>4)&1; - d->strafe = (physstate>>6)&2 ? -1 : (physstate>>6)&1; - vec oldpos(d->o); - d->o = o; - d->o.z += d->eyeheight; - d->vel = vel; - d->falling = falling; - d->physstate = physstate&7; - updatephysstate(d); - updatepos(d); - if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist) - { - d->newpos = d->o; - d->newyaw = d->yaw; - d->newpitch = d->pitch; - d->newroll = d->roll; - d->o = oldpos; - d->yaw = oldyaw; - d->pitch = oldpitch; - d->roll = oldroll; - (d->deltapos = oldpos).sub(d->newpos); - d->deltayaw = oldyaw - d->newyaw; - if(d->deltayaw > 180) d->deltayaw -= 360; - else if(d->deltayaw < -180) d->deltayaw += 360; - d->deltapitch = oldpitch - d->newpitch; - d->deltaroll = oldroll - d->newroll; - d->smoothmillis = lastmillis; - } - else d->smoothmillis = 0; - if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE; - break; - } - - case N_TELEPORT: - { - int cn = getint(p), tp = getint(p), td = getint(p); - fpsent *d = getclient(cn); - if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue; - entities::teleporteffects(d, tp, td, false); - break; - } - - case N_JUMPPAD: - { - int cn = getint(p), jp = getint(p); - fpsent *d = getclient(cn); - if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue; - entities::jumppadeffects(d, jp, false); - break; - } - - default: - neterr("type"); - return; - } - } - - void parsestate(fpsent *d, ucharbuf &p, bool resume = false) - { - if(!d) { static fpsent dummy; d = &dummy; } - if(resume) - { - if(d==player1) getint(p); - else d->state = getint(p); - d->frags = getint(p); - d->flags = getint(p); - d->deaths = getint(p); - if(d==player1) getint(p); - else d->quadmillis = getint(p); - } - d->lifesequence = getint(p); - d->health = getint(p); - d->maxhealth = getint(p); - d->armour = getint(p); - d->maxarmour = getint(p); - d->armourtype = getint(p); - if(resume && d==player1) - { - getint(p); - loopi(GUN_PISTOL-GUN_SG+1) getint(p); - } - else - { - int gun = getint(p); - d->gunselect = clamp(gun, int(GUN_FIST), int(GUN_PISTOL)); - loopi(GUN_PISTOL-GUN_SG+1) d->ammo[GUN_SG+i] = getint(p); - } - } - - extern int deathscore; - - void parsemessages(int cn, fpsent *d, ucharbuf &p) - { - static char text[MAXTRANS]; - int type; - bool mapchanged = false, demopacket = false; - - while(p.remaining()) switch(type = getint(p)) - { - case N_DEMOPACKET: demopacket = true; break; - - case N_SERVINFO: // welcome messsage from the server - { - int mycn = getint(p), prot = getint(p); - if(prot!=PROTOCOL_VERSION) - { - conoutf(CON_ERROR, "you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot); - disconnect(); - return; - } - sessionid = getint(p); - player1->clientnum = mycn; // we are now connected - if(getint(p) > 0) conoutf("this server is password protected"); - getstring(servinfo, p, sizeof(servinfo)); - getstring(servauth, p, sizeof(servauth)); - sendintro(); - break; - } - - case N_WELCOME: - { - connected = true; - notifywelcome(); - break; - } - - case N_PAUSEGAME: - { - bool val = getint(p) > 0; - int cn = getint(p); - fpsent *a = cn >= 0 ? getclient(cn) : NULL; - if(!demopacket) - { - gamepaused = val; - player1->attacking = false; - } - if(a) conoutf("%s %s the game", colorname(a), val ? "paused" : "resumed"); - else conoutf("game is %s", val ? "paused" : "resumed"); - break; - } - - case N_GAMESPEED: - { - int val = clamp(getint(p), 10, 1000), cn = getint(p); - fpsent *a = cn >= 0 ? getclient(cn) : NULL; - if(!demopacket) gamespeed = val; - if(a) conoutf("%s set gamespeed to %d", colorname(a), val); - else conoutf("gamespeed is %d", val); - break; - } - - case N_CLIENT: - { - int cn = getint(p), len = getuint(p); - ucharbuf q = p.subbuf(len); - parsemessages(cn, getclient(cn), q); - break; - } - - case N_SOUND: - if(!d) return; - playsound(getint(p), &d->o); - break; - - case N_TEXT: - { - if(!d) return; - getstring(text, p); - filtertext(text, text, true, true); - if(isignored(d->clientnum)) break; - if(d->state!=CS_DEAD && d->state!=CS_SPECTATOR) - particle_textcopy(d->abovehead(), text, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); - conoutf(CON_CHAT, "%s:\f0 %s", chatcolorname(d), text); - break; - } - - case N_SAYTEAM: - { - int tcn = getint(p); - fpsent *t = getclient(tcn); - getstring(text, p); - filtertext(text, text, true, true); - if(!t || isignored(t->clientnum)) break; - if(t->state!=CS_DEAD && t->state!=CS_SPECTATOR) - particle_textcopy(t->abovehead(), text, PART_TEXT, 2000, 0x6496FF, 4.0f, -8); - conoutf(CON_TEAMCHAT, "\fs\f8[team]\fr %s: \f8%s", chatcolorname(t), text); - break; - } - - case N_MAPCHANGE: - getstring(text, p); - filtertext(text, text, false); - fixmapname(text); - changemapserv(text, getint(p)); - mapchanged = true; - if(getint(p)) entities::spawnitems(); - else senditemstoserver = false; - break; - - case N_FORCEDEATH: - { - int cn = getint(p); - fpsent *d = cn==player1->clientnum ? player1 : newclient(cn); - if(!d) break; - if(d==player1) - { - if(editmode) toggleedit(); - stopfollowing(); - if(deathscore) showscores(true); - } - else d->resetinterp(); - d->state = CS_DEAD; - break; - } - - case N_ITEMLIST: - { - int n; - while((n = getint(p))>=0 && !p.overread()) - { - if(mapchanged) entities::setspawn(n, true); - getint(p); // type - } - break; - } - - case N_INITCLIENT: // another client either connected or changed name/team - { - int cn = getint(p); - fpsent *d = newclient(cn); - if(!d) - { - getstring(text, p); - getstring(text, p); - getint(p); - break; - } - getstring(text, p); - filtertext(text, text, false, false, MAXNAMELEN); - if(!text[0]) copystring(text, "Anonymous"); - if(d->name[0]) // already connected - { - if(strcmp(d->name, text) && !isignored(d->clientnum)) - conoutf("%s is now known as %s", colorname(d), colorname(d, text)); - } - else // new client - { - conoutf("\f0join:\f7 %s", colorname(d, text)); - if(needclipboard >= 0) needclipboard++; - } - copystring(d->name, text, MAXNAMELEN+1); - getstring(text, p); - filtertext(d->team, text, false, false, MAXTEAMLEN); - d->playermodel = getint(p); - d->playermodel = 0; - break; - } - - case N_SWITCHNAME: - getstring(text, p); - if(d) - { - filtertext(text, text, false, false, MAXNAMELEN); - if(!text[0]) copystring(text, "Anonymous"); - if(strcmp(text, d->name)) - { - if(!isignored(d->clientnum)) conoutf("%s is now known as %s", colorname(d), colorname(d, text)); - copystring(d->name, text, MAXNAMELEN+1); - } - } - break; - - case N_SWITCHMODEL: - break; - - case N_CDIS: - clientdisconnected(getint(p)); - break; - - case N_SPAWN: - { - if(d) - { - if(d->state==CS_DEAD && d->lastpain) saveragdoll(d); - d->respawn(); - } - parsestate(d, p); - if(!d) break; - d->state = CS_SPAWNING; - if(player1->state==CS_SPECTATOR && following==d->clientnum) - lasthit = 0; - break; - } - - case N_SPAWNSTATE: - { - int scn = getint(p); - fpsent *s = getclient(scn); - if(!s) { parsestate(NULL, p); break; } - if(s->state==CS_DEAD && s->lastpain) saveragdoll(s); - if(s==player1) - { - if(editmode) toggleedit(); - stopfollowing(); - } - s->respawn(); - parsestate(s, p); - s->state = CS_ALIVE; - pickgamespawn(s); - if(s == player1) - { - showscores(false); - lasthit = 0; - } - ai::spawned(s); - addmsg(N_SPAWN, "rcii", s, s->lifesequence, s->gunselect); - break; - } - - case N_SHOTFX: - { - int scn = getint(p), gun = getint(p), id = getint(p); - vec from, to; - loopk(3) from[k] = getint(p)/DMF; - loopk(3) to[k] = getint(p)/DMF; - fpsent *s = getclient(scn); - if(!s) break; - if(gun>GUN_FIST && gun<=GUN_PISTOL && s->ammo[gun]) s->ammo[gun]--; - s->gunselect = clamp(gun, (int)GUN_FIST, (int)GUN_PISTOL); - s->gunwait = guns[s->gunselect].attackdelay; - int prevaction = s->lastaction; - s->lastaction = lastmillis; - s->lastattackgun = s->gunselect; - shoteffects(s->gunselect, from, to, s, false, id, prevaction); - break; - } - - case N_EXPLODEFX: - { - int ecn = getint(p), gun = getint(p), id = getint(p); - fpsent *e = getclient(ecn); - if(!e) break; - explodeeffects(gun, e, false, id); - break; - } - case N_DAMAGE: - { - int tcn = getint(p), - acn = getint(p), - damage = getint(p), - armour = getint(p), - health = getint(p); - fpsent *target = getclient(tcn), - *actor = getclient(acn); - if(!target || !actor) break; - target->armour = armour; - target->health = health; - if(target->state == CS_ALIVE && actor != player1) target->lastpain = lastmillis; - damaged(damage, target, actor, false); - break; - } - - case N_HITPUSH: - { - int tcn = getint(p), gun = getint(p), damage = getint(p); - fpsent *target = getclient(tcn); - vec dir; - loopk(3) dir[k] = getint(p)/DNF; - if(target) target->hitpush(damage * (target->health<=0 ? deadpush : 1), dir, NULL, gun); - break; - } - - case N_DIED: - { - int vcn = getint(p), acn = getint(p), frags = getint(p), tfrags = getint(p); - fpsent *victim = getclient(vcn), - *actor = getclient(acn); - if(!actor) break; - actor->frags = frags; - if(m_teammode) setteaminfo(actor->team, tfrags); - extern int hidefrags; - if(actor!=player1 && (!hidefrags)) - { - defformatstring(ds, "%d", actor->frags); - particle_textcopy(actor->abovehead(), ds, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); - } - if(!victim) break; - killed(victim, actor); - break; - } - - case N_TEAMINFO: - for(;;) - { - getstring(text, p); - if(p.overread() || !text[0]) break; - int frags = getint(p); - if(p.overread()) break; - if(m_teammode) setteaminfo(text, frags); - } - break; - - case N_GUNSELECT: - { - if(!d) return; - int gun = getint(p); - d->gunselect = clamp(gun, int(GUN_FIST), int(GUN_PISTOL)); - playsound(S_WEAPLOAD, &d->o); - break; - } - - case N_TAUNT: - { - if(!d) return; - d->lasttaunt = lastmillis; - break; - } - - case N_RESUME: - { - for(;;) - { - int cn = getint(p); - if(p.overread() || cn<0) break; - fpsent *d = (cn == player1->clientnum ? player1 : newclient(cn)); - parsestate(d, p, true); - } - break; - } - - case N_ITEMSPAWN: - { - int i = getint(p); - if(!entities::ents.inrange(i)) break; - entities::setspawn(i, true); - ai::itemspawned(i); - playsound(S_ITEMSPAWN, &entities::ents[i]->o, NULL, 0, 0, 0, -1, 0, 1500); - #if 0 - const char *name = entities::itemname(i); - if(name) particle_text(entities::ents[i]->o, name, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); - #endif - int icon = entities::itemicon(i); - if(icon >= 0) particle_icon(vec(0.0f, 0.0f, 4.0f).add(entities::ents[i]->o), icon%4, icon/4, PART_HUD_ICON, 2000, 0xFFFFFF, 2.0f, -8); - break; - } - - case N_ITEMACC: // server acknowledges that I picked up this item - { - int i = getint(p), cn = getint(p); - if(cn >= 0) - { - fpsent *d = getclient(cn); - entities::pickupeffects(i, d); - } - else entities::setspawn(i, true); - break; - } - - case N_CLIPBOARD: - { - int cn = getint(p), unpacklen = getint(p), packlen = getint(p); - fpsent *d = getclient(cn); - ucharbuf q = p.subbuf(max(packlen, 0)); - if(d) unpackeditinfo(d->edit, q.buf, q.maxlen, unpacklen); - break; - } - case N_UNDO: - case N_REDO: - { - int cn = getint(p), unpacklen = getint(p), packlen = getint(p); - fpsent *d = getclient(cn); - ucharbuf q = p.subbuf(max(packlen, 0)); - if(d) unpackundo(q.buf, q.maxlen, unpacklen); - break; - } - - case N_EDITF: // coop editing messages - case N_EDITT: - case N_EDITM: - case N_FLIP: - case N_COPY: - case N_PASTE: - case N_ROTATE: - case N_REPLACE: - case N_DELCUBE: - case N_EDITVSLOT: - { - if(!d) return; - selinfo sel; - sel.o.x = getint(p); sel.o.y = getint(p); sel.o.z = getint(p); - sel.s.x = getint(p); sel.s.y = getint(p); sel.s.z = getint(p); - sel.grid = getint(p); sel.orient = getint(p); - sel.cx = getint(p); sel.cxs = getint(p); sel.cy = getint(p), sel.cys = getint(p); - sel.corner = getint(p); - switch(type) - { - case N_EDITF: { int dir = getint(p), mode = getint(p); if(sel.validate()) mpeditface(dir, mode, sel, false); break; } - case N_EDITT: - { - int tex = getint(p), - allfaces = getint(p); - if(p.remaining() < 2) return; - int extra = lilswap(*(const ushort *)p.pad(2)); - if(p.remaining() < extra) return; - ucharbuf ebuf = p.subbuf(extra); - if(sel.validate()) mpedittex(tex, allfaces, sel, ebuf); - break; - } - case N_EDITM: { int mat = getint(p), filter = getint(p); if(sel.validate()) mpeditmat(mat, filter, sel, false); break; } - case N_FLIP: if(sel.validate()) mpflip(sel, false); break; - case N_COPY: if(d && sel.validate()) mpcopy(d->edit, sel, false); break; - case N_PASTE: if(d && sel.validate()) mppaste(d->edit, sel, false); break; - case N_ROTATE: { int dir = getint(p); if(sel.validate()) mprotate(dir, sel, false); break; } - case N_REPLACE: - { - int oldtex = getint(p), - newtex = getint(p), - insel = getint(p); - if(p.remaining() < 2) return; - int extra = lilswap(*(const ushort *)p.pad(2)); - if(p.remaining() < extra) return; - ucharbuf ebuf = p.subbuf(extra); - if(sel.validate()) mpreplacetex(oldtex, newtex, insel>0, sel, ebuf); - break; - } - case N_DELCUBE: if(sel.validate()) mpdelcube(sel, false); break; - case N_EDITVSLOT: - { - int delta = getint(p), - allfaces = getint(p); - if(p.remaining() < 2) return; - int extra = lilswap(*(const ushort *)p.pad(2)); - if(p.remaining() < extra) return; - ucharbuf ebuf = p.subbuf(extra); - if(sel.validate()) mpeditvslot(delta, allfaces, sel, ebuf); - break; - } - } - break; - } - case N_REMIP: - { - if(!d) return; - conoutf("%s remipped", colorname(d)); - mpremip(false); - break; - } - case N_EDITENT: // coop edit of ent - { - if(!d) return; - int i = getint(p); - float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF; - int type = getint(p); - int attr1 = getint(p), attr2 = getint(p), attr3 = getint(p), attr4 = getint(p), attr5 = getint(p); - - mpeditent(i, vec(x, y, z), type, attr1, attr2, attr3, attr4, attr5, false); - break; - } - case N_EDITVAR: - { - if(!d) return; - int type = getint(p); - getstring(text, p); - string name; - filtertext(name, text, false); - ident *id = getident(name); - switch(type) - { - case ID_VAR: - { - int val = getint(p); - if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setvar(name, val); - break; - } - case ID_FVAR: - { - float val = getfloat(p); - if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setfvar(name, val); - break; - } - case ID_SVAR: - { - getstring(text, p); - if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setsvar(name, text); - break; - } - } - printvar(d, id); - break; - } - - case N_PONG: - addmsg(N_CLIENTPING, "i", player1->ping = (player1->ping*5+totalmillis-getint(p))/6); - break; - - case N_CLIENTPING: - if(!d) return; - d->ping = getint(p); - break; - - case N_TIMEUP: - timeupdate(getint(p)); - break; - - case N_SERVMSG: - getstring(text, p); - conoutf("%s", text); - break; - - case N_SENDDEMOLIST: - { - int demos = getint(p); - if(demos <= 0) conoutf("no demos available"); - else loopi(demos) - { - getstring(text, p); - if(p.overread()) break; - conoutf("%d. %s", i+1, text); - } - break; - } - - case N_DEMOPLAYBACK: - { - int on = getint(p); - if(on) player1->state = CS_SPECTATOR; - else clearclients(); - demoplayback = on!=0; - player1->clientnum = getint(p); - gamepaused = false; - execident(on ? "demostart" : "demoend"); - break; - } - - case N_CURRENTMASTER: - { - int mm = getint(p), mn; - loopv(players) players[i]->privilege = PRIV_NONE; - while((mn = getint(p))>=0 && !p.overread()) - { - fpsent *m = mn==player1->clientnum ? player1 : newclient(mn); - int priv = getint(p); - if(m) m->privilege = priv; - } - if(mm != mastermode) - { - mastermode = mm; - conoutf("mastermode is %s (%d)", server::mastermodename(mastermode), mastermode); - } - break; - } - - case N_MASTERMODE: - { - mastermode = getint(p); - conoutf("mastermode is %s (%d)", server::mastermodename(mastermode), mastermode); - break; - } - - case N_EDITMODE: - { - int val = getint(p); - if(!d) break; - if(val) - { - d->editstate = d->state; - d->state = CS_EDITING; - } - else - { - d->state = d->editstate; - if(d->state==CS_DEAD) deathstate(d, true); - } - break; - } - - case N_SPECTATOR: - { - int sn = getint(p), val = getint(p); - fpsent *s; - if(sn==player1->clientnum) - { - s = player1; - if(val && remote && !player1->privilege) senditemstoserver = false; - } - else s = newclient(sn); - if(!s) return; - if(val) - { - if(s==player1) - { - if(editmode) toggleedit(); - if(s->state==CS_DEAD) showscores(false); - disablezoom(); - } - s->state = CS_SPECTATOR; - } - else if(s->state==CS_SPECTATOR) - { - if(s==player1) stopfollowing(); - deathstate(s, true); - } - break; - } - - case N_SETTEAM: - { - int wn = getint(p); - getstring(text, p); - int reason = getint(p); - fpsent *w = getclient(wn); - if(!w) return; - filtertext(w->team, text, false, false, MAXTEAMLEN); - static const char * const fmt[2] = { "%s switched to team %s", "%s forced to team %s"}; - if(reason >= 0 && size_t(reason) < sizeof(fmt)/sizeof(fmt[0])) - conoutf(fmt[reason], colorname(w), w->team); - break; - } - - case N_ANNOUNCE: - { - int t = getint(p); - if (t==I_QUAD) { playsound(S_V_QUAD10, NULL, NULL, 0, 0, 0, -1, 0, 3000); conoutf(CON_GAMEINFO, "\f2quad damage will spawn in 10 seconds!"); } - else if(t==I_BOOST) { playsound(S_V_BOOST10, NULL, NULL, 0, 0, 0, -1, 0, 3000); conoutf(CON_GAMEINFO, "\f2health boost will spawn in 10 seconds!"); } - break; - } - - case N_NEWMAP: - { - int size = getint(p); - if(size>=0) emptymap(size, true, NULL); - else enlargemap(true); - if(d && d!=player1) - { - int newsize = 0; - while(1<=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", colorname(d), newsize); - } - break; - } - - case N_REQAUTH: - { - getstring(text, p); - if(autoauth && text[0] && tryauth(text)) conoutf("server requested authkey \"%s\"", text); - break; - } - - case N_AUTHCHAL: - { - getstring(text, p); - authkey *a = findauthkey(text); - uint id = (uint)getint(p); - getstring(text, p); - if(a && a->lastauth && lastmillis - a->lastauth < 60*1000) - { - vector buf; - answerchallenge(a->key, text, buf); - //conoutf(CON_DEBUG, "answering %u, challenge %s with %s", id, text, buf.getbuf()); - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - putint(p, N_AUTHANS); - sendstring(a->desc, p); - putint(p, id); - sendstring(buf.getbuf(), p); - sendclientpacket(p.finalize(), 1); - } - break; - } - - case N_INITAI: - { - int bn = getint(p), on = getint(p), at = getint(p), sk = clamp(getint(p), 1, 101), pm = getint(p); - string name, team; - getstring(text, p); - filtertext(name, text, false, false, MAXNAMELEN); - getstring(text, p); - filtertext(team, text, false, false, MAXTEAMLEN); - fpsent *b = newclient(bn); - if(!b) break; - ai::init(b, at, on, sk, bn, pm, name, team); - break; - } - - case N_SERVCMD: - getstring(text, p); - break; - - default: - neterr("type", cn < 0); - return; - } - } - - struct demoreq - { - int tag; - string name; - }; - vector demoreqs; - enum { MAXDEMOREQS = 7 }; - static int lastdemoreq = 0; - - void receivefile(packetbuf &p) - { - int type; - while(p.remaining()) switch(type = getint(p)) - { - case N_DEMOPACKET: return; - case N_SENDDEMO: - { - string fname; - fname[0] = '\0'; - int tag = getint(p); - loopv(demoreqs) if(demoreqs[i].tag == tag) - { - copystring(fname, demoreqs[i].name); - demoreqs.remove(i); - break; - } - if(!fname[0]) - { - time_t t = time(NULL); - size_t len = strftime(fname, sizeof(fname), "%Y-%m-%d_%H.%M.%S", localtime(&t)); - fname[min(len, sizeof(fname)-1)] = '\0'; - } - int len = strlen(fname); - if(len < 4 || strcasecmp(&fname[len-4], ".dmo")) concatstring(fname, ".dmo"); - stream *demo = NULL; - if(const char *buf = server::getdemofile(fname, true)) demo = openrawfile(buf, "wb"); - if(!demo) demo = openrawfile(fname, "wb"); - if(!demo) return; - conoutf("received demo \"%s\"", fname); - ucharbuf b = p.subbuf(p.remaining()); - demo->write(b.buf, b.maxlen); - delete demo; - break; - } - - case N_SENDMAP: - { - if(!m_edit) return; - string oldname; - copystring(oldname, getclientmap()); - defformatstring(mname, "getmap_%d", lastmillis); - defformatstring(fname, "packages/maps/%s.ogz", mname); - stream *map = openrawfile(path(fname), "wb"); - if(!map) return; - conoutf("received map"); - ucharbuf b = p.subbuf(p.remaining()); - map->write(b.buf, b.maxlen); - delete map; - if(load_world(mname, oldname[0] ? oldname : NULL)) - entities::spawnitems(true); - remove(findfile(fname, "rb")); - break; - } - } - } - - void parsepacketclient(int chan, packetbuf &p) // processes any updates from the server - { - if(p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED) return; - switch(chan) - { - case 0: - parsepositions(p); - break; - - case 1: - parsemessages(-1, NULL, p); - break; - - case 2: - receivefile(p); - break; - } - } - - void getmap() - { - if(!m_edit) { conoutf(CON_ERROR, "\"getmap\" only works in coop edit mode"); return; } - conoutf("getting map..."); - addmsg(N_GETMAP, "r"); - } - COMMAND(getmap, ""); - - void stopdemo() - { - if(remote) - { - if(player1->privilegeprivilegeprivilege= MAXDEMOREQS) demoreqs.remove(0); - demoreq &r = demoreqs.add(); - r.tag = lastdemoreq; - copystring(r.name, name); - } - addmsg(N_GETDEMO, "rii", i, lastdemoreq); - } - ICOMMAND(getdemo, "ss", (char *val, char *name), getdemo(val, name)); - - void listdemos() - { - conoutf("listing demos..."); - addmsg(N_LISTDEMOS, "r"); - } - COMMAND(listdemos, ""); - - void sendmap() - { - if(!m_edit || (player1->state==CS_SPECTATOR && remote && !player1->privilege)) { conoutf(CON_ERROR, "\"sendmap\" only works in coop edit mode"); return; } - conoutf("sending map..."); - defformatstring(mname, "sendmap_%d", lastmillis); - save_world(mname, true); - defformatstring(fname, "packages/maps/%s.ogz", mname); - stream *map = openrawfile(path(fname), "rb"); - if(map) - { - stream::offset len = map->size(); - if(len > 4*1024*1024) conoutf(CON_ERROR, "map is too large"); - else if(len <= 0) conoutf(CON_ERROR, "could not read map"); - else - { - sendfile(-1, 2, map); - if(needclipboard >= 0) needclipboard++; - } - delete map; - } - else conoutf(CON_ERROR, "could not read map"); - remove(findfile(fname, "rb")); - } - COMMAND(sendmap, ""); - - void gotoplayer(const char *arg) - { - if(player1->state!=CS_SPECTATOR && player1->state!=CS_EDITING) return; - int i = parseplayer(arg); - if(i>=0) - { - fpsent *d = getclient(i); - if(!d || d==player1) return; - player1->o = d->o; - vec dir; - vecfromyawpitch(player1->yaw, player1->pitch, 1, 0, dir); - player1->o.add(dir.mul(-32)); - player1->resetinterp(); - } - } - COMMANDN(goto, gotoplayer, "s"); - - void gotosel() - { - if(player1->state!=CS_EDITING) return; - player1->o = getselpos(); - vec dir; - vecfromyawpitch(player1->yaw, player1->pitch, 1, 0, dir); - player1->o.add(dir.mul(-32)); - player1->resetinterp(); - } - COMMAND(gotosel, ""); + bool senditemstoserver = false, sendcrc = false; // after a map change, since server doesn't have map data + int lastping = 0; + + bool connected = false, remote = false, demoplayback = false, gamepaused = false; + int sessionid = 0, mastermode = MM_OPEN, gamespeed = 100; + string servinfo = "", servauth = "", connectpass = ""; + + VARP(deadpush, 1, 2, 20); + + void switchname(const char *name) + { + filtertext(player1->name, name, false, false, MAXNAMELEN); + if(!player1->name[0]) copystring(player1->name, "Anonymous"); + addmsg(N_SWITCHNAME, "rs", player1->name); + } + void printname() + { + conoutf("your name is: %s", colorname(player1)); + } + ICOMMAND(name, "sN", (char *s, int *numargs), + { + if(*numargs > 0) switchname(s); + else if(!*numargs) printname(); + else result(colorname(player1)); + }); + ICOMMAND(getname, "", (), result(player1->name)); + + void switchteam(const char *team) + { + if(player1->clientnum < 0) filtertext(player1->team, team, false, false, MAXTEAMLEN); + else addmsg(N_SWITCHTEAM, "rs", team); + } + void printteam() + { + conoutf("your team is: %s", player1->team); + } + ICOMMAND(team, "sN", (char *s, int *numargs), + { + if(*numargs > 0) switchteam(s); + else if(!*numargs) printteam(); + else result(player1->team); + }); + ICOMMAND(getteam, "", (), result(player1->team)); + + struct authkey + { + char *name, *key, *desc; + int lastauth; + + authkey(const char *name, const char *key, const char *desc) + : name(newstring(name)), key(newstring(key)), desc(newstring(desc)), + lastauth(0) + { + } + + ~authkey() + { + DELETEA(name); + DELETEA(key); + DELETEA(desc); + } + }; + vector authkeys; + + authkey *findauthkey(const char *desc = "") + { + loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcasecmp(authkeys[i]->name, player1->name)) return authkeys[i]; + loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc)) return authkeys[i]; + return NULL; + } + + VARP(autoauth, 0, 1, 1); + + void addauthkey(const char *name, const char *key, const char *desc) + { + loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) delete authkeys.remove(i); + if(name[0] && key[0]) authkeys.add(new authkey(name, key, desc)); + } + ICOMMAND(authkey, "sss", (char *name, char *key, char *desc), addauthkey(name, key, desc)); + + bool hasauthkey(const char *name, const char *desc) + { + if(!name[0] && !desc[0]) return authkeys.length() > 0; + loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) return true; + return false; + } + + ICOMMAND(hasauthkey, "ss", (char *name, char *desc), intret(hasauthkey(name, desc) ? 1 : 0)); + + void genauthkey(const char *secret) + { + if(!secret[0]) { conoutf(CON_ERROR, "you must specify a secret password"); return; } + vector privkey, pubkey; + genprivkey(secret, privkey, pubkey); + conoutf("private key: %s", privkey.getbuf()); + conoutf("public key: %s", pubkey.getbuf()); + result(privkey.getbuf()); + } + COMMAND(genauthkey, "s"); + + void getpubkey(const char *desc) + { + authkey *k = findauthkey(desc); + if(!k) { if(desc[0]) conoutf(CON_ERROR, "no authkey found: %s", desc); else conoutf(CON_ERROR, "no global authkey found"); return; } + vector pubkey; + if(!calcpubkey(k->key, pubkey)) { conoutf(CON_ERROR, "failed calculating pubkey"); return; } + result(pubkey.getbuf()); + } + COMMAND(getpubkey, "s"); + + void saveauthkeys() + { + stream *f = openfile("auth.cfg", "w"); + if(!f) { conoutf(CON_ERROR, "failed to open auth.cfg for writing"); return; } + loopv(authkeys) + { + authkey *a = authkeys[i]; + f->printf("authkey %s %s %s\n", escapestring(a->name), escapestring(a->key), escapestring(a->desc)); + } + conoutf("saved authkeys to auth.cfg"); + delete f; + } + COMMAND(saveauthkeys, ""); + + void sendmapinfo() + { + if(!connected) return; + sendcrc = true; + if(player1->state!=CS_SPECTATOR || player1->privilege || !remote) senditemstoserver = true; + } + + void writeclientinfo(stream *f) + { + f->printf("name %s\n", escapestring(player1->name)); + } + + bool allowedittoggle() + { + if(editmode) return true; + if(isconnected() && multiplayer(false) && !m_edit) + { + conoutf(CON_ERROR, "editing in multiplayer requires coop edit mode (1)"); + return false; + } + return execidentbool("allowedittoggle", true); + } + + void edittoggled(bool on) + { + addmsg(N_EDITMODE, "ri", on ? 1 : 0); + if(player1->state==CS_DEAD) deathstate(player1, true); + else if(player1->state==CS_EDITING && player1->editstate==CS_DEAD) showscores(false); + disablezoom(); + player1->suicided = player1->respawned = -2; + } + + const char *getclientname(int cn) + { + fpsent *d = getclient(cn); + return d ? d->name : ""; + } + ICOMMAND(getclientname, "i", (int *cn), result(getclientname(*cn))); + + const char *getclientteam(int cn) + { + fpsent *d = getclient(cn); + return d ? d->team : ""; + } + ICOMMAND(getclientteam, "i", (int *cn), result(getclientteam(*cn))); + + const char *getclienticon(int cn) + { + fpsent *d = getclient(cn); + if(!d || d->state==CS_SPECTATOR) return "spectator"; + const playermodelinfo &mdl = getplayermodelinfo(d); + return m_teammode ? (isteam(player1->team, d->team) ? mdl.blueicon : mdl.redicon) : mdl.ffaicon; + } + ICOMMAND(getclienticon, "i", (int *cn), result(getclienticon(*cn))); + + bool ismaster(int cn) + { + fpsent *d = getclient(cn); + return d && d->privilege >= PRIV_MASTER; + } + ICOMMAND(ismaster, "i", (int *cn), intret(ismaster(*cn) ? 1 : 0)); + + bool isauth(int cn) + { + fpsent *d = getclient(cn); + return d && d->privilege >= PRIV_AUTH; + } + ICOMMAND(isauth, "i", (int *cn), intret(isauth(*cn) ? 1 : 0)); + + bool isadmin(int cn) + { + fpsent *d = getclient(cn); + return d && d->privilege >= PRIV_ADMIN; + } + ICOMMAND(isadmin, "i", (int *cn), intret(isadmin(*cn) ? 1 : 0)); + + ICOMMAND(getmastermode, "", (), intret(mastermode)); + ICOMMAND(mastermodename, "i", (int *mm), result(server::mastermodename(*mm, ""))); + + bool isspectator(int cn) + { + fpsent *d = getclient(cn); + return d && d->state==CS_SPECTATOR; + } + ICOMMAND(isspectator, "i", (int *cn), intret(isspectator(*cn) ? 1 : 0)); + + bool isai(int cn, int type) + { + fpsent *d = getclient(cn); + int aitype = type > 0 && type < AI_MAX ? type : AI_BOT; + return d && d->aitype==aitype; + } + ICOMMAND(isai, "ii", (int *cn, int *type), intret(isai(*cn, *type) ? 1 : 0)); + + int parseplayer(const char *arg) + { + char *end; + int n = strtol(arg, &end, 10); + if(*arg && !*end) + { + if(n!=player1->clientnum && !clients.inrange(n)) return -1; + return n; + } + // try case sensitive first + loopv(players) + { + fpsent *o = players[i]; + if(!strcmp(arg, o->name)) return o->clientnum; + } + // nothing found, try case insensitive + loopv(players) + { + fpsent *o = players[i]; + if(!strcasecmp(arg, o->name)) return o->clientnum; + } + return -1; + } + ICOMMAND(getclientnum, "s", (char *name), intret(name[0] ? parseplayer(name) : player1->clientnum)); + + void listclients(bool local, bool bots) + { + vector buf; + string cn; + int numclients = 0; + if(local && connected) + { + formatstring(cn, "%d", player1->clientnum); + buf.put(cn, strlen(cn)); + numclients++; + } + loopv(clients) if(clients[i] && (bots || clients[i]->aitype == AI_NONE)) + { + formatstring(cn, "%d", clients[i]->clientnum); + if(numclients++) buf.add(' '); + buf.put(cn, strlen(cn)); + } + buf.add('\0'); + result(buf.getbuf()); + } + ICOMMAND(listclients, "bb", (int *local, int *bots), listclients(*local>0, *bots!=0)); + + void clearbans() + { + addmsg(N_CLEARBANS, "r"); + } + COMMAND(clearbans, ""); + + void kick(const char *victim, const char *reason) + { + int vn = parseplayer(victim); + if(vn>=0 && vn!=player1->clientnum) addmsg(N_KICK, "ris", vn, reason); + } + COMMAND(kick, "ss"); + + void authkick(const char *desc, const char *victim, const char *reason) + { + authkey *a = findauthkey(desc); + int vn = parseplayer(victim); + if(a && vn>=0 && vn!=player1->clientnum) + { + a->lastauth = lastmillis; + addmsg(N_AUTHKICK, "rssis", a->desc, a->name, vn, reason); + } + } + ICOMMAND(authkick, "ss", (const char *victim, const char *reason), authkick("", victim, reason)); + ICOMMAND(sauthkick, "ss", (const char *victim, const char *reason), if(servauth[0]) authkick(servauth, victim, reason)); + ICOMMAND(dauthkick, "sss", (const char *desc, const char *victim, const char *reason), if(desc[0]) authkick(desc, victim, reason)); + + vector ignores; + + void ignore(int cn) + { + fpsent *d = getclient(cn); + if(!d || d == player1) return; + conoutf("ignoring %s", d->name); + if(ignores.find(cn) < 0) ignores.add(cn); + } + + void unignore(int cn) + { + if(ignores.find(cn) < 0) return; + fpsent *d = getclient(cn); + if(d) conoutf("stopped ignoring %s", d->name); + ignores.removeobj(cn); + } + + bool isignored(int cn) { return ignores.find(cn) >= 0; } + + ICOMMAND(ignore, "s", (char *arg), ignore(parseplayer(arg))); + ICOMMAND(unignore, "s", (char *arg), unignore(parseplayer(arg))); + ICOMMAND(isignored, "s", (char *arg), intret(isignored(parseplayer(arg)) ? 1 : 0)); + + void setteam(const char *arg1, const char *arg2) + { + int i = parseplayer(arg1); + if(i>=0) addmsg(N_SETTEAM, "ris", i, arg2); + } + COMMAND(setteam, "ss"); + + void hashpwd(const char *pwd) + { + if(player1->clientnum<0) return; + string hash; + server::hashpassword(player1->clientnum, sessionid, pwd, hash); + result(hash); + } + COMMAND(hashpwd, "s"); + + void setmaster(const char *arg, const char *who) + { + if(!arg[0]) return; + int val = 1, cn = player1->clientnum; + if(who[0]) + { + cn = parseplayer(who); + if(cn < 0) return; + } + string hash = ""; + if(!arg[1] && isdigit(arg[0])) val = parseint(arg); + else + { + if(cn != player1->clientnum) return; + server::hashpassword(player1->clientnum, sessionid, arg, hash); + } + addmsg(N_SETMASTER, "riis", cn, val, hash); + } + COMMAND(setmaster, "ss"); + ICOMMAND(mastermode, "i", (int *val), addmsg(N_MASTERMODE, "ri", *val)); + + bool tryauth(const char *desc) + { + authkey *a = findauthkey(desc); + if(!a) return false; + a->lastauth = lastmillis; + addmsg(N_AUTHTRY, "rss", a->desc, a->name); + return true; + } + ICOMMAND(auth, "s", (char *desc), tryauth(desc)); + ICOMMAND(sauth, "", (), if(servauth[0]) tryauth(servauth)); + ICOMMAND(dauth, "s", (char *desc), if(desc[0]) tryauth(desc)); + + ICOMMAND(getservauth, "", (), result(servauth)); + + void togglespectator(int val, const char *who) + { + int i = who[0] ? parseplayer(who) : player1->clientnum; + if(i>=0) addmsg(N_SPECTATOR, "rii", i, val); + } + ICOMMAND(spectator, "is", (int *val, char *who), togglespectator(*val, who)); + + ICOMMAND(checkmaps, "", (), addmsg(N_CHECKMAPS, "r")); + + int gamemode = INT_MAX, nextmode = INT_MAX; + string clientmap = ""; + + void changemapserv(const char *name, int mode) // forced map change from the server + { + if(multiplayer(false) && !m_mp(mode)) + { + conoutf(CON_ERROR, "mode %s (%d) not supported in multiplayer", server::modename(gamemode), gamemode); + loopi(NUMGAMEMODES) if(m_mp(STARTGAMEMODE + i)) { mode = STARTGAMEMODE + i; break; } + } + + gamemode = mode; + nextmode = mode; + if(editmode) toggleedit(); + if(m_demo) { entities::resetspawns(); return; } + if((m_edit && !name[0]) || !load_world(name)) + { + emptymap(0, true, name); + senditemstoserver = false; + } + startgame(); + } + + void setmode(int mode) + { + if(multiplayer(false) && !m_mp(mode)) + { + conoutf(CON_ERROR, "mode %s (%d) not supported in multiplayer", server::modename(mode), mode); + intret(0); + return; + } + nextmode = mode; + intret(1); + } + ICOMMAND(mode, "i", (int *val), setmode(*val)); + ICOMMAND(getmode, "", (), intret(gamemode)); + ICOMMAND(timeremaining, "i", (int *formatted), + { + int val = max(maplimit - lastmillis + 999, 0)/1000; + if(*formatted) + { + defformatstring(str, "%d:%02d", val/60, val%60); + result(str); + } + else intret(val); + }); + ICOMMANDS("m_noitems", "i", (int *mode), { int gamemode = *mode; intret(m_noitems); }); + ICOMMANDS("m_noammo", "i", (int *mode), { int gamemode = *mode; intret(m_noammo); }); + ICOMMANDS("m_insta", "i", (int *mode), { int gamemode = *mode; intret(m_insta); }); + ICOMMANDS("m_efficiency", "i", (int *mode), { int gamemode = *mode; intret(m_efficiency); }); + ICOMMANDS("m_teammode", "i", (int *mode), { int gamemode = *mode; intret(m_teammode); }); + ICOMMANDS("m_demo", "i", (int *mode), { int gamemode = *mode; intret(m_demo); }); + ICOMMANDS("m_edit", "i", (int *mode), { int gamemode = *mode; intret(m_edit); }); + ICOMMANDS("m_lobby", "i", (int *mode), { int gamemode = *mode; intret(m_lobby); }); + + void changemap(const char *name, int mode) // request map change, server may ignore + { + if(!remote) + { + server::forcemap(name, mode); + if(!isconnected()) localconnect(); + } + else if(player1->state!=CS_SPECTATOR || player1->privilege) addmsg(N_MAPVOTE, "rsi", name, mode); + } + void changemap(const char *name) + { + changemap(name, m_valid(nextmode) ? nextmode : (remote ? 0 : 1)); + } + ICOMMAND(map, "s", (char *name), changemap(name)); + + void forceintermission() + { + if(!remote && !hasnonlocalclients()) server::startintermission(); + else addmsg(N_FORCEINTERMISSION, "r"); + } + + void forceedit(const char *name) + { + changemap(name, 1); + } + + void newmap(int size) + { + addmsg(N_NEWMAP, "ri", size); + } + + int needclipboard = -1; + + void sendclipboard() + { + uchar *outbuf = NULL; + int inlen = 0, outlen = 0; + if(!packeditinfo(localedit, inlen, outbuf, outlen)) + { + outbuf = NULL; + inlen = outlen = 0; + } + packetbuf p(16 + outlen, ENET_PACKET_FLAG_RELIABLE); + putint(p, N_CLIPBOARD); + putint(p, inlen); + putint(p, outlen); + if(outlen > 0) p.put(outbuf, outlen); + sendclientpacket(p.finalize(), 1); + needclipboard = -1; + } + + void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3, const VSlot *vs) + { + if(m_edit) switch(op) + { + case EDIT_FLIP: + case EDIT_COPY: + case EDIT_PASTE: + case EDIT_DELCUBE: + { + switch(op) + { + case EDIT_COPY: needclipboard = 0; break; + case EDIT_PASTE: + if(needclipboard > 0) + { + c2sinfo(true); + sendclipboard(); + } + break; + } + addmsg(N_EDITF + op, "ri9i4", + sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, + sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner); + break; + } + case EDIT_ROTATE: + { + addmsg(N_EDITF + op, "ri9i5", + sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, + sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, + arg1); + break; + } + case EDIT_MAT: + case EDIT_FACE: + { + addmsg(N_EDITF + op, "ri9i6", + sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, + sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, + arg1, arg2); + break; + } + case EDIT_TEX: + { + int tex1 = shouldpacktex(arg1); + if(addmsg(N_EDITF + op, "ri9i6", + sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, + sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, + tex1 ? tex1 : arg1, arg2)) + { + messages.pad(2); + int offset = messages.length(); + if(tex1) packvslot(messages, arg1); + *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); + } + break; + } + case EDIT_REPLACE: + { + int tex1 = shouldpacktex(arg1), tex2 = shouldpacktex(arg2); + if(addmsg(N_EDITF + op, "ri9i7", + sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, + sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, + tex1 ? tex1 : arg1, tex2 ? tex2 : arg2, arg3)) + { + messages.pad(2); + int offset = messages.length(); + if(tex1) packvslot(messages, arg1); + if(tex2) packvslot(messages, arg2); + *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); + } + break; + } + case EDIT_REMIP: + { + addmsg(N_EDITF + op, "r"); + break; + } + case EDIT_VSLOT: + { + if(addmsg(N_EDITF + op, "ri9i6", + sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient, + sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner, + arg1, arg2)) + { + messages.pad(2); + int offset = messages.length(); + packvslot(messages, vs); + *(ushort *)&messages[offset-2] = lilswap(ushort(messages.length() - offset)); + } + break; + } + case EDIT_UNDO: + case EDIT_REDO: + { + uchar *outbuf = NULL; + int inlen = 0, outlen = 0; + if(packundo(op, inlen, outbuf, outlen)) + { + if(addmsg(N_EDITF + op, "ri2", inlen, outlen)) messages.put(outbuf, outlen); + delete[] outbuf; + } + break; + } + } + } + + void printvar(fpsent *d, ident *id) + { + if(id) switch(id->type) + { + case ID_VAR: + { + int val = *id->storage.i; + string str; + if(val < 0) + formatstring(str, "%d", val); + else if(id->flags&IDF_HEX && id->maxval==0xFFFFFF) + formatstring(str, "0x%.6X (%d, %d, %d)", val, (val>>16)&0xFF, (val>>8)&0xFF, val&0xFF); + else + formatstring(str, id->flags&IDF_HEX ? "0x%X" : "%d", val); + conoutf(CON_INFO, id->index, "%s set map var \"%s\" to %s", colorname(d), id->name, str); + break; + } + case ID_FVAR: + conoutf(CON_INFO, id->index, "%s set map var \"%s\" to %s", colorname(d), id->name, floatstr(*id->storage.f)); + break; + case ID_SVAR: + conoutf(CON_INFO, id->index, "%s set map var \"%s\" to \"%s\"", colorname(d), id->name, *id->storage.s); + break; + } + } + + void vartrigger(ident *id) + { + if(!m_edit) return; + switch(id->type) + { + case ID_VAR: + addmsg(N_EDITVAR, "risi", ID_VAR, id->name, *id->storage.i); + break; + + case ID_FVAR: + addmsg(N_EDITVAR, "risf", ID_FVAR, id->name, *id->storage.f); + break; + + case ID_SVAR: + addmsg(N_EDITVAR, "riss", ID_SVAR, id->name, *id->storage.s); + break; + default: return; + } + printvar(player1, id); + } + + void pausegame(bool val) + { + if(!connected) return; + if(!remote) server::forcepaused(val); + else addmsg(N_PAUSEGAME, "ri", val ? 1 : 0); + } + ICOMMAND(pausegame, "i", (int *val), pausegame(*val > 0)); + ICOMMAND(paused, "iN$", (int *val, int *numargs, ident *id), + { + if(*numargs > 0) pausegame(clampvar(id, *val, 0, 1) > 0); + else if(*numargs < 0) intret(gamepaused ? 1 : 0); + else printvar(id, gamepaused ? 1 : 0); + }); + + bool ispaused() { return gamepaused; } + + bool allowmouselook() { return !gamepaused || !remote || m_edit; } + + void changegamespeed(int val) + { + if(!connected) return; + if(!remote) server::forcegamespeed(val); + else addmsg(N_GAMESPEED, "ri", val); + } + ICOMMAND(gamespeed, "iN$", (int *val, int *numargs, ident *id), + { + if(*numargs > 0) changegamespeed(clampvar(id, *val, 10, 1000)); + else if(*numargs < 0) intret(gamespeed); + else printvar(id, gamespeed); + }); + + int scaletime(int t) { return t*gamespeed; } + + // collect c2s messages conveniently + vector messages; + int messagecn = -1, messagereliable = false; + + bool addmsg(int type, const char *fmt, ...) + { + if(!connected) return false; + static uchar buf[MAXTRANS]; + ucharbuf p(buf, sizeof(buf)); + putint(p, type); + int numi = 1, numf = 0, nums = 0, mcn = -1; + bool reliable = false; + if(fmt) + { + va_list args; + va_start(args, fmt); + while(*fmt) switch(*fmt++) + { + case 'r': reliable = true; break; + case 'c': + { + fpsent *d = va_arg(args, fpsent *); + mcn = !d || d == player1 ? -1 : d->clientnum; + break; + } + case 'v': + { + int n = va_arg(args, int); + int *v = va_arg(args, int *); + loopi(n) putint(p, v[i]); + numi += n; + break; + } + + case 'i': + { + int n = isdigit(*fmt) ? *fmt++-'0' : 1; + loopi(n) putint(p, va_arg(args, int)); + numi += n; + break; + } + case 'f': + { + int n = isdigit(*fmt) ? *fmt++-'0' : 1; + loopi(n) putfloat(p, (float)va_arg(args, double)); + numf += n; + break; + } + case 's': sendstring(va_arg(args, const char *), p); nums++; break; + } + va_end(args); + } + int num = nums || numf ? 0 : numi, msgsize = server::msgsizelookup(type); + if(msgsize && num!=msgsize) { fatal("inconsistent msg size for %d (%d != %d)", type, num, msgsize); } + if(reliable) messagereliable = true; + if(mcn != messagecn) + { + static uchar mbuf[16]; + ucharbuf m(mbuf, sizeof(mbuf)); + putint(m, N_FROMAI); + putint(m, mcn); + messages.put(mbuf, m.length()); + messagecn = mcn; + } + messages.put(buf, p.length()); + return true; + } + + void connectattempt(const char *name, const char *password, const ENetAddress &address) + { + copystring(connectpass, password); + } + + void connectfail() + { + memset(connectpass, 0, sizeof(connectpass)); + } + + void gameconnect(bool _remote) + { + remote = _remote; + if(editmode) toggleedit(); + } + + void gamedisconnect(bool cleanup) + { + if(remote) stopfollowing(); + ignores.setsize(0); + connected = remote = false; + player1->clientnum = -1; + sessionid = 0; + mastermode = MM_OPEN; + messages.setsize(0); + messagereliable = false; + messagecn = -1; + player1->respawn(); + player1->lifesequence = 0; + player1->state = CS_ALIVE; + player1->privilege = PRIV_NONE; + sendcrc = senditemstoserver = false; + demoplayback = false; + gamepaused = false; + gamespeed = 100; + clearclients(false); + if(cleanup) + { + nextmode = gamemode = INT_MAX; + clientmap[0] = '\0'; + } + } + + VARP(teamcolorchat, 0, 1, 1); + const char *chatcolorname(fpsent *d) { return teamcolorchat ? teamcolorname(d, NULL) : colorname(d); } + + void toserver(char *text) { conoutf(CON_CHAT, "%s:\f0 %s", chatcolorname(player1), text); addmsg(N_TEXT, "rcs", player1, text); } + COMMANDN(say, toserver, "C"); + + void sayteam(char *text) { conoutf(CON_TEAMCHAT, "\fs\f8[team]\fr %s: \f8%s", chatcolorname(player1), text); addmsg(N_SAYTEAM, "rcs", player1, text); } + COMMAND(sayteam, "C"); + + ICOMMAND(servcmd, "C", (char *cmd), addmsg(N_SERVCMD, "rs", cmd)); + + static void sendposition(fpsent *d, packetbuf &q) + { + putint(q, N_POS); + putuint(q, d->clientnum); + // 3 bits phys state, 1 bit life sequence, 2 bits move, 2 bits strafe + uchar physstate = d->physstate | ((d->lifesequence&1)<<3) | ((d->move&3)<<4) | ((d->strafe&3)<<6); + q.put(physstate); + ivec o = ivec(vec(d->o.x, d->o.y, d->o.z-d->eyeheight).mul(DMF)); + uint vel = min(int(d->vel.magnitude()*DVELF), 0xFFFF), fall = min(int(d->falling.magnitude()*DVELF), 0xFFFF); + // 3 bits position, 1 bit velocity, 3 bits falling, 1 bit material + uint flags = 0; + if(o.x < 0 || o.x > 0xFFFF) flags |= 1<<0; + if(o.y < 0 || o.y > 0xFFFF) flags |= 1<<1; + if(o.z < 0 || o.z > 0xFFFF) flags |= 1<<2; + if(vel > 0xFF) flags |= 1<<3; + if(fall > 0) + { + flags |= 1<<4; + if(fall > 0xFF) flags |= 1<<5; + if(d->falling.x || d->falling.y || d->falling.z > 0) flags |= 1<<6; + } + if((lookupmaterial(d->feetpos())&MATF_CLIP) == MAT_GAMECLIP) flags |= 1<<7; + putuint(q, flags); + loopk(3) + { + q.put(o[k]&0xFF); + q.put((o[k]>>8)&0xFF); + if(o[k] < 0 || o[k] > 0xFFFF) q.put((o[k]>>16)&0xFF); + } + uint dir = (d->yaw < 0 ? 360 + int(d->yaw)%360 : int(d->yaw)%360) + clamp(int(d->pitch+90), 0, 180)*360; + q.put(dir&0xFF); + q.put((dir>>8)&0xFF); + q.put(clamp(int(d->roll+90), 0, 180)); + q.put(vel&0xFF); + if(vel > 0xFF) q.put((vel>>8)&0xFF); + float velyaw, velpitch; + vectoyawpitch(d->vel, velyaw, velpitch); + uint veldir = (velyaw < 0 ? 360 + int(velyaw)%360 : int(velyaw)%360) + clamp(int(velpitch+90), 0, 180)*360; + q.put(veldir&0xFF); + q.put((veldir>>8)&0xFF); + if(fall > 0) + { + q.put(fall&0xFF); + if(fall > 0xFF) q.put((fall>>8)&0xFF); + if(d->falling.x || d->falling.y || d->falling.z > 0) + { + float fallyaw, fallpitch; + vectoyawpitch(d->falling, fallyaw, fallpitch); + uint falldir = (fallyaw < 0 ? 360 + int(fallyaw)%360 : int(fallyaw)%360) + clamp(int(fallpitch+90), 0, 180)*360; + q.put(falldir&0xFF); + q.put((falldir>>8)&0xFF); + } + } + } + + void sendposition(fpsent *d, bool reliable) + { + if(d->state != CS_ALIVE && d->state != CS_EDITING) return; + packetbuf q(100, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); + sendposition(d, q); + sendclientpacket(q.finalize(), 0); + } + + void sendpositions() + { + loopv(players) + { + fpsent *d = players[i]; + if((d == player1 || d->ai) && (d->state == CS_ALIVE || d->state == CS_EDITING)) + { + packetbuf q(100); + sendposition(d, q); + for(int j = i+1; j < players.length(); j++) + { + fpsent *d = players[j]; + if((d == player1 || d->ai) && (d->state == CS_ALIVE || d->state == CS_EDITING)) + sendposition(d, q); + } + sendclientpacket(q.finalize(), 0); + break; + } + } + } + + void sendmessages() + { + packetbuf p(MAXTRANS); + if(sendcrc) + { + p.reliable(); + sendcrc = false; + const char *mname = getclientmap(); + putint(p, N_MAPCRC); + sendstring(mname, p); + putint(p, mname[0] ? getmapcrc() : 0); + } + if(senditemstoserver) + { + if(!m_noitems) p.reliable(); + if(!m_noitems) entities::putitems(p); + senditemstoserver = false; + } + if(messages.length()) + { + p.put(messages.getbuf(), messages.length()); + messages.setsize(0); + if(messagereliable) p.reliable(); + messagereliable = false; + messagecn = -1; + } + if(totalmillis-lastping>250) + { + putint(p, N_PING); + putint(p, totalmillis); + lastping = totalmillis; + } + sendclientpacket(p.finalize(), 1); + } + + void c2sinfo(bool force) // send update to the server + { + static int lastupdate = -1000; + if(totalmillis - lastupdate < 33 && !force) return; // don't update faster than 30fps + lastupdate = totalmillis; + sendpositions(); + sendmessages(); + flushclient(); + } + + void sendintro() + { + packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(p, N_CONNECT); + sendstring(player1->name, p); + putint(p, player1->playermodel); + string hash = ""; + if(connectpass[0]) + { + server::hashpassword(player1->clientnum, sessionid, connectpass, hash); + memset(connectpass, 0, sizeof(connectpass)); + } + sendstring(hash, p); + authkey *a = servauth[0] && autoauth ? findauthkey(servauth) : NULL; + if(a) + { + a->lastauth = lastmillis; + sendstring(a->desc, p); + sendstring(a->name, p); + } + else + { + sendstring("", p); + sendstring("", p); + } + sendclientpacket(p.finalize(), 1); + } + + void updatepos(fpsent *d) + { + // update the position of other clients in the game in our world + // don't care if he's in the scenery or other players, + // just don't overlap with our client + + const float r = player1->radius+d->radius; + const float dx = player1->o.x-d->o.x; + const float dy = player1->o.y-d->o.y; + const float dz = player1->o.z-d->o.z; + const float rz = player1->aboveeye+d->eyeheight; + const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); + if(fxstate!=CS_SPECTATOR && d->state!=CS_DEAD) + { + if(fxo.y += dy<0 ? r-fy : -(r-fy); // push aside + else d->o.x += dx<0 ? r-fx : -(r-fx); + } + int lagtime = totalmillis-d->lastupdate; + if(lagtime) + { + if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6; + d->lastupdate = totalmillis; + } + } + + void parsepositions(ucharbuf &p) + { + int type; + while(p.remaining()) switch(type = getint(p)) + { + case N_DEMOPACKET: break; + case N_POS: // position of another client + { + int cn = getuint(p), physstate = p.get(), flags = getuint(p); + vec o, vel, falling; + float yaw, pitch, roll; + loopk(3) + { + int n = p.get(); n |= p.get()<<8; if(flags&(1<>3)&1; + fpsent *d = getclient(cn); + if(!d || d->lifesequence < 0 || seqcolor!=(d->lifesequence&1) || d->state==CS_DEAD) continue; + float oldyaw = d->yaw, oldpitch = d->pitch, oldroll = d->roll; + d->yaw = yaw; + d->pitch = pitch; + d->roll = roll; + d->move = (physstate>>4)&2 ? -1 : (physstate>>4)&1; + d->strafe = (physstate>>6)&2 ? -1 : (physstate>>6)&1; + vec oldpos(d->o); + d->o = o; + d->o.z += d->eyeheight; + d->vel = vel; + d->falling = falling; + d->physstate = physstate&7; + updatephysstate(d); + updatepos(d); + if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist) + { + d->newpos = d->o; + d->newyaw = d->yaw; + d->newpitch = d->pitch; + d->newroll = d->roll; + d->o = oldpos; + d->yaw = oldyaw; + d->pitch = oldpitch; + d->roll = oldroll; + (d->deltapos = oldpos).sub(d->newpos); + d->deltayaw = oldyaw - d->newyaw; + if(d->deltayaw > 180) d->deltayaw -= 360; + else if(d->deltayaw < -180) d->deltayaw += 360; + d->deltapitch = oldpitch - d->newpitch; + d->deltaroll = oldroll - d->newroll; + d->smoothmillis = lastmillis; + } + else d->smoothmillis = 0; + if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE; + break; + } + + case N_TELEPORT: + { + int cn = getint(p), tp = getint(p), td = getint(p); + fpsent *d = getclient(cn); + if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue; + entities::teleporteffects(d, tp, td, false); + break; + } + + case N_JUMPPAD: + { + int cn = getint(p), jp = getint(p); + fpsent *d = getclient(cn); + if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue; + entities::jumppadeffects(d, jp, false); + break; + } + + default: + neterr("type"); + return; + } + } + + void parsestate(fpsent *d, ucharbuf &p, bool resume = false) + { + if(!d) { static fpsent dummy; d = &dummy; } + if(resume) + { + if(d==player1) getint(p); + else d->state = getint(p); + d->frags = getint(p); + d->flags = getint(p); + d->deaths = getint(p); + if(d==player1) getint(p); + else d->quadmillis = getint(p); + } + d->lifesequence = getint(p); + d->health = getint(p); + d->maxhealth = getint(p); + d->armour = getint(p); + d->maxarmour = getint(p); + d->armourtype = getint(p); + if(resume && d==player1) + { + getint(p); + loopi(GUN_PISTOL-GUN_SG+1) getint(p); + } + else + { + int gun = getint(p); + d->gunselect = clamp(gun, int(GUN_FIST), int(GUN_PISTOL)); + loopi(GUN_PISTOL-GUN_SG+1) d->ammo[GUN_SG+i] = getint(p); + } + } + + extern int deathscore; + + void parsemessages(int cn, fpsent *d, ucharbuf &p) + { + static char text[MAXTRANS]; + int type; + bool mapchanged = false, demopacket = false; + + while(p.remaining()) switch(type = getint(p)) + { + case N_DEMOPACKET: demopacket = true; break; + + case N_SERVINFO: // welcome messsage from the server + { + int mycn = getint(p), prot = getint(p); + if(prot!=PROTOCOL_VERSION) + { + conoutf(CON_ERROR, "you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot); + disconnect(); + return; + } + sessionid = getint(p); + player1->clientnum = mycn; // we are now connected + if(getint(p) > 0) conoutf("this server is password protected"); + getstring(servinfo, p, sizeof(servinfo)); + getstring(servauth, p, sizeof(servauth)); + sendintro(); + break; + } + + case N_WELCOME: + { + connected = true; + notifywelcome(); + break; + } + + case N_PAUSEGAME: + { + bool val = getint(p) > 0; + int cn = getint(p); + fpsent *a = cn >= 0 ? getclient(cn) : NULL; + if(!demopacket) + { + gamepaused = val; + player1->attacking = false; + } + if(a) conoutf("%s %s the game", colorname(a), val ? "paused" : "resumed"); + else conoutf("game is %s", val ? "paused" : "resumed"); + break; + } + + case N_GAMESPEED: + { + int val = clamp(getint(p), 10, 1000), cn = getint(p); + fpsent *a = cn >= 0 ? getclient(cn) : NULL; + if(!demopacket) gamespeed = val; + if(a) conoutf("%s set gamespeed to %d", colorname(a), val); + else conoutf("gamespeed is %d", val); + break; + } + + case N_CLIENT: + { + int cn = getint(p), len = getuint(p); + ucharbuf q = p.subbuf(len); + parsemessages(cn, getclient(cn), q); + break; + } + + case N_SOUND: + if(!d) return; + playsound(getint(p), &d->o); + break; + + case N_TEXT: + { + if(!d) return; + getstring(text, p); + filtertext(text, text, true, true); + if(isignored(d->clientnum)) break; + if(d->state!=CS_DEAD && d->state!=CS_SPECTATOR) + particle_textcopy(d->abovehead(), text, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); + conoutf(CON_CHAT, "%s:\f0 %s", chatcolorname(d), text); + break; + } + + case N_SAYTEAM: + { + int tcn = getint(p); + fpsent *t = getclient(tcn); + getstring(text, p); + filtertext(text, text, true, true); + if(!t || isignored(t->clientnum)) break; + if(t->state!=CS_DEAD && t->state!=CS_SPECTATOR) + particle_textcopy(t->abovehead(), text, PART_TEXT, 2000, 0x6496FF, 4.0f, -8); + conoutf(CON_TEAMCHAT, "\fs\f8[team]\fr %s: \f8%s", chatcolorname(t), text); + break; + } + + case N_MAPCHANGE: + getstring(text, p); + filtertext(text, text, false); + fixmapname(text); + changemapserv(text, getint(p)); + mapchanged = true; + if(getint(p)) entities::spawnitems(); + else senditemstoserver = false; + break; + + case N_FORCEDEATH: + { + int cn = getint(p); + fpsent *d = cn==player1->clientnum ? player1 : newclient(cn); + if(!d) break; + if(d==player1) + { + if(editmode) toggleedit(); + stopfollowing(); + if(deathscore) showscores(true); + } + else d->resetinterp(); + d->state = CS_DEAD; + break; + } + + case N_ITEMLIST: + { + int n; + while((n = getint(p))>=0 && !p.overread()) + { + if(mapchanged) entities::setspawn(n, true); + getint(p); // type + } + break; + } + + case N_INITCLIENT: // another client either connected or changed name/team + { + int cn = getint(p); + fpsent *d = newclient(cn); + if(!d) + { + getstring(text, p); + getstring(text, p); + getint(p); + break; + } + getstring(text, p); + filtertext(text, text, false, false, MAXNAMELEN); + if(!text[0]) copystring(text, "Anonymous"); + if(d->name[0]) // already connected + { + if(strcmp(d->name, text) && !isignored(d->clientnum)) + conoutf("%s is now known as %s", colorname(d), colorname(d, text)); + } + else // new client + { + conoutf("\f0join:\f7 %s", colorname(d, text)); + if(needclipboard >= 0) needclipboard++; + } + copystring(d->name, text, MAXNAMELEN+1); + getstring(text, p); + filtertext(d->team, text, false, false, MAXTEAMLEN); + d->playermodel = getint(p); + d->playermodel = 0; + break; + } + + case N_SWITCHNAME: + getstring(text, p); + if(d) + { + filtertext(text, text, false, false, MAXNAMELEN); + if(!text[0]) copystring(text, "Anonymous"); + if(strcmp(text, d->name)) + { + if(!isignored(d->clientnum)) conoutf("%s is now known as %s", colorname(d), colorname(d, text)); + copystring(d->name, text, MAXNAMELEN+1); + } + } + break; + + case N_SWITCHMODEL: + break; + + case N_CDIS: + clientdisconnected(getint(p)); + break; + + case N_SPAWN: + { + if(d) + { + if(d->state==CS_DEAD && d->lastpain) saveragdoll(d); + d->respawn(); + } + parsestate(d, p); + if(!d) break; + d->state = CS_SPAWNING; + if(player1->state==CS_SPECTATOR && following==d->clientnum) + lasthit = 0; + break; + } + + case N_SPAWNSTATE: + { + int scn = getint(p); + fpsent *s = getclient(scn); + if(!s) { parsestate(NULL, p); break; } + if(s->state==CS_DEAD && s->lastpain) saveragdoll(s); + if(s==player1) + { + if(editmode) toggleedit(); + stopfollowing(); + } + s->respawn(); + parsestate(s, p); + s->state = CS_ALIVE; + pickgamespawn(s); + if(s == player1) + { + showscores(false); + lasthit = 0; + } + ai::spawned(s); + addmsg(N_SPAWN, "rcii", s, s->lifesequence, s->gunselect); + break; + } + + case N_SHOTFX: + { + int scn = getint(p), gun = getint(p), id = getint(p); + vec from, to; + loopk(3) from[k] = getint(p)/DMF; + loopk(3) to[k] = getint(p)/DMF; + fpsent *s = getclient(scn); + if(!s) break; + if(gun>GUN_FIST && gun<=GUN_PISTOL && s->ammo[gun]) s->ammo[gun]--; + s->gunselect = clamp(gun, (int)GUN_FIST, (int)GUN_PISTOL); + s->gunwait = guns[s->gunselect].attackdelay; + int prevaction = s->lastaction; + s->lastaction = lastmillis; + s->lastattackgun = s->gunselect; + shoteffects(s->gunselect, from, to, s, false, id, prevaction); + break; + } + + case N_EXPLODEFX: + { + int ecn = getint(p), gun = getint(p), id = getint(p); + fpsent *e = getclient(ecn); + if(!e) break; + explodeeffects(gun, e, false, id); + break; + } + case N_DAMAGE: + { + int tcn = getint(p), + acn = getint(p), + damage = getint(p), + armour = getint(p), + health = getint(p); + fpsent *target = getclient(tcn), + *actor = getclient(acn); + if(!target || !actor) break; + target->armour = armour; + target->health = health; + if(target->state == CS_ALIVE && actor != player1) target->lastpain = lastmillis; + damaged(damage, target, actor, false); + break; + } + + case N_HITPUSH: + { + int tcn = getint(p), gun = getint(p), damage = getint(p); + fpsent *target = getclient(tcn); + vec dir; + loopk(3) dir[k] = getint(p)/DNF; + if(target) target->hitpush(damage * (target->health<=0 ? deadpush : 1), dir, NULL, gun); + break; + } + + case N_DIED: + { + int vcn = getint(p), acn = getint(p), frags = getint(p), tfrags = getint(p); + fpsent *victim = getclient(vcn), + *actor = getclient(acn); + if(!actor) break; + actor->frags = frags; + if(m_teammode) setteaminfo(actor->team, tfrags); + extern int hidefrags; + if(actor!=player1 && (!hidefrags)) + { + defformatstring(ds, "%d", actor->frags); + particle_textcopy(actor->abovehead(), ds, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); + } + if(!victim) break; + killed(victim, actor); + break; + } + + case N_TEAMINFO: + for(;;) + { + getstring(text, p); + if(p.overread() || !text[0]) break; + int frags = getint(p); + if(p.overread()) break; + if(m_teammode) setteaminfo(text, frags); + } + break; + + case N_GUNSELECT: + { + if(!d) return; + int gun = getint(p); + d->gunselect = clamp(gun, int(GUN_FIST), int(GUN_PISTOL)); + playsound(S_WEAPLOAD, &d->o); + break; + } + + case N_TAUNT: + { + if(!d) return; + d->lasttaunt = lastmillis; + break; + } + + case N_RESUME: + { + for(;;) + { + int cn = getint(p); + if(p.overread() || cn<0) break; + fpsent *d = (cn == player1->clientnum ? player1 : newclient(cn)); + parsestate(d, p, true); + } + break; + } + + case N_ITEMSPAWN: + { + int i = getint(p); + if(!entities::ents.inrange(i)) break; + entities::setspawn(i, true); + ai::itemspawned(i); + playsound(S_ITEMSPAWN, &entities::ents[i]->o, NULL, 0, 0, 0, -1, 0, 1500); + #if 0 + const char *name = entities::itemname(i); + if(name) particle_text(entities::ents[i]->o, name, PART_TEXT, 2000, 0x32FF64, 4.0f, -8); + #endif + int icon = entities::itemicon(i); + if(icon >= 0) particle_icon(vec(0.0f, 0.0f, 4.0f).add(entities::ents[i]->o), icon%4, icon/4, PART_HUD_ICON, 2000, 0xFFFFFF, 2.0f, -8); + break; + } + + case N_ITEMACC: // server acknowledges that I picked up this item + { + int i = getint(p), cn = getint(p); + if(cn >= 0) + { + fpsent *d = getclient(cn); + entities::pickupeffects(i, d); + } + else entities::setspawn(i, true); + break; + } + + case N_CLIPBOARD: + { + int cn = getint(p), unpacklen = getint(p), packlen = getint(p); + fpsent *d = getclient(cn); + ucharbuf q = p.subbuf(max(packlen, 0)); + if(d) unpackeditinfo(d->edit, q.buf, q.maxlen, unpacklen); + break; + } + case N_UNDO: + case N_REDO: + { + int cn = getint(p), unpacklen = getint(p), packlen = getint(p); + fpsent *d = getclient(cn); + ucharbuf q = p.subbuf(max(packlen, 0)); + if(d) unpackundo(q.buf, q.maxlen, unpacklen); + break; + } + + case N_EDITF: // coop editing messages + case N_EDITT: + case N_EDITM: + case N_FLIP: + case N_COPY: + case N_PASTE: + case N_ROTATE: + case N_REPLACE: + case N_DELCUBE: + case N_EDITVSLOT: + { + if(!d) return; + selinfo sel; + sel.o.x = getint(p); sel.o.y = getint(p); sel.o.z = getint(p); + sel.s.x = getint(p); sel.s.y = getint(p); sel.s.z = getint(p); + sel.grid = getint(p); sel.orient = getint(p); + sel.cx = getint(p); sel.cxs = getint(p); sel.cy = getint(p), sel.cys = getint(p); + sel.corner = getint(p); + switch(type) + { + case N_EDITF: { int dir = getint(p), mode = getint(p); if(sel.validate()) mpeditface(dir, mode, sel, false); break; } + case N_EDITT: + { + int tex = getint(p), + allfaces = getint(p); + if(p.remaining() < 2) return; + int extra = lilswap(*(const ushort *)p.pad(2)); + if(p.remaining() < extra) return; + ucharbuf ebuf = p.subbuf(extra); + if(sel.validate()) mpedittex(tex, allfaces, sel, ebuf); + break; + } + case N_EDITM: { int mat = getint(p), filter = getint(p); if(sel.validate()) mpeditmat(mat, filter, sel, false); break; } + case N_FLIP: if(sel.validate()) mpflip(sel, false); break; + case N_COPY: if(d && sel.validate()) mpcopy(d->edit, sel, false); break; + case N_PASTE: if(d && sel.validate()) mppaste(d->edit, sel, false); break; + case N_ROTATE: { int dir = getint(p); if(sel.validate()) mprotate(dir, sel, false); break; } + case N_REPLACE: + { + int oldtex = getint(p), + newtex = getint(p), + insel = getint(p); + if(p.remaining() < 2) return; + int extra = lilswap(*(const ushort *)p.pad(2)); + if(p.remaining() < extra) return; + ucharbuf ebuf = p.subbuf(extra); + if(sel.validate()) mpreplacetex(oldtex, newtex, insel>0, sel, ebuf); + break; + } + case N_DELCUBE: if(sel.validate()) mpdelcube(sel, false); break; + case N_EDITVSLOT: + { + int delta = getint(p), + allfaces = getint(p); + if(p.remaining() < 2) return; + int extra = lilswap(*(const ushort *)p.pad(2)); + if(p.remaining() < extra) return; + ucharbuf ebuf = p.subbuf(extra); + if(sel.validate()) mpeditvslot(delta, allfaces, sel, ebuf); + break; + } + } + break; + } + case N_REMIP: + { + if(!d) return; + conoutf("%s remipped", colorname(d)); + mpremip(false); + break; + } + case N_EDITENT: // coop edit of ent + { + if(!d) return; + int i = getint(p); + float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF; + int type = getint(p); + int attr1 = getint(p), attr2 = getint(p), attr3 = getint(p), attr4 = getint(p), attr5 = getint(p); + + mpeditent(i, vec(x, y, z), type, attr1, attr2, attr3, attr4, attr5, false); + break; + } + case N_EDITVAR: + { + if(!d) return; + int type = getint(p); + getstring(text, p); + string name; + filtertext(name, text, false); + ident *id = getident(name); + switch(type) + { + case ID_VAR: + { + int val = getint(p); + if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setvar(name, val); + break; + } + case ID_FVAR: + { + float val = getfloat(p); + if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setfvar(name, val); + break; + } + case ID_SVAR: + { + getstring(text, p); + if(id && id->flags&IDF_OVERRIDE && !(id->flags&IDF_READONLY)) setsvar(name, text); + break; + } + } + printvar(d, id); + break; + } + + case N_PONG: + addmsg(N_CLIENTPING, "i", player1->ping = (player1->ping*5+totalmillis-getint(p))/6); + break; + + case N_CLIENTPING: + if(!d) return; + d->ping = getint(p); + break; + + case N_TIMEUP: + timeupdate(getint(p)); + break; + + case N_SERVMSG: + getstring(text, p); + conoutf("%s", text); + break; + + case N_SENDDEMOLIST: + { + int demos = getint(p); + if(demos <= 0) conoutf("no demos available"); + else loopi(demos) + { + getstring(text, p); + if(p.overread()) break; + conoutf("%d. %s", i+1, text); + } + break; + } + + case N_DEMOPLAYBACK: + { + int on = getint(p); + if(on) player1->state = CS_SPECTATOR; + else clearclients(); + demoplayback = on!=0; + player1->clientnum = getint(p); + gamepaused = false; + execident(on ? "demostart" : "demoend"); + break; + } + + case N_CURRENTMASTER: + { + int mm = getint(p), mn; + loopv(players) players[i]->privilege = PRIV_NONE; + while((mn = getint(p))>=0 && !p.overread()) + { + fpsent *m = mn==player1->clientnum ? player1 : newclient(mn); + int priv = getint(p); + if(m) m->privilege = priv; + } + if(mm != mastermode) + { + mastermode = mm; + conoutf("mastermode is %s (%d)", server::mastermodename(mastermode), mastermode); + } + break; + } + + case N_MASTERMODE: + { + mastermode = getint(p); + conoutf("mastermode is %s (%d)", server::mastermodename(mastermode), mastermode); + break; + } + + case N_EDITMODE: + { + int val = getint(p); + if(!d) break; + if(val) + { + d->editstate = d->state; + d->state = CS_EDITING; + } + else + { + d->state = d->editstate; + if(d->state==CS_DEAD) deathstate(d, true); + } + break; + } + + case N_SPECTATOR: + { + int sn = getint(p), val = getint(p); + fpsent *s; + if(sn==player1->clientnum) + { + s = player1; + if(val && remote && !player1->privilege) senditemstoserver = false; + } + else s = newclient(sn); + if(!s) return; + if(val) + { + if(s==player1) + { + if(editmode) toggleedit(); + if(s->state==CS_DEAD) showscores(false); + disablezoom(); + } + s->state = CS_SPECTATOR; + } + else if(s->state==CS_SPECTATOR) + { + if(s==player1) stopfollowing(); + deathstate(s, true); + } + break; + } + + case N_SETTEAM: + { + int wn = getint(p); + getstring(text, p); + int reason = getint(p); + fpsent *w = getclient(wn); + if(!w) return; + filtertext(w->team, text, false, false, MAXTEAMLEN); + static const char * const fmt[2] = { "%s switched to team %s", "%s forced to team %s"}; + if(reason >= 0 && size_t(reason) < sizeof(fmt)/sizeof(fmt[0])) + conoutf(fmt[reason], colorname(w), w->team); + break; + } + + case N_ANNOUNCE: + { + int t = getint(p); + if (t==I_QUAD) { playsound(S_V_QUAD10, NULL, NULL, 0, 0, 0, -1, 0, 3000); conoutf(CON_GAMEINFO, "\f2quad damage will spawn in 10 seconds!"); } + else if(t==I_BOOST) { playsound(S_V_BOOST10, NULL, NULL, 0, 0, 0, -1, 0, 3000); conoutf(CON_GAMEINFO, "\f2health boost will spawn in 10 seconds!"); } + break; + } + + case N_NEWMAP: + { + int size = getint(p); + if(size>=0) emptymap(size, true, NULL); + else enlargemap(true); + if(d && d!=player1) + { + int newsize = 0; + while(1<=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", colorname(d), newsize); + } + break; + } + + case N_REQAUTH: + { + getstring(text, p); + if(autoauth && text[0] && tryauth(text)) conoutf("server requested authkey \"%s\"", text); + break; + } + + case N_AUTHCHAL: + { + getstring(text, p); + authkey *a = findauthkey(text); + uint id = (uint)getint(p); + getstring(text, p); + if(a && a->lastauth && lastmillis - a->lastauth < 60*1000) + { + vector buf; + answerchallenge(a->key, text, buf); + //conoutf(CON_DEBUG, "answering %u, challenge %s with %s", id, text, buf.getbuf()); + packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(p, N_AUTHANS); + sendstring(a->desc, p); + putint(p, id); + sendstring(buf.getbuf(), p); + sendclientpacket(p.finalize(), 1); + } + break; + } + + case N_INITAI: + { + int bn = getint(p), on = getint(p), at = getint(p), sk = clamp(getint(p), 1, 101), pm = getint(p); + string name, team; + getstring(text, p); + filtertext(name, text, false, false, MAXNAMELEN); + getstring(text, p); + filtertext(team, text, false, false, MAXTEAMLEN); + fpsent *b = newclient(bn); + if(!b) break; + ai::init(b, at, on, sk, bn, pm, name, team); + break; + } + + case N_SERVCMD: + getstring(text, p); + break; + + default: + neterr("type", cn < 0); + return; + } + } + + struct demoreq + { + int tag; + string name; + }; + vector demoreqs; + enum { MAXDEMOREQS = 7 }; + static int lastdemoreq = 0; + + void receivefile(packetbuf &p) + { + int type; + while(p.remaining()) switch(type = getint(p)) + { + case N_DEMOPACKET: return; + case N_SENDDEMO: + { + string fname; + fname[0] = '\0'; + int tag = getint(p); + loopv(demoreqs) if(demoreqs[i].tag == tag) + { + copystring(fname, demoreqs[i].name); + demoreqs.remove(i); + break; + } + if(!fname[0]) + { + time_t t = time(NULL); + size_t len = strftime(fname, sizeof(fname), "%Y-%m-%d_%H.%M.%S", localtime(&t)); + fname[min(len, sizeof(fname)-1)] = '\0'; + } + int len = strlen(fname); + if(len < 4 || strcasecmp(&fname[len-4], ".dmo")) concatstring(fname, ".dmo"); + stream *demo = NULL; + if(const char *buf = server::getdemofile(fname, true)) demo = openrawfile(buf, "wb"); + if(!demo) demo = openrawfile(fname, "wb"); + if(!demo) return; + conoutf("received demo \"%s\"", fname); + ucharbuf b = p.subbuf(p.remaining()); + demo->write(b.buf, b.maxlen); + delete demo; + break; + } + + case N_SENDMAP: + { + if(!m_edit) return; + string oldname; + copystring(oldname, getclientmap()); + defformatstring(mname, "getmap_%d", lastmillis); + defformatstring(fname, "packages/maps/%s.ogz", mname); + stream *map = openrawfile(path(fname), "wb"); + if(!map) return; + conoutf("received map"); + ucharbuf b = p.subbuf(p.remaining()); + map->write(b.buf, b.maxlen); + delete map; + if(load_world(mname, oldname[0] ? oldname : NULL)) + entities::spawnitems(true); + remove(findfile(fname, "rb")); + break; + } + } + } + + void parsepacketclient(int chan, packetbuf &p) // processes any updates from the server + { + if(p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED) return; + switch(chan) + { + case 0: + parsepositions(p); + break; + + case 1: + parsemessages(-1, NULL, p); + break; + + case 2: + receivefile(p); + break; + } + } + + void getmap() + { + if(!m_edit) { conoutf(CON_ERROR, "\"getmap\" only works in coop edit mode"); return; } + conoutf("getting map..."); + addmsg(N_GETMAP, "r"); + } + COMMAND(getmap, ""); + + void stopdemo() + { + if(remote) + { + if(player1->privilegeprivilegeprivilege= MAXDEMOREQS) demoreqs.remove(0); + demoreq &r = demoreqs.add(); + r.tag = lastdemoreq; + copystring(r.name, name); + } + addmsg(N_GETDEMO, "rii", i, lastdemoreq); + } + ICOMMAND(getdemo, "ss", (char *val, char *name), getdemo(val, name)); + + void listdemos() + { + conoutf("listing demos..."); + addmsg(N_LISTDEMOS, "r"); + } + COMMAND(listdemos, ""); + + void sendmap() + { + if(!m_edit || (player1->state==CS_SPECTATOR && remote && !player1->privilege)) { conoutf(CON_ERROR, "\"sendmap\" only works in coop edit mode"); return; } + conoutf("sending map..."); + defformatstring(mname, "sendmap_%d", lastmillis); + save_world(mname, true); + defformatstring(fname, "packages/maps/%s.ogz", mname); + stream *map = openrawfile(path(fname), "rb"); + if(map) + { + stream::offset len = map->size(); + if(len > 4*1024*1024) conoutf(CON_ERROR, "map is too large"); + else if(len <= 0) conoutf(CON_ERROR, "could not read map"); + else + { + sendfile(-1, 2, map); + if(needclipboard >= 0) needclipboard++; + } + delete map; + } + else conoutf(CON_ERROR, "could not read map"); + remove(findfile(fname, "rb")); + } + COMMAND(sendmap, ""); + + void gotoplayer(const char *arg) + { + if(player1->state!=CS_SPECTATOR && player1->state!=CS_EDITING) return; + int i = parseplayer(arg); + if(i>=0) + { + fpsent *d = getclient(i); + if(!d || d==player1) return; + player1->o = d->o; + vec dir; + vecfromyawpitch(player1->yaw, player1->pitch, 1, 0, dir); + player1->o.add(dir.mul(-32)); + player1->resetinterp(); + } + } + COMMANDN(goto, gotoplayer, "s"); + + void gotosel() + { + if(player1->state!=CS_EDITING) return; + player1->o = getselpos(); + vec dir; + vecfromyawpitch(player1->yaw, player1->pitch, 1, 0, dir); + player1->o.add(dir.mul(-32)); + player1->resetinterp(); + } + COMMAND(gotosel, ""); } diff --git a/src/fpsgame/entities.cpp b/src/fpsgame/entities.cpp index 4676ca8..dc0e175 100644 --- a/src/fpsgame/entities.cpp +++ b/src/fpsgame/entities.cpp @@ -4,456 +4,456 @@ int pwitemspicked[7] = { 0 }; namespace entities { - using namespace game; + using namespace game; - int extraentinfosize() { return 0; } // size in bytes of what the 2 methods below read/write... so it can be skipped by other games + int extraentinfosize() { return 0; } // size in bytes of what the 2 methods below read/write... so it can be skipped by other games - void writeent(entity &e, char *buf) // write any additional data to disk (except for ET_ ents) - { - } + void writeent(entity &e, char *buf) // write any additional data to disk (except for ET_ ents) + { + } - void readent(entity &e, char *buf, int ver) // read from disk, and init - { - if(ver <= 30) switch(e.type) - { - case TELEDEST: - e.attr1 = (int(e.attr1)+180)%360; - break; - } - } + void readent(entity &e, char *buf, int ver) // read from disk, and init + { + if(ver <= 30) switch(e.type) + { + case TELEDEST: + e.attr1 = (int(e.attr1)+180)%360; + break; + } + } #ifndef STANDALONE - vector ents; - - vector &getents() { return ents; } - - bool mayattach(extentity &e) { return false; } - bool attachent(extentity &e, extentity &a) { return false; } - - const char *itemname(int i) - { - int t = ents[i]->type; - if(tI_QUAD) return NULL; - return itemstats[t-I_SHELLS].name; - } - - int itemicon(int i) - { - int t = ents[i]->type; - if(tI_QUAD) return -1; - return itemstats[t-I_SHELLS].icon; - } - - const char *entmdlname(int type) - { - static const char * const entmdlnames[] = - { - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - "ammo/shells", "ammo/bullets", "ammo/rockets", "ammo/rrounds", "ammo/grenades", "ammo/cartridges", - "health", "boost", "tinyhealth", "tinyarmour", "armor/green", "armor/yellow", "quad", "teleporter", - NULL, NULL, - "carrot", - NULL, NULL, - "checkpoint", - NULL, NULL, - NULL, NULL, - NULL - }; - return entmdlnames[type]; - } - - const char *entmodel(const entity &e) - { - if(e.type == TELEPORT) - { - if(e.attr2 > 0) return mapmodelname(e.attr2); - if(e.attr2 < 0) return NULL; - } - return e.type < MAXENTTYPES ? entmdlname(e.type) : NULL; - } - - void preloadentities() - { - loopi(MAXENTTYPES) - { - switch(i) - { - case I_SHELLS: - [[fallthrough]]; - case I_BULLETS: - [[fallthrough]]; - case I_ROCKETS: - [[fallthrough]]; - case I_ROUNDS: - [[fallthrough]]; - case I_GRENADES: - [[fallthrough]]; - case I_CARTRIDGES: - if(m_noammo) continue; - break; - case I_HEALTH: - [[fallthrough]]; - case I_BOOST: - [[fallthrough]]; - case I_TINYHEALTH: - [[fallthrough]]; - case I_TINYARMOUR: - [[fallthrough]]; - case I_GREENARMOUR: - [[fallthrough]]; - case I_YELLOWARMOUR: - [[fallthrough]]; - case I_QUAD: - if(m_noitems) continue; - break; - } - const char *mdl = entmdlname(i); - if(!mdl) continue; - preloadmodel(mdl); - } - loopv(ents) - { - extentity &e = *ents[i]; - switch(e.type) - { - case TELEPORT: - if(e.attr2 > 0) preloadmodel(mapmodelname(e.attr2)); - [[fallthrough]]; - - case JUMPPAD: - if(e.attr4 > 0) preloadmapsound(e.attr4); - break; - } - } - } - - void renderentities() - { - loopv(ents) - { - extentity &e = *ents[i]; - int revs = 10; - switch(e.type) - { - case TELEPORT: - if(e.attr2 < 0) continue; - break; - default: - if(!e.spawned() || e.type < I_SHELLS || e.type > I_QUAD) continue; - } - const char *mdlname = entmodel(e); - if(mdlname) - { - vec p = e.o; - p.z += 1+sinf(lastmillis/100.0+e.o.x+e.o.y)/20; - rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); - } - } - } - - void addammo(int type, int &v, bool local) - { - itemstat &is = itemstats[type-I_SHELLS]; - v += is.add; - if(v>is.max) v = is.max; - if(local) msgsound(is.sound); - } - - void repammo(fpsent *d, int type, bool local) - { - addammo(type, d->ammo[type-I_SHELLS+GUN_SG], local); - } - - // these two functions are called when the server acknowledges that you really - // picked up the item (in multiplayer someone may grab it before you). - - void pickupeffects(int n, fpsent *d) - { - if(!ents.inrange(n)) return; - extentity *e = ents[n]; - int type = e->type; - if(typeI_QUAD) return; - e->clearspawned(); - e->clearnopickup(); - if(!d) return; - itemstat &is = itemstats[type-I_SHELLS]; - fpsent *h = followingplayer(player1); - if(d!=h || isthirdperson()) - { - //particle_text(d->abovehead(), is.name, PART_TEXT, 2000, 0xFFC864, 4.0f, -8); - particle_icon(d->abovehead(), is.icon%4, is.icon/4, PART_HUD_ICON_GREY, 2000, 0xFFFFFF, 2.0f, -8); - } - playsound(itemstats[type-I_SHELLS].sound, d!=h ? &d->o : NULL, NULL, 0, 0, 0, -1, 0, 1500); - d->pickup(type); - switch(type) { - case I_TINYARMOUR: pwitemspicked[0]++; break; - case I_GREENARMOUR: pwitemspicked[1]++; break; - case I_YELLOWARMOUR: pwitemspicked[2]++; break; - case I_TINYHEALTH: pwitemspicked[3]++; break; - case I_HEALTH: pwitemspicked[4]++; break; - case I_BOOST: pwitemspicked[5]++; break; - case I_QUAD: pwitemspicked[6]++; break; - } - if(d==h) switch(type) - { - case I_BOOST: - conoutf(CON_GAMEINFO, "\f2you got the health boost!"); - playsound(S_V_BOOST, NULL, NULL, 0, 0, 0, -1, 0, 3000); - break; - - case I_QUAD: - conoutf(CON_GAMEINFO, "\f2you got the quad!"); - playsound(S_V_QUAD, NULL, NULL, 0, 0, 0, -1, 0, 3000); - break; - } - } - - // these functions are called when the client touches the item - - void teleporteffects(fpsent *d, int tp, int td, bool local) - { - if(ents.inrange(tp) && ents[tp]->type == TELEPORT) - { - extentity &e = *ents[tp]; - if(e.attr4 >= 0) - { - int snd = S_TELEPORT, flags = 0; - if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; } - fpsent *h = followingplayer(player1); - playsound(snd, d==h ? NULL : &e.o, NULL, flags); - if(d!=h && ents.inrange(td) && ents[td]->type == TELEDEST) playsound(snd, &ents[td]->o, NULL, flags); - } - } - if(local && d->clientnum >= 0) - { - sendposition(d); - packetbuf p(32, ENET_PACKET_FLAG_RELIABLE); - putint(p, N_TELEPORT); - putint(p, d->clientnum); - putint(p, tp); - putint(p, td); - sendclientpacket(p.finalize(), 0); - flushclient(); - } - } - - void jumppadeffects(fpsent *d, int jp, bool local) - { - if(ents.inrange(jp) && ents[jp]->type == JUMPPAD) - { - extentity &e = *ents[jp]; - if(e.attr4 >= 0) - { - int snd = S_JUMPPAD, flags = 0; - if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; } - playsound(snd, d == followingplayer(player1) ? NULL : &e.o, NULL, flags); - } - } - if(local && d->clientnum >= 0) - { - sendposition(d); - packetbuf p(16, ENET_PACKET_FLAG_RELIABLE); - putint(p, N_JUMPPAD); - putint(p, d->clientnum); - putint(p, jp); - sendclientpacket(p.finalize(), 0); - flushclient(); - } - } - - void teleport(int n, fpsent *d) // also used by monsters - { - int e = -1, tag = ents[n]->attr1, beenhere = -1; - for(;;) - { - e = findentity(TELEDEST, e+1); - if(e==beenhere || e<0) { conoutf(CON_WARN, "no teleport destination for tag %d", tag); return; } - if(beenhere<0) beenhere = e; - if(ents[e]->attr2==tag) - { - teleporteffects(d, n, e, true); - d->o = ents[e]->o; - d->yaw = ents[e]->attr1; - if(ents[e]->attr3 > 0) - { - vec dir; - vecfromyawpitch(d->yaw, 0, 1, 0, dir); - float speed = d->vel.magnitude2(); - d->vel.x = dir.x*speed; - d->vel.y = dir.y*speed; - } - else d->vel = vec(0, 0, 0); - entinmap(d); - updatedynentcache(d); - ai::inferwaypoints(d, ents[n]->o, ents[e]->o, 16.f); - break; - } - } - } - - void trypickup(int n, fpsent *d) - { - extentity *e = ents[n]; - switch(e->type) - { - default: - if(d->canpickup(e->type)) - { - addmsg(N_ITEMPICKUP, "rci", d, n); - e->setnopickup(); // even if someone else gets it first - } - break; - - case TELEPORT: - { - if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<500) break; - if(e->attr3 > 0) - { - defformatstring(hookname, "can_teleport_%d", e->attr3); - if(!execidentbool(hookname, true)) break; - } - d->lastpickup = e->type; - d->lastpickupmillis = lastmillis; - teleport(n, d); - break; - } - - case JUMPPAD: - { - if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<300) break; - d->lastpickup = e->type; - d->lastpickupmillis = lastmillis; - jumppadeffects(d, n, true); - vec v((int)(char)e->attr3*10.0f, (int)(char)e->attr2*10.0f, e->attr1*12.5f); - if(d->ai) d->ai->becareful = true; + vector ents; + + vector &getents() { return ents; } + + bool mayattach(extentity &e) { return false; } + bool attachent(extentity &e, extentity &a) { return false; } + + const char *itemname(int i) + { + int t = ents[i]->type; + if(tI_QUAD) return NULL; + return itemstats[t-I_SHELLS].name; + } + + int itemicon(int i) + { + int t = ents[i]->type; + if(tI_QUAD) return -1; + return itemstats[t-I_SHELLS].icon; + } + + const char *entmdlname(int type) + { + static const char * const entmdlnames[] = + { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + "ammo/shells", "ammo/bullets", "ammo/rockets", "ammo/rrounds", "ammo/grenades", "ammo/cartridges", + "health", "boost", "tinyhealth", "tinyarmour", "armor/green", "armor/yellow", "quad", "teleporter", + NULL, NULL, + "carrot", + NULL, NULL, + "checkpoint", + NULL, NULL, + NULL, NULL, + NULL + }; + return entmdlnames[type]; + } + + const char *entmodel(const entity &e) + { + if(e.type == TELEPORT) + { + if(e.attr2 > 0) return mapmodelname(e.attr2); + if(e.attr2 < 0) return NULL; + } + return e.type < MAXENTTYPES ? entmdlname(e.type) : NULL; + } + + void preloadentities() + { + loopi(MAXENTTYPES) + { + switch(i) + { + case I_SHELLS: + [[fallthrough]]; + case I_BULLETS: + [[fallthrough]]; + case I_ROCKETS: + [[fallthrough]]; + case I_ROUNDS: + [[fallthrough]]; + case I_GRENADES: + [[fallthrough]]; + case I_CARTRIDGES: + if(m_noammo) continue; + break; + case I_HEALTH: + [[fallthrough]]; + case I_BOOST: + [[fallthrough]]; + case I_TINYHEALTH: + [[fallthrough]]; + case I_TINYARMOUR: + [[fallthrough]]; + case I_GREENARMOUR: + [[fallthrough]]; + case I_YELLOWARMOUR: + [[fallthrough]]; + case I_QUAD: + if(m_noitems) continue; + break; + } + const char *mdl = entmdlname(i); + if(!mdl) continue; + preloadmodel(mdl); + } + loopv(ents) + { + extentity &e = *ents[i]; + switch(e.type) + { + case TELEPORT: + if(e.attr2 > 0) preloadmodel(mapmodelname(e.attr2)); + [[fallthrough]]; + + case JUMPPAD: + if(e.attr4 > 0) preloadmapsound(e.attr4); + break; + } + } + } + + void renderentities() + { + loopv(ents) + { + extentity &e = *ents[i]; + int revs = 10; + switch(e.type) + { + case TELEPORT: + if(e.attr2 < 0) continue; + break; + default: + if(!e.spawned() || e.type < I_SHELLS || e.type > I_QUAD) continue; + } + const char *mdlname = entmodel(e); + if(mdlname) + { + vec p = e.o; + p.z += 1+sinf(lastmillis/100.0+e.o.x+e.o.y)/20; + rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); + } + } + } + + void addammo(int type, int &v, bool local) + { + itemstat &is = itemstats[type-I_SHELLS]; + v += is.add; + if(v>is.max) v = is.max; + if(local) msgsound(is.sound); + } + + void repammo(fpsent *d, int type, bool local) + { + addammo(type, d->ammo[type-I_SHELLS+GUN_SG], local); + } + + // these two functions are called when the server acknowledges that you really + // picked up the item (in multiplayer someone may grab it before you). + + void pickupeffects(int n, fpsent *d) + { + if(!ents.inrange(n)) return; + extentity *e = ents[n]; + int type = e->type; + if(typeI_QUAD) return; + e->clearspawned(); + e->clearnopickup(); + if(!d) return; + itemstat &is = itemstats[type-I_SHELLS]; + fpsent *h = followingplayer(player1); + if(d!=h || isthirdperson()) + { + //particle_text(d->abovehead(), is.name, PART_TEXT, 2000, 0xFFC864, 4.0f, -8); + particle_icon(d->abovehead(), is.icon%4, is.icon/4, PART_HUD_ICON_GREY, 2000, 0xFFFFFF, 2.0f, -8); + } + playsound(itemstats[type-I_SHELLS].sound, d!=h ? &d->o : NULL, NULL, 0, 0, 0, -1, 0, 1500); + d->pickup(type); + switch(type) { + case I_TINYARMOUR: pwitemspicked[0]++; break; + case I_GREENARMOUR: pwitemspicked[1]++; break; + case I_YELLOWARMOUR: pwitemspicked[2]++; break; + case I_TINYHEALTH: pwitemspicked[3]++; break; + case I_HEALTH: pwitemspicked[4]++; break; + case I_BOOST: pwitemspicked[5]++; break; + case I_QUAD: pwitemspicked[6]++; break; + } + if(d==h) switch(type) + { + case I_BOOST: + conoutf(CON_GAMEINFO, "\f2you got the health boost!"); + playsound(S_V_BOOST, NULL, NULL, 0, 0, 0, -1, 0, 3000); + break; + + case I_QUAD: + conoutf(CON_GAMEINFO, "\f2you got the quad!"); + playsound(S_V_QUAD, NULL, NULL, 0, 0, 0, -1, 0, 3000); + break; + } + } + + // these functions are called when the client touches the item + + void teleporteffects(fpsent *d, int tp, int td, bool local) + { + if(ents.inrange(tp) && ents[tp]->type == TELEPORT) + { + extentity &e = *ents[tp]; + if(e.attr4 >= 0) + { + int snd = S_TELEPORT, flags = 0; + if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; } + fpsent *h = followingplayer(player1); + playsound(snd, d==h ? NULL : &e.o, NULL, flags); + if(d!=h && ents.inrange(td) && ents[td]->type == TELEDEST) playsound(snd, &ents[td]->o, NULL, flags); + } + } + if(local && d->clientnum >= 0) + { + sendposition(d); + packetbuf p(32, ENET_PACKET_FLAG_RELIABLE); + putint(p, N_TELEPORT); + putint(p, d->clientnum); + putint(p, tp); + putint(p, td); + sendclientpacket(p.finalize(), 0); + flushclient(); + } + } + + void jumppadeffects(fpsent *d, int jp, bool local) + { + if(ents.inrange(jp) && ents[jp]->type == JUMPPAD) + { + extentity &e = *ents[jp]; + if(e.attr4 >= 0) + { + int snd = S_JUMPPAD, flags = 0; + if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; } + playsound(snd, d == followingplayer(player1) ? NULL : &e.o, NULL, flags); + } + } + if(local && d->clientnum >= 0) + { + sendposition(d); + packetbuf p(16, ENET_PACKET_FLAG_RELIABLE); + putint(p, N_JUMPPAD); + putint(p, d->clientnum); + putint(p, jp); + sendclientpacket(p.finalize(), 0); + flushclient(); + } + } + + void teleport(int n, fpsent *d) // also used by monsters + { + int e = -1, tag = ents[n]->attr1, beenhere = -1; + for(;;) + { + e = findentity(TELEDEST, e+1); + if(e==beenhere || e<0) { conoutf(CON_WARN, "no teleport destination for tag %d", tag); return; } + if(beenhere<0) beenhere = e; + if(ents[e]->attr2==tag) + { + teleporteffects(d, n, e, true); + d->o = ents[e]->o; + d->yaw = ents[e]->attr1; + if(ents[e]->attr3 > 0) + { + vec dir; + vecfromyawpitch(d->yaw, 0, 1, 0, dir); + float speed = d->vel.magnitude2(); + d->vel.x = dir.x*speed; + d->vel.y = dir.y*speed; + } + else d->vel = vec(0, 0, 0); + entinmap(d); + updatedynentcache(d); + ai::inferwaypoints(d, ents[n]->o, ents[e]->o, 16.f); + break; + } + } + } + + void trypickup(int n, fpsent *d) + { + extentity *e = ents[n]; + switch(e->type) + { + default: + if(d->canpickup(e->type)) + { + addmsg(N_ITEMPICKUP, "rci", d, n); + e->setnopickup(); // even if someone else gets it first + } + break; + + case TELEPORT: + { + if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<500) break; + if(e->attr3 > 0) + { + defformatstring(hookname, "can_teleport_%d", e->attr3); + if(!execidentbool(hookname, true)) break; + } + d->lastpickup = e->type; + d->lastpickupmillis = lastmillis; + teleport(n, d); + break; + } + + case JUMPPAD: + { + if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<300) break; + d->lastpickup = e->type; + d->lastpickupmillis = lastmillis; + jumppadeffects(d, n, true); + vec v((int)(char)e->attr3*10.0f, (int)(char)e->attr2*10.0f, e->attr1*12.5f); + if(d->ai) d->ai->becareful = true; d->falling = vec(0, 0, 0); d->physstate = PHYS_FALL; - d->timeinair = 1; - d->vel = v; - break; - } - } - } - - void checkitems(fpsent *d) - { - if(d->state!=CS_ALIVE) return; - vec o = d->feetpos(); - loopv(ents) - { - extentity &e = *ents[i]; - if(e.type==NOTUSED) continue; - if((!e.spawned() || e.nopickup()) && e.type!=TELEPORT && e.type!=JUMPPAD) continue; - float dist = e.o.dist(o); - if(dist<(e.type==TELEPORT ? 16 : 12)) trypickup(i, d); - } - } - - void checkquad(int time, fpsent *d) - { - if(d->quadmillis && (d->quadmillis -= time)<=0) - { - d->quadmillis = 0; - fpsent *h = followingplayer(player1); - playsound(S_PUPOUT, d==h ? NULL : &d->o); - if(d==h) conoutf(CON_GAMEINFO, "\f2quad damage is over"); - } - } - - void putitems(packetbuf &p) // puts items in network stream and also spawns them locally - { - putint(p, N_ITEMLIST); - loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_noammo || ents[i]->typetype>I_CARTRIDGES)) - { - putint(p, i); - putint(p, ents[i]->type); - } - putint(p, -1); - } - - void resetspawns() { loopv(ents) { extentity *e = ents[i]; e->clearspawned(); e->clearnopickup(); } } - - void spawnitems(bool force) - { - if(m_noitems) return; - loopv(ents) - { - extentity *e = ents[i]; - if(e->type>=I_SHELLS && e->type<=I_QUAD && (!m_noammo || e->typetype>I_CARTRIDGES)) - { - e->setspawned(force || !server::delayspawn(e->type)); - e->clearnopickup(); - } - } - } - - void setspawn(int i, bool on) { if(ents.inrange(i)) { extentity *e = ents[i]; e->setspawned(on); e->clearnopickup(); } } - - extentity *newentity() { return new extentity(); } - void deleteentity(extentity *e) { delete e; } - - void clearents() - { - while(ents.length()) deleteentity(ents.pop()); - } - - void fixentity(extentity &e) - { - if(e.type == TELEDEST) e.attr3 = e.attr2; - } - - void entradius(extentity &e, bool color) - { - switch(e.type) - { - case TELEDEST: - { - vec dir; - vecfromyawpitch(e.attr1, 0, 1, 0, dir); - renderentarrow(e, dir, 4); - break; - } - case TELEPORT: - loopv(ents) if(ents[i]->type == TELEDEST && e.attr1==ents[i]->attr2) - { - renderentarrow(e, vec(ents[i]->o).sub(e.o).normalize(), e.o.dist(ents[i]->o)); - break; - } - break; - - case JUMPPAD: - renderentarrow(e, vec((int)(char)e.attr3*10.0f, (int)(char)e.attr2*10.0f, e.attr1*12.5f).normalize(), 4); - break; - - default: break; - } - } - - bool printent(extentity &e, char *buf, int len) - { - return false; - } - - const char *entnameinfo(entity &e) { return ""; } - const char *entname(int i) - { - static const char * const entnames[] = - { - "none?", "light", "mapmodel", "playerstart", "envmap", "particles", "sound", "spotlight", - "shells", "bullets", "rockets", "riflerounds", "grenades", "cartridges", - "health", "healthboost", "tinyhealth", "tinyarmour", "greenarmour", "yellowarmour", "quaddamage", - "teleport", "teledest", - "jumppad", - "", "", "", "", - }; - return i>=0 && size_t(i)timeinair = 1; + d->vel = v; + break; + } + } + } + + void checkitems(fpsent *d) + { + if(d->state!=CS_ALIVE) return; + vec o = d->feetpos(); + loopv(ents) + { + extentity &e = *ents[i]; + if(e.type==NOTUSED) continue; + if((!e.spawned() || e.nopickup()) && e.type!=TELEPORT && e.type!=JUMPPAD) continue; + float dist = e.o.dist(o); + if(dist<(e.type==TELEPORT ? 16 : 12)) trypickup(i, d); + } + } + + void checkquad(int time, fpsent *d) + { + if(d->quadmillis && (d->quadmillis -= time)<=0) + { + d->quadmillis = 0; + fpsent *h = followingplayer(player1); + playsound(S_PUPOUT, d==h ? NULL : &d->o); + if(d==h) conoutf(CON_GAMEINFO, "\f2quad damage is over"); + } + } + + void putitems(packetbuf &p) // puts items in network stream and also spawns them locally + { + putint(p, N_ITEMLIST); + loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_noammo || ents[i]->typetype>I_CARTRIDGES)) + { + putint(p, i); + putint(p, ents[i]->type); + } + putint(p, -1); + } + + void resetspawns() { loopv(ents) { extentity *e = ents[i]; e->clearspawned(); e->clearnopickup(); } } + + void spawnitems(bool force) + { + if(m_noitems) return; + loopv(ents) + { + extentity *e = ents[i]; + if(e->type>=I_SHELLS && e->type<=I_QUAD && (!m_noammo || e->typetype>I_CARTRIDGES)) + { + e->setspawned(force || !server::delayspawn(e->type)); + e->clearnopickup(); + } + } + } + + void setspawn(int i, bool on) { if(ents.inrange(i)) { extentity *e = ents[i]; e->setspawned(on); e->clearnopickup(); } } + + extentity *newentity() { return new extentity(); } + void deleteentity(extentity *e) { delete e; } + + void clearents() + { + while(ents.length()) deleteentity(ents.pop()); + } + + void fixentity(extentity &e) + { + if(e.type == TELEDEST) e.attr3 = e.attr2; + } + + void entradius(extentity &e, bool color) + { + switch(e.type) + { + case TELEDEST: + { + vec dir; + vecfromyawpitch(e.attr1, 0, 1, 0, dir); + renderentarrow(e, dir, 4); + break; + } + case TELEPORT: + loopv(ents) if(ents[i]->type == TELEDEST && e.attr1==ents[i]->attr2) + { + renderentarrow(e, vec(ents[i]->o).sub(e.o).normalize(), e.o.dist(ents[i]->o)); + break; + } + break; + + case JUMPPAD: + renderentarrow(e, vec((int)(char)e.attr3*10.0f, (int)(char)e.attr2*10.0f, e.attr1*12.5f).normalize(), 4); + break; + + default: break; + } + } + + bool printent(extentity &e, char *buf, int len) + { + return false; + } + + const char *entnameinfo(entity &e) { return ""; } + const char *entname(int i) + { + static const char * const entnames[] = + { + "none?", "light", "mapmodel", "playerstart", "envmap", "particles", "sound", "spotlight", + "shells", "bullets", "rockets", "riflerounds", "grenades", "cartridges", + "health", "healthboost", "tinyhealth", "tinyarmour", "greenarmour", "yellowarmour", "quaddamage", + "teleport", "teledest", + "jumppad", + "", "", "", "", + }; + return i>=0 && size_t(i) -1 and client does not exist# ... - EXT_PLAYERSTATS_RESP_IDS pid(s) #1 packet# - EXT_PLAYERSTATS_RESP_STATS pid playerdata #1 packet for each player# - C: 0 EXT_TEAMSCORE EXT_ACK EXT_VERSION 0 or 1 #error, no teammode# remaining_time gamemode loop(teamdata [numbases bases] or -1) - - Errors: - -------------- - B:C:default: 0 command EXT_ACK EXT_VERSION EXT_ERROR + Client: + ----- + A: 0 EXT_UPTIME + B: 0 EXT_PLAYERSTATS cn #a client number or -1 for all players# + C: 0 EXT_TEAMSCORE + + Server: + -------- + A: 0 EXT_UPTIME EXT_ACK EXT_VERSION uptime #in seconds# + B: 0 EXT_PLAYERSTATS cn #send by client# EXT_ACK EXT_VERSION 0 or 1 #error, if cn was > -1 and client does not exist# ... + EXT_PLAYERSTATS_RESP_IDS pid(s) #1 packet# + EXT_PLAYERSTATS_RESP_STATS pid playerdata #1 packet for each player# + C: 0 EXT_TEAMSCORE EXT_ACK EXT_VERSION 0 or 1 #error, no teammode# remaining_time gamemode loop(teamdata [numbases bases] or -1) + + Errors: + -------------- + B:C:default: 0 command EXT_ACK EXT_VERSION EXT_ERROR */ - VAR(extinfoip, 0, 0, 1); - - void extinfoplayer(ucharbuf &p, clientinfo *ci) - { - ucharbuf q = p; - putint(q, EXT_PLAYERSTATS_RESP_STATS); // send player stats following - putint(q, ci->clientnum); //add player id - putint(q, ci->ping); - sendstring(ci->name, q); - sendstring(ci->team, q); - putint(q, ci->state.frags); - putint(q, ci->state.flags); - putint(q, ci->state.deaths); - putint(q, ci->state.teamkills); - putint(q, ci->state.damage*100/max(ci->state.shotdamage,1)); - putint(q, ci->state.health); - putint(q, ci->state.armour); - putint(q, ci->state.gunselect); - putint(q, ci->privilege); - putint(q, ci->state.state); - uint ip = extinfoip ? getclientip(ci->clientnum) : 0; - q.put((uchar*)&ip, 3); - sendserverinforeply(q); - } - - static inline void extinfoteamscore(ucharbuf &p, const char *team, int score) - { - sendstring(team, p); - putint(p, score); - putint(p,-1); //no bases follow - } - - void extinfoteams(ucharbuf &p) - { - putint(p, m_teammode ? 0 : 1); - putint(p, gamemode); - putint(p, max((gamelimit - gamemillis)/1000, 0)); - if(!m_teammode) return; - - vector scores; - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.state!=CS_SPECTATOR && ci->team[0] && scores.htfind(ci->team) < 0) - { - teaminfo *ti = teaminfos.access(ci->team); - scores.add(teamscore(ci->team, ti ? ti->frags : 0)); - } - } - loopv(scores) extinfoteamscore(p, scores[i].team, scores[i].score); - } - - void extserverinforeply(ucharbuf &req, ucharbuf &p) - { - int extcmd = getint(req); // extended commands - - //Build a new packet - putint(p, EXT_ACK); //send ack - putint(p, EXT_VERSION); //send version of extended info - - switch(extcmd) - { - case EXT_UPTIME: - { - putint(p, totalsecs); //in seconds - break; - } - - case EXT_PLAYERSTATS: - { - int cn = getint(req); //a special player, -1 for all - - clientinfo *ci = NULL; - if(cn >= 0) - { - loopv(clients) if(clients[i]->clientnum == cn) { ci = clients[i]; break; } - if(!ci) - { - putint(p, EXT_ERROR); //client requested by id was not found - sendserverinforeply(p); - return; - } - } - - putint(p, EXT_NO_ERROR); //so far no error can happen anymore - - ucharbuf q = p; //remember buffer position - putint(q, EXT_PLAYERSTATS_RESP_IDS); //send player ids following - if(ci) putint(q, ci->clientnum); - else loopv(clients) putint(q, clients[i]->clientnum); - sendserverinforeply(q); - - if(ci) extinfoplayer(p, ci); - else loopv(clients) extinfoplayer(p, clients[i]); - return; - } - - case EXT_TEAMSCORE: - { - extinfoteams(p); - break; - } - - default: - { - putint(p, EXT_ERROR); - break; - } - } - sendserverinforeply(p); - } + VAR(extinfoip, 0, 0, 1); + + void extinfoplayer(ucharbuf &p, clientinfo *ci) + { + ucharbuf q = p; + putint(q, EXT_PLAYERSTATS_RESP_STATS); // send player stats following + putint(q, ci->clientnum); //add player id + putint(q, ci->ping); + sendstring(ci->name, q); + sendstring(ci->team, q); + putint(q, ci->state.frags); + putint(q, ci->state.flags); + putint(q, ci->state.deaths); + putint(q, ci->state.teamkills); + putint(q, ci->state.damage*100/max(ci->state.shotdamage,1)); + putint(q, ci->state.health); + putint(q, ci->state.armour); + putint(q, ci->state.gunselect); + putint(q, ci->privilege); + putint(q, ci->state.state); + uint ip = extinfoip ? getclientip(ci->clientnum) : 0; + q.put((uchar*)&ip, 3); + sendserverinforeply(q); + } + + static inline void extinfoteamscore(ucharbuf &p, const char *team, int score) + { + sendstring(team, p); + putint(p, score); + putint(p,-1); //no bases follow + } + + void extinfoteams(ucharbuf &p) + { + putint(p, m_teammode ? 0 : 1); + putint(p, gamemode); + putint(p, max((gamelimit - gamemillis)/1000, 0)); + if(!m_teammode) return; + + vector scores; + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.state!=CS_SPECTATOR && ci->team[0] && scores.htfind(ci->team) < 0) + { + teaminfo *ti = teaminfos.access(ci->team); + scores.add(teamscore(ci->team, ti ? ti->frags : 0)); + } + } + loopv(scores) extinfoteamscore(p, scores[i].team, scores[i].score); + } + + void extserverinforeply(ucharbuf &req, ucharbuf &p) + { + int extcmd = getint(req); // extended commands + + //Build a new packet + putint(p, EXT_ACK); //send ack + putint(p, EXT_VERSION); //send version of extended info + + switch(extcmd) + { + case EXT_UPTIME: + { + putint(p, totalsecs); //in seconds + break; + } + + case EXT_PLAYERSTATS: + { + int cn = getint(req); //a special player, -1 for all + + clientinfo *ci = NULL; + if(cn >= 0) + { + loopv(clients) if(clients[i]->clientnum == cn) { ci = clients[i]; break; } + if(!ci) + { + putint(p, EXT_ERROR); //client requested by id was not found + sendserverinforeply(p); + return; + } + } + + putint(p, EXT_NO_ERROR); //so far no error can happen anymore + + ucharbuf q = p; //remember buffer position + putint(q, EXT_PLAYERSTATS_RESP_IDS); //send player ids following + if(ci) putint(q, ci->clientnum); + else loopv(clients) putint(q, clients[i]->clientnum); + sendserverinforeply(q); + + if(ci) extinfoplayer(p, ci); + else loopv(clients) extinfoplayer(p, clients[i]); + return; + } + + case EXT_TEAMSCORE: + { + extinfoteams(p); + break; + } + + default: + { + putint(p, EXT_ERROR); + break; + } + } + sendserverinforeply(p); + } diff --git a/src/fpsgame/fps.cpp b/src/fpsgame/fps.cpp index 7b066ca..fde58e8 100644 --- a/src/fpsgame/fps.cpp +++ b/src/fpsgame/fps.cpp @@ -2,1401 +2,1400 @@ namespace game { - bool intermission = false; - int maptime = 0, maprealtime = 0, maplimit = -1; - int respawnent = -1; - int lasthit = 0, lastspawnattempt = 0; - - int following = -1, followdir = 0; - - fpsent *player1 = NULL; // our client - vector players; // other clients - int savedammo[NUMGUNS]; - - bool clientoption(const char *arg) { return false; } - - void taunt() - { - if(player1->state!=CS_ALIVE || player1->physstatelasttaunt<1000) return; - player1->lasttaunt = lastmillis; - addmsg(N_TAUNT, "rc", player1); - } - COMMAND(taunt, ""); - - ICOMMAND(getfollow, "", (), - { - fpsent *f = followingplayer(); - intret(f ? f->clientnum : -1); - }); + bool intermission = false; + int maptime = 0, maprealtime = 0, maplimit = -1; + int respawnent = -1; + int lasthit = 0, lastspawnattempt = 0; + + int following = -1, followdir = 0; + + fpsent *player1 = NULL; // our client + vector players; // other clients + int savedammo[NUMGUNS]; + + bool clientoption(const char *arg) { return false; } + + void taunt() + { + if(player1->state!=CS_ALIVE || player1->physstatelasttaunt<1000) return; + player1->lasttaunt = lastmillis; + addmsg(N_TAUNT, "rc", player1); + } + COMMAND(taunt, ""); + + ICOMMAND(getfollow, "", (), + { + fpsent *f = followingplayer(); + intret(f ? f->clientnum : -1); + }); void follow(char *arg) - { - if(arg[0] ? player1->state==CS_SPECTATOR : following>=0) - { - following = arg[0] ? parseplayer(arg) : -1; - if(following==player1->clientnum) following = -1; - followdir = 0; - conoutf("follow %s", following>=0 ? "on" : "off"); - } - } - COMMAND(follow, "s"); - - void nextfollow(int dir) - { - if(player1->state!=CS_SPECTATOR || clients.empty()) - { - stopfollowing(); - return; - } - int cur = following >= 0 ? following : (dir < 0 ? clients.length() - 1 : 0); - loopv(clients) - { - cur = (cur + dir + clients.length()) % clients.length(); - if(clients[cur] && clients[cur]->state!=CS_SPECTATOR) - { - if(following<0) conoutf("follow on"); - following = cur; - followdir = dir; - return; - } - } - stopfollowing(); - } - ICOMMAND(nextfollow, "i", (int *dir), nextfollow(*dir < 0 ? -1 : 1)); - - - const char *getclientmap() { return clientmap; } - - void resetgamestate() - { - clearprojectiles(); - clearbouncers(); - } - - fpsent *spawnstate(fpsent *d) // reset player state not persistent accross spawns - { - d->respawn(); - d->spawnstate(gamemode); - return d; - } - - void respawnself() - { - if(ispaused()) return; - if(m_mp(gamemode)) - { - int seq = (player1->lifesequence<<16)|((lastmillis/1000)&0xFFFF); - if(player1->respawned!=seq) { addmsg(N_TRYSPAWN, "rc", player1); player1->respawned = seq; } - } - else - { - spawnplayer(player1); - showscores(false); - lasthit = 0; - } - } - - fpsent *pointatplayer() - { - loopv(players) if(players[i] != player1 && intersect(players[i], player1->o, worldpos)) return players[i]; - return NULL; - } - - void stopfollowing() - { - if(following<0) return; - following = -1; - followdir = 0; - conoutf("follow off"); - } - - fpsent *followingplayer(fpsent *fallback) - { - if(player1->state!=CS_SPECTATOR || following<0) return fallback; - fpsent *target = getclient(following); - if(target && target->state!=CS_SPECTATOR) return target; - return fallback; - } - - fpsent *hudplayer() - { - if(thirdperson && allowthirdperson()) return player1; - return followingplayer(player1); - } - - void setupcamera() - { - fpsent *target = followingplayer(); - if(target) - { - player1->yaw = target->yaw; - player1->pitch = target->state==CS_DEAD ? 0 : target->pitch; - player1->o = target->o; - player1->resetinterp(); - } - } - - bool allowthirdperson(bool msg) - { - return player1->state==CS_SPECTATOR || player1->state==CS_EDITING || m_edit || !multiplayer(msg); - } - ICOMMAND(allowthirdperson, "b", (int *msg), intret(allowthirdperson(*msg!=0) ? 1 : 0)); - - bool detachcamera() - { - fpsent *d = hudplayer(); - return d->state==CS_DEAD; - } - - bool collidecamera() - { - switch(player1->state) - { - case CS_EDITING: return false; - case CS_SPECTATOR: return followingplayer()!=NULL; - } - return true; - } - - VARP(smoothmove, 0, 75, 100); - VARP(smoothdist, 0, 32, 64); - - void predictplayer(fpsent *d, bool move) - { - d->o = d->newpos; - d->yaw = d->newyaw; - d->pitch = d->newpitch; - d->roll = d->newroll; - if(move) - { - moveplayer(d, 1, false); - d->newpos = d->o; - } - float k = 1.0f - float(lastmillis - d->smoothmillis)/smoothmove; - if(k>0) - { - d->o.add(vec(d->deltapos).mul(k)); - d->yaw += d->deltayaw*k; - if(d->yaw<0) d->yaw += 360; - else if(d->yaw>=360) d->yaw -= 360; - d->pitch += d->deltapitch*k; - d->roll += d->deltaroll*k; - } - } - - void otherplayers(int curtime) - { - loopv(players) - { - fpsent *d = players[i]; - if(d == player1 || d->ai) continue; - - if(d->state==CS_DEAD && d->ragdoll) moveragdoll(d); - else if(!intermission) - { - if(lastmillis - d->lastaction >= d->gunwait) d->gunwait = 0; - if(d->quadmillis) entities::checkquad(curtime, d); - } - - const int lagtime = totalmillis-d->lastupdate; - if(!lagtime || intermission) continue; - else if(lagtime>1000 && d->state==CS_ALIVE) - { - d->state = CS_LAGGED; - continue; - } - if(d->state==CS_ALIVE || d->state==CS_EDITING) - { - if(smoothmove && d->smoothmillis>0) predictplayer(d, true); - else moveplayer(d, 1, false); - } - else if(d->state==CS_DEAD && !d->ragdoll && lastmillis-d->lastpain<2000) moveplayer(d, 1, true); - } - } - - void checkslowmo() - { - static int lastslowmohealth = 0; - server::forcegamespeed(intermission ? 100 : clamp(player1->health, 25, 200)); - if(player1->healthmaxhealth && lastmillis-max(maptime, lastslowmohealth)>player1->health*player1->health/2) - { - lastslowmohealth = lastmillis; - player1->health++; - } - } - - void updateworld() // main game update loop - { - if(!maptime) { maptime = lastmillis; maprealtime = totalmillis; return; } - if(!curtime) { gets2c(); if(player1->clientnum>=0) c2sinfo(); return; } - - physicsframe(); - ai::navigate(); - if(player1->state != CS_DEAD && !intermission) - { - if(player1->quadmillis) entities::checkquad(curtime, player1); - } - updateweapons(curtime); - otherplayers(curtime); - ai::update(); - moveragdolls(); - gets2c(); - if(connected) - { - if(player1->state == CS_DEAD) - { - if(player1->ragdoll) moveragdoll(player1); - else if(lastmillis-player1->lastpain<2000) - { - player1->move = player1->strafe = 0; - moveplayer(player1, 10, true); - } - } - else if(!intermission) - { - if(player1->ragdoll) cleanragdoll(player1); - moveplayer(player1, 10, true); - swayhudgun(curtime); - entities::checkitems(player1); - } - } - if(player1->clientnum>=0) c2sinfo(); // do this last, to reduce the effective frame lag - } - - float proximityscore(float x, float lower, float upper) - { - if(x <= lower) return 1.0f; - if(x >= upper) return 0.0f; - float a = x - lower, b = x - upper; - return (b * b) / (a * a + b * b); - } - - static inline float harmonicmean(float a, float b) { return a + b > 0 ? 2 * a * b / (a + b) : 0.0f; } - - // avoid spawning near other players - float ratespawn(dynent *d, const extentity &e) - { - fpsent *p = (fpsent *)d; - vec loc = vec(e.o).addz(p->eyeheight); - float maxrange = !m_noitems ? 400.0f : 110.0f; - float minplayerdist = maxrange; - loopv(players) - { - const fpsent *o = players[i]; - if(o == p) - { - if(m_noitems || (o->state != CS_ALIVE && lastmillis - o->lastpain > 3000)) continue; - } - else if(o->state != CS_ALIVE || isteam(o->team, p->team)) continue; - - vec dir = vec(o->o).sub(loc); - float dist = dir.squaredlen(); - if(dist >= minplayerdist*minplayerdist) continue; - dist = sqrtf(dist); - dir.mul(1/dist); - - // scale actual distance if not in line of sight - if(raycube(loc, dir, dist) < dist) dist *= 1.5f; - minplayerdist = min(minplayerdist, dist); - } - return 1.0f - proximityscore(minplayerdist, 80.0f, maxrange); - } - - void pickgamespawn(fpsent *d) - { - int ent = d == player1 && respawnent >= 0 ? respawnent : -1; - findplayerspawn(d, ent, 0); - } - - void spawnplayer(fpsent *d) // place at random spawn - { - pickgamespawn(d); - spawnstate(d); - if(d==player1) - { - if(editmode) d->state = CS_EDITING; - else if(d->state != CS_SPECTATOR) d->state = CS_ALIVE; - } - else d->state = CS_ALIVE; - } - - VARP(spawnwait, 0, 0, 1000); - - void respawn() - { - if(player1->state==CS_DEAD) - { - player1->attacking = false; - respawnself(); - } - } - COMMAND(respawn, ""); - - // inputs - - VARP(attackspawn, 0, 1, 1); - - void doattack(bool on) - { - if(!connected || intermission) return; - if((player1->attacking = on) && attackspawn) respawn(); - } - - VARP(jumpspawn, 0, 1, 1); - - bool canjump() - { - if(!connected || intermission) return false; - if(jumpspawn) respawn(); - return player1->state!=CS_DEAD; - } - - bool allowmove(physent *d) - { - if(d->type!=ENT_PLAYER) return true; - return !((fpsent *)d)->lasttaunt || lastmillis-((fpsent *)d)->lasttaunt>=1000; - } - - VARP(hitsound, 0, 0, 1); - - void damaged(int damage, fpsent *d, fpsent *actor, bool local) - { - if((d->state!=CS_ALIVE && d->state != CS_LAGGED && d->state != CS_SPAWNING) || intermission) return; - - if(local) damage = d->dodamage(damage); - else if(actor==player1) return; - - fpsent *h = hudplayer(); - if(h!=player1 && actor==h && d!=actor) - { - if(hitsound && lasthit != lastmillis) playsound(S_HIT); - lasthit = lastmillis; - } - if(d==h) - { - damageblend(damage); - damagecompass(damage, actor->o); - } - damageeffect(damage, d, d!=h); - - ai::damaged(d, actor); - - if(d->health<=0) { if(local) killed(d, actor); } - else if(d==h) playsound(S_PAIN6); - else playsound(S_PAIN1+rnd(5), &d->o); - } - - VARP(deathscore, 0, 1, 1); - - void deathstate(fpsent *d, bool restore) - { - d->state = CS_DEAD; - d->lastpain = lastmillis; - if(!restore) d->deaths++; - - if(d==player1) - { - if(deathscore) showscores(true); - disablezoom(); - if(!restore) loopi(NUMGUNS) savedammo[i] = player1->ammo[i]; - d->attacking = false; - d->roll = 0; - playsound(S_DIE1+rnd(2)); - } - else - { - d->move = d->strafe = 0; - d->resetinterp(); - d->smoothmillis = 0; - playsound(S_DIE1+rnd(2), &d->o); - } - } - - VARP(teamcolorfrags, 0, 1, 1); - - /// xolatile: HUD frag messages + { + if(arg[0] ? player1->state==CS_SPECTATOR : following>=0) + { + following = arg[0] ? parseplayer(arg) : -1; + if(following==player1->clientnum) following = -1; + followdir = 0; + conoutf("follow %s", following>=0 ? "on" : "off"); + } + } + COMMAND(follow, "s"); + + void nextfollow(int dir) + { + if(player1->state!=CS_SPECTATOR || clients.empty()) + { + stopfollowing(); + return; + } + int cur = following >= 0 ? following : (dir < 0 ? clients.length() - 1 : 0); + loopv(clients) + { + cur = (cur + dir + clients.length()) % clients.length(); + if(clients[cur] && clients[cur]->state!=CS_SPECTATOR) + { + if(following<0) conoutf("follow on"); + following = cur; + followdir = dir; + return; + } + } + stopfollowing(); + } + ICOMMAND(nextfollow, "i", (int *dir), nextfollow(*dir < 0 ? -1 : 1)); + + + const char *getclientmap() { return clientmap; } + + void resetgamestate() + { + clearprojectiles(); + clearbouncers(); + } + + fpsent *spawnstate(fpsent *d) // reset player state not persistent accross spawns + { + d->respawn(); + d->spawnstate(gamemode); + return d; + } + + void respawnself() + { + if(ispaused()) return; + if(m_mp(gamemode)) + { + int seq = (player1->lifesequence<<16)|((lastmillis/1000)&0xFFFF); + if(player1->respawned!=seq) { addmsg(N_TRYSPAWN, "rc", player1); player1->respawned = seq; } + } + else + { + spawnplayer(player1); + showscores(false); + lasthit = 0; + } + } + + fpsent *pointatplayer() + { + loopv(players) if(players[i] != player1 && intersect(players[i], player1->o, worldpos)) return players[i]; + return NULL; + } + + void stopfollowing() + { + if(following<0) return; + following = -1; + followdir = 0; + conoutf("follow off"); + } + + fpsent *followingplayer(fpsent *fallback) + { + if(player1->state!=CS_SPECTATOR || following<0) return fallback; + fpsent *target = getclient(following); + if(target && target->state!=CS_SPECTATOR) return target; + return fallback; + } + + fpsent *hudplayer() + { + if(thirdperson && allowthirdperson()) return player1; + return followingplayer(player1); + } + + void setupcamera() + { + fpsent *target = followingplayer(); + if(target) + { + player1->yaw = target->yaw; + player1->pitch = target->state==CS_DEAD ? 0 : target->pitch; + player1->o = target->o; + player1->resetinterp(); + } + } + + bool allowthirdperson(bool msg) + { + return player1->state==CS_SPECTATOR || player1->state==CS_EDITING || m_edit || !multiplayer(msg); + } + ICOMMAND(allowthirdperson, "b", (int *msg), intret(allowthirdperson(*msg!=0) ? 1 : 0)); + + bool detachcamera() + { + fpsent *d = hudplayer(); + return d->state==CS_DEAD; + } + + bool collidecamera() + { + switch(player1->state) + { + case CS_EDITING: return false; + case CS_SPECTATOR: return followingplayer()!=NULL; + } + return true; + } + + VARP(smoothmove, 0, 75, 100); + VARP(smoothdist, 0, 32, 64); + + void predictplayer(fpsent *d, bool move) + { + d->o = d->newpos; + d->yaw = d->newyaw; + d->pitch = d->newpitch; + d->roll = d->newroll; + if(move) + { + moveplayer(d, 1, false); + d->newpos = d->o; + } + float k = 1.0f - float(lastmillis - d->smoothmillis)/smoothmove; + if(k>0) + { + d->o.add(vec(d->deltapos).mul(k)); + d->yaw += d->deltayaw*k; + if(d->yaw<0) d->yaw += 360; + else if(d->yaw>=360) d->yaw -= 360; + d->pitch += d->deltapitch*k; + d->roll += d->deltaroll*k; + } + } + + void otherplayers(int curtime) + { + loopv(players) + { + fpsent *d = players[i]; + if(d == player1 || d->ai) continue; + + if(d->state==CS_DEAD && d->ragdoll) moveragdoll(d); + else if(!intermission) + { + if(lastmillis - d->lastaction >= d->gunwait) d->gunwait = 0; + if(d->quadmillis) entities::checkquad(curtime, d); + } + + const int lagtime = totalmillis-d->lastupdate; + if(!lagtime || intermission) continue; + else if(lagtime>1000 && d->state==CS_ALIVE) + { + d->state = CS_LAGGED; + continue; + } + if(d->state==CS_ALIVE || d->state==CS_EDITING) + { + if(smoothmove && d->smoothmillis>0) predictplayer(d, true); + else moveplayer(d, 1, false); + } + else if(d->state==CS_DEAD && !d->ragdoll && lastmillis-d->lastpain<2000) moveplayer(d, 1, true); + } + } + + void checkslowmo() + { + static int lastslowmohealth = 0; + server::forcegamespeed(intermission ? 100 : clamp(player1->health, 25, 200)); + if(player1->healthmaxhealth && lastmillis-max(maptime, lastslowmohealth)>player1->health*player1->health/2) + { + lastslowmohealth = lastmillis; + player1->health++; + } + } + + void updateworld() // main game update loop + { + if(!maptime) { maptime = lastmillis; maprealtime = totalmillis; return; } + if(!curtime) { gets2c(); if(player1->clientnum>=0) c2sinfo(); return; } + + physicsframe(); + ai::navigate(); + if(player1->state != CS_DEAD && !intermission) + { + if(player1->quadmillis) entities::checkquad(curtime, player1); + } + updateweapons(curtime); + otherplayers(curtime); + ai::update(); + moveragdolls(); + gets2c(); + if(connected) + { + if(player1->state == CS_DEAD) + { + if(player1->ragdoll) moveragdoll(player1); + else if(lastmillis-player1->lastpain<2000) + { + player1->move = player1->strafe = 0; + moveplayer(player1, 10, true); + } + } + else if(!intermission) + { + if(player1->ragdoll) cleanragdoll(player1); + moveplayer(player1, 10, true); + swayhudgun(curtime); + entities::checkitems(player1); + } + } + if(player1->clientnum>=0) c2sinfo(); // do this last, to reduce the effective frame lag + } + + float proximityscore(float x, float lower, float upper) + { + if(x <= lower) return 1.0f; + if(x >= upper) return 0.0f; + float a = x - lower, b = x - upper; + return (b * b) / (a * a + b * b); + } + + static inline float harmonicmean(float a, float b) { return a + b > 0 ? 2 * a * b / (a + b) : 0.0f; } + + // avoid spawning near other players + float ratespawn(dynent *d, const extentity &e) + { + fpsent *p = (fpsent *)d; + vec loc = vec(e.o).addz(p->eyeheight); + float maxrange = !m_noitems ? 400.0f : 110.0f; + float minplayerdist = maxrange; + loopv(players) + { + const fpsent *o = players[i]; + if(o == p) + { + if(m_noitems || (o->state != CS_ALIVE && lastmillis - o->lastpain > 3000)) continue; + } + else if(o->state != CS_ALIVE || isteam(o->team, p->team)) continue; + + vec dir = vec(o->o).sub(loc); + float dist = dir.squaredlen(); + if(dist >= minplayerdist*minplayerdist) continue; + dist = sqrtf(dist); + dir.mul(1/dist); + + // scale actual distance if not in line of sight + if(raycube(loc, dir, dist) < dist) dist *= 1.5f; + minplayerdist = min(minplayerdist, dist); + } + return 1.0f - proximityscore(minplayerdist, 80.0f, maxrange); + } + + void pickgamespawn(fpsent *d) + { + int ent = d == player1 && respawnent >= 0 ? respawnent : -1; + findplayerspawn(d, ent, 0); + } + + void spawnplayer(fpsent *d) // place at random spawn + { + pickgamespawn(d); + spawnstate(d); + if(d==player1) + { + if(editmode) d->state = CS_EDITING; + else if(d->state != CS_SPECTATOR) d->state = CS_ALIVE; + } + else d->state = CS_ALIVE; + } + + VARP(spawnwait, 0, 0, 1000); + + void respawn() + { + if(player1->state==CS_DEAD) + { + player1->attacking = false; + respawnself(); + } + } + COMMAND(respawn, ""); + + // inputs + + VARP(attackspawn, 0, 1, 1); + + void doattack(bool on) + { + if(!connected || intermission) return; + if((player1->attacking = on) && attackspawn) respawn(); + } + + VARP(jumpspawn, 0, 1, 1); + + bool canjump() + { + if(!connected || intermission) return false; + if(jumpspawn) respawn(); + return player1->state!=CS_DEAD; + } + + bool allowmove(physent *d) + { + if(d->type!=ENT_PLAYER) return true; + return !((fpsent *)d)->lasttaunt || lastmillis-((fpsent *)d)->lasttaunt>=1000; + } + + VARP(hitsound, 0, 0, 1); + + void damaged(int damage, fpsent *d, fpsent *actor, bool local) + { + if((d->state!=CS_ALIVE && d->state != CS_LAGGED && d->state != CS_SPAWNING) || intermission) return; + + if(local) damage = d->dodamage(damage); + else if(actor==player1) return; + + fpsent *h = hudplayer(); + if(h!=player1 && actor==h && d!=actor) + { + if(hitsound && lasthit != lastmillis) playsound(S_HIT); + lasthit = lastmillis; + } + if(d==h) + { + damagecompass(damage, actor->o); + } + damageeffect(damage, d, d!=h); + + ai::damaged(d, actor); + + if(d->health<=0) { if(local) killed(d, actor); } + else if(d==h) playsound(S_PAIN6); + else playsound(S_PAIN1+rnd(5), &d->o); + } + + VARP(deathscore, 0, 1, 1); + + void deathstate(fpsent *d, bool restore) + { + d->state = CS_DEAD; + d->lastpain = lastmillis; + if(!restore) d->deaths++; + + if(d==player1) + { + if(deathscore) showscores(true); + disablezoom(); + if(!restore) loopi(NUMGUNS) savedammo[i] = player1->ammo[i]; + d->attacking = false; + d->roll = 0; + playsound(S_DIE1+rnd(2)); + } + else + { + d->move = d->strafe = 0; + d->resetinterp(); + d->smoothmillis = 0; + playsound(S_DIE1+rnd(2), &d->o); + } + } + + VARP(teamcolorfrags, 0, 1, 1); + + /// xolatile: HUD frag messages #define fragmessageduration (2000) - string hudfragger, hudfragged; - int hudfraggun, hudfragmillis; - - void sethudfragdata(char *fragger, char *fragged, int gunid) - { - copystring(hudfragger, fragger ? fragger : ""); - copystring(hudfragged, fragged); - hudfraggun = gunid; - hudfragmillis = lastmillis; - } - - void killed(fpsent *d, fpsent *actor) - { - if(d->state==CS_EDITING) - { - d->editstate = CS_DEAD; - d->deaths++; - if(d!=player1) d->resetinterp(); - return; - } - else if((d->state!=CS_ALIVE && d->state != CS_LAGGED && d->state != CS_SPAWNING) || intermission) return; - - fpsent *h = followingplayer(player1); - int contype = d==h || actor==h ? CON_FRAG_SELF : CON_FRAG_OTHER; - const char *dname = "", *aname = ""; - if(m_teammode && teamcolorfrags) - { - dname = teamcolorname(d, "you"); - aname = teamcolorname(actor, "you"); - } - else - { - dname = colorname(d, NULL, "", "", "you"); - aname = colorname(actor, NULL, "", "", "you"); - } - if(actor->type==ENT_AI) - conoutf(contype, "\f2%s got killed by %s!", dname, aname); - else if(d==actor || actor->type==ENT_INANIMATE) - conoutf(contype, "\f2%s suicided%s", dname, d==player1 ? "!" : ""); - else if(isteam(d->team, actor->team)) - { - contype |= CON_TEAMKILL; - if(actor==player1) conoutf(contype, "\f6%s fragged a teammate (%s)", aname, dname); - else if(d==player1) conoutf(contype, "\f6%s got fragged by a teammate (%s)", dname, aname); - else conoutf(contype, "\f2%s fragged a teammate (%s)", aname, dname); - } - else - { - if(d==player1) - { - conoutf(contype, "\f2%s got fragged by %s", dname, aname); - sethudfragdata(actor->name, d->name, actor->gunselect); - } - else - { - conoutf(contype, "\f2%s fragged %s", aname, dname); - sethudfragdata(actor->name, d->name, actor->gunselect); - } - } - deathstate(d); - ai::killed(d, actor); - } - - void timeupdate(int secs) - { - server::timeupdate(secs); - if(secs > 0) - { - maplimit = lastmillis + secs*1000; - } - else - { - intermission = true; - player1->attacking = false; - conoutf(CON_GAMEINFO, "\f2intermission:"); - conoutf(CON_GAMEINFO, "\f2game has ended!"); - conoutf(CON_GAMEINFO, "\f2player frags: %d, deaths: %d", player1->frags, player1->deaths); - int accuracy = (player1->totaldamage*100)/max(player1->totalshots, 1); - conoutf(CON_GAMEINFO, "\f2player total damage dealt: %d, damage wasted: %d, accuracy(%%): %d", player1->totaldamage, player1->totalshots-player1->totaldamage, accuracy); - - showscores(true); - disablezoom(); - - execident("intermission"); - } - } - - ICOMMAND(getfrags, "", (), intret(player1->frags)); - ICOMMAND(getflags, "", (), intret(player1->flags)); - ICOMMAND(getdeaths, "", (), intret(player1->deaths)); - ICOMMAND(getaccuracy, "", (), intret((player1->totaldamage*100)/max(player1->totalshots, 1))); - ICOMMAND(gettotaldamage, "", (), intret(player1->totaldamage)); - ICOMMAND(gettotalshots, "", (), intret(player1->totalshots)); - - vector clients; - - fpsent *newclient(int cn) // ensure valid entity - { - if(cn < 0 || cn > max(0xFF, MAXCLIENTS + MAXBOTS)) - { - neterr("clientnum", false); - return NULL; - } - - if(cn == player1->clientnum) return player1; - - while(cn >= clients.length()) clients.add(NULL); - if(!clients[cn]) - { - fpsent *d = new fpsent; - d->clientnum = cn; - clients[cn] = d; - players.add(d); - } - return clients[cn]; - } - - fpsent *getclient(int cn) // ensure valid entity - { - if(cn == player1->clientnum) return player1; - return clients.inrange(cn) ? clients[cn] : NULL; - } - - void clientdisconnected(int cn, bool notify) - { - if(!clients.inrange(cn)) return; - if(following==cn) - { - if(followdir) nextfollow(followdir); - else stopfollowing(); - } - unignore(cn); - fpsent *d = clients[cn]; - if(!d) return; - if(notify && d->name[0]) conoutf("\f4leave:\f7 %s", colorname(d)); - removeweapons(d); - removetrackedparticles(d); - removetrackeddynlights(d); - players.removeobj(d); - DELETEP(clients[cn]); - cleardynentcache(); - } - - void clearclients(bool notify) - { - loopv(clients) if(clients[i]) clientdisconnected(i, notify); - } - - void initclient() - { - player1 = spawnstate(new fpsent); - filtertext(player1->name, "Anonymous", false, false, MAXNAMELEN); - players.add(player1); - } - - VARP(showmodeinfo, 0, 1, 1); - - void startgame() - { - clearprojectiles(); - clearbouncers(); - clearragdolls(); - - clearteaminfo(); - - // reset perma-state - loopv(players) - { - fpsent *d = players[i]; - d->frags = d->flags = 0; - d->deaths = 0; - d->totaldamage = 0; - d->totalshots = 0; - d->maxhealth = 100; - d->maxarmour = 50; - d->lifesequence = -1; - d->respawned = d->suicided = -2; - } - - intermission = false; - maptime = maprealtime = 0; - maplimit = -1; - - conoutf(CON_GAMEINFO, "\f2game mode is %s", server::modename(gamemode)); - - const char *info = m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL; - if(showmodeinfo && info) conoutf(CON_GAMEINFO, "\f0%s", info); - - showscores(false); - disablezoom(); - lasthit = 0; - - execident("mapstart"); - } - - void loadingmap(const char *name) - { - execident("playsong"); - } - - void startmap(const char *name) // called just after a map load - { - pwreset(); - ai::savewaypoints(); - ai::clearwaypoints(true); - - respawnent = -1; // so we don't respawn at an old spot - if(!m_mp(gamemode)) spawnplayer(player1); - else findplayerspawn(player1, -1); - entities::resetspawns(); - copystring(clientmap, name ? name : ""); - - sendmapinfo(); - } - - const char *getmapinfo() - { - return showmodeinfo && m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL; - } - - const char *getscreenshotinfo() - { - return server::modename(gamemode, NULL); - } - - void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material) - { - if(d->type==ENT_INANIMATE) return; - if (waterlevel>0) { if(material!=MAT_LAVA) playsound(S_SPLASH1, d==player1 ? NULL : &d->o); } - else if(waterlevel<0) playsound(material==MAT_LAVA ? S_BURN : S_SPLASH2, d==player1 ? NULL : &d->o); - if (floorlevel>0) { if(d==player1 || d->type!=ENT_PLAYER || ((fpsent *)d)->ai) msgsound(S_JUMP, d); } - else if(floorlevel<0) { if(d==player1 || d->type!=ENT_PLAYER || ((fpsent *)d)->ai) msgsound(S_LAND, d); } - } - - void dynentcollide(physent *d, physent *o, const vec &dir) - { - return; - } - - void msgsound(int n, physent *d) - { - if(!d || d==player1) - { - addmsg(N_SOUND, "ci", d, n); - playsound(n); - } - else - { - if(d->type==ENT_PLAYER && ((fpsent *)d)->ai) - addmsg(N_SOUND, "ci", d, n); - playsound(n, &d->o); - } - } - - int numdynents() { return players.length(); } - - dynent *iterdynents(int i) - { - if(iname; - if(alt && d != player1 && !strcmp(name, alt)) return true; - loopv(players) if(d!=players[i] && !strcmp(name, players[i]->name)) return true; - return false; - } - - static string cname[3]; - static int cidx = 0; - - const char *colorname(fpsent *d, const char *name, const char *prefix, const char *suffix, const char *alt) - { - if(!name) name = alt && d == player1 ? alt : d->name; - bool dup = !name[0] || duplicatename(d, name, alt) || d->aitype != AI_NONE; - if(dup || prefix[0] || suffix[0]) - { - cidx = (cidx+1)%3; - if(dup) formatstring(cname[cidx], d->aitype == AI_NONE ? "%s%s \fs\f5(%d)\fr%s" : "%s%s \fs\f5[%d]\fr%s", prefix, name, d->clientnum, suffix); - else formatstring(cname[cidx], "%s%s%s", prefix, name, suffix); - return cname[cidx]; - } - return name; - } - - VARP(teamcolortext, 0, 1, 1); - - const char *teamcolorname(fpsent *d, const char *alt) - { - if(!teamcolortext || !m_teammode || d->state==CS_SPECTATOR) return colorname(d, NULL, "", "", alt); - return colorname(d, NULL, isteam(d->team, player1->team) ? "\fs\f1" : "\fs\f3", "\fr", alt); - } - - const char *teamcolor(const char *name, bool sameteam, const char *alt) - { - if(!teamcolortext || !m_teammode) return sameteam || !alt ? name : alt; - cidx = (cidx+1)%3; - formatstring(cname[cidx], sameteam ? "\fs\f1%s\fr" : "\fs\f3%s\fr", sameteam || !alt ? name : alt); - return cname[cidx]; - } - - const char *teamcolor(const char *name, const char *team, const char *alt) - { - return teamcolor(name, team && isteam(team, player1->team), alt); - } - - VARP(teamsounds, 0, 1, 1); - - void teamsound(bool sameteam, int n, const vec *loc) - { - playsound(n, loc, NULL, teamsounds ? (m_teammode && sameteam ? SND_USE_ALT : SND_NO_ALT) : 0); - } - - void teamsound(fpsent *d, int n, const vec *loc) - { - teamsound(isteam(d->team, player1->team), n, loc); - } - - void suicide(physent *d) - { - if(d==player1 || (d->type==ENT_PLAYER && ((fpsent *)d)->ai)) - { - if(d->state!=CS_ALIVE) return; - fpsent *pl = (fpsent *)d; - if(!m_mp(gamemode)) killed(pl, pl); - else - { - int seq = (pl->lifesequence<<16)|((lastmillis/1000)&0xFFFF); - if(pl->suicided!=seq) { addmsg(N_SUICIDE, "rc", pl); pl->suicided = seq; } - } - } - } - ICOMMAND(suicide, "", (), suicide(player1)); - - void drawicon(int icon, float x, float y, float sz) - { - settexture("packages/hud/items.png"); - float tsz = 0.25f, tx = tsz*(icon%4), ty = tsz*(icon/4); - gle::defvertex(2); - gle::deftexcoord0(); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x, y); gle::attribf(tx, ty); - gle::attribf(x+sz, y); gle::attribf(tx+tsz, ty); - gle::attribf(x, y+sz); gle::attribf(tx, ty+tsz); - gle::attribf(x+sz, y+sz); gle::attribf(tx+tsz, ty+tsz); - gle::end(); - } - - float abovegameplayhud(int w, int h) - { - switch(hudplayer()->state) - { - case CS_EDITING: - case CS_SPECTATOR: - return 1; - default: - return 1650.0f/1800.0f; - } - } - - int ammohudup[3] = { GUN_CG, GUN_RL, GUN_GL }, - ammohuddown[3] = { GUN_RIFLE, GUN_SG, GUN_PISTOL }, - ammohudcycle[7] = { -1, -1, -1, -1, -1, -1, -1 }; - - ICOMMAND(ammohudup, "V", (tagval *args, int numargs), - { - loopi(3) ammohudup[i] = i < numargs ? getweapon(args[i].getstr()) : -1; - }); - - ICOMMAND(ammohuddown, "V", (tagval *args, int numargs), - { - loopi(3) ammohuddown[i] = i < numargs ? getweapon(args[i].getstr()) : -1; - }); - - ICOMMAND(ammohudcycle, "V", (tagval *args, int numargs), - { - loopi(7) ammohudcycle[i] = i < numargs ? getweapon(args[i].getstr()) : -1; - }); - - VARP(ammohud, 0, 1, 1); - - void drawammohud(fpsent *d) - { - float x = HICON_X + 2*HICON_STEP, y = HICON_Y, sz = HICON_SIZE; - pushhudmatrix(); - hudmatrix.scale(1/3.2f, 1/3.2f, 1); - flushhudmatrix(); - float xup = (x+sz)*3.2f, yup = y*3.2f + 0.1f*sz; - loopi(3) - { - int gun = ammohudup[i]; - if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue; - drawicon(HICON_FIST+gun, xup, yup, sz); - yup += sz; - } - float xdown = x*3.2f - sz, ydown = (y+sz)*3.2f - 0.1f*sz; - loopi(3) - { - int gun = ammohuddown[3-i-1]; - if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue; - ydown -= sz; - drawicon(HICON_FIST+gun, xdown, ydown, sz); - } - int offset = 0, num = 0; - loopi(7) - { - int gun = ammohudcycle[i]; - if(gun < GUN_FIST || gun > GUN_PISTOL) continue; - if(gun == d->gunselect) offset = i + 1; - else if(d->ammo[gun]) num++; - } - float xcycle = (x+sz/2)*3.2f + 0.5f*num*sz, ycycle = y*3.2f-sz; - loopi(7) - { - int gun = ammohudcycle[(i + offset)%7]; - if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue; - xcycle -= sz; - drawicon(HICON_FIST+gun, xcycle, ycycle, sz); - } - pophudmatrix(); - } - - void hudquad(float x, float y, float w, float h, float r = 1, float g = 1, float b = 1, float tx = 0, float ty = 0, float tw = 1, float th = 1) - { - gle::defvertex(2); - gle::deftexcoord0(); - gle::colorf(r, g, b); - gle::begin(GL_TRIANGLE_STRIP); - gle::attribf(x, y); gle::attribf(tx, ty); - gle::attribf(x+w, y); gle::attribf(tx + tw, ty); - gle::attribf(x, y+h); gle::attribf(tx, ty + th); - gle::attribf(x+w, y+h); gle::attribf(tx + tw, ty + th); - gle::end(); - } - - VARP(healthcolors, 0, 1, 1); - - VARP(hudhealth, 0, 1, 1); - FVARP(hudhealthx, 0, 0, 1); - FVARP(hudhealthy, 0, 1, 1); - FVARP(hudhealthscale, 0.1, 1.0, 1.0); - - void drawhudhealth(fpsent *d, int w, int h) - { - pushhudmatrix(); - hudmatrix.scale(hudhealthscale, hudhealthscale, 1); - flushhudmatrix(); - - bvec healthcolor = bvec::hexcolor(healthcolors && !m_insta ? - (d->state==CS_DEAD ? 0x808080 : - (d->health<=25 ? 0xc02020 : - (d->health<=50 ? 0xc08020 : - (d->health<=75 ? 0xc0c040 : - (d->health<=100 ? 0xFFFFFF : 0x4080c0))))) : 0xFFFFFF); - const float proportion = (w/4.0f)/600.0f; - const float healthbarw = 600*proportion; - const float healthbarh = 113*proportion; - vec2 offset = vec2(hudhealthx, hudhealthy).mul(vec2(w-healthbarw, h-healthbarh).div(hudhealthscale)); - settexture("packages/hud/health_bar_base.png"); - float hp = (float)d->health/d->maxhealth; - hudquad(offset.x, offset.y, hp*healthbarw, healthbarh, healthcolor.r, healthcolor.g, healthcolor.b, 0, 0, hp, 1); - settexture("packages/hud/health_bar_over.png"); - hudquad(offset.x, offset.y, healthbarw, healthbarh); - if (d->quadmillis) { - settexture("packages/hud/health_bar_quad.png"); - hudquad(offset.x, offset.y, healthbarw, healthbarh); - } - defformatstring(health, "%d", d->state==CS_DEAD ? 0 : d->health); - float tw=0, th=0; text_boundsf(health, tw, th); - draw_text(health, offset.x+(125*proportion-tw)/2, offset.y+(healthbarh-th)/2, healthcolor.r, healthcolor.g, healthcolor.b); - - pophudmatrix(); - } - - VARP(hudmaxhealth, 0, 1, 1); - FVARP(hudmaxhealthx, 0, 0.207, 1); - FVARP(hudmaxhealthy, 0, 0.97, 1); - FVARP(hudmaxhealthscale, 0.1, 1.0, 1.0); - - void drawhudmaxhealth(fpsent *d, int w, int h) - { - pushhudmatrix(); - hudmatrix.scale(hudmaxhealthscale, hudmaxhealthscale, 1); - flushhudmatrix(); - - const float proportion = (w/15.0f)/160.0f; - const float healthboostw = 160*proportion; - const float healthboosth = 78*proportion; - float hb = (float)d->maxhealth/100.0f-1.0f; - vec2 offset = vec2(hudmaxhealthx, hudmaxhealthy).mul(vec2(w-healthboostw, h-healthboosth).div(hudhealthscale)); - settexture("packages/hud/health_boost_base.png"); - hudquad(offset.x, offset.y, hb*healthboostw, healthboosth, 0.3f, 0.6f, 0.9f, 0, 0, hb, 1); - settexture("packages/hud/health_boost_over.png"); - hudquad(offset.x, offset.y, healthboostw, healthboosth); - if (d->quadmillis) { - settexture("packages/hud/health_boost_quad.png"); - hudquad(offset.x, offset.y, healthboostw, healthboosth); - } - - pophudmatrix(); - } - - VARP(armourcolors, 0, 1, 1); - - VARP(hudarmour, 0, 1, 1); - FVARP(hudarmourx, 0, 1, 1); - FVARP(hudarmoury, 0, 1, 1); - FVARP(hudarmourscale, 0.1, 1.0, 1.0); - - void drawhudarmour(fpsent *d, int w, int h) - { - pushhudmatrix(); - hudmatrix.scale(hudarmourscale, hudarmourscale, 1); - flushhudmatrix(); - - bvec armourcolor = bvec::hexcolor(d->armourtype == A_BLUE ? 0x83ade5 : (d->armourtype == A_GREEN ? 0x77f29e : (d->armourtype == A_YELLOW ? 0xf5f19b : 0xffffff))); - const float proportion = (w/4.0f)/600.0f; - const float armourbarw = 600*proportion; - const float armourbarh = 113*proportion; - vec2 offset = vec2(hudarmourx, hudarmoury).mul(vec2(w-armourbarw, h-armourbarh).div(hudarmourscale)); - settexture("packages/hud/armour_bar_base.png"); - float ap = (float)d->armour/d->maxarmour; - hudquad(offset.x, offset.y, ap*armourbarw, armourbarh, armourcolor.r, armourcolor.g, armourcolor.b, 0, 0, ap, 1); - settexture("packages/hud/armour_bar_over.png"); - hudquad(offset.x, offset.y, armourbarw, armourbarh); - if (d->quadmillis) { - settexture("packages/hud/armour_bar_quad.png"); - hudquad(offset.x, offset.y, armourbarw, armourbarh); - } - defformatstring(armour, "%d", d->state==CS_DEAD ? 0 : d->armour); - float tw=0, th=0; text_boundsf(armour, tw, th); - draw_text(armour, offset.x+(2*(600-63)*proportion-tw)/2, offset.y+(armourbarh-th)/2, armourcolor.r, armourcolor.g, armourcolor.b); - - pophudmatrix(); - } - - void drawhudicons(fpsent *d, int w, int h) - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - - if(hudhealth) drawhudhealth(d, w, h); - if(hudmaxhealth) drawhudmaxhealth(d, w, h); - if(hudarmour) drawhudarmour(d, w, h); - - if(d->state!=CS_DEAD) { - drawicon(HICON_FIST+d->gunselect, HICON_X + 2*HICON_STEP, HICON_Y); - if(ammohud) drawammohud(d); - } - } - - VARP(gameclock, 0, 0, 1); - FVARP(gameclockscale, 1e-3f, 0.75f, 1e3f); - HVARP(gameclockcolour, 0, 0xFFFFFF, 0xFFFFFF); - VARP(gameclockalpha, 0, 255, 255); - HVARP(gameclocklowcolour, 0, 0xFFC040, 0xFFFFFF); - VARP(gameclockalign, -1, 0, 1); - FVARP(gameclockx, 0, 0.50f, 1); - FVARP(gameclocky, 0, 0.03f, 1); - - void drawgameclock(int w, int h) - { - int secs = max(maplimit-lastmillis + 999, 0)/1000, mins = secs/60; - secs %= 60; - - defformatstring(buf, "%d:%02d", mins, secs); - int tw = 0, th = 0; - text_bounds(buf, tw, th); - - vec2 offset = vec2(gameclockx, gameclocky).mul(vec2(w, h).div(gameclockscale)); - if(gameclockalign == 1) offset.x -= tw; - else if(gameclockalign == 0) offset.x -= tw/2.0f; - offset.y -= th/2.0f; - - pushhudmatrix(); - hudmatrix.scale(gameclockscale, gameclockscale, 1); - flushhudmatrix(); - - int color = mins < 1 ? gameclocklowcolour : gameclockcolour; - draw_text(buf, int(offset.x), int(offset.y), (color>>16)&0xFF, (color>>8)&0xFF, color&0xFF, gameclockalpha); - - pophudmatrix(); - } - - extern int hudscore; - extern void drawhudscore(int w, int h); - - VARP(ammobar, 0, 0, 1); - VARP(ammobaralign, -1, 0, 1); - VARP(ammobarhorizontal, 0, 0, 1); - VARP(ammobarflip, 0, 0, 1); - VARP(ammobarhideempty, 0, 1, 1); - VARP(ammobarsep, 0, 20, 500); - VARP(ammobarcountsep, 0, 20, 500); - FVARP(ammobarcountscale, 0.5, 1.5, 2); - FVARP(ammobarx, 0, 0.025f, 1.0f); - FVARP(ammobary, 0, 0.5f, 1.0f); - FVARP(ammobarscale, 0.1f, 0.5f, 1.0f); - - void drawammobarcounter(const vec2 ¢er, const fpsent *p, int gun) - { - vec2 icondrawpos = vec2(center).sub(HICON_SIZE / 2); - int alpha = p->ammo[gun] ? 0xFF : 0x7F; - gle::color(bvec(0xFF, 0xFF, 0xFF), alpha); - drawicon(HICON_FIST + gun, icondrawpos.x, icondrawpos.y); - - int fw, fh; text_bounds("000", fw, fh); - float labeloffset = HICON_SIZE / 2.0f + ammobarcountsep + ammobarcountscale * (ammobarhorizontal ? fh : fw) / 2.0f; - vec2 offsetdir = (ammobarhorizontal ? vec2(0, 1) : vec2(1, 0)).mul(ammobarflip ? -1 : 1); - vec2 labelorigin = vec2(offsetdir).mul(labeloffset).add(center); - - pushhudmatrix(); - hudmatrix.translate(labelorigin.x, labelorigin.y, 0); - hudmatrix.scale(ammobarcountscale, ammobarcountscale, 1); - flushhudmatrix(); - - defformatstring(label, "%d", p->ammo[gun]); - int tw, th; text_bounds(label, tw, th); - vec2 textdrawpos = vec2(-tw, -th).div(2); - float ammoratio = (float)p->ammo[gun] / itemstats[gun-GUN_SG].add; - bvec color = bvec::hexcolor(p->ammo[gun] == 0 || ammoratio >= 1.0f ? 0xFFFFFF : (ammoratio >= 0.5f ? 0xFFC040 : 0xFF0000)); - draw_text(label, textdrawpos.x, textdrawpos.y, color.r, color.g, color.b, alpha); - - pophudmatrix(); - } - - static inline bool ammobargunvisible(const fpsent *d, int gun) - { - return d->ammo[gun] > 0 || d->gunselect == gun; - } - - void drawammobar(int w, int h, fpsent *p) - { - if(m_insta) return; - - int NUMPLAYERGUNS = GUN_PISTOL - GUN_SG + 1; - int numvisibleguns = NUMPLAYERGUNS; - if(ammobarhideempty) loopi(NUMPLAYERGUNS) if(!ammobargunvisible(p, GUN_SG + i)) numvisibleguns--; - - vec2 origin = vec2(ammobarx, ammobary).mul(vec2(w, h).div(ammobarscale)); - vec2 offsetdir = ammobarhorizontal ? vec2(1, 0) : vec2(0, 1); - float stepsize = HICON_SIZE + ammobarsep; - float initialoffset = (ammobaralign - 1) * (numvisibleguns - 1) * stepsize / 2; - - pushhudmatrix(); - hudmatrix.scale(ammobarscale, ammobarscale, 1); - flushhudmatrix(); - - int numskippedguns = 0; - loopi(NUMPLAYERGUNS) if(ammobargunvisible(p, GUN_SG + i) || !ammobarhideempty) - { - float offset = initialoffset + (i - numskippedguns) * stepsize; - vec2 drawpos = vec2(offsetdir).mul(offset).add(origin); - drawammobarcounter(drawpos, p, GUN_SG + i); - } - else numskippedguns++; - - pophudmatrix(); - } - - VARP(speedometer, 0, 1, 1); - FVARP(speedometerx, 0.0, 0.5, 1.0); - FVARP(speedometery, 0.0, 0.6, 1.0); - FVARP(speedometerscale, 0.1, 0.5, 1.0); - VARP(speedometercolor, 0, 1, 1); - FVARP(speedometeralpha, 0.0, 0.5, 1.0); - - void drawspeedometer(fpsent *d, int w, int h) - { - int speedforreal = (int) (sqrtf(d->vel.squaredlen()) + 1.0f); - speedforreal = (speedforreal == 1) ? 0 : speedforreal; - vec colour = vec(255, 255, 255); - float realw = 0; - float realh = 0; - if (speedometercolor) { - if (speedforreal==0) colour = vec(60, 60, 60); - else if (speedforreal>0 && speedforreal<=60) colour = vec(240, 30, 30); - else if (speedforreal>60 && speedforreal<=120) colour = vec(180, 90, 60); - else if (speedforreal>120 && speedforreal<=180) colour = vec(180, 180, 30); - else if (speedforreal>180 && speedforreal<=240) colour = vec(90, 180, 60); - else colour = vec(30, 240, 30); - } - - defformatstring(speedstring, "%d", speedforreal); - text_boundsf(speedstring, realw, realh); - vec2 offset = vec2(speedometerx, speedometery).mul(vec2(w, h).div(speedometerscale)); - offset.x -= realw/2.0f; - offset.y -= realh/2.0f; - - pushhudmatrix(); - hudmatrix.scale(speedometerscale, speedometerscale, 1); - flushhudmatrix(); - - const int speedow = 220; - const int speedoh = 101; - settexture("packages/hud/speedometer.png"); - hudquad(offset.x+realw/2.0f-speedow/2, offset.y+realh/2.0f-speedoh/2, speedow, speedoh); - - draw_text(speedstring, int(offset.x), int(offset.y), colour.x, colour.y, colour.z, (int)(speedometeralpha*255.0f)); - - pophudmatrix(); - } - - void gameplayhud(int w, int h) - { - pushhudmatrix(); - hudmatrix.scale(h/1800.0f, h/1800.0f, 1); - flushhudmatrix(); - - if(player1->state==CS_SPECTATOR) - { - int pw, ph, tw, th, fw, fh; - text_bounds(" ", pw, ph); - text_bounds("SPECTATOR", tw, th); - th = max(th, ph); - fpsent *f = followingplayer(); - text_bounds(f ? colorname(f) : " ", fw, fh); - fh = max(fh, ph); - draw_text("SPECTATOR", w*1800/h - tw - pw, 1650 - th - fh); - if(f) - { - int color = statuscolor(f, 0xFFFFFF); - draw_text(colorname(f), w*1800/h - fw - pw, 1650 - fh, (color>>16)&0xFF, (color>>8)&0xFF, color&0xFF); - } - } - - fpsent *d = hudplayer(); - - pophudmatrix(); - - if(d->state!=CS_EDITING && d->state!=CS_SPECTATOR) drawhudicons(d, w, h); - - if(d->state!=CS_EDITING && d->state!=CS_SPECTATOR && d->state!=CS_DEAD) - { - if(ammobar) drawammobar(w, h, d); - if(speedometer) drawspeedometer(d, w, h); - } - - - if(!m_edit) - { - if(gameclock) drawgameclock(w, h); - if(hudscore) drawhudscore(w, h); - } - - /// Frag message. - - if((hudfragmillis+fragmessageduration > lastmillis) && (lastmillis>fragmessageduration)) - { - vec2 center = vec2(0.5*w, 0.2*h); - float width = 0, height = 0; - pushhudmatrix(); - hudmatrix.scale(0.8, 0.8, 1); - flushhudmatrix(); - draw_textf("%s", center.x, center.y, hudfragger); - text_boundsf(hudfragger, width, height); - drawicon(HICON_FIST+hudfraggun, center.x+width, center.y, height); - draw_textf("%s", center.x+width+height, center.y, hudfragged); - pophudmatrix(); - } - } - - int clipconsole(int w, int h) - { - return 0; - } - - VARP(teamcrosshair, 0, 1, 1); - VARP(hitcrosshair, 0, 425, 1000); - - const char *defaultcrosshair(int index) - { - switch(index) - { - case 2: return "data/hit.png"; - case 1: return "data/teammate.png"; - default: return "data/crosshair.png"; - } - } - - int selectcrosshair(vec &color) - { - fpsent *d = hudplayer(); - if(d->state==CS_SPECTATOR || d->state==CS_DEAD) return -1; - - if(d->state!=CS_ALIVE) return 0; - - int crosshair = 0; - if(lasthit && lastmillis - lasthit < hitcrosshair) crosshair = 2; - else if(teamcrosshair) - { - dynent *o = intersectclosest(d->o, worldpos, d); - if(o && o->type==ENT_PLAYER && isteam(((fpsent *)o)->team, d->team)) - { - crosshair = 1; - color = vec(0, 0, 1); - } - } - - if(crosshair!=1 && !editmode && !m_insta) - { - if(d->health<=25) color = vec(1, 0, 0); - else if(d->health<=50) color = vec(1, 0.5f, 0); - } - if(d->gunwait) color.mul(0.5f); - return crosshair; - } - - void lighteffects(dynent *e, vec &color, vec &dir) - { + string hudfragger, hudfragged; + int hudfraggun, hudfragmillis; + + void sethudfragdata(char *fragger, char *fragged, int gunid) + { + copystring(hudfragger, fragger ? fragger : ""); + copystring(hudfragged, fragged); + hudfraggun = gunid; + hudfragmillis = lastmillis; + } + + void killed(fpsent *d, fpsent *actor) + { + if(d->state==CS_EDITING) + { + d->editstate = CS_DEAD; + d->deaths++; + if(d!=player1) d->resetinterp(); + return; + } + else if((d->state!=CS_ALIVE && d->state != CS_LAGGED && d->state != CS_SPAWNING) || intermission) return; + + fpsent *h = followingplayer(player1); + int contype = d==h || actor==h ? CON_FRAG_SELF : CON_FRAG_OTHER; + const char *dname = "", *aname = ""; + if(m_teammode && teamcolorfrags) + { + dname = teamcolorname(d, "you"); + aname = teamcolorname(actor, "you"); + } + else + { + dname = colorname(d, NULL, "", "", "you"); + aname = colorname(actor, NULL, "", "", "you"); + } + if(actor->type==ENT_AI) + conoutf(contype, "\f2%s got killed by %s!", dname, aname); + else if(d==actor || actor->type==ENT_INANIMATE) + conoutf(contype, "\f2%s suicided%s", dname, d==player1 ? "!" : ""); + else if(isteam(d->team, actor->team)) + { + contype |= CON_TEAMKILL; + if(actor==player1) conoutf(contype, "\f6%s fragged a teammate (%s)", aname, dname); + else if(d==player1) conoutf(contype, "\f6%s got fragged by a teammate (%s)", dname, aname); + else conoutf(contype, "\f2%s fragged a teammate (%s)", aname, dname); + } + else + { + if(d==player1) + { + conoutf(contype, "\f2%s got fragged by %s", dname, aname); + sethudfragdata(actor->name, d->name, actor->gunselect); + } + else + { + conoutf(contype, "\f2%s fragged %s", aname, dname); + sethudfragdata(actor->name, d->name, actor->gunselect); + } + } + deathstate(d); + ai::killed(d, actor); + } + + void timeupdate(int secs) + { + server::timeupdate(secs); + if(secs > 0) + { + maplimit = lastmillis + secs*1000; + } + else + { + intermission = true; + player1->attacking = false; + conoutf(CON_GAMEINFO, "\f2intermission:"); + conoutf(CON_GAMEINFO, "\f2game has ended!"); + conoutf(CON_GAMEINFO, "\f2player frags: %d, deaths: %d", player1->frags, player1->deaths); + int accuracy = (player1->totaldamage*100)/max(player1->totalshots, 1); + conoutf(CON_GAMEINFO, "\f2player total damage dealt: %d, damage wasted: %d, accuracy(%%): %d", player1->totaldamage, player1->totalshots-player1->totaldamage, accuracy); + + showscores(true); + disablezoom(); + + execident("intermission"); + } + } + + ICOMMAND(getfrags, "", (), intret(player1->frags)); + ICOMMAND(getflags, "", (), intret(player1->flags)); + ICOMMAND(getdeaths, "", (), intret(player1->deaths)); + ICOMMAND(getaccuracy, "", (), intret((player1->totaldamage*100)/max(player1->totalshots, 1))); + ICOMMAND(gettotaldamage, "", (), intret(player1->totaldamage)); + ICOMMAND(gettotalshots, "", (), intret(player1->totalshots)); + + vector clients; + + fpsent *newclient(int cn) // ensure valid entity + { + if(cn < 0 || cn > max(0xFF, MAXCLIENTS + MAXBOTS)) + { + neterr("clientnum", false); + return NULL; + } + + if(cn == player1->clientnum) return player1; + + while(cn >= clients.length()) clients.add(NULL); + if(!clients[cn]) + { + fpsent *d = new fpsent; + d->clientnum = cn; + clients[cn] = d; + players.add(d); + } + return clients[cn]; + } + + fpsent *getclient(int cn) // ensure valid entity + { + if(cn == player1->clientnum) return player1; + return clients.inrange(cn) ? clients[cn] : NULL; + } + + void clientdisconnected(int cn, bool notify) + { + if(!clients.inrange(cn)) return; + if(following==cn) + { + if(followdir) nextfollow(followdir); + else stopfollowing(); + } + unignore(cn); + fpsent *d = clients[cn]; + if(!d) return; + if(notify && d->name[0]) conoutf("\f4leave:\f7 %s", colorname(d)); + removeweapons(d); + removetrackedparticles(d); + removetrackeddynlights(d); + players.removeobj(d); + DELETEP(clients[cn]); + cleardynentcache(); + } + + void clearclients(bool notify) + { + loopv(clients) if(clients[i]) clientdisconnected(i, notify); + } + + void initclient() + { + player1 = spawnstate(new fpsent); + filtertext(player1->name, "Anonymous", false, false, MAXNAMELEN); + players.add(player1); + } + + VARP(showmodeinfo, 0, 1, 1); + + void startgame() + { + clearprojectiles(); + clearbouncers(); + clearragdolls(); + + clearteaminfo(); + + // reset perma-state + loopv(players) + { + fpsent *d = players[i]; + d->frags = d->flags = 0; + d->deaths = 0; + d->totaldamage = 0; + d->totalshots = 0; + d->maxhealth = 100; + d->maxarmour = 50; + d->lifesequence = -1; + d->respawned = d->suicided = -2; + } + + intermission = false; + maptime = maprealtime = 0; + maplimit = -1; + + conoutf(CON_GAMEINFO, "\f2game mode is %s", server::modename(gamemode)); + + const char *info = m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL; + if(showmodeinfo && info) conoutf(CON_GAMEINFO, "\f0%s", info); + + showscores(false); + disablezoom(); + lasthit = 0; + + execident("mapstart"); + } + + void loadingmap(const char *name) + { + execident("playsong"); + } + + void startmap(const char *name) // called just after a map load + { + pwreset(); + ai::savewaypoints(); + ai::clearwaypoints(true); + + respawnent = -1; // so we don't respawn at an old spot + if(!m_mp(gamemode)) spawnplayer(player1); + else findplayerspawn(player1, -1); + entities::resetspawns(); + copystring(clientmap, name ? name : ""); + + sendmapinfo(); + } + + const char *getmapinfo() + { + return showmodeinfo && m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL; + } + + const char *getscreenshotinfo() + { + return server::modename(gamemode, NULL); + } + + void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material) + { + if(d->type==ENT_INANIMATE) return; + if (waterlevel>0) { if(material!=MAT_LAVA) playsound(S_SPLASH1, d==player1 ? NULL : &d->o); } + else if(waterlevel<0) playsound(material==MAT_LAVA ? S_BURN : S_SPLASH2, d==player1 ? NULL : &d->o); + if (floorlevel>0) { if(d==player1 || d->type!=ENT_PLAYER || ((fpsent *)d)->ai) msgsound(S_JUMP, d); } + else if(floorlevel<0) { if(d==player1 || d->type!=ENT_PLAYER || ((fpsent *)d)->ai) msgsound(S_LAND, d); } + } + + void dynentcollide(physent *d, physent *o, const vec &dir) + { + return; + } + + void msgsound(int n, physent *d) + { + if(!d || d==player1) + { + addmsg(N_SOUND, "ci", d, n); + playsound(n); + } + else + { + if(d->type==ENT_PLAYER && ((fpsent *)d)->ai) + addmsg(N_SOUND, "ci", d, n); + playsound(n, &d->o); + } + } + + int numdynents() { return players.length(); } + + dynent *iterdynents(int i) + { + if(iname; + if(alt && d != player1 && !strcmp(name, alt)) return true; + loopv(players) if(d!=players[i] && !strcmp(name, players[i]->name)) return true; + return false; + } + + static string cname[3]; + static int cidx = 0; + + const char *colorname(fpsent *d, const char *name, const char *prefix, const char *suffix, const char *alt) + { + if(!name) name = alt && d == player1 ? alt : d->name; + bool dup = !name[0] || duplicatename(d, name, alt) || d->aitype != AI_NONE; + if(dup || prefix[0] || suffix[0]) + { + cidx = (cidx+1)%3; + if(dup) formatstring(cname[cidx], d->aitype == AI_NONE ? "%s%s \fs\f5(%d)\fr%s" : "%s%s \fs\f5[%d]\fr%s", prefix, name, d->clientnum, suffix); + else formatstring(cname[cidx], "%s%s%s", prefix, name, suffix); + return cname[cidx]; + } + return name; + } + + VARP(teamcolortext, 0, 1, 1); + + const char *teamcolorname(fpsent *d, const char *alt) + { + if(!teamcolortext || !m_teammode || d->state==CS_SPECTATOR) return colorname(d, NULL, "", "", alt); + return colorname(d, NULL, isteam(d->team, player1->team) ? "\fs\f1" : "\fs\f3", "\fr", alt); + } + + const char *teamcolor(const char *name, bool sameteam, const char *alt) + { + if(!teamcolortext || !m_teammode) return sameteam || !alt ? name : alt; + cidx = (cidx+1)%3; + formatstring(cname[cidx], sameteam ? "\fs\f1%s\fr" : "\fs\f3%s\fr", sameteam || !alt ? name : alt); + return cname[cidx]; + } + + const char *teamcolor(const char *name, const char *team, const char *alt) + { + return teamcolor(name, team && isteam(team, player1->team), alt); + } + + VARP(teamsounds, 0, 1, 1); + + void teamsound(bool sameteam, int n, const vec *loc) + { + playsound(n, loc, NULL, teamsounds ? (m_teammode && sameteam ? SND_USE_ALT : SND_NO_ALT) : 0); + } + + void teamsound(fpsent *d, int n, const vec *loc) + { + teamsound(isteam(d->team, player1->team), n, loc); + } + + void suicide(physent *d) + { + if(d==player1 || (d->type==ENT_PLAYER && ((fpsent *)d)->ai)) + { + if(d->state!=CS_ALIVE) return; + fpsent *pl = (fpsent *)d; + if(!m_mp(gamemode)) killed(pl, pl); + else + { + int seq = (pl->lifesequence<<16)|((lastmillis/1000)&0xFFFF); + if(pl->suicided!=seq) { addmsg(N_SUICIDE, "rc", pl); pl->suicided = seq; } + } + } + } + ICOMMAND(suicide, "", (), suicide(player1)); + + void drawicon(int icon, float x, float y, float sz) + { + settexture("packages/hud/items.png"); + float tsz = 0.25f, tx = tsz*(icon%4), ty = tsz*(icon/4); + gle::defvertex(2); + gle::deftexcoord0(); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x, y); gle::attribf(tx, ty); + gle::attribf(x+sz, y); gle::attribf(tx+tsz, ty); + gle::attribf(x, y+sz); gle::attribf(tx, ty+tsz); + gle::attribf(x+sz, y+sz); gle::attribf(tx+tsz, ty+tsz); + gle::end(); + } + + float abovegameplayhud(int w, int h) + { + switch(hudplayer()->state) + { + case CS_EDITING: + case CS_SPECTATOR: + return 1; + default: + return 1650.0f/1800.0f; + } + } + + int ammohudup[3] = { GUN_CG, GUN_RL, GUN_GL }, + ammohuddown[3] = { GUN_RIFLE, GUN_SG, GUN_PISTOL }, + ammohudcycle[7] = { -1, -1, -1, -1, -1, -1, -1 }; + + ICOMMAND(ammohudup, "V", (tagval *args, int numargs), + { + loopi(3) ammohudup[i] = i < numargs ? getweapon(args[i].getstr()) : -1; + }); + + ICOMMAND(ammohuddown, "V", (tagval *args, int numargs), + { + loopi(3) ammohuddown[i] = i < numargs ? getweapon(args[i].getstr()) : -1; + }); + + ICOMMAND(ammohudcycle, "V", (tagval *args, int numargs), + { + loopi(7) ammohudcycle[i] = i < numargs ? getweapon(args[i].getstr()) : -1; + }); + + VARP(ammohud, 0, 1, 1); + + void drawammohud(fpsent *d) + { + float x = HICON_X + 2*HICON_STEP, y = HICON_Y, sz = HICON_SIZE; + pushhudmatrix(); + hudmatrix.scale(1/3.2f, 1/3.2f, 1); + flushhudmatrix(); + float xup = (x+sz)*3.2f, yup = y*3.2f + 0.1f*sz; + loopi(3) + { + int gun = ammohudup[i]; + if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue; + drawicon(HICON_FIST+gun, xup, yup, sz); + yup += sz; + } + float xdown = x*3.2f - sz, ydown = (y+sz)*3.2f - 0.1f*sz; + loopi(3) + { + int gun = ammohuddown[3-i-1]; + if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue; + ydown -= sz; + drawicon(HICON_FIST+gun, xdown, ydown, sz); + } + int offset = 0, num = 0; + loopi(7) + { + int gun = ammohudcycle[i]; + if(gun < GUN_FIST || gun > GUN_PISTOL) continue; + if(gun == d->gunselect) offset = i + 1; + else if(d->ammo[gun]) num++; + } + float xcycle = (x+sz/2)*3.2f + 0.5f*num*sz, ycycle = y*3.2f-sz; + loopi(7) + { + int gun = ammohudcycle[(i + offset)%7]; + if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue; + xcycle -= sz; + drawicon(HICON_FIST+gun, xcycle, ycycle, sz); + } + pophudmatrix(); + } + + void hudquad(float x, float y, float w, float h, float r = 1, float g = 1, float b = 1, float tx = 0, float ty = 0, float tw = 1, float th = 1) + { + gle::defvertex(2); + gle::deftexcoord0(); + gle::colorf(r, g, b); + gle::begin(GL_TRIANGLE_STRIP); + gle::attribf(x, y); gle::attribf(tx, ty); + gle::attribf(x+w, y); gle::attribf(tx + tw, ty); + gle::attribf(x, y+h); gle::attribf(tx, ty + th); + gle::attribf(x+w, y+h); gle::attribf(tx + tw, ty + th); + gle::end(); + } + + VARP(healthcolors, 0, 1, 1); + + VARP(hudhealth, 0, 1, 1); + FVARP(hudhealthx, 0, 0, 1); + FVARP(hudhealthy, 0, 1, 1); + FVARP(hudhealthscale, 0.1, 1.0, 1.0); + + void drawhudhealth(fpsent *d, int w, int h) + { + pushhudmatrix(); + hudmatrix.scale(hudhealthscale, hudhealthscale, 1); + flushhudmatrix(); + + bvec healthcolor = bvec::hexcolor(healthcolors && !m_insta ? + (d->state==CS_DEAD ? 0x808080 : + (d->health<=25 ? 0xc02020 : + (d->health<=50 ? 0xc08020 : + (d->health<=75 ? 0xc0c040 : + (d->health<=100 ? 0xFFFFFF : 0x4080c0))))) : 0xFFFFFF); + const float proportion = (w/4.0f)/600.0f; + const float healthbarw = 600*proportion; + const float healthbarh = 113*proportion; + vec2 offset = vec2(hudhealthx, hudhealthy).mul(vec2(w-healthbarw, h-healthbarh).div(hudhealthscale)); + settexture("packages/hud/health_bar_base.png"); + float hp = (float)d->health/d->maxhealth; + hudquad(offset.x, offset.y, hp*healthbarw, healthbarh, healthcolor.r, healthcolor.g, healthcolor.b, 0, 0, hp, 1); + settexture("packages/hud/health_bar_over.png"); + hudquad(offset.x, offset.y, healthbarw, healthbarh); + if (d->quadmillis) { + settexture("packages/hud/health_bar_quad.png"); + hudquad(offset.x, offset.y, healthbarw, healthbarh); + } + defformatstring(health, "%d", d->state==CS_DEAD ? 0 : d->health); + float tw=0, th=0; text_boundsf(health, tw, th); + draw_text(health, offset.x+(125*proportion-tw)/2, offset.y+(healthbarh-th)/2, healthcolor.r, healthcolor.g, healthcolor.b); + + pophudmatrix(); + } + + VARP(hudmaxhealth, 0, 1, 1); + FVARP(hudmaxhealthx, 0, 0.207, 1); + FVARP(hudmaxhealthy, 0, 0.97, 1); + FVARP(hudmaxhealthscale, 0.1, 1.0, 1.0); + + void drawhudmaxhealth(fpsent *d, int w, int h) + { + pushhudmatrix(); + hudmatrix.scale(hudmaxhealthscale, hudmaxhealthscale, 1); + flushhudmatrix(); + + const float proportion = (w/15.0f)/160.0f; + const float healthboostw = 160*proportion; + const float healthboosth = 78*proportion; + float hb = (float)d->maxhealth/100.0f-1.0f; + vec2 offset = vec2(hudmaxhealthx, hudmaxhealthy).mul(vec2(w-healthboostw, h-healthboosth).div(hudhealthscale)); + settexture("packages/hud/health_boost_base.png"); + hudquad(offset.x, offset.y, hb*healthboostw, healthboosth, 0.3f, 0.6f, 0.9f, 0, 0, hb, 1); + settexture("packages/hud/health_boost_over.png"); + hudquad(offset.x, offset.y, healthboostw, healthboosth); + if (d->quadmillis) { + settexture("packages/hud/health_boost_quad.png"); + hudquad(offset.x, offset.y, healthboostw, healthboosth); + } + + pophudmatrix(); + } + + VARP(armourcolors, 0, 1, 1); + + VARP(hudarmour, 0, 1, 1); + FVARP(hudarmourx, 0, 1, 1); + FVARP(hudarmoury, 0, 1, 1); + FVARP(hudarmourscale, 0.1, 1.0, 1.0); + + void drawhudarmour(fpsent *d, int w, int h) + { + pushhudmatrix(); + hudmatrix.scale(hudarmourscale, hudarmourscale, 1); + flushhudmatrix(); + + bvec armourcolor = bvec::hexcolor(d->armourtype == A_BLUE ? 0x83ade5 : (d->armourtype == A_GREEN ? 0x77f29e : (d->armourtype == A_YELLOW ? 0xf5f19b : 0xffffff))); + const float proportion = (w/4.0f)/600.0f; + const float armourbarw = 600*proportion; + const float armourbarh = 113*proportion; + vec2 offset = vec2(hudarmourx, hudarmoury).mul(vec2(w-armourbarw, h-armourbarh).div(hudarmourscale)); + settexture("packages/hud/armour_bar_base.png"); + float ap = (float)d->armour/d->maxarmour; + hudquad(offset.x, offset.y, ap*armourbarw, armourbarh, armourcolor.r, armourcolor.g, armourcolor.b, 0, 0, ap, 1); + settexture("packages/hud/armour_bar_over.png"); + hudquad(offset.x, offset.y, armourbarw, armourbarh); + if (d->quadmillis) { + settexture("packages/hud/armour_bar_quad.png"); + hudquad(offset.x, offset.y, armourbarw, armourbarh); + } + defformatstring(armour, "%d", d->state==CS_DEAD ? 0 : d->armour); + float tw=0, th=0; text_boundsf(armour, tw, th); + draw_text(armour, offset.x+(2*(600-63)*proportion-tw)/2, offset.y+(armourbarh-th)/2, armourcolor.r, armourcolor.g, armourcolor.b); + + pophudmatrix(); + } + + void drawhudicons(fpsent *d, int w, int h) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + if(hudhealth) drawhudhealth(d, w, h); + if(hudmaxhealth) drawhudmaxhealth(d, w, h); + if(hudarmour) drawhudarmour(d, w, h); + + if(d->state!=CS_DEAD) { + drawicon(HICON_FIST+d->gunselect, HICON_X + 2*HICON_STEP, HICON_Y); + if(ammohud) drawammohud(d); + } + } + + VARP(gameclock, 0, 0, 1); + FVARP(gameclockscale, 1e-3f, 0.75f, 1e3f); + HVARP(gameclockcolour, 0, 0xFFFFFF, 0xFFFFFF); + VARP(gameclockalpha, 0, 255, 255); + HVARP(gameclocklowcolour, 0, 0xFFC040, 0xFFFFFF); + VARP(gameclockalign, -1, 0, 1); + FVARP(gameclockx, 0, 0.50f, 1); + FVARP(gameclocky, 0, 0.03f, 1); + + void drawgameclock(int w, int h) + { + int secs = max(maplimit-lastmillis + 999, 0)/1000, mins = secs/60; + secs %= 60; + + defformatstring(buf, "%d:%02d", mins, secs); + int tw = 0, th = 0; + text_bounds(buf, tw, th); + + vec2 offset = vec2(gameclockx, gameclocky).mul(vec2(w, h).div(gameclockscale)); + if(gameclockalign == 1) offset.x -= tw; + else if(gameclockalign == 0) offset.x -= tw/2.0f; + offset.y -= th/2.0f; + + pushhudmatrix(); + hudmatrix.scale(gameclockscale, gameclockscale, 1); + flushhudmatrix(); + + int color = mins < 1 ? gameclocklowcolour : gameclockcolour; + draw_text(buf, int(offset.x), int(offset.y), (color>>16)&0xFF, (color>>8)&0xFF, color&0xFF, gameclockalpha); + + pophudmatrix(); + } + + extern int hudscore; + extern void drawhudscore(int w, int h); + + VARP(ammobar, 0, 0, 1); + VARP(ammobaralign, -1, 0, 1); + VARP(ammobarhorizontal, 0, 0, 1); + VARP(ammobarflip, 0, 0, 1); + VARP(ammobarhideempty, 0, 1, 1); + VARP(ammobarsep, 0, 20, 500); + VARP(ammobarcountsep, 0, 20, 500); + FVARP(ammobarcountscale, 0.5, 1.5, 2); + FVARP(ammobarx, 0, 0.025f, 1.0f); + FVARP(ammobary, 0, 0.5f, 1.0f); + FVARP(ammobarscale, 0.1f, 0.5f, 1.0f); + + void drawammobarcounter(const vec2 ¢er, const fpsent *p, int gun) + { + vec2 icondrawpos = vec2(center).sub(HICON_SIZE / 2); + int alpha = p->ammo[gun] ? 0xFF : 0x7F; + gle::color(bvec(0xFF, 0xFF, 0xFF), alpha); + drawicon(HICON_FIST + gun, icondrawpos.x, icondrawpos.y); + + int fw, fh; text_bounds("000", fw, fh); + float labeloffset = HICON_SIZE / 2.0f + ammobarcountsep + ammobarcountscale * (ammobarhorizontal ? fh : fw) / 2.0f; + vec2 offsetdir = (ammobarhorizontal ? vec2(0, 1) : vec2(1, 0)).mul(ammobarflip ? -1 : 1); + vec2 labelorigin = vec2(offsetdir).mul(labeloffset).add(center); + + pushhudmatrix(); + hudmatrix.translate(labelorigin.x, labelorigin.y, 0); + hudmatrix.scale(ammobarcountscale, ammobarcountscale, 1); + flushhudmatrix(); + + defformatstring(label, "%d", p->ammo[gun]); + int tw, th; text_bounds(label, tw, th); + vec2 textdrawpos = vec2(-tw, -th).div(2); + float ammoratio = (float)p->ammo[gun] / itemstats[gun-GUN_SG].add; + bvec color = bvec::hexcolor(p->ammo[gun] == 0 || ammoratio >= 1.0f ? 0xFFFFFF : (ammoratio >= 0.5f ? 0xFFC040 : 0xFF0000)); + draw_text(label, textdrawpos.x, textdrawpos.y, color.r, color.g, color.b, alpha); + + pophudmatrix(); + } + + static inline bool ammobargunvisible(const fpsent *d, int gun) + { + return d->ammo[gun] > 0 || d->gunselect == gun; + } + + void drawammobar(int w, int h, fpsent *p) + { + if(m_insta) return; + + int NUMPLAYERGUNS = GUN_PISTOL - GUN_SG + 1; + int numvisibleguns = NUMPLAYERGUNS; + if(ammobarhideempty) loopi(NUMPLAYERGUNS) if(!ammobargunvisible(p, GUN_SG + i)) numvisibleguns--; + + vec2 origin = vec2(ammobarx, ammobary).mul(vec2(w, h).div(ammobarscale)); + vec2 offsetdir = ammobarhorizontal ? vec2(1, 0) : vec2(0, 1); + float stepsize = HICON_SIZE + ammobarsep; + float initialoffset = (ammobaralign - 1) * (numvisibleguns - 1) * stepsize / 2; + + pushhudmatrix(); + hudmatrix.scale(ammobarscale, ammobarscale, 1); + flushhudmatrix(); + + int numskippedguns = 0; + loopi(NUMPLAYERGUNS) if(ammobargunvisible(p, GUN_SG + i) || !ammobarhideempty) + { + float offset = initialoffset + (i - numskippedguns) * stepsize; + vec2 drawpos = vec2(offsetdir).mul(offset).add(origin); + drawammobarcounter(drawpos, p, GUN_SG + i); + } + else numskippedguns++; + + pophudmatrix(); + } + + VARP(speedometer, 0, 1, 1); + FVARP(speedometerx, 0.0, 0.5, 1.0); + FVARP(speedometery, 0.0, 0.6, 1.0); + FVARP(speedometerscale, 0.1, 0.5, 1.0); + VARP(speedometercolor, 0, 1, 1); + FVARP(speedometeralpha, 0.0, 0.5, 1.0); + + void drawspeedometer(fpsent *d, int w, int h) + { + int speedforreal = (int) (sqrtf(d->vel.squaredlen()) + 1.0f); + speedforreal = (speedforreal == 1) ? 0 : speedforreal; + vec colour = vec(255, 255, 255); + float realw = 0; + float realh = 0; + if (speedometercolor) { + if (speedforreal==0) colour = vec(60, 60, 60); + else if (speedforreal>0 && speedforreal<=60) colour = vec(240, 30, 30); + else if (speedforreal>60 && speedforreal<=120) colour = vec(180, 90, 60); + else if (speedforreal>120 && speedforreal<=180) colour = vec(180, 180, 30); + else if (speedforreal>180 && speedforreal<=240) colour = vec(90, 180, 60); + else colour = vec(30, 240, 30); + } + + defformatstring(speedstring, "%d", speedforreal); + text_boundsf(speedstring, realw, realh); + vec2 offset = vec2(speedometerx, speedometery).mul(vec2(w, h).div(speedometerscale)); + offset.x -= realw/2.0f; + offset.y -= realh/2.0f; + + pushhudmatrix(); + hudmatrix.scale(speedometerscale, speedometerscale, 1); + flushhudmatrix(); + + const int speedow = 220; + const int speedoh = 101; + settexture("packages/hud/speedometer.png"); + hudquad(offset.x+realw/2.0f-speedow/2, offset.y+realh/2.0f-speedoh/2, speedow, speedoh); + + draw_text(speedstring, int(offset.x), int(offset.y), colour.x, colour.y, colour.z, (int)(speedometeralpha*255.0f)); + + pophudmatrix(); + } + + void gameplayhud(int w, int h) + { + pushhudmatrix(); + hudmatrix.scale(h/1800.0f, h/1800.0f, 1); + flushhudmatrix(); + + if(player1->state==CS_SPECTATOR) + { + int pw, ph, tw, th, fw, fh; + text_bounds(" ", pw, ph); + text_bounds("SPECTATOR", tw, th); + th = max(th, ph); + fpsent *f = followingplayer(); + text_bounds(f ? colorname(f) : " ", fw, fh); + fh = max(fh, ph); + draw_text("SPECTATOR", w*1800/h - tw - pw, 1650 - th - fh); + if(f) + { + int color = statuscolor(f, 0xFFFFFF); + draw_text(colorname(f), w*1800/h - fw - pw, 1650 - fh, (color>>16)&0xFF, (color>>8)&0xFF, color&0xFF); + } + } + + fpsent *d = hudplayer(); + + pophudmatrix(); + + if(d->state!=CS_EDITING && d->state!=CS_SPECTATOR) drawhudicons(d, w, h); + + if(d->state!=CS_EDITING && d->state!=CS_SPECTATOR && d->state!=CS_DEAD) + { + if(ammobar) drawammobar(w, h, d); + if(speedometer) drawspeedometer(d, w, h); + } + + + if(!m_edit) + { + if(gameclock) drawgameclock(w, h); + if(hudscore) drawhudscore(w, h); + } + + /// Frag message. + + if((hudfragmillis+fragmessageduration > lastmillis) && (lastmillis>fragmessageduration)) + { + vec2 center = vec2(0.5*w, 0.2*h); + float width = 0, height = 0; + pushhudmatrix(); + hudmatrix.scale(0.8, 0.8, 1); + flushhudmatrix(); + draw_textf("%s", center.x, center.y, hudfragger); + text_boundsf(hudfragger, width, height); + drawicon(HICON_FIST+hudfraggun, center.x+width, center.y, height); + draw_textf("%s", center.x+width+height, center.y, hudfragged); + pophudmatrix(); + } + } + + int clipconsole(int w, int h) + { + return 0; + } + + VARP(teamcrosshair, 0, 1, 1); + VARP(hitcrosshair, 0, 425, 1000); + + const char *defaultcrosshair(int index) + { + switch(index) + { + case 2: return "data/hit.png"; + case 1: return "data/teammate.png"; + default: return "data/crosshair.png"; + } + } + + int selectcrosshair(vec &color) + { + fpsent *d = hudplayer(); + if(d->state==CS_SPECTATOR || d->state==CS_DEAD) return -1; + + if(d->state!=CS_ALIVE) return 0; + + int crosshair = 0; + if(lasthit && lastmillis - lasthit < hitcrosshair) crosshair = 2; + else if(teamcrosshair) + { + dynent *o = intersectclosest(d->o, worldpos, d); + if(o && o->type==ENT_PLAYER && isteam(((fpsent *)o)->team, d->team)) + { + crosshair = 1; + color = vec(0, 0, 1); + } + } + + if(crosshair!=1 && !editmode && !m_insta) + { + if(d->health<=25) color = vec(1, 0, 0); + else if(d->health<=50) color = vec(1, 0.5f, 0); + } + if(d->gunwait) color.mul(0.5f); + return crosshair; + } + + void lighteffects(dynent *e, vec &color, vec &dir) + { #if 0 - fpsent *d = (fpsent *)e; - if(d->state!=CS_DEAD && d->quadmillis) - { - float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f); - color.y = color.y*(1-t) + t; - } + fpsent *d = (fpsent *)e; + if(d->state!=CS_DEAD && d->quadmillis) + { + float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f); + color.y = color.y*(1-t) + t; + } #endif - } - - int maxsoundradius(int n) - { - switch(n) - { - case S_JUMP: - case S_LAND: - case S_WEAPLOAD: - case S_ITEMAMMO: - case S_ITEMHEALTH: - case S_ITEMARMOUR: - case S_ITEMPUP: - case S_ITEMSPAWN: - case S_NOAMMO: - case S_PUPOUT: - return 340; - default: - return 500; - } - } - - bool serverinfostartcolumn(g3d_gui *g, int i) - { - static const char * const names[] = { "ping ", "players ", "mode ", "map ", "time ", "master ", "host ", "port ", "description " }; - static const float struts[] = { 7, 7, 12.5f, 14, 7, 8, 14, 7, 24.5f }; - if(size_t(i) >= sizeof(names)/sizeof(names[0])) return false; - g->pushlist(); - g->text(names[i], 0xFFFF80, !i ? " " : NULL); - if(struts[i]) g->strut(struts[i]); - g->mergehits(true); - return true; - } - - void serverinfoendcolumn(g3d_gui *g, int i) - { - g->mergehits(false); - g->column(i); - g->poplist(); - } - - const char *mastermodecolor(int n, const char *unknown) - { - return (n>=MM_START && size_t(n-MM_START)=MM_START && size_t(n-MM_START) &attr, int np) - { - if(ping < 0 || attr.empty() || attr[0]!=PROTOCOL_VERSION) - { - switch(i) - { - case 0: - if(g->button(" ", 0xFFFFDD, "serverunk")&G3D_UP) return true; - break; - - case 1: - case 2: - case 3: - case 4: - case 5: - if(g->button(" ", 0xFFFFDD)&G3D_UP) return true; - break; - - case 6: - if(g->buttonf("%s ", 0xFFFFDD, NULL, name)&G3D_UP) return true; - break; - - case 7: - if(g->buttonf("%d ", 0xFFFFDD, NULL, port)&G3D_UP) return true; - break; - - case 8: - if(ping < 0) - { - if(g->button(sdesc, 0xFFFFDD)&G3D_UP) return true; - } - else if(g->buttonf("[%s protocol] ", 0xFFFFDD, NULL, attr.empty() ? "unknown" : (attr[0] < PROTOCOL_VERSION ? "older" : "newer"))&G3D_UP) return true; - break; - } - return false; - } - - switch(i) - { - case 0: - { - const char *icon = attr.inrange(3) && np >= attr[3] ? "serverfull" : (attr.inrange(4) ? mastermodeicon(attr[4], "serverunk") : "serverunk"); - if(g->buttonf("%d ", 0xFFFFDD, icon, ping)&G3D_UP) return true; - break; - } - - case 1: - if(attr.length()>=4) - { - if(g->buttonf(np >= attr[3] ? "\f3%d/%d " : "%d/%d ", 0xFFFFDD, NULL, np, attr[3])&G3D_UP) return true; - } - else if(g->buttonf("%d ", 0xFFFFDD, NULL, np)&G3D_UP) return true; - break; - - case 2: - if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=2 ? server::modename(attr[1], "") : "")&G3D_UP) return true; - break; - - case 3: - if(g->buttonf("%.25s ", 0xFFFFDD, NULL, map)&G3D_UP) return true; - break; - - case 4: - if(attr.length()>=3 && attr[2] > 0) - { - int secs = clamp(attr[2], 0, 59*60+59), - mins = secs/60; - secs %= 60; - if(g->buttonf("%d:%02d ", 0xFFFFDD, NULL, mins, secs)&G3D_UP) return true; - } - else if(g->buttonf(" ", 0xFFFFDD)&G3D_UP) return true; - break; - case 5: - if(g->buttonf("%s%s ", 0xFFFFDD, NULL, attr.length()>=5 ? mastermodecolor(attr[4], "") : "", attr.length()>=5 ? server::mastermodename(attr[4], "") : "")&G3D_UP) return true; - break; - - case 6: - if(g->buttonf("%s ", 0xFFFFDD, NULL, name)&G3D_UP) return true; - break; - - case 7: - if(g->buttonf("%d ", 0xFFFFDD, NULL, port)&G3D_UP) return true; - break; - - case 8: - if(g->buttonf("%.25s", 0xFFFFDD, NULL, sdesc)&G3D_UP) return true; - break; - } - return false; - } - - // any data written into this vector will get saved with the map data. Must take care to do own versioning, and endianess if applicable. Will not get called when loading maps from other games, so provide defaults. - void writegamedata(vector &extras) {} - void readgamedata(vector &extras) {} - - const char *savedconfig() { return "config.cfg"; } - const char *restoreconfig() { return "restore.cfg"; } - const char *defaultconfig() { return "data/defaults.cfg"; } - const char *autoexec() { return "autoexec.cfg"; } - const char *savedservers() { return "servers.cfg"; } - - void loadconfigs() - { - execident("playsong"); - - execfile("auth.cfg", false); - } + } + + int maxsoundradius(int n) + { + switch(n) + { + case S_JUMP: + case S_LAND: + case S_WEAPLOAD: + case S_ITEMAMMO: + case S_ITEMHEALTH: + case S_ITEMARMOUR: + case S_ITEMPUP: + case S_ITEMSPAWN: + case S_NOAMMO: + case S_PUPOUT: + return 340; + default: + return 500; + } + } + + bool serverinfostartcolumn(g3d_gui *g, int i) + { + static const char * const names[] = { "ping ", "players ", "mode ", "map ", "time ", "master ", "host ", "port ", "description " }; + static const float struts[] = { 7, 7, 12.5f, 14, 7, 8, 14, 7, 24.5f }; + if(size_t(i) >= sizeof(names)/sizeof(names[0])) return false; + g->pushlist(); + g->text(names[i], 0xFFFF80, !i ? " " : NULL); + if(struts[i]) g->strut(struts[i]); + g->mergehits(true); + return true; + } + + void serverinfoendcolumn(g3d_gui *g, int i) + { + g->mergehits(false); + g->column(i); + g->poplist(); + } + + const char *mastermodecolor(int n, const char *unknown) + { + return (n>=MM_START && size_t(n-MM_START)=MM_START && size_t(n-MM_START) &attr, int np) + { + if(ping < 0 || attr.empty() || attr[0]!=PROTOCOL_VERSION) + { + switch(i) + { + case 0: + if(g->button(" ", 0xFFFFDD, "serverunk")&G3D_UP) return true; + break; + + case 1: + case 2: + case 3: + case 4: + case 5: + if(g->button(" ", 0xFFFFDD)&G3D_UP) return true; + break; + + case 6: + if(g->buttonf("%s ", 0xFFFFDD, NULL, name)&G3D_UP) return true; + break; + + case 7: + if(g->buttonf("%d ", 0xFFFFDD, NULL, port)&G3D_UP) return true; + break; + + case 8: + if(ping < 0) + { + if(g->button(sdesc, 0xFFFFDD)&G3D_UP) return true; + } + else if(g->buttonf("[%s protocol] ", 0xFFFFDD, NULL, attr.empty() ? "unknown" : (attr[0] < PROTOCOL_VERSION ? "older" : "newer"))&G3D_UP) return true; + break; + } + return false; + } + + switch(i) + { + case 0: + { + const char *icon = attr.inrange(3) && np >= attr[3] ? "serverfull" : (attr.inrange(4) ? mastermodeicon(attr[4], "serverunk") : "serverunk"); + if(g->buttonf("%d ", 0xFFFFDD, icon, ping)&G3D_UP) return true; + break; + } + + case 1: + if(attr.length()>=4) + { + if(g->buttonf(np >= attr[3] ? "\f3%d/%d " : "%d/%d ", 0xFFFFDD, NULL, np, attr[3])&G3D_UP) return true; + } + else if(g->buttonf("%d ", 0xFFFFDD, NULL, np)&G3D_UP) return true; + break; + + case 2: + if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=2 ? server::modename(attr[1], "") : "")&G3D_UP) return true; + break; + + case 3: + if(g->buttonf("%.25s ", 0xFFFFDD, NULL, map)&G3D_UP) return true; + break; + + case 4: + if(attr.length()>=3 && attr[2] > 0) + { + int secs = clamp(attr[2], 0, 59*60+59), + mins = secs/60; + secs %= 60; + if(g->buttonf("%d:%02d ", 0xFFFFDD, NULL, mins, secs)&G3D_UP) return true; + } + else if(g->buttonf(" ", 0xFFFFDD)&G3D_UP) return true; + break; + case 5: + if(g->buttonf("%s%s ", 0xFFFFDD, NULL, attr.length()>=5 ? mastermodecolor(attr[4], "") : "", attr.length()>=5 ? server::mastermodename(attr[4], "") : "")&G3D_UP) return true; + break; + + case 6: + if(g->buttonf("%s ", 0xFFFFDD, NULL, name)&G3D_UP) return true; + break; + + case 7: + if(g->buttonf("%d ", 0xFFFFDD, NULL, port)&G3D_UP) return true; + break; + + case 8: + if(g->buttonf("%.25s", 0xFFFFDD, NULL, sdesc)&G3D_UP) return true; + break; + } + return false; + } + + // any data written into this vector will get saved with the map data. Must take care to do own versioning, and endianess if applicable. Will not get called when loading maps from other games, so provide defaults. + void writegamedata(vector &extras) {} + void readgamedata(vector &extras) {} + + const char *savedconfig() { return "config.cfg"; } + const char *restoreconfig() { return "restore.cfg"; } + const char *defaultconfig() { return "data/defaults.cfg"; } + const char *autoexec() { return "autoexec.cfg"; } + const char *savedservers() { return "servers.cfg"; } + + void loadconfigs() + { + execident("playsong"); + + execfile("auth.cfg", false); + } } diff --git a/src/fpsgame/game.h b/src/fpsgame/game.h index d99bf70..0495502 100644 --- a/src/fpsgame/game.h +++ b/src/fpsgame/game.h @@ -7,123 +7,123 @@ enum { - CON_CHAT = 1<<8, - CON_TEAMCHAT = 1<<9, - CON_GAMEINFO = 1<<10, - CON_FRAG_SELF = 1<<11, - CON_FRAG_OTHER = 1<<12, - CON_TEAMKILL = 1<<13 + CON_CHAT = 1<<8, + CON_TEAMCHAT = 1<<9, + CON_GAMEINFO = 1<<10, + CON_FRAG_SELF = 1<<11, + CON_FRAG_OTHER = 1<<12, + CON_TEAMKILL = 1<<13 }; // network quantization scale -#define DMF 16.0f // for world locations -#define DNF 100.0f // for normalized vectors -#define DVELF 1.0f // for playerspeed based velocity vectors +#define DMF 16.0f // for world locations +#define DNF 100.0f // for normalized vectors +#define DVELF 1.0f // for playerspeed based velocity vectors -enum // static entity types +enum // static entity types { - NOTUSED = ET_EMPTY, // entity slot not in use in map - LIGHT = ET_LIGHT, // lightsource, attr1 = radius, attr2 = intensity - MAPMODEL = ET_MAPMODEL, // attr1 = angle, attr2 = idx - PLAYERSTART, // attr1 = angle, attr2 = team - ENVMAP = ET_ENVMAP, // attr1 = radius - PARTICLES = ET_PARTICLES, - MAPSOUND = ET_SOUND, - SPOTLIGHT = ET_SPOTLIGHT, - I_SHELLS, I_BULLETS, I_ROCKETS, I_ROUNDS, I_GRENADES, I_CARTRIDGES, - I_HEALTH, I_BOOST, - /**/I_TINYHEALTH, I_TINYARMOUR, - I_GREENARMOUR, I_YELLOWARMOUR, - I_QUAD, - TELEPORT, // attr1 = idx, attr2 = model, attr3 = tag - TELEDEST, // attr1 = angle, attr2 = idx - JUMPPAD, // attr1 = zpush, attr2 = ypush, attr3 = xpush - MAXENTTYPES + NOTUSED = ET_EMPTY, // entity slot not in use in map + LIGHT = ET_LIGHT, // lightsource, attr1 = radius, attr2 = intensity + MAPMODEL = ET_MAPMODEL, // attr1 = angle, attr2 = idx + PLAYERSTART, // attr1 = angle, attr2 = team + ENVMAP = ET_ENVMAP, // attr1 = radius + PARTICLES = ET_PARTICLES, + MAPSOUND = ET_SOUND, + SPOTLIGHT = ET_SPOTLIGHT, + I_SHELLS, I_BULLETS, I_ROCKETS, I_ROUNDS, I_GRENADES, I_CARTRIDGES, + I_HEALTH, I_BOOST, + /**/I_TINYHEALTH, I_TINYARMOUR, + I_GREENARMOUR, I_YELLOWARMOUR, + I_QUAD, + TELEPORT, // attr1 = idx, attr2 = model, attr3 = tag + TELEDEST, // attr1 = angle, attr2 = idx + JUMPPAD, // attr1 = zpush, attr2 = ypush, attr3 = xpush + MAXENTTYPES }; enum { GUN_FIST = 0, GUN_SG, GUN_CG, GUN_RL, GUN_RIFLE, GUN_GL, GUN_PISTOL, NUMGUNS }; -enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off +enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off enum { - M_TEAM = 1<<0, - M_NOITEMS = 1<<1, - M_NOAMMO = 1<<2, - M_INSTA = 1<<3, - M_EFFICIENCY = 1<<4, - M_EDIT = 1<<12, - M_DEMO = 1<<13, - M_LOCAL = 1<<14, - M_LOBBY = 1<<15 + M_TEAM = 1<<0, + M_NOITEMS = 1<<1, + M_NOAMMO = 1<<2, + M_INSTA = 1<<3, + M_EFFICIENCY = 1<<4, + M_EDIT = 1<<12, + M_DEMO = 1<<13, + M_LOCAL = 1<<14, + M_LOBBY = 1<<15 }; static struct gamemodeinfo { - const char *name; - int flags; - const char *info; + const char *name; + int flags; + const char *info; } gamemodes[] = { - { "demo", M_DEMO | M_LOCAL, NULL}, - { "ffa", M_LOBBY, "Free For All: Collect items for ammo. Frag everyone to score points." }, - { "coop edit", M_EDIT, "Cooperative Editing: Edit maps with multiple players simultaneously." }, - { "teamplay", M_TEAM, "Teamplay: Collect items for ammo. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." }, - { "instagib", M_NOITEMS | M_INSTA, "Instagib: You spawn with full rifle ammo and die instantly from one shot. There are no items. Frag everyone to score points." }, - { "insta team", M_NOITEMS | M_INSTA | M_TEAM, "Instagib Team: You spawn with full rifle ammo and die instantly from one shot. There are no items. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." }, - { "efficiency", M_NOITEMS | M_EFFICIENCY, "Efficiency: You spawn with all weapons and armour. There are no items. Frag everyone to score points." }, - { "effic team", M_NOITEMS | M_EFFICIENCY | M_TEAM, "Efficiency Team: You spawn with all weapons and armour. There are no items. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." }, + { "demo", M_DEMO | M_LOCAL, NULL}, + { "ffa", M_LOBBY, "Free For All: Collect items for ammo. Frag everyone to score points." }, + { "coop edit", M_EDIT, "Cooperative Editing: Edit maps with multiple players simultaneously." }, + { "teamplay", M_TEAM, "Teamplay: Collect items for ammo. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." }, + { "instagib", M_NOITEMS | M_INSTA, "Instagib: You spawn with full rifle ammo and die instantly from one shot. There are no items. Frag everyone to score points." }, + { "insta team", M_NOITEMS | M_INSTA | M_TEAM, "Instagib Team: You spawn with full rifle ammo and die instantly from one shot. There are no items. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." }, + { "efficiency", M_NOITEMS | M_EFFICIENCY, "Efficiency: You spawn with all weapons and armour. There are no items. Frag everyone to score points." }, + { "effic team", M_NOITEMS | M_EFFICIENCY | M_TEAM, "Efficiency Team: You spawn with all weapons and armour. There are no items. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." }, }; #define STARTGAMEMODE (-1) #define NUMGAMEMODES ((int)(sizeof(gamemodes)/sizeof(gamemodes[0]))) -#define m_valid(mode) ((mode) >= STARTGAMEMODE && (mode) < STARTGAMEMODE + NUMGAMEMODES) -#define m_check(mode, flag) (m_valid(mode) && gamemodes[(mode) - STARTGAMEMODE].flags&(flag)) +#define m_valid(mode) ((mode) >= STARTGAMEMODE && (mode) < STARTGAMEMODE + NUMGAMEMODES) +#define m_check(mode, flag) (m_valid(mode) && gamemodes[(mode) - STARTGAMEMODE].flags&(flag)) #define m_checknot(mode, flag) (m_valid(mode) && !(gamemodes[(mode) - STARTGAMEMODE].flags&(flag))) #define m_checkall(mode, flag) (m_valid(mode) && (gamemodes[(mode) - STARTGAMEMODE].flags&(flag)) == (flag)) #define m_checkonly(mode, flag, exclude) (m_valid(mode) && (gamemodes[(mode) - STARTGAMEMODE].flags&((flag)|(exclude))) == (flag)) -#define m_noitems (m_check(gamemode, M_NOITEMS)) -#define m_noammo (m_check(gamemode, M_NOAMMO|M_NOITEMS)) -#define m_insta (m_check(gamemode, M_INSTA)) +#define m_noitems (m_check(gamemode, M_NOITEMS)) +#define m_noammo (m_check(gamemode, M_NOAMMO|M_NOITEMS)) +#define m_insta (m_check(gamemode, M_INSTA)) #define m_efficiency (m_check(gamemode, M_EFFICIENCY)) -#define m_teammode (m_check(gamemode, M_TEAM)) -#define isteam(a,b) (m_teammode && strcmp(a, b)==0) +#define m_teammode (m_check(gamemode, M_TEAM)) +#define isteam(a,b) (m_teammode && strcmp(a, b)==0) -#define m_demo (m_check(gamemode, M_DEMO)) -#define m_edit (m_check(gamemode, M_EDIT)) -#define m_lobby (m_check(gamemode, M_LOBBY)) -#define m_timed (m_checknot(gamemode, M_DEMO|M_EDIT|M_LOCAL)) -#define m_botmode (m_checknot(gamemode, M_DEMO|M_LOCAL)) -#define m_mp(mode) (m_checknot(mode, M_LOCAL)) +#define m_demo (m_check(gamemode, M_DEMO)) +#define m_edit (m_check(gamemode, M_EDIT)) +#define m_lobby (m_check(gamemode, M_LOBBY)) +#define m_timed (m_checknot(gamemode, M_DEMO|M_EDIT|M_LOCAL)) +#define m_botmode (m_checknot(gamemode, M_DEMO|M_LOCAL)) +#define m_mp(mode) (m_checknot(mode, M_LOCAL)) enum { MM_AUTH = -1, MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE, MM_PASSWORD, MM_START = MM_AUTH }; -static const char * const mastermodenames[] = { "auth", "open", "veto", "locked", "private", "password" }; -static const char * const mastermodecolors[] = { "", "\f0", "\f2", "\f2", "\f3", "\f3" }; +static const char * const mastermodenames[] = { "auth", "open", "veto", "locked", "private", "password" }; +static const char * const mastermodecolors[] = { "", "\f0", "\f2", "\f2", "\f3", "\f3" }; static const char * const mastermodeicons[] = { "server", "server", "serverlock", "serverlock", "serverpriv", "serverpriv" }; // hardcoded sounds, defined in sounds.cfg enum { - S_JUMP = 0, S_LAND, S_RIFLE, S_PUNCH1, S_SG, S_CG, - S_RLFIRE, S_RLHIT, S_WEAPLOAD, S_ITEMAMMO, S_ITEMHEALTH, - S_ITEMARMOUR, S_ITEMPUP, S_ITEMSPAWN, S_TELEPORT, S_NOAMMO, S_PUPOUT, - S_PAIN1, S_PAIN2, S_PAIN3, S_PAIN4, S_PAIN5, S_PAIN6, - S_DIE1, S_DIE2, - S_FLAUNCH, S_FEXPLODE, - S_SPLASH1, S_SPLASH2, - S_JUMPPAD, S_PISTOL, - - S_V_FIGHT, - S_V_BOOST, S_V_BOOST10, - S_V_QUAD, S_V_QUAD10, - - S_BURN, - S_CHAINSAW_ATTACK, - S_CHAINSAW_IDLE, - - S_HIT + S_JUMP = 0, S_LAND, S_RIFLE, S_PUNCH1, S_SG, S_CG, + S_RLFIRE, S_RLHIT, S_WEAPLOAD, S_ITEMAMMO, S_ITEMHEALTH, + S_ITEMARMOUR, S_ITEMPUP, S_ITEMSPAWN, S_TELEPORT, S_NOAMMO, S_PUPOUT, + S_PAIN1, S_PAIN2, S_PAIN3, S_PAIN4, S_PAIN5, S_PAIN6, + S_DIE1, S_DIE2, + S_FLAUNCH, S_FEXPLODE, + S_SPLASH1, S_SPLASH2, + S_JUMPPAD, S_PISTOL, + + S_V_FIGHT, + S_V_BOOST, S_V_BOOST10, + S_V_QUAD, S_V_QUAD10, + + S_BURN, + S_CHAINSAW_ATTACK, + S_CHAINSAW_IDLE, + + S_HIT }; // network messages codes, c2s, c2c, s2c @@ -132,72 +132,72 @@ enum { PRIV_NONE = 0, PRIV_MASTER, PRIV_AUTH, PRIV_ADMIN }; enum { - N_CONNECT = 0, N_SERVINFO, N_WELCOME, N_INITCLIENT, N_POS, N_TEXT, N_SOUND, N_CDIS, - N_SHOOT, N_EXPLODE, N_SUICIDE, - N_DIED, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, - N_TRYSPAWN, N_SPAWNSTATE, N_SPAWN, N_FORCEDEATH, - N_GUNSELECT, N_TAUNT, - N_MAPCHANGE, N_MAPVOTE, N_TEAMINFO, N_ITEMSPAWN, N_ITEMPICKUP, N_ITEMACC, N_TELEPORT, N_JUMPPAD, - N_PING, N_PONG, N_CLIENTPING, - N_TIMEUP, N_FORCEINTERMISSION, - N_SERVMSG, N_ITEMLIST, N_RESUME, - N_EDITMODE, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_REMIP, N_EDITVSLOT, N_UNDO, N_REDO, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, N_EDITVAR, - N_MASTERMODE, N_KICK, N_CLEARBANS, N_CURRENTMASTER, N_SPECTATOR, N_SETMASTER, N_SETTEAM, N_ANNOUNCE, - N_LISTDEMOS, N_SENDDEMOLIST, N_GETDEMO, N_SENDDEMO, - N_DEMOPLAYBACK, N_RECORDDEMO, N_STOPDEMO, N_CLEARDEMOS, - N_SAYTEAM, - N_CLIENT, - N_AUTHTRY, N_AUTHKICK, N_AUTHCHAL, N_AUTHANS, N_REQAUTH, - N_PAUSEGAME, N_GAMESPEED, - N_ADDBOT, N_DELBOT, N_INITAI, N_FROMAI, N_BOTLIMIT, N_BOTBALANCE, - N_MAPCRC, N_CHECKMAPS, - N_SWITCHNAME, N_SWITCHMODEL, N_SWITCHTEAM, - N_INITTOKENS, N_TAKETOKEN, N_EXPIRETOKENS, N_DROPTOKENS, N_DEPOSITTOKENS, N_STEALTOKENS, - N_SERVCMD, - N_DEMOPACKET, - NUMMSG + N_CONNECT = 0, N_SERVINFO, N_WELCOME, N_INITCLIENT, N_POS, N_TEXT, N_SOUND, N_CDIS, + N_SHOOT, N_EXPLODE, N_SUICIDE, + N_DIED, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, + N_TRYSPAWN, N_SPAWNSTATE, N_SPAWN, N_FORCEDEATH, + N_GUNSELECT, N_TAUNT, + N_MAPCHANGE, N_MAPVOTE, N_TEAMINFO, N_ITEMSPAWN, N_ITEMPICKUP, N_ITEMACC, N_TELEPORT, N_JUMPPAD, + N_PING, N_PONG, N_CLIENTPING, + N_TIMEUP, N_FORCEINTERMISSION, + N_SERVMSG, N_ITEMLIST, N_RESUME, + N_EDITMODE, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_REMIP, N_EDITVSLOT, N_UNDO, N_REDO, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, N_EDITVAR, + N_MASTERMODE, N_KICK, N_CLEARBANS, N_CURRENTMASTER, N_SPECTATOR, N_SETMASTER, N_SETTEAM, N_ANNOUNCE, + N_LISTDEMOS, N_SENDDEMOLIST, N_GETDEMO, N_SENDDEMO, + N_DEMOPLAYBACK, N_RECORDDEMO, N_STOPDEMO, N_CLEARDEMOS, + N_SAYTEAM, + N_CLIENT, + N_AUTHTRY, N_AUTHKICK, N_AUTHCHAL, N_AUTHANS, N_REQAUTH, + N_PAUSEGAME, N_GAMESPEED, + N_ADDBOT, N_DELBOT, N_INITAI, N_FROMAI, N_BOTLIMIT, N_BOTBALANCE, + N_MAPCRC, N_CHECKMAPS, + N_SWITCHNAME, N_SWITCHMODEL, N_SWITCHTEAM, + N_INITTOKENS, N_TAKETOKEN, N_EXPIRETOKENS, N_DROPTOKENS, N_DEPOSITTOKENS, N_STEALTOKENS, + N_SERVCMD, + N_DEMOPACKET, + NUMMSG }; -static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes +static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes { - N_CONNECT, 0, N_SERVINFO, 0, N_WELCOME, 1, N_INITCLIENT, 0, N_POS, 0, N_TEXT, 0, N_SOUND, 2, N_CDIS, 2, - N_SHOOT, 0, N_EXPLODE, 0, N_SUICIDE, 1, - N_DIED, 5, N_DAMAGE, 6, N_HITPUSH, 7, N_SHOTFX, 10, N_EXPLODEFX, 4, - N_TRYSPAWN, 1, N_SPAWNSTATE, 14, N_SPAWN, 3, N_FORCEDEATH, 2, - N_GUNSELECT, 2, N_TAUNT, 1, - N_MAPCHANGE, 0, N_MAPVOTE, 0, N_TEAMINFO, 0, N_ITEMSPAWN, 2, N_ITEMPICKUP, 2, N_ITEMACC, 3, - N_PING, 2, N_PONG, 2, N_CLIENTPING, 2, - N_TIMEUP, 2, N_FORCEINTERMISSION, 1, - N_SERVMSG, 0, N_ITEMLIST, 0, N_RESUME, 0, - N_EDITMODE, 2, N_EDITENT, 11, N_EDITF, 16, N_EDITT, 16, N_EDITM, 16, N_FLIP, 14, N_COPY, 14, N_PASTE, 14, N_ROTATE, 15, N_REPLACE, 17, N_DELCUBE, 14, N_REMIP, 1, N_EDITVSLOT, 16, N_UNDO, 0, N_REDO, 0, N_NEWMAP, 2, N_GETMAP, 1, N_SENDMAP, 0, N_EDITVAR, 0, - N_MASTERMODE, 2, N_KICK, 0, N_CLEARBANS, 1, N_CURRENTMASTER, 0, N_SPECTATOR, 3, N_SETMASTER, 0, N_SETTEAM, 0, N_ANNOUNCE, 2, - N_LISTDEMOS, 1, N_SENDDEMOLIST, 0, N_GETDEMO, 3, N_SENDDEMO, 0, - N_DEMOPLAYBACK, 3, N_RECORDDEMO, 2, N_STOPDEMO, 1, N_CLEARDEMOS, 2, - N_SAYTEAM, 0, - N_CLIENT, 0, - N_AUTHTRY, 0, N_AUTHKICK, 0, N_AUTHCHAL, 0, N_AUTHANS, 0, N_REQAUTH, 0, - N_PAUSEGAME, 0, N_GAMESPEED, 0, - N_ADDBOT, 2, N_DELBOT, 1, N_INITAI, 0, N_FROMAI, 2, N_BOTLIMIT, 2, N_BOTBALANCE, 2, - N_MAPCRC, 0, N_CHECKMAPS, 1, - N_SWITCHNAME, 0, N_SWITCHMODEL, 2, N_SWITCHTEAM, 0, - N_INITTOKENS, 0, N_TAKETOKEN, 2, N_EXPIRETOKENS, 0, N_DROPTOKENS, 0, N_DEPOSITTOKENS, 2, N_STEALTOKENS, 0, - N_SERVCMD, 0, - N_DEMOPACKET, 0, - -1 + N_CONNECT, 0, N_SERVINFO, 0, N_WELCOME, 1, N_INITCLIENT, 0, N_POS, 0, N_TEXT, 0, N_SOUND, 2, N_CDIS, 2, + N_SHOOT, 0, N_EXPLODE, 0, N_SUICIDE, 1, + N_DIED, 5, N_DAMAGE, 6, N_HITPUSH, 7, N_SHOTFX, 10, N_EXPLODEFX, 4, + N_TRYSPAWN, 1, N_SPAWNSTATE, 14, N_SPAWN, 3, N_FORCEDEATH, 2, + N_GUNSELECT, 2, N_TAUNT, 1, + N_MAPCHANGE, 0, N_MAPVOTE, 0, N_TEAMINFO, 0, N_ITEMSPAWN, 2, N_ITEMPICKUP, 2, N_ITEMACC, 3, + N_PING, 2, N_PONG, 2, N_CLIENTPING, 2, + N_TIMEUP, 2, N_FORCEINTERMISSION, 1, + N_SERVMSG, 0, N_ITEMLIST, 0, N_RESUME, 0, + N_EDITMODE, 2, N_EDITENT, 11, N_EDITF, 16, N_EDITT, 16, N_EDITM, 16, N_FLIP, 14, N_COPY, 14, N_PASTE, 14, N_ROTATE, 15, N_REPLACE, 17, N_DELCUBE, 14, N_REMIP, 1, N_EDITVSLOT, 16, N_UNDO, 0, N_REDO, 0, N_NEWMAP, 2, N_GETMAP, 1, N_SENDMAP, 0, N_EDITVAR, 0, + N_MASTERMODE, 2, N_KICK, 0, N_CLEARBANS, 1, N_CURRENTMASTER, 0, N_SPECTATOR, 3, N_SETMASTER, 0, N_SETTEAM, 0, N_ANNOUNCE, 2, + N_LISTDEMOS, 1, N_SENDDEMOLIST, 0, N_GETDEMO, 3, N_SENDDEMO, 0, + N_DEMOPLAYBACK, 3, N_RECORDDEMO, 2, N_STOPDEMO, 1, N_CLEARDEMOS, 2, + N_SAYTEAM, 0, + N_CLIENT, 0, + N_AUTHTRY, 0, N_AUTHKICK, 0, N_AUTHCHAL, 0, N_AUTHANS, 0, N_REQAUTH, 0, + N_PAUSEGAME, 0, N_GAMESPEED, 0, + N_ADDBOT, 2, N_DELBOT, 1, N_INITAI, 0, N_FROMAI, 2, N_BOTLIMIT, 2, N_BOTBALANCE, 2, + N_MAPCRC, 0, N_CHECKMAPS, 1, + N_SWITCHNAME, 0, N_SWITCHMODEL, 2, N_SWITCHTEAM, 0, + N_INITTOKENS, 0, N_TAKETOKEN, 2, N_EXPIRETOKENS, 0, N_DROPTOKENS, 0, N_DEPOSITTOKENS, 2, N_STEALTOKENS, 0, + N_SERVCMD, 0, + N_DEMOPACKET, 0, + -1 }; #define SAUERBRATEN_LANINFO_PORT 28784 #define SAUERBRATEN_SERVER_PORT 28785 #define SAUERBRATEN_SERVINFO_PORT 28786 #define SAUERBRATEN_MASTER_PORT 28787 -#define PROTOCOL_VERSION 260 // bump when protocol changes -#define DEMO_VERSION 1 // bump when demo format changes +#define PROTOCOL_VERSION 260 // bump when protocol changes +#define DEMO_VERSION 1 // bump when demo format changes #define DEMO_MAGIC "SAUERBRATEN_DEMO" struct demoheader { - char magic[16]; - int version, protocol; + char magic[16]; + int version, protocol; }; #define MAXNAMELEN 15 @@ -205,50 +205,50 @@ struct demoheader enum { - HICON_BLUE_ARMOUR = 0, - HICON_GREEN_ARMOUR, - HICON_YELLOW_ARMOUR, - - HICON_HEALTH, - - HICON_FIST, - HICON_SG, - HICON_CG, - HICON_RL, - HICON_RIFLE, - HICON_GL, - HICON_PISTOL, - - HICON_QUAD, - - HICON_TOKEN, - - HICON_X = 20, - HICON_Y = 1650, - HICON_TEXTY = 1644, - HICON_STEP = 490, - HICON_SIZE = 120, - HICON_SPACE = 40 + HICON_BLUE_ARMOUR = 0, + HICON_GREEN_ARMOUR, + HICON_YELLOW_ARMOUR, + + HICON_HEALTH, + + HICON_FIST, + HICON_SG, + HICON_CG, + HICON_RL, + HICON_RIFLE, + HICON_GL, + HICON_PISTOL, + + HICON_QUAD, + + HICON_TOKEN, + + HICON_X = 20, + HICON_Y = 1650, + HICON_TEXTY = 1644, + HICON_STEP = 490, + HICON_SIZE = 120, + HICON_SPACE = 40 }; static struct itemstat { - int add, max, sound; - const char *name; - int icon, info; + int add, max, sound; + const char *name; + int icon, info; } itemstats[] = { - {10, 30, S_ITEMAMMO, "SG", HICON_SG, GUN_SG}, - {20, 60, S_ITEMAMMO, "CG", HICON_CG, GUN_CG}, - {5, 15, S_ITEMAMMO, "RL", HICON_RL, GUN_RL}, - {5, 15, S_ITEMAMMO, "RI", HICON_RIFLE, GUN_RIFLE}, - {10, 30, S_ITEMAMMO, "GL", HICON_GL, GUN_GL}, - {30, 120, S_ITEMAMMO, "PI", HICON_PISTOL, GUN_PISTOL}, - {25, 100, S_ITEMHEALTH, "H", HICON_HEALTH, -1}, - {100, 200, S_ITEMHEALTH, "MH", HICON_HEALTH, 50}, - {5, 100, S_ITEMHEALTH, "TH", HICON_HEALTH, -1}, - {5, 50, S_ITEMARMOUR, "TA", HICON_BLUE_ARMOUR, A_BLUE}, - {50, 100, S_ITEMARMOUR, "GA", HICON_GREEN_ARMOUR, A_GREEN}, - {100, 200, S_ITEMARMOUR, "YA", HICON_YELLOW_ARMOUR, A_YELLOW}, - {20000, 30000, S_ITEMPUP, "Q", HICON_QUAD, -1}, + {10, 30, S_ITEMAMMO, "SG", HICON_SG, GUN_SG}, + {20, 60, S_ITEMAMMO, "CG", HICON_CG, GUN_CG}, + {5, 15, S_ITEMAMMO, "RL", HICON_RL, GUN_RL}, + {5, 15, S_ITEMAMMO, "RI", HICON_RIFLE, GUN_RIFLE}, + {10, 30, S_ITEMAMMO, "GL", HICON_GL, GUN_GL}, + {30, 120, S_ITEMAMMO, "PI", HICON_PISTOL, GUN_PISTOL}, + {25, 100, S_ITEMHEALTH, "H", HICON_HEALTH, -1}, + {100, 200, S_ITEMHEALTH, "MH", HICON_HEALTH, 50}, + {5, 100, S_ITEMHEALTH, "TH", HICON_HEALTH, -1}, + {5, 50, S_ITEMARMOUR, "TA", HICON_BLUE_ARMOUR, A_BLUE}, + {50, 100, S_ITEMARMOUR, "GA", HICON_GREEN_ARMOUR, A_GREEN}, + {100, 200, S_ITEMARMOUR, "YA", HICON_YELLOW_ARMOUR, A_YELLOW}, + {20000, 30000, S_ITEMPUP, "Q", HICON_QUAD, -1}, }; #define MAXRAYS 12 @@ -257,18 +257,18 @@ static struct itemstat { #define EXP_DISTSCALE 1.5f static const struct guninfo { - int sound, attackdelay, damage, spread, projspeed, kickamount, range, rays, hitpush, exprad, ttl; - const char *name, *file; - short part; + int sound, attackdelay, damage, spread, projspeed, kickamount, range, rays, hitpush, exprad, ttl; + const char *name, *file; + short part; } guns[NUMGUNS] = { - // delay| dmg| spr| spd| kck| rng| ray| pus| exp| - { S_PUNCH1, 100, 30, 0, 0, 0, 30, 1, 80, 0, 0, "fist", "fist", 0 }, - { S_SG, 1000, 20, 280, 0, 20, 1024, MAXRAYS, 100, 0, 0, "shotgun", "shotg", 0 }, - { S_CG, 100, 20, 70, 0, 10, 1024, 1, 80, 0, 0, "chaingun", "chaing", 0 }, - { S_RLFIRE, 800, 120, 0, 270, 10, 1024, 1, 240, 40, 0, "rocketlauncher", "rocket", 0 }, - { S_RIFLE, 1200, 120, 0, 0, 30, 2048, 1, 120, 0, 0, "rifle", "rifle", 0 }, - { S_FLAUNCH, 600, 90, 0, 300, 20, 1024, 1, 160, 45, 1500, "grenadelauncher", "gl", 0 }, - { S_PISTOL, 400, 60, 110, 0, 10, 1024, 1, 80, 0, 0, "pistol", "pistol", 0 }, + // delay| dmg| spr| spd| kck| rng| ray| pus| exp| + { S_PUNCH1, 100, 30, 0, 0, 0, 30, 1, 80, 0, 0, "fist", "fist", 0 }, + { S_SG, 1000, 20, 280, 0, 20, 1024, MAXRAYS, 100, 0, 0, "shotgun", "shotg", 0 }, + { S_CG, 100, 20, 70, 0, 10, 1024, 1, 80, 0, 0, "chaingun", "chaing", 0 }, + { S_RLFIRE, 800, 120, 0, 270, 10, 1024, 1, 240, 40, 0, "rocketlauncher", "rocket", 0 }, + { S_RIFLE, 1200, 120, 0, 0, 30, 2048, 1, 120, 0, 0, "rifle", "rifle", 0 }, + { S_FLAUNCH, 600, 90, 0, 300, 20, 1024, 1, 160, 45, 1500, "grenadelauncher", "gl", 0 }, + { S_PISTOL, 400, 60, 110, 0, 10, 1024, 1, 80, 0, 0, "pistol", "pistol", 0 }, }; /// Rough accuracy code, client-side only. @@ -290,243 +290,243 @@ extern void pwreset(void); // inherited by fpsent and server clients struct fpsstate { - int health, maxhealth; - int armour, maxarmour, armourtype; - int quadmillis; - int gunselect, gunwait; - int ammo[NUMGUNS]; - int aitype, skill; - - fpsstate() : maxhealth(100), maxarmour(50), aitype(AI_NONE), skill(0) {} - - void baseammo(int gun, int k = 2, int scale = 1) - { - ammo[gun] = (itemstats[gun-GUN_SG].add*k)/scale; - } - - void addammo(int gun, int k = 1, int scale = 1) - { - itemstat &is = itemstats[gun-GUN_SG]; - ammo[gun] = min(ammo[gun] + (is.add*k)/scale, is.max); - } - - bool hasmaxammo(int type) - { - const itemstat &is = itemstats[type-I_SHELLS]; - return ammo[type-I_SHELLS+GUN_SG]>=is.max; - } - - bool canpickup(int type) - { - if(typeI_QUAD) return false; - itemstat &is = itemstats[type-I_SHELLS]; - switch(type) - { - case I_BOOST: return maxhealthI_QUAD) return; - itemstat &is = itemstats[type-I_SHELLS]; - switch(type) - { - case I_TINYHEALTH: - health = min(health+is.add, maxhealth); - break; - case I_BOOST: - maxhealth = min(maxhealth+is.info, is.max); - [[fallthrough]]; - case I_HEALTH: // boost also adds to health - health = min(health+is.add, maxhealth); - break; - case I_TINYARMOUR: - [[fallthrough]]; - case I_GREENARMOUR: - [[fallthrough]]; - case I_YELLOWARMOUR: - maxarmour = max(maxarmour, is.max); - armour = min(armour+is.add, maxarmour); - armourtype = is.info; - break; - case I_QUAD: - quadmillis = min(quadmillis+is.add, is.max); - break; - default: - ammo[is.info] = min(ammo[is.info]+is.add, is.max); - break; - } - } - - void respawn() - { - maxhealth = 100; - health = maxhealth; - maxarmour = 50; - armour = 0; - armourtype = A_BLUE; - quadmillis = 0; - gunselect = GUN_PISTOL; - gunwait = 0; - loopi(NUMGUNS) ammo[i] = 0; - ammo[GUN_FIST] = 1; - } - - void spawnstate(int gamemode) - { - if(m_demo) - { - gunselect = GUN_FIST; - } - else if(m_insta) - { - armour = 0; - health = 1; - gunselect = GUN_RIFLE; - ammo[GUN_RIFLE] = 100; - } - else if(m_efficiency) - { - armourtype = A_GREEN; - armour = 100; - loopi(NUMGUNS-1) baseammo(i+1); - gunselect = GUN_CG; - ammo[GUN_CG] /= 2; - } - else - { - armourtype = A_BLUE; - armour = 25; - ammo[GUN_PISTOL] = 40; - } - } - - // just subtract damage here, can set death, etc. later in code calling this - int dodamage(int damage) - { - int ad = (damage*(armourtype+1)*30)/100; // let armour absorb when possible - if(ad>armour) ad = armour; - armour -= ad; - damage -= ad; - health -= damage; - return damage; - } - - int hasammo(int gun, int exclude = -1) - { - return gun >= 0 && gun <= NUMGUNS && gun != exclude && ammo[gun] > 0; - } + int health, maxhealth; + int armour, maxarmour, armourtype; + int quadmillis; + int gunselect, gunwait; + int ammo[NUMGUNS]; + int aitype, skill; + + fpsstate() : maxhealth(100), maxarmour(50), aitype(AI_NONE), skill(0) {} + + void baseammo(int gun, int k = 2, int scale = 1) + { + ammo[gun] = (itemstats[gun-GUN_SG].add*k)/scale; + } + + void addammo(int gun, int k = 1, int scale = 1) + { + itemstat &is = itemstats[gun-GUN_SG]; + ammo[gun] = min(ammo[gun] + (is.add*k)/scale, is.max); + } + + bool hasmaxammo(int type) + { + const itemstat &is = itemstats[type-I_SHELLS]; + return ammo[type-I_SHELLS+GUN_SG]>=is.max; + } + + bool canpickup(int type) + { + if(typeI_QUAD) return false; + itemstat &is = itemstats[type-I_SHELLS]; + switch(type) + { + case I_BOOST: return maxhealthI_QUAD) return; + itemstat &is = itemstats[type-I_SHELLS]; + switch(type) + { + case I_TINYHEALTH: + health = min(health+is.add, maxhealth); + break; + case I_BOOST: + maxhealth = min(maxhealth+is.info, is.max); + [[fallthrough]]; + case I_HEALTH: // boost also adds to health + health = min(health+is.add, maxhealth); + break; + case I_TINYARMOUR: + [[fallthrough]]; + case I_GREENARMOUR: + [[fallthrough]]; + case I_YELLOWARMOUR: + maxarmour = max(maxarmour, is.max); + armour = min(armour+is.add, maxarmour); + armourtype = is.info; + break; + case I_QUAD: + quadmillis = min(quadmillis+is.add, is.max); + break; + default: + ammo[is.info] = min(ammo[is.info]+is.add, is.max); + break; + } + } + + void respawn() + { + maxhealth = 100; + health = maxhealth; + maxarmour = 50; + armour = 0; + armourtype = A_BLUE; + quadmillis = 0; + gunselect = GUN_PISTOL; + gunwait = 0; + loopi(NUMGUNS) ammo[i] = 0; + ammo[GUN_FIST] = 1; + } + + void spawnstate(int gamemode) + { + if(m_demo) + { + gunselect = GUN_FIST; + } + else if(m_insta) + { + armour = 0; + health = 1; + gunselect = GUN_RIFLE; + ammo[GUN_RIFLE] = 100; + } + else if(m_efficiency) + { + armourtype = A_GREEN; + armour = 100; + loopi(NUMGUNS-1) baseammo(i+1); + gunselect = GUN_CG; + ammo[GUN_CG] /= 2; + } + else + { + armourtype = A_BLUE; + armour = 25; + ammo[GUN_PISTOL] = 40; + } + } + + // just subtract damage here, can set death, etc. later in code calling this + int dodamage(int damage) + { + int ad = (damage*(armourtype+1)*30)/100; // let armour absorb when possible + if(ad>armour) ad = armour; + armour -= ad; + damage -= ad; + health -= damage; + return damage; + } + + int hasammo(int gun, int exclude = -1) + { + return gun >= 0 && gun <= NUMGUNS && gun != exclude && ammo[gun] > 0; + } }; extern int screenw, screenh; struct fpsent : dynent, fpsstate { - int weight; // affects the effectiveness of hitpush - int clientnum, privilege, lastupdate, plag, ping; - int lifesequence; // sequence id for each respawn, used in damage test - int respawned, suicided; - int lastpain; - int lastaction, lastattackgun; - bool attacking; - int attacksound, attackchan, idlesound, idlechan; - int lasttaunt; - int lastpickup, lastpickupmillis, lastbase, lastrepammo, flagpickup, tokens; - vec lastcollect; - int frags, flags, deaths, totaldamage, totalshots; - editinfo *edit; - float deltayaw, deltapitch, deltaroll, newyaw, newpitch, newroll; - int smoothmillis; - - string name, team, info; - int playermodel; - ai::aiinfo *ai; - int ownernum, lastnode; - - vec muzzle; - - fpsent() : weight(100), clientnum(-1), privilege(PRIV_NONE), lastupdate(0), plag(0), ping(0), lifesequence(0), respawned(-1), suicided(-1), lastpain(0), attacksound(-1), attackchan(-1), idlesound(-1), idlechan(-1), frags(0), flags(0), deaths(0), totaldamage(0), totalshots(0), edit(NULL), smoothmillis(-1), playermodel(-1), ai(NULL), ownernum(-1), muzzle(-1, -1, -1) - { - name[0] = team[0] = info[0] = 0; - respawn(); - } - ~fpsent() - { - freeeditinfo(edit); - if(attackchan >= 0) stopsound(attacksound, attackchan); - if(idlechan >= 0) stopsound(idlesound, idlechan); - if(ai) delete ai; - } - - void hitpush(int damage, const vec &dir, fpsent *actor, int gun) - { - vec push(dir); - push.mul((actor==this && guns[gun].exprad ? EXP_SELFPUSH : 1.0f)*guns[gun].hitpush*damage/weight); - vel.add(push); - } - - void stopattacksound() - { - if(attackchan >= 0) stopsound(attacksound, attackchan, 250); - attacksound = attackchan = -1; - } - - void stopidlesound() - { - if(idlechan >= 0) stopsound(idlesound, idlechan, 100); - idlesound = idlechan = -1; - } - - void respawn() - { - dynent::reset(); - fpsstate::respawn(); - respawned = suicided = -1; - lastaction = 0; - lastattackgun = gunselect; - attacking = false; - lasttaunt = 0; - lastpickup = -1; - lastpickupmillis = 0; - lastbase = lastrepammo = -1; - flagpickup = 0; - tokens = 0; - lastcollect = vec(-1e10f, -1e10f, -1e10f); - stopattacksound(); - lastnode = -1; - } - - int respawnwait(int secs, int delay = 0) - { - return max(0, secs - (::lastmillis - lastpain - delay)/1000); - } + int weight; // affects the effectiveness of hitpush + int clientnum, privilege, lastupdate, plag, ping; + int lifesequence; // sequence id for each respawn, used in damage test + int respawned, suicided; + int lastpain; + int lastaction, lastattackgun; + bool attacking; + int attacksound, attackchan, idlesound, idlechan; + int lasttaunt; + int lastpickup, lastpickupmillis, lastbase, lastrepammo, flagpickup, tokens; + vec lastcollect; + int frags, flags, deaths, totaldamage, totalshots; + editinfo *edit; + float deltayaw, deltapitch, deltaroll, newyaw, newpitch, newroll; + int smoothmillis; + + string name, team, info; + int playermodel; + ai::aiinfo *ai; + int ownernum, lastnode; + + vec muzzle; + + fpsent() : weight(100), clientnum(-1), privilege(PRIV_NONE), lastupdate(0), plag(0), ping(0), lifesequence(0), respawned(-1), suicided(-1), lastpain(0), attacksound(-1), attackchan(-1), idlesound(-1), idlechan(-1), frags(0), flags(0), deaths(0), totaldamage(0), totalshots(0), edit(NULL), smoothmillis(-1), playermodel(-1), ai(NULL), ownernum(-1), muzzle(-1, -1, -1) + { + name[0] = team[0] = info[0] = 0; + respawn(); + } + ~fpsent() + { + freeeditinfo(edit); + if(attackchan >= 0) stopsound(attacksound, attackchan); + if(idlechan >= 0) stopsound(idlesound, idlechan); + if(ai) delete ai; + } + + void hitpush(int damage, const vec &dir, fpsent *actor, int gun) + { + vec push(dir); + push.mul((actor==this && guns[gun].exprad ? EXP_SELFPUSH : 1.0f)*guns[gun].hitpush*damage/weight); + vel.add(push); + } + + void stopattacksound() + { + if(attackchan >= 0) stopsound(attacksound, attackchan, 250); + attacksound = attackchan = -1; + } + + void stopidlesound() + { + if(idlechan >= 0) stopsound(idlesound, idlechan, 100); + idlesound = idlechan = -1; + } + + void respawn() + { + dynent::reset(); + fpsstate::respawn(); + respawned = suicided = -1; + lastaction = 0; + lastattackgun = gunselect; + attacking = false; + lasttaunt = 0; + lastpickup = -1; + lastpickupmillis = 0; + lastbase = lastrepammo = -1; + flagpickup = 0; + tokens = 0; + lastcollect = vec(-1e10f, -1e10f, -1e10f); + stopattacksound(); + lastnode = -1; + } + + int respawnwait(int secs, int delay = 0) + { + return max(0, secs - (::lastmillis - lastpain - delay)/1000); + } }; struct teamscore { - const char *team; - int score; - teamscore() {} - teamscore(const char *s, int n) : team(s), score(n) {} - - static bool compare(const teamscore &x, const teamscore &y) - { - if(x.score > y.score) return true; - if(x.score < y.score) return false; - return strcmp(x.team, y.team) < 0; - } + const char *team; + int score; + teamscore() {} + teamscore(const char *s, int n) : team(s), score(n) {} + + static bool compare(const teamscore &x, const teamscore &y) + { + if(x.score > y.score) return true; + if(x.score < y.score) return false; + return strcmp(x.team, y.team) < 0; + } }; static inline uint hthash(const teamscore &t) { return hthash(t.team); } @@ -536,8 +536,8 @@ static inline bool htcmp(const char *key, const teamscore &t) { return htcmp(key struct teaminfo { - char team[MAXTEAMLEN+1]; - int frags; + char team[MAXTEAMLEN+1]; + int frags; }; static inline uint hthash(const teaminfo &t) { return hthash(t.team); } @@ -545,157 +545,157 @@ static inline bool htcmp(const char *team, const teaminfo &t) { return !strcmp(t namespace entities { - extern vector ents; - - extern const char *entmdlname(int type); - extern const char *itemname(int i); - extern int itemicon(int i); - - extern void preloadentities(); - extern void renderentities(); - extern void checkitems(fpsent *d); - extern void checkquad(int time, fpsent *d); - extern void resetspawns(); - extern void spawnitems(bool force = false); - extern void putitems(packetbuf &p); - extern void setspawn(int i, bool on); - extern void teleport(int n, fpsent *d); - extern void pickupeffects(int n, fpsent *d); - extern void teleporteffects(fpsent *d, int tp, int td, bool local = true); - extern void jumppadeffects(fpsent *d, int jp, bool local = true); - - extern void repammo(fpsent *d, int type, bool local = true); + extern vector ents; + + extern const char *entmdlname(int type); + extern const char *itemname(int i); + extern int itemicon(int i); + + extern void preloadentities(); + extern void renderentities(); + extern void checkitems(fpsent *d); + extern void checkquad(int time, fpsent *d); + extern void resetspawns(); + extern void spawnitems(bool force = false); + extern void putitems(packetbuf &p); + extern void setspawn(int i, bool on); + extern void teleport(int n, fpsent *d); + extern void pickupeffects(int n, fpsent *d); + extern void teleporteffects(fpsent *d, int tp, int td, bool local = true); + extern void jumppadeffects(fpsent *d, int jp, bool local = true); + + extern void repammo(fpsent *d, int type, bool local = true); } namespace game { - // fps - extern int gamemode, nextmode; - extern string clientmap; - extern bool intermission; - extern int maptime, maprealtime, maplimit; - extern fpsent *player1; - extern vector players, clients; - extern int lastspawnattempt; - extern int lasthit; - extern int respawnent; - extern int following; - extern int smoothmove, smoothdist; - - extern bool clientoption(const char *arg); - extern fpsent *getclient(int cn); - extern fpsent *newclient(int cn); - extern const char *colorname(fpsent *d, const char *name = NULL, const char *prefix = "", const char *suffix = "", const char *alt = NULL); - extern const char *teamcolorname(fpsent *d, const char *alt = "you"); - extern const char *teamcolor(const char *name, bool sameteam, const char *alt = NULL); - extern const char *teamcolor(const char *name, const char *team, const char *alt = NULL); - extern void teamsound(bool sameteam, int n, const vec *loc = NULL); - extern void teamsound(fpsent *d, int n, const vec *loc = NULL); - extern fpsent *pointatplayer(); - extern fpsent *hudplayer(); - extern fpsent *followingplayer(fpsent *fallback = NULL); - extern void stopfollowing(); - extern void clientdisconnected(int cn, bool notify = true); - extern void clearclients(bool notify = true); - extern void startgame(); - extern float proximityscore(float x, float lower, float upper); - extern void pickgamespawn(fpsent *d); - extern void spawnplayer(fpsent *d); - extern void deathstate(fpsent *d, bool restore = false); - extern void damaged(int damage, fpsent *d, fpsent *actor, bool local = true); - extern void killed(fpsent *d, fpsent *actor); - extern void timeupdate(int timeremain); - extern void msgsound(int n, physent *d = NULL); - extern void drawicon(int icon, float x, float y, float sz = 120); - const char *mastermodecolor(int n, const char *unknown); - const char *mastermodeicon(int n, const char *unknown); - - // client - extern bool connected, remote, demoplayback; - extern string servinfo; - extern vector messages; - - extern int parseplayer(const char *arg); - extern void ignore(int cn); - extern void unignore(int cn); - extern bool isignored(int cn); - extern bool addmsg(int type, const char *fmt = NULL, ...); - extern void switchname(const char *name); - extern void switchteam(const char *name); - extern void sendmapinfo(); - extern void stopdemo(); - extern void changemap(const char *name, int mode); - extern void forceintermission(); - extern void c2sinfo(bool force = false); - extern void sendposition(fpsent *d, bool reliable = false); - - // weapon - extern int getweapon(const char *name); - extern void shoot(fpsent *d, const vec &targ); - extern void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int id, int prevaction); - extern void explode(bool local, fpsent *owner, const vec &v, dynent *safe, int dam, int gun); - extern void explodeeffects(int gun, fpsent *d, bool local, int id = 0); - extern void damageeffect(int damage, fpsent *d, bool thirdperson = true); - extern float intersectdist; - extern bool intersect(dynent *d, const vec &from, const vec &to, float &dist = intersectdist); - extern dynent *intersectclosest(const vec &from, const vec &to, fpsent *at, float &dist = intersectdist); - extern void clearbouncers(); - extern void updatebouncers(int curtime); - extern void removebouncers(fpsent *owner); - extern void renderbouncers(); - extern void clearprojectiles(); - extern void updateprojectiles(int curtime); - extern void removeprojectiles(fpsent *owner); - extern void renderprojectiles(); - extern void preloadbouncers(); - extern void removeweapons(fpsent *owner); - extern void updateweapons(int curtime); - extern void gunselect(int gun, fpsent *d); - extern void weaponswitch(fpsent *d); - extern void avoidweapons(ai::avoidset &obstacles, float radius); - - // scoreboard - extern void showscores(bool on); - extern void getbestplayers(vector &best); - extern void getbestteams(vector &best); - extern void clearteaminfo(); - extern void setteaminfo(const char *team, int frags); - extern int statuscolor(fpsent *d, int color); - - // render - struct playermodelinfo - { - const char *ffa, *blueteam, *redteam, *hudguns, - *vwep, *quad, *armour[3], - *ffaicon, *blueicon, *redicon; - bool ragdoll; - }; - - extern int playermodel, teamskins, testteam; - - extern void saveragdoll(fpsent *d); - extern void clearragdolls(); - extern void moveragdolls(); - extern const playermodelinfo &getplayermodelinfo(fpsent *d); - extern void swayhudgun(int curtime); - extern vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d); + // fps + extern int gamemode, nextmode; + extern string clientmap; + extern bool intermission; + extern int maptime, maprealtime, maplimit; + extern fpsent *player1; + extern vector players, clients; + extern int lastspawnattempt; + extern int lasthit; + extern int respawnent; + extern int following; + extern int smoothmove, smoothdist; + + extern bool clientoption(const char *arg); + extern fpsent *getclient(int cn); + extern fpsent *newclient(int cn); + extern const char *colorname(fpsent *d, const char *name = NULL, const char *prefix = "", const char *suffix = "", const char *alt = NULL); + extern const char *teamcolorname(fpsent *d, const char *alt = "you"); + extern const char *teamcolor(const char *name, bool sameteam, const char *alt = NULL); + extern const char *teamcolor(const char *name, const char *team, const char *alt = NULL); + extern void teamsound(bool sameteam, int n, const vec *loc = NULL); + extern void teamsound(fpsent *d, int n, const vec *loc = NULL); + extern fpsent *pointatplayer(); + extern fpsent *hudplayer(); + extern fpsent *followingplayer(fpsent *fallback = NULL); + extern void stopfollowing(); + extern void clientdisconnected(int cn, bool notify = true); + extern void clearclients(bool notify = true); + extern void startgame(); + extern float proximityscore(float x, float lower, float upper); + extern void pickgamespawn(fpsent *d); + extern void spawnplayer(fpsent *d); + extern void deathstate(fpsent *d, bool restore = false); + extern void damaged(int damage, fpsent *d, fpsent *actor, bool local = true); + extern void killed(fpsent *d, fpsent *actor); + extern void timeupdate(int timeremain); + extern void msgsound(int n, physent *d = NULL); + extern void drawicon(int icon, float x, float y, float sz = 120); + const char *mastermodecolor(int n, const char *unknown); + const char *mastermodeicon(int n, const char *unknown); + + // client + extern bool connected, remote, demoplayback; + extern string servinfo; + extern vector messages; + + extern int parseplayer(const char *arg); + extern void ignore(int cn); + extern void unignore(int cn); + extern bool isignored(int cn); + extern bool addmsg(int type, const char *fmt = NULL, ...); + extern void switchname(const char *name); + extern void switchteam(const char *name); + extern void sendmapinfo(); + extern void stopdemo(); + extern void changemap(const char *name, int mode); + extern void forceintermission(); + extern void c2sinfo(bool force = false); + extern void sendposition(fpsent *d, bool reliable = false); + + // weapon + extern int getweapon(const char *name); + extern void shoot(fpsent *d, const vec &targ); + extern void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int id, int prevaction); + extern void explode(bool local, fpsent *owner, const vec &v, dynent *safe, int dam, int gun); + extern void explodeeffects(int gun, fpsent *d, bool local, int id = 0); + extern void damageeffect(int damage, fpsent *d, bool thirdperson = true); + extern float intersectdist; + extern bool intersect(dynent *d, const vec &from, const vec &to, float &dist = intersectdist); + extern dynent *intersectclosest(const vec &from, const vec &to, fpsent *at, float &dist = intersectdist); + extern void clearbouncers(); + extern void updatebouncers(int curtime); + extern void removebouncers(fpsent *owner); + extern void renderbouncers(); + extern void clearprojectiles(); + extern void updateprojectiles(int curtime); + extern void removeprojectiles(fpsent *owner); + extern void renderprojectiles(); + extern void preloadbouncers(); + extern void removeweapons(fpsent *owner); + extern void updateweapons(int curtime); + extern void gunselect(int gun, fpsent *d); + extern void weaponswitch(fpsent *d); + extern void avoidweapons(ai::avoidset &obstacles, float radius); + + // scoreboard + extern void showscores(bool on); + extern void getbestplayers(vector &best); + extern void getbestteams(vector &best); + extern void clearteaminfo(); + extern void setteaminfo(const char *team, int frags); + extern int statuscolor(fpsent *d, int color); + + // render + struct playermodelinfo + { + const char *ffa, *blueteam, *redteam, *hudguns, + *vwep, *quad, *armour[3], + *ffaicon, *blueicon, *redicon; + bool ragdoll; + }; + + extern int playermodel, teamskins, testteam; + + extern void saveragdoll(fpsent *d); + extern void clearragdolls(); + extern void moveragdolls(); + extern const playermodelinfo &getplayermodelinfo(fpsent *d); + extern void swayhudgun(int curtime); + extern vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d); } namespace server { - extern const char *modename(int n, const char *unknown = "unknown"); - extern const char *mastermodename(int n, const char *unknown = "unknown"); - extern void startintermission(); - extern void stopdemo(); - extern void timeupdate(int secs); - extern const char *getdemofile(const char *file, bool init); - extern void forcemap(const char *map, int mode); - extern void forcepaused(bool paused); - extern void forcegamespeed(int speed); - extern void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen = MAXSTRLEN); - extern int msgsizelookup(int msg); - extern bool serveroption(const char *arg); - extern bool delayspawn(int type); + extern const char *modename(int n, const char *unknown = "unknown"); + extern const char *mastermodename(int n, const char *unknown = "unknown"); + extern void startintermission(); + extern void stopdemo(); + extern void timeupdate(int secs); + extern const char *getdemofile(const char *file, bool init); + extern void forcemap(const char *map, int mode); + extern void forcepaused(bool paused); + extern void forcegamespeed(int speed); + extern void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen = MAXSTRLEN); + extern int msgsizelookup(int msg); + extern bool serveroption(const char *arg); + extern bool delayspawn(int type); } #endif diff --git a/src/fpsgame/render.cpp b/src/fpsgame/render.cpp index b79d73a..f35daec 100644 --- a/src/fpsgame/render.cpp +++ b/src/fpsgame/render.cpp @@ -5,494 +5,460 @@ extern float gatherspawninfos(dynent *d, int tag, vector &spawninfos) namespace game { - vector bestplayers; - vector bestteams; - - VARP(ragdoll, 0, 1, 1); - VARP(ragdollmillis, 0, 10000, 300000); - VARP(ragdollfade, 0, 1000, 300000); - VARP(playermodel, 0, 0, 0); - VARP(hidedead, 0, 0, 2); - - vector ragdolls; - - void saveragdoll(fpsent *d) - { - if(!d->ragdoll || !ragdollmillis || (!ragdollfade && lastmillis > d->lastpain + ragdollmillis)) return; - fpsent *r = new fpsent(*d); - r->lastupdate = ragdollfade && lastmillis > d->lastpain + max(ragdollmillis - ragdollfade, 0) ? lastmillis - max(ragdollmillis - ragdollfade, 0) : d->lastpain; - r->edit = NULL; - r->ai = NULL; - r->attackchan = r->idlechan = -1; - if(d==player1) r->playermodel = playermodel; - ragdolls.add(r); - d->ragdoll = NULL; - } - - void clearragdolls() - { - ragdolls.deletecontents(); - } - - void moveragdolls() - { - loopv(ragdolls) - { - fpsent *d = ragdolls[i]; - if(lastmillis > d->lastupdate + ragdollmillis) - { - delete ragdolls.remove(i--); - continue; - } - moveragdoll(d); - } - } - - static const playermodelinfo playermodels[1] = - { - { "mrfixit", "mrfixit/blue", "mrfixit/red", "mrfixit/hudguns", NULL, "mrfixit/horns", { "mrfixit/armor/blue", "mrfixit/armor/green", "mrfixit/armor/yellow" }, "mrfixit", "mrfixit_blue", "mrfixit_red", true }, - }; - - const playermodelinfo *getplayermodelinfo(int n) - { - (void) n; - return &playermodels[0]; - } - - const playermodelinfo &getplayermodelinfo(fpsent *d) - { - const playermodelinfo *mdl = getplayermodelinfo(0); - if(!mdl) mdl = getplayermodelinfo(playermodel); - return *mdl; - } - - void preloadplayermodel() - { - const playermodelinfo *mdl = getplayermodelinfo(0); - if(m_teammode) - { - preloadmodel(mdl->blueteam); - preloadmodel(mdl->redteam); - } - else preloadmodel(mdl->ffa); - if(mdl->vwep) preloadmodel(mdl->vwep); - if(mdl->quad) preloadmodel(mdl->quad); - loopj(3) if(mdl->armour[j]) preloadmodel(mdl->armour[j]); - } - - VAR(testquad, 0, 0, 1); - VAR(testarmour, 0, 0, 1); - VAR(testteam, 0, 0, 3); - - void renderplayer(fpsent *d, const playermodelinfo &mdl, int team, float fade, bool mainpass) - { - int lastaction = d->lastaction, hold = mdl.vwep || d->gunselect==GUN_PISTOL ? 0 : (ANIM_HOLD1+d->gunselect)|ANIM_LOOP, attack = ANIM_ATTACK1+d->gunselect, delay = mdl.vwep ? 300 : guns[d->gunselect].attackdelay+50; - if(intermission && d->state!=CS_DEAD) - { - lastaction = 0; - hold = attack = ANIM_LOSE|ANIM_LOOP; - delay = 0; - if(m_teammode ? bestteams.htfind(d->team)>=0 : bestplayers.find(d)>=0) hold = attack = ANIM_WIN|ANIM_LOOP; - } - else if(d->state==CS_ALIVE && d->lasttaunt && lastmillis-d->lasttaunt<1000 && lastmillis-d->lastaction>delay) - { - lastaction = d->lasttaunt; - hold = attack = ANIM_TAUNT; - delay = 1000; - } - modelattach a[5]; - static const char * const vweps[] = {"vwep/fist", "vwep/shotg", "vwep/chaing", "vwep/rocket", "vwep/rifle", "vwep/gl", "vwep/pistol"}; - int ai = 0; - if((!mdl.vwep || d->gunselect!=GUN_FIST) && d->gunselect<=GUN_PISTOL) - { - int vanim = ANIM_VWEP_IDLE|ANIM_LOOP, vtime = 0; - if(lastaction && d->lastattackgun==d->gunselect && lastmillis < lastaction + delay) - { - vanim = ANIM_VWEP_SHOOT; - vtime = lastaction; - } - a[ai++] = modelattach("tag_weapon", mdl.vwep ? mdl.vwep : vweps[d->gunselect], vanim, vtime); - } - if(d->state==CS_ALIVE) - { - if((testquad || d->quadmillis) && mdl.quad) - a[ai++] = modelattach("tag_powerup", mdl.quad, ANIM_POWERUP|ANIM_LOOP, 0); - if(testarmour || d->armour) - { - int type = clamp(d->armourtype, (int)A_BLUE, (int)A_YELLOW); - if(mdl.armour[type]) - a[ai++] = modelattach("tag_shield", mdl.armour[type], ANIM_SHIELD|ANIM_LOOP, 0); - } - } - if(mainpass) - { - d->muzzle = vec(-1, -1, -1); - a[ai++] = modelattach("tag_muzzle", &d->muzzle); - } - const char *mdlname = mdl.ffa; - switch(testteam ? testteam-1 : team) - { - case 1: mdlname = mdl.blueteam; break; - case 2: mdlname = mdl.redteam; break; - } - renderclient(d, mdlname, a[0].tag ? a : NULL, hold, attack, delay, lastaction, intermission && d->state!=CS_DEAD ? 0 : d->lastpain, fade, ragdoll && mdl.ragdoll); - } - - VARP(teamskins, 0, 0, 1); + vector bestplayers; + vector bestteams; + + VARP(ragdoll, 0, 1, 1); + VARP(ragdollmillis, 0, 10000, 300000); + VARP(ragdollfade, 0, 1000, 300000); + VARP(playermodel, 0, 0, 0); + VARP(hidedead, 0, 0, 2); + + vector ragdolls; + + void saveragdoll(fpsent *d) + { + if(!d->ragdoll || !ragdollmillis || (!ragdollfade && lastmillis > d->lastpain + ragdollmillis)) return; + fpsent *r = new fpsent(*d); + r->lastupdate = ragdollfade && lastmillis > d->lastpain + max(ragdollmillis - ragdollfade, 0) ? lastmillis - max(ragdollmillis - ragdollfade, 0) : d->lastpain; + r->edit = NULL; + r->ai = NULL; + r->attackchan = r->idlechan = -1; + if(d==player1) r->playermodel = playermodel; + ragdolls.add(r); + d->ragdoll = NULL; + } + + void clearragdolls() + { + ragdolls.deletecontents(); + } + + void moveragdolls() + { + loopv(ragdolls) + { + fpsent *d = ragdolls[i]; + if(lastmillis > d->lastupdate + ragdollmillis) + { + delete ragdolls.remove(i--); + continue; + } + moveragdoll(d); + } + } + + static const playermodelinfo playermodels[1] = + { + { "mrfixit", "mrfixit/blue", "mrfixit/red", "mrfixit/hudguns", NULL, "mrfixit/horns", { "mrfixit/armor/blue", "mrfixit/armor/green", "mrfixit/armor/yellow" }, "mrfixit", "mrfixit_blue", "mrfixit_red", true }, + }; + + const playermodelinfo *getplayermodelinfo(int n) + { + (void) n; + return &playermodels[0]; + } + + const playermodelinfo &getplayermodelinfo(fpsent *d) + { + const playermodelinfo *mdl = getplayermodelinfo(0); + if(!mdl) mdl = getplayermodelinfo(playermodel); + return *mdl; + } + + void preloadplayermodel() + { + const playermodelinfo *mdl = getplayermodelinfo(0); + if(m_teammode) + { + preloadmodel(mdl->blueteam); + preloadmodel(mdl->redteam); + } + else preloadmodel(mdl->ffa); + if(mdl->vwep) preloadmodel(mdl->vwep); + if(mdl->quad) preloadmodel(mdl->quad); + loopj(3) if(mdl->armour[j]) preloadmodel(mdl->armour[j]); + } + + VAR(testquad, 0, 0, 1); + VAR(testarmour, 0, 0, 1); + VAR(testteam, 0, 0, 3); + + void renderplayer(fpsent *d, const playermodelinfo &mdl, int team, float fade, bool mainpass) + { + int lastaction = d->lastaction, hold = mdl.vwep || d->gunselect==GUN_PISTOL ? 0 : (ANIM_HOLD1+d->gunselect)|ANIM_LOOP, attack = ANIM_ATTACK1+d->gunselect, delay = mdl.vwep ? 300 : guns[d->gunselect].attackdelay+50; + if(intermission && d->state!=CS_DEAD) + { + lastaction = 0; + hold = attack = ANIM_LOSE|ANIM_LOOP; + delay = 0; + if(m_teammode ? bestteams.htfind(d->team)>=0 : bestplayers.find(d)>=0) hold = attack = ANIM_WIN|ANIM_LOOP; + } + else if(d->state==CS_ALIVE && d->lasttaunt && lastmillis-d->lasttaunt<1000 && lastmillis-d->lastaction>delay) + { + lastaction = d->lasttaunt; + hold = attack = ANIM_TAUNT; + delay = 1000; + } + modelattach a[5]; + static const char * const vweps[] = {"vwep/fist", "vwep/shotg", "vwep/chaing", "vwep/rocket", "vwep/rifle", "vwep/gl", "vwep/pistol"}; + int ai = 0; + if((!mdl.vwep || d->gunselect!=GUN_FIST) && d->gunselect<=GUN_PISTOL) + { + int vanim = ANIM_VWEP_IDLE|ANIM_LOOP, vtime = 0; + if(lastaction && d->lastattackgun==d->gunselect && lastmillis < lastaction + delay) + { + vanim = ANIM_VWEP_SHOOT; + vtime = lastaction; + } + a[ai++] = modelattach("tag_weapon", mdl.vwep ? mdl.vwep : vweps[d->gunselect], vanim, vtime); + } + if(d->state==CS_ALIVE) + { + if((testquad || d->quadmillis) && mdl.quad) + a[ai++] = modelattach("tag_powerup", mdl.quad, ANIM_POWERUP|ANIM_LOOP, 0); + if(testarmour || d->armour) + { + int type = clamp(d->armourtype, (int)A_BLUE, (int)A_YELLOW); + if(mdl.armour[type]) + a[ai++] = modelattach("tag_shield", mdl.armour[type], ANIM_SHIELD|ANIM_LOOP, 0); + } + } + if(mainpass) + { + d->muzzle = vec(-1, -1, -1); + a[ai++] = modelattach("tag_muzzle", &d->muzzle); + } + const char *mdlname = mdl.ffa; + switch(testteam ? testteam-1 : team) + { + case 1: mdlname = mdl.blueteam; break; + case 2: mdlname = mdl.redteam; break; + } + renderclient(d, mdlname, a[0].tag ? a : NULL, hold, attack, delay, lastaction, intermission && d->state!=CS_DEAD ? 0 : d->lastpain, fade, ragdoll && mdl.ragdoll); + } + + VARP(teamskins, 0, 0, 1); + + VARP(statusicons, 0, 1, 1); + + void renderstatusicons(fpsent *d, int team, float yoffset)///TODO + { + vec p = d->abovehead().madd(camup, yoffset); + int icons = 0; + const itemstat &boost = itemstats[I_BOOST-I_SHELLS]; + if(statusicons && (d->state==CS_ALIVE || d->state==CS_LAGGED)) + { + if(d->quadmillis) icons++; + if(d->maxhealth>100) icons += (min(d->maxhealth, boost.max) - 100 + boost.info-1) / boost.info; + if(d->armour>0 && d->armourtype>=A_GREEN && !m_noitems) icons++; + } + if(icons) concatstring(d->info, " "); + particle_text(p, d->info, PART_TEXT, 1, team ? (team==1 ? 0x6496FF : 0xFF4B19) : 0x1EC850, 2.0f, 0, icons); + if(icons) + { + float tw, th; + text_boundsf(d->info, tw, th); + float offset = (tw - icons*th)/2; + if(d->armour>0 && d->armourtype>=A_GREEN && !m_noitems) + { + int icon = itemstats[(d->armourtype==A_YELLOW ? I_YELLOWARMOUR : I_GREENARMOUR)-I_SHELLS].icon; + particle_texticon(p, icon%4, icon/4, offset, PART_TEXT_ICON, 1, 0xFFFFFF, 2.0f); + offset += th; + } + for(int i = 100; i < min(d->maxhealth, boost.max); i += boost.info) + { + particle_texticon(p, boost.icon%4, boost.icon/4, offset, PART_TEXT_ICON, 1, 0xFFFFFF, 2.0f); + offset += th; + } + if(d->quadmillis) + { + int icon = itemstats[I_QUAD-I_SHELLS].icon; + particle_texticon(p, icon%4, icon/4, offset, PART_TEXT_ICON, 1, 0xFFFFFF, 2.0f); + offset += th; + } + } + } + + VARP(statusbars, 0, 1, 2); + FVARP(statusbarscale, 0, 1, 2); + + float renderstatusbars(fpsent *d, int team)///TODO + { + if(!statusbars || m_insta || (player1->state==CS_SPECTATOR ? statusbars <= 1 : team != 1) || (d->state!=CS_ALIVE && d->state!=CS_LAGGED)) return 0; + vec p = d->abovehead().msub(camdir, 50/80.0f).msub(camup, 2.0f); + float offset = 0; + float scale = statusbarscale; + if(d->armour > 0) + { + int limit = d->armourtype==A_YELLOW ? 200 : (d->armourtype==A_GREEN ? 100 : 50); + int color = d->armourtype==A_YELLOW ? 0xFFC040 : (d->armourtype==A_GREEN ? 0x008C00 : 0x0B5899); + float size = scale*sqrtf(max(d->armour, limit)/100.0f); + float fill = float(d->armour)/limit; + offset += size; + particle_meter(vec(p).madd(camup, offset), fill, PART_METER, 1, color, 0, size); + } + int color = d->health<=25 ? 0xFF0000 : (d->health<=50 ? 0xFF8000 : (d->health<=100 ? 0x40FF80 : 0x40C0FF)); + float size = scale*sqrtf(max(d->health, d->maxhealth)/100.0f); + float fill = float(d->health)/d->maxhealth; + offset += size; + particle_meter(vec(p).madd(camup, offset), fill, PART_METER, 1, color, 0, size); + return offset; + } + + void rendergame(bool mainpass) + { + if(mainpass) ai::render(); + + if(intermission) + { + bestteams.shrink(0); + bestplayers.shrink(0); + if(m_teammode) getbestteams(bestteams); + else getbestplayers(bestplayers); + } + + startmodelbatches(); + + fpsent *exclude = isthirdperson() ? NULL : followingplayer(); + loopv(players) + { + fpsent *d = players[i]; + if(d == player1 || d->state==CS_SPECTATOR || d->state==CS_SPAWNING || d->lifesequence < 0 || d == exclude || (d->state==CS_DEAD && hidedead)) continue; + int team = 0; + if(teamskins || m_teammode) team = isteam(player1->team, d->team) ? 1 : 2; + renderplayer(d, getplayermodelinfo(d), team, 1, mainpass); + + vec dir = vec(d->o).sub(camera1->o); + float dist = dir.magnitude(); + dir.div(dist); + if(d->state!=CS_EDITING && raycube(camera1->o, dir, dist, 0) < dist) + { + d->info[0] = '\0'; + continue; + } + + copystring(d->info, colorname(d)); + if(d->state!=CS_DEAD) + { + float offset = renderstatusbars(d, team); + renderstatusicons(d, team, offset); + } + } + loopv(ragdolls) + { + fpsent *d = ragdolls[i]; + int team = 0; + if(teamskins || m_teammode) team = isteam(player1->team, d->team) ? 1 : 2; + float fade = 1.0f; + if(ragdollmillis && ragdollfade) + fade -= clamp(float(lastmillis - (d->lastupdate + max(ragdollmillis - ragdollfade, 0)))/min(ragdollmillis, ragdollfade), 0.0f, 1.0f); + renderplayer(d, getplayermodelinfo(d), team, fade, mainpass); + } + if(isthirdperson() && !followingplayer() && (player1->state!=CS_DEAD || hidedead != 1)) renderplayer(player1, getplayermodelinfo(player1), teamskins || m_teammode ? 1 : 0, 1, mainpass); + entities::renderentities(); + renderbouncers(); + renderprojectiles(); + + endmodelbatches(); + } + + VARP(hudgun, 0, 1, 1); + VARP(hudgunsway, 0, 1, 1); + VARP(teamhudguns, 0, 1, 1); + VARP(chainsawhudgun, 0, 1, 1); + VAR(testhudgun, 0, 0, 1); + + FVAR(swaystep, 1, 35.0f, 100); + FVAR(swayside, 0, 0.04f, 1); + FVAR(swayup, -1, 0.05f, 1); + + float swayfade = 0, swayspeed = 0, swaydist = 0; + vec swaydir(0, 0, 0); + + void swayhudgun(int curtime) + { + fpsent *d = hudplayer(); + if(d->state != CS_SPECTATOR) + { + if(d->physstate >= PHYS_SLOPE) + { + swayspeed = min(sqrtf(d->vel.x*d->vel.x + d->vel.y*d->vel.y), d->maxspeed); + swaydist += swayspeed*curtime/1000.0f; + swaydist = fmod(swaydist, 2*swaystep); + swayfade = 1; + } + else if(swayfade > 0) + { + swaydist += swayspeed*swayfade*curtime/1000.0f; + swaydist = fmod(swaydist, 2*swaystep); + swayfade -= 0.5f*(curtime*d->maxspeed)/(swaystep*1000.0f); + } + + float k = pow(0.7f, curtime/10.0f); + swaydir.mul(k); + vec vel(d->vel); + vel.add(d->falling); + swaydir.add(vec(vel).mul((1-k)/(15*max(vel.magnitude(), d->maxspeed)))); + } + } + + struct hudent : dynent + { + hudent() { type = ENT_CAMERA; } + } guninterp; + + SVARP(hudgunsdir, ""); + + void drawhudmodel(fpsent *d, int anim, float speed = 0, int base = 0) + { + if(d->gunselect>GUN_PISTOL) return; + + vec sway; + vecfromyawpitch(d->yaw, 0, 0, 1, sway); + float steps = swaydist/swaystep*M_PI; + sway.mul(swayside*cosf(steps)); + sway.z = swayup*(fabs(sinf(steps)) - 1); + sway.add(swaydir).add(d->o); + if(!hudgunsway) sway = d->o; #if 0 - // for testing spawns - - float hsv2rgb(float h, float s, float v, int n) - { - float k = fmod(n + h / 60.0f, 6.0f); - return v - v * s * max(min(min(k, 4.0f - k), 1.0f), 0.0f); - } - - vec hsv2rgb(float h, float s, float v) - { - return vec(hsv2rgb(h, s, v, 5), hsv2rgb(h, s, v, 3), hsv2rgb(h, s, v, 1)); - } - - void renderspawn(const vec &o, int rating, float probability) - { - defformatstring(score, "%d", rating); - defformatstring(percentage, "(%.2f%%)", probability * 100); - bvec colorvec = bvec::fromcolor(hsv2rgb(rating * 1.2f, 0.8, 1)); - int color = (colorvec.r << 16) + (colorvec.g << 8) + colorvec.b; - particle_textcopy(vec(o).addz(5), score, PART_TEXT, 1, color, 5.0f); - particle_textcopy(vec(o).addz(1), percentage, PART_TEXT, 1, color, 4.0f); - } - - void renderspawns() - { - vector spawninfos; - float ratingsum = gatherspawninfos(player1, 0, spawninfos); - loopv(spawninfos) renderspawn(spawninfos[i].e->o, spawninfos[i].weight * 100, spawninfos[i].weight / ratingsum); - } - - VAR(dbgspawns, 0, 0, 1); + if(player1->state!=CS_DEAD && player1->quadmillis) + { + float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f); + color.y = color.y*(1-t) + t; + } #endif - - VARP(statusicons, 0, 1, 1); - - void renderstatusicons(fpsent *d, int team, float yoffset)///TODO - { - vec p = d->abovehead().madd(camup, yoffset); - int icons = 0; - const itemstat &boost = itemstats[I_BOOST-I_SHELLS]; - if(statusicons && (d->state==CS_ALIVE || d->state==CS_LAGGED)) - { - if(d->quadmillis) icons++; - if(d->maxhealth>100) icons += (min(d->maxhealth, boost.max) - 100 + boost.info-1) / boost.info; - if(d->armour>0 && d->armourtype>=A_GREEN && !m_noitems) icons++; - } - if(icons) concatstring(d->info, " "); - particle_text(p, d->info, PART_TEXT, 1, team ? (team==1 ? 0x6496FF : 0xFF4B19) : 0x1EC850, 2.0f, 0, icons); - if(icons) - { - float tw, th; - text_boundsf(d->info, tw, th); - float offset = (tw - icons*th)/2; - if(d->armour>0 && d->armourtype>=A_GREEN && !m_noitems) - { - int icon = itemstats[(d->armourtype==A_YELLOW ? I_YELLOWARMOUR : I_GREENARMOUR)-I_SHELLS].icon; - particle_texticon(p, icon%4, icon/4, offset, PART_TEXT_ICON, 1, 0xFFFFFF, 2.0f); - offset += th; - } - for(int i = 100; i < min(d->maxhealth, boost.max); i += boost.info) - { - particle_texticon(p, boost.icon%4, boost.icon/4, offset, PART_TEXT_ICON, 1, 0xFFFFFF, 2.0f); - offset += th; - } - if(d->quadmillis) - { - int icon = itemstats[I_QUAD-I_SHELLS].icon; - particle_texticon(p, icon%4, icon/4, offset, PART_TEXT_ICON, 1, 0xFFFFFF, 2.0f); - offset += th; - } - } - } - - VARP(statusbars, 0, 1, 2); - FVARP(statusbarscale, 0, 1, 2); - - float renderstatusbars(fpsent *d, int team)///TODO - { - if(!statusbars || m_insta || (player1->state==CS_SPECTATOR ? statusbars <= 1 : team != 1) || (d->state!=CS_ALIVE && d->state!=CS_LAGGED)) return 0; - vec p = d->abovehead().msub(camdir, 50/80.0f).msub(camup, 2.0f); - float offset = 0; - float scale = statusbarscale; - if(d->armour > 0) - { - int limit = d->armourtype==A_YELLOW ? 200 : (d->armourtype==A_GREEN ? 100 : 50); - int color = d->armourtype==A_YELLOW ? 0xFFC040 : (d->armourtype==A_GREEN ? 0x008C00 : 0x0B5899); - float size = scale*sqrtf(max(d->armour, limit)/100.0f); - float fill = float(d->armour)/limit; - offset += size; - particle_meter(vec(p).madd(camup, offset), fill, PART_METER, 1, color, 0, size); - } - int color = d->health<=25 ? 0xFF0000 : (d->health<=50 ? 0xFF8000 : (d->health<=100 ? 0x40FF80 : 0x40C0FF)); - float size = scale*sqrtf(max(d->health, d->maxhealth)/100.0f); - float fill = float(d->health)/d->maxhealth; - offset += size; - particle_meter(vec(p).madd(camup, offset), fill, PART_METER, 1, color, 0, size); - return offset; - } - - void rendergame(bool mainpass) - { - if(mainpass) ai::render(); - - if(intermission) - { - bestteams.shrink(0); - bestplayers.shrink(0); - if(m_teammode) getbestteams(bestteams); - else getbestplayers(bestplayers); - } - - startmodelbatches(); - - fpsent *exclude = isthirdperson() ? NULL : followingplayer(); - loopv(players) - { - fpsent *d = players[i]; - if(d == player1 || d->state==CS_SPECTATOR || d->state==CS_SPAWNING || d->lifesequence < 0 || d == exclude || (d->state==CS_DEAD && hidedead)) continue; - int team = 0; - if(teamskins || m_teammode) team = isteam(player1->team, d->team) ? 1 : 2; - renderplayer(d, getplayermodelinfo(d), team, 1, mainpass); - - vec dir = vec(d->o).sub(camera1->o); - float dist = dir.magnitude(); - dir.div(dist); - if(d->state!=CS_EDITING && raycube(camera1->o, dir, dist, 0) < dist) - { - d->info[0] = '\0'; - continue; - } - - copystring(d->info, colorname(d)); - if(d->state!=CS_DEAD) - { - float offset = renderstatusbars(d, team); - renderstatusicons(d, team, offset); - } - } - loopv(ragdolls) - { - fpsent *d = ragdolls[i]; - int team = 0; - if(teamskins || m_teammode) team = isteam(player1->team, d->team) ? 1 : 2; - float fade = 1.0f; - if(ragdollmillis && ragdollfade) - fade -= clamp(float(lastmillis - (d->lastupdate + max(ragdollmillis - ragdollfade, 0)))/min(ragdollmillis, ragdollfade), 0.0f, 1.0f); - renderplayer(d, getplayermodelinfo(d), team, fade, mainpass); - } - if(isthirdperson() && !followingplayer() && (player1->state!=CS_DEAD || hidedead != 1)) renderplayer(player1, getplayermodelinfo(player1), teamskins || m_teammode ? 1 : 0, 1, mainpass); - entities::renderentities(); - renderbouncers(); - renderprojectiles(); - - endmodelbatches(); - } - - VARP(hudgun, 0, 1, 1); - VARP(hudgunsway, 0, 1, 1); - VARP(teamhudguns, 0, 1, 1); - VARP(chainsawhudgun, 0, 1, 1); - VAR(testhudgun, 0, 0, 1); - - FVAR(swaystep, 1, 35.0f, 100); - FVAR(swayside, 0, 0.04f, 1); - FVAR(swayup, -1, 0.05f, 1); - - float swayfade = 0, swayspeed = 0, swaydist = 0; - vec swaydir(0, 0, 0); - - void swayhudgun(int curtime) - { - fpsent *d = hudplayer(); - if(d->state != CS_SPECTATOR) - { - if(d->physstate >= PHYS_SLOPE) - { - swayspeed = min(sqrtf(d->vel.x*d->vel.x + d->vel.y*d->vel.y), d->maxspeed); - swaydist += swayspeed*curtime/1000.0f; - swaydist = fmod(swaydist, 2*swaystep); - swayfade = 1; - } - else if(swayfade > 0) - { - swaydist += swayspeed*swayfade*curtime/1000.0f; - swaydist = fmod(swaydist, 2*swaystep); - swayfade -= 0.5f*(curtime*d->maxspeed)/(swaystep*1000.0f); - } - - float k = pow(0.7f, curtime/10.0f); - swaydir.mul(k); - vec vel(d->vel); - vel.add(d->falling); - swaydir.add(vec(vel).mul((1-k)/(15*max(vel.magnitude(), d->maxspeed)))); - } - } - - struct hudent : dynent - { - hudent() { type = ENT_CAMERA; } - } guninterp; - - SVARP(hudgunsdir, ""); - - void drawhudmodel(fpsent *d, int anim, float speed = 0, int base = 0) - { - if(d->gunselect>GUN_PISTOL) return; - - vec sway; - vecfromyawpitch(d->yaw, 0, 0, 1, sway); - float steps = swaydist/swaystep*M_PI; - sway.mul(swayside*cosf(steps)); - sway.z = swayup*(fabs(sinf(steps)) - 1); - sway.add(swaydir).add(d->o); - if(!hudgunsway) sway = d->o; - -#if 0 - if(player1->state!=CS_DEAD && player1->quadmillis) - { - float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f); - color.y = color.y*(1-t) + t; - } -#endif - const playermodelinfo &mdl = getplayermodelinfo(d); - defformatstring(gunname, "%s/%s", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, guns[d->gunselect].file); - if((m_teammode || teamskins) && teamhudguns) - concatstring(gunname, d==player1 || isteam(d->team, player1->team) ? "/blue" : "/red"); - else if(testteam > 1) - concatstring(gunname, testteam==2 ? "/blue" : "/red"); - modelattach a[2]; - d->muzzle = vec(-1, -1, -1); - a[0] = modelattach("tag_muzzle", &d->muzzle); - dynent *interp = NULL; - if(d->gunselect==GUN_FIST && chainsawhudgun) - { - anim |= ANIM_LOOP; - base = 0; - interp = &guninterp; - } - rendermodel(NULL, gunname, anim, sway, testhudgun ? 0 : d->yaw+90, testhudgun ? 0 : d->pitch, MDL_LIGHT|MDL_HUD, interp, a, base, (int)ceil(speed)); - if(d->muzzle.x >= 0) d->muzzle = calcavatarpos(d->muzzle, 12); - } - - void drawhudgun() - { - fpsent *d = hudplayer(); - if(d->state==CS_SPECTATOR || d->state==CS_EDITING || !hudgun || editmode) - { - d->muzzle = player1->muzzle = vec(-1, -1, -1); - return; - } - - int rtime = guns[d->gunselect].attackdelay; - if(d->lastaction && d->lastattackgun==d->gunselect && lastmillis-d->lastactionlastaction); - } - else - { - drawhudmodel(d, ANIM_GUN_IDLE|ANIM_LOOP); - } - } - - void renderavatar() - { - drawhudgun(); - } - - void renderplayerpreview(int model, int team, int weap) - { - static fpsent *previewent = NULL; - if(!previewent) - { - previewent = new fpsent; - previewent->light.color = vec(1, 1, 1); - previewent->light.dir = vec(0, -1, 2).normalize(); - loopi(GUN_PISTOL-GUN_FIST) previewent->ammo[GUN_FIST+1+i] = 1; - } - float height = previewent->eyeheight + previewent->aboveeye, - zrad = height/2; - vec2 xyrad = vec2(previewent->xradius, previewent->yradius).max(height/4); - previewent->o = calcmodelpreviewpos(vec(xyrad, zrad), previewent->yaw).addz(previewent->eyeheight - zrad); - previewent->gunselect = clamp(weap, int(GUN_FIST), int(GUN_PISTOL)); - previewent->light.millis = -1; - const playermodelinfo *mdlinfo = getplayermodelinfo(model); - if(!mdlinfo) return; - renderplayer(previewent, *mdlinfo, team >= 0 && team <= 2 ? team : 0, 1, false); - } - - vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d) - { - if(d->muzzle.x >= 0) return d->muzzle; - vec offset(from); - if(d!=hudplayer() || isthirdperson()) - { - vec front, right; - vecfromyawpitch(d->yaw, d->pitch, 1, 0, front); - offset.add(front.mul(d->radius)); - if(d->type!=ENT_AI) - { - offset.z += (d->aboveeye + d->eyeheight)*0.75f - d->eyeheight; - vecfromyawpitch(d->yaw, 0, 0, -1, right); - offset.add(right.mul(0.5f*d->radius)); - offset.add(front); - } - return offset; - } - offset.add(vec(to).sub(from).normalize().mul(2)); - if(hudgun) - { - offset.sub(vec(camup).mul(1.0f)); - offset.add(vec(camright).mul(0.8f)); - } - else offset.sub(vec(camup).mul(0.8f)); - return offset; - } - - void preloadweapons() - { - const playermodelinfo &mdl = getplayermodelinfo(player1); - loopi(NUMGUNS) - { - const char *file = guns[i].file; - if(!file) continue; - string fname; - if((m_teammode || teamskins) && teamhudguns) - { - formatstring(fname, "%s/%s/blue", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, file); - preloadmodel(fname); - } - else - { - formatstring(fname, "%s/%s", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, file); - preloadmodel(fname); - } - formatstring(fname, "vwep/%s", file); - preloadmodel(fname); - } - } - - void preloadsounds() - { - for(int i = S_JUMP; i <= S_HIT; i++) preloadsound(i); - } - - void preload() - { - if(hudgun) preloadweapons(); - preloadbouncers(); - preloadplayermodel(); - preloadsounds(); - entities::preloadentities(); - } + const playermodelinfo &mdl = getplayermodelinfo(d); + defformatstring(gunname, "%s/%s", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, guns[d->gunselect].file); + if((m_teammode || teamskins) && teamhudguns) + concatstring(gunname, d==player1 || isteam(d->team, player1->team) ? "/blue" : "/red"); + else if(testteam > 1) + concatstring(gunname, testteam==2 ? "/blue" : "/red"); + modelattach a[2]; + d->muzzle = vec(-1, -1, -1); + a[0] = modelattach("tag_muzzle", &d->muzzle); + dynent *interp = NULL; + if(d->gunselect==GUN_FIST && chainsawhudgun) + { + anim |= ANIM_LOOP; + base = 0; + interp = &guninterp; + } + rendermodel(NULL, gunname, anim, sway, testhudgun ? 0 : d->yaw+90, testhudgun ? 0 : d->pitch, MDL_LIGHT|MDL_HUD, interp, a, base, (int)ceil(speed)); + if(d->muzzle.x >= 0) d->muzzle = calcavatarpos(d->muzzle, 12); + } + + void drawhudgun() + { + fpsent *d = hudplayer(); + if(d->state==CS_SPECTATOR || d->state==CS_EDITING || !hudgun || editmode) + { + d->muzzle = player1->muzzle = vec(-1, -1, -1); + return; + } + + int rtime = guns[d->gunselect].attackdelay; + if(d->lastaction && d->lastattackgun==d->gunselect && lastmillis-d->lastactionlastaction); + } + else + { + drawhudmodel(d, ANIM_GUN_IDLE|ANIM_LOOP); + } + } + + void renderavatar() + { + drawhudgun(); + } + + void renderplayerpreview(int model, int team, int weap) + { + static fpsent *previewent = NULL; + if(!previewent) + { + previewent = new fpsent; + previewent->light.color = vec(1, 1, 1); + previewent->light.dir = vec(0, -1, 2).normalize(); + loopi(GUN_PISTOL-GUN_FIST) previewent->ammo[GUN_FIST+1+i] = 1; + } + float height = previewent->eyeheight + previewent->aboveeye, + zrad = height/2; + vec2 xyrad = vec2(previewent->xradius, previewent->yradius).max(height/4); + previewent->o = calcmodelpreviewpos(vec(xyrad, zrad), previewent->yaw).addz(previewent->eyeheight - zrad); + previewent->gunselect = clamp(weap, int(GUN_FIST), int(GUN_PISTOL)); + previewent->light.millis = -1; + const playermodelinfo *mdlinfo = getplayermodelinfo(model); + if(!mdlinfo) return; + renderplayer(previewent, *mdlinfo, team >= 0 && team <= 2 ? team : 0, 1, false); + } + + vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d) + { + if(d->muzzle.x >= 0) return d->muzzle; + vec offset(from); + if(d!=hudplayer() || isthirdperson()) + { + vec front, right; + vecfromyawpitch(d->yaw, d->pitch, 1, 0, front); + offset.add(front.mul(d->radius)); + if(d->type!=ENT_AI) + { + offset.z += (d->aboveeye + d->eyeheight)*0.75f - d->eyeheight; + vecfromyawpitch(d->yaw, 0, 0, -1, right); + offset.add(right.mul(0.5f*d->radius)); + offset.add(front); + } + return offset; + } + offset.add(vec(to).sub(from).normalize().mul(2)); + if(hudgun) + { + offset.sub(vec(camup).mul(1.0f)); + offset.add(vec(camright).mul(0.8f)); + } + else offset.sub(vec(camup).mul(0.8f)); + return offset; + } + + void preloadweapons() + { + const playermodelinfo &mdl = getplayermodelinfo(player1); + loopi(NUMGUNS) + { + const char *file = guns[i].file; + if(!file) continue; + string fname; + if((m_teammode || teamskins) && teamhudguns) + { + formatstring(fname, "%s/%s/blue", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, file); + preloadmodel(fname); + } + else + { + formatstring(fname, "%s/%s", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, file); + preloadmodel(fname); + } + formatstring(fname, "vwep/%s", file); + preloadmodel(fname); + } + } + + void preloadsounds() + { + for(int i = S_JUMP; i <= S_HIT; i++) preloadsound(i); + } + + void preload() + { + if(hudgun) preloadweapons(); + preloadbouncers(); + preloadplayermodel(); + preloadsounds(); + entities::preloadentities(); + } } diff --git a/src/fpsgame/scoreboard.cpp b/src/fpsgame/scoreboard.cpp index 5e0ae17..96f8868 100644 --- a/src/fpsgame/scoreboard.cpp +++ b/src/fpsgame/scoreboard.cpp @@ -3,579 +3,579 @@ namespace game { - VARP(scoreboard2d, 0, 1, 1); - VARP(showservinfo, 0, 1, 1); - VARP(showclientnum, 0, 1, 1); - VARP(showpj, 0, 0, 1); - VARP(showping, 0, 1, 2); - VARP(showspectators, 0, 1, 1); - VARP(showspectatorping, 0, 1, 1); - VARP(highlightscore, 0, 1, 1); - VARP(showconnecting, 0, 0, 1); - VARP(hidefrags, 0, 0, 1); - VARP(showdeaths, 0, 1, 1); - VARP(showdamagedealt, 0, 1, 1); - - static hashset teaminfos; - - void clearteaminfo() - { - teaminfos.clear(); - } - - void setteaminfo(const char *team, int frags) - { - teaminfo *t = teaminfos.access(team); - if(!t) { t = &teaminfos[team]; copystring(t->team, team, sizeof(t->team)); } - t->frags = frags; - } - - static inline bool playersort(const fpsent *a, const fpsent *b) - { - if(a->state==CS_SPECTATOR) - { - if(b->state==CS_SPECTATOR) return strcmp(a->name, b->name) < 0; - else return false; - } - else if(b->state==CS_SPECTATOR) return true; - if(a->frags > b->frags) return true; - if(a->frags < b->frags) return false; - return strcmp(a->name, b->name) < 0; - } - - void getbestplayers(vector &best) - { - loopv(players) - { - fpsent *o = players[i]; - if(o->state!=CS_SPECTATOR) best.add(o); - } - best.sort(playersort); - while(best.length() > 1 && best.last()->frags < best[0]->frags) best.drop(); - } - - void getbestteams(vector &best) - { - if(!hidefrags) - { - vector teamscores; - teamscores.sort(teamscore::compare); - while(teamscores.length() > 1 && teamscores.last().score < teamscores[0].score) teamscores.drop(); - loopv(teamscores) best.add(teamscores[i].team); - } - else - { - int bestfrags = INT_MIN; - enumerate(teaminfos, teaminfo, t, bestfrags = max(bestfrags, t.frags)); - if(bestfrags <= 0) loopv(players) - { - fpsent *o = players[i]; - if(o->state!=CS_SPECTATOR && !teaminfos.access(o->team) && best.htfind(o->team) < 0) { bestfrags = 0; best.add(o->team); } - } - enumerate(teaminfos, teaminfo, t, if(t.frags >= bestfrags) best.add(t.team)); - } - } - - struct scoregroup : teamscore - { - vector players; - }; - static vector groups; - static vector spectators; - - static inline bool scoregroupcmp(const scoregroup *x, const scoregroup *y) - { - if(!x->team) - { - if(y->team) return false; - } - else if(!y->team) return true; - if(x->score > y->score) return true; - if(x->score < y->score) return false; - if(x->players.length() > y->players.length()) return true; - if(x->players.length() < y->players.length()) return false; - return x->team && y->team && strcmp(x->team, y->team) < 0; - } - - static int groupplayers() - { - int numgroups = 0; - spectators.setsize(0); - loopv(players) - { - fpsent *o = players[i]; - if(!showconnecting && !o->name[0]) continue; - if(o->state==CS_SPECTATOR) { spectators.add(o); continue; } - const char *team = m_teammode && o->team[0] ? o->team : NULL; - bool found = false; - loopj(numgroups) - { - scoregroup &g = *groups[j]; - if(team!=g.team && (!team || !g.team || strcmp(team, g.team))) continue; - g.players.add(o); - found = true; - } - if(found) continue; - if(numgroups>=groups.length()) groups.add(new scoregroup); - scoregroup &g = *groups[numgroups++]; - g.team = team; - if(!team) g.score = 0; - else { teaminfo *ti = teaminfos.access(team); g.score = ti ? ti->frags : 0; } - g.players.setsize(0); - g.players.add(o); - } - loopi(numgroups) groups[i]->players.sort(playersort); - spectators.sort(playersort); - groups.sort(scoregroupcmp, 0, numgroups); - return numgroups; - } - - int statuscolor(fpsent *d, int color) - { - if(d->privilege) - { - color = d->privilege>=PRIV_ADMIN ? 0xFF8000 : (d->privilege>=PRIV_AUTH ? 0xC040C0 : 0x40FF80); - if(d->state==CS_DEAD) color = (color>>1)&0x7F7F7F; - } - else if(d->state==CS_DEAD) color = 0x606060; - return color; - } - - void renderscoreboard(g3d_gui &g, bool firstpass) - { - const ENetAddress *address = connectedpeer(); - if(showservinfo && address) - { - string hostname; - if(enet_address_get_host_ip(address, hostname, sizeof(hostname)) >= 0) - { - if(servinfo[0]) g.titlef("%.25s", 0xFFFF80, NULL, servinfo); - else g.titlef("%s:%d", 0xFFFF80, NULL, hostname, address->port); - } - } - - g.pushlist(); - g.spring(); - g.text(server::modename(gamemode), 0xFFFF80); - g.separator(); - const char *mname = getclientmap(); - g.text(mname[0] ? mname : "[new map]", 0xFFFF80); - extern int gamespeed; - if(gamespeed != 100) { g.separator(); g.textf("%d.%02dx", 0xFFFF80, NULL, gamespeed/100, gamespeed%100); } - if(m_timed && mname[0] && (maplimit >= 0 || intermission)) - { - g.separator(); - if(intermission) g.text("intermission", 0xFFFF80); - else - { - int secs = max(maplimit-lastmillis+999, 0)/1000, mins = secs/60; - secs %= 60; - g.pushlist(); - g.strut(mins >= 10 ? 4.5f : 3.5f); - g.textf("%d:%02d", 0xFFFF80, NULL, mins, secs); - g.poplist(); - } - } - if(ispaused()) { g.separator(); g.text("paused", 0xFFFF80); } - g.spring(); - g.poplist(); - - g.separator(); - - int numgroups = groupplayers(); - loopk(numgroups) - { - if((k%2)==0) g.pushlist(); // horizontal - - scoregroup &sg = *groups[k]; - int bgcolor = sg.team && m_teammode ? (isteam(player1->team, sg.team) ? 0x3030C0 : 0xC03030) : 0, - fgcolor = 0xFFFF80; - - g.pushlist(); // vertical - g.pushlist(); // horizontal - - #define loopscoregroup(o, b) \ - loopv(sg.players) \ - { \ - fpsent *o = sg.players[i]; \ - b; \ - } - - g.pushlist(); - if(sg.team && m_teammode) - { - g.pushlist(); - g.background(bgcolor, numgroups>1 ? 3 : 5); - g.strut(1); - g.poplist(); - } - g.text("", 0, " "); - loopscoregroup(o, - { - if(o==player1 && highlightscore && (multiplayer(false) || demoplayback || players.length() > 1)) - { - g.pushlist(); - g.background(0x808080, numgroups>1 ? 3 : 5); - } - const playermodelinfo &mdl = getplayermodelinfo(o); - const char *icon = sg.team && m_teammode ? (isteam(player1->team, sg.team) ? mdl.blueicon : mdl.redicon) : mdl.ffaicon; - g.text("", 0, icon); - if(o==player1 && highlightscore && (multiplayer(false) || demoplayback || players.length() > 1)) g.poplist(); - }); - g.poplist(); - - if(sg.team && m_teammode) - { - g.pushlist(); // vertical - - if(sg.score>=10000) g.textf("%s: WIN", fgcolor, NULL, sg.team); - else g.textf("%s: %d", fgcolor, NULL, sg.team, sg.score); - - g.pushlist(); // horizontal - } - - if(!hidefrags) - { - g.pushlist(); - g.strut(6); - g.text("frags", fgcolor); - loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->frags)); - g.poplist(); - } - - if(showdeaths) - { - g.pushlist(); - g.strut(6); - g.text("deaths", fgcolor); - loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->deaths)); - g.poplist(); - } - - if(showdamagedealt) - { - g.pushlist(); - g.strut(7); - g.text("damage", fgcolor); - loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->totaldamage)); - g.poplist(); - } - - g.pushlist(); - g.text("name", fgcolor); - g.strut(12); - loopscoregroup(o, { g.textf("%s ", statuscolor(o, 0xFFFFDD), NULL, colorname(o)); }); - g.poplist(); - - if(multiplayer(false) || demoplayback) - { - if(showpj || showping) g.space(1); - - if(showpj && showping <= 1) - { - g.pushlist(); - g.strut(6); - g.text("pj", fgcolor); - loopscoregroup(o, - { - if(o->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); - else g.textf("%d", 0xFFFFDD, NULL, o->plag); - }); - g.poplist(); - } - - if(showping > 1) - { - g.pushlist(); - g.strut(6); - - g.pushlist(); - g.text("ping", fgcolor); - g.space(1); - g.spring(); - g.text("pj", fgcolor); - g.poplist(); - - loopscoregroup(o, - { - fpsent *p = o->ownernum >= 0 ? getclient(o->ownernum) : o; - if(!p) p = o; - g.pushlist(); - if(p->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); - else - { - g.textf("%d", 0xFFFFDD, NULL, p->ping); - g.space(1); - g.spring(); - g.textf("%d", 0xFFFFDD, NULL, o->plag); - } - g.poplist(); - - }); - g.poplist(); - } - else if(showping) - { - g.pushlist(); - g.text("ping", fgcolor); - g.strut(6); - loopscoregroup(o, - { - fpsent *p = o->ownernum >= 0 ? getclient(o->ownernum) : o; - if(!p) p = o; - if(!showpj && p->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); - else g.textf("%d", 0xFFFFDD, NULL, p->ping); - }); - g.poplist(); - } - } - - if(showclientnum || player1->privilege>=PRIV_MASTER) - { - g.space(1); - g.pushlist(); - g.text("cn", fgcolor); - loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->clientnum)); - g.poplist(); - } - - if(sg.team && m_teammode) - { - g.poplist(); // horizontal - g.poplist(); // vertical - } - - g.poplist(); // horizontal - g.poplist(); // vertical - - if(k+1privilege>=PRIV_MASTER) - { - g.pushlist(); - - g.pushlist(); - g.text("spectator", 0xFFFF80, " "); - g.strut(12); - loopv(spectators) - { - fpsent *o = spectators[i]; - if(o==player1 && highlightscore) - { - g.pushlist(); - g.background(0x808080, 3); - } - g.text(colorname(o), statuscolor(o, 0xFFFFDD), "spectator"); - if(o==player1 && highlightscore) g.poplist(); - } - g.poplist(); - - if((multiplayer(false) || demoplayback) && showspectatorping) - { - g.space(1); - g.pushlist(); - g.text("ping", 0xFFFF80); - g.strut(6); - loopv(spectators) - { - fpsent *o = spectators[i]; - fpsent *p = o->ownernum >= 0 ? getclient(o->ownernum) : o; - if(!p) p = o; - if(p->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); - else g.textf("%d", 0xFFFFDD, NULL, p->ping); - } - g.poplist(); - } - - g.space(1); - g.pushlist(); - g.text("cn", 0xFFFF80); - loopv(spectators) g.textf("%d", 0xFFFFDD, NULL, spectators[i]->clientnum); - g.poplist(); - - g.poplist(); - } - else - { - g.textf("%d spectator%s", 0xFFFF80, " ", spectators.length(), spectators.length()!=1 ? "s" : ""); - loopv(spectators) - { - if((i%3)==0) - { - g.pushlist(); - g.text("", 0xFFFFDD, "spectator"); - } - fpsent *o = spectators[i]; - if(o==player1 && highlightscore) - { - g.pushlist(); - g.background(0x808080); - } - g.text(colorname(o), statuscolor(o, 0xFFFFDD)); - if(o==player1 && highlightscore) g.poplist(); - if(i+1score; - best = isteam(player1->team, g->team); - if(numgroups > 1) - { - if(best) score2 = groups[1]->score; - else for(int i = 1; i < groups.length(); ++i) if(isteam(player1->team, groups[i]->team)) { score2 = groups[i]->score; break; } - if(score2 == INT_MIN) - { - fpsent *p = followingplayer(player1); - if(p->state==CS_SPECTATOR) score2 = groups[1]->score; - } - } - } - else - { - fpsent *p = followingplayer(player1); - score = g->players[0]->frags; - best = p == g->players[0]; - if(g->players.length() > 1) - { - if(best || p->state==CS_SPECTATOR) score2 = g->players[1]->frags; - else score2 = p->frags; - } - } - if(score == score2 && !best) best = true; - - score = clamp(score, -999, 9999); - defformatstring(buf, "%d", score); - int tw = 0, th = 0; - text_bounds(buf, tw, th); - - string buf2; - int tw2 = 0, th2 = 0; - if(score2 > INT_MIN) - { - score2 = clamp(score2, -999, 9999); - formatstring(buf2, "%d", score2); - text_bounds(buf2, tw2, th2); - } - - int fw = 0, fh = 0; - text_bounds("00", fw, fh); - fw = max(fw, max(tw, tw2)); - - vec2 offset = vec2(hudscorex, hudscorey).mul(vec2(w, h).div(hudscorescale)); - if(hudscorealign == 1) offset.x -= 2*fw + hudscoresep; - else if(hudscorealign == 0) offset.x -= (2*fw + hudscoresep) / 2.0f; - vec2 offset2 = offset; - offset.x += (fw-tw)/2.0f; - offset.y -= th/2.0f; - offset2.x += fw + hudscoresep + (fw-tw2)/2.0f; - offset2.y -= th2/2.0f; - - pushhudmatrix(); - hudmatrix.scale(hudscorescale, hudscorescale, 1); - flushhudmatrix(); - - int color = hudscoreplayercolour, color2 = hudscoreenemycolour; - if(!best) swap(color, color2); - - draw_text(buf, int(offset.x), int(offset.y), (color>>16)&0xFF, (color>>8)&0xFF, color&0xFF, hudscorealpha); - if(score2 > INT_MIN) draw_text(buf2, int(offset2.x), int(offset2.y), (color2>>16)&0xFF, (color2>>8)&0xFF, color2&0xFF, hudscorealpha); - - pophudmatrix(); - } + VARP(scoreboard2d, 0, 1, 1); + VARP(showservinfo, 0, 1, 1); + VARP(showclientnum, 0, 1, 1); + VARP(showpj, 0, 0, 1); + VARP(showping, 0, 1, 2); + VARP(showspectators, 0, 1, 1); + VARP(showspectatorping, 0, 1, 1); + VARP(highlightscore, 0, 1, 1); + VARP(showconnecting, 0, 0, 1); + VARP(hidefrags, 0, 0, 1); + VARP(showdeaths, 0, 1, 1); + VARP(showdamagedealt, 0, 1, 1); + + static hashset teaminfos; + + void clearteaminfo() + { + teaminfos.clear(); + } + + void setteaminfo(const char *team, int frags) + { + teaminfo *t = teaminfos.access(team); + if(!t) { t = &teaminfos[team]; copystring(t->team, team, sizeof(t->team)); } + t->frags = frags; + } + + static inline bool playersort(const fpsent *a, const fpsent *b) + { + if(a->state==CS_SPECTATOR) + { + if(b->state==CS_SPECTATOR) return strcmp(a->name, b->name) < 0; + else return false; + } + else if(b->state==CS_SPECTATOR) return true; + if(a->frags > b->frags) return true; + if(a->frags < b->frags) return false; + return strcmp(a->name, b->name) < 0; + } + + void getbestplayers(vector &best) + { + loopv(players) + { + fpsent *o = players[i]; + if(o->state!=CS_SPECTATOR) best.add(o); + } + best.sort(playersort); + while(best.length() > 1 && best.last()->frags < best[0]->frags) best.drop(); + } + + void getbestteams(vector &best) + { + if(!hidefrags) + { + vector teamscores; + teamscores.sort(teamscore::compare); + while(teamscores.length() > 1 && teamscores.last().score < teamscores[0].score) teamscores.drop(); + loopv(teamscores) best.add(teamscores[i].team); + } + else + { + int bestfrags = INT_MIN; + enumerate(teaminfos, teaminfo, t, bestfrags = max(bestfrags, t.frags)); + if(bestfrags <= 0) loopv(players) + { + fpsent *o = players[i]; + if(o->state!=CS_SPECTATOR && !teaminfos.access(o->team) && best.htfind(o->team) < 0) { bestfrags = 0; best.add(o->team); } + } + enumerate(teaminfos, teaminfo, t, if(t.frags >= bestfrags) best.add(t.team)); + } + } + + struct scoregroup : teamscore + { + vector players; + }; + static vector groups; + static vector spectators; + + static inline bool scoregroupcmp(const scoregroup *x, const scoregroup *y) + { + if(!x->team) + { + if(y->team) return false; + } + else if(!y->team) return true; + if(x->score > y->score) return true; + if(x->score < y->score) return false; + if(x->players.length() > y->players.length()) return true; + if(x->players.length() < y->players.length()) return false; + return x->team && y->team && strcmp(x->team, y->team) < 0; + } + + static int groupplayers() + { + int numgroups = 0; + spectators.setsize(0); + loopv(players) + { + fpsent *o = players[i]; + if(!showconnecting && !o->name[0]) continue; + if(o->state==CS_SPECTATOR) { spectators.add(o); continue; } + const char *team = m_teammode && o->team[0] ? o->team : NULL; + bool found = false; + loopj(numgroups) + { + scoregroup &g = *groups[j]; + if(team!=g.team && (!team || !g.team || strcmp(team, g.team))) continue; + g.players.add(o); + found = true; + } + if(found) continue; + if(numgroups>=groups.length()) groups.add(new scoregroup); + scoregroup &g = *groups[numgroups++]; + g.team = team; + if(!team) g.score = 0; + else { teaminfo *ti = teaminfos.access(team); g.score = ti ? ti->frags : 0; } + g.players.setsize(0); + g.players.add(o); + } + loopi(numgroups) groups[i]->players.sort(playersort); + spectators.sort(playersort); + groups.sort(scoregroupcmp, 0, numgroups); + return numgroups; + } + + int statuscolor(fpsent *d, int color) + { + if(d->privilege) + { + color = d->privilege>=PRIV_ADMIN ? 0xFF8000 : (d->privilege>=PRIV_AUTH ? 0xC040C0 : 0x40FF80); + if(d->state==CS_DEAD) color = (color>>1)&0x7F7F7F; + } + else if(d->state==CS_DEAD) color = 0x606060; + return color; + } + + void renderscoreboard(g3d_gui &g, bool firstpass) + { + const ENetAddress *address = connectedpeer(); + if(showservinfo && address) + { + string hostname; + if(enet_address_get_host_ip(address, hostname, sizeof(hostname)) >= 0) + { + if(servinfo[0]) g.titlef("%.25s", 0xFFFF80, NULL, servinfo); + else g.titlef("%s:%d", 0xFFFF80, NULL, hostname, address->port); + } + } + + g.pushlist(); + g.spring(); + g.text(server::modename(gamemode), 0xFFFF80); + g.separator(); + const char *mname = getclientmap(); + g.text(mname[0] ? mname : "[new map]", 0xFFFF80); + extern int gamespeed; + if(gamespeed != 100) { g.separator(); g.textf("%d.%02dx", 0xFFFF80, NULL, gamespeed/100, gamespeed%100); } + if(m_timed && mname[0] && (maplimit >= 0 || intermission)) + { + g.separator(); + if(intermission) g.text("intermission", 0xFFFF80); + else + { + int secs = max(maplimit-lastmillis+999, 0)/1000, mins = secs/60; + secs %= 60; + g.pushlist(); + g.strut(mins >= 10 ? 4.5f : 3.5f); + g.textf("%d:%02d", 0xFFFF80, NULL, mins, secs); + g.poplist(); + } + } + if(ispaused()) { g.separator(); g.text("paused", 0xFFFF80); } + g.spring(); + g.poplist(); + + g.separator(); + + int numgroups = groupplayers(); + loopk(numgroups) + { + if((k%2)==0) g.pushlist(); // horizontal + + scoregroup &sg = *groups[k]; + int bgcolor = sg.team && m_teammode ? (isteam(player1->team, sg.team) ? 0x3030C0 : 0xC03030) : 0, + fgcolor = 0xFFFF80; + + g.pushlist(); // vertical + g.pushlist(); // horizontal + + #define loopscoregroup(o, b) \ + loopv(sg.players) \ + { \ + fpsent *o = sg.players[i]; \ + b; \ + } + + g.pushlist(); + if(sg.team && m_teammode) + { + g.pushlist(); + g.background(bgcolor, numgroups>1 ? 3 : 5); + g.strut(1); + g.poplist(); + } + g.text("", 0, " "); + loopscoregroup(o, + { + if(o==player1 && highlightscore && (multiplayer(false) || demoplayback || players.length() > 1)) + { + g.pushlist(); + g.background(0x808080, numgroups>1 ? 3 : 5); + } + const playermodelinfo &mdl = getplayermodelinfo(o); + const char *icon = sg.team && m_teammode ? (isteam(player1->team, sg.team) ? mdl.blueicon : mdl.redicon) : mdl.ffaicon; + g.text("", 0, icon); + if(o==player1 && highlightscore && (multiplayer(false) || demoplayback || players.length() > 1)) g.poplist(); + }); + g.poplist(); + + if(sg.team && m_teammode) + { + g.pushlist(); // vertical + + if(sg.score>=10000) g.textf("%s: WIN", fgcolor, NULL, sg.team); + else g.textf("%s: %d", fgcolor, NULL, sg.team, sg.score); + + g.pushlist(); // horizontal + } + + if(!hidefrags) + { + g.pushlist(); + g.strut(6); + g.text("frags", fgcolor); + loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->frags)); + g.poplist(); + } + + if(showdeaths) + { + g.pushlist(); + g.strut(6); + g.text("deaths", fgcolor); + loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->deaths)); + g.poplist(); + } + + if(showdamagedealt) + { + g.pushlist(); + g.strut(7); + g.text("damage", fgcolor); + loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->totaldamage)); + g.poplist(); + } + + g.pushlist(); + g.text("name", fgcolor); + g.strut(12); + loopscoregroup(o, { g.textf("%s ", statuscolor(o, 0xFFFFDD), NULL, colorname(o)); }); + g.poplist(); + + if(multiplayer(false) || demoplayback) + { + if(showpj || showping) g.space(1); + + if(showpj && showping <= 1) + { + g.pushlist(); + g.strut(6); + g.text("pj", fgcolor); + loopscoregroup(o, + { + if(o->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); + else g.textf("%d", 0xFFFFDD, NULL, o->plag); + }); + g.poplist(); + } + + if(showping > 1) + { + g.pushlist(); + g.strut(6); + + g.pushlist(); + g.text("ping", fgcolor); + g.space(1); + g.spring(); + g.text("pj", fgcolor); + g.poplist(); + + loopscoregroup(o, + { + fpsent *p = o->ownernum >= 0 ? getclient(o->ownernum) : o; + if(!p) p = o; + g.pushlist(); + if(p->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); + else + { + g.textf("%d", 0xFFFFDD, NULL, p->ping); + g.space(1); + g.spring(); + g.textf("%d", 0xFFFFDD, NULL, o->plag); + } + g.poplist(); + + }); + g.poplist(); + } + else if(showping) + { + g.pushlist(); + g.text("ping", fgcolor); + g.strut(6); + loopscoregroup(o, + { + fpsent *p = o->ownernum >= 0 ? getclient(o->ownernum) : o; + if(!p) p = o; + if(!showpj && p->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); + else g.textf("%d", 0xFFFFDD, NULL, p->ping); + }); + g.poplist(); + } + } + + if(showclientnum || player1->privilege>=PRIV_MASTER) + { + g.space(1); + g.pushlist(); + g.text("cn", fgcolor); + loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->clientnum)); + g.poplist(); + } + + if(sg.team && m_teammode) + { + g.poplist(); // horizontal + g.poplist(); // vertical + } + + g.poplist(); // horizontal + g.poplist(); // vertical + + if(k+1privilege>=PRIV_MASTER) + { + g.pushlist(); + + g.pushlist(); + g.text("spectator", 0xFFFF80, " "); + g.strut(12); + loopv(spectators) + { + fpsent *o = spectators[i]; + if(o==player1 && highlightscore) + { + g.pushlist(); + g.background(0x808080, 3); + } + g.text(colorname(o), statuscolor(o, 0xFFFFDD), "spectator"); + if(o==player1 && highlightscore) g.poplist(); + } + g.poplist(); + + if((multiplayer(false) || demoplayback) && showspectatorping) + { + g.space(1); + g.pushlist(); + g.text("ping", 0xFFFF80); + g.strut(6); + loopv(spectators) + { + fpsent *o = spectators[i]; + fpsent *p = o->ownernum >= 0 ? getclient(o->ownernum) : o; + if(!p) p = o; + if(p->state==CS_LAGGED) g.text("LAG", 0xFFFFDD); + else g.textf("%d", 0xFFFFDD, NULL, p->ping); + } + g.poplist(); + } + + g.space(1); + g.pushlist(); + g.text("cn", 0xFFFF80); + loopv(spectators) g.textf("%d", 0xFFFFDD, NULL, spectators[i]->clientnum); + g.poplist(); + + g.poplist(); + } + else + { + g.textf("%d spectator%s", 0xFFFF80, " ", spectators.length(), spectators.length()!=1 ? "s" : ""); + loopv(spectators) + { + if((i%3)==0) + { + g.pushlist(); + g.text("", 0xFFFFDD, "spectator"); + } + fpsent *o = spectators[i]; + if(o==player1 && highlightscore) + { + g.pushlist(); + g.background(0x808080); + } + g.text(colorname(o), statuscolor(o, 0xFFFFDD)); + if(o==player1 && highlightscore) g.poplist(); + if(i+1score; + best = isteam(player1->team, g->team); + if(numgroups > 1) + { + if(best) score2 = groups[1]->score; + else for(int i = 1; i < groups.length(); ++i) if(isteam(player1->team, groups[i]->team)) { score2 = groups[i]->score; break; } + if(score2 == INT_MIN) + { + fpsent *p = followingplayer(player1); + if(p->state==CS_SPECTATOR) score2 = groups[1]->score; + } + } + } + else + { + fpsent *p = followingplayer(player1); + score = g->players[0]->frags; + best = p == g->players[0]; + if(g->players.length() > 1) + { + if(best || p->state==CS_SPECTATOR) score2 = g->players[1]->frags; + else score2 = p->frags; + } + } + if(score == score2 && !best) best = true; + + score = clamp(score, -999, 9999); + defformatstring(buf, "%d", score); + int tw = 0, th = 0; + text_bounds(buf, tw, th); + + string buf2; + int tw2 = 0, th2 = 0; + if(score2 > INT_MIN) + { + score2 = clamp(score2, -999, 9999); + formatstring(buf2, "%d", score2); + text_bounds(buf2, tw2, th2); + } + + int fw = 0, fh = 0; + text_bounds("00", fw, fh); + fw = max(fw, max(tw, tw2)); + + vec2 offset = vec2(hudscorex, hudscorey).mul(vec2(w, h).div(hudscorescale)); + if(hudscorealign == 1) offset.x -= 2*fw + hudscoresep; + else if(hudscorealign == 0) offset.x -= (2*fw + hudscoresep) / 2.0f; + vec2 offset2 = offset; + offset.x += (fw-tw)/2.0f; + offset.y -= th/2.0f; + offset2.x += fw + hudscoresep + (fw-tw2)/2.0f; + offset2.y -= th2/2.0f; + + pushhudmatrix(); + hudmatrix.scale(hudscorescale, hudscorescale, 1); + flushhudmatrix(); + + int color = hudscoreplayercolour, color2 = hudscoreenemycolour; + if(!best) swap(color, color2); + + draw_text(buf, int(offset.x), int(offset.y), (color>>16)&0xFF, (color>>8)&0xFF, color&0xFF, hudscorealpha); + if(score2 > INT_MIN) draw_text(buf2, int(offset2.x), int(offset2.y), (color2>>16)&0xFF, (color2>>8)&0xFF, color2&0xFF, hudscorealpha); + + pophudmatrix(); + } } diff --git a/src/fpsgame/server.cpp b/src/fpsgame/server.cpp index a3f0481..ec781c6 100644 --- a/src/fpsgame/server.cpp +++ b/src/fpsgame/server.cpp @@ -2,17 +2,17 @@ namespace game { - void parseoptions(vector &args) - { - loopv(args) + void parseoptions(vector &args) + { + loopv(args) #ifndef STANDALONE - if(!game::clientoption(args[i])) + if(!game::clientoption(args[i])) #endif - if(!server::serveroption(args[i])) - conoutf(CON_ERROR, "unknown command-line option: %s", args[i]); - } + if(!server::serveroption(args[i])) + conoutf(CON_ERROR, "unknown command-line option: %s", args[i]); + } - const char *gameident() { return "fps"; } + const char *gameident() { return "fps"; } } VAR(regenbluearmour, 0, 1, 1); @@ -21,603 +21,603 @@ extern ENetAddress masteraddress; namespace server { - struct server_entity // server side version of "entity" type - { - int type; - int spawntime; - bool spawned; - }; - - static const int DEATHMILLIS = 300; - - struct clientinfo; - - struct gameevent - { - virtual ~gameevent() {} - - virtual bool flush(clientinfo *ci, int fmillis); - virtual void process(clientinfo *ci) {} - - virtual bool keepable() const { return false; } - }; - - struct timedevent : gameevent - { - int millis; - - bool flush(clientinfo *ci, int fmillis); - }; - - struct hitinfo - { - int target; - int lifesequence; - int rays; - float dist; - vec dir; - }; - - struct shotevent : timedevent - { - int id, gun; - vec from, to; - vector hits; - - void process(clientinfo *ci); - }; - - struct explodeevent : timedevent - { - int id, gun; - vector hits; - - bool keepable() const { return true; } - - void process(clientinfo *ci); - }; - - struct suicideevent : gameevent - { - void process(clientinfo *ci); - }; - - struct pickupevent : gameevent - { - int ent; - - void process(clientinfo *ci); - }; - - template - struct projectilestate - { - int projs[N]; - int numprojs; - - projectilestate() : numprojs(0) {} - - void reset() { numprojs = 0; } - - void add(int val) - { - if(numprojs>=N) numprojs = 0; - projs[numprojs++] = val; - } - - bool remove(int val) - { - loopi(numprojs) if(projs[i]==val) - { - projs[i] = projs[--numprojs]; - return true; - } - return false; - } - }; - - struct gamestate : fpsstate - { - vec o; - int state, editstate; - int lastdeath, deadflush, lastspawn, lifesequence; - int lastshot; - projectilestate<8> rockets, grenades; - int frags, flags, deaths, teamkills, shotdamage, damage, tokens; - int lasttimeplayed, timeplayed; - float effectiveness; - - gamestate() : state(CS_DEAD), editstate(CS_DEAD), lifesequence(0) {} - - bool isalive(int gamemillis) - { - return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS); - } - - bool waitexpired(int gamemillis) - { - return gamemillis - lastshot >= gunwait; - } - - void reset() - { - if(state!=CS_SPECTATOR) state = editstate = CS_DEAD; - maxhealth = 100; - maxarmour = 50; - rockets.reset(); - grenades.reset(); - - timeplayed = 0; - effectiveness = 0; - frags = flags = deaths = teamkills = shotdamage = damage = tokens = 0; - - lastdeath = 0; - - respawn(); - } - - void respawn() - { - fpsstate::respawn(); - o = vec(-1e10f, -1e10f, -1e10f); - deadflush = 0; - lastspawn = -1; - lastshot = 0; - tokens = 0; - } - - void reassign() - { - respawn(); - rockets.reset(); - grenades.reset(); - } - }; - - struct savedscore - { - uint ip; - string name; - int frags, flags, deaths, teamkills, shotdamage, damage; - int timeplayed; - float effectiveness; - - void save(gamestate &gs) - { - frags = gs.frags; - flags = gs.flags; - deaths = gs.deaths; - teamkills = gs.teamkills; - shotdamage = gs.shotdamage; - damage = gs.damage; - timeplayed = gs.timeplayed; - effectiveness = gs.effectiveness; - } - - void restore(gamestate &gs) - { - gs.frags = frags; - gs.flags = flags; - gs.deaths = deaths; - gs.teamkills = teamkills; - gs.shotdamage = shotdamage; - gs.damage = damage; - gs.timeplayed = timeplayed; - gs.effectiveness = effectiveness; - } - }; - - extern int gamemillis, nextexceeded; - - struct clientinfo - { - int clientnum, ownernum, connectmillis, sessionid, overflow; - string name, team, mapvote; - int playermodel; - int modevote; - int privilege; - bool connected, local, timesync; - int gameoffset, lastevent, pushed, exceeded; - gamestate state; - vector events; - vector position, messages; - uchar *wsdata; - int wslen; - vector bots; - int ping, aireinit; - string clientmap; - int mapcrc; - bool warned, gameclip; - ENetPacket *getdemo, *getmap, *clipboard; - int lastclipboard, needclipboard; - int connectauth; - uint authreq; - string authname, authdesc; - void *authchallenge; - int authkickvictim; - char *authkickreason; - - clientinfo() : getdemo(NULL), getmap(NULL), clipboard(NULL), authchallenge(NULL), authkickreason(NULL) { reset(); } - ~clientinfo() { events.deletecontents(); cleanclipboard(); cleanauth(); } - - void addevent(gameevent *e) - { - if(state.state==CS_SPECTATOR || events.length()>100) delete e; - else events.add(e); - } - - enum - { - PUSHMILLIS = 3000 - }; - - int calcpushrange() - { - ENetPeer *peer = getclientpeer(ownernum); - return PUSHMILLIS + ((peer) ? (int) (peer->roundTripTime + peer->roundTripTimeVariance) : (int) ENET_PEER_DEFAULT_ROUND_TRIP_TIME); - } - - bool checkpushed(int millis, int range) - { - return millis >= pushed - range && millis <= pushed + range; - } - - void scheduleexceeded() - { - if(state.state!=CS_ALIVE || !exceeded) return; - int range = calcpushrange(); - if(!nextexceeded || exceeded + range < nextexceeded) nextexceeded = exceeded + range; - } - - void setexceeded() - { - if(state.state==CS_ALIVE && !exceeded && !checkpushed(gamemillis, calcpushrange())) exceeded = gamemillis; - scheduleexceeded(); - } - - void setpushed() - { - pushed = max(pushed, gamemillis); - if(exceeded && checkpushed(exceeded, calcpushrange())) exceeded = 0; - } - - bool checkexceeded() - { - return state.state==CS_ALIVE && exceeded && gamemillis > exceeded + calcpushrange(); - } - - void mapchange() - { - mapvote[0] = 0; - modevote = INT_MAX; - state.reset(); - events.deletecontents(); - overflow = 0; - timesync = false; - lastevent = 0; - exceeded = 0; - pushed = 0; - clientmap[0] = '\0'; - mapcrc = 0; - warned = false; - gameclip = false; - } - - void reassign() - { - state.reassign(); - events.deletecontents(); - timesync = false; - lastevent = 0; - } - - void cleanclipboard(bool fullclean = true) - { - if(clipboard) { if(--clipboard->referenceCount <= 0) enet_packet_destroy(clipboard); clipboard = NULL; } - if(fullclean) lastclipboard = 0; - } - - void cleanauthkick() - { - authkickvictim = -1; - DELETEA(authkickreason); - } - - void cleanauth(bool full = true) - { - authreq = 0; - if(authchallenge) { freechallenge(authchallenge); authchallenge = NULL; } - if(full) cleanauthkick(); - } - - void reset() - { - name[0] = team[0] = 0; - playermodel = -1; - privilege = PRIV_NONE; - connected = local = false; - connectauth = 0; - position.setsize(0); - messages.setsize(0); - ping = 0; - aireinit = 0; - needclipboard = 0; - cleanclipboard(); - cleanauth(); - mapchange(); - } - - int geteventmillis(int servmillis, int clientmillis) - { - if(!timesync || (events.empty() && state.waitexpired(servmillis))) - { - timesync = true; - gameoffset = servmillis - clientmillis; - return servmillis; - } - else return gameoffset + clientmillis; - } - }; - - struct ban - { - int time, expire; - uint ip; - }; - - namespace aiman - { - extern void removeai(clientinfo *ci); - extern void clearai(); - extern void checkai(); - extern void reqadd(clientinfo *ci, int skill); - extern void reqdel(clientinfo *ci); - extern void setbotlimit(clientinfo *ci, int limit); - extern void setbotbalance(clientinfo *ci, bool balance); - extern void changemap(); - extern void addclient(clientinfo *ci); - extern void changeteam(clientinfo *ci); - } - - #define MM_MODE 0xF - #define MM_AUTOAPPROVE 0x1000 - #define MM_PRIVSERV (MM_MODE | MM_AUTOAPPROVE) - #define MM_PUBSERV ((1< allowedips; - vector bannedips; - - void addban(uint ip, int expire) - { - allowedips.removeobj(ip); - ban b; - b.time = totalmillis; - b.expire = totalmillis + expire; - b.ip = ip; - loopv(bannedips) if(bannedips[i].expire - b.expire > 0) { bannedips.insert(i, b); return; } - bannedips.add(b); - } - - vector connects, clients, bots; - - void kickclients(uint ip, clientinfo *actor = NULL, int priv = PRIV_NONE) - { - loopvrev(clients) - { - clientinfo &c = *clients[i]; - if(c.state.aitype != AI_NONE || c.privilege >= PRIV_ADMIN || c.local) continue; - if(actor && ((c.privilege > priv && !actor->local) || c.clientnum == actor->clientnum)) continue; - //~if(getclientip(c.clientnum) == ip) disconnect_client(c.clientnum, DISC_KICK); - } - } - - struct maprotation - { - static int exclude; - int modes; - string map; - - int calcmodemask() const { return modes&(1< maprotations; - int curmaprotation = 0; - - VAR(lockmaprotation, 0, 0, 2); - - void maprotationreset() - { - maprotations.setsize(0); - curmaprotation = 0; - maprotation::exclude = 0; - } - - void nextmaprotation() - { - curmaprotation++; - if(maprotations.inrange(curmaprotation) && maprotations[curmaprotation].modes) return; - do curmaprotation--; - while(maprotations.inrange(curmaprotation) && maprotations[curmaprotation].modes); - curmaprotation++; - } - - int findmaprotation(int mode, const char *map) - { - for(int i = max(curmaprotation, 0); i < maprotations.length(); i++) - { - maprotation &rot = maprotations[i]; - if(!rot.modes) break; - if(rot.match(mode, map)) return i; - } - int start; - for(start = max(curmaprotation, 0) - 1; start >= 0; start--) if(!maprotations[start].modes) break; - start++; - for(int i = start; i < curmaprotation; i++) - { - maprotation &rot = maprotations[i]; - if(!rot.modes) break; - if(rot.match(mode, map)) return i; - } - int best = -1; - loopv(maprotations) - { - maprotation &rot = maprotations[i]; - if(rot.match(mode, map) && (best < 0 || maprotations[best].includes(rot))) best = i; - } - return best; - } - - bool searchmodename(const char *haystack, const char *needle) - { - if(!needle[0]) return true; - do - { - if(needle[0] != '.') - { - haystack = strchr(haystack, needle[0]); - if(!haystack) break; - haystack++; - } - const char *h = haystack, *n = needle+1; - for(; *h && *n; h++) - { - if(*h == *n) n++; - else if(*h != ' ') break; - } - if(!*n) return true; - if(*n == '.') return !*h; - } while(needle[0] != '.'); - return false; - } - - int genmodemask(vector &modes) - { - int modemask = 0; - loopv(modes) - { - const char *mode = modes[i]; - int op = mode[0]; - switch(mode[0]) - { - case '*': - modemask |= 1< modes, maps; - for(int i = 0; i + 1 < numargs; i += 2) - { - explodelist(args[i].getstr(), modes); - explodelist(args[i+1].getstr(), maps); - int modemask = genmodemask(modes); - if(maps.length()) loopvj(maps) addmaprotation(modemask, maps[j]); - else addmaprotation(modemask, ""); - modes.deletearrays(); - maps.deletearrays(); - } - if(maprotations.length() && maprotations.last().modes) - { - maprotation &rot = maprotations.add(); - rot.modes = 0; - rot.map[0] = '\0'; - } - } - - COMMAND(maprotationreset, ""); - COMMANDN(maprotation, addmaprotations, "ss2V"); - - struct demofile - { - string info; - uchar *data; - int len; - }; - - vector demos; - - bool demonextmatch = false; - stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL; - int nextplayback = 0; - - VAR(maxdemos, 0, 5, 25); - VAR(maxdemosize, 0, 16, 31); - VAR(restrictdemos, 0, 1, 1); - VARF(autorecorddemo, 0, 0, 1, demonextmatch = autorecorddemo!=0); - - VAR(restrictpausegame, 0, 1, 1); - VAR(restrictgamespeed, 0, 1, 1); - - SVAR(serverdesc, ""); - SVAR(serverpass, ""); - SVAR(adminpass, ""); - VARF(publicserver, 0, 0, 2, { + struct server_entity // server side version of "entity" type + { + int type; + int spawntime; + bool spawned; + }; + + static const int DEATHMILLIS = 300; + + struct clientinfo; + + struct gameevent + { + virtual ~gameevent() {} + + virtual bool flush(clientinfo *ci, int fmillis); + virtual void process(clientinfo *ci) {} + + virtual bool keepable() const { return false; } + }; + + struct timedevent : gameevent + { + int millis; + + bool flush(clientinfo *ci, int fmillis); + }; + + struct hitinfo + { + int target; + int lifesequence; + int rays; + float dist; + vec dir; + }; + + struct shotevent : timedevent + { + int id, gun; + vec from, to; + vector hits; + + void process(clientinfo *ci); + }; + + struct explodeevent : timedevent + { + int id, gun; + vector hits; + + bool keepable() const { return true; } + + void process(clientinfo *ci); + }; + + struct suicideevent : gameevent + { + void process(clientinfo *ci); + }; + + struct pickupevent : gameevent + { + int ent; + + void process(clientinfo *ci); + }; + + template + struct projectilestate + { + int projs[N]; + int numprojs; + + projectilestate() : numprojs(0) {} + + void reset() { numprojs = 0; } + + void add(int val) + { + if(numprojs>=N) numprojs = 0; + projs[numprojs++] = val; + } + + bool remove(int val) + { + loopi(numprojs) if(projs[i]==val) + { + projs[i] = projs[--numprojs]; + return true; + } + return false; + } + }; + + struct gamestate : fpsstate + { + vec o; + int state, editstate; + int lastdeath, deadflush, lastspawn, lifesequence; + int lastshot; + projectilestate<8> rockets, grenades; + int frags, flags, deaths, teamkills, shotdamage, damage, tokens; + int lasttimeplayed, timeplayed; + float effectiveness; + + gamestate() : state(CS_DEAD), editstate(CS_DEAD), lifesequence(0) {} + + bool isalive(int gamemillis) + { + return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS); + } + + bool waitexpired(int gamemillis) + { + return gamemillis - lastshot >= gunwait; + } + + void reset() + { + if(state!=CS_SPECTATOR) state = editstate = CS_DEAD; + maxhealth = 100; + maxarmour = 50; + rockets.reset(); + grenades.reset(); + + timeplayed = 0; + effectiveness = 0; + frags = flags = deaths = teamkills = shotdamage = damage = tokens = 0; + + lastdeath = 0; + + respawn(); + } + + void respawn() + { + fpsstate::respawn(); + o = vec(-1e10f, -1e10f, -1e10f); + deadflush = 0; + lastspawn = -1; + lastshot = 0; + tokens = 0; + } + + void reassign() + { + respawn(); + rockets.reset(); + grenades.reset(); + } + }; + + struct savedscore + { + uint ip; + string name; + int frags, flags, deaths, teamkills, shotdamage, damage; + int timeplayed; + float effectiveness; + + void save(gamestate &gs) + { + frags = gs.frags; + flags = gs.flags; + deaths = gs.deaths; + teamkills = gs.teamkills; + shotdamage = gs.shotdamage; + damage = gs.damage; + timeplayed = gs.timeplayed; + effectiveness = gs.effectiveness; + } + + void restore(gamestate &gs) + { + gs.frags = frags; + gs.flags = flags; + gs.deaths = deaths; + gs.teamkills = teamkills; + gs.shotdamage = shotdamage; + gs.damage = damage; + gs.timeplayed = timeplayed; + gs.effectiveness = effectiveness; + } + }; + + extern int gamemillis, nextexceeded; + + struct clientinfo + { + int clientnum, ownernum, connectmillis, sessionid, overflow; + string name, team, mapvote; + int playermodel; + int modevote; + int privilege; + bool connected, local, timesync; + int gameoffset, lastevent, pushed, exceeded; + gamestate state; + vector events; + vector position, messages; + uchar *wsdata; + int wslen; + vector bots; + int ping, aireinit; + string clientmap; + int mapcrc; + bool warned, gameclip; + ENetPacket *getdemo, *getmap, *clipboard; + int lastclipboard, needclipboard; + int connectauth; + uint authreq; + string authname, authdesc; + void *authchallenge; + int authkickvictim; + char *authkickreason; + + clientinfo() : getdemo(NULL), getmap(NULL), clipboard(NULL), authchallenge(NULL), authkickreason(NULL) { reset(); } + ~clientinfo() { events.deletecontents(); cleanclipboard(); cleanauth(); } + + void addevent(gameevent *e) + { + if(state.state==CS_SPECTATOR || events.length()>100) delete e; + else events.add(e); + } + + enum + { + PUSHMILLIS = 3000 + }; + + int calcpushrange() + { + ENetPeer *peer = getclientpeer(ownernum); + return PUSHMILLIS + ((peer) ? (int) (peer->roundTripTime + peer->roundTripTimeVariance) : (int) ENET_PEER_DEFAULT_ROUND_TRIP_TIME); + } + + bool checkpushed(int millis, int range) + { + return millis >= pushed - range && millis <= pushed + range; + } + + void scheduleexceeded() + { + if(state.state!=CS_ALIVE || !exceeded) return; + int range = calcpushrange(); + if(!nextexceeded || exceeded + range < nextexceeded) nextexceeded = exceeded + range; + } + + void setexceeded() + { + if(state.state==CS_ALIVE && !exceeded && !checkpushed(gamemillis, calcpushrange())) exceeded = gamemillis; + scheduleexceeded(); + } + + void setpushed() + { + pushed = max(pushed, gamemillis); + if(exceeded && checkpushed(exceeded, calcpushrange())) exceeded = 0; + } + + bool checkexceeded() + { + return state.state==CS_ALIVE && exceeded && gamemillis > exceeded + calcpushrange(); + } + + void mapchange() + { + mapvote[0] = 0; + modevote = INT_MAX; + state.reset(); + events.deletecontents(); + overflow = 0; + timesync = false; + lastevent = 0; + exceeded = 0; + pushed = 0; + clientmap[0] = '\0'; + mapcrc = 0; + warned = false; + gameclip = false; + } + + void reassign() + { + state.reassign(); + events.deletecontents(); + timesync = false; + lastevent = 0; + } + + void cleanclipboard(bool fullclean = true) + { + if(clipboard) { if(--clipboard->referenceCount <= 0) enet_packet_destroy(clipboard); clipboard = NULL; } + if(fullclean) lastclipboard = 0; + } + + void cleanauthkick() + { + authkickvictim = -1; + DELETEA(authkickreason); + } + + void cleanauth(bool full = true) + { + authreq = 0; + if(authchallenge) { freechallenge(authchallenge); authchallenge = NULL; } + if(full) cleanauthkick(); + } + + void reset() + { + name[0] = team[0] = 0; + playermodel = -1; + privilege = PRIV_NONE; + connected = local = false; + connectauth = 0; + position.setsize(0); + messages.setsize(0); + ping = 0; + aireinit = 0; + needclipboard = 0; + cleanclipboard(); + cleanauth(); + mapchange(); + } + + int geteventmillis(int servmillis, int clientmillis) + { + if(!timesync || (events.empty() && state.waitexpired(servmillis))) + { + timesync = true; + gameoffset = servmillis - clientmillis; + return servmillis; + } + else return gameoffset + clientmillis; + } + }; + + struct ban + { + int time, expire; + uint ip; + }; + + namespace aiman + { + extern void removeai(clientinfo *ci); + extern void clearai(); + extern void checkai(); + extern void reqadd(clientinfo *ci, int skill); + extern void reqdel(clientinfo *ci); + extern void setbotlimit(clientinfo *ci, int limit); + extern void setbotbalance(clientinfo *ci, bool balance); + extern void changemap(); + extern void addclient(clientinfo *ci); + extern void changeteam(clientinfo *ci); + } + + #define MM_MODE 0xF + #define MM_AUTOAPPROVE 0x1000 + #define MM_PRIVSERV (MM_MODE | MM_AUTOAPPROVE) + #define MM_PUBSERV ((1< allowedips; + vector bannedips; + + void addban(uint ip, int expire) + { + allowedips.removeobj(ip); + ban b; + b.time = totalmillis; + b.expire = totalmillis + expire; + b.ip = ip; + loopv(bannedips) if(bannedips[i].expire - b.expire > 0) { bannedips.insert(i, b); return; } + bannedips.add(b); + } + + vector connects, clients, bots; + + void kickclients(uint ip, clientinfo *actor = NULL, int priv = PRIV_NONE) + { + loopvrev(clients) + { + clientinfo &c = *clients[i]; + if(c.state.aitype != AI_NONE || c.privilege >= PRIV_ADMIN || c.local) continue; + if(actor && ((c.privilege > priv && !actor->local) || c.clientnum == actor->clientnum)) continue; + //~if(getclientip(c.clientnum) == ip) disconnect_client(c.clientnum, DISC_KICK); + } + } + + struct maprotation + { + static int exclude; + int modes; + string map; + + int calcmodemask() const { return modes&(1< maprotations; + int curmaprotation = 0; + + VAR(lockmaprotation, 0, 0, 2); + + void maprotationreset() + { + maprotations.setsize(0); + curmaprotation = 0; + maprotation::exclude = 0; + } + + void nextmaprotation() + { + curmaprotation++; + if(maprotations.inrange(curmaprotation) && maprotations[curmaprotation].modes) return; + do curmaprotation--; + while(maprotations.inrange(curmaprotation) && maprotations[curmaprotation].modes); + curmaprotation++; + } + + int findmaprotation(int mode, const char *map) + { + for(int i = max(curmaprotation, 0); i < maprotations.length(); i++) + { + maprotation &rot = maprotations[i]; + if(!rot.modes) break; + if(rot.match(mode, map)) return i; + } + int start; + for(start = max(curmaprotation, 0) - 1; start >= 0; start--) if(!maprotations[start].modes) break; + start++; + for(int i = start; i < curmaprotation; i++) + { + maprotation &rot = maprotations[i]; + if(!rot.modes) break; + if(rot.match(mode, map)) return i; + } + int best = -1; + loopv(maprotations) + { + maprotation &rot = maprotations[i]; + if(rot.match(mode, map) && (best < 0 || maprotations[best].includes(rot))) best = i; + } + return best; + } + + bool searchmodename(const char *haystack, const char *needle) + { + if(!needle[0]) return true; + do + { + if(needle[0] != '.') + { + haystack = strchr(haystack, needle[0]); + if(!haystack) break; + haystack++; + } + const char *h = haystack, *n = needle+1; + for(; *h && *n; h++) + { + if(*h == *n) n++; + else if(*h != ' ') break; + } + if(!*n) return true; + if(*n == '.') return !*h; + } while(needle[0] != '.'); + return false; + } + + int genmodemask(vector &modes) + { + int modemask = 0; + loopv(modes) + { + const char *mode = modes[i]; + int op = mode[0]; + switch(mode[0]) + { + case '*': + modemask |= 1< modes, maps; + for(int i = 0; i + 1 < numargs; i += 2) + { + explodelist(args[i].getstr(), modes); + explodelist(args[i+1].getstr(), maps); + int modemask = genmodemask(modes); + if(maps.length()) loopvj(maps) addmaprotation(modemask, maps[j]); + else addmaprotation(modemask, ""); + modes.deletearrays(); + maps.deletearrays(); + } + if(maprotations.length() && maprotations.last().modes) + { + maprotation &rot = maprotations.add(); + rot.modes = 0; + rot.map[0] = '\0'; + } + } + + COMMAND(maprotationreset, ""); + COMMANDN(maprotation, addmaprotations, "ss2V"); + + struct demofile + { + string info; + uchar *data; + int len; + }; + + vector demos; + + bool demonextmatch = false; + stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL; + int nextplayback = 0; + + VAR(maxdemos, 0, 5, 25); + VAR(maxdemosize, 0, 16, 31); + VAR(restrictdemos, 0, 1, 1); + VARF(autorecorddemo, 0, 0, 1, demonextmatch = autorecorddemo!=0); + + VAR(restrictpausegame, 0, 1, 1); + VAR(restrictgamespeed, 0, 1, 1); + + SVAR(serverdesc, ""); + SVAR(serverpass, ""); + SVAR(adminpass, ""); + VARF(publicserver, 0, 0, 2, { switch(publicserver) { case 0: default: mastermask = MM_PRIVSERV; break; @@ -625,3096 +625,3093 @@ namespace server case 2: mastermask = MM_COOPSERV; break; } }); - SVAR(servermotd, ""); - - struct teamkillkick - { - int modes, limit, ban; - - bool match(int mode) const - { - return (modes&(1<<(mode-STARTGAMEMODE)))!=0; - } - - bool includes(const teamkillkick &tk) const - { - return tk.modes != modes && (tk.modes & modes) == tk.modes; - } - }; - vector teamkillkicks; - - void teamkillkickreset() - { - teamkillkicks.setsize(0); - } - - void addteamkillkick(char *modestr, int *limit, int *ban) - { - vector modes; - explodelist(modestr, modes); - teamkillkick &kick = teamkillkicks.add(); - kick.modes = genmodemask(modes); - kick.limit = *limit; - kick.ban = *ban > 0 ? *ban*60000 : (*ban < 0 ? 0 : 30*60000); - modes.deletearrays(); - } - - COMMAND(teamkillkickreset, ""); - COMMANDN(teamkillkick, addteamkillkick, "sii"); - - struct teamkillinfo - { - uint ip; - int teamkills; - }; - vector teamkills; - bool shouldcheckteamkills = false; - - void addteamkill(clientinfo *actor, clientinfo *victim, int n) - { - if(!m_timed || actor->state.aitype != AI_NONE || actor->local || actor->privilege || (victim && victim->state.aitype != AI_NONE)) return; - shouldcheckteamkills = true; - uint ip = getclientip(actor->clientnum); - loopv(teamkills) if(teamkills[i].ip == ip) - { - teamkills[i].teamkills += n; - return; - } - teamkillinfo &tk = teamkills.add(); - tk.ip = ip; - tk.teamkills = n; - } - - void checkteamkills() - { - teamkillkick *kick = NULL; - if(m_timed) loopv(teamkillkicks) if(teamkillkicks[i].match(gamemode) && (!kick || kick->includes(teamkillkicks[i]))) - kick = &teamkillkicks[i]; - if(kick) loopvrev(teamkills) - { - teamkillinfo &tk = teamkills[i]; - if(tk.teamkills >= kick->limit) - { - if(kick->ban > 0) addban(tk.ip, kick->ban); - kickclients(tk.ip); - teamkills.removeunordered(i); - } - } - shouldcheckteamkills = false; - } - - void *newclientinfo() { return new clientinfo; } - void deleteclientinfo(void *ci) { delete (clientinfo *)ci; } - - clientinfo *getinfo(int n) - { - if(n < MAXCLIENTS) return (clientinfo *)getclientinfo(n); - n -= MAXCLIENTS; - return bots.inrange(n) ? bots[n] : NULL; - } - - uint mcrc = 0; - vector ments; - vector sents; - vector scores; - - int msgsizelookup(int msg) - { - static int sizetable[NUMMSG] = { -1 }; - if(sizetable[0] < 0) - { - memset(sizetable, -1, sizeof(sizetable)); - for(const int *p = msgsizes; *p >= 0; p += 2) sizetable[p[0]] = p[1]; - } - return msg >= 0 && msg < NUMMSG ? sizetable[msg] : -1; - } - - const char *modename(int n, const char *unknown) - { - if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].name; - return unknown; - } - - const char *mastermodename(int n, const char *unknown) - { - return (n>=MM_START && size_t(n-MM_START)clientnum!=exclude && (!nospec || ci->state.state!=CS_SPECTATOR || (priv && (ci->privilege || ci->local))) && (!noai || ci->state.aitype == AI_NONE)) n++; - } - return n; - } - - bool duplicatename(clientinfo *ci, const char *name) - { - if(!name) name = ci->name; - loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true; - return false; - } - - const char *colorname(clientinfo *ci, const char *name = NULL) - { - if(!name) name = ci->name; - if(name[0] && !duplicatename(ci, name) && ci->state.aitype == AI_NONE) return name; - static string cname[3]; - static int cidx = 0; - cidx = (cidx+1)%3; - formatstring(cname[cidx], ci->state.aitype == AI_NONE ? "%s \fs\f5(%d)\fr" : "%s \fs\f5[%d]\fr", name, ci->clientnum); - return cname[cidx]; - } - - bool canspawnitem(int type) { return !m_noitems && (type>=I_SHELLS && type<=I_QUAD && (!m_noammo || typeI_CARTRIDGES)); } - - int spawntime(int type) - { - int np = numclients(-1, true, false); - np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players - int sec = 0; - switch(type) - { - case I_SHELLS: - case I_BULLETS: - case I_ROCKETS: - case I_ROUNDS: - case I_GRENADES: - case I_CARTRIDGES: sec = np*4; break; - case I_HEALTH: sec = np*5; break; - case I_TINYHEALTH: sec = np*5; break; - case I_TINYARMOUR: sec = np*5; break; - case I_GREENARMOUR: sec = 20; break; - case I_YELLOWARMOUR: sec = 30; break; - case I_BOOST: sec = 60; break; - case I_QUAD: sec = 70; break; - } - return sec*1000; - } - - bool delayspawn(int type) - { - switch(type) - { - case I_GREENARMOUR: - case I_YELLOWARMOUR: - case I_BOOST: - case I_QUAD: - return true; - default: - return false; - } - } - - bool pickup(int i, int sender) // server side item pickup, acknowledge first client that gets it - { - if((m_timed && gamemillis>=gamelimit) || !sents.inrange(i) || !sents[i].spawned) return false; - clientinfo *ci = getinfo(sender); - if(!ci) return false; - if(!ci->local && !ci->state.canpickup(sents[i].type)) - { - sendf(sender, 1, "ri3", N_ITEMACC, i, -1); - return false; - } - sents[i].spawned = false; - sents[i].spawntime = spawntime(sents[i].type); - sendf(-1, 1, "ri3", N_ITEMACC, i, sender); - ci->state.pickup(sents[i].type); - return true; - } - - static hashset teaminfos; - - void clearteaminfo() - { - teaminfos.clear(); - } - - bool teamhasplayers(const char *team) { loopv(clients) if(!strcmp(clients[i]->team, team)) return true; return false; } - - bool pruneteaminfo() - { - int oldteams = teaminfos.numelems; - enumerate(teaminfos, teaminfo, old, - if(!old.frags && !teamhasplayers(old.team)) teaminfos.remove(old.team); - ); - return teaminfos.numelems < oldteams; - } - - teaminfo *addteaminfo(const char *team) - { - teaminfo *t = teaminfos.access(team); - if(!t) - { - if(teaminfos.numelems >= MAXTEAMS && !pruneteaminfo()) return NULL; - t = &teaminfos[team]; - copystring(t->team, team, sizeof(t->team)); - t->frags = 0; - } - return t; - } - - clientinfo *choosebestclient(float &bestrank) - { - clientinfo *best = NULL; - bestrank = -1; - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.timeplayed<0) continue; - float rank = ci->state.state!=CS_SPECTATOR ? ci->state.effectiveness/max(ci->state.timeplayed, 1) : -1; - if(!best || rank > bestrank) { best = ci; bestrank = rank; } - } - return best; - } - - VAR(persistteams, 0, 0, 1); - - void autoteam() - { - static const char * const teamnames[2] = {"good", "evil"}; - vector team[2]; - float teamrank[2] = {0, 0}; - for(int round = 0, remaining = clients.length(); remaining>=0; round++) - { - int first = round&1, second = (round+1)&1, selected = 0; - while(teamrank[first] <= teamrank[second]) - { - float rank; - clientinfo *ci = choosebestclient(rank); - if(!ci) break; - if(selected && rank<=0) break; - ci->state.timeplayed = -1; - team[first].add(ci); - if(rank>0) teamrank[first] += rank; - selected++; - if(rank<=0) break; - } - if(!selected) break; - remaining -= selected; - } - loopi(sizeof(team)/sizeof(team[0])) - { - addteaminfo(teamnames[i]); - loopvj(team[i]) - { - clientinfo *ci = team[i][j]; - if(!strcmp(ci->team, teamnames[i])) continue; - if(persistteams && ci->team[0]) - { - addteaminfo(ci->team); - continue; - } - copystring(ci->team, teamnames[i], MAXTEAMLEN+1); - sendf(-1, 1, "riisi", N_SETTEAM, ci->clientnum, teamnames[i], -1); - } - } - } - - struct teamrank - { - const char *name; - float rank; - int clients; - - teamrank(const char *name) : name(name), rank(0), clients(0) {} - }; - - const char *chooseworstteam(const char *suggest = NULL, clientinfo *exclude = NULL) - { - teamrank teamranks[2] = { teamrank("good"), teamrank("evil") }; - const int numteams = sizeof(teamranks)/sizeof(teamranks[0]); - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci==exclude || ci->state.aitype!=AI_NONE || ci->state.state==CS_SPECTATOR || !ci->team[0]) continue; - ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; - ci->state.lasttimeplayed = lastmillis; - - loopj(numteams) if(!strcmp(ci->team, teamranks[j].name)) - { - teamrank &ts = teamranks[j]; - ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1); - ts.clients++; - break; - } - } - teamrank *worst = &teamranks[numteams-1]; - loopi(numteams-1) - { - teamrank &ts = teamranks[i]; - if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts; - } - return worst->name; - } - - void prunedemos(int extra = 0) - { - int n = clamp(demos.length() + extra - maxdemos, 0, demos.length()); - if(n <= 0) return; - loopi(n) delete[] demos[i].data; - demos.remove(0, n); - } - - void adddemo() - { - if(!demotmp) return; - int len = (int)min(demotmp->size(), stream::offset((maxdemosize<<20) + 0x10000)); - demofile &d = demos.add(); - time_t t = time(NULL); - char *timestr = ctime(&t), *trim = timestr + strlen(timestr); - while(trim>timestr && iscubespace(*--trim)) *trim = '\0'; - formatstring(d.info, "%s: %s, %s, %.2f%s", timestr, modename(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); - sendservmsgf("demo \"%s\" recorded", d.info); - d.data = new uchar[len]; - d.len = len; - demotmp->seek(0, SEEK_SET); - demotmp->read(d.data, len); - DELETEP(demotmp); - } - - void enddemorecord() - { - if(!demorecord) return; - - DELETEP(demorecord); - - if(!demotmp) return; - if(!maxdemos || !maxdemosize) { DELETEP(demotmp); return; } - - prunedemos(1); - adddemo(); - } - - void writedemo(int chan, void *data, int len) - { - if(!demorecord) return; - int stamp[3] = { gamemillis, chan, len }; - lilswap(stamp, 3); - demorecord->write(stamp, sizeof(stamp)); - demorecord->write(data, len); - if(demorecord->rawtell() >= (maxdemosize<<20)) enddemorecord(); - } - - void recordpacket(int chan, void *data, int len) - { - writedemo(chan, data, len); - } - - int welcomepacket(packetbuf &p, clientinfo *ci); - void sendwelcome(clientinfo *ci); - - void setupdemorecord() - { - if(!m_mp(gamemode) || m_edit) return; - - demotmp = opentempfile("demorecord", "w+b"); - if(!demotmp) return; - - stream *f = opengzfile(NULL, "wb", demotmp); - if(!f) { DELETEP(demotmp); return; } - - sendservmsg("recording demo"); - - demorecord = f; - - demoheader hdr; - memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)); - hdr.version = DEMO_VERSION; - hdr.protocol = PROTOCOL_VERSION; - lilswap(&hdr.version, 2); - demorecord->write(&hdr, sizeof(demoheader)); - - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - welcomepacket(p, NULL); - writedemo(1, p.buf, p.len); - } - - void listdemos(int cn) - { - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - putint(p, N_SENDDEMOLIST); - putint(p, demos.length()); - loopv(demos) sendstring(demos[i].info, p); - sendpacket(cn, 1, p.finalize()); - } - - void cleardemos(int n) - { - if(!n) - { - loopv(demos) delete[] demos[i].data; - demos.shrink(0); - sendservmsg("cleared all demos"); - } - else if(demos.inrange(n-1)) - { - delete[] demos[n-1].data; - demos.remove(n-1); - sendservmsgf("cleared demo %d", n); - } - } - - static void freegetmap(ENetPacket *packet) - { - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->getmap == packet) ci->getmap = NULL; - } - } - - static void freegetdemo(ENetPacket *packet) - { - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->getdemo == packet) ci->getdemo = NULL; - } - } - - void senddemo(clientinfo *ci, int num, int tag) - { - if(ci->getdemo) return; - if(!num) num = demos.length(); - if(!demos.inrange(num-1)) return; - demofile &d = demos[num-1]; - if((ci->getdemo = sendf(ci->clientnum, 2, "riim", N_SENDDEMO, tag, d.len, d.data))) - ci->getdemo->freeCallback = freegetdemo; - } - - void enddemoplayback() - { - if(!demoplayback) return; - DELETEP(demoplayback); - - loopv(clients) sendf(clients[i]->clientnum, 1, "ri3", N_DEMOPLAYBACK, 0, clients[i]->clientnum); - - sendservmsg("demo playback finished"); - - loopv(clients) sendwelcome(clients[i]); - } - - SVARP(demodir, "demo"); - - const char *getdemofile(const char *file, bool init) - { - if(!demodir[0]) return NULL; - static string buf; - copystring(buf, demodir); - int dirlen = strlen(buf); - if(buf[dirlen] != '/' && buf[dirlen] != '\\' && dirlen+1 < (int)sizeof(buf)) { buf[dirlen++] = '/'; buf[dirlen] = '\0'; } - if(init) - { - const char *dir = findfile(buf, "w"); - if(!fileexists(dir, "w")) createdir(dir); - } - concatstring(buf, file); - return buf; - } - - void setupdemoplayback() - { - if(demoplayback) return; - demoheader hdr; - string msg; - msg[0] = '\0'; - string file; - copystring(file, smapname); - int len = strlen(file); - if(len < 4 || strcasecmp(&file[len-4], ".dmo")) concatstring(file, ".dmo"); - if(const char *buf = getdemofile(file, false)) demoplayback = opengzfile(buf, "rb"); - if(!demoplayback) demoplayback = opengzfile(file, "rb"); - if(!demoplayback) formatstring(msg, "could not read demo \"%s\"", file); - else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic))) - formatstring(msg, "\"%s\" is not a demo file", file); - else - { - lilswap(&hdr.version, 2); - if(hdr.version!=DEMO_VERSION) formatstring(msg, "demo \"%s\" requires an %s version of Cube 2: Sauerbraten", file, hdr.versionread(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) - { - enddemoplayback(); - return; - } - lilswap(&nextplayback, 1); - } - - void readdemo() - { - if(!demoplayback) return; - while(gamemillis>=nextplayback) - { - int chan, len; - if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) || - demoplayback->read(&len, sizeof(len))!=sizeof(len)) - { - enddemoplayback(); - return; - } - lilswap(&chan, 1); - lilswap(&len, 1); - ENetPacket *packet = enet_packet_create(NULL, len+1, 0); - if(!packet || demoplayback->read(packet->data+1, len)!=size_t(len)) - { - if(packet) enet_packet_destroy(packet); - enddemoplayback(); - return; - } - packet->data[0] = N_DEMOPACKET; - sendpacket(-1, chan, packet); - if(!packet->referenceCount) enet_packet_destroy(packet); - if(!demoplayback) break; - if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) - { - enddemoplayback(); - return; - } - lilswap(&nextplayback, 1); - } - } - - void timeupdate(int secs) - { - if(!demoplayback) return; - if(secs <= 0) interm = -1; - else gamelimit = max(gamelimit, nextplayback + secs*1000); - } - - void seekdemo(char *t) - { - if(!demoplayback) return; - bool rev = *t == '-'; - if(rev) t++; - int mins = strtoul(t, &t, 10), secs = 0, millis = 0; - if(*t == ':') secs = strtoul(t+1, &t, 10); - else { secs = mins; mins = 0; } - if(*t == '.') millis = strtoul(t+1, &t, 10); - int offset = max(millis + (mins*60 + secs)*1000, 0), prevmillis = gamemillis; - if(rev) while(gamelimit - offset > gamemillis) - { - gamemillis = gamelimit - offset; - readdemo(); - } - else if(offset > gamemillis) - { - gamemillis = offset; - readdemo(); - } - if(gamemillis > prevmillis) - { - if(!interm) sendf(-1, 1, "ri2", N_TIMEUP, max((gamelimit - gamemillis)/1000, 1)); -#ifndef STANDALONE - cleardamagescreen(); -#endif - } - } - - ICOMMAND(seekdemo, "sN$", (char *t, int *numargs, ident *id), - { - if(*numargs > 0) seekdemo(t); - else - { - int secs = gamemillis/1000; - defformatstring(str, "%d:%02d.%03d", secs/60, secs%60, gamemillis%1000); - if(*numargs < 0) result(str); - else printsvar(id, str); - } - }); - - void stopdemo() - { - if(m_demo) enddemoplayback(); - else enddemorecord(); - } - - void pausegame(bool val, clientinfo *ci = NULL) - { - if(gamepaused==val) return; - gamepaused = val; - sendf(-1, 1, "riii", N_PAUSEGAME, gamepaused ? 1 : 0, ci ? ci->clientnum : -1); - } - - void checkpausegame() - { - if(!gamepaused) return; - int admins = 0; - loopv(clients) if(clients[i]->privilege >= (restrictpausegame ? PRIV_ADMIN : PRIV_MASTER) || clients[i]->local) admins++; - if(!admins) pausegame(false); - } - - void forcepaused(bool paused) - { - pausegame(paused); - } - - bool ispaused() { return gamepaused; } - - void changegamespeed(int val, clientinfo *ci = NULL) - { - val = clamp(val, 10, 1000); - if(gamespeed==val) return; - gamespeed = val; - sendf(-1, 1, "riii", N_GAMESPEED, gamespeed, ci ? ci->clientnum : -1); - } - - void forcegamespeed(int speed) - { - changegamespeed(speed); - } - - int scaletime(int t) { return t*gamespeed; } - - SVAR(serverauth, ""); - - struct userkey - { - char *name; - char *desc; - - userkey() : name(NULL), desc(NULL) {} - userkey(char *name, char *desc) : name(name), desc(desc) {} - }; - - static inline uint hthash(const userkey &k) { return ::hthash(k.name); } - static inline bool htcmp(const userkey &x, const userkey &y) { return !strcmp(x.name, y.name) && !strcmp(x.desc, y.desc); } - - struct userinfo : userkey - { - void *pubkey; - int privilege; - - userinfo() : pubkey(NULL), privilege(PRIV_NONE) {} - ~userinfo() { delete[] name; delete[] desc; if(pubkey) freepubkey(pubkey); } - }; - hashset users; - - void adduser(char *name, char *desc, char *pubkey, char *priv) - { - userkey key(name, desc); - userinfo &u = users[key]; - if(u.pubkey) { freepubkey(u.pubkey); u.pubkey = NULL; } - if(!u.name) u.name = newstring(name); - if(!u.desc) u.desc = newstring(desc); - u.pubkey = parsepubkey(pubkey); - switch(priv[0]) - { - case 'a': case 'A': u.privilege = PRIV_ADMIN; break; - case 'm': case 'M': default: u.privilege = PRIV_AUTH; break; - case 'n': case 'N': u.privilege = PRIV_NONE; break; - } - } - COMMAND(adduser, "ssss"); - - void clearusers() - { - users.clear(); - } - COMMAND(clearusers, ""); - - void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen) - { - char buf[2*sizeof(string)]; - formatstring(buf, "%d %d ", cn, sessionid); - concatstring(buf, pwd, sizeof(buf)); - if(!hashstring(buf, result, maxlen)) *result = '\0'; - } - - bool checkpassword(clientinfo *ci, const char *wanted, const char *given) - { - string hash; - hashpassword(ci->clientnum, ci->sessionid, wanted, hash, sizeof(hash)); - return !strcmp(hash, given); - } - - void revokemaster(clientinfo *ci) - { - ci->privilege = PRIV_NONE; - if(ci->state.state==CS_SPECTATOR && !ci->local) aiman::removeai(ci); - } - - extern void connected(clientinfo *ci); - - bool setmaster(clientinfo *ci, bool val, const char *pass = "", const char *authname = NULL, const char *authdesc = NULL, int authpriv = PRIV_MASTER, bool force = false, bool trial = false) - { - if(authname && !val) return false; - const char *name = ""; - if(val) - { - bool haspass = adminpass[0] && checkpassword(ci, adminpass, pass); - int wantpriv = ci->local || haspass ? PRIV_ADMIN : authpriv; - if(wantpriv <= ci->privilege) return true; - else if(wantpriv <= PRIV_MASTER && !force) - { - if(ci->state.state==CS_SPECTATOR) - { - sendf(ci->clientnum, 1, "ris", N_SERVMSG, "Spectators may not claim master."); - return false; - } - loopv(clients) if(ci!=clients[i] && clients[i]->privilege) - { - sendf(ci->clientnum, 1, "ris", N_SERVMSG, "Master is already claimed."); - return false; - } - if(!authname && !(mastermask&MM_AUTOAPPROVE) && !ci->privilege && !ci->local) - { - sendf(ci->clientnum, 1, "ris", N_SERVMSG, "This server requires you to use the \"/auth\" command to claim master."); - return false; - } - } - if(trial) return true; - ci->privilege = wantpriv; - name = privname(ci->privilege); - } - else - { - if(!ci->privilege) return false; - if(trial) return true; - name = privname(ci->privilege); - revokemaster(ci); - } - bool hasmaster = false; - loopv(clients) if(clients[i]->local || clients[i]->privilege >= PRIV_MASTER) hasmaster = true; - if(!hasmaster) - { - mastermode = MM_OPEN; - allowedips.shrink(0); - } - string msg; - if(val && authname) - { - if(authdesc && authdesc[0]) formatstring(msg, "%s claimed %s as '\fs\f5%s\fr' [\fs\f0%s\fr]", colorname(ci), name, authname, authdesc); - else formatstring(msg, "%s claimed %s as '\fs\f5%s\fr'", colorname(ci), name, authname); - } - else formatstring(msg, "%s %s %s", colorname(ci), val ? "claimed" : "relinquished", name); - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - putint(p, N_SERVMSG); - sendstring(msg, p); - putint(p, N_CURRENTMASTER); - putint(p, mastermode); - loopv(clients) if(clients[i]->privilege >= PRIV_MASTER) - { - putint(p, clients[i]->clientnum); - putint(p, clients[i]->privilege); - } - putint(p, -1); - sendpacket(-1, 1, p.finalize()); - checkpausegame(); - return true; - } - - bool trykick(clientinfo *ci, int victim, const char *reason = NULL, const char *authname = NULL, const char *authdesc = NULL, int authpriv = PRIV_NONE, bool trial = false) - { - int priv = ci->privilege; - if(authname) - { - if(priv >= authpriv || ci->local) authname = authdesc = NULL; - else priv = authpriv; - } - if((priv || ci->local) && ci->clientnum!=victim) - { - clientinfo *vinfo = (clientinfo *)getclientinfo(victim); - if(vinfo && vinfo->connected && (priv >= vinfo->privilege || ci->local) && vinfo->privilege < PRIV_ADMIN && !vinfo->local) - { - if(trial) return true; - string kicker; - if(authname) - { - if(authdesc && authdesc[0]) formatstring(kicker, "%s as '\fs\f5%s\fr' [\fs\f0%s\fr]", colorname(ci), authname, authdesc); - else formatstring(kicker, "%s as '\fs\f5%s\fr'", colorname(ci), authname); - } - else copystring(kicker, colorname(ci)); - if(reason && reason[0]) sendservmsgf("%s kicked %s because: %s", kicker, colorname(vinfo), reason); - else sendservmsgf("%s kicked %s", kicker, colorname(vinfo)); - uint ip = getclientip(victim); - addban(ip, 4*60*60000); - kickclients(ip, ci, priv); - } - } - return false; - } - - savedscore *findscore(clientinfo *ci, bool insert) - { - uint ip = getclientip(ci->clientnum); - if(!ip && !ci->local) return 0; - if(!insert) - { - loopv(clients) - { - clientinfo *oi = clients[i]; - if(oi->clientnum != ci->clientnum && getclientip(oi->clientnum) == ip && !strcmp(oi->name, ci->name)) - { - oi->state.timeplayed += lastmillis - oi->state.lasttimeplayed; - oi->state.lasttimeplayed = lastmillis; - static savedscore curscore; - curscore.save(oi->state); - return &curscore; - } - } - } - loopv(scores) - { - savedscore &sc = scores[i]; - if(sc.ip == ip && !strcmp(sc.name, ci->name)) return ≻ - } - if(!insert) return 0; - savedscore &sc = scores.add(); - sc.ip = ip; - copystring(sc.name, ci->name); - return ≻ - } - - void savescore(clientinfo *ci) - { - savedscore *sc = findscore(ci, true); - if(sc) sc->save(ci->state); - } - - static struct msgfilter - { - uchar msgmask[NUMMSG]; - - msgfilter(int msg, ...) - { - memset(msgmask, 0, sizeof(msgmask)); - va_list msgs; - va_start(msgs, msg); - for(uchar val = 1; msg < NUMMSG; msg = va_arg(msgs, int)) - { - if(msg < 0) val = uchar(-msg); - else msgmask[msg] = val; - } - va_end(msgs); - } - - uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; } - } msgfilter(-1, N_CONNECT, N_SERVINFO, N_INITCLIENT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, N_DIED, - N_SPAWNSTATE, N_FORCEDEATH, N_TEAMINFO, N_ITEMACC, N_ITEMSPAWN, N_TIMEUP, N_CDIS, N_CURRENTMASTER, N_PONG, N_RESUME, - N_ANNOUNCE, N_SENDDEMOLIST, N_SENDDEMO, N_DEMOPLAYBACK, N_SENDMAP, - N_CLIENT, N_AUTHCHAL, N_INITAI, N_EXPIRETOKENS, N_DROPTOKENS, N_STEALTOKENS, N_DEMOPACKET, -2, N_REMIP, - N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, - N_DELCUBE, N_EDITVAR, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, NUMMSG), - connectfilter(-1, N_CONNECT, -2, N_AUTHANS, -3, N_PING, NUMMSG); - - int checktype(int type, clientinfo *ci) - { - if(ci) - { - if(!ci->connected) switch(connectfilter[type]) - { - // allow only before authconnect - case 1: return !ci->connectauth ? type : -1; - // allow only during authconnect - case 2: return ci->connectauth ? type : -1; - // always allow - case 3: return type; - // never allow - default: return -1; - } - if(ci->local) return type; - } - switch(msgfilter[type]) - { - // server-only messages - case 1: return ci ? -1 : type; - // only allowed in coop-edit - case 2: if(m_edit) break; return -1; - // only allowed in coop-edit, no overflow check - case 3: return m_edit ? type : -1; - // no overflow check - case 4: return type; - } - if(ci && ++ci->overflow >= 200) return -2; - return type; - } - - struct worldstate - { - int uses, len; - uchar *data; - - worldstate() : uses(0), len(0), data(NULL) {} - - void setup(int n) { len = n; data = new uchar[n]; } - void cleanup() { DELETEA(data); len = 0; } - bool contains(const uchar *p) const { return p >= data && p < &data[len]; } - }; - vector worldstates; - bool reliablemessages = false; - - void cleanworldstate(ENetPacket *packet) - { - loopv(worldstates) - { - worldstate &ws = worldstates[i]; - if(!ws.contains(packet->data)) continue; - ws.uses--; - if(ws.uses <= 0) - { - ws.cleanup(); - worldstates.removeunordered(i); - } - break; - } - } - - void flushclientposition(clientinfo &ci) - { - if(ci.position.empty() || (!hasnonlocalclients() && !demorecord)) return; - packetbuf p(ci.position.length(), 0); - p.put(ci.position.getbuf(), ci.position.length()); - ci.position.setsize(0); - sendpacket(-1, 0, p.finalize(), ci.ownernum); - } - - static void sendpositions(worldstate &ws, ucharbuf &wsbuf) - { - if(wsbuf.empty()) return; - int wslen = wsbuf.length(); - recordpacket(0, wsbuf.buf, wslen); - wsbuf.put(wsbuf.buf, wslen); - loopv(clients) - { - clientinfo &ci = *clients[i]; - if(ci.state.aitype != AI_NONE) continue; - uchar *data = wsbuf.buf; - int size = wslen; - if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } - if(size <= 0) continue; - ENetPacket *packet = enet_packet_create(data, size, ENET_PACKET_FLAG_NO_ALLOCATE); - sendpacket(ci.clientnum, 0, packet); - if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } - else enet_packet_destroy(packet); - } - wsbuf.offset(wsbuf.length()); - } - - static inline void addposition(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) - { - if(bi.position.empty()) return; - if(wsbuf.length() + bi.position.length() > mtu) sendpositions(ws, wsbuf); - int offset = wsbuf.length(); - wsbuf.put(bi.position.getbuf(), bi.position.length()); - bi.position.setsize(0); - int len = wsbuf.length() - offset; - if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } - else ci.wslen += len; - } - - static void sendmessages(worldstate &ws, ucharbuf &wsbuf) - { - if(wsbuf.empty()) return; - int wslen = wsbuf.length(); - recordpacket(1, wsbuf.buf, wslen); - wsbuf.put(wsbuf.buf, wslen); - loopv(clients) - { - clientinfo &ci = *clients[i]; - if(ci.state.aitype != AI_NONE) continue; - uchar *data = wsbuf.buf; - int size = wslen; - if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } - if(size <= 0) continue; - ENetPacket *packet = enet_packet_create(data, size, (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE); - sendpacket(ci.clientnum, 1, packet); - if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } - else enet_packet_destroy(packet); - } - wsbuf.offset(wsbuf.length()); - } - - static inline void addmessages(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) - { - if(bi.messages.empty()) return; - if(wsbuf.length() + 10 + bi.messages.length() > mtu) sendmessages(ws, wsbuf); - int offset = wsbuf.length(); - putint(wsbuf, N_CLIENT); - putint(wsbuf, bi.clientnum); - putuint(wsbuf, bi.messages.length()); - wsbuf.put(bi.messages.getbuf(), bi.messages.length()); - bi.messages.setsize(0); - int len = wsbuf.length() - offset; - if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } - else ci.wslen += len; - } - - bool buildworldstate() - { - int wsmax = 0; - loopv(clients) - { - clientinfo &ci = *clients[i]; - ci.overflow = 0; - ci.wsdata = NULL; - wsmax += ci.position.length(); - if(ci.messages.length()) wsmax += 10 + ci.messages.length(); - } - if(wsmax <= 0) - { - reliablemessages = false; - return false; - } - worldstate &ws = worldstates.add(); - ws.setup(2*wsmax); - int mtu = getservermtu() - 100; - if(mtu <= 0) mtu = ws.len; - ucharbuf wsbuf(ws.data, ws.len); - loopv(clients) - { - clientinfo &ci = *clients[i]; - if(ci.state.aitype != AI_NONE) continue; - addposition(ws, wsbuf, mtu, ci, ci); - loopvj(ci.bots) addposition(ws, wsbuf, mtu, *ci.bots[j], ci); - } - sendpositions(ws, wsbuf); - loopv(clients) - { - clientinfo &ci = *clients[i]; - if(ci.state.aitype != AI_NONE) continue; - addmessages(ws, wsbuf, mtu, ci, ci); - loopvj(ci.bots) addmessages(ws, wsbuf, mtu, *ci.bots[j], ci); - } - sendmessages(ws, wsbuf); - reliablemessages = false; - if(ws.uses) return true; - ws.cleanup(); - worldstates.drop(); - return false; - } - - bool sendpackets(bool force) - { - if(clients.empty() || (!hasnonlocalclients() && !demorecord)) return false; - enet_uint32 curtime = enet_time_get()-lastsend; - if(curtime<33 && !force) return false; - bool flush = buildworldstate(); - lastsend += curtime - (curtime%33); - return flush; - } - - template - void sendstate(gamestate &gs, T &p) - { - putint(p, gs.lifesequence); - putint(p, gs.health); - putint(p, gs.maxhealth); - putint(p, gs.armour); - putint(p, gs.maxarmour); - putint(p, gs.armourtype); - putint(p, gs.gunselect); - loopi(GUN_PISTOL-GUN_SG+1) putint(p, gs.ammo[GUN_SG+i]); - } - - void spawnstate(clientinfo *ci) - { - gamestate &gs = ci->state; - gs.spawnstate(gamemode); - gs.lifesequence = (gs.lifesequence + 1)&0x7F; - } - - void sendspawn(clientinfo *ci) - { - gamestate &gs = ci->state; - spawnstate(ci); - sendf(ci->ownernum, 1, "rii8v", N_SPAWNSTATE, ci->clientnum, gs.lifesequence, - gs.health, gs.maxhealth, - gs.armour, gs.maxarmour, gs.armourtype, - gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG]); - gs.lastspawn = gamemillis; - } - - void sendwelcome(clientinfo *ci) - { - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - int chan = welcomepacket(p, ci); - sendpacket(ci->clientnum, chan, p.finalize()); - } - - void putinitclient(clientinfo *ci, packetbuf &p) - { - if(ci->state.aitype != AI_NONE) - { - putint(p, N_INITAI); - putint(p, ci->clientnum); - putint(p, ci->ownernum); - putint(p, ci->state.aitype); - putint(p, ci->state.skill); - putint(p, ci->playermodel); - sendstring(ci->name, p); - sendstring(ci->team, p); - } - else - { - putint(p, N_INITCLIENT); - putint(p, ci->clientnum); - sendstring(ci->name, p); - sendstring(ci->team, p); - putint(p, ci->playermodel); - } - } - - void welcomeinitclient(packetbuf &p, int exclude = -1) - { - loopv(clients) - { - clientinfo *ci = clients[i]; - if(!ci->connected || ci->clientnum == exclude) continue; - - putinitclient(ci, p); - } - } - - bool hasmap(clientinfo *ci) - { - return (m_edit && (clients.length() > 0 || ci->local)) || - (smapname[0] && (!m_timed || gamemillis < gamelimit || (ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || numclients(ci->clientnum, true, true, true))); - } - - int welcomepacket(packetbuf &p, clientinfo *ci) - { - putint(p, N_WELCOME); - putint(p, N_MAPCHANGE); - sendstring(smapname, p); - putint(p, gamemode); - putint(p, notgotitems ? 1 : 0); - if(!ci || (m_timed && smapname[0])) - { - putint(p, N_TIMEUP); - putint(p, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0); - } - if(!notgotitems) - { - putint(p, N_ITEMLIST); - loopv(sents) if(sents[i].spawned) - { - putint(p, i); - putint(p, sents[i].type); - } - putint(p, -1); - } - bool hasmaster = false; - if(mastermode != MM_OPEN) - { - putint(p, N_CURRENTMASTER); - putint(p, mastermode); - hasmaster = true; - } - loopv(clients) if(clients[i]->privilege >= PRIV_MASTER) - { - if(!hasmaster) - { - putint(p, N_CURRENTMASTER); - putint(p, mastermode); - hasmaster = true; - } - putint(p, clients[i]->clientnum); - putint(p, clients[i]->privilege); - } - if(hasmaster) putint(p, -1); - if(gamepaused) - { - putint(p, N_PAUSEGAME); - putint(p, 1); - putint(p, -1); - } - if(gamespeed != 100) - { - putint(p, N_GAMESPEED); - putint(p, gamespeed); - putint(p, -1); - } - if(m_teammode) - { - putint(p, N_TEAMINFO); - enumerate(teaminfos, teaminfo, t, - if(t.frags) { sendstring(t.team, p); putint(p, t.frags); } - ); - sendstring("", p); - } - if(ci) - { - putint(p, N_SETTEAM); - putint(p, ci->clientnum); - sendstring(ci->team, p); - putint(p, -1); - } - if(ci && (m_demo || m_mp(gamemode)) && ci->state.state!=CS_SPECTATOR) - { - gamestate &gs = ci->state; - spawnstate(ci); - putint(p, N_SPAWNSTATE); - putint(p, ci->clientnum); - sendstate(gs, p); - gs.lastspawn = gamemillis; - } - if(ci && ci->state.state==CS_SPECTATOR) - { - putint(p, N_SPECTATOR); - putint(p, ci->clientnum); - putint(p, 1); - sendf(-1, 1, "ri3x", N_SPECTATOR, ci->clientnum, 1, ci->clientnum); - } - if(!ci || clients.length()>1) - { - putint(p, N_RESUME); - loopv(clients) - { - clientinfo *oi = clients[i]; - if(ci && oi->clientnum==ci->clientnum) continue; - putint(p, oi->clientnum); - putint(p, oi->state.state); - putint(p, oi->state.frags); - putint(p, oi->state.flags); - putint(p, oi->state.deaths); - putint(p, oi->state.quadmillis); - sendstate(oi->state, p); - } - putint(p, -1); - welcomeinitclient(p, ci ? ci->clientnum : -1); - } - return 1; - } - - bool restorescore(clientinfo *ci) - { - //if(ci->local) return false; - savedscore *sc = findscore(ci, false); - if(sc) - { - sc->restore(ci->state); - return true; - } - return false; - } - - void sendresume(clientinfo *ci) - { - gamestate &gs = ci->state; - sendf(-1, 1, "ri3i5i6vi", N_RESUME, ci->clientnum, gs.state, - gs.frags, gs.flags, gs.deaths, gs.quadmillis, - gs.lifesequence, - gs.health, gs.maxhealth, - gs.armour, gs.maxarmour, gs.armourtype, - gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG], -1); - } - - void sendinitclient(clientinfo *ci) - { - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - putinitclient(ci, p); - sendpacket(-1, 1, p.finalize(), ci->clientnum); - } - - void loaditems() - { - resetitems(); - notgotitems = true; - if(m_edit || !loadents(smapname, ments, &mcrc)) - return; - loopv(ments) if(canspawnitem(ments[i].type)) - { - server_entity se = { NOTUSED, 0, false }; - while(sents.length()<=i) sents.add(se); - sents[i].type = ments[i].type; - if(m_mp(gamemode) && delayspawn(sents[i].type)) sents[i].spawntime = spawntime(sents[i].type); - else sents[i].spawned = true; - } - notgotitems = false; - } - - void changemap(const char *s, int mode) - { - stopdemo(); - pausegame(false); - changegamespeed(100); - aiman::clearai(); - - gamemode = mode; - gamemillis = 0; - gamelimit = 10*60000; - interm = 0; - nextexceeded = 0; - copystring(smapname, s); - loaditems(); - scores.shrink(0); - shouldcheckteamkills = false; - teamkills.shrink(0); - loopv(clients) - { - clientinfo *ci = clients[i]; - ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; - } - - if(!m_mp(gamemode)) kicknonlocalclients(DISC_LOCAL); - - sendf(-1, 1, "risii", N_MAPCHANGE, smapname, gamemode, 1); - - clearteaminfo(); - if(m_teammode) autoteam(); - - if(m_timed && smapname[0]) sendf(-1, 1, "ri2", N_TIMEUP, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0); - loopv(clients) - { - clientinfo *ci = clients[i]; - ci->mapchange(); - ci->state.lasttimeplayed = lastmillis; - if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci); - } - - aiman::changemap(); - - if(m_demo) - { - if(clients.length()) setupdemoplayback(); - } - else - { - if(demonextmatch) setupdemorecord(); - demonextmatch = autorecorddemo!=0; - } - } - - void rotatemap(bool next) - { - if(!maprotations.inrange(curmaprotation)) - { - changemap("", 1); - return; - } - if(next) - { - curmaprotation = findmaprotation(gamemode, smapname); - if(curmaprotation >= 0) nextmaprotation(); - else curmaprotation = smapname[0] ? max(findmaprotation(gamemode, ""), 0) : 0; - } - maprotation &rot = maprotations[curmaprotation]; - changemap(rot.map, rot.findmode(gamemode)); - } - - struct votecount - { - char *map; - int mode, count; - votecount() {} - votecount(char *s, int n) : map(s), mode(n), count(0) {} - }; - - void checkvotes(bool force = false) - { - vector votes; - int maxvotes = 0; - loopv(clients) - { - clientinfo *oi = clients[i]; - if(oi->state.state==CS_SPECTATOR && !oi->privilege && !oi->local) continue; - if(oi->state.aitype!=AI_NONE) continue; - maxvotes++; - if(!m_valid(oi->modevote)) continue; - votecount *vc = NULL; - loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote==votes[j].mode) - { - vc = &votes[j]; - break; - } - if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote)); - vc->count++; - } - votecount *best = NULL; - loopv(votes) if(!best || votes[i].count > best->count || (votes[i].count == best->count && rnd(2))) best = &votes[i]; - if(force || (best && best->count > maxvotes/2)) - { - sendpackets(true); - if(demorecord) enddemorecord(); - if(best && (best->count > (force ? 1 : maxvotes/2))) - { - sendservmsg(force ? "vote passed by default" : "vote passed by majority"); - changemap(best->map, best->mode); - } - else rotatemap(true); - } - } - - void forcemap(const char *map, int mode) - { - stopdemo(); - if(!map[0] && !m_check(mode, M_EDIT)) - { - int idx = findmaprotation(mode, smapname); - if(idx < 0 && smapname[0]) idx = findmaprotation(mode, ""); - if(idx < 0) return; - map = maprotations[idx].map; - } - if(hasnonlocalclients()) sendservmsgf("local player forced %s on map %s", modename(mode), map[0] ? map : "[new map]"); - changemap(map, mode); - } - - void vote(const char *map, int reqmode, int sender) - { - clientinfo *ci = getinfo(sender); - if(!ci || (ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || (!ci->local && !m_mp(reqmode))) return; - if(!m_valid(reqmode)) return; - if(!map[0] && !m_check(reqmode, M_EDIT)) - { - int idx = findmaprotation(reqmode, smapname); - if(idx < 0 && smapname[0]) idx = findmaprotation(reqmode, ""); - if(idx < 0) return; - map = maprotations[idx].map; - } - if(lockmaprotation && !ci->local && ci->privilege < (lockmaprotation > 1 ? PRIV_ADMIN : PRIV_MASTER) && findmaprotation(reqmode, map) < 0) - { - sendf(sender, 1, "ris", N_SERVMSG, "This server has locked the map rotation."); - return; - } - copystring(ci->mapvote, map); - ci->modevote = reqmode; - if(ci->local || (ci->privilege && mastermode>=MM_VETO)) - { - sendpackets(true); - if(demorecord) enddemorecord(); - if(!ci->local || hasnonlocalclients()) - sendservmsgf("%s forced %s on map %s", colorname(ci), modename(ci->modevote), ci->mapvote[0] ? ci->mapvote : "[new map]"); - changemap(ci->mapvote, ci->modevote); - } - else - { - sendservmsgf("%s suggests %s on map %s (select map to vote)", colorname(ci), modename(reqmode), map[0] ? map : "[new map]"); - checkvotes(); - } - } - - VAR(overtime, 0, 0, 1); - - bool checkovertime() - { - if(!m_timed || !overtime) return false; - const char* topteam = NULL; - int topscore = INT_MIN; - bool tied = false; - if(m_teammode) - { - vector scores; - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue; - int score = 0; - if(teaminfo *ti = teaminfos.access(ci->team)) score = ti->frags; - if(!topteam || score > topscore) { topteam = ci->team; topscore = score; tied = false; } - else if(score == topscore && strcmp(ci->team, topteam)) tied = true; - } - } - else - { - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.state==CS_SPECTATOR) continue; - int score = ci->state.frags; - if(score > topscore) { topscore = score; tied = false; } - else if(score == topscore) tied = true; - } - } - if(!tied) return false; - sendservmsg("the game is tied with overtime"); - gamelimit = max(gamemillis, gamelimit) + 2*60000; - sendf(-1, 1, "ri2", N_TIMEUP, max((gamelimit - gamemillis)/1000, 1)); - return true; - } - - void checkintermission(bool force = false) - { - if(gamemillis >= gamelimit && !interm && (force || !checkovertime())) - { - sendf(-1, 1, "ri2", N_TIMEUP, 0); - changegamespeed(100); - interm = gamemillis + 10000; - } - } - - void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(true); } - - void dodamage(clientinfo *target, clientinfo *actor, int damage, int gun, const vec &hitpush = vec(0, 0, 0)) - { - gamestate &ts = target->state; - ts.dodamage(damage); - if(target!=actor && !isteam(target->team, actor->team)) actor->state.damage += damage; - sendf(-1, 1, "ri6", N_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health); - if(target==actor) target->setpushed(); - else if(!hitpush.iszero()) - { - ivec v(vec(hitpush).rescale(DNF)); - sendf(ts.health<=0 ? -1 : target->ownernum, 1, "ri7", N_HITPUSH, target->clientnum, gun, damage, v.x, v.y, v.z); - target->setpushed(); - } - if(ts.health<=0) - { - target->state.deaths++; - int fragvalue = (target==actor || isteam(target->team, actor->team) ? -1 : 1); - actor->state.frags += fragvalue; - if(fragvalue>0) - { - int friends = 0, enemies = 0; // note: friends also includes the fragger - if(m_teammode) loopv(clients) if(strcmp(clients[i]->team, actor->team)) enemies++; else friends++; - else { friends = 1; enemies = clients.length()-1; } - actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1)); - } - teaminfo *t = m_teammode ? teaminfos.access(actor->team) : NULL; - if(t) t->frags += fragvalue; - sendf(-1, 1, "ri5", N_DIED, target->clientnum, actor->clientnum, actor->state.frags, t ? t->frags : 0); - target->position.setsize(0); - ts.state = CS_DEAD; - ts.lastdeath = gamemillis; - if(actor!=target && isteam(actor->team, target->team)) - { - actor->state.teamkills++; - addteamkill(actor, target, 1); - } - ts.deadflush = ts.lastdeath + DEATHMILLIS; - // don't issue respawn yet until DEATHMILLIS has elapsed - // ts.respawn(); - } - } - - void suicide(clientinfo *ci) - { - gamestate &gs = ci->state; - if(gs.state!=CS_ALIVE) return; - int fragvalue = -1; - ci->state.frags += fragvalue; - ci->state.deaths++; - teaminfo *t = m_teammode ? teaminfos.access(ci->team) : NULL; - if(t) t->frags += fragvalue; - sendf(-1, 1, "ri5", N_DIED, ci->clientnum, ci->clientnum, gs.frags, t ? t->frags : 0); - ci->position.setsize(0); - gs.state = CS_DEAD; - gs.lastdeath = gamemillis; - gs.respawn(); - } - - void suicideevent::process(clientinfo *ci) - { - suicide(ci); - } - - void explodeevent::process(clientinfo *ci) - { - gamestate &gs = ci->state; - switch(gun) - { - case GUN_RL: - if(!gs.rockets.remove(id)) return; - break; - - case GUN_GL: - if(!gs.grenades.remove(id)) return; - break; - - default: - return; - } - sendf(-1, 1, "ri4x", N_EXPLODEFX, ci->clientnum, gun, id, ci->ownernum); - loopv(hits) - { - hitinfo &h = hits[i]; - clientinfo *target = getinfo(h.target); - if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>guns[gun].exprad) continue; - - bool dup = false; - loopj(i) if(hits[j].target==h.target) { dup = true; break; } - if(dup) continue; - - int damage = guns[gun].damage; - if(gs.quadmillis) damage *= 4; - damage = int(damage*(1-h.dist/EXP_DISTSCALE/guns[gun].exprad)); - if(target==ci) damage /= EXP_SELFDAMDIV; - dodamage(target, ci, damage, gun, h.dir); - } - } - - void shotevent::process(clientinfo *ci) - { - gamestate &gs = ci->state; - int wait = millis - gs.lastshot; - if(!gs.isalive(gamemillis) || - waitGUN_PISTOL || - gs.ammo[gun]<=0 || (guns[gun].range && from.dist(to) > guns[gun].range + 1)) - return; - if(gun!=GUN_FIST) gs.ammo[gun]--; - gs.lastshot = millis; - gs.gunwait = guns[gun].attackdelay; - sendf(-1, 1, "rii9x", N_SHOTFX, ci->clientnum, gun, id, - int(from.x*DMF), int(from.y*DMF), int(from.z*DMF), - int(to.x*DMF), int(to.y*DMF), int(to.z*DMF), - ci->ownernum); - gs.shotdamage += guns[gun].damage*(gs.quadmillis ? 4 : 1)*guns[gun].rays; - switch(gun) - { - case GUN_RL: gs.rockets.add(id); break; - case GUN_GL: gs.grenades.add(id); break; - default: - { - int totalrays = 0, maxrays = guns[gun].rays; - loopv(hits) - { - hitinfo &h = hits[i]; - clientinfo *target = getinfo(h.target); - if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1 || h.dist > guns[gun].range + 1) continue; - - totalrays += h.rays; - if(totalrays>maxrays) continue; - int damage = h.rays*guns[gun].damage; - if(gs.quadmillis) damage *= 4; - dodamage(target, ci, damage, gun, h.dir); - } - break; - } - } - } - - void pickupevent::process(clientinfo *ci) - { - gamestate &gs = ci->state; - if(m_mp(gamemode) && !gs.isalive(gamemillis)) return; - pickup(ent, ci->clientnum); - } - - bool gameevent::flush(clientinfo *ci, int fmillis) - { - process(ci); - return true; - } - - bool timedevent::flush(clientinfo *ci, int fmillis) - { - if(millis > fmillis) return false; - else if(millis >= ci->lastevent) - { - ci->lastevent = millis; - process(ci); - } - return true; - } - - void clearevent(clientinfo *ci) - { - delete ci->events.remove(0); - } - - void flushevents(clientinfo *ci, int millis) - { - while(ci->events.length()) - { - gameevent *ev = ci->events[0]; - if(ev->flush(ci, millis)) clearevent(ci); - else break; - } - } - - void processevents() - { - loopv(clients) - { - clientinfo *ci = clients[i]; - if(curtime>0 && ci->state.quadmillis) ci->state.quadmillis = max(ci->state.quadmillis-curtime, 0); - flushevents(ci, gamemillis); - } - } - - void cleartimedevents(clientinfo *ci) - { - int keep = 0; - loopv(ci->events) - { - if(ci->events[i]->keepable()) - { - if(keep < i) - { - for(int j = keep; j < i; j++) delete ci->events[j]; - ci->events.remove(keep, i - keep); - i = keep; - } - keep = i+1; - continue; - } - } - while(ci->events.length() > keep) delete ci->events.pop(); - ci->timesync = false; - } - - void serverupdate() - { - if(shouldstep && !gamepaused) - { - gamemillis += curtime; - - if(m_demo) readdemo(); - else if(!m_timed || gamemillis < gamelimit) - { - processevents(); - if(curtime) - { - loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached - { - int oldtime = sents[i].spawntime; - sents[i].spawntime -= curtime; - if(sents[i].spawntime<=0) - { - sents[i].spawntime = 0; - sents[i].spawned = true; - sendf(-1, 1, "ri2", N_ITEMSPAWN, i); - } - else if(sents[i].spawntime<=10000 && oldtime>10000 && (sents[i].type==I_QUAD || sents[i].type==I_BOOST)) - { - sendf(-1, 1, "ri2", N_ANNOUNCE, sents[i].type); - } - } - } - aiman::checkai(); - } - } - - while(bannedips.length() && bannedips[0].expire-totalmillis <= 0) bannedips.remove(0); - //~loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT); - - if(nextexceeded && gamemillis > nextexceeded && (!m_timed || gamemillis < gamelimit)) - { - nextexceeded = 0; - loopvrev(clients) - { - clientinfo &c = *clients[i]; - if(c.state.aitype != AI_NONE) continue; - //~if(c.checkexceeded()) disconnect_client(c.clientnum, DISC_MSGERR); - else c.scheduleexceeded(); - } - } - - if(shouldcheckteamkills) checkteamkills(); - - if(shouldstep && !gamepaused) - { - if(m_timed && smapname[0] && gamemillis-curtime>0) checkintermission(); - if(interm > 0 && gamemillis>interm) - { - if(demorecord) enddemorecord(); - interm = -1; - checkvotes(true); - } - } - - shouldstep = clients.length() > 0; - } - - void forcespectator(clientinfo *ci) - { - if(ci->state.state==CS_ALIVE) suicide(ci); - ci->state.state = CS_SPECTATOR; - ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; - if(!ci->local && (!ci->privilege || ci->warned)) aiman::removeai(ci); - sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 1); - } - - struct crcinfo - { - int crc, matches; - - crcinfo() {} - crcinfo(int crc, int matches) : crc(crc), matches(matches) {} - - static bool compare(const crcinfo &x, const crcinfo &y) { return x.matches > y.matches; } - }; - - VAR(modifiedmapspectator, 0, 1, 2); - - void checkmaps(int req = -1) - { - if(m_edit || !smapname[0]) return; - vector crcs; - int total = 0, unsent = 0, invalid = 0; - if(mcrc) crcs.add(crcinfo(mcrc, clients.length() + 1)); - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE) continue; - total++; - if(!ci->clientmap[0]) - { - if(ci->mapcrc < 0) invalid++; - else if(!ci->mapcrc) unsent++; - } - else - { - crcinfo *match = NULL; - loopvj(crcs) if(crcs[j].crc == ci->mapcrc) { match = &crcs[j]; break; } - if(!match) crcs.add(crcinfo(ci->mapcrc, 1)); - else match->matches++; - } - } - if(!mcrc && total - unsent < min(total, 4)) return; - crcs.sort(crcinfo::compare); - string msg; - loopv(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || ci->clientmap[0] || ci->mapcrc >= 0 || (req < 0 && ci->warned)) continue; - formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname); - sendf(req, 1, "ris", N_SERVMSG, msg); - if(req < 0) ci->warned = true; - } - if(crcs.length() >= 2) loopv(crcs) - { - crcinfo &info = crcs[i]; - if(i || info.matches <= crcs[i+1].matches) loopvj(clients) - { - clientinfo *ci = clients[j]; - if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || !ci->clientmap[0] || ci->mapcrc != info.crc || (req < 0 && ci->warned)) continue; - formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname); - sendf(req, 1, "ris", N_SERVMSG, msg); - if(req < 0) ci->warned = true; - } - } - if(req < 0 && modifiedmapspectator && (mcrc || modifiedmapspectator > 1)) loopv(clients) - { - clientinfo *ci = clients[i]; - if(!ci->local && ci->warned && ci->state.state != CS_SPECTATOR) forcespectator(ci); - } - } - - bool shouldspectate(clientinfo *ci) - { - return !ci->local && ci->warned && modifiedmapspectator && (mcrc || modifiedmapspectator > 1); - } - - void unspectate(clientinfo *ci) - { - if(shouldspectate(ci)) return; - ci->state.state = CS_DEAD; - ci->state.respawn(); - ci->state.lasttimeplayed = lastmillis; - aiman::addclient(ci); - sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 0); - if(ci->clientmap[0] || ci->mapcrc) checkmaps(); - if(!hasmap(ci)) rotatemap(true); - } - - void sendservinfo(clientinfo *ci) - { - sendf(ci->clientnum, 1, "ri5ss", N_SERVINFO, ci->clientnum, PROTOCOL_VERSION, ci->sessionid, serverpass[0] ? 1 : 0, serverdesc, serverauth); - } - - void noclients() - { - bannedips.shrink(0); - aiman::clearai(); - } - - void localconnect(int n) - { - clientinfo *ci = getinfo(n); - ci->clientnum = ci->ownernum = n; - ci->connectmillis = totalmillis; - ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF; - ci->local = true; - - connects.add(ci); - sendservinfo(ci); - } - - void localdisconnect(int n) - { - if(m_demo) enddemoplayback(); - clientdisconnect(n); - } - - int clientconnect(int n) - { - clientinfo *ci = getinfo(n); - ci->clientnum = ci->ownernum = n; - ci->connectmillis = totalmillis; - ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF; - - connects.add(ci); - if(!m_mp(gamemode)) return DISC_LOCAL; - sendservinfo(ci); - return DISC_NONE; - } - - void clientdisconnect(int n) - { - clientinfo *ci = getinfo(n); - loopv(clients) if(clients[i]->authkickvictim == ci->clientnum) clients[i]->cleanauth(); - if(ci->connected) - { - if(ci->privilege) setmaster(ci, false); - ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; - savescore(ci); - sendf(-1, 1, "ri2", N_CDIS, n); - clients.removeobj(ci); - aiman::removeai(ci); - if(!numclients(-1, false, true)) noclients(); // bans clear when server empties - if(ci->local) checkpausegame(); - } - else connects.removeobj(ci); - } - - int reserveclients() { return 3; } - - extern void verifybans(); - - struct banlist - { - vector bans; - - void clear() { bans.shrink(0); } - - bool check(uint ip) - { - loopv(bans) if(bans[i].check(ip)) return true; - return false; - } - - void add(const char *ipname) - { - ipmask ban; - ban.parse(ipname); - bans.add(ban); - - verifybans(); - } - } ipbans, gbans; - - bool checkbans(uint ip) - { - loopv(bannedips) if(bannedips[i].ip==ip) return true; - return ipbans.check(ip) || gbans.check(ip); - } - - void verifybans() - { - loopvrev(clients) - { - clientinfo *ci = clients[i]; - if(ci->state.aitype != AI_NONE || ci->local || ci->privilege >= PRIV_ADMIN) continue; - //~if(checkbans(getclientip(ci->clientnum))) disconnect_client(ci->clientnum, DISC_IPBAN); - } - } - - ICOMMAND(clearipbans, "", (), ipbans.clear()); - ICOMMAND(ipban, "s", (const char *ipname), ipbans.add(ipname)); - - int allowconnect(clientinfo *ci, const char *pwd = "") - { - if(ci->local) return DISC_NONE; - if(!m_mp(gamemode)) return DISC_LOCAL; - if(serverpass[0]) - { - if(!checkpassword(ci, serverpass, pwd)) return DISC_PASSWORD; - return DISC_NONE; - } - if(adminpass[0] && checkpassword(ci, adminpass, pwd)) return DISC_NONE; - if(numclients(-1, false, true)>=maxclients) return DISC_MAXCLIENTS; - uint ip = getclientip(ci->clientnum); - if(checkbans(ip)) return DISC_IPBAN; - if(mastermode>=MM_PRIVATE && allowedips.find(ip)<0) return DISC_PRIVATE; - return DISC_NONE; - } - - bool allowbroadcast(int n) - { - clientinfo *ci = getinfo(n); - return ci && ci->connected; - } - - clientinfo *findauth(uint id) - { - loopv(clients) if(clients[i]->authreq == id) return clients[i]; - return NULL; - } - - - void authfailed(clientinfo *ci) - { - if(!ci) return; - ci->cleanauth(); - //~if(ci->connectauth) disconnect_client(ci->clientnum, ci->connectauth); - } - - void authfailed(uint id) - { - authfailed(findauth(id)); - } - - void authsucceeded(uint id) - { - clientinfo *ci = findauth(id); - if(!ci) return; - ci->cleanauth(ci->connectauth!=0); - if(ci->connectauth) connected(ci); - if(ci->authkickvictim >= 0) - { - if(setmaster(ci, true, "", ci->authname, NULL, PRIV_AUTH, false, true)) - trykick(ci, ci->authkickvictim, ci->authkickreason, ci->authname, NULL, PRIV_AUTH); - ci->cleanauthkick(); - } - else setmaster(ci, true, "", ci->authname, NULL, PRIV_AUTH); - } - - void authchallenged(uint id, const char *val, const char *desc = "") - { - clientinfo *ci = findauth(id); - if(!ci) return; - sendf(ci->clientnum, 1, "risis", N_AUTHCHAL, desc, id, val); - } - - uint nextauthreq = 0; - - bool tryauth(clientinfo *ci, const char *user, const char *desc) - { - ci->cleanauth(); - if(!nextauthreq) nextauthreq = 1; - ci->authreq = nextauthreq++; - filtertext(ci->authname, user, false, false, 100); - copystring(ci->authdesc, desc); - if(ci->authdesc[0]) - { - userinfo *u = users.access(userkey(ci->authname, ci->authdesc)); - if(u) - { - uint seed[3] = { ::hthash(serverauth) + detrnd(size_t(ci) + size_t(user) + size_t(desc), 0x10000), uint(totalmillis), randomMT() }; - vector buf; - ci->authchallenge = genchallenge(u->pubkey, seed, sizeof(seed), buf); - sendf(ci->clientnum, 1, "risis", N_AUTHCHAL, desc, ci->authreq, buf.getbuf()); - } - else ci->cleanauth(); - } - else if(!requestmasterf("reqauth %u %s\n", ci->authreq, ci->authname)) - { - ci->cleanauth(); - sendf(ci->clientnum, 1, "ris", N_SERVMSG, "not connected to authentication server"); - } - if(ci->authreq) return true; - //~if(ci->connectauth) disconnect_client(ci->clientnum, ci->connectauth); - return false; - } - - bool answerchallenge(clientinfo *ci, uint id, char *val, const char *desc) - { - if(ci->authreq != id || strcmp(ci->authdesc, desc)) - { - ci->cleanauth(); - return !ci->connectauth; - } - for(char *s = val; *s; s++) - { - if(!isxdigit(*s)) { *s = '\0'; break; } - } - if(desc[0]) - { - if(ci->authchallenge && checkchallenge(val, ci->authchallenge)) - { - userinfo *u = users.access(userkey(ci->authname, ci->authdesc)); - if(u) - { - if(ci->connectauth) connected(ci); - if(ci->authkickvictim >= 0) - { - if(setmaster(ci, true, "", ci->authname, ci->authdesc, u->privilege, false, true)) - trykick(ci, ci->authkickvictim, ci->authkickreason, ci->authname, ci->authdesc, u->privilege); - } - else setmaster(ci, true, "", ci->authname, ci->authdesc, u->privilege); - } - } - ci->cleanauth(); - } - else if(!requestmasterf("confauth %u %s\n", id, val)) - { - ci->cleanauth(); - sendf(ci->clientnum, 1, "ris", N_SERVMSG, "not connected to authentication server"); - } - return ci->authreq || !ci->connectauth; - } - - void masterconnected() - { - } - - void masterdisconnected() - { - loopvrev(clients) - { - clientinfo *ci = clients[i]; - if(ci->authreq) authfailed(ci); - } - } - - void processmasterinput(const char *cmd, int cmdlen, const char *args) - { - uint id; - string val; - if(sscanf(cmd, "failauth %u", &id) == 1) - authfailed(id); - else if(sscanf(cmd, "succauth %u", &id) == 1) - authsucceeded(id); - else if(sscanf(cmd, "chalauth %u %255s", &id, val) == 2) - authchallenged(id, val); - else if(matchstring(cmd, cmdlen, "cleargbans")) - gbans.clear(); - else if(sscanf(cmd, "addgban %100s", val) == 1) - gbans.add(val); - } - - void receivefile(int sender, uchar *data, int len) - { - if(!m_edit || len <= 0 || len > 4*1024*1024) return; - clientinfo *ci = getinfo(sender); - if(ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) return; - if(mapdata) DELETEP(mapdata); - mapdata = opentempfile("mapdata", "w+b"); - if(!mapdata) { sendf(sender, 1, "ris", N_SERVMSG, "failed to open temporary file for map"); return; } - mapdata->write(data, len); - sendservmsgf("[%s sent a map to server, \"/getmap\" to receive it]", colorname(ci)); - } - - void sendclipboard(clientinfo *ci) - { - if(!ci->lastclipboard || !ci->clipboard) return; - bool flushed = false; - loopv(clients) - { - clientinfo &e = *clients[i]; - if(e.clientnum != ci->clientnum && e.needclipboard - ci->lastclipboard >= 0) - { - if(!flushed) { flushserver(true); flushed = true; } - sendpacket(e.clientnum, 1, ci->clipboard); - } - } - } - - void connected(clientinfo *ci) - { - if(m_demo) enddemoplayback(); - - if(!hasmap(ci)) rotatemap(false); - - shouldstep = true; - - connects.removeobj(ci); - clients.add(ci); - - ci->connectauth = 0; - ci->connected = true; - ci->needclipboard = totalmillis ? totalmillis : 1; - if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR; - ci->state.lasttimeplayed = lastmillis; - - const char *worst = m_teammode ? chooseworstteam(NULL, ci) : NULL; - copystring(ci->team, worst ? worst : "good", MAXTEAMLEN+1); - - sendwelcome(ci); - if(restorescore(ci)) sendresume(ci); - sendinitclient(ci); - - aiman::addclient(ci); - - if(m_demo) setupdemoplayback(); - - if(servermotd[0]) sendf(ci->clientnum, 1, "ris", N_SERVMSG, servermotd); - } - - void parsepacket(int sender, int chan, packetbuf &p) // has to parse exactly each byte of the packet - { - if(sender<0 || p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED || chan > 2) return; - char text[MAXTRANS]; - int type; - clientinfo *ci = sender>=0 ? getinfo(sender) : NULL, *cq = ci, *cm = ci; - if(ci && !ci->connected) - { - if(chan==0) return; - //~else if(chan!=1) { disconnect_client(sender, DISC_MSGERR); return; } - else while(p.length() < p.maxlen) switch(checktype(getint(p), ci)) - { - case N_CONNECT: - { - getstring(text, p); - filtertext(text, text, false, false, MAXNAMELEN); - if(!text[0]) copystring(text, "Anonymous"); - copystring(ci->name, text, MAXNAMELEN+1); - ci->playermodel = 0; - - string password, authdesc, authname; - getstring(password, p, sizeof(password)); - getstring(authdesc, p, sizeof(authdesc)); - getstring(authname, p, sizeof(authname)); - int disc = allowconnect(ci, password); - if(disc) - { - if(disc == DISC_LOCAL || !serverauth[0] || strcmp(serverauth, authdesc) || !tryauth(ci, authname, authdesc)) - { - //~disconnect_client(sender, disc); - return; - } - ci->connectauth = disc; - } - else connected(ci); - break; - } - - case N_AUTHANS: - { - string desc, ans; - getstring(desc, p, sizeof(desc)); - uint id = (uint)getint(p); - getstring(ans, p, sizeof(ans)); - if(!answerchallenge(ci, id, ans, desc)) - { - //~disconnect_client(sender, ci->connectauth); - return; - } - break; - } - - case N_PING: - getint(p); - break; - - default: - //~disconnect_client(sender, DISC_MSGERR); - return; - } - return; - } - else if(chan==2) - { - receivefile(sender, p.buf, p.maxlen); - return; - } - - if(p.packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; - #define QUEUE_AI clientinfo *cm = cq; - #define QUEUE_MSG { if(cm && (!cm->local || demorecord || hasnonlocalclients())) while(curmsgmessages.add(p.buf[curmsg++]); } - #define QUEUE_BUF(body) { \ - if(cm && (!cm->local || demorecord || hasnonlocalclients())) \ - { \ - curmsg = p.length(); \ - { body; } \ - } \ - } - #define QUEUE_INT(n) QUEUE_BUF(putint(cm->messages, n)) - #define QUEUE_UINT(n) QUEUE_BUF(putuint(cm->messages, n)) - #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, cm->messages)) - int curmsg; - while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci)) - { - case N_POS: - { - int pcn = getuint(p); - p.get(); - uint flags = getuint(p); - clientinfo *cp = getinfo(pcn); - if(cp && pcn != sender && cp->ownernum != sender) cp = NULL; - vec pos; - loopk(3) - { - int n = p.get(); n |= p.get()<<8; if(flags&(1<local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING)) - { - if(!ci->local && !m_edit && max(vel.magnitude2(), (float)fabs(vel.z)) >= 180) - cp->setexceeded(); - cp->position.setsize(0); - while(curmsgposition.add(p.buf[curmsg++]); - } - cp->state.o = pos; - cp->gameclip = (flags&0x80)!=0; - } - break; - } - - case N_TELEPORT: - { - int pcn = getint(p), teleport = getint(p), teledest = getint(p); - clientinfo *cp = getinfo(pcn); - if(cp && pcn != sender && cp->ownernum != sender) cp = NULL; - if(cp && (!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING)) - { - flushclientposition(*cp); - sendf(-1, 0, "ri4x", N_TELEPORT, pcn, teleport, teledest, cp->ownernum); - } - break; - } - - case N_JUMPPAD: - { - int pcn = getint(p), jumppad = getint(p); - clientinfo *cp = getinfo(pcn); - if(cp && pcn != sender && cp->ownernum != sender) cp = NULL; - if(cp && (!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING)) - { - cp->setpushed(); - flushclientposition(*cp); - sendf(-1, 0, "ri3x", N_JUMPPAD, pcn, jumppad, cp->ownernum); - } - break; - } - - case N_FROMAI: - { - int qcn = getint(p); - if(qcn < 0) cq = ci; - else - { - cq = getinfo(qcn); - if(cq && qcn != sender && cq->ownernum != sender) cq = NULL; - } - break; - } - - case N_EDITMODE: - { - int val = getint(p); - if(!ci->local && !m_edit) break; - if(val ? ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD : ci->state.state!=CS_EDITING) break; - if(val) - { - ci->state.editstate = ci->state.state; - ci->state.state = CS_EDITING; - ci->events.setsize(0); - ci->state.rockets.reset(); - ci->state.grenades.reset(); - } - else ci->state.state = ci->state.editstate; - QUEUE_MSG; - break; - } - - case N_MAPCRC: - { - getstring(text, p); - int crc = getint(p); - if(!ci) break; - if(strcmp(text, smapname)) - { - if(ci->clientmap[0]) - { - ci->clientmap[0] = '\0'; - ci->mapcrc = 0; - } - else if(ci->mapcrc > 0) ci->mapcrc = 0; - break; - } - copystring(ci->clientmap, text); - ci->mapcrc = text[0] ? crc : 1; - checkmaps(); - if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL; - break; - } - - case N_CHECKMAPS: - checkmaps(sender); - break; - - case N_TRYSPAWN: - if(!ci || !cq || cq->state.state!=CS_DEAD || cq->state.lastspawn>=0) break; - if(!ci->clientmap[0] && !ci->mapcrc) - { - ci->mapcrc = -1; - checkmaps(); - if(ci == cq) { if(ci->state.state != CS_DEAD) break; } - else if(cq->ownernum != ci->clientnum) { cq = NULL; break; } - } - if(cq->state.deadflush) - { - flushevents(cq, cq->state.deadflush); - cq->state.respawn(); - } - cleartimedevents(cq); - sendspawn(cq); - break; - - case N_GUNSELECT: - { - int gunselect = getint(p); - if(!cq || cq->state.state!=CS_ALIVE) break; - cq->state.gunselect = gunselect >= GUN_FIST && gunselect <= GUN_PISTOL ? gunselect : GUN_FIST; - QUEUE_AI; - QUEUE_MSG; - break; - } - - case N_SPAWN: - { - int ls = getint(p), gunselect = getint(p); - if(!cq || (cq->state.state!=CS_ALIVE && cq->state.state!=CS_DEAD && cq->state.state!=CS_EDITING) || ls!=cq->state.lifesequence || cq->state.lastspawn<0) break; - cq->state.lastspawn = -1; - cq->state.state = CS_ALIVE; - cq->state.gunselect = gunselect >= GUN_FIST && gunselect <= GUN_PISTOL ? gunselect : GUN_FIST; - cq->exceeded = 0; - QUEUE_AI; - QUEUE_BUF({ - putint(cm->messages, N_SPAWN); - sendstate(cq->state, cm->messages); - }); - break; - } - - case N_SUICIDE: - { - if(cq) cq->addevent(new suicideevent); - break; - } - - case N_SHOOT: - { - shotevent *shot = new shotevent; - shot->id = getint(p); - shot->millis = cq ? cq->geteventmillis(gamemillis, shot->id) : 0; - shot->gun = getint(p); - loopk(3) shot->from[k] = getint(p)/DMF; - loopk(3) shot->to[k] = getint(p)/DMF; - int hits = getint(p); - loopk(hits) - { - if(p.overread()) break; - hitinfo &hit = shot->hits.add(); - hit.target = getint(p); - hit.lifesequence = getint(p); - hit.dist = getint(p)/DMF; - hit.rays = getint(p); - loopk(3) hit.dir[k] = getint(p)/DNF; - } - if(cq) - { - cq->addevent(shot); - cq->setpushed(); - } - else delete shot; - break; - } - - case N_EXPLODE: - { - explodeevent *exp = new explodeevent; - int cmillis = getint(p); - exp->millis = cq ? cq->geteventmillis(gamemillis, cmillis) : 0; - exp->gun = getint(p); - exp->id = getint(p); - int hits = getint(p); - loopk(hits) - { - if(p.overread()) break; - hitinfo &hit = exp->hits.add(); - hit.target = getint(p); - hit.lifesequence = getint(p); - hit.dist = getint(p)/DMF; - hit.rays = getint(p); - loopk(3) hit.dir[k] = getint(p)/DNF; - } - if(cq) cq->addevent(exp); - else delete exp; - break; - } - - case N_ITEMPICKUP: - { - int n = getint(p); - if(!cq) break; - pickupevent *pickup = new pickupevent; - pickup->ent = n; - cq->addevent(pickup); - break; - } - - case N_TEXT: - { - QUEUE_AI; - QUEUE_MSG; - getstring(text, p); - filtertext(text, text, true, true); - QUEUE_STR(text); - if(isdedicatedserver() && cq) logoutf("%s: %s", colorname(cq), text); - break; - } - - case N_SAYTEAM: - { - getstring(text, p); - if(!ci || !cq || (ci->state.state==CS_SPECTATOR && !ci->local && !ci->privilege) || !m_teammode || !cq->team[0]) break; - filtertext(text, text, true, true); - loopv(clients) - { - clientinfo *t = clients[i]; - if(t==cq || t->state.state==CS_SPECTATOR || t->state.aitype != AI_NONE || strcmp(cq->team, t->team)) continue; - sendf(t->clientnum, 1, "riis", N_SAYTEAM, cq->clientnum, text); - } - if(isdedicatedserver() && cq) logoutf("%s <%s>: %s", colorname(cq), cq->team, text); - break; - } - - case N_SWITCHNAME: - { - QUEUE_MSG; - getstring(text, p); - filtertext(ci->name, text, false, false, MAXNAMELEN); - if(!ci->name[0]) copystring(ci->name, "Anonymous"); - QUEUE_STR(ci->name); - break; - } - - case N_SWITCHMODEL: - { - ci->playermodel = 0; - QUEUE_MSG; - break; - } - - case N_SWITCHTEAM: - { - getstring(text, p); - filtertext(text, text, false, false, MAXTEAMLEN); - if(m_teammode && text[0] && strcmp(ci->team, text) && addteaminfo(text)) - { - if(ci->state.state==CS_ALIVE) suicide(ci); - copystring(ci->team, text); - aiman::changeteam(ci); - sendf(-1, 1, "riisi", N_SETTEAM, sender, ci->team, ci->state.state==CS_SPECTATOR ? -1 : 0); - } - break; - } - - case N_MAPVOTE: - { - getstring(text, p); - filtertext(text, text, false); - fixmapname(text); - int reqmode = getint(p); - vote(text, reqmode, sender); - break; - } - - case N_ITEMLIST: - { - if((ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || !notgotitems || strcmp(ci->clientmap, smapname)) { while(getint(p)>=0 && !p.overread()) getint(p); break; } - int n; - while((n = getint(p))>=0 && nstate.state==CS_SPECTATOR) break; - QUEUE_MSG; - bool canspawn = canspawnitem(type); - if(istate.state!=CS_SPECTATOR) QUEUE_MSG; - break; - } - - case N_PING: - sendf(sender, 1, "i2", N_PONG, getint(p)); - break; - - case N_CLIENTPING: - { - int ping = getint(p); - if(ci) - { - ci->ping = ping; - loopv(ci->bots) ci->bots[i]->ping = ping; - } - QUEUE_MSG; - break; - } - - case N_MASTERMODE: - { - int mm = getint(p); - if((ci->privilege || ci->local) && mm>=MM_OPEN && mm<=MM_PRIVATE) - { - if((ci->privilege>=PRIV_ADMIN || ci->local) || (mastermask&(1<=MM_PRIVATE) - { - loopv(clients) allowedips.add(getclientip(clients[i]->clientnum)); - } - sendf(-1, 1, "rii", N_MASTERMODE, mastermode); - //sendservmsgf("mastermode is now %s (%d)", mastermodename(mastermode), mastermode); - } - else - { - defformatstring(s, "mastermode %d is disabled on this server", mm); - sendf(sender, 1, "ris", N_SERVMSG, s); - } - } - break; - } - - case N_CLEARBANS: - { - if(ci->privilege || ci->local) - { - bannedips.shrink(0); - sendservmsg("cleared all bans"); - } - break; - } - - case N_KICK: - { - int victim = getint(p); - getstring(text, p); - filtertext(text, text); - trykick(ci, victim, text); - break; - } - - case N_SPECTATOR: - { - int spectator = getint(p), val = getint(p); - if(!ci->privilege && !ci->local && (spectator!=sender || (ci->state.state==CS_SPECTATOR && mastermode>=MM_LOCKED))) break; - clientinfo *spinfo = (clientinfo *)getclientinfo(spectator); // no bots - if(!spinfo || !spinfo->connected || (spinfo->state.state==CS_SPECTATOR ? val : !val)) break; - - if(spinfo->state.state!=CS_SPECTATOR && val) forcespectator(spinfo); - else if(spinfo->state.state==CS_SPECTATOR && !val) unspectate(spinfo); - - if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL; - break; - } - - case N_SETTEAM: - { - int who = getint(p); - getstring(text, p); - filtertext(text, text, false, false, MAXTEAMLEN); - if(!ci->privilege && !ci->local) break; - clientinfo *wi = getinfo(who); - if(!m_teammode || !text[0] || !wi || !wi->connected || !strcmp(wi->team, text)) break; - if(addteaminfo(text)) - { - if(wi->state.state==CS_ALIVE) suicide(wi); - copystring(wi->team, text, MAXTEAMLEN+1); - } - aiman::changeteam(wi); - sendf(-1, 1, "riisi", N_SETTEAM, who, wi->team, 1); - break; - } - - case N_FORCEINTERMISSION: - if(ci->local && !hasnonlocalclients()) startintermission(); - break; - - case N_RECORDDEMO: - { - int val = getint(p); - if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; - if(!maxdemos || !maxdemosize) - { - sendf(ci->clientnum, 1, "ris", N_SERVMSG, "the server has disabled demo recording"); - break; - } - demonextmatch = val!=0; - sendservmsgf("demo recording is %s for next match", demonextmatch ? "enabled" : "disabled"); - break; - } - - case N_STOPDEMO: - { - if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; - stopdemo(); - break; - } - - case N_CLEARDEMOS: - { - int demo = getint(p); - if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; - cleardemos(demo); - break; - } - - case N_LISTDEMOS: - if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break; - listdemos(sender); - break; - - case N_GETDEMO: - { - int n = getint(p), tag = getint(p); - if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break; - senddemo(ci, n, tag); - break; - } - - case N_GETMAP: - if(!mapdata) sendf(sender, 1, "ris", N_SERVMSG, "no map to send"); - else if(ci->getmap) sendf(sender, 1, "ris", N_SERVMSG, "already sending map"); - else - { - sendservmsgf("[%s is getting the map]", colorname(ci)); - if((ci->getmap = sendfile(sender, 2, mapdata, "ri", N_SENDMAP))) - ci->getmap->freeCallback = freegetmap; - ci->needclipboard = totalmillis ? totalmillis : 1; - } - break; - - case N_NEWMAP: - { - int size = getint(p); - if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break; - if(size>=0) - { - smapname[0] = '\0'; - resetitems(); - notgotitems = false; - } - QUEUE_MSG; - break; - } - - case N_SETMASTER: - { - int mn = getint(p), val = getint(p); - getstring(text, p); - if(mn != ci->clientnum) - { - if(!ci->privilege && !ci->local) break; - clientinfo *minfo = (clientinfo *)getclientinfo(mn); - if(!minfo || !minfo->connected || (!ci->local && minfo->privilege >= ci->privilege) || (val && minfo->privilege)) break; - setmaster(minfo, val!=0, "", NULL, NULL, PRIV_MASTER, true); - } - else setmaster(ci, val!=0, text); - // don't broadcast the master password - break; - } - - case N_ADDBOT: - { - aiman::reqadd(ci, getint(p)); - break; - } - - case N_DELBOT: - { - aiman::reqdel(ci); - break; - } - - case N_BOTLIMIT: - { - int limit = getint(p); - if(ci) aiman::setbotlimit(ci, limit); - break; - } - - case N_BOTBALANCE: - { - int balance = getint(p); - if(ci) aiman::setbotbalance(ci, balance!=0); - break; - } - - case N_AUTHTRY: - { - string desc, name; - getstring(desc, p, sizeof(desc)); - getstring(name, p, sizeof(name)); - tryauth(ci, name, desc); - break; - } - - case N_AUTHKICK: - { - string desc, name; - getstring(desc, p, sizeof(desc)); - getstring(name, p, sizeof(name)); - int victim = getint(p); - getstring(text, p); - filtertext(text, text); - int authpriv = PRIV_AUTH; - if(desc[0]) - { - userinfo *u = users.access(userkey(name, desc)); - if(u) authpriv = u->privilege; else break; - } - if(ci->local || ci->privilege >= authpriv) trykick(ci, victim, text); - else if(trykick(ci, victim, text, name, desc, authpriv, true) && tryauth(ci, name, desc)) - { - ci->authkickvictim = victim; - ci->authkickreason = newstring(text); - } - break; - } - - case N_AUTHANS: - { - string desc, ans; - getstring(desc, p, sizeof(desc)); - uint id = (uint)getint(p); - getstring(ans, p, sizeof(ans)); - answerchallenge(ci, id, ans, desc); - break; - } - - case N_PAUSEGAME: - { - int val = getint(p); - if(ci->privilege < (restrictpausegame ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; - pausegame(val > 0, ci); - break; - } - - case N_GAMESPEED: - { - int val = getint(p); - if(ci->privilege < (restrictgamespeed ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; - changegamespeed(val, ci); - break; - } - - case N_COPY: - ci->cleanclipboard(); - ci->lastclipboard = totalmillis ? totalmillis : 1; - goto genericmsg; - - case N_PASTE: - if(ci->state.state!=CS_SPECTATOR) sendclipboard(ci); - goto genericmsg; - - case N_CLIPBOARD: - { - int unpacklen = getint(p), packlen = getint(p); - ci->cleanclipboard(false); - if(ci->state.state==CS_SPECTATOR) - { - if(packlen > 0) p.subbuf(packlen); - break; - } - if(packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) - { - if(packlen > 0) p.subbuf(packlen); - packlen = unpacklen = 0; - } - packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE); - putint(q, N_CLIPBOARD); - putint(q, ci->clientnum); - putint(q, unpacklen); - putint(q, packlen); - if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); - ci->clipboard = q.finalize(); - ci->clipboard->referenceCount++; - break; - } - - case N_EDITT: - case N_REPLACE: - case N_EDITVSLOT: - { - int size = server::msgsizelookup(type); - //~if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; } - loopi(size-1) getint(p); - //~if(p.remaining() < 2) { disconnect_client(sender, DISC_MSGERR); return; } - int extra = lilswap(*(const ushort *)p.pad(2)); - //~if(p.remaining() < extra) { disconnect_client(sender, DISC_MSGERR); return; } - p.pad(extra); - if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG; - break; - } - - case N_UNDO: - case N_REDO: - { - int unpacklen = getint(p), packlen = getint(p); - if(!ci || ci->state.state==CS_SPECTATOR || packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) - { - if(packlen > 0) p.subbuf(packlen); - break; - } - //~if(p.remaining() < packlen) { disconnect_client(sender, DISC_MSGERR); return; } - packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE); - putint(q, type); - putint(q, ci->clientnum); - putint(q, unpacklen); - putint(q, packlen); - if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); - sendpacket(-1, 1, q.finalize(), ci->clientnum); - break; - } - - case N_SERVCMD: - getstring(text, p); - break; - - - case -1: - //~disconnect_client(sender, DISC_MSGERR); - return; - - case -2: - //~disconnect_client(sender, DISC_OVERFLOW); - return; - - default: genericmsg: - { - int size = server::msgsizelookup(type); - //~if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; } - loopi(size-1) getint(p); - if(ci) switch(msgfilter[type]) - { - case 2: case 3: if(ci->state.state != CS_SPECTATOR) QUEUE_MSG; break; - default: if(cq && (ci != cq || ci->state.state!=CS_SPECTATOR)) { QUEUE_AI; QUEUE_MSG; } break; - } - break; - } - } - } - - int laninfoport() { return SAUERBRATEN_LANINFO_PORT; } - int serverinfoport(int servport) { return servport < 0 ? SAUERBRATEN_SERVINFO_PORT : servport+1; } - int serverport(int infoport) { return infoport < 0 ? SAUERBRATEN_SERVER_PORT : infoport-1; } - const char *defaultmaster() { return "master.sauerbraten.org"; } - int masterport() { return SAUERBRATEN_MASTER_PORT; } - int numchannels() { return 3; } - - #include "extinfo.h" - - void serverinforeply(ucharbuf &req, ucharbuf &p) - { - if(req.remaining() && !getint(req)) - { - extserverinforeply(req, p); - return; - } - - putint(p, numclients(-1, false, true)); - putint(p, gamepaused || gamespeed != 100 ? 7 : 5); // number of attrs following - putint(p, PROTOCOL_VERSION); // generic attributes, passed back below - putint(p, gamemode); - putint(p, m_timed ? max((gamelimit - gamemillis)/1000, 0) : 0); - putint(p, maxclients); - putint(p, serverpass[0] ? MM_PASSWORD : (!m_mp(gamemode) ? MM_PRIVATE : (mastermode || mastermask&MM_AUTOAPPROVE ? mastermode : MM_AUTH))); - if(gamepaused || gamespeed != 100) - { - putint(p, gamepaused ? 1 : 0); - putint(p, gamespeed); - } - sendstring(smapname, p); - sendstring(serverdesc, p); - sendserverinforeply(p); - } - - #include "aiman.h" + SVAR(servermotd, ""); + + struct teamkillkick + { + int modes, limit, ban; + + bool match(int mode) const + { + return (modes&(1<<(mode-STARTGAMEMODE)))!=0; + } + + bool includes(const teamkillkick &tk) const + { + return tk.modes != modes && (tk.modes & modes) == tk.modes; + } + }; + vector teamkillkicks; + + void teamkillkickreset() + { + teamkillkicks.setsize(0); + } + + void addteamkillkick(char *modestr, int *limit, int *ban) + { + vector modes; + explodelist(modestr, modes); + teamkillkick &kick = teamkillkicks.add(); + kick.modes = genmodemask(modes); + kick.limit = *limit; + kick.ban = *ban > 0 ? *ban*60000 : (*ban < 0 ? 0 : 30*60000); + modes.deletearrays(); + } + + COMMAND(teamkillkickreset, ""); + COMMANDN(teamkillkick, addteamkillkick, "sii"); + + struct teamkillinfo + { + uint ip; + int teamkills; + }; + vector teamkills; + bool shouldcheckteamkills = false; + + void addteamkill(clientinfo *actor, clientinfo *victim, int n) + { + if(!m_timed || actor->state.aitype != AI_NONE || actor->local || actor->privilege || (victim && victim->state.aitype != AI_NONE)) return; + shouldcheckteamkills = true; + uint ip = getclientip(actor->clientnum); + loopv(teamkills) if(teamkills[i].ip == ip) + { + teamkills[i].teamkills += n; + return; + } + teamkillinfo &tk = teamkills.add(); + tk.ip = ip; + tk.teamkills = n; + } + + void checkteamkills() + { + teamkillkick *kick = NULL; + if(m_timed) loopv(teamkillkicks) if(teamkillkicks[i].match(gamemode) && (!kick || kick->includes(teamkillkicks[i]))) + kick = &teamkillkicks[i]; + if(kick) loopvrev(teamkills) + { + teamkillinfo &tk = teamkills[i]; + if(tk.teamkills >= kick->limit) + { + if(kick->ban > 0) addban(tk.ip, kick->ban); + kickclients(tk.ip); + teamkills.removeunordered(i); + } + } + shouldcheckteamkills = false; + } + + void *newclientinfo() { return new clientinfo; } + void deleteclientinfo(void *ci) { delete (clientinfo *)ci; } + + clientinfo *getinfo(int n) + { + if(n < MAXCLIENTS) return (clientinfo *)getclientinfo(n); + n -= MAXCLIENTS; + return bots.inrange(n) ? bots[n] : NULL; + } + + uint mcrc = 0; + vector ments; + vector sents; + vector scores; + + int msgsizelookup(int msg) + { + static int sizetable[NUMMSG] = { -1 }; + if(sizetable[0] < 0) + { + memset(sizetable, -1, sizeof(sizetable)); + for(const int *p = msgsizes; *p >= 0; p += 2) sizetable[p[0]] = p[1]; + } + return msg >= 0 && msg < NUMMSG ? sizetable[msg] : -1; + } + + const char *modename(int n, const char *unknown) + { + if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].name; + return unknown; + } + + const char *mastermodename(int n, const char *unknown) + { + return (n>=MM_START && size_t(n-MM_START)clientnum!=exclude && (!nospec || ci->state.state!=CS_SPECTATOR || (priv && (ci->privilege || ci->local))) && (!noai || ci->state.aitype == AI_NONE)) n++; + } + return n; + } + + bool duplicatename(clientinfo *ci, const char *name) + { + if(!name) name = ci->name; + loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true; + return false; + } + + const char *colorname(clientinfo *ci, const char *name = NULL) + { + if(!name) name = ci->name; + if(name[0] && !duplicatename(ci, name) && ci->state.aitype == AI_NONE) return name; + static string cname[3]; + static int cidx = 0; + cidx = (cidx+1)%3; + formatstring(cname[cidx], ci->state.aitype == AI_NONE ? "%s \fs\f5(%d)\fr" : "%s \fs\f5[%d]\fr", name, ci->clientnum); + return cname[cidx]; + } + + bool canspawnitem(int type) { return !m_noitems && (type>=I_SHELLS && type<=I_QUAD && (!m_noammo || typeI_CARTRIDGES)); } + + int spawntime(int type) + { + int np = numclients(-1, true, false); + np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players + int sec = 0; + switch(type) + { + case I_SHELLS: + case I_BULLETS: + case I_ROCKETS: + case I_ROUNDS: + case I_GRENADES: + case I_CARTRIDGES: sec = np*4; break; + case I_HEALTH: sec = np*5; break; + case I_TINYHEALTH: sec = np*5; break; + case I_TINYARMOUR: sec = np*5; break; + case I_GREENARMOUR: sec = 20; break; + case I_YELLOWARMOUR: sec = 30; break; + case I_BOOST: sec = 60; break; + case I_QUAD: sec = 70; break; + } + return sec*1000; + } + + bool delayspawn(int type) + { + switch(type) + { + case I_GREENARMOUR: + case I_YELLOWARMOUR: + case I_BOOST: + case I_QUAD: + return true; + default: + return false; + } + } + + bool pickup(int i, int sender) // server side item pickup, acknowledge first client that gets it + { + if((m_timed && gamemillis>=gamelimit) || !sents.inrange(i) || !sents[i].spawned) return false; + clientinfo *ci = getinfo(sender); + if(!ci) return false; + if(!ci->local && !ci->state.canpickup(sents[i].type)) + { + sendf(sender, 1, "ri3", N_ITEMACC, i, -1); + return false; + } + sents[i].spawned = false; + sents[i].spawntime = spawntime(sents[i].type); + sendf(-1, 1, "ri3", N_ITEMACC, i, sender); + ci->state.pickup(sents[i].type); + return true; + } + + static hashset teaminfos; + + void clearteaminfo() + { + teaminfos.clear(); + } + + bool teamhasplayers(const char *team) { loopv(clients) if(!strcmp(clients[i]->team, team)) return true; return false; } + + bool pruneteaminfo() + { + int oldteams = teaminfos.numelems; + enumerate(teaminfos, teaminfo, old, + if(!old.frags && !teamhasplayers(old.team)) teaminfos.remove(old.team); + ); + return teaminfos.numelems < oldteams; + } + + teaminfo *addteaminfo(const char *team) + { + teaminfo *t = teaminfos.access(team); + if(!t) + { + if(teaminfos.numelems >= MAXTEAMS && !pruneteaminfo()) return NULL; + t = &teaminfos[team]; + copystring(t->team, team, sizeof(t->team)); + t->frags = 0; + } + return t; + } + + clientinfo *choosebestclient(float &bestrank) + { + clientinfo *best = NULL; + bestrank = -1; + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.timeplayed<0) continue; + float rank = ci->state.state!=CS_SPECTATOR ? ci->state.effectiveness/max(ci->state.timeplayed, 1) : -1; + if(!best || rank > bestrank) { best = ci; bestrank = rank; } + } + return best; + } + + VAR(persistteams, 0, 0, 1); + + void autoteam() + { + static const char * const teamnames[2] = {"good", "evil"}; + vector team[2]; + float teamrank[2] = {0, 0}; + for(int round = 0, remaining = clients.length(); remaining>=0; round++) + { + int first = round&1, second = (round+1)&1, selected = 0; + while(teamrank[first] <= teamrank[second]) + { + float rank; + clientinfo *ci = choosebestclient(rank); + if(!ci) break; + if(selected && rank<=0) break; + ci->state.timeplayed = -1; + team[first].add(ci); + if(rank>0) teamrank[first] += rank; + selected++; + if(rank<=0) break; + } + if(!selected) break; + remaining -= selected; + } + loopi(sizeof(team)/sizeof(team[0])) + { + addteaminfo(teamnames[i]); + loopvj(team[i]) + { + clientinfo *ci = team[i][j]; + if(!strcmp(ci->team, teamnames[i])) continue; + if(persistteams && ci->team[0]) + { + addteaminfo(ci->team); + continue; + } + copystring(ci->team, teamnames[i], MAXTEAMLEN+1); + sendf(-1, 1, "riisi", N_SETTEAM, ci->clientnum, teamnames[i], -1); + } + } + } + + struct teamrank + { + const char *name; + float rank; + int clients; + + teamrank(const char *name) : name(name), rank(0), clients(0) {} + }; + + const char *chooseworstteam(const char *suggest = NULL, clientinfo *exclude = NULL) + { + teamrank teamranks[2] = { teamrank("good"), teamrank("evil") }; + const int numteams = sizeof(teamranks)/sizeof(teamranks[0]); + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci==exclude || ci->state.aitype!=AI_NONE || ci->state.state==CS_SPECTATOR || !ci->team[0]) continue; + ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; + ci->state.lasttimeplayed = lastmillis; + + loopj(numteams) if(!strcmp(ci->team, teamranks[j].name)) + { + teamrank &ts = teamranks[j]; + ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1); + ts.clients++; + break; + } + } + teamrank *worst = &teamranks[numteams-1]; + loopi(numteams-1) + { + teamrank &ts = teamranks[i]; + if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts; + } + return worst->name; + } + + void prunedemos(int extra = 0) + { + int n = clamp(demos.length() + extra - maxdemos, 0, demos.length()); + if(n <= 0) return; + loopi(n) delete[] demos[i].data; + demos.remove(0, n); + } + + void adddemo() + { + if(!demotmp) return; + int len = (int)min(demotmp->size(), stream::offset((maxdemosize<<20) + 0x10000)); + demofile &d = demos.add(); + time_t t = time(NULL); + char *timestr = ctime(&t), *trim = timestr + strlen(timestr); + while(trim>timestr && iscubespace(*--trim)) *trim = '\0'; + formatstring(d.info, "%s: %s, %s, %.2f%s", timestr, modename(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); + sendservmsgf("demo \"%s\" recorded", d.info); + d.data = new uchar[len]; + d.len = len; + demotmp->seek(0, SEEK_SET); + demotmp->read(d.data, len); + DELETEP(demotmp); + } + + void enddemorecord() + { + if(!demorecord) return; + + DELETEP(demorecord); + + if(!demotmp) return; + if(!maxdemos || !maxdemosize) { DELETEP(demotmp); return; } + + prunedemos(1); + adddemo(); + } + + void writedemo(int chan, void *data, int len) + { + if(!demorecord) return; + int stamp[3] = { gamemillis, chan, len }; + lilswap(stamp, 3); + demorecord->write(stamp, sizeof(stamp)); + demorecord->write(data, len); + if(demorecord->rawtell() >= (maxdemosize<<20)) enddemorecord(); + } + + void recordpacket(int chan, void *data, int len) + { + writedemo(chan, data, len); + } + + int welcomepacket(packetbuf &p, clientinfo *ci); + void sendwelcome(clientinfo *ci); + + void setupdemorecord() + { + if(!m_mp(gamemode) || m_edit) return; + + demotmp = opentempfile("demorecord", "w+b"); + if(!demotmp) return; + + stream *f = opengzfile(NULL, "wb", demotmp); + if(!f) { DELETEP(demotmp); return; } + + sendservmsg("recording demo"); + + demorecord = f; + + demoheader hdr; + memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)); + hdr.version = DEMO_VERSION; + hdr.protocol = PROTOCOL_VERSION; + lilswap(&hdr.version, 2); + demorecord->write(&hdr, sizeof(demoheader)); + + packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + welcomepacket(p, NULL); + writedemo(1, p.buf, p.len); + } + + void listdemos(int cn) + { + packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(p, N_SENDDEMOLIST); + putint(p, demos.length()); + loopv(demos) sendstring(demos[i].info, p); + sendpacket(cn, 1, p.finalize()); + } + + void cleardemos(int n) + { + if(!n) + { + loopv(demos) delete[] demos[i].data; + demos.shrink(0); + sendservmsg("cleared all demos"); + } + else if(demos.inrange(n-1)) + { + delete[] demos[n-1].data; + demos.remove(n-1); + sendservmsgf("cleared demo %d", n); + } + } + + static void freegetmap(ENetPacket *packet) + { + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->getmap == packet) ci->getmap = NULL; + } + } + + static void freegetdemo(ENetPacket *packet) + { + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->getdemo == packet) ci->getdemo = NULL; + } + } + + void senddemo(clientinfo *ci, int num, int tag) + { + if(ci->getdemo) return; + if(!num) num = demos.length(); + if(!demos.inrange(num-1)) return; + demofile &d = demos[num-1]; + if((ci->getdemo = sendf(ci->clientnum, 2, "riim", N_SENDDEMO, tag, d.len, d.data))) + ci->getdemo->freeCallback = freegetdemo; + } + + void enddemoplayback() + { + if(!demoplayback) return; + DELETEP(demoplayback); + + loopv(clients) sendf(clients[i]->clientnum, 1, "ri3", N_DEMOPLAYBACK, 0, clients[i]->clientnum); + + sendservmsg("demo playback finished"); + + loopv(clients) sendwelcome(clients[i]); + } + + SVARP(demodir, "demo"); + + const char *getdemofile(const char *file, bool init) + { + if(!demodir[0]) return NULL; + static string buf; + copystring(buf, demodir); + int dirlen = strlen(buf); + if(buf[dirlen] != '/' && buf[dirlen] != '\\' && dirlen+1 < (int)sizeof(buf)) { buf[dirlen++] = '/'; buf[dirlen] = '\0'; } + if(init) + { + const char *dir = findfile(buf, "w"); + if(!fileexists(dir, "w")) createdir(dir); + } + concatstring(buf, file); + return buf; + } + + void setupdemoplayback() + { + if(demoplayback) return; + demoheader hdr; + string msg; + msg[0] = '\0'; + string file; + copystring(file, smapname); + int len = strlen(file); + if(len < 4 || strcasecmp(&file[len-4], ".dmo")) concatstring(file, ".dmo"); + if(const char *buf = getdemofile(file, false)) demoplayback = opengzfile(buf, "rb"); + if(!demoplayback) demoplayback = opengzfile(file, "rb"); + if(!demoplayback) formatstring(msg, "could not read demo \"%s\"", file); + else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic))) + formatstring(msg, "\"%s\" is not a demo file", file); + else + { + lilswap(&hdr.version, 2); + if(hdr.version!=DEMO_VERSION) formatstring(msg, "demo \"%s\" requires an %s version of Cube 2: Sauerbraten", file, hdr.versionread(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) + { + enddemoplayback(); + return; + } + lilswap(&nextplayback, 1); + } + + void readdemo() + { + if(!demoplayback) return; + while(gamemillis>=nextplayback) + { + int chan, len; + if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) || + demoplayback->read(&len, sizeof(len))!=sizeof(len)) + { + enddemoplayback(); + return; + } + lilswap(&chan, 1); + lilswap(&len, 1); + ENetPacket *packet = enet_packet_create(NULL, len+1, 0); + if(!packet || demoplayback->read(packet->data+1, len)!=size_t(len)) + { + if(packet) enet_packet_destroy(packet); + enddemoplayback(); + return; + } + packet->data[0] = N_DEMOPACKET; + sendpacket(-1, chan, packet); + if(!packet->referenceCount) enet_packet_destroy(packet); + if(!demoplayback) break; + if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) + { + enddemoplayback(); + return; + } + lilswap(&nextplayback, 1); + } + } + + void timeupdate(int secs) + { + if(!demoplayback) return; + if(secs <= 0) interm = -1; + else gamelimit = max(gamelimit, nextplayback + secs*1000); + } + + void seekdemo(char *t) + { + if(!demoplayback) return; + bool rev = *t == '-'; + if(rev) t++; + int mins = strtoul(t, &t, 10), secs = 0, millis = 0; + if(*t == ':') secs = strtoul(t+1, &t, 10); + else { secs = mins; mins = 0; } + if(*t == '.') millis = strtoul(t+1, &t, 10); + int offset = max(millis + (mins*60 + secs)*1000, 0), prevmillis = gamemillis; + if(rev) while(gamelimit - offset > gamemillis) + { + gamemillis = gamelimit - offset; + readdemo(); + } + else if(offset > gamemillis) + { + gamemillis = offset; + readdemo(); + } + if(gamemillis > prevmillis) + { + if(!interm) sendf(-1, 1, "ri2", N_TIMEUP, max((gamelimit - gamemillis)/1000, 1)); + } + } + + ICOMMAND(seekdemo, "sN$", (char *t, int *numargs, ident *id), + { + if(*numargs > 0) seekdemo(t); + else + { + int secs = gamemillis/1000; + defformatstring(str, "%d:%02d.%03d", secs/60, secs%60, gamemillis%1000); + if(*numargs < 0) result(str); + else printsvar(id, str); + } + }); + + void stopdemo() + { + if(m_demo) enddemoplayback(); + else enddemorecord(); + } + + void pausegame(bool val, clientinfo *ci = NULL) + { + if(gamepaused==val) return; + gamepaused = val; + sendf(-1, 1, "riii", N_PAUSEGAME, gamepaused ? 1 : 0, ci ? ci->clientnum : -1); + } + + void checkpausegame() + { + if(!gamepaused) return; + int admins = 0; + loopv(clients) if(clients[i]->privilege >= (restrictpausegame ? PRIV_ADMIN : PRIV_MASTER) || clients[i]->local) admins++; + if(!admins) pausegame(false); + } + + void forcepaused(bool paused) + { + pausegame(paused); + } + + bool ispaused() { return gamepaused; } + + void changegamespeed(int val, clientinfo *ci = NULL) + { + val = clamp(val, 10, 1000); + if(gamespeed==val) return; + gamespeed = val; + sendf(-1, 1, "riii", N_GAMESPEED, gamespeed, ci ? ci->clientnum : -1); + } + + void forcegamespeed(int speed) + { + changegamespeed(speed); + } + + int scaletime(int t) { return t*gamespeed; } + + SVAR(serverauth, ""); + + struct userkey + { + char *name; + char *desc; + + userkey() : name(NULL), desc(NULL) {} + userkey(char *name, char *desc) : name(name), desc(desc) {} + }; + + static inline uint hthash(const userkey &k) { return ::hthash(k.name); } + static inline bool htcmp(const userkey &x, const userkey &y) { return !strcmp(x.name, y.name) && !strcmp(x.desc, y.desc); } + + struct userinfo : userkey + { + void *pubkey; + int privilege; + + userinfo() : pubkey(NULL), privilege(PRIV_NONE) {} + ~userinfo() { delete[] name; delete[] desc; if(pubkey) freepubkey(pubkey); } + }; + hashset users; + + void adduser(char *name, char *desc, char *pubkey, char *priv) + { + userkey key(name, desc); + userinfo &u = users[key]; + if(u.pubkey) { freepubkey(u.pubkey); u.pubkey = NULL; } + if(!u.name) u.name = newstring(name); + if(!u.desc) u.desc = newstring(desc); + u.pubkey = parsepubkey(pubkey); + switch(priv[0]) + { + case 'a': case 'A': u.privilege = PRIV_ADMIN; break; + case 'm': case 'M': default: u.privilege = PRIV_AUTH; break; + case 'n': case 'N': u.privilege = PRIV_NONE; break; + } + } + COMMAND(adduser, "ssss"); + + void clearusers() + { + users.clear(); + } + COMMAND(clearusers, ""); + + void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen) + { + char buf[2*sizeof(string)]; + formatstring(buf, "%d %d ", cn, sessionid); + concatstring(buf, pwd, sizeof(buf)); + if(!hashstring(buf, result, maxlen)) *result = '\0'; + } + + bool checkpassword(clientinfo *ci, const char *wanted, const char *given) + { + string hash; + hashpassword(ci->clientnum, ci->sessionid, wanted, hash, sizeof(hash)); + return !strcmp(hash, given); + } + + void revokemaster(clientinfo *ci) + { + ci->privilege = PRIV_NONE; + if(ci->state.state==CS_SPECTATOR && !ci->local) aiman::removeai(ci); + } + + extern void connected(clientinfo *ci); + + bool setmaster(clientinfo *ci, bool val, const char *pass = "", const char *authname = NULL, const char *authdesc = NULL, int authpriv = PRIV_MASTER, bool force = false, bool trial = false) + { + if(authname && !val) return false; + const char *name = ""; + if(val) + { + bool haspass = adminpass[0] && checkpassword(ci, adminpass, pass); + int wantpriv = ci->local || haspass ? PRIV_ADMIN : authpriv; + if(wantpriv <= ci->privilege) return true; + else if(wantpriv <= PRIV_MASTER && !force) + { + if(ci->state.state==CS_SPECTATOR) + { + sendf(ci->clientnum, 1, "ris", N_SERVMSG, "Spectators may not claim master."); + return false; + } + loopv(clients) if(ci!=clients[i] && clients[i]->privilege) + { + sendf(ci->clientnum, 1, "ris", N_SERVMSG, "Master is already claimed."); + return false; + } + if(!authname && !(mastermask&MM_AUTOAPPROVE) && !ci->privilege && !ci->local) + { + sendf(ci->clientnum, 1, "ris", N_SERVMSG, "This server requires you to use the \"/auth\" command to claim master."); + return false; + } + } + if(trial) return true; + ci->privilege = wantpriv; + name = privname(ci->privilege); + } + else + { + if(!ci->privilege) return false; + if(trial) return true; + name = privname(ci->privilege); + revokemaster(ci); + } + bool hasmaster = false; + loopv(clients) if(clients[i]->local || clients[i]->privilege >= PRIV_MASTER) hasmaster = true; + if(!hasmaster) + { + mastermode = MM_OPEN; + allowedips.shrink(0); + } + string msg; + if(val && authname) + { + if(authdesc && authdesc[0]) formatstring(msg, "%s claimed %s as '\fs\f5%s\fr' [\fs\f0%s\fr]", colorname(ci), name, authname, authdesc); + else formatstring(msg, "%s claimed %s as '\fs\f5%s\fr'", colorname(ci), name, authname); + } + else formatstring(msg, "%s %s %s", colorname(ci), val ? "claimed" : "relinquished", name); + packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(p, N_SERVMSG); + sendstring(msg, p); + putint(p, N_CURRENTMASTER); + putint(p, mastermode); + loopv(clients) if(clients[i]->privilege >= PRIV_MASTER) + { + putint(p, clients[i]->clientnum); + putint(p, clients[i]->privilege); + } + putint(p, -1); + sendpacket(-1, 1, p.finalize()); + checkpausegame(); + return true; + } + + bool trykick(clientinfo *ci, int victim, const char *reason = NULL, const char *authname = NULL, const char *authdesc = NULL, int authpriv = PRIV_NONE, bool trial = false) + { + int priv = ci->privilege; + if(authname) + { + if(priv >= authpriv || ci->local) authname = authdesc = NULL; + else priv = authpriv; + } + if((priv || ci->local) && ci->clientnum!=victim) + { + clientinfo *vinfo = (clientinfo *)getclientinfo(victim); + if(vinfo && vinfo->connected && (priv >= vinfo->privilege || ci->local) && vinfo->privilege < PRIV_ADMIN && !vinfo->local) + { + if(trial) return true; + string kicker; + if(authname) + { + if(authdesc && authdesc[0]) formatstring(kicker, "%s as '\fs\f5%s\fr' [\fs\f0%s\fr]", colorname(ci), authname, authdesc); + else formatstring(kicker, "%s as '\fs\f5%s\fr'", colorname(ci), authname); + } + else copystring(kicker, colorname(ci)); + if(reason && reason[0]) sendservmsgf("%s kicked %s because: %s", kicker, colorname(vinfo), reason); + else sendservmsgf("%s kicked %s", kicker, colorname(vinfo)); + uint ip = getclientip(victim); + addban(ip, 4*60*60000); + kickclients(ip, ci, priv); + } + } + return false; + } + + savedscore *findscore(clientinfo *ci, bool insert) + { + uint ip = getclientip(ci->clientnum); + if(!ip && !ci->local) return 0; + if(!insert) + { + loopv(clients) + { + clientinfo *oi = clients[i]; + if(oi->clientnum != ci->clientnum && getclientip(oi->clientnum) == ip && !strcmp(oi->name, ci->name)) + { + oi->state.timeplayed += lastmillis - oi->state.lasttimeplayed; + oi->state.lasttimeplayed = lastmillis; + static savedscore curscore; + curscore.save(oi->state); + return &curscore; + } + } + } + loopv(scores) + { + savedscore &sc = scores[i]; + if(sc.ip == ip && !strcmp(sc.name, ci->name)) return ≻ + } + if(!insert) return 0; + savedscore &sc = scores.add(); + sc.ip = ip; + copystring(sc.name, ci->name); + return ≻ + } + + void savescore(clientinfo *ci) + { + savedscore *sc = findscore(ci, true); + if(sc) sc->save(ci->state); + } + + static struct msgfilter + { + uchar msgmask[NUMMSG]; + + msgfilter(int msg, ...) + { + memset(msgmask, 0, sizeof(msgmask)); + va_list msgs; + va_start(msgs, msg); + for(uchar val = 1; msg < NUMMSG; msg = va_arg(msgs, int)) + { + if(msg < 0) val = uchar(-msg); + else msgmask[msg] = val; + } + va_end(msgs); + } + + uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; } + } msgfilter(-1, N_CONNECT, N_SERVINFO, N_INITCLIENT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, N_DIED, + N_SPAWNSTATE, N_FORCEDEATH, N_TEAMINFO, N_ITEMACC, N_ITEMSPAWN, N_TIMEUP, N_CDIS, N_CURRENTMASTER, N_PONG, N_RESUME, + N_ANNOUNCE, N_SENDDEMOLIST, N_SENDDEMO, N_DEMOPLAYBACK, N_SENDMAP, + N_CLIENT, N_AUTHCHAL, N_INITAI, N_EXPIRETOKENS, N_DROPTOKENS, N_STEALTOKENS, N_DEMOPACKET, -2, N_REMIP, + N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, + N_DELCUBE, N_EDITVAR, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, NUMMSG), + connectfilter(-1, N_CONNECT, -2, N_AUTHANS, -3, N_PING, NUMMSG); + + int checktype(int type, clientinfo *ci) + { + if(ci) + { + if(!ci->connected) switch(connectfilter[type]) + { + // allow only before authconnect + case 1: return !ci->connectauth ? type : -1; + // allow only during authconnect + case 2: return ci->connectauth ? type : -1; + // always allow + case 3: return type; + // never allow + default: return -1; + } + if(ci->local) return type; + } + switch(msgfilter[type]) + { + // server-only messages + case 1: return ci ? -1 : type; + // only allowed in coop-edit + case 2: if(m_edit) break; return -1; + // only allowed in coop-edit, no overflow check + case 3: return m_edit ? type : -1; + // no overflow check + case 4: return type; + } + if(ci && ++ci->overflow >= 200) return -2; + return type; + } + + struct worldstate + { + int uses, len; + uchar *data; + + worldstate() : uses(0), len(0), data(NULL) {} + + void setup(int n) { len = n; data = new uchar[n]; } + void cleanup() { DELETEA(data); len = 0; } + bool contains(const uchar *p) const { return p >= data && p < &data[len]; } + }; + vector worldstates; + bool reliablemessages = false; + + void cleanworldstate(ENetPacket *packet) + { + loopv(worldstates) + { + worldstate &ws = worldstates[i]; + if(!ws.contains(packet->data)) continue; + ws.uses--; + if(ws.uses <= 0) + { + ws.cleanup(); + worldstates.removeunordered(i); + } + break; + } + } + + void flushclientposition(clientinfo &ci) + { + if(ci.position.empty() || (!hasnonlocalclients() && !demorecord)) return; + packetbuf p(ci.position.length(), 0); + p.put(ci.position.getbuf(), ci.position.length()); + ci.position.setsize(0); + sendpacket(-1, 0, p.finalize(), ci.ownernum); + } + + static void sendpositions(worldstate &ws, ucharbuf &wsbuf) + { + if(wsbuf.empty()) return; + int wslen = wsbuf.length(); + recordpacket(0, wsbuf.buf, wslen); + wsbuf.put(wsbuf.buf, wslen); + loopv(clients) + { + clientinfo &ci = *clients[i]; + if(ci.state.aitype != AI_NONE) continue; + uchar *data = wsbuf.buf; + int size = wslen; + if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } + if(size <= 0) continue; + ENetPacket *packet = enet_packet_create(data, size, ENET_PACKET_FLAG_NO_ALLOCATE); + sendpacket(ci.clientnum, 0, packet); + if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } + else enet_packet_destroy(packet); + } + wsbuf.offset(wsbuf.length()); + } + + static inline void addposition(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) + { + if(bi.position.empty()) return; + if(wsbuf.length() + bi.position.length() > mtu) sendpositions(ws, wsbuf); + int offset = wsbuf.length(); + wsbuf.put(bi.position.getbuf(), bi.position.length()); + bi.position.setsize(0); + int len = wsbuf.length() - offset; + if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } + else ci.wslen += len; + } + + static void sendmessages(worldstate &ws, ucharbuf &wsbuf) + { + if(wsbuf.empty()) return; + int wslen = wsbuf.length(); + recordpacket(1, wsbuf.buf, wslen); + wsbuf.put(wsbuf.buf, wslen); + loopv(clients) + { + clientinfo &ci = *clients[i]; + if(ci.state.aitype != AI_NONE) continue; + uchar *data = wsbuf.buf; + int size = wslen; + if(ci.wsdata >= wsbuf.buf) { data = ci.wsdata + ci.wslen; size -= ci.wslen; } + if(size <= 0) continue; + ENetPacket *packet = enet_packet_create(data, size, (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE); + sendpacket(ci.clientnum, 1, packet); + if(packet->referenceCount) { ws.uses++; packet->freeCallback = cleanworldstate; } + else enet_packet_destroy(packet); + } + wsbuf.offset(wsbuf.length()); + } + + static inline void addmessages(worldstate &ws, ucharbuf &wsbuf, int mtu, clientinfo &bi, clientinfo &ci) + { + if(bi.messages.empty()) return; + if(wsbuf.length() + 10 + bi.messages.length() > mtu) sendmessages(ws, wsbuf); + int offset = wsbuf.length(); + putint(wsbuf, N_CLIENT); + putint(wsbuf, bi.clientnum); + putuint(wsbuf, bi.messages.length()); + wsbuf.put(bi.messages.getbuf(), bi.messages.length()); + bi.messages.setsize(0); + int len = wsbuf.length() - offset; + if(ci.wsdata < wsbuf.buf) { ci.wsdata = &wsbuf.buf[offset]; ci.wslen = len; } + else ci.wslen += len; + } + + bool buildworldstate() + { + int wsmax = 0; + loopv(clients) + { + clientinfo &ci = *clients[i]; + ci.overflow = 0; + ci.wsdata = NULL; + wsmax += ci.position.length(); + if(ci.messages.length()) wsmax += 10 + ci.messages.length(); + } + if(wsmax <= 0) + { + reliablemessages = false; + return false; + } + worldstate &ws = worldstates.add(); + ws.setup(2*wsmax); + int mtu = getservermtu() - 100; + if(mtu <= 0) mtu = ws.len; + ucharbuf wsbuf(ws.data, ws.len); + loopv(clients) + { + clientinfo &ci = *clients[i]; + if(ci.state.aitype != AI_NONE) continue; + addposition(ws, wsbuf, mtu, ci, ci); + loopvj(ci.bots) addposition(ws, wsbuf, mtu, *ci.bots[j], ci); + } + sendpositions(ws, wsbuf); + loopv(clients) + { + clientinfo &ci = *clients[i]; + if(ci.state.aitype != AI_NONE) continue; + addmessages(ws, wsbuf, mtu, ci, ci); + loopvj(ci.bots) addmessages(ws, wsbuf, mtu, *ci.bots[j], ci); + } + sendmessages(ws, wsbuf); + reliablemessages = false; + if(ws.uses) return true; + ws.cleanup(); + worldstates.drop(); + return false; + } + + bool sendpackets(bool force) + { + if(clients.empty() || (!hasnonlocalclients() && !demorecord)) return false; + enet_uint32 curtime = enet_time_get()-lastsend; + if(curtime<33 && !force) return false; + bool flush = buildworldstate(); + lastsend += curtime - (curtime%33); + return flush; + } + + template + void sendstate(gamestate &gs, T &p) + { + putint(p, gs.lifesequence); + putint(p, gs.health); + putint(p, gs.maxhealth); + putint(p, gs.armour); + putint(p, gs.maxarmour); + putint(p, gs.armourtype); + putint(p, gs.gunselect); + loopi(GUN_PISTOL-GUN_SG+1) putint(p, gs.ammo[GUN_SG+i]); + } + + void spawnstate(clientinfo *ci) + { + gamestate &gs = ci->state; + gs.spawnstate(gamemode); + gs.lifesequence = (gs.lifesequence + 1)&0x7F; + } + + void sendspawn(clientinfo *ci) + { + gamestate &gs = ci->state; + spawnstate(ci); + sendf(ci->ownernum, 1, "rii8v", N_SPAWNSTATE, ci->clientnum, gs.lifesequence, + gs.health, gs.maxhealth, + gs.armour, gs.maxarmour, gs.armourtype, + gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG]); + gs.lastspawn = gamemillis; + } + + void sendwelcome(clientinfo *ci) + { + packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + int chan = welcomepacket(p, ci); + sendpacket(ci->clientnum, chan, p.finalize()); + } + + void putinitclient(clientinfo *ci, packetbuf &p) + { + if(ci->state.aitype != AI_NONE) + { + putint(p, N_INITAI); + putint(p, ci->clientnum); + putint(p, ci->ownernum); + putint(p, ci->state.aitype); + putint(p, ci->state.skill); + putint(p, ci->playermodel); + sendstring(ci->name, p); + sendstring(ci->team, p); + } + else + { + putint(p, N_INITCLIENT); + putint(p, ci->clientnum); + sendstring(ci->name, p); + sendstring(ci->team, p); + putint(p, ci->playermodel); + } + } + + void welcomeinitclient(packetbuf &p, int exclude = -1) + { + loopv(clients) + { + clientinfo *ci = clients[i]; + if(!ci->connected || ci->clientnum == exclude) continue; + + putinitclient(ci, p); + } + } + + bool hasmap(clientinfo *ci) + { + return (m_edit && (clients.length() > 0 || ci->local)) || + (smapname[0] && (!m_timed || gamemillis < gamelimit || (ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || numclients(ci->clientnum, true, true, true))); + } + + int welcomepacket(packetbuf &p, clientinfo *ci) + { + putint(p, N_WELCOME); + putint(p, N_MAPCHANGE); + sendstring(smapname, p); + putint(p, gamemode); + putint(p, notgotitems ? 1 : 0); + if(!ci || (m_timed && smapname[0])) + { + putint(p, N_TIMEUP); + putint(p, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0); + } + if(!notgotitems) + { + putint(p, N_ITEMLIST); + loopv(sents) if(sents[i].spawned) + { + putint(p, i); + putint(p, sents[i].type); + } + putint(p, -1); + } + bool hasmaster = false; + if(mastermode != MM_OPEN) + { + putint(p, N_CURRENTMASTER); + putint(p, mastermode); + hasmaster = true; + } + loopv(clients) if(clients[i]->privilege >= PRIV_MASTER) + { + if(!hasmaster) + { + putint(p, N_CURRENTMASTER); + putint(p, mastermode); + hasmaster = true; + } + putint(p, clients[i]->clientnum); + putint(p, clients[i]->privilege); + } + if(hasmaster) putint(p, -1); + if(gamepaused) + { + putint(p, N_PAUSEGAME); + putint(p, 1); + putint(p, -1); + } + if(gamespeed != 100) + { + putint(p, N_GAMESPEED); + putint(p, gamespeed); + putint(p, -1); + } + if(m_teammode) + { + putint(p, N_TEAMINFO); + enumerate(teaminfos, teaminfo, t, + if(t.frags) { sendstring(t.team, p); putint(p, t.frags); } + ); + sendstring("", p); + } + if(ci) + { + putint(p, N_SETTEAM); + putint(p, ci->clientnum); + sendstring(ci->team, p); + putint(p, -1); + } + if(ci && (m_demo || m_mp(gamemode)) && ci->state.state!=CS_SPECTATOR) + { + gamestate &gs = ci->state; + spawnstate(ci); + putint(p, N_SPAWNSTATE); + putint(p, ci->clientnum); + sendstate(gs, p); + gs.lastspawn = gamemillis; + } + if(ci && ci->state.state==CS_SPECTATOR) + { + putint(p, N_SPECTATOR); + putint(p, ci->clientnum); + putint(p, 1); + sendf(-1, 1, "ri3x", N_SPECTATOR, ci->clientnum, 1, ci->clientnum); + } + if(!ci || clients.length()>1) + { + putint(p, N_RESUME); + loopv(clients) + { + clientinfo *oi = clients[i]; + if(ci && oi->clientnum==ci->clientnum) continue; + putint(p, oi->clientnum); + putint(p, oi->state.state); + putint(p, oi->state.frags); + putint(p, oi->state.flags); + putint(p, oi->state.deaths); + putint(p, oi->state.quadmillis); + sendstate(oi->state, p); + } + putint(p, -1); + welcomeinitclient(p, ci ? ci->clientnum : -1); + } + return 1; + } + + bool restorescore(clientinfo *ci) + { + //if(ci->local) return false; + savedscore *sc = findscore(ci, false); + if(sc) + { + sc->restore(ci->state); + return true; + } + return false; + } + + void sendresume(clientinfo *ci) + { + gamestate &gs = ci->state; + sendf(-1, 1, "ri3i5i6vi", N_RESUME, ci->clientnum, gs.state, + gs.frags, gs.flags, gs.deaths, gs.quadmillis, + gs.lifesequence, + gs.health, gs.maxhealth, + gs.armour, gs.maxarmour, gs.armourtype, + gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG], -1); + } + + void sendinitclient(clientinfo *ci) + { + packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putinitclient(ci, p); + sendpacket(-1, 1, p.finalize(), ci->clientnum); + } + + void loaditems() + { + resetitems(); + notgotitems = true; + if(m_edit || !loadents(smapname, ments, &mcrc)) + return; + loopv(ments) if(canspawnitem(ments[i].type)) + { + server_entity se = { NOTUSED, 0, false }; + while(sents.length()<=i) sents.add(se); + sents[i].type = ments[i].type; + if(m_mp(gamemode) && delayspawn(sents[i].type)) sents[i].spawntime = spawntime(sents[i].type); + else sents[i].spawned = true; + } + notgotitems = false; + } + + void changemap(const char *s, int mode) + { + stopdemo(); + pausegame(false); + changegamespeed(100); + aiman::clearai(); + + gamemode = mode; + gamemillis = 0; + gamelimit = 10*60000; + interm = 0; + nextexceeded = 0; + copystring(smapname, s); + loaditems(); + scores.shrink(0); + shouldcheckteamkills = false; + teamkills.shrink(0); + loopv(clients) + { + clientinfo *ci = clients[i]; + ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; + } + + if(!m_mp(gamemode)) kicknonlocalclients(DISC_LOCAL); + + sendf(-1, 1, "risii", N_MAPCHANGE, smapname, gamemode, 1); + + clearteaminfo(); + if(m_teammode) autoteam(); + + if(m_timed && smapname[0]) sendf(-1, 1, "ri2", N_TIMEUP, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0); + loopv(clients) + { + clientinfo *ci = clients[i]; + ci->mapchange(); + ci->state.lasttimeplayed = lastmillis; + if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci); + } + + aiman::changemap(); + + if(m_demo) + { + if(clients.length()) setupdemoplayback(); + } + else + { + if(demonextmatch) setupdemorecord(); + demonextmatch = autorecorddemo!=0; + } + } + + void rotatemap(bool next) + { + if(!maprotations.inrange(curmaprotation)) + { + changemap("", 1); + return; + } + if(next) + { + curmaprotation = findmaprotation(gamemode, smapname); + if(curmaprotation >= 0) nextmaprotation(); + else curmaprotation = smapname[0] ? max(findmaprotation(gamemode, ""), 0) : 0; + } + maprotation &rot = maprotations[curmaprotation]; + changemap(rot.map, rot.findmode(gamemode)); + } + + struct votecount + { + char *map; + int mode, count; + votecount() {} + votecount(char *s, int n) : map(s), mode(n), count(0) {} + }; + + void checkvotes(bool force = false) + { + vector votes; + int maxvotes = 0; + loopv(clients) + { + clientinfo *oi = clients[i]; + if(oi->state.state==CS_SPECTATOR && !oi->privilege && !oi->local) continue; + if(oi->state.aitype!=AI_NONE) continue; + maxvotes++; + if(!m_valid(oi->modevote)) continue; + votecount *vc = NULL; + loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote==votes[j].mode) + { + vc = &votes[j]; + break; + } + if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote)); + vc->count++; + } + votecount *best = NULL; + loopv(votes) if(!best || votes[i].count > best->count || (votes[i].count == best->count && rnd(2))) best = &votes[i]; + if(force || (best && best->count > maxvotes/2)) + { + sendpackets(true); + if(demorecord) enddemorecord(); + if(best && (best->count > (force ? 1 : maxvotes/2))) + { + sendservmsg(force ? "vote passed by default" : "vote passed by majority"); + changemap(best->map, best->mode); + } + else rotatemap(true); + } + } + + void forcemap(const char *map, int mode) + { + stopdemo(); + if(!map[0] && !m_check(mode, M_EDIT)) + { + int idx = findmaprotation(mode, smapname); + if(idx < 0 && smapname[0]) idx = findmaprotation(mode, ""); + if(idx < 0) return; + map = maprotations[idx].map; + } + if(hasnonlocalclients()) sendservmsgf("local player forced %s on map %s", modename(mode), map[0] ? map : "[new map]"); + changemap(map, mode); + } + + void vote(const char *map, int reqmode, int sender) + { + clientinfo *ci = getinfo(sender); + if(!ci || (ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || (!ci->local && !m_mp(reqmode))) return; + if(!m_valid(reqmode)) return; + if(!map[0] && !m_check(reqmode, M_EDIT)) + { + int idx = findmaprotation(reqmode, smapname); + if(idx < 0 && smapname[0]) idx = findmaprotation(reqmode, ""); + if(idx < 0) return; + map = maprotations[idx].map; + } + if(lockmaprotation && !ci->local && ci->privilege < (lockmaprotation > 1 ? PRIV_ADMIN : PRIV_MASTER) && findmaprotation(reqmode, map) < 0) + { + sendf(sender, 1, "ris", N_SERVMSG, "This server has locked the map rotation."); + return; + } + copystring(ci->mapvote, map); + ci->modevote = reqmode; + if(ci->local || (ci->privilege && mastermode>=MM_VETO)) + { + sendpackets(true); + if(demorecord) enddemorecord(); + if(!ci->local || hasnonlocalclients()) + sendservmsgf("%s forced %s on map %s", colorname(ci), modename(ci->modevote), ci->mapvote[0] ? ci->mapvote : "[new map]"); + changemap(ci->mapvote, ci->modevote); + } + else + { + sendservmsgf("%s suggests %s on map %s (select map to vote)", colorname(ci), modename(reqmode), map[0] ? map : "[new map]"); + checkvotes(); + } + } + + VAR(overtime, 0, 0, 1); + + bool checkovertime() + { + if(!m_timed || !overtime) return false; + const char* topteam = NULL; + int topscore = INT_MIN; + bool tied = false; + if(m_teammode) + { + vector scores; + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue; + int score = 0; + if(teaminfo *ti = teaminfos.access(ci->team)) score = ti->frags; + if(!topteam || score > topscore) { topteam = ci->team; topscore = score; tied = false; } + else if(score == topscore && strcmp(ci->team, topteam)) tied = true; + } + } + else + { + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.state==CS_SPECTATOR) continue; + int score = ci->state.frags; + if(score > topscore) { topscore = score; tied = false; } + else if(score == topscore) tied = true; + } + } + if(!tied) return false; + sendservmsg("the game is tied with overtime"); + gamelimit = max(gamemillis, gamelimit) + 2*60000; + sendf(-1, 1, "ri2", N_TIMEUP, max((gamelimit - gamemillis)/1000, 1)); + return true; + } + + void checkintermission(bool force = false) + { + if(gamemillis >= gamelimit && !interm && (force || !checkovertime())) + { + sendf(-1, 1, "ri2", N_TIMEUP, 0); + changegamespeed(100); + interm = gamemillis + 10000; + } + } + + void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(true); } + + void dodamage(clientinfo *target, clientinfo *actor, int damage, int gun, const vec &hitpush = vec(0, 0, 0)) + { + gamestate &ts = target->state; + ts.dodamage(damage); + if(target!=actor && !isteam(target->team, actor->team)) actor->state.damage += damage; + sendf(-1, 1, "ri6", N_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health); + if(target==actor) target->setpushed(); + else if(!hitpush.iszero()) + { + ivec v(vec(hitpush).rescale(DNF)); + sendf(ts.health<=0 ? -1 : target->ownernum, 1, "ri7", N_HITPUSH, target->clientnum, gun, damage, v.x, v.y, v.z); + target->setpushed(); + } + if(ts.health<=0) + { + target->state.deaths++; + int fragvalue = (target==actor || isteam(target->team, actor->team) ? -1 : 1); + actor->state.frags += fragvalue; + if(fragvalue>0) + { + int friends = 0, enemies = 0; // note: friends also includes the fragger + if(m_teammode) loopv(clients) if(strcmp(clients[i]->team, actor->team)) enemies++; else friends++; + else { friends = 1; enemies = clients.length()-1; } + actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1)); + } + teaminfo *t = m_teammode ? teaminfos.access(actor->team) : NULL; + if(t) t->frags += fragvalue; + sendf(-1, 1, "ri5", N_DIED, target->clientnum, actor->clientnum, actor->state.frags, t ? t->frags : 0); + target->position.setsize(0); + ts.state = CS_DEAD; + ts.lastdeath = gamemillis; + if(actor!=target && isteam(actor->team, target->team)) + { + actor->state.teamkills++; + addteamkill(actor, target, 1); + } + ts.deadflush = ts.lastdeath + DEATHMILLIS; + // don't issue respawn yet until DEATHMILLIS has elapsed + // ts.respawn(); + } + } + + void suicide(clientinfo *ci) + { + gamestate &gs = ci->state; + if(gs.state!=CS_ALIVE) return; + int fragvalue = -1; + ci->state.frags += fragvalue; + ci->state.deaths++; + teaminfo *t = m_teammode ? teaminfos.access(ci->team) : NULL; + if(t) t->frags += fragvalue; + sendf(-1, 1, "ri5", N_DIED, ci->clientnum, ci->clientnum, gs.frags, t ? t->frags : 0); + ci->position.setsize(0); + gs.state = CS_DEAD; + gs.lastdeath = gamemillis; + gs.respawn(); + } + + void suicideevent::process(clientinfo *ci) + { + suicide(ci); + } + + void explodeevent::process(clientinfo *ci) + { + gamestate &gs = ci->state; + switch(gun) + { + case GUN_RL: + if(!gs.rockets.remove(id)) return; + break; + + case GUN_GL: + if(!gs.grenades.remove(id)) return; + break; + + default: + return; + } + sendf(-1, 1, "ri4x", N_EXPLODEFX, ci->clientnum, gun, id, ci->ownernum); + loopv(hits) + { + hitinfo &h = hits[i]; + clientinfo *target = getinfo(h.target); + if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>guns[gun].exprad) continue; + + bool dup = false; + loopj(i) if(hits[j].target==h.target) { dup = true; break; } + if(dup) continue; + + int damage = guns[gun].damage; + if(gs.quadmillis) damage *= 4; + damage = int(damage*(1-h.dist/EXP_DISTSCALE/guns[gun].exprad)); + if(target==ci) damage /= EXP_SELFDAMDIV; + dodamage(target, ci, damage, gun, h.dir); + } + } + + void shotevent::process(clientinfo *ci) + { + gamestate &gs = ci->state; + int wait = millis - gs.lastshot; + if(!gs.isalive(gamemillis) || + waitGUN_PISTOL || + gs.ammo[gun]<=0 || (guns[gun].range && from.dist(to) > guns[gun].range + 1)) + return; + if(gun!=GUN_FIST) gs.ammo[gun]--; + gs.lastshot = millis; + gs.gunwait = guns[gun].attackdelay; + sendf(-1, 1, "rii9x", N_SHOTFX, ci->clientnum, gun, id, + int(from.x*DMF), int(from.y*DMF), int(from.z*DMF), + int(to.x*DMF), int(to.y*DMF), int(to.z*DMF), + ci->ownernum); + gs.shotdamage += guns[gun].damage*(gs.quadmillis ? 4 : 1)*guns[gun].rays; + switch(gun) + { + case GUN_RL: gs.rockets.add(id); break; + case GUN_GL: gs.grenades.add(id); break; + default: + { + int totalrays = 0, maxrays = guns[gun].rays; + loopv(hits) + { + hitinfo &h = hits[i]; + clientinfo *target = getinfo(h.target); + if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1 || h.dist > guns[gun].range + 1) continue; + + totalrays += h.rays; + if(totalrays>maxrays) continue; + int damage = h.rays*guns[gun].damage; + if(gs.quadmillis) damage *= 4; + dodamage(target, ci, damage, gun, h.dir); + } + break; + } + } + } + + void pickupevent::process(clientinfo *ci) + { + gamestate &gs = ci->state; + if(m_mp(gamemode) && !gs.isalive(gamemillis)) return; + pickup(ent, ci->clientnum); + } + + bool gameevent::flush(clientinfo *ci, int fmillis) + { + process(ci); + return true; + } + + bool timedevent::flush(clientinfo *ci, int fmillis) + { + if(millis > fmillis) return false; + else if(millis >= ci->lastevent) + { + ci->lastevent = millis; + process(ci); + } + return true; + } + + void clearevent(clientinfo *ci) + { + delete ci->events.remove(0); + } + + void flushevents(clientinfo *ci, int millis) + { + while(ci->events.length()) + { + gameevent *ev = ci->events[0]; + if(ev->flush(ci, millis)) clearevent(ci); + else break; + } + } + + void processevents() + { + loopv(clients) + { + clientinfo *ci = clients[i]; + if(curtime>0 && ci->state.quadmillis) ci->state.quadmillis = max(ci->state.quadmillis-curtime, 0); + flushevents(ci, gamemillis); + } + } + + void cleartimedevents(clientinfo *ci) + { + int keep = 0; + loopv(ci->events) + { + if(ci->events[i]->keepable()) + { + if(keep < i) + { + for(int j = keep; j < i; j++) delete ci->events[j]; + ci->events.remove(keep, i - keep); + i = keep; + } + keep = i+1; + continue; + } + } + while(ci->events.length() > keep) delete ci->events.pop(); + ci->timesync = false; + } + + void serverupdate() + { + if(shouldstep && !gamepaused) + { + gamemillis += curtime; + + if(m_demo) readdemo(); + else if(!m_timed || gamemillis < gamelimit) + { + processevents(); + if(curtime) + { + loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached + { + int oldtime = sents[i].spawntime; + sents[i].spawntime -= curtime; + if(sents[i].spawntime<=0) + { + sents[i].spawntime = 0; + sents[i].spawned = true; + sendf(-1, 1, "ri2", N_ITEMSPAWN, i); + } + else if(sents[i].spawntime<=10000 && oldtime>10000 && (sents[i].type==I_QUAD || sents[i].type==I_BOOST)) + { + sendf(-1, 1, "ri2", N_ANNOUNCE, sents[i].type); + } + } + } + aiman::checkai(); + } + } + + while(bannedips.length() && bannedips[0].expire-totalmillis <= 0) bannedips.remove(0); + //~loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT); + + if(nextexceeded && gamemillis > nextexceeded && (!m_timed || gamemillis < gamelimit)) + { + nextexceeded = 0; + loopvrev(clients) + { + clientinfo &c = *clients[i]; + if(c.state.aitype != AI_NONE) continue; + //~if(c.checkexceeded()) disconnect_client(c.clientnum, DISC_MSGERR); + else c.scheduleexceeded(); + } + } + + if(shouldcheckteamkills) checkteamkills(); + + if(shouldstep && !gamepaused) + { + if(m_timed && smapname[0] && gamemillis-curtime>0) checkintermission(); + if(interm > 0 && gamemillis>interm) + { + if(demorecord) enddemorecord(); + interm = -1; + checkvotes(true); + } + } + + shouldstep = clients.length() > 0; + } + + void forcespectator(clientinfo *ci) + { + if(ci->state.state==CS_ALIVE) suicide(ci); + ci->state.state = CS_SPECTATOR; + ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; + if(!ci->local && (!ci->privilege || ci->warned)) aiman::removeai(ci); + sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 1); + } + + struct crcinfo + { + int crc, matches; + + crcinfo() {} + crcinfo(int crc, int matches) : crc(crc), matches(matches) {} + + static bool compare(const crcinfo &x, const crcinfo &y) { return x.matches > y.matches; } + }; + + VAR(modifiedmapspectator, 0, 1, 2); + + void checkmaps(int req = -1) + { + if(m_edit || !smapname[0]) return; + vector crcs; + int total = 0, unsent = 0, invalid = 0; + if(mcrc) crcs.add(crcinfo(mcrc, clients.length() + 1)); + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE) continue; + total++; + if(!ci->clientmap[0]) + { + if(ci->mapcrc < 0) invalid++; + else if(!ci->mapcrc) unsent++; + } + else + { + crcinfo *match = NULL; + loopvj(crcs) if(crcs[j].crc == ci->mapcrc) { match = &crcs[j]; break; } + if(!match) crcs.add(crcinfo(ci->mapcrc, 1)); + else match->matches++; + } + } + if(!mcrc && total - unsent < min(total, 4)) return; + crcs.sort(crcinfo::compare); + string msg; + loopv(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || ci->clientmap[0] || ci->mapcrc >= 0 || (req < 0 && ci->warned)) continue; + formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname); + sendf(req, 1, "ris", N_SERVMSG, msg); + if(req < 0) ci->warned = true; + } + if(crcs.length() >= 2) loopv(crcs) + { + crcinfo &info = crcs[i]; + if(i || info.matches <= crcs[i+1].matches) loopvj(clients) + { + clientinfo *ci = clients[j]; + if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || !ci->clientmap[0] || ci->mapcrc != info.crc || (req < 0 && ci->warned)) continue; + formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname); + sendf(req, 1, "ris", N_SERVMSG, msg); + if(req < 0) ci->warned = true; + } + } + if(req < 0 && modifiedmapspectator && (mcrc || modifiedmapspectator > 1)) loopv(clients) + { + clientinfo *ci = clients[i]; + if(!ci->local && ci->warned && ci->state.state != CS_SPECTATOR) forcespectator(ci); + } + } + + bool shouldspectate(clientinfo *ci) + { + return !ci->local && ci->warned && modifiedmapspectator && (mcrc || modifiedmapspectator > 1); + } + + void unspectate(clientinfo *ci) + { + if(shouldspectate(ci)) return; + ci->state.state = CS_DEAD; + ci->state.respawn(); + ci->state.lasttimeplayed = lastmillis; + aiman::addclient(ci); + sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 0); + if(ci->clientmap[0] || ci->mapcrc) checkmaps(); + if(!hasmap(ci)) rotatemap(true); + } + + void sendservinfo(clientinfo *ci) + { + sendf(ci->clientnum, 1, "ri5ss", N_SERVINFO, ci->clientnum, PROTOCOL_VERSION, ci->sessionid, serverpass[0] ? 1 : 0, serverdesc, serverauth); + } + + void noclients() + { + bannedips.shrink(0); + aiman::clearai(); + } + + void localconnect(int n) + { + clientinfo *ci = getinfo(n); + ci->clientnum = ci->ownernum = n; + ci->connectmillis = totalmillis; + ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF; + ci->local = true; + + connects.add(ci); + sendservinfo(ci); + } + + void localdisconnect(int n) + { + if(m_demo) enddemoplayback(); + clientdisconnect(n); + } + + int clientconnect(int n) + { + clientinfo *ci = getinfo(n); + ci->clientnum = ci->ownernum = n; + ci->connectmillis = totalmillis; + ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF; + + connects.add(ci); + if(!m_mp(gamemode)) return DISC_LOCAL; + sendservinfo(ci); + return DISC_NONE; + } + + void clientdisconnect(int n) + { + clientinfo *ci = getinfo(n); + loopv(clients) if(clients[i]->authkickvictim == ci->clientnum) clients[i]->cleanauth(); + if(ci->connected) + { + if(ci->privilege) setmaster(ci, false); + ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; + savescore(ci); + sendf(-1, 1, "ri2", N_CDIS, n); + clients.removeobj(ci); + aiman::removeai(ci); + if(!numclients(-1, false, true)) noclients(); // bans clear when server empties + if(ci->local) checkpausegame(); + } + else connects.removeobj(ci); + } + + int reserveclients() { return 3; } + + extern void verifybans(); + + struct banlist + { + vector bans; + + void clear() { bans.shrink(0); } + + bool check(uint ip) + { + loopv(bans) if(bans[i].check(ip)) return true; + return false; + } + + void add(const char *ipname) + { + ipmask ban; + ban.parse(ipname); + bans.add(ban); + + verifybans(); + } + } ipbans, gbans; + + bool checkbans(uint ip) + { + loopv(bannedips) if(bannedips[i].ip==ip) return true; + return ipbans.check(ip) || gbans.check(ip); + } + + void verifybans() + { + loopvrev(clients) + { + clientinfo *ci = clients[i]; + if(ci->state.aitype != AI_NONE || ci->local || ci->privilege >= PRIV_ADMIN) continue; + //~if(checkbans(getclientip(ci->clientnum))) disconnect_client(ci->clientnum, DISC_IPBAN); + } + } + + ICOMMAND(clearipbans, "", (), ipbans.clear()); + ICOMMAND(ipban, "s", (const char *ipname), ipbans.add(ipname)); + + int allowconnect(clientinfo *ci, const char *pwd = "") + { + if(ci->local) return DISC_NONE; + if(!m_mp(gamemode)) return DISC_LOCAL; + if(serverpass[0]) + { + if(!checkpassword(ci, serverpass, pwd)) return DISC_PASSWORD; + return DISC_NONE; + } + if(adminpass[0] && checkpassword(ci, adminpass, pwd)) return DISC_NONE; + if(numclients(-1, false, true)>=maxclients) return DISC_MAXCLIENTS; + uint ip = getclientip(ci->clientnum); + if(checkbans(ip)) return DISC_IPBAN; + if(mastermode>=MM_PRIVATE && allowedips.find(ip)<0) return DISC_PRIVATE; + return DISC_NONE; + } + + bool allowbroadcast(int n) + { + clientinfo *ci = getinfo(n); + return ci && ci->connected; + } + + clientinfo *findauth(uint id) + { + loopv(clients) if(clients[i]->authreq == id) return clients[i]; + return NULL; + } + + + void authfailed(clientinfo *ci) + { + if(!ci) return; + ci->cleanauth(); + //~if(ci->connectauth) disconnect_client(ci->clientnum, ci->connectauth); + } + + void authfailed(uint id) + { + authfailed(findauth(id)); + } + + void authsucceeded(uint id) + { + clientinfo *ci = findauth(id); + if(!ci) return; + ci->cleanauth(ci->connectauth!=0); + if(ci->connectauth) connected(ci); + if(ci->authkickvictim >= 0) + { + if(setmaster(ci, true, "", ci->authname, NULL, PRIV_AUTH, false, true)) + trykick(ci, ci->authkickvictim, ci->authkickreason, ci->authname, NULL, PRIV_AUTH); + ci->cleanauthkick(); + } + else setmaster(ci, true, "", ci->authname, NULL, PRIV_AUTH); + } + + void authchallenged(uint id, const char *val, const char *desc = "") + { + clientinfo *ci = findauth(id); + if(!ci) return; + sendf(ci->clientnum, 1, "risis", N_AUTHCHAL, desc, id, val); + } + + uint nextauthreq = 0; + + bool tryauth(clientinfo *ci, const char *user, const char *desc) + { + ci->cleanauth(); + if(!nextauthreq) nextauthreq = 1; + ci->authreq = nextauthreq++; + filtertext(ci->authname, user, false, false, 100); + copystring(ci->authdesc, desc); + if(ci->authdesc[0]) + { + userinfo *u = users.access(userkey(ci->authname, ci->authdesc)); + if(u) + { + uint seed[3] = { ::hthash(serverauth) + detrnd(size_t(ci) + size_t(user) + size_t(desc), 0x10000), uint(totalmillis), randomMT() }; + vector buf; + ci->authchallenge = genchallenge(u->pubkey, seed, sizeof(seed), buf); + sendf(ci->clientnum, 1, "risis", N_AUTHCHAL, desc, ci->authreq, buf.getbuf()); + } + else ci->cleanauth(); + } + else if(!requestmasterf("reqauth %u %s\n", ci->authreq, ci->authname)) + { + ci->cleanauth(); + sendf(ci->clientnum, 1, "ris", N_SERVMSG, "not connected to authentication server"); + } + if(ci->authreq) return true; + //~if(ci->connectauth) disconnect_client(ci->clientnum, ci->connectauth); + return false; + } + + bool answerchallenge(clientinfo *ci, uint id, char *val, const char *desc) + { + if(ci->authreq != id || strcmp(ci->authdesc, desc)) + { + ci->cleanauth(); + return !ci->connectauth; + } + for(char *s = val; *s; s++) + { + if(!isxdigit(*s)) { *s = '\0'; break; } + } + if(desc[0]) + { + if(ci->authchallenge && checkchallenge(val, ci->authchallenge)) + { + userinfo *u = users.access(userkey(ci->authname, ci->authdesc)); + if(u) + { + if(ci->connectauth) connected(ci); + if(ci->authkickvictim >= 0) + { + if(setmaster(ci, true, "", ci->authname, ci->authdesc, u->privilege, false, true)) + trykick(ci, ci->authkickvictim, ci->authkickreason, ci->authname, ci->authdesc, u->privilege); + } + else setmaster(ci, true, "", ci->authname, ci->authdesc, u->privilege); + } + } + ci->cleanauth(); + } + else if(!requestmasterf("confauth %u %s\n", id, val)) + { + ci->cleanauth(); + sendf(ci->clientnum, 1, "ris", N_SERVMSG, "not connected to authentication server"); + } + return ci->authreq || !ci->connectauth; + } + + void masterconnected() + { + } + + void masterdisconnected() + { + loopvrev(clients) + { + clientinfo *ci = clients[i]; + if(ci->authreq) authfailed(ci); + } + } + + void processmasterinput(const char *cmd, int cmdlen, const char *args) + { + uint id; + string val; + if(sscanf(cmd, "failauth %u", &id) == 1) + authfailed(id); + else if(sscanf(cmd, "succauth %u", &id) == 1) + authsucceeded(id); + else if(sscanf(cmd, "chalauth %u %255s", &id, val) == 2) + authchallenged(id, val); + else if(matchstring(cmd, cmdlen, "cleargbans")) + gbans.clear(); + else if(sscanf(cmd, "addgban %100s", val) == 1) + gbans.add(val); + } + + void receivefile(int sender, uchar *data, int len) + { + if(!m_edit || len <= 0 || len > 4*1024*1024) return; + clientinfo *ci = getinfo(sender); + if(ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) return; + if(mapdata) DELETEP(mapdata); + mapdata = opentempfile("mapdata", "w+b"); + if(!mapdata) { sendf(sender, 1, "ris", N_SERVMSG, "failed to open temporary file for map"); return; } + mapdata->write(data, len); + sendservmsgf("[%s sent a map to server, \"/getmap\" to receive it]", colorname(ci)); + } + + void sendclipboard(clientinfo *ci) + { + if(!ci->lastclipboard || !ci->clipboard) return; + bool flushed = false; + loopv(clients) + { + clientinfo &e = *clients[i]; + if(e.clientnum != ci->clientnum && e.needclipboard - ci->lastclipboard >= 0) + { + if(!flushed) { flushserver(true); flushed = true; } + sendpacket(e.clientnum, 1, ci->clipboard); + } + } + } + + void connected(clientinfo *ci) + { + if(m_demo) enddemoplayback(); + + if(!hasmap(ci)) rotatemap(false); + + shouldstep = true; + + connects.removeobj(ci); + clients.add(ci); + + ci->connectauth = 0; + ci->connected = true; + ci->needclipboard = totalmillis ? totalmillis : 1; + if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR; + ci->state.lasttimeplayed = lastmillis; + + const char *worst = m_teammode ? chooseworstteam(NULL, ci) : NULL; + copystring(ci->team, worst ? worst : "good", MAXTEAMLEN+1); + + sendwelcome(ci); + if(restorescore(ci)) sendresume(ci); + sendinitclient(ci); + + aiman::addclient(ci); + + if(m_demo) setupdemoplayback(); + + if(servermotd[0]) sendf(ci->clientnum, 1, "ris", N_SERVMSG, servermotd); + } + + void parsepacket(int sender, int chan, packetbuf &p) // has to parse exactly each byte of the packet + { + if(sender<0 || p.packet->flags&ENET_PACKET_FLAG_UNSEQUENCED || chan > 2) return; + char text[MAXTRANS]; + int type; + clientinfo *ci = sender>=0 ? getinfo(sender) : NULL, *cq = ci, *cm = ci; + if(ci && !ci->connected) + { + if(chan==0) return; + //~else if(chan!=1) { disconnect_client(sender, DISC_MSGERR); return; } + else while(p.length() < p.maxlen) switch(checktype(getint(p), ci)) + { + case N_CONNECT: + { + getstring(text, p); + filtertext(text, text, false, false, MAXNAMELEN); + if(!text[0]) copystring(text, "Anonymous"); + copystring(ci->name, text, MAXNAMELEN+1); + ci->playermodel = 0; + + string password, authdesc, authname; + getstring(password, p, sizeof(password)); + getstring(authdesc, p, sizeof(authdesc)); + getstring(authname, p, sizeof(authname)); + int disc = allowconnect(ci, password); + if(disc) + { + if(disc == DISC_LOCAL || !serverauth[0] || strcmp(serverauth, authdesc) || !tryauth(ci, authname, authdesc)) + { + //~disconnect_client(sender, disc); + return; + } + ci->connectauth = disc; + } + else connected(ci); + break; + } + + case N_AUTHANS: + { + string desc, ans; + getstring(desc, p, sizeof(desc)); + uint id = (uint)getint(p); + getstring(ans, p, sizeof(ans)); + if(!answerchallenge(ci, id, ans, desc)) + { + //~disconnect_client(sender, ci->connectauth); + return; + } + break; + } + + case N_PING: + getint(p); + break; + + default: + //~disconnect_client(sender, DISC_MSGERR); + return; + } + return; + } + else if(chan==2) + { + receivefile(sender, p.buf, p.maxlen); + return; + } + + if(p.packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; + #define QUEUE_AI clientinfo *cm = cq; + #define QUEUE_MSG { if(cm && (!cm->local || demorecord || hasnonlocalclients())) while(curmsgmessages.add(p.buf[curmsg++]); } + #define QUEUE_BUF(body) { \ + if(cm && (!cm->local || demorecord || hasnonlocalclients())) \ + { \ + curmsg = p.length(); \ + { body; } \ + } \ + } + #define QUEUE_INT(n) QUEUE_BUF(putint(cm->messages, n)) + #define QUEUE_UINT(n) QUEUE_BUF(putuint(cm->messages, n)) + #define QUEUE_STR(text) QUEUE_BUF(sendstring(text, cm->messages)) + int curmsg; + while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci)) + { + case N_POS: + { + int pcn = getuint(p); + p.get(); + uint flags = getuint(p); + clientinfo *cp = getinfo(pcn); + if(cp && pcn != sender && cp->ownernum != sender) cp = NULL; + vec pos; + loopk(3) + { + int n = p.get(); n |= p.get()<<8; if(flags&(1<local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING)) + { + if(!ci->local && !m_edit && max(vel.magnitude2(), (float)fabs(vel.z)) >= 180) + cp->setexceeded(); + cp->position.setsize(0); + while(curmsgposition.add(p.buf[curmsg++]); + } + cp->state.o = pos; + cp->gameclip = (flags&0x80)!=0; + } + break; + } + + case N_TELEPORT: + { + int pcn = getint(p), teleport = getint(p), teledest = getint(p); + clientinfo *cp = getinfo(pcn); + if(cp && pcn != sender && cp->ownernum != sender) cp = NULL; + if(cp && (!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING)) + { + flushclientposition(*cp); + sendf(-1, 0, "ri4x", N_TELEPORT, pcn, teleport, teledest, cp->ownernum); + } + break; + } + + case N_JUMPPAD: + { + int pcn = getint(p), jumppad = getint(p); + clientinfo *cp = getinfo(pcn); + if(cp && pcn != sender && cp->ownernum != sender) cp = NULL; + if(cp && (!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING)) + { + cp->setpushed(); + flushclientposition(*cp); + sendf(-1, 0, "ri3x", N_JUMPPAD, pcn, jumppad, cp->ownernum); + } + break; + } + + case N_FROMAI: + { + int qcn = getint(p); + if(qcn < 0) cq = ci; + else + { + cq = getinfo(qcn); + if(cq && qcn != sender && cq->ownernum != sender) cq = NULL; + } + break; + } + + case N_EDITMODE: + { + int val = getint(p); + if(!ci->local && !m_edit) break; + if(val ? ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD : ci->state.state!=CS_EDITING) break; + if(val) + { + ci->state.editstate = ci->state.state; + ci->state.state = CS_EDITING; + ci->events.setsize(0); + ci->state.rockets.reset(); + ci->state.grenades.reset(); + } + else ci->state.state = ci->state.editstate; + QUEUE_MSG; + break; + } + + case N_MAPCRC: + { + getstring(text, p); + int crc = getint(p); + if(!ci) break; + if(strcmp(text, smapname)) + { + if(ci->clientmap[0]) + { + ci->clientmap[0] = '\0'; + ci->mapcrc = 0; + } + else if(ci->mapcrc > 0) ci->mapcrc = 0; + break; + } + copystring(ci->clientmap, text); + ci->mapcrc = text[0] ? crc : 1; + checkmaps(); + if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL; + break; + } + + case N_CHECKMAPS: + checkmaps(sender); + break; + + case N_TRYSPAWN: + if(!ci || !cq || cq->state.state!=CS_DEAD || cq->state.lastspawn>=0) break; + if(!ci->clientmap[0] && !ci->mapcrc) + { + ci->mapcrc = -1; + checkmaps(); + if(ci == cq) { if(ci->state.state != CS_DEAD) break; } + else if(cq->ownernum != ci->clientnum) { cq = NULL; break; } + } + if(cq->state.deadflush) + { + flushevents(cq, cq->state.deadflush); + cq->state.respawn(); + } + cleartimedevents(cq); + sendspawn(cq); + break; + + case N_GUNSELECT: + { + int gunselect = getint(p); + if(!cq || cq->state.state!=CS_ALIVE) break; + cq->state.gunselect = gunselect >= GUN_FIST && gunselect <= GUN_PISTOL ? gunselect : GUN_FIST; + QUEUE_AI; + QUEUE_MSG; + break; + } + + case N_SPAWN: + { + int ls = getint(p), gunselect = getint(p); + if(!cq || (cq->state.state!=CS_ALIVE && cq->state.state!=CS_DEAD && cq->state.state!=CS_EDITING) || ls!=cq->state.lifesequence || cq->state.lastspawn<0) break; + cq->state.lastspawn = -1; + cq->state.state = CS_ALIVE; + cq->state.gunselect = gunselect >= GUN_FIST && gunselect <= GUN_PISTOL ? gunselect : GUN_FIST; + cq->exceeded = 0; + QUEUE_AI; + QUEUE_BUF({ + putint(cm->messages, N_SPAWN); + sendstate(cq->state, cm->messages); + }); + break; + } + + case N_SUICIDE: + { + if(cq) cq->addevent(new suicideevent); + break; + } + + case N_SHOOT: + { + shotevent *shot = new shotevent; + shot->id = getint(p); + shot->millis = cq ? cq->geteventmillis(gamemillis, shot->id) : 0; + shot->gun = getint(p); + loopk(3) shot->from[k] = getint(p)/DMF; + loopk(3) shot->to[k] = getint(p)/DMF; + int hits = getint(p); + loopk(hits) + { + if(p.overread()) break; + hitinfo &hit = shot->hits.add(); + hit.target = getint(p); + hit.lifesequence = getint(p); + hit.dist = getint(p)/DMF; + hit.rays = getint(p); + loopk(3) hit.dir[k] = getint(p)/DNF; + } + if(cq) + { + cq->addevent(shot); + cq->setpushed(); + } + else delete shot; + break; + } + + case N_EXPLODE: + { + explodeevent *exp = new explodeevent; + int cmillis = getint(p); + exp->millis = cq ? cq->geteventmillis(gamemillis, cmillis) : 0; + exp->gun = getint(p); + exp->id = getint(p); + int hits = getint(p); + loopk(hits) + { + if(p.overread()) break; + hitinfo &hit = exp->hits.add(); + hit.target = getint(p); + hit.lifesequence = getint(p); + hit.dist = getint(p)/DMF; + hit.rays = getint(p); + loopk(3) hit.dir[k] = getint(p)/DNF; + } + if(cq) cq->addevent(exp); + else delete exp; + break; + } + + case N_ITEMPICKUP: + { + int n = getint(p); + if(!cq) break; + pickupevent *pickup = new pickupevent; + pickup->ent = n; + cq->addevent(pickup); + break; + } + + case N_TEXT: + { + QUEUE_AI; + QUEUE_MSG; + getstring(text, p); + filtertext(text, text, true, true); + QUEUE_STR(text); + if(isdedicatedserver() && cq) logoutf("%s: %s", colorname(cq), text); + break; + } + + case N_SAYTEAM: + { + getstring(text, p); + if(!ci || !cq || (ci->state.state==CS_SPECTATOR && !ci->local && !ci->privilege) || !m_teammode || !cq->team[0]) break; + filtertext(text, text, true, true); + loopv(clients) + { + clientinfo *t = clients[i]; + if(t==cq || t->state.state==CS_SPECTATOR || t->state.aitype != AI_NONE || strcmp(cq->team, t->team)) continue; + sendf(t->clientnum, 1, "riis", N_SAYTEAM, cq->clientnum, text); + } + if(isdedicatedserver() && cq) logoutf("%s <%s>: %s", colorname(cq), cq->team, text); + break; + } + + case N_SWITCHNAME: + { + QUEUE_MSG; + getstring(text, p); + filtertext(ci->name, text, false, false, MAXNAMELEN); + if(!ci->name[0]) copystring(ci->name, "Anonymous"); + QUEUE_STR(ci->name); + break; + } + + case N_SWITCHMODEL: + { + ci->playermodel = 0; + QUEUE_MSG; + break; + } + + case N_SWITCHTEAM: + { + getstring(text, p); + filtertext(text, text, false, false, MAXTEAMLEN); + if(m_teammode && text[0] && strcmp(ci->team, text) && addteaminfo(text)) + { + if(ci->state.state==CS_ALIVE) suicide(ci); + copystring(ci->team, text); + aiman::changeteam(ci); + sendf(-1, 1, "riisi", N_SETTEAM, sender, ci->team, ci->state.state==CS_SPECTATOR ? -1 : 0); + } + break; + } + + case N_MAPVOTE: + { + getstring(text, p); + filtertext(text, text, false); + fixmapname(text); + int reqmode = getint(p); + vote(text, reqmode, sender); + break; + } + + case N_ITEMLIST: + { + if((ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || !notgotitems || strcmp(ci->clientmap, smapname)) { while(getint(p)>=0 && !p.overread()) getint(p); break; } + int n; + while((n = getint(p))>=0 && nstate.state==CS_SPECTATOR) break; + QUEUE_MSG; + bool canspawn = canspawnitem(type); + if(istate.state!=CS_SPECTATOR) QUEUE_MSG; + break; + } + + case N_PING: + sendf(sender, 1, "i2", N_PONG, getint(p)); + break; + + case N_CLIENTPING: + { + int ping = getint(p); + if(ci) + { + ci->ping = ping; + loopv(ci->bots) ci->bots[i]->ping = ping; + } + QUEUE_MSG; + break; + } + + case N_MASTERMODE: + { + int mm = getint(p); + if((ci->privilege || ci->local) && mm>=MM_OPEN && mm<=MM_PRIVATE) + { + if((ci->privilege>=PRIV_ADMIN || ci->local) || (mastermask&(1<=MM_PRIVATE) + { + loopv(clients) allowedips.add(getclientip(clients[i]->clientnum)); + } + sendf(-1, 1, "rii", N_MASTERMODE, mastermode); + //sendservmsgf("mastermode is now %s (%d)", mastermodename(mastermode), mastermode); + } + else + { + defformatstring(s, "mastermode %d is disabled on this server", mm); + sendf(sender, 1, "ris", N_SERVMSG, s); + } + } + break; + } + + case N_CLEARBANS: + { + if(ci->privilege || ci->local) + { + bannedips.shrink(0); + sendservmsg("cleared all bans"); + } + break; + } + + case N_KICK: + { + int victim = getint(p); + getstring(text, p); + filtertext(text, text); + trykick(ci, victim, text); + break; + } + + case N_SPECTATOR: + { + int spectator = getint(p), val = getint(p); + if(!ci->privilege && !ci->local && (spectator!=sender || (ci->state.state==CS_SPECTATOR && mastermode>=MM_LOCKED))) break; + clientinfo *spinfo = (clientinfo *)getclientinfo(spectator); // no bots + if(!spinfo || !spinfo->connected || (spinfo->state.state==CS_SPECTATOR ? val : !val)) break; + + if(spinfo->state.state!=CS_SPECTATOR && val) forcespectator(spinfo); + else if(spinfo->state.state==CS_SPECTATOR && !val) unspectate(spinfo); + + if(cq && cq != ci && cq->ownernum != ci->clientnum) cq = NULL; + break; + } + + case N_SETTEAM: + { + int who = getint(p); + getstring(text, p); + filtertext(text, text, false, false, MAXTEAMLEN); + if(!ci->privilege && !ci->local) break; + clientinfo *wi = getinfo(who); + if(!m_teammode || !text[0] || !wi || !wi->connected || !strcmp(wi->team, text)) break; + if(addteaminfo(text)) + { + if(wi->state.state==CS_ALIVE) suicide(wi); + copystring(wi->team, text, MAXTEAMLEN+1); + } + aiman::changeteam(wi); + sendf(-1, 1, "riisi", N_SETTEAM, who, wi->team, 1); + break; + } + + case N_FORCEINTERMISSION: + if(ci->local && !hasnonlocalclients()) startintermission(); + break; + + case N_RECORDDEMO: + { + int val = getint(p); + if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; + if(!maxdemos || !maxdemosize) + { + sendf(ci->clientnum, 1, "ris", N_SERVMSG, "the server has disabled demo recording"); + break; + } + demonextmatch = val!=0; + sendservmsgf("demo recording is %s for next match", demonextmatch ? "enabled" : "disabled"); + break; + } + + case N_STOPDEMO: + { + if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; + stopdemo(); + break; + } + + case N_CLEARDEMOS: + { + int demo = getint(p); + if(ci->privilege < (restrictdemos ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; + cleardemos(demo); + break; + } + + case N_LISTDEMOS: + if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break; + listdemos(sender); + break; + + case N_GETDEMO: + { + int n = getint(p), tag = getint(p); + if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break; + senddemo(ci, n, tag); + break; + } + + case N_GETMAP: + if(!mapdata) sendf(sender, 1, "ris", N_SERVMSG, "no map to send"); + else if(ci->getmap) sendf(sender, 1, "ris", N_SERVMSG, "already sending map"); + else + { + sendservmsgf("[%s is getting the map]", colorname(ci)); + if((ci->getmap = sendfile(sender, 2, mapdata, "ri", N_SENDMAP))) + ci->getmap->freeCallback = freegetmap; + ci->needclipboard = totalmillis ? totalmillis : 1; + } + break; + + case N_NEWMAP: + { + int size = getint(p); + if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break; + if(size>=0) + { + smapname[0] = '\0'; + resetitems(); + notgotitems = false; + } + QUEUE_MSG; + break; + } + + case N_SETMASTER: + { + int mn = getint(p), val = getint(p); + getstring(text, p); + if(mn != ci->clientnum) + { + if(!ci->privilege && !ci->local) break; + clientinfo *minfo = (clientinfo *)getclientinfo(mn); + if(!minfo || !minfo->connected || (!ci->local && minfo->privilege >= ci->privilege) || (val && minfo->privilege)) break; + setmaster(minfo, val!=0, "", NULL, NULL, PRIV_MASTER, true); + } + else setmaster(ci, val!=0, text); + // don't broadcast the master password + break; + } + + case N_ADDBOT: + { + aiman::reqadd(ci, getint(p)); + break; + } + + case N_DELBOT: + { + aiman::reqdel(ci); + break; + } + + case N_BOTLIMIT: + { + int limit = getint(p); + if(ci) aiman::setbotlimit(ci, limit); + break; + } + + case N_BOTBALANCE: + { + int balance = getint(p); + if(ci) aiman::setbotbalance(ci, balance!=0); + break; + } + + case N_AUTHTRY: + { + string desc, name; + getstring(desc, p, sizeof(desc)); + getstring(name, p, sizeof(name)); + tryauth(ci, name, desc); + break; + } + + case N_AUTHKICK: + { + string desc, name; + getstring(desc, p, sizeof(desc)); + getstring(name, p, sizeof(name)); + int victim = getint(p); + getstring(text, p); + filtertext(text, text); + int authpriv = PRIV_AUTH; + if(desc[0]) + { + userinfo *u = users.access(userkey(name, desc)); + if(u) authpriv = u->privilege; else break; + } + if(ci->local || ci->privilege >= authpriv) trykick(ci, victim, text); + else if(trykick(ci, victim, text, name, desc, authpriv, true) && tryauth(ci, name, desc)) + { + ci->authkickvictim = victim; + ci->authkickreason = newstring(text); + } + break; + } + + case N_AUTHANS: + { + string desc, ans; + getstring(desc, p, sizeof(desc)); + uint id = (uint)getint(p); + getstring(ans, p, sizeof(ans)); + answerchallenge(ci, id, ans, desc); + break; + } + + case N_PAUSEGAME: + { + int val = getint(p); + if(ci->privilege < (restrictpausegame ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; + pausegame(val > 0, ci); + break; + } + + case N_GAMESPEED: + { + int val = getint(p); + if(ci->privilege < (restrictgamespeed ? PRIV_ADMIN : PRIV_MASTER) && !ci->local) break; + changegamespeed(val, ci); + break; + } + + case N_COPY: + ci->cleanclipboard(); + ci->lastclipboard = totalmillis ? totalmillis : 1; + goto genericmsg; + + case N_PASTE: + if(ci->state.state!=CS_SPECTATOR) sendclipboard(ci); + goto genericmsg; + + case N_CLIPBOARD: + { + int unpacklen = getint(p), packlen = getint(p); + ci->cleanclipboard(false); + if(ci->state.state==CS_SPECTATOR) + { + if(packlen > 0) p.subbuf(packlen); + break; + } + if(packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) + { + if(packlen > 0) p.subbuf(packlen); + packlen = unpacklen = 0; + } + packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE); + putint(q, N_CLIPBOARD); + putint(q, ci->clientnum); + putint(q, unpacklen); + putint(q, packlen); + if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); + ci->clipboard = q.finalize(); + ci->clipboard->referenceCount++; + break; + } + + case N_EDITT: + case N_REPLACE: + case N_EDITVSLOT: + { + int size = server::msgsizelookup(type); + //~if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; } + loopi(size-1) getint(p); + //~if(p.remaining() < 2) { disconnect_client(sender, DISC_MSGERR); return; } + int extra = lilswap(*(const ushort *)p.pad(2)); + //~if(p.remaining() < extra) { disconnect_client(sender, DISC_MSGERR); return; } + p.pad(extra); + if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG; + break; + } + + case N_UNDO: + case N_REDO: + { + int unpacklen = getint(p), packlen = getint(p); + if(!ci || ci->state.state==CS_SPECTATOR || packlen <= 0 || packlen > (1<<16) || unpacklen <= 0) + { + if(packlen > 0) p.subbuf(packlen); + break; + } + //~if(p.remaining() < packlen) { disconnect_client(sender, DISC_MSGERR); return; } + packetbuf q(32 + packlen, ENET_PACKET_FLAG_RELIABLE); + putint(q, type); + putint(q, ci->clientnum); + putint(q, unpacklen); + putint(q, packlen); + if(packlen > 0) p.get(q.subbuf(packlen).buf, packlen); + sendpacket(-1, 1, q.finalize(), ci->clientnum); + break; + } + + case N_SERVCMD: + getstring(text, p); + break; + + + case -1: + //~disconnect_client(sender, DISC_MSGERR); + return; + + case -2: + //~disconnect_client(sender, DISC_OVERFLOW); + return; + + default: genericmsg: + { + int size = server::msgsizelookup(type); + //~if(size<=0) { disconnect_client(sender, DISC_MSGERR); return; } + loopi(size-1) getint(p); + if(ci) switch(msgfilter[type]) + { + case 2: case 3: if(ci->state.state != CS_SPECTATOR) QUEUE_MSG; break; + default: if(cq && (ci != cq || ci->state.state!=CS_SPECTATOR)) { QUEUE_AI; QUEUE_MSG; } break; + } + break; + } + } + } + + int laninfoport() { return SAUERBRATEN_LANINFO_PORT; } + int serverinfoport(int servport) { return servport < 0 ? SAUERBRATEN_SERVINFO_PORT : servport+1; } + int serverport(int infoport) { return infoport < 0 ? SAUERBRATEN_SERVER_PORT : infoport-1; } + const char *defaultmaster() { return "master.sauerbraten.org"; } + int masterport() { return SAUERBRATEN_MASTER_PORT; } + int numchannels() { return 3; } + + #include "extinfo.h" + + void serverinforeply(ucharbuf &req, ucharbuf &p) + { + if(req.remaining() && !getint(req)) + { + extserverinforeply(req, p); + return; + } + + putint(p, numclients(-1, false, true)); + putint(p, gamepaused || gamespeed != 100 ? 7 : 5); // number of attrs following + putint(p, PROTOCOL_VERSION); // generic attributes, passed back below + putint(p, gamemode); + putint(p, m_timed ? max((gamelimit - gamemillis)/1000, 0) : 0); + putint(p, maxclients); + putint(p, serverpass[0] ? MM_PASSWORD : (!m_mp(gamemode) ? MM_PRIVATE : (mastermode || mastermask&MM_AUTOAPPROVE ? mastermode : MM_AUTH))); + if(gamepaused || gamespeed != 100) + { + putint(p, gamepaused ? 1 : 0); + putint(p, gamespeed); + } + sendstring(smapname, p); + sendstring(serverdesc, p); + sendserverinforeply(p); + } + + #include "aiman.h" } diff --git a/src/fpsgame/waypoint.cpp b/src/fpsgame/waypoint.cpp index b59b388..c6025c5 100644 --- a/src/fpsgame/waypoint.cpp +++ b/src/fpsgame/waypoint.cpp @@ -4,443 +4,443 @@ extern selinfo sel; namespace ai { - using namespace game; - - vector waypoints; - - bool clipped(const vec &o) - { - int material = lookupmaterial(o), clipmat = material&MATF_CLIP; - return clipmat == MAT_CLIP || material&MAT_DEATH || (material&MATF_VOLUME) == MAT_LAVA; - } - - int getweight(const vec &o) - { - vec pos = o; pos.z += ai::JUMPMIN; - if(!insideworld(vec(pos.x, pos.y, min(pos.z, getworldsize() - 1e-3f)))) return -2; - float dist = raycube(pos, vec(0, 0, -1), 0, RAY_CLIPMAT); - int posmat = lookupmaterial(pos), weight = 1; - if(isliquid(posmat&MATF_VOLUME)) weight *= 5; - if(dist >= 0) - { - weight = int(dist/ai::JUMPMIN); - pos.z -= clamp(dist-8.0f, 0.0f, pos.z); - int trgmat = lookupmaterial(pos); - if(trgmat&MAT_DEATH || (trgmat&MATF_VOLUME) == MAT_LAVA) weight *= 10; - else if(isliquid(trgmat&MATF_VOLUME)) weight *= 2; - } - return weight; - } - - enum - { - WPCACHE_STATIC = 0, - WPCACHE_DYNAMIC, - NUMWPCACHES - }; - - struct wpcache - { - struct node - { - float split[2]; - uint child[2]; - - int axis() const { return child[0]>>30; } - int childindex(int which) const { return child[which]&0x3FFFFFFF; } - bool isleaf(int which) const { return (child[1]&(1<<(30+which)))!=0; } - }; - - vector nodes; - int firstwp, lastwp; - vec bbmin, bbmax; - - wpcache() { clear(); } - - void clear() - { - nodes.setsize(0); - firstwp = lastwp = -1; - bbmin = vec(1e16f, 1e16f, 1e16f); - bbmax = vec(-1e16f, -1e16f, -1e16f); - } - - void build(int first = 0, int last = -1) - { - if(last < 0) last = waypoints.length(); - vector indices; - for(int i = first; i < last; i++) - { - waypoint &w = waypoints[i]; - indices.add(i); - if(firstwp < 0) firstwp = i; - float radius = WAYPOINTRADIUS; - bbmin.min(vec(w.o).sub(radius)); - bbmax.max(vec(w.o).add(radius)); - } - if(first < last) lastwp = max(lastwp, last-1); - if(indices.length()) - { - nodes.reserve(indices.length()); - build(indices.getbuf(), indices.length(), bbmin, bbmax); - } - } - - void build(int *indices, int numindices, const vec &vmin, const vec &vmax) - { - int axis = 2; - loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k; - - vec leftmin(1e16f, 1e16f, 1e16f), leftmax(-1e16f, -1e16f, -1e16f), rightmin(1e16f, 1e16f, 1e16f), rightmax(-1e16f, -1e16f, -1e16f); - float split = 0.5f*(vmax[axis] + vmin[axis]), splitleft = -1e16f, splitright = 1e16f; - int left, right; - for(left = 0, right = numindices; left < right;) - { - waypoint &w = waypoints[indices[left]]; - float radius = WAYPOINTRADIUS; - if(max(split - (w.o[axis]-radius), 0.0f) > max((w.o[axis]+radius) - split, 0.0f)) - { - ++left; - splitleft = max(splitleft, w.o[axis]+radius); - leftmin.min(vec(w.o).sub(radius)); - leftmax.max(vec(w.o).add(radius)); - } - else - { - --right; - swap(indices[left], indices[right]); - splitright = min(splitright, w.o[axis]-radius); - rightmin.min(vec(w.o).sub(radius)); - rightmax.max(vec(w.o).add(radius)); - } - } - - if(!left || right==numindices) - { - leftmin = rightmin = vec(1e16f, 1e16f, 1e16f); - leftmax = rightmax = vec(-1e16f, -1e16f, -1e16f); - left = right = numindices/2; - splitleft = -1e16f; - splitright = 1e16f; - loopi(numindices) - { - waypoint &w = waypoints[indices[i]]; - float radius = WAYPOINTRADIUS; - if(i < left) - { - splitleft = max(splitleft, w.o[axis]+radius); - leftmin.min(vec(w.o).sub(radius)); - leftmax.max(vec(w.o).add(radius)); - } - else - { - splitright = min(splitright, w.o[axis]-radius); - rightmin.min(vec(w.o).sub(radius)); - rightmax.max(vec(w.o).add(radius)); - } - } - } - - int offset = nodes.length(); - node &curnode = nodes.add(); - curnode.split[0] = splitleft; - curnode.split[1] = splitright; - - if(left<=1) curnode.child[0] = (axis<<30) | (left>0 ? indices[0] : 0x3FFFFFFF); - else - { - curnode.child[0] = (axis<<30) | (nodes.length()-offset); - if(left) build(indices, left, leftmin, leftmax); - } - - if(numindices-right<=1) curnode.child[1] = (1<<31) | (left<=1 ? 1<<30 : 0) | (numindices-right>0 ? indices[right] : 0x3FFFFFFF); - else - { - curnode.child[1] = (left<=1 ? 1<<30 : 0) | (nodes.length()-offset); - if(numindices-right) build(&indices[right], numindices-right, rightmin, rightmax); - } - } - } wpcaches[NUMWPCACHES]; - - static int invalidatedwpcaches = 0, clearedwpcaches = (1<= 1000) { numinvalidatewpcaches = 0; invalidatedwpcaches = (1<= wpcaches[i].firstwp && wp <= wpcaches[i].lastwp) { invalidatedwpcaches |= 1< 0 ? wpcaches[i-1].lastwp+1 : 1, i+1 >= NUMWPCACHES || wpcaches[i+1].firstwp < 0 ? -1 : wpcaches[i+1].firstwp); - clearedwpcaches = 0; - lastwpcache = waypoints.length(); - - wpavoid.clear(); + using namespace game; + + vector waypoints; + + bool clipped(const vec &o) + { + int material = lookupmaterial(o), clipmat = material&MATF_CLIP; + return clipmat == MAT_CLIP || material&MAT_DEATH || (material&MATF_VOLUME) == MAT_LAVA; + } + + int getweight(const vec &o) + { + vec pos = o; pos.z += ai::JUMPMIN; + if(!insideworld(vec(pos.x, pos.y, min(pos.z, getworldsize() - 1e-3f)))) return -2; + float dist = raycube(pos, vec(0, 0, -1), 0, RAY_CLIPMAT); + int posmat = lookupmaterial(pos), weight = 1; + if(isliquid(posmat&MATF_VOLUME)) weight *= 5; + if(dist >= 0) + { + weight = int(dist/ai::JUMPMIN); + pos.z -= clamp(dist-8.0f, 0.0f, pos.z); + int trgmat = lookupmaterial(pos); + if(trgmat&MAT_DEATH || (trgmat&MATF_VOLUME) == MAT_LAVA) weight *= 10; + else if(isliquid(trgmat&MATF_VOLUME)) weight *= 2; + } + return weight; + } + + enum + { + WPCACHE_STATIC = 0, + WPCACHE_DYNAMIC, + NUMWPCACHES + }; + + struct wpcache + { + struct node + { + float split[2]; + uint child[2]; + + int axis() const { return child[0]>>30; } + int childindex(int which) const { return child[which]&0x3FFFFFFF; } + bool isleaf(int which) const { return (child[1]&(1<<(30+which)))!=0; } + }; + + vector nodes; + int firstwp, lastwp; + vec bbmin, bbmax; + + wpcache() { clear(); } + + void clear() + { + nodes.setsize(0); + firstwp = lastwp = -1; + bbmin = vec(1e16f, 1e16f, 1e16f); + bbmax = vec(-1e16f, -1e16f, -1e16f); + } + + void build(int first = 0, int last = -1) + { + if(last < 0) last = waypoints.length(); + vector indices; + for(int i = first; i < last; i++) + { + waypoint &w = waypoints[i]; + indices.add(i); + if(firstwp < 0) firstwp = i; + float radius = WAYPOINTRADIUS; + bbmin.min(vec(w.o).sub(radius)); + bbmax.max(vec(w.o).add(radius)); + } + if(first < last) lastwp = max(lastwp, last-1); + if(indices.length()) + { + nodes.reserve(indices.length()); + build(indices.getbuf(), indices.length(), bbmin, bbmax); + } + } + + void build(int *indices, int numindices, const vec &vmin, const vec &vmax) + { + int axis = 2; + loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k; + + vec leftmin(1e16f, 1e16f, 1e16f), leftmax(-1e16f, -1e16f, -1e16f), rightmin(1e16f, 1e16f, 1e16f), rightmax(-1e16f, -1e16f, -1e16f); + float split = 0.5f*(vmax[axis] + vmin[axis]), splitleft = -1e16f, splitright = 1e16f; + int left, right; + for(left = 0, right = numindices; left < right;) + { + waypoint &w = waypoints[indices[left]]; + float radius = WAYPOINTRADIUS; + if(max(split - (w.o[axis]-radius), 0.0f) > max((w.o[axis]+radius) - split, 0.0f)) + { + ++left; + splitleft = max(splitleft, w.o[axis]+radius); + leftmin.min(vec(w.o).sub(radius)); + leftmax.max(vec(w.o).add(radius)); + } + else + { + --right; + swap(indices[left], indices[right]); + splitright = min(splitright, w.o[axis]-radius); + rightmin.min(vec(w.o).sub(radius)); + rightmax.max(vec(w.o).add(radius)); + } + } + + if(!left || right==numindices) + { + leftmin = rightmin = vec(1e16f, 1e16f, 1e16f); + leftmax = rightmax = vec(-1e16f, -1e16f, -1e16f); + left = right = numindices/2; + splitleft = -1e16f; + splitright = 1e16f; + loopi(numindices) + { + waypoint &w = waypoints[indices[i]]; + float radius = WAYPOINTRADIUS; + if(i < left) + { + splitleft = max(splitleft, w.o[axis]+radius); + leftmin.min(vec(w.o).sub(radius)); + leftmax.max(vec(w.o).add(radius)); + } + else + { + splitright = min(splitright, w.o[axis]-radius); + rightmin.min(vec(w.o).sub(radius)); + rightmax.max(vec(w.o).add(radius)); + } + } + } + + int offset = nodes.length(); + node &curnode = nodes.add(); + curnode.split[0] = splitleft; + curnode.split[1] = splitright; + + if(left<=1) curnode.child[0] = (axis<<30) | (left>0 ? indices[0] : 0x3FFFFFFF); + else + { + curnode.child[0] = (axis<<30) | (nodes.length()-offset); + if(left) build(indices, left, leftmin, leftmax); + } + + if(numindices-right<=1) curnode.child[1] = (1<<31) | (left<=1 ? 1<<30 : 0) | (numindices-right>0 ? indices[right] : 0x3FFFFFFF); + else + { + curnode.child[1] = (left<=1 ? 1<<30 : 0) | (nodes.length()-offset); + if(numindices-right) build(&indices[right], numindices-right, rightmin, rightmax); + } + } + } wpcaches[NUMWPCACHES]; + + static int invalidatedwpcaches = 0, clearedwpcaches = (1<= 1000) { numinvalidatewpcaches = 0; invalidatedwpcaches = (1<= wpcaches[i].firstwp && wp <= wpcaches[i].lastwp) { invalidatedwpcaches |= 1< 0 ? wpcaches[i-1].lastwp+1 : 1, i+1 >= NUMWPCACHES || wpcaches[i+1].firstwp < 0 ? -1 : wpcaches[i+1].firstwp); + clearedwpcaches = 0; + lastwpcache = waypoints.length(); + + wpavoid.clear(); loopv(waypoints) if(waypoints[i].weight < 0) wpavoid.avoidnear(NULL, waypoints[i].o.z + WAYPOINTRADIUS, waypoints[i].o, WAYPOINTRADIUS); - } - - struct wpcachestack - { - wpcache::node *node; - float tmin, tmax; - }; - - vector wpcachestack; - - int closestwaypoint(const vec &pos, float mindist, bool links, fpsent *d) - { - if(waypoints.empty()) return -1; - if(clearedwpcaches) buildwpcache(); - - #define CHECKCLOSEST(index) do { \ - int n = (index); \ - if(n < waypoints.length()) \ - { \ - const waypoint &w = waypoints[n]; \ - if(!links || w.links[0]) \ - { \ - float dist = w.o.squaredist(pos); \ - if(dist < mindist*mindist) { closest = n; mindist = sqrtf(dist); } \ - } \ - } \ - } while(0) - int closest = -1; - wpcache::node *curnode; - loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;) - { - int axis = curnode->axis(); - float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis]; - if(dist1 >= mindist) - { - if(dist2 < mindist) - { - if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } - CHECKCLOSEST(curnode->childindex(1)); - } - } - else if(curnode->isleaf(0)) - { - CHECKCLOSEST(curnode->childindex(0)); - if(dist2 < mindist) - { - if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } - CHECKCLOSEST(curnode->childindex(1)); - } - } - else - { - if(dist2 < mindist) - { - if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1)); - else CHECKCLOSEST(curnode->childindex(1)); - } - curnode += curnode->childindex(0); - continue; - } - if(wpcachestack.empty()) break; - curnode = wpcachestack.pop(); - } - for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKCLOSEST(i); } - return closest; - } - - void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector &results) - { - if(waypoints.empty()) return; - if(clearedwpcaches) buildwpcache(); - - float mindist2 = mindist*mindist, maxdist2 = maxdist*maxdist; - #define CHECKWITHIN(index) do { \ - int n = (index); \ - if(n < waypoints.length()) \ - { \ - const waypoint &w = waypoints[n]; \ - float dist = w.o.squaredist(pos); \ - if(dist > mindist2 && dist < maxdist2) results.add(n); \ - } \ - } while(0) - wpcache::node *curnode; - loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;) - { - int axis = curnode->axis(); - float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis]; - if(dist1 >= maxdist) - { - if(dist2 < maxdist) - { - if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } - CHECKWITHIN(curnode->childindex(1)); - } - } - else if(curnode->isleaf(0)) - { - CHECKWITHIN(curnode->childindex(0)); - if(dist2 < maxdist) - { - if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } - CHECKWITHIN(curnode->childindex(1)); - } - } - else - { - if(dist2 < maxdist) - { - if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1)); - else CHECKWITHIN(curnode->childindex(1)); - } - curnode += curnode->childindex(0); - continue; - } - if(wpcachestack.empty()) break; - curnode = wpcachestack.pop(); - } - for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKWITHIN(i); } - } - - void avoidset::avoidnear(void *owner, float above, const vec &pos, float limit) - { - if(ai::waypoints.empty()) return; - if(clearedwpcaches) buildwpcache(); - - float limit2 = limit*limit; - #define CHECKNEAR(index) do { \ - int n = (index); \ - if(n < ai::waypoints.length()) \ - { \ - const waypoint &w = ai::waypoints[n]; \ - if(w.o.squaredist(pos) < limit2) add(owner, above, n); \ - } \ - } while(0) - wpcache::node *curnode; - loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;) - { - int axis = curnode->axis(); - float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis]; - if(dist1 >= limit) - { - if(dist2 < limit) - { - if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } - CHECKNEAR(curnode->childindex(1)); - } - } - else if(curnode->isleaf(0)) - { - CHECKNEAR(curnode->childindex(0)); - if(dist2 < limit) - { - if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } - CHECKNEAR(curnode->childindex(1)); - } - } - else - { - if(dist2 < limit) - { - if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1)); - else CHECKNEAR(curnode->childindex(1)); - } - curnode += curnode->childindex(0); - continue; - } - if(wpcachestack.empty()) break; - curnode = wpcachestack.pop(); - } - for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKNEAR(i); } - } - - int avoidset::remap(fpsent *d, int n, vec &pos, bool retry) - { - if(!obstacles.empty()) - { - int cur = 0; - loopv(obstacles) - { - obstacle &ob = obstacles[i]; - int next = cur + ob.numwaypoints; - if(ob.owner != d) - { - for(; cur < next; cur++) if(waypoints[cur] == n) - { - if(ob.above < 0) return retry ? n : -1; - vec above(pos.x, pos.y, ob.above); - if(above.z-d->o.z >= ai::JUMPMAX) - return retry ? n : -1; // too much scotty - int node = closestwaypoint(above, ai::SIGHTMIN, true, d); - if(ai::iswaypoint(node) && node != n) - { // try to reroute above their head? - if(!find(node, d)) - { - pos = ai::waypoints[node].o; - return node; - } - else return retry ? n : -1; - } - else - { - vec old = d->o; - d->o = vec(above).add(vec(0, 0, d->eyeheight)); - bool col = collide(d, vec(0, 0, 1)); - d->o = old; - if(!col) - { - pos = above; - return n; - } - else return retry ? n : -1; - } - } - } - cur = next; - } - } - return n; - } - - static inline float heapscore(waypoint *q) { return q->score(); } - - bool route(fpsent *d, int node, int goal, vector &route, const avoidset &obstacles, int retries) - { - if(waypoints.empty() || !iswaypoint(node) || !iswaypoint(goal) || goal == node || !waypoints[node].links[0]) - return false; - - static ushort routeid = 1; - static vector queue; - - if(!routeid) - { - loopv(waypoints) waypoints[i].route = 0; - routeid = 1; - } - - if(d) - { - if(retries <= 1 && d->ai) loopi(ai::NUMPREVNODES) if(d->ai->prevnodes[i] != node && iswaypoint(d->ai->prevnodes[i])) - { - waypoints[d->ai->prevnodes[i]].route = routeid; - waypoints[d->ai->prevnodes[i]].curscore = -1; - waypoints[d->ai->prevnodes[i]].estscore = 0; - } + } + + struct wpcachestack + { + wpcache::node *node; + float tmin, tmax; + }; + + vector wpcachestack; + + int closestwaypoint(const vec &pos, float mindist, bool links, fpsent *d) + { + if(waypoints.empty()) return -1; + if(clearedwpcaches) buildwpcache(); + + #define CHECKCLOSEST(index) do { \ + int n = (index); \ + if(n < waypoints.length()) \ + { \ + const waypoint &w = waypoints[n]; \ + if(!links || w.links[0]) \ + { \ + float dist = w.o.squaredist(pos); \ + if(dist < mindist*mindist) { closest = n; mindist = sqrtf(dist); } \ + } \ + } \ + } while(0) + int closest = -1; + wpcache::node *curnode; + loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;) + { + int axis = curnode->axis(); + float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis]; + if(dist1 >= mindist) + { + if(dist2 < mindist) + { + if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } + CHECKCLOSEST(curnode->childindex(1)); + } + } + else if(curnode->isleaf(0)) + { + CHECKCLOSEST(curnode->childindex(0)); + if(dist2 < mindist) + { + if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } + CHECKCLOSEST(curnode->childindex(1)); + } + } + else + { + if(dist2 < mindist) + { + if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1)); + else CHECKCLOSEST(curnode->childindex(1)); + } + curnode += curnode->childindex(0); + continue; + } + if(wpcachestack.empty()) break; + curnode = wpcachestack.pop(); + } + for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKCLOSEST(i); } + return closest; + } + + void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector &results) + { + if(waypoints.empty()) return; + if(clearedwpcaches) buildwpcache(); + + float mindist2 = mindist*mindist, maxdist2 = maxdist*maxdist; + #define CHECKWITHIN(index) do { \ + int n = (index); \ + if(n < waypoints.length()) \ + { \ + const waypoint &w = waypoints[n]; \ + float dist = w.o.squaredist(pos); \ + if(dist > mindist2 && dist < maxdist2) results.add(n); \ + } \ + } while(0) + wpcache::node *curnode; + loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;) + { + int axis = curnode->axis(); + float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis]; + if(dist1 >= maxdist) + { + if(dist2 < maxdist) + { + if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } + CHECKWITHIN(curnode->childindex(1)); + } + } + else if(curnode->isleaf(0)) + { + CHECKWITHIN(curnode->childindex(0)); + if(dist2 < maxdist) + { + if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } + CHECKWITHIN(curnode->childindex(1)); + } + } + else + { + if(dist2 < maxdist) + { + if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1)); + else CHECKWITHIN(curnode->childindex(1)); + } + curnode += curnode->childindex(0); + continue; + } + if(wpcachestack.empty()) break; + curnode = wpcachestack.pop(); + } + for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKWITHIN(i); } + } + + void avoidset::avoidnear(void *owner, float above, const vec &pos, float limit) + { + if(ai::waypoints.empty()) return; + if(clearedwpcaches) buildwpcache(); + + float limit2 = limit*limit; + #define CHECKNEAR(index) do { \ + int n = (index); \ + if(n < ai::waypoints.length()) \ + { \ + const waypoint &w = ai::waypoints[n]; \ + if(w.o.squaredist(pos) < limit2) add(owner, above, n); \ + } \ + } while(0) + wpcache::node *curnode; + loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;) + { + int axis = curnode->axis(); + float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis]; + if(dist1 >= limit) + { + if(dist2 < limit) + { + if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } + CHECKNEAR(curnode->childindex(1)); + } + } + else if(curnode->isleaf(0)) + { + CHECKNEAR(curnode->childindex(0)); + if(dist2 < limit) + { + if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; } + CHECKNEAR(curnode->childindex(1)); + } + } + else + { + if(dist2 < limit) + { + if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1)); + else CHECKNEAR(curnode->childindex(1)); + } + curnode += curnode->childindex(0); + continue; + } + if(wpcachestack.empty()) break; + curnode = wpcachestack.pop(); + } + for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKNEAR(i); } + } + + int avoidset::remap(fpsent *d, int n, vec &pos, bool retry) + { + if(!obstacles.empty()) + { + int cur = 0; + loopv(obstacles) + { + obstacle &ob = obstacles[i]; + int next = cur + ob.numwaypoints; + if(ob.owner != d) + { + for(; cur < next; cur++) if(waypoints[cur] == n) + { + if(ob.above < 0) return retry ? n : -1; + vec above(pos.x, pos.y, ob.above); + if(above.z-d->o.z >= ai::JUMPMAX) + return retry ? n : -1; // too much scotty + int node = closestwaypoint(above, ai::SIGHTMIN, true, d); + if(ai::iswaypoint(node) && node != n) + { // try to reroute above their head? + if(!find(node, d)) + { + pos = ai::waypoints[node].o; + return node; + } + else return retry ? n : -1; + } + else + { + vec old = d->o; + d->o = vec(above).add(vec(0, 0, d->eyeheight)); + bool col = collide(d, vec(0, 0, 1)); + d->o = old; + if(!col) + { + pos = above; + return n; + } + else return retry ? n : -1; + } + } + } + cur = next; + } + } + return n; + } + + static inline float heapscore(waypoint *q) { return q->score(); } + + bool route(fpsent *d, int node, int goal, vector &route, const avoidset &obstacles, int retries) + { + if(waypoints.empty() || !iswaypoint(node) || !iswaypoint(goal) || goal == node || !waypoints[node].links[0]) + return false; + + static ushort routeid = 1; + static vector queue; + + if(!routeid) + { + loopv(waypoints) waypoints[i].route = 0; + routeid = 1; + } + + if(d) + { + if(retries <= 1 && d->ai) loopi(ai::NUMPREVNODES) if(d->ai->prevnodes[i] != node && iswaypoint(d->ai->prevnodes[i])) + { + waypoints[d->ai->prevnodes[i]].route = routeid; + waypoints[d->ai->prevnodes[i]].curscore = -1; + waypoints[d->ai->prevnodes[i]].estscore = 0; + } if(retries <= 0) { loopavoid(obstacles, d, @@ -453,99 +453,99 @@ namespace ai } }); } - } - - waypoints[node].route = routeid; - waypoints[node].curscore = waypoints[node].estscore = 0; - waypoints[node].prev = 0; - queue.setsize(0); - queue.add(&waypoints[node]); - route.setsize(0); - - int lowest = -1; - while(!queue.empty()) - { - waypoint &m = *queue.removeheap(); - float prevscore = m.curscore; - m.curscore = -1; - loopi(MAXWAYPOINTLINKS) - { - int link = m.links[i]; - if(!link) break; - if(iswaypoint(link) && (link == node || link == goal || waypoints[link].links[0])) - { - waypoint &n = waypoints[link]; - int weight = max(n.weight, 1); - float curscore = prevscore + n.o.dist(m.o)*weight; - if(n.route == routeid && curscore >= n.curscore) continue; - n.curscore = curscore; - n.prev = ushort(&m - &waypoints[0]); - if(n.route != routeid) - { - n.estscore = n.o.dist(waypoints[goal].o)*weight; - if(n.estscore <= WAYPOINTRADIUS*4 && (lowest < 0 || n.estscore <= waypoints[lowest].estscore)) - lowest = link; - n.route = routeid; - if(link == goal) goto foundgoal; - queue.addheap(&n); - } - else loopvj(queue) if(queue[j] == &n) { queue.upheap(j); break; } - } - } - } - foundgoal: - - routeid++; - - if(lowest >= 0) // otherwise nothing got there - { - for(waypoint *m = &waypoints[lowest]; m > &waypoints[0]; m = &waypoints[m->prev]) - route.add(m - &waypoints[0]); // just keep it stored backward - } - - return !route.empty(); - } - - VARF(dropwaypoints, 0, 0, 1, { player1->lastnode = -1; }); - - int addwaypoint(const vec &o, int weight = -1) - { - if(waypoints.length() > MAXWAYPOINTS) return -1; - int n = waypoints.length(); - waypoints.add(waypoint(o, weight >= 0 ? weight : getweight(o))); - invalidatewpcache(n); - return n; - } - - void linkwaypoint(waypoint &a, int n) - { - loopi(MAXWAYPOINTLINKS) - { - if(a.links[i] == n) return; - if(!a.links[i]) { a.links[i] = n; return; } - } - a.links[rnd(MAXWAYPOINTLINKS)] = n; - } - - string loadedwaypoints = ""; - - static inline bool shouldnavigate() - { - if(dropwaypoints) return true; - loopvrev(players) if(players[i]->aitype != AI_NONE) return true; - return false; - } - - static inline bool shoulddrop(fpsent *d) - { - return !d->ai && (dropwaypoints || !loadedwaypoints[0]); - } - - void inferwaypoints(fpsent *d, const vec &o, const vec &v, float mindist) - { - if(!shouldnavigate()) return; - if(shoulddrop(d)) - { + } + + waypoints[node].route = routeid; + waypoints[node].curscore = waypoints[node].estscore = 0; + waypoints[node].prev = 0; + queue.setsize(0); + queue.add(&waypoints[node]); + route.setsize(0); + + int lowest = -1; + while(!queue.empty()) + { + waypoint &m = *queue.removeheap(); + float prevscore = m.curscore; + m.curscore = -1; + loopi(MAXWAYPOINTLINKS) + { + int link = m.links[i]; + if(!link) break; + if(iswaypoint(link) && (link == node || link == goal || waypoints[link].links[0])) + { + waypoint &n = waypoints[link]; + int weight = max(n.weight, 1); + float curscore = prevscore + n.o.dist(m.o)*weight; + if(n.route == routeid && curscore >= n.curscore) continue; + n.curscore = curscore; + n.prev = ushort(&m - &waypoints[0]); + if(n.route != routeid) + { + n.estscore = n.o.dist(waypoints[goal].o)*weight; + if(n.estscore <= WAYPOINTRADIUS*4 && (lowest < 0 || n.estscore <= waypoints[lowest].estscore)) + lowest = link; + n.route = routeid; + if(link == goal) goto foundgoal; + queue.addheap(&n); + } + else loopvj(queue) if(queue[j] == &n) { queue.upheap(j); break; } + } + } + } + foundgoal: + + routeid++; + + if(lowest >= 0) // otherwise nothing got there + { + for(waypoint *m = &waypoints[lowest]; m > &waypoints[0]; m = &waypoints[m->prev]) + route.add(m - &waypoints[0]); // just keep it stored backward + } + + return !route.empty(); + } + + VARF(dropwaypoints, 0, 0, 1, { player1->lastnode = -1; }); + + int addwaypoint(const vec &o, int weight = -1) + { + if(waypoints.length() > MAXWAYPOINTS) return -1; + int n = waypoints.length(); + waypoints.add(waypoint(o, weight >= 0 ? weight : getweight(o))); + invalidatewpcache(n); + return n; + } + + void linkwaypoint(waypoint &a, int n) + { + loopi(MAXWAYPOINTLINKS) + { + if(a.links[i] == n) return; + if(!a.links[i]) { a.links[i] = n; return; } + } + a.links[rnd(MAXWAYPOINTLINKS)] = n; + } + + string loadedwaypoints = ""; + + static inline bool shouldnavigate() + { + if(dropwaypoints) return true; + loopvrev(players) if(players[i]->aitype != AI_NONE) return true; + return false; + } + + static inline bool shoulddrop(fpsent *d) + { + return !d->ai && (dropwaypoints || !loadedwaypoints[0]); + } + + void inferwaypoints(fpsent *d, const vec &o, const vec &v, float mindist) + { + if(!shouldnavigate()) return; + if(shoulddrop(d)) + { if(waypoints.empty()) seedwaypoints(); int from = closestwaypoint(o, mindist, false), to = closestwaypoint(v, mindist, false); if(!iswaypoint(from)) from = addwaypoint(o); @@ -560,249 +560,249 @@ namespace ai } } else d->lastnode = closestwaypoint(v, WAYPOINTRADIUS*2, false, d); - } - - void navigate(fpsent *d) - { - vec v(d->feetpos()); - if(d->state != CS_ALIVE) { d->lastnode = -1; return; } - bool dropping = shoulddrop(d); - int mat = lookupmaterial(v); - if((mat&MATF_CLIP) == MAT_CLIP || (mat&MATF_VOLUME) == MAT_LAVA || mat&MAT_DEATH) dropping = false; - float dist = dropping ? WAYPOINTRADIUS : (d->ai ? WAYPOINTRADIUS : SIGHTMIN); - int curnode = closestwaypoint(v, dist, false, d), prevnode = d->lastnode; - if(!iswaypoint(curnode) && dropping) - { + } + + void navigate(fpsent *d) + { + vec v(d->feetpos()); + if(d->state != CS_ALIVE) { d->lastnode = -1; return; } + bool dropping = shoulddrop(d); + int mat = lookupmaterial(v); + if((mat&MATF_CLIP) == MAT_CLIP || (mat&MATF_VOLUME) == MAT_LAVA || mat&MAT_DEATH) dropping = false; + float dist = dropping ? WAYPOINTRADIUS : (d->ai ? WAYPOINTRADIUS : SIGHTMIN); + int curnode = closestwaypoint(v, dist, false, d), prevnode = d->lastnode; + if(!iswaypoint(curnode) && dropping) + { if(waypoints.empty()) seedwaypoints(); - curnode = addwaypoint(v); - } - if(iswaypoint(curnode)) - { - if(dropping && d->lastnode != curnode && iswaypoint(d->lastnode)) - { - linkwaypoint(waypoints[d->lastnode], curnode); - if(!d->timeinair) linkwaypoint(waypoints[curnode], d->lastnode); - } - d->lastnode = curnode; - if(d->ai && iswaypoint(prevnode) && d->lastnode != prevnode) d->ai->addprevnode(prevnode); - } - else if(!iswaypoint(d->lastnode) || waypoints[d->lastnode].o.squaredist(v) > SIGHTMIN*SIGHTMIN) + curnode = addwaypoint(v); + } + if(iswaypoint(curnode)) + { + if(dropping && d->lastnode != curnode && iswaypoint(d->lastnode)) + { + linkwaypoint(waypoints[d->lastnode], curnode); + if(!d->timeinair) linkwaypoint(waypoints[curnode], d->lastnode); + } + d->lastnode = curnode; + if(d->ai && iswaypoint(prevnode) && d->lastnode != prevnode) d->ai->addprevnode(prevnode); + } + else if(!iswaypoint(d->lastnode) || waypoints[d->lastnode].o.squaredist(v) > SIGHTMIN*SIGHTMIN) d->lastnode = closestwaypoint(v, SIGHTMAX, false, d); - } - - void navigate() - { - if(shouldnavigate()) loopv(players) ai::navigate(players[i]); - if(invalidatedwpcaches) clearwpcache(false); - } - - void clearwaypoints(bool full) - { - waypoints.setsize(0); - clearwpcache(); - if(full) - { - loadedwaypoints[0] = '\0'; - dropwaypoints = 0; - } - } - ICOMMAND(clearwaypoints, "", (), clearwaypoints()); - - void seedwaypoints() - { - if(waypoints.empty()) addwaypoint(vec(0, 0, 0)); - loopv(entities::ents) - { - extentity &e = *entities::ents[i]; - switch(e.type) - { - case PLAYERSTART: case TELEPORT: case JUMPPAD: - addwaypoint(e.o); - break; - default: - if(e.type >= I_SHELLS && e.type <= I_QUAD) addwaypoint(e.o); - break; - } - } - } - - void remapwaypoints() - { - vector remap; - int total = 0; - loopv(waypoints) remap.add(waypoints[i].links[1] == 0xFFFF ? 0 : total++); - total = 0; - loopvj(waypoints) - { - if(waypoints[j].links[1] == 0xFFFF) continue; - waypoint &w = waypoints[total]; - if(j != total) w = waypoints[j]; - int k = 0; - loopi(MAXWAYPOINTLINKS) - { - int link = w.links[i]; - if(!link) break; - if((w.links[k] = remap[link])) k++; - } - if(k < MAXWAYPOINTLINKS) w.links[k] = 0; - total++; - } - waypoints.setsize(total); - } - - bool cleanwaypoints() - { - int cleared = 0; - for(int i = 1; i < waypoints.length(); i++) - { - waypoint &w = waypoints[i]; - if(clipped(w.o)) - { - w.links[0] = 0; - w.links[1] = 0xFFFF; - cleared++; - } - } - if(cleared) - { - player1->lastnode = -1; - loopv(players) if(players[i]) players[i]->lastnode = -1; - remapwaypoints(); - clearwpcache(); - return true; - } - return false; - } - - bool getwaypointfile(const char *mname, char *wptname) - { - if(!mname || !*mname) mname = getclientmap(); - if(!*mname) return false; - - string pakname, mapname, cfgname; - getmapfilenames(mname, NULL, pakname, mapname, cfgname); - nformatstring(wptname, MAXSTRLEN, "packages/%s.wpt", mapname); - path(wptname); - return true; - } - - void loadwaypoints(bool force, const char *mname) - { - string wptname; - if(!getwaypointfile(mname, wptname)) return; - if(!force && (waypoints.length() || !strcmp(loadedwaypoints, wptname))) return; - - stream *f = opengzfile(wptname, "rb"); - if(!f) return; - char magic[4]; - if(f->read(magic, 4) < 4 || memcmp(magic, "OWPT", 4)) { delete f; return; } - - copystring(loadedwaypoints, wptname); - - waypoints.setsize(0); - waypoints.add(vec(0, 0, 0)); - ushort numwp = f->getlil(); - loopi(numwp) - { - if(f->end()) break; - vec o; - o.x = f->getlil(); - o.y = f->getlil(); - o.z = f->getlil(); - waypoint &w = waypoints.add(waypoint(o, getweight(o))); - int numlinks = f->getchar(), k = 0; - loopi(numlinks) - { - if((w.links[k] = f->getlil())) - { - if(++k >= MAXWAYPOINTLINKS) break; - } - } - } - - delete f; - conoutf("loaded %d waypoints from %s", numwp, wptname); - - if(!cleanwaypoints()) clearwpcache(); - } - ICOMMAND(loadwaypoints, "s", (char *mname), loadwaypoints(true, mname)); - - void savewaypoints(bool force, const char *mname) - { - if((!dropwaypoints && !force) || waypoints.empty()) return; - - string wptname; - if(!getwaypointfile(mname, wptname)) return; - - stream *f = opengzfile(wptname, "wb"); - if(!f) return; - f->write("OWPT", 4); - f->putlil(waypoints.length()-1); - for(int i = 1; i < waypoints.length(); i++) - { - waypoint &w = waypoints[i]; - f->putlil(w.o.x); - f->putlil(w.o.y); - f->putlil(w.o.z); - int numlinks = 0; - loopj(MAXWAYPOINTLINKS) { if(!w.links[j]) break; numlinks++; } - f->putchar(numlinks); - loopj(numlinks) f->putlil(w.links[j]); - } - - delete f; - conoutf("saved %d waypoints to %s", waypoints.length()-1, wptname); - } - - ICOMMAND(savewaypoints, "s", (char *mname), savewaypoints(true, mname)); - - void delselwaypoints() - { - if(noedit(true)) return; - vec o = vec(sel.o).sub(0.1f), s = vec(sel.s).mul(sel.grid).add(o).add(0.1f); - int cleared = 0; - for(int i = 1; i < waypoints.length(); i++) - { - waypoint &w = waypoints[i]; - if(w.o.x >= o.x && w.o.x <= s.x && w.o.y >= o.y && w.o.y <= s.y && w.o.z >= o.z && w.o.z <= s.z) - { - w.links[0] = 0; - w.links[1] = 0xFFFF; - cleared++; - } - } - if(cleared) - { - player1->lastnode = -1; - remapwaypoints(); - clearwpcache(); - } - } - COMMAND(delselwaypoints, ""); - - void movewaypoints(const vec &d) - { - if(noedit(true)) return; - int worldsize = getworldsize(); - if(d.x < -worldsize || d.x > worldsize || d.y < -worldsize || d.y > worldsize || d.z < -worldsize || d.z > worldsize) - { - clearwaypoints(); - return; - } - int cleared = 0; - for(int i = 1; i < waypoints.length(); i++) - { - waypoint &w = waypoints[i]; - w.o.add(d); - if(!insideworld(w.o)) { w.links[0] = 0; w.links[1] = 0xFFFF; cleared++; } - } - if(cleared) - { - player1->lastnode = -1; - remapwaypoints(); - } - clearwpcache(); - } - ICOMMAND(movewaypoints, "iii", (int *dx, int *dy, int *dz), movewaypoints(vec(*dx, *dy, *dz))); + } + + void navigate() + { + if(shouldnavigate()) loopv(players) ai::navigate(players[i]); + if(invalidatedwpcaches) clearwpcache(false); + } + + void clearwaypoints(bool full) + { + waypoints.setsize(0); + clearwpcache(); + if(full) + { + loadedwaypoints[0] = '\0'; + dropwaypoints = 0; + } + } + ICOMMAND(clearwaypoints, "", (), clearwaypoints()); + + void seedwaypoints() + { + if(waypoints.empty()) addwaypoint(vec(0, 0, 0)); + loopv(entities::ents) + { + extentity &e = *entities::ents[i]; + switch(e.type) + { + case PLAYERSTART: case TELEPORT: case JUMPPAD: + addwaypoint(e.o); + break; + default: + if(e.type >= I_SHELLS && e.type <= I_QUAD) addwaypoint(e.o); + break; + } + } + } + + void remapwaypoints() + { + vector remap; + int total = 0; + loopv(waypoints) remap.add(waypoints[i].links[1] == 0xFFFF ? 0 : total++); + total = 0; + loopvj(waypoints) + { + if(waypoints[j].links[1] == 0xFFFF) continue; + waypoint &w = waypoints[total]; + if(j != total) w = waypoints[j]; + int k = 0; + loopi(MAXWAYPOINTLINKS) + { + int link = w.links[i]; + if(!link) break; + if((w.links[k] = remap[link])) k++; + } + if(k < MAXWAYPOINTLINKS) w.links[k] = 0; + total++; + } + waypoints.setsize(total); + } + + bool cleanwaypoints() + { + int cleared = 0; + for(int i = 1; i < waypoints.length(); i++) + { + waypoint &w = waypoints[i]; + if(clipped(w.o)) + { + w.links[0] = 0; + w.links[1] = 0xFFFF; + cleared++; + } + } + if(cleared) + { + player1->lastnode = -1; + loopv(players) if(players[i]) players[i]->lastnode = -1; + remapwaypoints(); + clearwpcache(); + return true; + } + return false; + } + + bool getwaypointfile(const char *mname, char *wptname) + { + if(!mname || !*mname) mname = getclientmap(); + if(!*mname) return false; + + string pakname, mapname, cfgname; + getmapfilenames(mname, NULL, pakname, mapname, cfgname); + nformatstring(wptname, MAXSTRLEN, "packages/%s.wpt", mapname); + path(wptname); + return true; + } + + void loadwaypoints(bool force, const char *mname) + { + string wptname; + if(!getwaypointfile(mname, wptname)) return; + if(!force && (waypoints.length() || !strcmp(loadedwaypoints, wptname))) return; + + stream *f = opengzfile(wptname, "rb"); + if(!f) return; + char magic[4]; + if(f->read(magic, 4) < 4 || memcmp(magic, "OWPT", 4)) { delete f; return; } + + copystring(loadedwaypoints, wptname); + + waypoints.setsize(0); + waypoints.add(vec(0, 0, 0)); + ushort numwp = f->getlil(); + loopi(numwp) + { + if(f->end()) break; + vec o; + o.x = f->getlil(); + o.y = f->getlil(); + o.z = f->getlil(); + waypoint &w = waypoints.add(waypoint(o, getweight(o))); + int numlinks = f->getchar(), k = 0; + loopi(numlinks) + { + if((w.links[k] = f->getlil())) + { + if(++k >= MAXWAYPOINTLINKS) break; + } + } + } + + delete f; + conoutf("loaded %d waypoints from %s", numwp, wptname); + + if(!cleanwaypoints()) clearwpcache(); + } + ICOMMAND(loadwaypoints, "s", (char *mname), loadwaypoints(true, mname)); + + void savewaypoints(bool force, const char *mname) + { + if((!dropwaypoints && !force) || waypoints.empty()) return; + + string wptname; + if(!getwaypointfile(mname, wptname)) return; + + stream *f = opengzfile(wptname, "wb"); + if(!f) return; + f->write("OWPT", 4); + f->putlil(waypoints.length()-1); + for(int i = 1; i < waypoints.length(); i++) + { + waypoint &w = waypoints[i]; + f->putlil(w.o.x); + f->putlil(w.o.y); + f->putlil(w.o.z); + int numlinks = 0; + loopj(MAXWAYPOINTLINKS) { if(!w.links[j]) break; numlinks++; } + f->putchar(numlinks); + loopj(numlinks) f->putlil(w.links[j]); + } + + delete f; + conoutf("saved %d waypoints to %s", waypoints.length()-1, wptname); + } + + ICOMMAND(savewaypoints, "s", (char *mname), savewaypoints(true, mname)); + + void delselwaypoints() + { + if(noedit(true)) return; + vec o = vec(sel.o).sub(0.1f), s = vec(sel.s).mul(sel.grid).add(o).add(0.1f); + int cleared = 0; + for(int i = 1; i < waypoints.length(); i++) + { + waypoint &w = waypoints[i]; + if(w.o.x >= o.x && w.o.x <= s.x && w.o.y >= o.y && w.o.y <= s.y && w.o.z >= o.z && w.o.z <= s.z) + { + w.links[0] = 0; + w.links[1] = 0xFFFF; + cleared++; + } + } + if(cleared) + { + player1->lastnode = -1; + remapwaypoints(); + clearwpcache(); + } + } + COMMAND(delselwaypoints, ""); + + void movewaypoints(const vec &d) + { + if(noedit(true)) return; + int worldsize = getworldsize(); + if(d.x < -worldsize || d.x > worldsize || d.y < -worldsize || d.y > worldsize || d.z < -worldsize || d.z > worldsize) + { + clearwaypoints(); + return; + } + int cleared = 0; + for(int i = 1; i < waypoints.length(); i++) + { + waypoint &w = waypoints[i]; + w.o.add(d); + if(!insideworld(w.o)) { w.links[0] = 0; w.links[1] = 0xFFFF; cleared++; } + } + if(cleared) + { + player1->lastnode = -1; + remapwaypoints(); + } + clearwpcache(); + } + ICOMMAND(movewaypoints, "iii", (int *dx, int *dy, int *dz), movewaypoints(vec(*dx, *dy, *dz))); } diff --git a/src/fpsgame/weapon.cpp b/src/fpsgame/weapon.cpp index d911aba..1bdb302 100644 --- a/src/fpsgame/weapon.cpp +++ b/src/fpsgame/weapon.cpp @@ -3,975 +3,974 @@ namespace game { - static const int OFFSETMILLIS = 500; - vec rays[MAXRAYS]; - - struct hitmsg - { - int target, lifesequence, info1, info2; - ivec dir; - }; - vector hits; - - ICOMMAND(getweapon, "", (), intret(player1->gunselect)); - - void gunselect(int gun, fpsent *d) - { - if(gun!=d->gunselect) - { - addmsg(N_GUNSELECT, "rci", d, gun); - playsound(S_WEAPLOAD, d == player1 ? NULL : &d->o); - } - d->gunselect = gun; - } - - void nextweapon(int dir, bool force = false) - { - if(player1->state!=CS_ALIVE) return; - dir = (dir < 0 ? NUMGUNS-1 : 1); - int gun = player1->gunselect; - loopi(NUMGUNS) - { - gun = (gun + dir)%NUMGUNS; - if(force || player1->ammo[gun]) break; - } - if(gun != player1->gunselect) gunselect(gun, player1); - else playsound(S_NOAMMO); - } - ICOMMAND(nextweapon, "ii", (int *dir, int *force), nextweapon(*dir, *force!=0)); - - int getweapon(const char *name) - { - const char *abbrevs[] = { "FI", "SG", "CG", "RL", "RI", "GL", "PI" }; - if(isdigit(name[0])) return parseint(name); - else loopi(sizeof(abbrevs)/sizeof(abbrevs[0])) if(!strcasecmp(abbrevs[i], name)) return i; - return -1; - } - - void setweapon(const char *name, bool force = false) - { - int gun = getweapon(name); - if(player1->state!=CS_ALIVE || gunGUN_PISTOL) return; - if(force || player1->ammo[gun]) gunselect(gun, player1); - else playsound(S_NOAMMO); - } - ICOMMAND(setweapon, "si", (char *name, int *force), setweapon(name, *force!=0)); - - void cycleweapon(int numguns, int *guns, bool force = false) - { - if(numguns<=0 || player1->state!=CS_ALIVE) return; - int offset = 0; - loopi(numguns) if(guns[i] == player1->gunselect) { offset = i+1; break; } - loopi(numguns) - { - int gun = guns[(i+offset)%numguns]; - if(gun>=0 && gunammo[gun])) - { - gunselect(gun, player1); - return; - } - } - playsound(S_NOAMMO); - } - ICOMMAND(cycleweapon, "V", (tagval *args, int numargs), - { - int numguns = min(numargs, 7); - int guns[7]; - loopi(numguns) guns[i] = getweapon(args[i].getstr()); - cycleweapon(numguns, guns); - }); - - void weaponswitch(fpsent *d) - { - if(d->state!=CS_ALIVE) return; - int s = d->gunselect; - if (s!=GUN_CG && d->ammo[GUN_CG]) s = GUN_CG; - else if(s!=GUN_RL && d->ammo[GUN_RL]) s = GUN_RL; - else if(s!=GUN_SG && d->ammo[GUN_SG]) s = GUN_SG; - else if(s!=GUN_RIFLE && d->ammo[GUN_RIFLE]) s = GUN_RIFLE; - else if(s!=GUN_GL && d->ammo[GUN_GL]) s = GUN_GL; - else if(s!=GUN_PISTOL && d->ammo[GUN_PISTOL]) s = GUN_PISTOL; - else s = GUN_FIST; - - gunselect(s, d); - } - - ICOMMAND(weapon, "V", (tagval *args, int numargs), - { - if(player1->state!=CS_ALIVE) return; - loopi(7) - { - const char *name = i < numargs ? args[i].getstr() : ""; - if(name[0]) - { - int gun = getweapon(name); - if(gun >= GUN_FIST && gun <= GUN_PISTOL && gun != player1->gunselect && player1->ammo[gun]) { gunselect(gun, player1); return; } - } else { weaponswitch(player1); return; } - } - playsound(S_NOAMMO); - }); - - void offsetray(const vec &from, const vec &to, int spread, float range, vec &dest) - { - vec offset; - do offset = vec(rndscale(1), rndscale(1), rndscale(1)).sub(0.5f); - while(offset.squaredlen() > 0.5f*0.5f); - offset.mul((to.dist(from)/1024)*spread); - offset.z /= 2; - dest = vec(offset).add(to); - if(dest != from) - { - vec dir = vec(dest).sub(from).normalize(); - raycubepos(from, dir, dest, range, RAY_CLIPMAT|RAY_ALPHAPOLY); - } - } - - void createrays(int gun, const vec &from, const vec &to) // create random spread of rays - { - loopi(guns[gun].rays) offsetray(from, to, guns[gun].spread, guns[gun].range, rays[i]); - } - - enum { BNC_GRENADE }; - - struct bouncer : physent - { - int lifetime, bounces; - float lastyaw, roll; - bool local; - fpsent *owner; - int bouncetype, variant; - vec offset; - int offsetmillis; - float offsetheight; - int id; - entitylight light; - - bouncer() : bounces(0), roll(0), variant(0) - { - type = ENT_BOUNCE; - } - - vec offsetpos() - { - vec pos(o); - if(offsetmillis > 0) - { - pos.add(vec(offset).mul(offsetmillis/float(OFFSETMILLIS))); - if(offsetheight >= 0) pos.z = max(pos.z, o.z - max(offsetheight - eyeheight, 0.0f)); - } - return pos; - } - - void limitoffset() - { - if(bouncetype == BNC_GRENADE && offsetmillis > 0 && offset.z < 0) - offsetheight = raycube(vec(o.x + offset.x, o.y + offset.y, o.z), vec(0, 0, -1), -offset.z); - else offsetheight = -1; - } - }; - - vector bouncers; - - vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d); - - void newbouncer(const vec &from, const vec &to, bool local, int id, fpsent *owner, int type, int lifetime, int speed, entitylight *light = NULL) - { - bouncer &bnc = *bouncers.add(new bouncer); - bnc.o = from; - bnc.radius = bnc.xradius = bnc.yradius = 1.5f; - bnc.eyeheight = bnc.radius; - bnc.aboveeye = bnc.radius; - bnc.lifetime = lifetime; - bnc.local = local; - bnc.owner = owner; - bnc.bouncetype = type; - bnc.id = local ? lastmillis : id; - if(light) bnc.light = *light; - - bnc.collidetype = COLLIDE_ELLIPSE_PRECISE; - - vec dir(to); - dir.sub(from).safenormalize(); - bnc.vel = dir; - bnc.vel.mul(speed); - - avoidcollision(&bnc, dir, owner, 0.1f); - - if(type==BNC_GRENADE) - { - bnc.offset = hudgunorigin(GUN_GL, from, to, owner); - if(owner==followingplayer(player1) && !isthirdperson()) bnc.offset.sub(owner->o).rescale(16).add(owner->o); - } - else bnc.offset = from; - bnc.offset.sub(bnc.o); - bnc.offsetmillis = OFFSETMILLIS; - bnc.limitoffset(); - - bnc.resetinterp(); - } - - void bounced(physent *d, const vec &surface) - { - if(d->type != ENT_BOUNCE) return; - bouncer *b = (bouncer *)d; - if(b->bounces >= 2) return; - b->bounces++; - adddecal(DECAL_BLOOD, vec(b->o).sub(vec(surface).mul(b->radius)), surface, 2.96f/b->bounces, bvec(0x60, 0xFF, 0xFF), rnd(4)); - } - - void updatebouncers(int time) - { - loopv(bouncers) - { - bouncer &bnc = *bouncers[i]; - if(bnc.bouncetype==BNC_GRENADE && bnc.vel.magnitude() > 50.0f) - { - vec pos = bnc.offsetpos(); - regular_particle_splash(PART_SMOKE, 1, 150, pos, 0x404040, 2.4f, 50, -20); - } - vec old(bnc.o); - bool stopped = false; - if(bnc.bouncetype==BNC_GRENADE) stopped = bounce(&bnc, 0.6f, 0.5f, 0.8f) || (bnc.lifetime -= time)<0; - - if(stopped) - { - if(bnc.bouncetype==BNC_GRENADE) - { - int qdam = guns[GUN_GL].damage*(bnc.owner->quadmillis ? 4 : 1); - hits.setsize(0); - explode(bnc.local, bnc.owner, bnc.o, NULL, qdam, GUN_GL); - adddecal(DECAL_SCORCH, bnc.o, vec(0, 0, 1), guns[GUN_GL].exprad/2); - if(bnc.local) - addmsg(N_EXPLODE, "rci3iv", bnc.owner, lastmillis-maptime, GUN_GL, bnc.id-maptime, - hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); - } - delete bouncers.remove(i--); - } - else - { - bnc.roll += old.sub(bnc.o).magnitude()/(4*RAD); - bnc.offsetmillis = max(bnc.offsetmillis-time, 0); - bnc.limitoffset(); - } - } - } - - void removebouncers(fpsent *owner) - { - loopv(bouncers) if(bouncers[i]->owner==owner) { delete bouncers[i]; bouncers.remove(i--); } - } - - void clearbouncers() { bouncers.deletecontents(); } - - struct projectile - { - vec dir, o, to, offset; - float speed; - fpsent *owner; - int gun; - bool local; - int offsetmillis; - int id; - entitylight light; - }; - vector projs; - - void clearprojectiles() { projs.shrink(0); } - - void newprojectile(const vec &from, const vec &to, float speed, bool local, int id, fpsent *owner, int gun) - { - projectile &p = projs.add(); - p.dir = vec(to).sub(from).safenormalize(); - p.o = from; - p.to = to; - p.offset = hudgunorigin(gun, from, to, owner); - p.offset.sub(from); - p.speed = speed; - p.local = local; - p.owner = owner; - p.gun = gun; - p.offsetmillis = OFFSETMILLIS; - p.id = local ? lastmillis : id; - } - - void removeprojectiles(fpsent *owner) - { - // can't use loopv here due to strange GCC optimizer bug - int len = projs.length(); - loopi(len) if(projs[i].owner==owner) { projs.remove(i--); len--; } - } - - VARP(blood, 0, 1, 1); - - void damageeffect(int damage, fpsent *d, bool thirdperson) - { - vec p = d->o; - p.z += 0.6f*(d->eyeheight + d->aboveeye) - d->eyeheight; - if(blood) particle_splash(PART_BLOOD, damage/10, 1000, p, 0x60FFFF, 2.96f); - if(thirdperson) - { - defformatstring(ds, "%d", damage); - particle_textcopy(d->abovehead(), ds, PART_TEXT, 2000, 0xFF4B19, 4.0f, -8); - } - } - - void spawnbouncer(const vec &p, const vec &vel, fpsent *d, int type, entitylight *light = NULL) - { - vec to(rnd(100)-50, rnd(100)-50, rnd(100)-50); - if(to.iszero()) to.z += 1; - to.normalize(); - to.add(p); - newbouncer(p, to, true, 0, d, type, rnd(1000)+1000, rnd(100)+20, light); - } - - void hit(int damage, dynent *d, fpsent *at, const vec &vel, int gun, float info1, int info2 = 1) - { - if(at==player1 && d!=at) - { - extern int hitsound; - if(hitsound && lasthit != lastmillis) playsound(S_HIT); - lasthit = lastmillis; - } - - fpsent *f = (fpsent *)d; - - f->lastpain = lastmillis; - if(at->type==ENT_PLAYER && !isteam(at->team, f->team)) at->totaldamage += damage; - - if(f->type==ENT_AI || !m_mp(gamemode) || f==at) f->hitpush(damage, vel, at, gun); - - pwhit(gun, damage); - - if(!m_mp(gamemode)) damaged(damage, f, at); - else - { - hitmsg &h = hits.add(); - h.target = f->clientnum; - h.lifesequence = f->lifesequence; - h.info1 = int(info1*DMF); - h.info2 = info2; - h.dir = f==at ? ivec(0, 0, 0) : ivec(vec(vel).mul(DNF)); - if(at==player1) - { - damageeffect(damage, f); - if(f==player1) - { - damageblend(damage); - damagecompass(damage, at ? at->o : f->o); - playsound(S_PAIN6); - } - else playsound(S_PAIN1+rnd(5), &f->o); - } - } - } - - void hitpush(int damage, dynent *d, fpsent *at, vec &from, vec &to, int gun, int rays) - { - hit(damage, d, at, vec(to).sub(from).safenormalize(), gun, from.dist(to), rays); - } - - float projdist(dynent *o, vec &dir, const vec &v) - { - vec middle = o->o; - middle.z += (o->aboveeye-o->eyeheight)/2; - float dist = middle.dist(v, dir); - dir.div(dist); - if(dist<0) dist = 0; - return dist; - } - - void radialeffect(dynent *o, const vec &v, int qdam, fpsent *at, int gun) - { - if(o->state!=CS_ALIVE) return; - vec dir; - float dist = projdist(o, dir, v); - if(disto.reject(v, o->radius + guns[gun].exprad) || o==safe) continue; - radialeffect(o, v, damage, owner, gun); - } - } - - void projsplash(projectile &p, vec &v, dynent *safe, int damage) - { - if(guns[p.gun].part) - { - particle_splash(PART_SPARK, 100, 200, v, 0xB49B4B, 0.24f); - playsound(S_FEXPLODE, &v); - // no push? - } - else - { - explode(p.local, p.owner, v, safe, damage, GUN_RL); - adddecal(DECAL_SCORCH, v, vec(p.dir).neg(), guns[p.gun].exprad/2); - } - } - - void explodeeffects(int gun, fpsent *d, bool local, int id) - { - if(local) return; - switch(gun) - { - case GUN_RL: - loopv(projs) - { - projectile &p = projs[i]; - if(p.gun == gun && p.owner == d && p.id == id && !p.local) - { - vec pos(p.o); - pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS))); - explode(p.local, p.owner, pos, NULL, 0, GUN_RL); - adddecal(DECAL_SCORCH, pos, vec(p.dir).neg(), guns[gun].exprad/2); - projs.remove(i); - break; - } - } - break; - case GUN_GL: - loopv(bouncers) - { - bouncer &b = *bouncers[i]; - if(b.bouncetype == BNC_GRENADE && b.owner == d && b.id == id && !b.local) - { - vec pos = b.offsetpos(); - explode(b.local, b.owner, pos, NULL, 0, GUN_GL); - adddecal(DECAL_SCORCH, pos, vec(0, 0, 1), guns[gun].exprad/2); - delete bouncers.remove(i); - break; - } - } - break; - default: - break; - } - } - - bool projdamage(dynent *o, projectile &p, vec &v, int qdam) - { - if(o->state!=CS_ALIVE) return false; - if(!intersect(o, p.o, v)) return false; - projsplash(p, v, o, qdam); - vec dir; - projdist(o, dir, v); - hit(qdam, o, p.owner, dir, p.gun, 0); - return true; - } - - void updateprojectiles(int time) - { - loopv(projs) - { - projectile &p = projs[i]; - p.offsetmillis = max(p.offsetmillis-time, 0); - int qdam = guns[p.gun].damage*(p.owner->quadmillis ? 4 : 1); - vec dv; - float dist = p.to.dist(p.o, dv); - dv.mul(time/max(dist*1000/p.speed, float(time))); - vec v = vec(p.o).add(dv); - bool exploded = false; - hits.setsize(0); - if(p.local) - { - vec halfdv = vec(dv).mul(0.5f), bo = vec(p.o).add(halfdv); - float br = max(fabs(halfdv.x), fabs(halfdv.y)) + 1; - loopj(numdynents()) - { - dynent *o = iterdynents(j); - if(p.owner==o || o->o.reject(bo, o->radius + br)) continue; - if(projdamage(o, p, v, qdam)) { exploded = true; break; } - } - } - if(!exploded) - { - if(dist<4) - { - if(p.o!=p.to) // if original target was moving, reevaluate endpoint - { - if(raycubepos(p.o, p.dir, p.to, 0, RAY_CLIPMAT|RAY_ALPHAPOLY)>=4) continue; - } - projsplash(p, v, NULL, qdam); - exploded = true; - } - else - { - vec pos(v); - pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS))); - regular_particle_splash(PART_SMOKE, 2, 300, pos, 0x404040, 2.4f, 50, -20); - } - } - if(exploded) - { - if(p.local) - addmsg(N_EXPLODE, "rci3iv", p.owner, lastmillis-maptime, p.gun, p.id-maptime, - hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); - projs.remove(i--); - } - else p.o = v; - } - } - - extern int chainsawhudgun; - - VARP(muzzleflash, 0, 1, 1); - VARP(muzzlelight, 0, 1, 1); - - void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int id, int prevaction) // create visual effect from a shot - { - int sound = guns[gun].sound, pspeed = 25; - switch(gun) - { - case GUN_FIST: - if(d->type==ENT_PLAYER && chainsawhudgun) sound = S_CHAINSAW_ATTACK; - break; - - case GUN_SG: - { - if(!local) createrays(gun, from, to); - if(muzzleflash && d->muzzle.x >= 0) - particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH3, 0xFFFFFF, 2.75f, d); - loopi(guns[gun].rays) - { - if(d->quadmillis) - particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, rays[i], d), rays[i], 0x802010, 0.6f, 36); - particle_splash(PART_SPARK, 20, 250, rays[i], 0xB49B4B, 0.24f); - particle_flare(hudgunorigin(gun, from, rays[i], d), rays[i], 300, PART_STREAK, 0xFFC864, 0.28f); - if(!local) adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f); - } - if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 30, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d); - break; - } - - case GUN_CG: - case GUN_PISTOL: - { - particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f); - if(d->quadmillis) - particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36); - particle_flare(hudgunorigin(gun, from, to, d), to, 600, PART_STREAK, 0xFFC864, 0.28f); - if(muzzleflash && d->muzzle.x >= 0) - particle_flare(d->muzzle, d->muzzle, gun==GUN_CG ? 100 : 200, PART_MUZZLE_FLASH1, 0xFFFFFF, gun==GUN_CG ? 2.25f : 1.25f, d); - if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 2.0f); - if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), gun==GUN_CG ? 30 : 15, vec(0.5f, 0.375f, 0.25f), gun==GUN_CG ? 50 : 100, gun==GUN_CG ? 50 : 100, DL_FLASH, 0, vec(0, 0, 0), d); - break; - } - - case GUN_RL: - if(d->quadmillis) - particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36); - if(muzzleflash && d->muzzle.x >= 0) - particle_flare(d->muzzle, d->muzzle, 250, PART_MUZZLE_FLASH2, 0xFFFFFF, 3.0f, d); - pspeed = guns[gun].projspeed; - newprojectile(from, to, (float)pspeed, local, id, d, gun); - break; - - case GUN_GL: - { - float dist = from.dist(to); - vec up = to; - up.z += dist/8; - if(muzzleflash && d->muzzle.x >= 0) - particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH2, 0xFFFFFF, 1.5f, d); - if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 20, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d); - newbouncer(from, up, local, id, d, BNC_GRENADE, guns[gun].ttl, guns[gun].projspeed); - break; - } - - case GUN_RIFLE: - particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f); - if(d->quadmillis) - particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36); - particle_trail(PART_SMOKE, 500, hudgunorigin(gun, from, to, d), to, 0x404040, 0.6f, 18); - if(muzzleflash && d->muzzle.x >= 0) - particle_flare(d->muzzle, d->muzzle, 150, PART_MUZZLE_FLASH3, 0xFFFFFF, 1.25f, d); - if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 3.0f); - if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 25, vec(0.5f, 0.375f, 0.25f), 75, 75, DL_FLASH, 0, vec(0, 0, 0), d); - break; - } - - bool looped = false; - if(d->attacksound >= 0 && d->attacksound != sound) d->stopattacksound(); - if(d->idlesound >= 0) d->stopidlesound(); - fpsent *h = followingplayer(player1); - switch(sound) - { - case S_CHAINSAW_ATTACK: - if(d->attacksound >= 0) looped = true; - d->attacksound = sound; - d->attackchan = playsound(sound, d==h ? NULL : &d->o, NULL, 0, -1, 100, d->attackchan); - break; - default: - playsound(sound, d==h ? NULL : &d->o); - break; - } - if(d->quadmillis && lastmillis-prevaction>200 && !looped) playsound(S_ITEMPUP, d==h ? NULL : &d->o); - } - - void particletrack(physent *owner, vec &o, vec &d) - { - if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return; - fpsent *pl = (fpsent *)owner; - if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return; - float dist = o.dist(d); - o = pl->muzzle; - if(dist <= 0) d = o; - else - { - vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d); - float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY); - d.mul(min(newdist, dist)).add(owner->o); - } - } - - void dynlighttrack(physent *owner, vec &o, vec &hud) - { - if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return; - fpsent *pl = (fpsent *)owner; - if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return; - o = pl->muzzle; - hud = owner == followingplayer(player1) ? vec(pl->o).add(vec(0, 0, 2)) : pl->muzzle; - } - - float intersectdist = 1e16f; - - bool intersect(dynent *d, const vec &from, const vec &to, float &dist) // if lineseg hits entity bounding box - { - vec bottom(d->o), top(d->o); - bottom.z -= d->eyeheight; - top.z += d->aboveeye; - return linecylinderintersect(from, to, bottom, top, d->radius, dist); - } - - dynent *intersectclosest(const vec &from, const vec &to, fpsent *at, float &bestdist) - { - dynent *best = NULL; - bestdist = 1e16f; - loopi(numdynents()) - { - dynent *o = iterdynents(i); - if(o==at || o->state!=CS_ALIVE) continue; - float dist; - if(!intersect(o, from, to, dist)) continue; - if(distgunselect].damage; - if(d->quadmillis) qdam *= 4; - dynent *o; - float dist; - if(guns[d->gunselect].rays > 1) - { - dynent *hits[MAXRAYS]; - int maxrays = guns[d->gunselect].rays; - loopi(maxrays) - { - if((hits[i] = intersectclosest(from, rays[i], d, dist))) shorten(from, rays[i], dist); - else adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f); - } - loopi(maxrays) if(hits[i]) - { - o = hits[i]; - hits[i] = NULL; - int numhits = 1; - for(int j = i+1; j < maxrays; j++) if(hits[j] == o) - { - hits[j] = NULL; - numhits++; - } - hitpush(numhits*qdam, o, d, from, to, d->gunselect, numhits); - } - } - else if((o = intersectclosest(from, to, d, dist))) - { - shorten(from, to, dist); - hitpush(qdam, o, d, from, to, d->gunselect, 1); - } - else if(d->gunselect!=GUN_FIST) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), d->gunselect==GUN_RIFLE ? 3.0f : 2.0f); - } - - void shoot(fpsent *d, const vec &targ) - { - int prevaction = d->lastaction, attacktime = lastmillis-prevaction; - if(attacktimegunwait) return; - d->gunwait = 0; - if((d==player1 || d->ai) && !d->attacking) return; - d->lastaction = lastmillis; - d->lastattackgun = d->gunselect; - if(!d->ammo[d->gunselect]) - { - if(d==player1) - { - msgsound(S_NOAMMO, d); - d->gunwait = 600; - d->lastattackgun = -1; - weaponswitch(d); - } - return; - } - if(d->gunselect) d->ammo[d->gunselect]--; - - pwshot(d->gunselect); /// PW - - vec from = d->o, to = targ, dir = vec(to).sub(from).safenormalize(); - float dist = to.dist(from); - vec kickback = vec(dir).mul(guns[d->gunselect].kickamount*-2.5f); - d->vel.add(kickback); - float shorten = 0; - if(guns[d->gunselect].range && dist > guns[d->gunselect].range) - shorten = guns[d->gunselect].range; - float barrier = raycube(d->o, dir, dist, RAY_CLIPMAT|RAY_ALPHAPOLY); - if(barrier > 0 && barrier < dist && (!shorten || barrier < shorten)) - shorten = barrier; - if(shorten) to = vec(dir).mul(shorten).add(from); - - if(guns[d->gunselect].rays > 1) createrays(d->gunselect, from, to); - else if(guns[d->gunselect].spread) offsetray(from, to, guns[d->gunselect].spread, guns[d->gunselect].range, to); - - hits.setsize(0); - - if(!guns[d->gunselect].projspeed) raydamage(from, to, d); - - shoteffects(d->gunselect, from, to, d, true, 0, prevaction); - - if(d==player1 || d->ai) - { - addmsg(N_SHOOT, "rci2i6iv", d, lastmillis-maptime, d->gunselect, - (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), - (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), - hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); - } - - d->gunwait = guns[d->gunselect].attackdelay; - if(d->gunselect == GUN_PISTOL && d->ai) d->gunwait += int(d->gunwait*(((101-d->skill)+rnd(111-d->skill))/100.f)); - d->totalshots += guns[d->gunselect].damage*(d->quadmillis ? 4 : 1)*guns[d->gunselect].rays; - } - - void adddynlights() - { - loopv(projs) - { - projectile &p = projs[i]; - if(p.gun!=GUN_RL) continue; - vec pos(p.o); - pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS))); - adddynlight(pos, 20, vec(1, 0.75f, 0.5f)); - } - loopv(bouncers) - { - bouncer &bnc = *bouncers[i]; - if(bnc.bouncetype!=BNC_GRENADE) continue; - vec pos = bnc.offsetpos(); - adddynlight(pos, 8, vec(0.25f, 1, 1)); - } - } - - static const char * const projnames[2] = { "projectiles/grenade", "projectiles/rocket" }; - - void preloadbouncers() { - loopi(sizeof(projnames)/sizeof(projnames[0])) preloadmodel(projnames[i]); - } - - void renderbouncers() - { - float yaw, pitch; - loopv(bouncers) - { - bouncer &bnc = *bouncers[i]; - vec pos = bnc.offsetpos(); - vec vel(bnc.vel); - if(vel.magnitude() <= 25.0f) yaw = bnc.lastyaw; - else - { - vectoyawpitch(vel, yaw, pitch); - yaw += 90; - bnc.lastyaw = yaw; - } - pitch = -bnc.roll; - if(bnc.bouncetype==BNC_GRENADE) - rendermodel(&bnc.light, "projectiles/grenade", ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST|MDL_DYNSHADOW); - else - { - const char *mdl = NULL; - int cull = MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED; - float fade = 1; - if(bnc.lifetime < 250) fade = bnc.lifetime/250.0f; - rendermodel(&bnc.light, mdl, ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, cull, NULL, NULL, 0, 0, fade); - } - } - } - - void renderprojectiles() - { - float yaw, pitch; - loopv(projs) - { - projectile &p = projs[i]; - if(p.gun!=GUN_RL) continue; - float dist = min(p.o.dist(p.to)/32.0f, 1.0f); - vec pos = vec(p.o).add(vec(p.offset).mul(dist*p.offsetmillis/float(OFFSETMILLIS))), - v = dist < 1e-6f ? p.dir : vec(p.to).sub(pos).normalize(); - // the amount of distance in front of the smoke trail needs to change if the model does - vectoyawpitch(v, yaw, pitch); - yaw += 90; - v.mul(3); - v.add(pos); - rendermodel(&p.light, "projectiles/rocket", ANIM_MAPMODEL|ANIM_LOOP, v, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST); - } - } - - void checkattacksound(fpsent *d, bool local) - { - int gun = -1; - switch(d->attacksound) - { - case S_CHAINSAW_ATTACK: - if(chainsawhudgun) gun = GUN_FIST; - break; - default: - return; - } - if(gun >= 0 && gun < NUMGUNS && - d->clientnum >= 0 && d->state == CS_ALIVE && - d->lastattackgun == gun && lastmillis - d->lastaction < guns[gun].attackdelay + 50) - { - d->attackchan = playsound(d->attacksound, local ? NULL : &d->o, NULL, 0, -1, -1, d->attackchan); - if(d->attackchan < 0) d->attacksound = -1; - } - else d->stopattacksound(); - } - - void checkidlesound(fpsent *d, bool local) - { - int sound = -1, radius = 0; - if(d->clientnum >= 0 && d->state == CS_ALIVE) switch(d->gunselect) - { - case GUN_FIST: - if(chainsawhudgun && d->attacksound < 0) - { - sound = S_CHAINSAW_IDLE; - radius = 50; - } - break; - } - if(d->idlesound != sound) - { - if(d->idlesound >= 0) d->stopidlesound(); - if(sound >= 0) - { - d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, 100, d->idlechan, radius); - if(d->idlechan >= 0) d->idlesound = sound; - } - } - else if(sound >= 0) - { - d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, -1, d->idlechan, radius); - if(d->idlechan < 0) d->idlesound = -1; - } - } - - void removeweapons(fpsent *d) - { - removebouncers(d); - removeprojectiles(d); - } - - void updateweapons(int curtime) - { - updateprojectiles(curtime); - pwcalcaccuracy(); - if(player1->clientnum>=0 && player1->state==CS_ALIVE) shoot(player1, worldpos); // only shoot when connected to server - updatebouncers(curtime); // need to do this after the player shoots so grenades don't end up inside player's BB next frame - fpsent *following = followingplayer(); - if(!following) following = player1; - loopv(players) - { - fpsent *d = players[i]; - checkattacksound(d, d==following); - checkidlesound(d, d==following); - } - } - - void avoidweapons(ai::avoidset &obstacles, float radius) - { - loopv(projs) - { - projectile &p = projs[i]; - obstacles.avoidnear(NULL, p.o.z + guns[p.gun].exprad + 1, p.o, radius + guns[p.gun].exprad); - } - loopv(bouncers) - { - bouncer &bnc = *bouncers[i]; - if(bnc.bouncetype != BNC_GRENADE) continue; - obstacles.avoidnear(NULL, bnc.o.z + guns[GUN_GL].exprad + 1, bnc.o, radius + guns[GUN_GL].exprad); - } - } + static const int OFFSETMILLIS = 500; + vec rays[MAXRAYS]; + + struct hitmsg + { + int target, lifesequence, info1, info2; + ivec dir; + }; + vector hits; + + ICOMMAND(getweapon, "", (), intret(player1->gunselect)); + + void gunselect(int gun, fpsent *d) + { + if(gun!=d->gunselect) + { + addmsg(N_GUNSELECT, "rci", d, gun); + playsound(S_WEAPLOAD, d == player1 ? NULL : &d->o); + } + d->gunselect = gun; + } + + void nextweapon(int dir, bool force = false) + { + if(player1->state!=CS_ALIVE) return; + dir = (dir < 0 ? NUMGUNS-1 : 1); + int gun = player1->gunselect; + loopi(NUMGUNS) + { + gun = (gun + dir)%NUMGUNS; + if(force || player1->ammo[gun]) break; + } + if(gun != player1->gunselect) gunselect(gun, player1); + else playsound(S_NOAMMO); + } + ICOMMAND(nextweapon, "ii", (int *dir, int *force), nextweapon(*dir, *force!=0)); + + int getweapon(const char *name) + { + const char *abbrevs[] = { "FI", "SG", "CG", "RL", "RI", "GL", "PI" }; + if(isdigit(name[0])) return parseint(name); + else loopi(sizeof(abbrevs)/sizeof(abbrevs[0])) if(!strcasecmp(abbrevs[i], name)) return i; + return -1; + } + + void setweapon(const char *name, bool force = false) + { + int gun = getweapon(name); + if(player1->state!=CS_ALIVE || gunGUN_PISTOL) return; + if(force || player1->ammo[gun]) gunselect(gun, player1); + else playsound(S_NOAMMO); + } + ICOMMAND(setweapon, "si", (char *name, int *force), setweapon(name, *force!=0)); + + void cycleweapon(int numguns, int *guns, bool force = false) + { + if(numguns<=0 || player1->state!=CS_ALIVE) return; + int offset = 0; + loopi(numguns) if(guns[i] == player1->gunselect) { offset = i+1; break; } + loopi(numguns) + { + int gun = guns[(i+offset)%numguns]; + if(gun>=0 && gunammo[gun])) + { + gunselect(gun, player1); + return; + } + } + playsound(S_NOAMMO); + } + ICOMMAND(cycleweapon, "V", (tagval *args, int numargs), + { + int numguns = min(numargs, 7); + int guns[7]; + loopi(numguns) guns[i] = getweapon(args[i].getstr()); + cycleweapon(numguns, guns); + }); + + void weaponswitch(fpsent *d) + { + if(d->state!=CS_ALIVE) return; + int s = d->gunselect; + if (s!=GUN_CG && d->ammo[GUN_CG]) s = GUN_CG; + else if(s!=GUN_RL && d->ammo[GUN_RL]) s = GUN_RL; + else if(s!=GUN_SG && d->ammo[GUN_SG]) s = GUN_SG; + else if(s!=GUN_RIFLE && d->ammo[GUN_RIFLE]) s = GUN_RIFLE; + else if(s!=GUN_GL && d->ammo[GUN_GL]) s = GUN_GL; + else if(s!=GUN_PISTOL && d->ammo[GUN_PISTOL]) s = GUN_PISTOL; + else s = GUN_FIST; + + gunselect(s, d); + } + + ICOMMAND(weapon, "V", (tagval *args, int numargs), + { + if(player1->state!=CS_ALIVE) return; + loopi(7) + { + const char *name = i < numargs ? args[i].getstr() : ""; + if(name[0]) + { + int gun = getweapon(name); + if(gun >= GUN_FIST && gun <= GUN_PISTOL && gun != player1->gunselect && player1->ammo[gun]) { gunselect(gun, player1); return; } + } else { weaponswitch(player1); return; } + } + playsound(S_NOAMMO); + }); + + void offsetray(const vec &from, const vec &to, int spread, float range, vec &dest) + { + vec offset; + do offset = vec(rndscale(1), rndscale(1), rndscale(1)).sub(0.5f); + while(offset.squaredlen() > 0.5f*0.5f); + offset.mul((to.dist(from)/1024)*spread); + offset.z /= 2; + dest = vec(offset).add(to); + if(dest != from) + { + vec dir = vec(dest).sub(from).normalize(); + raycubepos(from, dir, dest, range, RAY_CLIPMAT|RAY_ALPHAPOLY); + } + } + + void createrays(int gun, const vec &from, const vec &to) // create random spread of rays + { + loopi(guns[gun].rays) offsetray(from, to, guns[gun].spread, guns[gun].range, rays[i]); + } + + enum { BNC_GRENADE }; + + struct bouncer : physent + { + int lifetime, bounces; + float lastyaw, roll; + bool local; + fpsent *owner; + int bouncetype, variant; + vec offset; + int offsetmillis; + float offsetheight; + int id; + entitylight light; + + bouncer() : bounces(0), roll(0), variant(0) + { + type = ENT_BOUNCE; + } + + vec offsetpos() + { + vec pos(o); + if(offsetmillis > 0) + { + pos.add(vec(offset).mul(offsetmillis/float(OFFSETMILLIS))); + if(offsetheight >= 0) pos.z = max(pos.z, o.z - max(offsetheight - eyeheight, 0.0f)); + } + return pos; + } + + void limitoffset() + { + if(bouncetype == BNC_GRENADE && offsetmillis > 0 && offset.z < 0) + offsetheight = raycube(vec(o.x + offset.x, o.y + offset.y, o.z), vec(0, 0, -1), -offset.z); + else offsetheight = -1; + } + }; + + vector bouncers; + + vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d); + + void newbouncer(const vec &from, const vec &to, bool local, int id, fpsent *owner, int type, int lifetime, int speed, entitylight *light = NULL) + { + bouncer &bnc = *bouncers.add(new bouncer); + bnc.o = from; + bnc.radius = bnc.xradius = bnc.yradius = 1.5f; + bnc.eyeheight = bnc.radius; + bnc.aboveeye = bnc.radius; + bnc.lifetime = lifetime; + bnc.local = local; + bnc.owner = owner; + bnc.bouncetype = type; + bnc.id = local ? lastmillis : id; + if(light) bnc.light = *light; + + bnc.collidetype = COLLIDE_ELLIPSE_PRECISE; + + vec dir(to); + dir.sub(from).safenormalize(); + bnc.vel = dir; + bnc.vel.mul(speed); + + avoidcollision(&bnc, dir, owner, 0.1f); + + if(type==BNC_GRENADE) + { + bnc.offset = hudgunorigin(GUN_GL, from, to, owner); + if(owner==followingplayer(player1) && !isthirdperson()) bnc.offset.sub(owner->o).rescale(16).add(owner->o); + } + else bnc.offset = from; + bnc.offset.sub(bnc.o); + bnc.offsetmillis = OFFSETMILLIS; + bnc.limitoffset(); + + bnc.resetinterp(); + } + + void bounced(physent *d, const vec &surface) + { + if(d->type != ENT_BOUNCE) return; + bouncer *b = (bouncer *)d; + if(b->bounces >= 2) return; + b->bounces++; + adddecal(DECAL_BLOOD, vec(b->o).sub(vec(surface).mul(b->radius)), surface, 2.96f/b->bounces, bvec(0x60, 0xFF, 0xFF), rnd(4)); + } + + void updatebouncers(int time) + { + loopv(bouncers) + { + bouncer &bnc = *bouncers[i]; + if(bnc.bouncetype==BNC_GRENADE && bnc.vel.magnitude() > 50.0f) + { + vec pos = bnc.offsetpos(); + regular_particle_splash(PART_SMOKE, 1, 150, pos, 0x404040, 2.4f, 50, -20); + } + vec old(bnc.o); + bool stopped = false; + if(bnc.bouncetype==BNC_GRENADE) stopped = bounce(&bnc, 0.6f, 0.5f, 0.8f) || (bnc.lifetime -= time)<0; + + if(stopped) + { + if(bnc.bouncetype==BNC_GRENADE) + { + int qdam = guns[GUN_GL].damage*(bnc.owner->quadmillis ? 4 : 1); + hits.setsize(0); + explode(bnc.local, bnc.owner, bnc.o, NULL, qdam, GUN_GL); + adddecal(DECAL_SCORCH, bnc.o, vec(0, 0, 1), guns[GUN_GL].exprad/2); + if(bnc.local) + addmsg(N_EXPLODE, "rci3iv", bnc.owner, lastmillis-maptime, GUN_GL, bnc.id-maptime, + hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); + } + delete bouncers.remove(i--); + } + else + { + bnc.roll += old.sub(bnc.o).magnitude()/(4*RAD); + bnc.offsetmillis = max(bnc.offsetmillis-time, 0); + bnc.limitoffset(); + } + } + } + + void removebouncers(fpsent *owner) + { + loopv(bouncers) if(bouncers[i]->owner==owner) { delete bouncers[i]; bouncers.remove(i--); } + } + + void clearbouncers() { bouncers.deletecontents(); } + + struct projectile + { + vec dir, o, to, offset; + float speed; + fpsent *owner; + int gun; + bool local; + int offsetmillis; + int id; + entitylight light; + }; + vector projs; + + void clearprojectiles() { projs.shrink(0); } + + void newprojectile(const vec &from, const vec &to, float speed, bool local, int id, fpsent *owner, int gun) + { + projectile &p = projs.add(); + p.dir = vec(to).sub(from).safenormalize(); + p.o = from; + p.to = to; + p.offset = hudgunorigin(gun, from, to, owner); + p.offset.sub(from); + p.speed = speed; + p.local = local; + p.owner = owner; + p.gun = gun; + p.offsetmillis = OFFSETMILLIS; + p.id = local ? lastmillis : id; + } + + void removeprojectiles(fpsent *owner) + { + // can't use loopv here due to strange GCC optimizer bug + int len = projs.length(); + loopi(len) if(projs[i].owner==owner) { projs.remove(i--); len--; } + } + + VARP(blood, 0, 1, 1); + + void damageeffect(int damage, fpsent *d, bool thirdperson) + { + vec p = d->o; + p.z += 0.6f*(d->eyeheight + d->aboveeye) - d->eyeheight; + if(blood) particle_splash(PART_BLOOD, damage/10, 1000, p, 0x60FFFF, 2.96f); + if(thirdperson) + { + defformatstring(ds, "%d", damage); + particle_textcopy(d->abovehead(), ds, PART_TEXT, 2000, 0xFF4B19, 4.0f, -8); + } + } + + void spawnbouncer(const vec &p, const vec &vel, fpsent *d, int type, entitylight *light = NULL) + { + vec to(rnd(100)-50, rnd(100)-50, rnd(100)-50); + if(to.iszero()) to.z += 1; + to.normalize(); + to.add(p); + newbouncer(p, to, true, 0, d, type, rnd(1000)+1000, rnd(100)+20, light); + } + + void hit(int damage, dynent *d, fpsent *at, const vec &vel, int gun, float info1, int info2 = 1) + { + if(at==player1 && d!=at) + { + extern int hitsound; + if(hitsound && lasthit != lastmillis) playsound(S_HIT); + lasthit = lastmillis; + } + + fpsent *f = (fpsent *)d; + + f->lastpain = lastmillis; + if(at->type==ENT_PLAYER && !isteam(at->team, f->team)) at->totaldamage += damage; + + if(f->type==ENT_AI || !m_mp(gamemode) || f==at) f->hitpush(damage, vel, at, gun); + + pwhit(gun, damage); + + if(!m_mp(gamemode)) damaged(damage, f, at); + else + { + hitmsg &h = hits.add(); + h.target = f->clientnum; + h.lifesequence = f->lifesequence; + h.info1 = int(info1*DMF); + h.info2 = info2; + h.dir = f==at ? ivec(0, 0, 0) : ivec(vec(vel).mul(DNF)); + if(at==player1) + { + damageeffect(damage, f); + if(f==player1) + { + damagecompass(damage, at ? at->o : f->o); + playsound(S_PAIN6); + } + else playsound(S_PAIN1+rnd(5), &f->o); + } + } + } + + void hitpush(int damage, dynent *d, fpsent *at, vec &from, vec &to, int gun, int rays) + { + hit(damage, d, at, vec(to).sub(from).safenormalize(), gun, from.dist(to), rays); + } + + float projdist(dynent *o, vec &dir, const vec &v) + { + vec middle = o->o; + middle.z += (o->aboveeye-o->eyeheight)/2; + float dist = middle.dist(v, dir); + dir.div(dist); + if(dist<0) dist = 0; + return dist; + } + + void radialeffect(dynent *o, const vec &v, int qdam, fpsent *at, int gun) + { + if(o->state!=CS_ALIVE) return; + vec dir; + float dist = projdist(o, dir, v); + if(disto.reject(v, o->radius + guns[gun].exprad) || o==safe) continue; + radialeffect(o, v, damage, owner, gun); + } + } + + void projsplash(projectile &p, vec &v, dynent *safe, int damage) + { + if(guns[p.gun].part) + { + particle_splash(PART_SPARK, 100, 200, v, 0xB49B4B, 0.24f); + playsound(S_FEXPLODE, &v); + // no push? + } + else + { + explode(p.local, p.owner, v, safe, damage, GUN_RL); + adddecal(DECAL_SCORCH, v, vec(p.dir).neg(), guns[p.gun].exprad/2); + } + } + + void explodeeffects(int gun, fpsent *d, bool local, int id) + { + if(local) return; + switch(gun) + { + case GUN_RL: + loopv(projs) + { + projectile &p = projs[i]; + if(p.gun == gun && p.owner == d && p.id == id && !p.local) + { + vec pos(p.o); + pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS))); + explode(p.local, p.owner, pos, NULL, 0, GUN_RL); + adddecal(DECAL_SCORCH, pos, vec(p.dir).neg(), guns[gun].exprad/2); + projs.remove(i); + break; + } + } + break; + case GUN_GL: + loopv(bouncers) + { + bouncer &b = *bouncers[i]; + if(b.bouncetype == BNC_GRENADE && b.owner == d && b.id == id && !b.local) + { + vec pos = b.offsetpos(); + explode(b.local, b.owner, pos, NULL, 0, GUN_GL); + adddecal(DECAL_SCORCH, pos, vec(0, 0, 1), guns[gun].exprad/2); + delete bouncers.remove(i); + break; + } + } + break; + default: + break; + } + } + + bool projdamage(dynent *o, projectile &p, vec &v, int qdam) + { + if(o->state!=CS_ALIVE) return false; + if(!intersect(o, p.o, v)) return false; + projsplash(p, v, o, qdam); + vec dir; + projdist(o, dir, v); + hit(qdam, o, p.owner, dir, p.gun, 0); + return true; + } + + void updateprojectiles(int time) + { + loopv(projs) + { + projectile &p = projs[i]; + p.offsetmillis = max(p.offsetmillis-time, 0); + int qdam = guns[p.gun].damage*(p.owner->quadmillis ? 4 : 1); + vec dv; + float dist = p.to.dist(p.o, dv); + dv.mul(time/max(dist*1000/p.speed, float(time))); + vec v = vec(p.o).add(dv); + bool exploded = false; + hits.setsize(0); + if(p.local) + { + vec halfdv = vec(dv).mul(0.5f), bo = vec(p.o).add(halfdv); + float br = max(fabs(halfdv.x), fabs(halfdv.y)) + 1; + loopj(numdynents()) + { + dynent *o = iterdynents(j); + if(p.owner==o || o->o.reject(bo, o->radius + br)) continue; + if(projdamage(o, p, v, qdam)) { exploded = true; break; } + } + } + if(!exploded) + { + if(dist<4) + { + if(p.o!=p.to) // if original target was moving, reevaluate endpoint + { + if(raycubepos(p.o, p.dir, p.to, 0, RAY_CLIPMAT|RAY_ALPHAPOLY)>=4) continue; + } + projsplash(p, v, NULL, qdam); + exploded = true; + } + else + { + vec pos(v); + pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS))); + regular_particle_splash(PART_SMOKE, 2, 300, pos, 0x404040, 2.4f, 50, -20); + } + } + if(exploded) + { + if(p.local) + addmsg(N_EXPLODE, "rci3iv", p.owner, lastmillis-maptime, p.gun, p.id-maptime, + hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); + projs.remove(i--); + } + else p.o = v; + } + } + + extern int chainsawhudgun; + + VARP(muzzleflash, 0, 1, 1); + VARP(muzzlelight, 0, 1, 1); + + void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int id, int prevaction) // create visual effect from a shot + { + int sound = guns[gun].sound, pspeed = 25; + switch(gun) + { + case GUN_FIST: + if(d->type==ENT_PLAYER && chainsawhudgun) sound = S_CHAINSAW_ATTACK; + break; + + case GUN_SG: + { + if(!local) createrays(gun, from, to); + if(muzzleflash && d->muzzle.x >= 0) + particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH3, 0xFFFFFF, 2.75f, d); + loopi(guns[gun].rays) + { + if(d->quadmillis) + particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, rays[i], d), rays[i], 0x802010, 0.6f, 36); + particle_splash(PART_SPARK, 20, 250, rays[i], 0xB49B4B, 0.24f); + particle_flare(hudgunorigin(gun, from, rays[i], d), rays[i], 300, PART_STREAK, 0xFFC864, 0.28f); + if(!local) adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f); + } + if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 30, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d); + break; + } + + case GUN_CG: + case GUN_PISTOL: + { + particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f); + if(d->quadmillis) + particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36); + particle_flare(hudgunorigin(gun, from, to, d), to, 600, PART_STREAK, 0xFFC864, 0.28f); + if(muzzleflash && d->muzzle.x >= 0) + particle_flare(d->muzzle, d->muzzle, gun==GUN_CG ? 100 : 200, PART_MUZZLE_FLASH1, 0xFFFFFF, gun==GUN_CG ? 2.25f : 1.25f, d); + if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 2.0f); + if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), gun==GUN_CG ? 30 : 15, vec(0.5f, 0.375f, 0.25f), gun==GUN_CG ? 50 : 100, gun==GUN_CG ? 50 : 100, DL_FLASH, 0, vec(0, 0, 0), d); + break; + } + + case GUN_RL: + if(d->quadmillis) + particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36); + if(muzzleflash && d->muzzle.x >= 0) + particle_flare(d->muzzle, d->muzzle, 250, PART_MUZZLE_FLASH2, 0xFFFFFF, 3.0f, d); + pspeed = guns[gun].projspeed; + newprojectile(from, to, (float)pspeed, local, id, d, gun); + break; + + case GUN_GL: + { + float dist = from.dist(to); + vec up = to; + up.z += dist/8; + if(muzzleflash && d->muzzle.x >= 0) + particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH2, 0xFFFFFF, 1.5f, d); + if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 20, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d); + newbouncer(from, up, local, id, d, BNC_GRENADE, guns[gun].ttl, guns[gun].projspeed); + break; + } + + case GUN_RIFLE: + particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f); + if(d->quadmillis) + particle_trail(PART_FLAME, 400, hudgunorigin(gun, from, to, d), to, 0x802010, 0.6f, 36); + particle_trail(PART_SMOKE, 500, hudgunorigin(gun, from, to, d), to, 0x404040, 0.6f, 18); + if(muzzleflash && d->muzzle.x >= 0) + particle_flare(d->muzzle, d->muzzle, 150, PART_MUZZLE_FLASH3, 0xFFFFFF, 1.25f, d); + if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), 3.0f); + if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 25, vec(0.5f, 0.375f, 0.25f), 75, 75, DL_FLASH, 0, vec(0, 0, 0), d); + break; + } + + bool looped = false; + if(d->attacksound >= 0 && d->attacksound != sound) d->stopattacksound(); + if(d->idlesound >= 0) d->stopidlesound(); + fpsent *h = followingplayer(player1); + switch(sound) + { + case S_CHAINSAW_ATTACK: + if(d->attacksound >= 0) looped = true; + d->attacksound = sound; + d->attackchan = playsound(sound, d==h ? NULL : &d->o, NULL, 0, -1, 100, d->attackchan); + break; + default: + playsound(sound, d==h ? NULL : &d->o); + break; + } + if(d->quadmillis && lastmillis-prevaction>200 && !looped) playsound(S_ITEMPUP, d==h ? NULL : &d->o); + } + + void particletrack(physent *owner, vec &o, vec &d) + { + if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return; + fpsent *pl = (fpsent *)owner; + if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return; + float dist = o.dist(d); + o = pl->muzzle; + if(dist <= 0) d = o; + else + { + vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d); + float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY); + d.mul(min(newdist, dist)).add(owner->o); + } + } + + void dynlighttrack(physent *owner, vec &o, vec &hud) + { + if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return; + fpsent *pl = (fpsent *)owner; + if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return; + o = pl->muzzle; + hud = owner == followingplayer(player1) ? vec(pl->o).add(vec(0, 0, 2)) : pl->muzzle; + } + + float intersectdist = 1e16f; + + bool intersect(dynent *d, const vec &from, const vec &to, float &dist) // if lineseg hits entity bounding box + { + vec bottom(d->o), top(d->o); + bottom.z -= d->eyeheight; + top.z += d->aboveeye; + return linecylinderintersect(from, to, bottom, top, d->radius, dist); + } + + dynent *intersectclosest(const vec &from, const vec &to, fpsent *at, float &bestdist) + { + dynent *best = NULL; + bestdist = 1e16f; + loopi(numdynents()) + { + dynent *o = iterdynents(i); + if(o==at || o->state!=CS_ALIVE) continue; + float dist; + if(!intersect(o, from, to, dist)) continue; + if(distgunselect].damage; + if(d->quadmillis) qdam *= 4; + dynent *o; + float dist; + if(guns[d->gunselect].rays > 1) + { + dynent *hits[MAXRAYS]; + int maxrays = guns[d->gunselect].rays; + loopi(maxrays) + { + if((hits[i] = intersectclosest(from, rays[i], d, dist))) shorten(from, rays[i], dist); + else adddecal(DECAL_BULLET, rays[i], vec(from).sub(rays[i]).safenormalize(), 2.0f); + } + loopi(maxrays) if(hits[i]) + { + o = hits[i]; + hits[i] = NULL; + int numhits = 1; + for(int j = i+1; j < maxrays; j++) if(hits[j] == o) + { + hits[j] = NULL; + numhits++; + } + hitpush(numhits*qdam, o, d, from, to, d->gunselect, numhits); + } + } + else if((o = intersectclosest(from, to, d, dist))) + { + shorten(from, to, dist); + hitpush(qdam, o, d, from, to, d->gunselect, 1); + } + else if(d->gunselect!=GUN_FIST) adddecal(DECAL_BULLET, to, vec(from).sub(to).safenormalize(), d->gunselect==GUN_RIFLE ? 3.0f : 2.0f); + } + + void shoot(fpsent *d, const vec &targ) + { + int prevaction = d->lastaction, attacktime = lastmillis-prevaction; + if(attacktimegunwait) return; + d->gunwait = 0; + if((d==player1 || d->ai) && !d->attacking) return; + d->lastaction = lastmillis; + d->lastattackgun = d->gunselect; + if(!d->ammo[d->gunselect]) + { + if(d==player1) + { + msgsound(S_NOAMMO, d); + d->gunwait = 600; + d->lastattackgun = -1; + weaponswitch(d); + } + return; + } + if(d->gunselect) d->ammo[d->gunselect]--; + + pwshot(d->gunselect); /// PW + + vec from = d->o, to = targ, dir = vec(to).sub(from).safenormalize(); + float dist = to.dist(from); + vec kickback = vec(dir).mul(guns[d->gunselect].kickamount*-2.5f); + d->vel.add(kickback); + float shorten = 0; + if(guns[d->gunselect].range && dist > guns[d->gunselect].range) + shorten = guns[d->gunselect].range; + float barrier = raycube(d->o, dir, dist, RAY_CLIPMAT|RAY_ALPHAPOLY); + if(barrier > 0 && barrier < dist && (!shorten || barrier < shorten)) + shorten = barrier; + if(shorten) to = vec(dir).mul(shorten).add(from); + + if(guns[d->gunselect].rays > 1) createrays(d->gunselect, from, to); + else if(guns[d->gunselect].spread) offsetray(from, to, guns[d->gunselect].spread, guns[d->gunselect].range, to); + + hits.setsize(0); + + if(!guns[d->gunselect].projspeed) raydamage(from, to, d); + + shoteffects(d->gunselect, from, to, d, true, 0, prevaction); + + if(d==player1 || d->ai) + { + addmsg(N_SHOOT, "rci2i6iv", d, lastmillis-maptime, d->gunselect, + (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), + (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), + hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); + } + + d->gunwait = guns[d->gunselect].attackdelay; + if(d->gunselect == GUN_PISTOL && d->ai) d->gunwait += int(d->gunwait*(((101-d->skill)+rnd(111-d->skill))/100.f)); + d->totalshots += guns[d->gunselect].damage*(d->quadmillis ? 4 : 1)*guns[d->gunselect].rays; + } + + void adddynlights() + { + loopv(projs) + { + projectile &p = projs[i]; + if(p.gun!=GUN_RL) continue; + vec pos(p.o); + pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS))); + adddynlight(pos, 20, vec(1, 0.75f, 0.5f)); + } + loopv(bouncers) + { + bouncer &bnc = *bouncers[i]; + if(bnc.bouncetype!=BNC_GRENADE) continue; + vec pos = bnc.offsetpos(); + adddynlight(pos, 8, vec(0.25f, 1, 1)); + } + } + + static const char * const projnames[2] = { "projectiles/grenade", "projectiles/rocket" }; + + void preloadbouncers() { + loopi(sizeof(projnames)/sizeof(projnames[0])) preloadmodel(projnames[i]); + } + + void renderbouncers() + { + float yaw, pitch; + loopv(bouncers) + { + bouncer &bnc = *bouncers[i]; + vec pos = bnc.offsetpos(); + vec vel(bnc.vel); + if(vel.magnitude() <= 25.0f) yaw = bnc.lastyaw; + else + { + vectoyawpitch(vel, yaw, pitch); + yaw += 90; + bnc.lastyaw = yaw; + } + pitch = -bnc.roll; + if(bnc.bouncetype==BNC_GRENADE) + rendermodel(&bnc.light, "projectiles/grenade", ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST|MDL_DYNSHADOW); + else + { + const char *mdl = NULL; + int cull = MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED; + float fade = 1; + if(bnc.lifetime < 250) fade = bnc.lifetime/250.0f; + rendermodel(&bnc.light, mdl, ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, cull, NULL, NULL, 0, 0, fade); + } + } + } + + void renderprojectiles() + { + float yaw, pitch; + loopv(projs) + { + projectile &p = projs[i]; + if(p.gun!=GUN_RL) continue; + float dist = min(p.o.dist(p.to)/32.0f, 1.0f); + vec pos = vec(p.o).add(vec(p.offset).mul(dist*p.offsetmillis/float(OFFSETMILLIS))), + v = dist < 1e-6f ? p.dir : vec(p.to).sub(pos).normalize(); + // the amount of distance in front of the smoke trail needs to change if the model does + vectoyawpitch(v, yaw, pitch); + yaw += 90; + v.mul(3); + v.add(pos); + rendermodel(&p.light, "projectiles/rocket", ANIM_MAPMODEL|ANIM_LOOP, v, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT|MDL_LIGHT_FAST); + } + } + + void checkattacksound(fpsent *d, bool local) + { + int gun = -1; + switch(d->attacksound) + { + case S_CHAINSAW_ATTACK: + if(chainsawhudgun) gun = GUN_FIST; + break; + default: + return; + } + if(gun >= 0 && gun < NUMGUNS && + d->clientnum >= 0 && d->state == CS_ALIVE && + d->lastattackgun == gun && lastmillis - d->lastaction < guns[gun].attackdelay + 50) + { + d->attackchan = playsound(d->attacksound, local ? NULL : &d->o, NULL, 0, -1, -1, d->attackchan); + if(d->attackchan < 0) d->attacksound = -1; + } + else d->stopattacksound(); + } + + void checkidlesound(fpsent *d, bool local) + { + int sound = -1, radius = 0; + if(d->clientnum >= 0 && d->state == CS_ALIVE) switch(d->gunselect) + { + case GUN_FIST: + if(chainsawhudgun && d->attacksound < 0) + { + sound = S_CHAINSAW_IDLE; + radius = 50; + } + break; + } + if(d->idlesound != sound) + { + if(d->idlesound >= 0) d->stopidlesound(); + if(sound >= 0) + { + d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, 100, d->idlechan, radius); + if(d->idlechan >= 0) d->idlesound = sound; + } + } + else if(sound >= 0) + { + d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, 0, -1, -1, d->idlechan, radius); + if(d->idlechan < 0) d->idlesound = -1; + } + } + + void removeweapons(fpsent *d) + { + removebouncers(d); + removeprojectiles(d); + } + + void updateweapons(int curtime) + { + updateprojectiles(curtime); + pwcalcaccuracy(); + if(player1->clientnum>=0 && player1->state==CS_ALIVE) shoot(player1, worldpos); // only shoot when connected to server + updatebouncers(curtime); // need to do this after the player shoots so grenades don't end up inside player's BB next frame + fpsent *following = followingplayer(); + if(!following) following = player1; + loopv(players) + { + fpsent *d = players[i]; + checkattacksound(d, d==following); + checkidlesound(d, d==following); + } + } + + void avoidweapons(ai::avoidset &obstacles, float radius) + { + loopv(projs) + { + projectile &p = projs[i]; + obstacles.avoidnear(NULL, p.o.z + guns[p.gun].exprad + 1, p.o, radius + guns[p.gun].exprad); + } + loopv(bouncers) + { + bouncer &bnc = *bouncers[i]; + if(bnc.bouncetype != BNC_GRENADE) continue; + obstacles.avoidnear(NULL, bnc.o.z + guns[GUN_GL].exprad + 1, bnc.o, radius + guns[GUN_GL].exprad); + } + } }; /// Rough accuracy code, client-side only. int pwshotsfired [NUMGUNS] = { 0 }; -int pwshotshit [NUMGUNS] = { 0 }; +int pwshotshit [NUMGUNS] = { 0 }; int pwdamagedealt [NUMGUNS] = { 0 }; -int pwaccuracy [NUMGUNS] = { 0 }; +int pwaccuracy [NUMGUNS] = { 0 }; int pwavgaccuracy = 0; void pwshot(int gun) { - pwshotsfired[gun]++; + pwshotsfired[gun]++; } void pwhit(int gun, int damage) { - pwshotshit[gun]++; - pwdamagedealt[gun] += damage; + pwshotshit[gun]++; + pwdamagedealt[gun] += damage; } void pwcalcaccuracy(void) { - pwavgaccuracy = 0; - loopi(NUMGUNS) - if(pwshotsfired[i]) - pwaccuracy[i] = (pwdamagedealt[i] * 100) / (guns[i].damage * guns[i].rays * pwshotsfired[i]); - else - pwaccuracy[i] = 0; - loopi(NUMGUNS) pwavgaccuracy += pwaccuracy[i]; - pwavgaccuracy /= NUMGUNS; + pwavgaccuracy = 0; + loopi(NUMGUNS) + if(pwshotsfired[i]) + pwaccuracy[i] = (pwdamagedealt[i] * 100) / (guns[i].damage * guns[i].rays * pwshotsfired[i]); + else + pwaccuracy[i] = 0; + loopi(NUMGUNS) pwavgaccuracy += pwaccuracy[i]; + pwavgaccuracy /= NUMGUNS; } void pwreset(void) { - loopi(NUMGUNS) pwshotsfired[i] = pwshotshit[i] = pwdamagedealt[i] = pwaccuracy[i] = 0; - loopi(7) pwitemspicked[i] = 0; - pwavgaccuracy = 0; + loopi(NUMGUNS) pwshotsfired[i] = pwshotshit[i] = pwdamagedealt[i] = pwaccuracy[i] = 0; + loopi(7) pwitemspicked[i] = 0; + pwavgaccuracy = 0; } diff --git a/src/shared/command.h b/src/shared/command.h index c07dee8..ffb2115 100644 --- a/src/shared/command.h +++ b/src/shared/command.h @@ -4,38 +4,38 @@ enum { VAL_NULL = 0, VAL_INT, VAL_FLOAT, VAL_STR, VAL_ANY, VAL_CODE, VAL_MACRO, enum { - CODE_START = 0, - CODE_OFFSET, - CODE_POP, - CODE_ENTER, - CODE_EXIT, - CODE_VAL, - CODE_VALI, - CODE_MACRO, - CODE_BOOL, - CODE_BLOCK, - CODE_COMPILE, - CODE_FORCE, - CODE_RESULT, - CODE_IDENT, CODE_IDENTU, CODE_IDENTARG, - CODE_COM, CODE_COMD, CODE_COMC, CODE_COMV, - CODE_CONC, CODE_CONCW, CODE_CONCM, CODE_DOWN, - CODE_SVAR, CODE_SVAR1, - CODE_IVAR, CODE_IVAR1, CODE_IVAR2, CODE_IVAR3, - CODE_FVAR, CODE_FVAR1, - CODE_LOOKUP, CODE_LOOKUPU, CODE_LOOKUPARG, CODE_ALIAS, CODE_ALIASU, CODE_ALIASARG, CODE_CALL, CODE_CALLU, CODE_CALLARG, - CODE_PRINT, - CODE_LOCAL, - - CODE_OP_MASK = 0x3F, - CODE_RET = 6, - CODE_RET_MASK = 0xC0, - - /* return type flags */ - RET_NULL = VAL_NULL< x ? IDF_READONLY : 0)), name(n), minval(m), maxval(x), fun((identfun)f) - { storage.i = s; } - // ID_FVAR - ident(int t, const char *n, float m, float x, float *s, void *f = NULL, int flags = 0) - : type(t), flags(flags | (m > x ? IDF_READONLY : 0)), name(n), minvalf(m), maxvalf(x), fun((identfun)f) - { storage.f = s; } - // ID_SVAR - ident(int t, const char *n, char **s, void *f = NULL, int flags = 0) - : type(t), flags(flags), name(n), fun((identfun)f) - { storage.s = s; } - // ID_ALIAS - ident(int t, const char *n, char *a, int flags) - : type(t), valtype(VAL_STR), flags(flags), name(n), code(NULL), stack(NULL) - { val.s = a; } - ident(int t, const char *n, int a, int flags) - : type(t), valtype(VAL_INT), flags(flags), name(n), code(NULL), stack(NULL) - { val.i = a; } - ident(int t, const char *n, float a, int flags) - : type(t), valtype(VAL_FLOAT), flags(flags), name(n), code(NULL), stack(NULL) - { val.f = a; } - ident(int t, const char *n, int flags) - : type(t), valtype(VAL_NULL), flags(flags), name(n), code(NULL), stack(NULL) - {} - ident(int t, const char *n, const tagval &v, int flags) - : type(t), valtype(v.type), flags(flags), name(n), code(NULL), stack(NULL) - { val = v; } - // ID_COMMAND - ident(int t, const char *n, const char *args, uint argmask, int numargs, void *f = NULL, int flags = 0) - : type(t), numargs(numargs), flags(flags), name(n), args(args), argmask(argmask), fun((identfun)f) - {} - - void changed() { if(fun) fun(); } - - void setval(const tagval &v) - { - valtype = v.type; - val = v; - } - - void setval(const identstack &v) - { - valtype = v.valtype; - val = v.val; - } - - void forcenull() - { - if(valtype==VAL_STR) delete[] val.s; - valtype = VAL_NULL; - } - - float getfloat() const; - int getint() const; - const char *getstr() const; - void getval(tagval &v) const; + uchar type; // one of ID_* above + union + { + uchar valtype; // ID_ALIAS + uchar numargs; // ID_COMMAND + }; + ushort flags; + int index; + const char *name; + union + { + struct // ID_VAR, ID_FVAR, ID_SVAR + { + union + { + struct { int minval, maxval; }; // ID_VAR + struct { float minvalf, maxvalf; }; // ID_FVAR + }; + identvalptr storage; + identval overrideval; + }; + struct // ID_ALIAS + { + uint *code; + identval val; + identstack *stack; + }; + struct // ID_COMMAND + { + const char *args; + uint argmask; + }; + }; + identfun fun; // ID_VAR, ID_FVAR, ID_SVAR, ID_COMMAND + + ident() {} + // ID_VAR + ident(int t, const char *n, int m, int x, int *s, void *f = NULL, int flags = 0) + : type(t), flags(flags | (m > x ? IDF_READONLY : 0)), name(n), minval(m), maxval(x), fun((identfun)f) + { storage.i = s; } + // ID_FVAR + ident(int t, const char *n, float m, float x, float *s, void *f = NULL, int flags = 0) + : type(t), flags(flags | (m > x ? IDF_READONLY : 0)), name(n), minvalf(m), maxvalf(x), fun((identfun)f) + { storage.f = s; } + // ID_SVAR + ident(int t, const char *n, char **s, void *f = NULL, int flags = 0) + : type(t), flags(flags), name(n), fun((identfun)f) + { storage.s = s; } + // ID_ALIAS + ident(int t, const char *n, char *a, int flags) + : type(t), valtype(VAL_STR), flags(flags), name(n), code(NULL), stack(NULL) + { val.s = a; } + ident(int t, const char *n, int a, int flags) + : type(t), valtype(VAL_INT), flags(flags), name(n), code(NULL), stack(NULL) + { val.i = a; } + ident(int t, const char *n, float a, int flags) + : type(t), valtype(VAL_FLOAT), flags(flags), name(n), code(NULL), stack(NULL) + { val.f = a; } + ident(int t, const char *n, int flags) + : type(t), valtype(VAL_NULL), flags(flags), name(n), code(NULL), stack(NULL) + {} + ident(int t, const char *n, const tagval &v, int flags) + : type(t), valtype(v.type), flags(flags), name(n), code(NULL), stack(NULL) + { val = v; } + // ID_COMMAND + ident(int t, const char *n, const char *args, uint argmask, int numargs, void *f = NULL, int flags = 0) + : type(t), numargs(numargs), flags(flags), name(n), args(args), argmask(argmask), fun((identfun)f) + {} + + void changed() { if(fun) fun(); } + + void setval(const tagval &v) + { + valtype = v.type; + val = v; + } + + void setval(const identstack &v) + { + valtype = v.valtype; + val = v.val; + } + + void forcenull() + { + if(valtype==VAL_STR) delete[] val.s; + valtype = VAL_NULL; + } + + float getfloat() const; + int getint() const; + const char *getstr() const; + void getval(tagval &v) const; }; extern void addident(ident *id); @@ -202,15 +202,15 @@ extern void result(const char *s); static inline int parseint(const char *s) { - return int(strtoul(s, NULL, 0)); + return int(strtoul(s, NULL, 0)); } static inline float parsefloat(const char *s) { - // not all platforms (windows) can parse hexadecimal integers via strtod - char *end; - double val = strtod(s, &end); - return val || end==s || (*end!='x' && *end!='X') ? float(val) : float(parseint(s)); + // not all platforms (windows) can parse hexadecimal integers via strtod + char *end; + double val = strtod(s, &end); + return val || end==s || (*end!='x' && *end!='X') ? float(val) : float(parseint(s)); } static inline void intformat(char *buf, int v, int len = 20) { nformatstring(buf, len, "%d", v); } @@ -218,52 +218,52 @@ static inline void floatformat(char *buf, float v, int len = 20) { nformatstring static inline const char *getstr(const identval &v, int type) { - switch(type) - { - case VAL_STR: case VAL_MACRO: return v.s; - case VAL_INT: return intstr(v.i); - case VAL_FLOAT: return floatstr(v.f); - default: return ""; - } + switch(type) + { + case VAL_STR: case VAL_MACRO: return v.s; + case VAL_INT: return intstr(v.i); + case VAL_FLOAT: return floatstr(v.f); + default: return ""; + } } inline const char *tagval::getstr() const { return ::getstr(*this, type); } inline const char *ident::getstr() const { return ::getstr(val, valtype); } static inline int getint(const identval &v, int type) { - switch(type) - { - case VAL_INT: return v.i; - case VAL_FLOAT: return int(v.f); - case VAL_STR: case VAL_MACRO: return parseint(v.s); - default: return 0; - } + switch(type) + { + case VAL_INT: return v.i; + case VAL_FLOAT: return int(v.f); + case VAL_STR: case VAL_MACRO: return parseint(v.s); + default: return 0; + } } inline int tagval::getint() const { return ::getint(*this, type); } inline int ident::getint() const { return ::getint(val, valtype); } static inline float getfloat(const identval &v, int type) { - switch(type) - { - case VAL_FLOAT: return v.f; - case VAL_INT: return float(v.i); - case VAL_STR: case VAL_MACRO: return parsefloat(v.s); - default: return 0.0f; - } + switch(type) + { + case VAL_FLOAT: return v.f; + case VAL_INT: return float(v.i); + case VAL_STR: case VAL_MACRO: return parsefloat(v.s); + default: return 0.0f; + } } inline float tagval::getfloat() const { return ::getfloat(*this, type); } inline float ident::getfloat() const { return ::getfloat(val, valtype); } inline void ident::getval(tagval &v) const { - switch(valtype) - { - case VAL_STR: case VAL_MACRO: v.setstr(newstring(val.s)); break; - case VAL_INT: v.setint(val.i); break; - case VAL_FLOAT: v.setfloat(val.f); break; - default: v.setnull(); break; - } + switch(valtype) + { + case VAL_STR: case VAL_MACRO: v.setstr(newstring(val.s)); break; + case VAL_INT: v.setint(val.i); break; + case VAL_FLOAT: v.setfloat(val.f); break; + default: v.setnull(); break; + } } // nasty macros for registering script functions, abuses globals to avoid excessive infrastructure @@ -326,7 +326,7 @@ inline void ident::getval(tagval &v) const // anonymous inline commands, uses nasty template trick with line numbers to keep names unique #define ICOMMANDNS(name, cmdname, nargs, proto, b) template struct cmdname; template<> struct cmdname<__LINE__> { static bool init; static void run proto; }; bool cmdname<__LINE__>::init = addcommand(name, (identfun)cmdname<__LINE__>::run, nargs); void cmdname<__LINE__>::run proto \ - { b; } + { b; } #define ICOMMANDN(name, cmdname, nargs, proto, b) ICOMMANDNS(#name, cmdname, nargs, proto, b) #define ICOMMANDNAME(name) _icmd_##name #define ICOMMAND(name, nargs, proto, b) ICOMMANDN(name, ICOMMANDNAME(name), nargs, proto, b) diff --git a/src/shared/crypto.cpp b/src/shared/crypto.cpp index 72dea47..fd0a950 100644 --- a/src/shared/crypto.cpp +++ b/src/shared/crypto.cpp @@ -11,42 +11,42 @@ namespace tiger { - typedef unsigned long long int chunk; - - union hashval - { - uchar bytes[3*8]; - chunk chunks[3]; - }; - - chunk sboxes[4*256]; - - void compress(const chunk *str, chunk state[3]) - { - chunk a, b, c; - chunk aa, bb, cc; - chunk x0, x1, x2, x3, x4, x5, x6, x7; - - a = state[0]; - b = state[1]; - c = state[2]; - - x0=str[0]; x1=str[1]; x2=str[2]; x3=str[3]; - x4=str[4]; x5=str[5]; x6=str[6]; x7=str[7]; - - aa = a; - bb = b; - cc = c; - - loop(pass_no, TIGER_PASSES) - { - if(pass_no) - { - x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5ULL; x1 ^= x0; x2 += x1; x3 -= x2 ^ ((~x1)<<19); - x4 ^= x3; x5 += x4; x6 -= x5 ^ ((~x4)>>23); x7 ^= x6; - x0 += x7; x1 -= x0 ^ ((~x7)<<19); x2 ^= x1; x3 += x2; - x4 -= x3 ^ ((~x2)>>23); x5 ^= x4; x6 += x5; x7 -= x6 ^ 0x0123456789ABCDEFULL; - } + typedef unsigned long long int chunk; + + union hashval + { + uchar bytes[3*8]; + chunk chunks[3]; + }; + + chunk sboxes[4*256]; + + void compress(const chunk *str, chunk state[3]) + { + chunk a, b, c; + chunk aa, bb, cc; + chunk x0, x1, x2, x3, x4, x5, x6, x7; + + a = state[0]; + b = state[1]; + c = state[2]; + + x0=str[0]; x1=str[1]; x2=str[2]; x3=str[3]; + x4=str[4]; x5=str[5]; x6=str[6]; x7=str[7]; + + aa = a; + bb = b; + cc = c; + + loop(pass_no, TIGER_PASSES) + { + if(pass_no) + { + x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5ULL; x1 ^= x0; x2 += x1; x3 -= x2 ^ ((~x1)<<19); + x4 ^= x3; x5 += x4; x6 -= x5 ^ ((~x4)>>23); x7 ^= x6; + x0 += x7; x1 -= x0 ^ ((~x7)<<19); x2 ^= x1; x3 += x2; + x4 -= x3 ^ ((~x2)>>23); x5 ^= x4; x6 += x5; x7 -= x6 ^ 0x0123456789ABCDEFULL; + } #define sb1 (sboxes) #define sb2 (sboxes+256) @@ -54,107 +54,107 @@ namespace tiger #define sb4 (sboxes+256*3) #define round(a, b, c, x) \ - c ^= x; \ - a -= sb1[((c)>>(0*8))&0xFF] ^ sb2[((c)>>(2*8))&0xFF] ^ \ - sb3[((c)>>(4*8))&0xFF] ^ sb4[((c)>>(6*8))&0xFF] ; \ - b += sb4[((c)>>(1*8))&0xFF] ^ sb3[((c)>>(3*8))&0xFF] ^ \ - sb2[((c)>>(5*8))&0xFF] ^ sb1[((c)>>(7*8))&0xFF] ; \ - b *= mul; - - uint mul = !pass_no ? 5 : (pass_no==1 ? 7 : 9); - round(a, b, c, x0) round(b, c, a, x1) round(c, a, b, x2) round(a, b, c, x3) - round(b, c, a, x4) round(c, a, b, x5) round(a, b, c, x6) round(b, c, a, x7) - - chunk tmp = a; a = c; c = b; b = tmp; - } - - a ^= aa; - b -= bb; - c += cc; - - state[0] = a; - state[1] = b; - state[2] = c; - } - - void gensboxes() - { - const char *str = "Tiger - A Fast New Hash Function, by Ross Anderson and Eli Biham"; - chunk state[3] = { 0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL, 0xF096A5B4C3B2E187ULL }; - uchar temp[64]; - - if(!*(const uchar *)&islittleendian) loopj(64) temp[j^7] = str[j]; - else loopj(64) temp[j] = str[j]; - loopi(1024) loop(col, 8) ((uchar *)&sboxes[i])[col] = i&0xFF; - - int abc = 2; - loop(pass, 5) loopi(256) for(int sb = 0; sb < 1024; sb += 256) - { - abc++; - if(abc >= 3) { abc = 0; compress((chunk *)temp, state); } - loop(col, 8) - { - uchar val = ((uchar *)&sboxes[sb+i])[col]; - ((uchar *)&sboxes[sb+i])[col] = ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col]; - ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col] = val; - } - } - } - - void hash(const uchar *str, int length, hashval &val) - { - static bool init = false; - if(!init) { gensboxes(); init = true; } - - uchar temp[65]; - - val.chunks[0] = 0x0123456789ABCDEFULL; - val.chunks[1] = 0xFEDCBA9876543210ULL; - val.chunks[2] = 0xF096A5B4C3B2E187ULL; - - int i = length; - for(; i >= 64; i -= 64, str += 64) - { - if(!*(const uchar *)&islittleendian) - { - loopj(64) temp[j^7] = str[j]; - compress((chunk *)temp, val.chunks); - } - else compress((chunk *)str, val.chunks); - } - - int j; - if(!*(const uchar *)&islittleendian) - { - for(j = 0; j < i; j++) temp[j^7] = str[j]; - temp[j^7] = 0x01; - while(++j&7) temp[j^7] = 0; - } - else - { - for(j = 0; j < i; j++) temp[j] = str[j]; - temp[j] = 0x01; - while(++j&7) temp[j] = 0; - } - - if(j > 56) - { - while(j < 64) temp[j++] = 0; - compress((chunk *)temp, val.chunks); - j = 0; - } - while(j < 56) temp[j++] = 0; - *(chunk *)(temp+56) = (chunk)length<<3; - compress((chunk *)temp, val.chunks); - if(!*(const uchar *)&islittleendian) - { - loopk(3) - { - uchar *c = &val.bytes[k*sizeof(chunk)]; - loopl(sizeof(chunk)/2) swap(c[l], c[sizeof(chunk)-1-l]); - } - } - } + c ^= x; \ + a -= sb1[((c)>>(0*8))&0xFF] ^ sb2[((c)>>(2*8))&0xFF] ^ \ + sb3[((c)>>(4*8))&0xFF] ^ sb4[((c)>>(6*8))&0xFF] ; \ + b += sb4[((c)>>(1*8))&0xFF] ^ sb3[((c)>>(3*8))&0xFF] ^ \ + sb2[((c)>>(5*8))&0xFF] ^ sb1[((c)>>(7*8))&0xFF] ; \ + b *= mul; + + uint mul = !pass_no ? 5 : (pass_no==1 ? 7 : 9); + round(a, b, c, x0) round(b, c, a, x1) round(c, a, b, x2) round(a, b, c, x3) + round(b, c, a, x4) round(c, a, b, x5) round(a, b, c, x6) round(b, c, a, x7) + + chunk tmp = a; a = c; c = b; b = tmp; + } + + a ^= aa; + b -= bb; + c += cc; + + state[0] = a; + state[1] = b; + state[2] = c; + } + + void gensboxes() + { + const char *str = "Tiger - A Fast New Hash Function, by Ross Anderson and Eli Biham"; + chunk state[3] = { 0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL, 0xF096A5B4C3B2E187ULL }; + uchar temp[64]; + + if(!*(const uchar *)&islittleendian) loopj(64) temp[j^7] = str[j]; + else loopj(64) temp[j] = str[j]; + loopi(1024) loop(col, 8) ((uchar *)&sboxes[i])[col] = i&0xFF; + + int abc = 2; + loop(pass, 5) loopi(256) for(int sb = 0; sb < 1024; sb += 256) + { + abc++; + if(abc >= 3) { abc = 0; compress((chunk *)temp, state); } + loop(col, 8) + { + uchar val = ((uchar *)&sboxes[sb+i])[col]; + ((uchar *)&sboxes[sb+i])[col] = ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col]; + ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col] = val; + } + } + } + + void hash(const uchar *str, int length, hashval &val) + { + static bool init = false; + if(!init) { gensboxes(); init = true; } + + uchar temp[65]; + + val.chunks[0] = 0x0123456789ABCDEFULL; + val.chunks[1] = 0xFEDCBA9876543210ULL; + val.chunks[2] = 0xF096A5B4C3B2E187ULL; + + int i = length; + for(; i >= 64; i -= 64, str += 64) + { + if(!*(const uchar *)&islittleendian) + { + loopj(64) temp[j^7] = str[j]; + compress((chunk *)temp, val.chunks); + } + else compress((chunk *)str, val.chunks); + } + + int j; + if(!*(const uchar *)&islittleendian) + { + for(j = 0; j < i; j++) temp[j^7] = str[j]; + temp[j^7] = 0x01; + while(++j&7) temp[j^7] = 0; + } + else + { + for(j = 0; j < i; j++) temp[j] = str[j]; + temp[j] = 0x01; + while(++j&7) temp[j] = 0; + } + + if(j > 56) + { + while(j < 64) temp[j++] = 0; + compress((chunk *)temp, val.chunks); + j = 0; + } + while(j < 56) temp[j++] = 0; + *(chunk *)(temp+56) = (chunk)length<<3; + compress((chunk *)temp, val.chunks); + if(!*(const uchar *)&islittleendian) + { + loopk(3) + { + uchar *c = &val.bytes[k*sizeof(chunk)]; + loopl(sizeof(chunk)/2) swap(c[l], c[sizeof(chunk)-1-l]); + } + } + } } /* Elliptic curve cryptography based on NIST DSS prime curves. */ @@ -164,254 +164,254 @@ namespace tiger template struct bigint { - typedef ushort digit; - typedef uint dbldigit; - - int len; - digit digits[BI_DIGITS]; - - bigint() {} - bigint(digit n) { if(n) { len = 1; digits[0] = n; } else len = 0; } - bigint(const char *s) { parse(s); } - template bigint(const bigint &y) { *this = y; } - - static int parsedigits(ushort *digits, int maxlen, const char *s) - { - int slen = 0; - while(isxdigit(s[slen])) slen++; - int len = (slen+2*sizeof(ushort)-1)/(2*sizeof(ushort)); - if(len>maxlen) return 0; - memset(digits, 0, len*sizeof(ushort)); - loopi(slen) - { - int c = s[slen-i-1]; - if(isalpha(c)) c = toupper(c) - 'A' + 10; - else if(isdigit(c)) c -= '0'; - else return 0; - digits[i/(2*sizeof(ushort))] |= c<<(4*(i%(2*sizeof(ushort)))); - } - return len; - } - - void parse(const char *s) - { - len = parsedigits(digits, BI_DIGITS, s); - shrink(); - } - - void zero() { len = 0; } - - void print(stream *out) const - { - vector buf; - printdigits(buf); - out->write(buf.getbuf(), buf.length()); - } - - void printdigits(vector &buf) const - { - loopi(len) - { - digit d = digits[len-i-1]; - loopj(BI_DIGIT_BITS/4) - { - uint shift = BI_DIGIT_BITS - (j+1)*4; - int val = (d >> shift) & 0xF; - if(val < 10) buf.add('0' + val); - else buf.add('a' + val - 10); - } - } - } - - template bigint &operator=(const bigint &y) - { - len = y.len; - memcpy(digits, y.digits, len*sizeof(digit)); - return *this; - } - - bool iszero() const { return !len; } - bool isone() const { return len==1 && digits[0]==1; } - - int numbits() const - { - if(!len) return 0; - int bits = len*BI_DIGIT_BITS; - digit last = digits[len-1], mask = 1<<(BI_DIGIT_BITS-1); - while(mask) - { - if(last&mask) return bits; - bits--; - mask >>= 1; - } - return 0; - } - - bool hasbit(int n) const { return n/BI_DIGIT_BITS < len && ((digits[n/BI_DIGIT_BITS]>>(n%BI_DIGIT_BITS))&1); } - - bool morebits(int n) const { return len > n/BI_DIGIT_BITS; } - - template bigint &add(const bigint &x, const bigint &y) - { - dbldigit carry = 0; - int maxlen = max(x.len, y.len), i; - for(i = 0; i < y.len || carry; i++) - { - carry += (i < x.len ? (dbldigit)x.digits[i] : 0) + (i < y.len ? (dbldigit)y.digits[i] : 0); - digits[i] = (digit)carry; - carry >>= BI_DIGIT_BITS; - } - if(i < x.len && this != &x) memcpy(&digits[i], &x.digits[i], (x.len - i)*sizeof(digit)); - len = max(i, maxlen); - return *this; - } - template bigint &add(const bigint &y) { return add(*this, y); } - - template bigint &sub(const bigint &x, const bigint &y) - { - ASSERT(x >= y); - dbldigit borrow = 0; - int i; - for(i = 0; i < y.len || borrow; i++) - { - borrow = (1<>BI_DIGIT_BITS)^1; - } - if(i < x.len && this != &x) memcpy(&digits[i], &x.digits[i], (x.len - i)*sizeof(digit)); - len = x.len; - shrink(); - return *this; - } - template bigint &sub(const bigint &y) { return sub(*this, y); } - - void shrink() { while(len > 0 && !digits[len-1]) len--; } - void shrinkdigits(int n) { len = n; shrink(); } - void shrinkbits(int n) { shrinkdigits(n/BI_DIGIT_BITS); } - - template void copyshrinkdigits(const bigint &y, int n) - { - len = clamp(y.len, 0, n); - memcpy(digits, y.digits, len*sizeof(digit)); - shrink(); - } - template void copyshrinkbits(const bigint &y, int n) - { - copyshrinkdigits(y, n/BI_DIGIT_BITS); - } - - template bigint &mul(const bigint &x, const bigint &y) - { - if(!x.len || !y.len) { len = 0; return *this; } - memset(digits, 0, y.len*sizeof(digit)); - loopi(x.len) - { - dbldigit carry = 0; - loopj(y.len) - { - carry += (dbldigit)x.digits[i] * (dbldigit)y.digits[j] + (dbldigit)digits[i+j]; - digits[i+j] = (digit)carry; - carry >>= BI_DIGIT_BITS; - } - digits[i+y.len] = carry; - } - len = x.len + y.len; - shrink(); - return *this; - } - - bigint &rshift(int n) - { - assert(len <= BI_DIGITS); - if(!len || n<=0) return *this; - if(n >= len*BI_DIGIT_BITS) { len = 0; return *this; } - int dig = (n-1)/BI_DIGIT_BITS; - n = ((n-1) % BI_DIGIT_BITS)+1; - digit carry = digit(digits[dig]>>n); - for(int i = dig+1; i < len; i++) - { - digit tmp = digits[i]; - digits[i-dig-1] = digit((tmp<<(BI_DIGIT_BITS-n)) | carry); - carry = digit(tmp>>n); - } - digits[len-dig-1] = carry; - len -= dig + (n/BI_DIGIT_BITS); - shrink(); - return *this; - } - - bigint &lshift(int n) - { - if(!len || n<=0) return *this; - int dig = n/BI_DIGIT_BITS; - n %= BI_DIGIT_BITS; - digit carry = 0; - loopirev(len) - { - digit tmp = digits[i]; - digits[i+dig] = digit((tmp<>(BI_DIGIT_BITS-n)); - } - len += dig; - if(carry) digits[len++] = carry; - if(dig) memset(digits, 0, dig*sizeof(digit)); - return *this; - } - - void zerodigits(int i, int n) - { - memset(&digits[i], 0, n*sizeof(digit)); - } - void zerobits(int i, int n) - { - zerodigits(i/BI_DIGIT_BITS, n/BI_DIGIT_BITS); - } - - template void copydigits(int to, const bigint &y, int from, int n) - { - int avail = clamp(y.len-from, 0, n); - memcpy(&digits[to], &y.digits[from], avail*sizeof(digit)); - if(avail < n) memset(&digits[to+avail], 0, (n-avail)*sizeof(digit)); - } - template void copybits(int to, const bigint &y, int from, int n) - { - copydigits(to/BI_DIGIT_BITS, y, from/BI_DIGIT_BITS, n/BI_DIGIT_BITS); - } - - void dupdigits(int to, int from, int n) - { - memcpy(&digits[to], &digits[from], n*sizeof(digit)); - } - void dupbits(int to, int from, int n) - { - dupdigits(to/BI_DIGIT_BITS, from/BI_DIGIT_BITS, n/BI_DIGIT_BITS); - } - - template bool operator==(const bigint &y) const - { - if(len!=y.len) return false; - loopirev(len) if(digits[i]!=y.digits[i]) return false; - return true; - } - template bool operator!=(const bigint &y) const { return !(*this==y); } - template bool operator<(const bigint &y) const - { - if(leny.len) return false; - loopirev(len) - { - if(digits[i]y.digits[i]) return false; - } - return false; - } - template bool operator>(const bigint &y) const { return y<*this; } - template bool operator<=(const bigint &y) const { return !(y<*this); } - template bool operator>=(const bigint &y) const { return !(*this bigint(const bigint &y) { *this = y; } + + static int parsedigits(ushort *digits, int maxlen, const char *s) + { + int slen = 0; + while(isxdigit(s[slen])) slen++; + int len = (slen+2*sizeof(ushort)-1)/(2*sizeof(ushort)); + if(len>maxlen) return 0; + memset(digits, 0, len*sizeof(ushort)); + loopi(slen) + { + int c = s[slen-i-1]; + if(isalpha(c)) c = toupper(c) - 'A' + 10; + else if(isdigit(c)) c -= '0'; + else return 0; + digits[i/(2*sizeof(ushort))] |= c<<(4*(i%(2*sizeof(ushort)))); + } + return len; + } + + void parse(const char *s) + { + len = parsedigits(digits, BI_DIGITS, s); + shrink(); + } + + void zero() { len = 0; } + + void print(stream *out) const + { + vector buf; + printdigits(buf); + out->write(buf.getbuf(), buf.length()); + } + + void printdigits(vector &buf) const + { + loopi(len) + { + digit d = digits[len-i-1]; + loopj(BI_DIGIT_BITS/4) + { + uint shift = BI_DIGIT_BITS - (j+1)*4; + int val = (d >> shift) & 0xF; + if(val < 10) buf.add('0' + val); + else buf.add('a' + val - 10); + } + } + } + + template bigint &operator=(const bigint &y) + { + len = y.len; + memcpy(digits, y.digits, len*sizeof(digit)); + return *this; + } + + bool iszero() const { return !len; } + bool isone() const { return len==1 && digits[0]==1; } + + int numbits() const + { + if(!len) return 0; + int bits = len*BI_DIGIT_BITS; + digit last = digits[len-1], mask = 1<<(BI_DIGIT_BITS-1); + while(mask) + { + if(last&mask) return bits; + bits--; + mask >>= 1; + } + return 0; + } + + bool hasbit(int n) const { return n/BI_DIGIT_BITS < len && ((digits[n/BI_DIGIT_BITS]>>(n%BI_DIGIT_BITS))&1); } + + bool morebits(int n) const { return len > n/BI_DIGIT_BITS; } + + template bigint &add(const bigint &x, const bigint &y) + { + dbldigit carry = 0; + int maxlen = max(x.len, y.len), i; + for(i = 0; i < y.len || carry; i++) + { + carry += (i < x.len ? (dbldigit)x.digits[i] : 0) + (i < y.len ? (dbldigit)y.digits[i] : 0); + digits[i] = (digit)carry; + carry >>= BI_DIGIT_BITS; + } + if(i < x.len && this != &x) memcpy(&digits[i], &x.digits[i], (x.len - i)*sizeof(digit)); + len = max(i, maxlen); + return *this; + } + template bigint &add(const bigint &y) { return add(*this, y); } + + template bigint &sub(const bigint &x, const bigint &y) + { + ASSERT(x >= y); + dbldigit borrow = 0; + int i; + for(i = 0; i < y.len || borrow; i++) + { + borrow = (1<>BI_DIGIT_BITS)^1; + } + if(i < x.len && this != &x) memcpy(&digits[i], &x.digits[i], (x.len - i)*sizeof(digit)); + len = x.len; + shrink(); + return *this; + } + template bigint &sub(const bigint &y) { return sub(*this, y); } + + void shrink() { while(len > 0 && !digits[len-1]) len--; } + void shrinkdigits(int n) { len = n; shrink(); } + void shrinkbits(int n) { shrinkdigits(n/BI_DIGIT_BITS); } + + template void copyshrinkdigits(const bigint &y, int n) + { + len = clamp(y.len, 0, n); + memcpy(digits, y.digits, len*sizeof(digit)); + shrink(); + } + template void copyshrinkbits(const bigint &y, int n) + { + copyshrinkdigits(y, n/BI_DIGIT_BITS); + } + + template bigint &mul(const bigint &x, const bigint &y) + { + if(!x.len || !y.len) { len = 0; return *this; } + memset(digits, 0, y.len*sizeof(digit)); + loopi(x.len) + { + dbldigit carry = 0; + loopj(y.len) + { + carry += (dbldigit)x.digits[i] * (dbldigit)y.digits[j] + (dbldigit)digits[i+j]; + digits[i+j] = (digit)carry; + carry >>= BI_DIGIT_BITS; + } + digits[i+y.len] = carry; + } + len = x.len + y.len; + shrink(); + return *this; + } + + bigint &rshift(int n) + { + assert(len <= BI_DIGITS); + if(!len || n<=0) return *this; + if(n >= len*BI_DIGIT_BITS) { len = 0; return *this; } + int dig = (n-1)/BI_DIGIT_BITS; + n = ((n-1) % BI_DIGIT_BITS)+1; + digit carry = digit(digits[dig]>>n); + for(int i = dig+1; i < len; i++) + { + digit tmp = digits[i]; + digits[i-dig-1] = digit((tmp<<(BI_DIGIT_BITS-n)) | carry); + carry = digit(tmp>>n); + } + digits[len-dig-1] = carry; + len -= dig + (n/BI_DIGIT_BITS); + shrink(); + return *this; + } + + bigint &lshift(int n) + { + if(!len || n<=0) return *this; + int dig = n/BI_DIGIT_BITS; + n %= BI_DIGIT_BITS; + digit carry = 0; + loopirev(len) + { + digit tmp = digits[i]; + digits[i+dig] = digit((tmp<>(BI_DIGIT_BITS-n)); + } + len += dig; + if(carry) digits[len++] = carry; + if(dig) memset(digits, 0, dig*sizeof(digit)); + return *this; + } + + void zerodigits(int i, int n) + { + memset(&digits[i], 0, n*sizeof(digit)); + } + void zerobits(int i, int n) + { + zerodigits(i/BI_DIGIT_BITS, n/BI_DIGIT_BITS); + } + + template void copydigits(int to, const bigint &y, int from, int n) + { + int avail = clamp(y.len-from, 0, n); + memcpy(&digits[to], &y.digits[from], avail*sizeof(digit)); + if(avail < n) memset(&digits[to+avail], 0, (n-avail)*sizeof(digit)); + } + template void copybits(int to, const bigint &y, int from, int n) + { + copydigits(to/BI_DIGIT_BITS, y, from/BI_DIGIT_BITS, n/BI_DIGIT_BITS); + } + + void dupdigits(int to, int from, int n) + { + memcpy(&digits[to], &digits[from], n*sizeof(digit)); + } + void dupbits(int to, int from, int n) + { + dupdigits(to/BI_DIGIT_BITS, from/BI_DIGIT_BITS, n/BI_DIGIT_BITS); + } + + template bool operator==(const bigint &y) const + { + if(len!=y.len) return false; + loopirev(len) if(digits[i]!=y.digits[i]) return false; + return true; + } + template bool operator!=(const bigint &y) const { return !(*this==y); } + template bool operator<(const bigint &y) const + { + if(leny.len) return false; + loopirev(len) + { + if(digits[i]y.digits[i]) return false; + } + return false; + } + template bool operator>(const bigint &y) const { return y<*this; } + template bool operator<=(const bigint &y) const { return !(y<*this); } + template bool operator>=(const bigint &y) const { return !(*this gfint; @@ -420,380 +420,380 @@ typedef bigint gfint; */ struct gfield : gfint { - static const gfield P; - - gfield() {} - gfield(digit n) : gfint(n) {} - gfield(const char *s) : gfint(s) {} - - template gfield(const bigint &y) : gfint(y) {} - - template gfield &operator=(const bigint &y) - { - gfint::operator=(y); - return *this; - } - - template gfield &add(const bigint &x, const bigint &y) - { - gfint::add(x, y); - if(*this >= P) gfint::sub(*this, P); - return *this; - } - template gfield &add(const bigint &y) { return add(*this, y); } - - template gfield &mul2(const bigint &x) { return add(x, x); } - gfield &mul2() { return mul2(*this); } - - gfield &div2() - { - if(hasbit(0)) gfint::add(*this, P); - rshift(1); - return *this; - } - - template gfield &sub(const bigint &x, const bigint &y) - { - if(x < y) - { - gfint tmp; /* necessary if this==&y, using this instead would clobber y */ - tmp.add(x, P); - gfint::sub(tmp, y); - } - else gfint::sub(x, y); - return *this; - } - template gfield &sub(const bigint &y) { return sub(*this, y); } - - template gfield &neg(const bigint &x) - { - gfint::sub(P, x); - return *this; - } - gfield &neg() { return neg(*this); } - - template gfield &square(const bigint &x) { return mul(x, x); } - gfield &square() { return square(*this); } - - template gfield &mul(const bigint &x, const bigint &y) - { - bigint result; - result.mul(x, y); - reduce(result); - return *this; - } - template gfield &mul(const bigint &y) { return mul(*this, y); } - - template void reduce(const bigint &result) - { + static const gfield P; + + gfield() {} + gfield(digit n) : gfint(n) {} + gfield(const char *s) : gfint(s) {} + + template gfield(const bigint &y) : gfint(y) {} + + template gfield &operator=(const bigint &y) + { + gfint::operator=(y); + return *this; + } + + template gfield &add(const bigint &x, const bigint &y) + { + gfint::add(x, y); + if(*this >= P) gfint::sub(*this, P); + return *this; + } + template gfield &add(const bigint &y) { return add(*this, y); } + + template gfield &mul2(const bigint &x) { return add(x, x); } + gfield &mul2() { return mul2(*this); } + + gfield &div2() + { + if(hasbit(0)) gfint::add(*this, P); + rshift(1); + return *this; + } + + template gfield &sub(const bigint &x, const bigint &y) + { + if(x < y) + { + gfint tmp; /* necessary if this==&y, using this instead would clobber y */ + tmp.add(x, P); + gfint::sub(tmp, y); + } + else gfint::sub(x, y); + return *this; + } + template gfield &sub(const bigint &y) { return sub(*this, y); } + + template gfield &neg(const bigint &x) + { + gfint::sub(P, x); + return *this; + } + gfield &neg() { return neg(*this); } + + template gfield &square(const bigint &x) { return mul(x, x); } + gfield &square() { return square(*this); } + + template gfield &mul(const bigint &x, const bigint &y) + { + bigint result; + result.mul(x, y); + reduce(result); + return *this; + } + template gfield &mul(const bigint &y) { return mul(*this, y); } + + template void reduce(const bigint &result) + { #if GF_BITS==192 - // B = T + S1 + S2 + S3 mod p - copyshrinkdigits(result, GF_DIGITS); // T - - if(result.morebits(192)) - { - gfield s; - s.copybits(0, result, 192, 64); - s.dupbits(64, 0, 64); - s.shrinkbits(128); - add(s); // S1 - - if(result.morebits(256)) - { - s.zerobits(0, 64); - s.copybits(64, result, 256, 64); - s.dupbits(128, 64, 64); - s.shrinkdigits(GF_DIGITS); - add(s); // S2 - - if(result.morebits(320)) - { - s.copybits(0, result, 320, 64); - s.dupbits(64, 0, 64); - s.dupbits(128, 0, 64); - s.shrinkdigits(GF_DIGITS); - add(s); // S3 - } - } - } - else if(*this >= P) gfint::sub(*this, P); + // B = T + S1 + S2 + S3 mod p + copyshrinkdigits(result, GF_DIGITS); // T + + if(result.morebits(192)) + { + gfield s; + s.copybits(0, result, 192, 64); + s.dupbits(64, 0, 64); + s.shrinkbits(128); + add(s); // S1 + + if(result.morebits(256)) + { + s.zerobits(0, 64); + s.copybits(64, result, 256, 64); + s.dupbits(128, 64, 64); + s.shrinkdigits(GF_DIGITS); + add(s); // S2 + + if(result.morebits(320)) + { + s.copybits(0, result, 320, 64); + s.dupbits(64, 0, 64); + s.dupbits(128, 0, 64); + s.shrinkdigits(GF_DIGITS); + add(s); // S3 + } + } + } + else if(*this >= P) gfint::sub(*this, P); #elif GF_BITS==256 - // B = T + 2*S1 + 2*S2 + S3 + S4 - D1 - D2 - D3 - D4 mod p - copyshrinkdigits(result, GF_DIGITS); // T - - if(result.morebits(256)) - { - gfield s; - if(result.morebits(352)) - { - s.zerobits(0, 96); - s.copybits(96, result, 352, 160); - s.shrinkdigits(GF_DIGITS); - add(s); add(s); // S1 - - if(result.morebits(384)) - { - //s.zerobits(0, 96); - s.copybits(96, result, 384, 128); - s.shrinkbits(224); - add(s); add(s); // S2 - } - } - - s.copybits(0, result, 256, 96); - s.zerobits(96, 96); - s.copybits(192, result, 448, 64); - s.shrinkdigits(GF_DIGITS); - add(s); // S3 - - s.copybits(0, result, 288, 96); - s.copybits(96, result, 416, 96); - s.dupbits(192, 96, 32); - s.copybits(224, result, 256, 32); - s.shrinkdigits(GF_DIGITS); - add(s); // S4 - - s.copybits(0, result, 352, 96); - s.zerobits(96, 96); - s.copybits(192, result, 256, 32); - s.copybits(224, result, 320, 32); - s.shrinkdigits(GF_DIGITS); - sub(s); // D1 - - s.copybits(0, result, 384, 128); - //s.zerobits(128, 64); - s.copybits(192, result, 288, 32); - s.copybits(224, result, 352, 32); - s.shrinkdigits(GF_DIGITS); - sub(s); // D2 - - s.copybits(0, result, 416, 96); - s.copybits(96, result, 256, 96); - s.zerobits(192, 32); - s.copybits(224, result, 384, 32); - s.shrinkdigits(GF_DIGITS); - sub(s); // D3 - - s.copybits(0, result, 448, 64); - s.zerobits(64, 32); - s.copybits(96, result, 288, 96); - //s.zerobits(192, 32); - s.copybits(224, result, 416, 32); - s.shrinkdigits(GF_DIGITS); - sub(s); // D4 - } - else if(*this >= P) gfint::sub(*this, P); + // B = T + 2*S1 + 2*S2 + S3 + S4 - D1 - D2 - D3 - D4 mod p + copyshrinkdigits(result, GF_DIGITS); // T + + if(result.morebits(256)) + { + gfield s; + if(result.morebits(352)) + { + s.zerobits(0, 96); + s.copybits(96, result, 352, 160); + s.shrinkdigits(GF_DIGITS); + add(s); add(s); // S1 + + if(result.morebits(384)) + { + //s.zerobits(0, 96); + s.copybits(96, result, 384, 128); + s.shrinkbits(224); + add(s); add(s); // S2 + } + } + + s.copybits(0, result, 256, 96); + s.zerobits(96, 96); + s.copybits(192, result, 448, 64); + s.shrinkdigits(GF_DIGITS); + add(s); // S3 + + s.copybits(0, result, 288, 96); + s.copybits(96, result, 416, 96); + s.dupbits(192, 96, 32); + s.copybits(224, result, 256, 32); + s.shrinkdigits(GF_DIGITS); + add(s); // S4 + + s.copybits(0, result, 352, 96); + s.zerobits(96, 96); + s.copybits(192, result, 256, 32); + s.copybits(224, result, 320, 32); + s.shrinkdigits(GF_DIGITS); + sub(s); // D1 + + s.copybits(0, result, 384, 128); + //s.zerobits(128, 64); + s.copybits(192, result, 288, 32); + s.copybits(224, result, 352, 32); + s.shrinkdigits(GF_DIGITS); + sub(s); // D2 + + s.copybits(0, result, 416, 96); + s.copybits(96, result, 256, 96); + s.zerobits(192, 32); + s.copybits(224, result, 384, 32); + s.shrinkdigits(GF_DIGITS); + sub(s); // D3 + + s.copybits(0, result, 448, 64); + s.zerobits(64, 32); + s.copybits(96, result, 288, 96); + //s.zerobits(192, 32); + s.copybits(224, result, 416, 32); + s.shrinkdigits(GF_DIGITS); + sub(s); // D4 + } + else if(*this >= P) gfint::sub(*this, P); #else #error Unsupported GF #endif - } - - template gfield &pow(const bigint &x, const bigint &y) - { - gfield a(x); - if(y.hasbit(0)) *this = a; - else - { - len = 1; - digits[0] = 1; - if(!y.len) return *this; - } - for(int i = 1, j = y.numbits(); i < j; i++) - { - a.square(); - if(y.hasbit(i)) mul(a); - } - return *this; - } - template gfield &pow(const bigint &y) { return pow(*this, y); } - - bool invert(const gfield &x) - { - if(!x.len) return false; - gfint u(x), v(P), A((gfint::digit)1), C((gfint::digit)0); - while(!u.iszero()) - { - int ushift = 0, ashift = 0; - while(!u.hasbit(ushift)) - { - ushift++; - if(A.hasbit(ashift)) - { - if(ashift) { A.rshift(ashift); ashift = 0; } - A.add(P); - } - ashift++; - } - if(ushift) u.rshift(ushift); - if(ashift) A.rshift(ashift); - int vshift = 0, cshift = 0; - while(!v.hasbit(vshift)) - { - vshift++; - if(C.hasbit(cshift)) - { - if(cshift) { C.rshift(cshift); cshift = 0; } - C.add(P); - } - cshift++; - } - if(vshift) v.rshift(vshift); - if(cshift) C.rshift(cshift); - if(u >= v) - { - u.sub(v); - if(A < C) A.add(P); - A.sub(C); - } - else - { - v.sub(v, u); - if(C < A) C.add(P); - C.sub(A); - } - } - if(C >= P) gfint::sub(C, P); - else { len = C.len; memcpy(digits, C.digits, len*sizeof(digit)); } - ASSERT(*this < P); - return true; - } - void invert() { invert(*this); } - - template static int legendre(const bigint &x) - { - static const gfint Psub1div2(gfint(P).sub(bigint<1>(1)).rshift(1)); - gfield L; - L.pow(x, Psub1div2); - if(!L.len) return 0; - if(L.len==1) return 1; - return -1; - } - int legendre() const { return legendre(*this); } - - bool sqrt(const gfield &x) - { - if(!x.len) { len = 0; return true; } + } + + template gfield &pow(const bigint &x, const bigint &y) + { + gfield a(x); + if(y.hasbit(0)) *this = a; + else + { + len = 1; + digits[0] = 1; + if(!y.len) return *this; + } + for(int i = 1, j = y.numbits(); i < j; i++) + { + a.square(); + if(y.hasbit(i)) mul(a); + } + return *this; + } + template gfield &pow(const bigint &y) { return pow(*this, y); } + + bool invert(const gfield &x) + { + if(!x.len) return false; + gfint u(x), v(P), A((gfint::digit)1), C((gfint::digit)0); + while(!u.iszero()) + { + int ushift = 0, ashift = 0; + while(!u.hasbit(ushift)) + { + ushift++; + if(A.hasbit(ashift)) + { + if(ashift) { A.rshift(ashift); ashift = 0; } + A.add(P); + } + ashift++; + } + if(ushift) u.rshift(ushift); + if(ashift) A.rshift(ashift); + int vshift = 0, cshift = 0; + while(!v.hasbit(vshift)) + { + vshift++; + if(C.hasbit(cshift)) + { + if(cshift) { C.rshift(cshift); cshift = 0; } + C.add(P); + } + cshift++; + } + if(vshift) v.rshift(vshift); + if(cshift) C.rshift(cshift); + if(u >= v) + { + u.sub(v); + if(A < C) A.add(P); + A.sub(C); + } + else + { + v.sub(v, u); + if(C < A) C.add(P); + C.sub(A); + } + } + if(C >= P) gfint::sub(C, P); + else { len = C.len; memcpy(digits, C.digits, len*sizeof(digit)); } + ASSERT(*this < P); + return true; + } + void invert() { invert(*this); } + + template static int legendre(const bigint &x) + { + static const gfint Psub1div2(gfint(P).sub(bigint<1>(1)).rshift(1)); + gfield L; + L.pow(x, Psub1div2); + if(!L.len) return 0; + if(L.len==1) return 1; + return -1; + } + int legendre() const { return legendre(*this); } + + bool sqrt(const gfield &x) + { + if(!x.len) { len = 0; return true; } #if GF_BITS==224 #error Unsupported GF #else - ASSERT((P.digits[0]%4)==3); - static const gfint Padd1div4(gfint(P).add(bigint<1>(1)).rshift(2)); - switch(legendre(x)) - { - case 0: len = 0; return true; - case -1: return false; - default: pow(x, Padd1div4); return true; - } + ASSERT((P.digits[0]%4)==3); + static const gfint Padd1div4(gfint(P).add(bigint<1>(1)).rshift(2)); + switch(legendre(x)) + { + case 0: len = 0; return true; + case -1: return false; + default: pow(x, Padd1div4); return true; + } #endif - } - bool sqrt() { return sqrt(*this); } + } + bool sqrt() { return sqrt(*this); } }; struct ecjacobian { - static const gfield B; - static const ecjacobian base; - static const ecjacobian origin; - - gfield x, y, z; - - ecjacobian() {} - ecjacobian(const gfield &x, const gfield &y) : x(x), y(y), z(bigint<1>(1)) {} - ecjacobian(const gfield &x, const gfield &y, const gfield &z) : x(x), y(y), z(z) {} - - void mul2() - { - if(z.iszero()) return; - else if(y.iszero()) { *this = origin; return; } - gfield a, b, c, d; - d.sub(x, c.square(z)); - d.mul(c.add(x)); - c.mul2(d).add(d); - z.mul(y).add(z); - a.square(y); - b.mul2(a); - d.mul2(x).mul(b); - x.square(c).sub(d).sub(d); - a.square(b).add(a); - y.sub(d, x).mul(c).sub(a); - } - - void add(const ecjacobian &q) - { - if(q.z.iszero()) return; - else if(z.iszero()) { *this = q; return; } - gfield a, b, c, d, e, f; - a.square(z); - b.mul(q.y, a).mul(z); - a.mul(q.x); - if(q.z.isone()) - { - c.add(x, a); - d.add(y, b); - a.sub(x, a); - b.sub(y, b); - } - else - { - f.mul(y, e.square(q.z)).mul(q.z); - e.mul(x); - c.add(e, a); - d.add(f, b); - a.sub(e, a); - b.sub(f, b); - } - if(a.iszero()) { if(b.iszero()) mul2(); else *this = origin; return; } - if(!q.z.isone()) z.mul(q.z); - z.mul(a); - x.square(b).sub(f.mul(c, e.square(a))); - y.sub(f, x).sub(x).mul(b).sub(e.mul(a).mul(d)).div2(); - } - - template void mul(const ecjacobian &p, const bigint &q) - { - *this = origin; - loopirev(q.numbits()) - { - mul2(); - if(q.hasbit(i)) add(p); - } - } - template void mul(const bigint &q) { ecjacobian tmp(*this); mul(tmp, q); } - - void normalize() - { - if(z.iszero() || z.isone()) return; - gfield tmp; - z.invert(); - tmp.square(z); - x.mul(tmp); - y.mul(tmp).mul(z); - z = bigint<1>(1); - } - - bool calcy(bool ybit) - { - gfield y2, tmp; - y2.square(x).mul(x).sub(tmp.add(x, x).add(x)).add(B); - if(!y.sqrt(y2)) { y.zero(); return false; } - if(y.hasbit(0) != ybit) y.neg(); - return true; - } - - void print(vector &buf) - { - normalize(); - buf.add(y.hasbit(0) ? '-' : '+'); - x.printdigits(buf); - } - - void parse(const char *s) - { - bool ybit = *s++ == '-'; - x.parse(s); - calcy(ybit); - z = bigint<1>(1); - } + static const gfield B; + static const ecjacobian base; + static const ecjacobian origin; + + gfield x, y, z; + + ecjacobian() {} + ecjacobian(const gfield &x, const gfield &y) : x(x), y(y), z(bigint<1>(1)) {} + ecjacobian(const gfield &x, const gfield &y, const gfield &z) : x(x), y(y), z(z) {} + + void mul2() + { + if(z.iszero()) return; + else if(y.iszero()) { *this = origin; return; } + gfield a, b, c, d; + d.sub(x, c.square(z)); + d.mul(c.add(x)); + c.mul2(d).add(d); + z.mul(y).add(z); + a.square(y); + b.mul2(a); + d.mul2(x).mul(b); + x.square(c).sub(d).sub(d); + a.square(b).add(a); + y.sub(d, x).mul(c).sub(a); + } + + void add(const ecjacobian &q) + { + if(q.z.iszero()) return; + else if(z.iszero()) { *this = q; return; } + gfield a, b, c, d, e, f; + a.square(z); + b.mul(q.y, a).mul(z); + a.mul(q.x); + if(q.z.isone()) + { + c.add(x, a); + d.add(y, b); + a.sub(x, a); + b.sub(y, b); + } + else + { + f.mul(y, e.square(q.z)).mul(q.z); + e.mul(x); + c.add(e, a); + d.add(f, b); + a.sub(e, a); + b.sub(f, b); + } + if(a.iszero()) { if(b.iszero()) mul2(); else *this = origin; return; } + if(!q.z.isone()) z.mul(q.z); + z.mul(a); + x.square(b).sub(f.mul(c, e.square(a))); + y.sub(f, x).sub(x).mul(b).sub(e.mul(a).mul(d)).div2(); + } + + template void mul(const ecjacobian &p, const bigint &q) + { + *this = origin; + loopirev(q.numbits()) + { + mul2(); + if(q.hasbit(i)) add(p); + } + } + template void mul(const bigint &q) { ecjacobian tmp(*this); mul(tmp, q); } + + void normalize() + { + if(z.iszero() || z.isone()) return; + gfield tmp; + z.invert(); + tmp.square(z); + x.mul(tmp); + y.mul(tmp).mul(z); + z = bigint<1>(1); + } + + bool calcy(bool ybit) + { + gfield y2, tmp; + y2.square(x).mul(x).sub(tmp.add(x, x).add(x)).add(B); + if(!y.sqrt(y2)) { y.zero(); return false; } + if(y.hasbit(0) != ybit) y.neg(); + return true; + } + + void print(vector &buf) + { + normalize(); + buf.add(y.hasbit(0) ? '-' : '+'); + x.printdigits(buf); + } + + void parse(const char *s) + { + bool ybit = *s++ == '-'; + x.parse(s); + calcy(ybit); + z = bigint<1>(1); + } }; const ecjacobian ecjacobian::origin(gfield((gfield::digit)1), gfield((gfield::digit)1), gfield((gfield::digit)0)); @@ -802,36 +802,36 @@ const ecjacobian ecjacobian::origin(gfield((gfield::digit)1), gfield((gfield::di const gfield gfield::P("fffffffffffffffffffffffffffffffeffffffffffffffff"); const gfield ecjacobian::B("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1"); const ecjacobian ecjacobian::base( - gfield("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012"), - gfield("07192b95ffc8da78631011ed6b24cdd573f977a11e794811") + gfield("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012"), + gfield("07192b95ffc8da78631011ed6b24cdd573f977a11e794811") ); #elif GF_BITS==224 const gfield gfield::P("ffffffffffffffffffffffffffffffff000000000000000000000001"); const gfield ecjacobian::B("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"); const ecjacobian ecjacobian::base( - gfield("b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"), - gfield("bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34") + gfield("b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"), + gfield("bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34") ); #elif GF_BITS==256 const gfield gfield::P("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff"); const gfield ecjacobian::B("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"); const ecjacobian ecjacobian::base( - gfield("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"), - gfield("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5") + gfield("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"), + gfield("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5") ); #elif GF_BITS==384 const gfield gfield::P("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"); const gfield ecjacobian::B("b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"); const ecjacobian ecjacobian::base( - gfield("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"), - gfield("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f") + gfield("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"), + gfield("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f") ); #elif GF_BITS==521 const gfield gfield::P("1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); const gfield ecjacobian::B("051953eb968e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00"); const ecjacobian ecjacobian::base( - gfield("c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66"), - gfield("11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650") + gfield("c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66"), + gfield("11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650") ); #else #error Unsupported GF @@ -839,106 +839,106 @@ const ecjacobian ecjacobian::base( void calcpubkey(gfint privkey, vector &pubstr) { - ecjacobian c(ecjacobian::base); - c.mul(privkey); - c.normalize(); - c.print(pubstr); - pubstr.add('\0'); + ecjacobian c(ecjacobian::base); + c.mul(privkey); + c.normalize(); + c.print(pubstr); + pubstr.add('\0'); } bool calcpubkey(const char *privstr, vector &pubstr) { - if(!privstr[0]) return false; - gfint privkey; - privkey.parse(privstr); - calcpubkey(privkey, pubstr); - return true; + if(!privstr[0]) return false; + gfint privkey; + privkey.parse(privstr); + calcpubkey(privkey, pubstr); + return true; } void genprivkey(const char *seed, vector &privstr, vector &pubstr) { - tiger::hashval hash; - tiger::hash((const uchar *)seed, (int)strlen(seed), hash); - bigint<8*sizeof(hash.bytes)/BI_DIGIT_BITS> privkey; - memcpy(privkey.digits, hash.bytes, sizeof(hash.bytes)); - privkey.len = 8*sizeof(hash.bytes)/BI_DIGIT_BITS; - privkey.shrink(); - privkey.printdigits(privstr); - privstr.add('\0'); - - calcpubkey(privkey, pubstr); + tiger::hashval hash; + tiger::hash((const uchar *)seed, (int)strlen(seed), hash); + bigint<8*sizeof(hash.bytes)/BI_DIGIT_BITS> privkey; + memcpy(privkey.digits, hash.bytes, sizeof(hash.bytes)); + privkey.len = 8*sizeof(hash.bytes)/BI_DIGIT_BITS; + privkey.shrink(); + privkey.printdigits(privstr); + privstr.add('\0'); + + calcpubkey(privkey, pubstr); } bool hashstring(const char *str, char *result, int maxlen) { - tiger::hashval hv; - if(maxlen < 2*(int)sizeof(hv.bytes) + 1) return false; - tiger::hash((uchar *)str, strlen(str), hv); - loopi(sizeof(hv.bytes)) - { - uchar c = hv.bytes[i]; - *result++ = "0123456789abcdef"[c&0xF]; - *result++ = "0123456789abcdef"[c>>4]; - } - *result = '\0'; - return true; + tiger::hashval hv; + if(maxlen < 2*(int)sizeof(hv.bytes) + 1) return false; + tiger::hash((uchar *)str, strlen(str), hv); + loopi(sizeof(hv.bytes)) + { + uchar c = hv.bytes[i]; + *result++ = "0123456789abcdef"[c&0xF]; + *result++ = "0123456789abcdef"[c>>4]; + } + *result = '\0'; + return true; } void answerchallenge(const char *privstr, const char *challenge, vector &answerstr) { - gfint privkey; - privkey.parse(privstr); - ecjacobian answer; - answer.parse(challenge); - answer.mul(privkey); - answer.normalize(); - answer.x.printdigits(answerstr); - answerstr.add('\0'); + gfint privkey; + privkey.parse(privstr); + ecjacobian answer; + answer.parse(challenge); + answer.mul(privkey); + answer.normalize(); + answer.x.printdigits(answerstr); + answerstr.add('\0'); } void *parsepubkey(const char *pubstr) { - ecjacobian *pubkey = new ecjacobian; - pubkey->parse(pubstr); - return pubkey; + ecjacobian *pubkey = new ecjacobian; + pubkey->parse(pubstr); + return pubkey; } void freepubkey(void *pubkey) { - delete (ecjacobian *)pubkey; + delete (ecjacobian *)pubkey; } void *genchallenge(void *pubkey, const void *seed, int seedlen, vector &challengestr) { - tiger::hashval hash; - tiger::hash((const uchar *)seed, seedlen, hash); - gfint challenge; - memcpy(challenge.digits, hash.bytes, sizeof(hash.bytes)); - challenge.len = 8*sizeof(hash.bytes)/BI_DIGIT_BITS; - challenge.shrink(); + tiger::hashval hash; + tiger::hash((const uchar *)seed, seedlen, hash); + gfint challenge; + memcpy(challenge.digits, hash.bytes, sizeof(hash.bytes)); + challenge.len = 8*sizeof(hash.bytes)/BI_DIGIT_BITS; + challenge.shrink(); - ecjacobian answer(*(ecjacobian *)pubkey); - answer.mul(challenge); - answer.normalize(); + ecjacobian answer(*(ecjacobian *)pubkey); + answer.mul(challenge); + answer.normalize(); - ecjacobian secret(ecjacobian::base); - secret.mul(challenge); - secret.normalize(); + ecjacobian secret(ecjacobian::base); + secret.mul(challenge); + secret.normalize(); - secret.print(challengestr); - challengestr.add('\0'); + secret.print(challengestr); + challengestr.add('\0'); - return new gfield(answer.x); + return new gfield(answer.x); } void freechallenge(void *answer) { - delete (gfint *)answer; + delete (gfint *)answer; } bool checkchallenge(const char *answerstr, void *correct) { - gfint answer(answerstr); - return answer == *(gfint *)correct; + gfint answer(answerstr); + return answer == *(gfint *)correct; } diff --git a/src/shared/cube.h b/src/shared/cube.h index ffb1d1b..d804511 100644 --- a/src/shared/cube.h +++ b/src/shared/cube.h @@ -3,9 +3,6 @@ #define _FILE_OFFSET_BITS 64 -#ifdef WIN32 -#define _USE_MATH_DEFINES -#endif #include #include @@ -17,34 +14,9 @@ #include #include -#ifdef WIN32 - #define WIN32_LEAN_AND_MEAN - #ifdef _WIN32_WINNT - #undef _WIN32_WINNT - #endif - #define _WIN32_WINNT 0x0500 - #include "windows.h" - #ifndef _WINDOWS - #define _WINDOWS - #endif - #ifndef __GNUC__ - #include - #include - #include - #endif - #define ZLIB_DLL -#endif - #ifndef STANDALONE - #ifdef __APPLE__ - #include "SDL.h" - #define GL_GLEXT_LEGACY - #define __glext_h_ - #include - #else - #include - #include - #endif + #include + #include #endif #include diff --git a/src/shared/cube2font.c b/src/shared/cube2font.c index 94e407d..75ea8ff 100644 --- a/src/shared/cube2font.c +++ b/src/shared/cube2font.c @@ -18,180 +18,180 @@ int imax(int a, int b) { return a > b ? a : b; } void fatal(const char *fmt, ...) { - va_list v; - va_start(v, fmt); - vfprintf(stderr, fmt, v); - va_end(v); - fputc('\n', stderr); + va_list v; + va_start(v, fmt); + vfprintf(stderr, fmt, v); + va_end(v); + fputc('\n', stderr); - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } uint bigswap(uint n) { - const int islittleendian = 1; - return *(const uchar *)&islittleendian ? (n<<24) | (n>>24) | ((n>>8)&0xFF00) | ((n<<8)&0xFF0000) : n; + const int islittleendian = 1; + return *(const uchar *)&islittleendian ? (n<<24) | (n>>24) | ((n>>8)&0xFF00) | ((n<<8)&0xFF0000) : n; } size_t writebig(FILE *f, uint n) { - n = bigswap(n); - return fwrite(&n, 1, sizeof(n), f); + n = bigswap(n); + return fwrite(&n, 1, sizeof(n), f); } void writepngchunk(FILE *f, const char *type, uchar *data, uint len) { - uint crc; - writebig(f, len); - fwrite(type, 1, 4, f); - fwrite(data, 1, len, f); - - crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, (const Bytef *)type, 4); - if(data) crc = crc32(crc, data, len); - writebig(f, crc); + uint crc; + writebig(f, len); + fwrite(type, 1, 4, f); + fwrite(data, 1, len, f); + + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, (const Bytef *)type, 4); + if(data) crc = crc32(crc, data, len); + writebig(f, crc); } struct pngihdr { - uint width, height; - uchar bitdepth, colortype, compress, filter, interlace; + uint width, height; + uchar bitdepth, colortype, compress, filter, interlace; }; void savepng(const char *filename, uchar *data, int w, int h, int bpp, int flip) { - const uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; - struct pngihdr ihdr; - FILE *f; - long idat; - uint len, crc; - z_stream z; - uchar buf[1<<12]; - int i, j; - - memset(&ihdr, 0, sizeof(ihdr)); - ihdr.width = bigswap(w); - ihdr.height = bigswap(h); - ihdr.bitdepth = 8; - switch(bpp) - { - case 1: ihdr.colortype = 0; break; - case 2: ihdr.colortype = 4; break; - case 3: ihdr.colortype = 2; break; - case 4: ihdr.colortype = 6; break; - default: fatal("cube2font: invalid PNG bpp"); return; - } - f = fopen(filename, "wb"); - if(!f) { fatal("cube2font: could not write to %s", filename); return; } - - fwrite(signature, 1, sizeof(signature), f); - - writepngchunk(f, "IHDR", (uchar *)&ihdr, 13); - - idat = ftell(f); - len = 0; - fwrite("\0\0\0\0IDAT", 1, 8, f); - crc = crc32(0, Z_NULL, 0); - crc = crc32(crc, (const Bytef *)"IDAT", 4); - - z.zalloc = NULL; - z.zfree = NULL; - z.opaque = NULL; - - if(deflateInit(&z, Z_BEST_COMPRESSION) != Z_OK) - goto error; - - z.next_out = (Bytef *)buf; - z.avail_out = sizeof(buf); - - for(i = 0; i < h; i++) - { - uchar filter = 0; - for(j = 0; j < 2; j++) - { - z.next_in = j ? (Bytef *)data + (flip ? h-i-1 : i)*w*bpp : (Bytef *)&filter; - z.avail_in = j ? w*bpp : 1; - while(z.avail_in > 0) - { - if(deflate(&z, Z_NO_FLUSH) != Z_OK) goto cleanuperror; - #define FLUSHZ do { \ - int flush = sizeof(buf) - z.avail_out; \ - crc = crc32(crc, buf, flush); \ - len += flush; \ - fwrite(buf, 1, flush, f); \ - z.next_out = (Bytef *)buf; \ - z.avail_out = sizeof(buf); \ - } while(0) - FLUSHZ; - } - } - } - - for(;;) - { - int err = deflate(&z, Z_FINISH); - if(err != Z_OK && err != Z_STREAM_END) goto cleanuperror; - FLUSHZ; - if(err == Z_STREAM_END) break; - } - - deflateEnd(&z); - - fseek(f, idat, SEEK_SET); - writebig(f, len); - fseek(f, 0, SEEK_END); - writebig(f, crc); - - writepngchunk(f, "IEND", NULL, 0); - - fclose(f); - return; + const uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + struct pngihdr ihdr; + FILE *f; + long idat; + uint len, crc; + z_stream z; + uchar buf[1<<12]; + int i, j; + + memset(&ihdr, 0, sizeof(ihdr)); + ihdr.width = bigswap(w); + ihdr.height = bigswap(h); + ihdr.bitdepth = 8; + switch(bpp) + { + case 1: ihdr.colortype = 0; break; + case 2: ihdr.colortype = 4; break; + case 3: ihdr.colortype = 2; break; + case 4: ihdr.colortype = 6; break; + default: fatal("cube2font: invalid PNG bpp"); return; + } + f = fopen(filename, "wb"); + if(!f) { fatal("cube2font: could not write to %s", filename); return; } + + fwrite(signature, 1, sizeof(signature), f); + + writepngchunk(f, "IHDR", (uchar *)&ihdr, 13); + + idat = ftell(f); + len = 0; + fwrite("\0\0\0\0IDAT", 1, 8, f); + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, (const Bytef *)"IDAT", 4); + + z.zalloc = NULL; + z.zfree = NULL; + z.opaque = NULL; + + if(deflateInit(&z, Z_BEST_COMPRESSION) != Z_OK) + goto error; + + z.next_out = (Bytef *)buf; + z.avail_out = sizeof(buf); + + for(i = 0; i < h; i++) + { + uchar filter = 0; + for(j = 0; j < 2; j++) + { + z.next_in = j ? (Bytef *)data + (flip ? h-i-1 : i)*w*bpp : (Bytef *)&filter; + z.avail_in = j ? w*bpp : 1; + while(z.avail_in > 0) + { + if(deflate(&z, Z_NO_FLUSH) != Z_OK) goto cleanuperror; + #define FLUSHZ do { \ + int flush = sizeof(buf) - z.avail_out; \ + crc = crc32(crc, buf, flush); \ + len += flush; \ + fwrite(buf, 1, flush, f); \ + z.next_out = (Bytef *)buf; \ + z.avail_out = sizeof(buf); \ + } while(0) + FLUSHZ; + } + } + } + + for(;;) + { + int err = deflate(&z, Z_FINISH); + if(err != Z_OK && err != Z_STREAM_END) goto cleanuperror; + FLUSHZ; + if(err == Z_STREAM_END) break; + } + + deflateEnd(&z); + + fseek(f, idat, SEEK_SET); + writebig(f, len); + fseek(f, 0, SEEK_END); + writebig(f, crc); + + writepngchunk(f, "IEND", NULL, 0); + + fclose(f); + return; cleanuperror: - deflateEnd(&z); + deflateEnd(&z); error: - fclose(f); + fclose(f); - fatal("cube2font: failed saving PNG to %s", filename); + fatal("cube2font: failed saving PNG to %s", filename); } enum { - CT_PRINT = 1<<0, - CT_SPACE = 1<<1, - CT_DIGIT = 1<<2, - CT_ALPHA = 1<<3, - CT_LOWER = 1<<4, - CT_UPPER = 1<<5, - CT_UNICODE = 1<<6 + CT_PRINT = 1<<0, + CT_SPACE = 1<<1, + CT_DIGIT = 1<<2, + CT_ALPHA = 1<<3, + CT_LOWER = 1<<4, + CT_UPPER = 1<<5, + CT_UNICODE = 1<<6 }; #define CUBECTYPE(s, p, d, a, A, u, U) \ - 0, U, U, U, U, U, U, U, U, s, s, s, s, s, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - s, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, \ - d, d, d, d, d, d, d, d, d, d, p, p, p, p, p, p, \ - p, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, \ - A, A, A, A, A, A, A, A, A, A, A, p, p, p, p, p, \ - p, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, \ - a, a, a, a, a, a, a, a, a, a, a, p, p, p, p, U, \ - U, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, U, u, U, u, U, u, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - U, U, U, U, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, u + 0, U, U, U, U, U, U, U, U, s, s, s, s, s, U, U, \ + U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ + s, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, \ + d, d, d, d, d, d, d, d, d, d, p, p, p, p, p, p, \ + p, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, \ + A, A, A, A, A, A, A, A, A, A, A, p, p, p, p, p, \ + p, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, \ + a, a, a, a, a, a, a, a, a, a, a, p, p, p, p, U, \ + U, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, \ + u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, \ + u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ + u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ + u, U, u, U, u, U, u, U, U, u, U, u, U, u, U, U, \ + U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ + U, U, U, U, u, u, u, u, u, u, u, u, u, u, u, u, \ + u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, u const uchar cubectype[256] = { - CUBECTYPE(CT_SPACE, - CT_PRINT, - CT_PRINT|CT_DIGIT, - CT_PRINT|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_ALPHA|CT_UPPER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_UPPER) + CUBECTYPE(CT_SPACE, + CT_PRINT, + CT_PRINT|CT_DIGIT, + CT_PRINT|CT_ALPHA|CT_LOWER, + CT_PRINT|CT_ALPHA|CT_UPPER, + CT_PRINT|CT_UNICODE|CT_ALPHA|CT_LOWER, + CT_PRINT|CT_UNICODE|CT_ALPHA|CT_UPPER) }; int iscubeprint(uchar c) { return cubectype[c]&CT_PRINT; } int iscubespace(uchar c) { return cubectype[c]&CT_SPACE; } @@ -201,46 +201,46 @@ int iscubelower(uchar c) { return cubectype[c]&CT_LOWER; } int iscubeupper(uchar c) { return cubectype[c]&CT_UPPER; } const int cube2unichars[256] = { - 0, 192, 193, 194, 195, 196, 197, 198, 199, 9, 10, 11, 12, 13, 200, 201, - 202, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 220, - 221, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, - 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 0x104, - 0x105, 0x106, 0x107, 0x10C, 0x10D, 0x10E, 0x10F, 0x118, 0x119, 0x11A, 0x11B, 0x11E, 0x11F, 0x130, 0x131, 0x141, - 0x142, 0x143, 0x144, 0x147, 0x148, 0x150, 0x151, 0x152, 0x153, 0x158, 0x159, 0x15A, 0x15B, 0x15E, 0x15F, 0x160, - 0x161, 0x164, 0x165, 0x16E, 0x16F, 0x170, 0x171, 0x178, 0x179, 0x17A, 0x17B, 0x17C, 0x17D, 0x17E, 0x404, 0x411, - 0x413, 0x414, 0x416, 0x417, 0x418, 0x419, 0x41B, 0x41F, 0x423, 0x424, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, - 0x42C, 0x42D, 0x42E, 0x42F, 0x431, 0x432, 0x433, 0x434, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, - 0x43F, 0x442, 0x444, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F, 0x454, 0x490, 0x491 + 0, 192, 193, 194, 195, 196, 197, 198, 199, 9, 10, 11, 12, 13, 200, 201, + 202, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 220, + 221, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, + 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 0x104, + 0x105, 0x106, 0x107, 0x10C, 0x10D, 0x10E, 0x10F, 0x118, 0x119, 0x11A, 0x11B, 0x11E, 0x11F, 0x130, 0x131, 0x141, + 0x142, 0x143, 0x144, 0x147, 0x148, 0x150, 0x151, 0x152, 0x153, 0x158, 0x159, 0x15A, 0x15B, 0x15E, 0x15F, 0x160, + 0x161, 0x164, 0x165, 0x16E, 0x16F, 0x170, 0x171, 0x178, 0x179, 0x17A, 0x17B, 0x17C, 0x17D, 0x17E, 0x404, 0x411, + 0x413, 0x414, 0x416, 0x417, 0x418, 0x419, 0x41B, 0x41F, 0x423, 0x424, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, + 0x42C, 0x42D, 0x42E, 0x42F, 0x431, 0x432, 0x433, 0x434, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, + 0x43F, 0x442, 0x444, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F, 0x454, 0x490, 0x491 }; int cube2uni(uchar c) { - return cube2unichars[c]; + return cube2unichars[c]; } const char *encodeutf8(int uni) { - static char buf[7]; - char *dst = buf; - if(uni <= 0x7F) { *dst++ = uni; goto uni1; } - else if(uni <= 0x7FF) { *dst++ = 0xC0 | (uni>>6); goto uni2; } - else if(uni <= 0xFFFF) { *dst++ = 0xE0 | (uni>>12); goto uni3; } - else if(uni <= 0x1FFFFF) { *dst++ = 0xF0 | (uni>>18); goto uni4; } - else if(uni <= 0x3FFFFFF) { *dst++ = 0xF8 | (uni>>24); goto uni5; } - else if(uni <= 0x7FFFFFFF) { *dst++ = 0xFC | (uni>>30); goto uni6; } - else goto uni1; + static char buf[7]; + char *dst = buf; + if(uni <= 0x7F) { *dst++ = uni; goto uni1; } + else if(uni <= 0x7FF) { *dst++ = 0xC0 | (uni>>6); goto uni2; } + else if(uni <= 0xFFFF) { *dst++ = 0xE0 | (uni>>12); goto uni3; } + else if(uni <= 0x1FFFFF) { *dst++ = 0xF0 | (uni>>18); goto uni4; } + else if(uni <= 0x3FFFFFF) { *dst++ = 0xF8 | (uni>>24); goto uni5; } + else if(uni <= 0x7FFFFFFF) { *dst++ = 0xFC | (uni>>30); goto uni6; } + else goto uni1; uni6: *dst++ = 0x80 | ((uni>>24)&0x3F); uni5: *dst++ = 0x80 | ((uni>>18)&0x3F); uni4: *dst++ = 0x80 | ((uni>>12)&0x3F); uni3: *dst++ = 0x80 | ((uni>>6)&0x3F); uni2: *dst++ = 0x80 | (uni&0x3F); uni1: *dst++ = '\0'; - return buf; + return buf; } struct fontchar { int code, uni, tex, x, y, w, h, offx, offy, offset, advance; FT_BitmapGlyph color, alpha; }; @@ -249,308 +249,308 @@ const char *texdir = ""; const char *texfilename(const char *name, int texnum) { - static char file[256]; - snprintf(file, sizeof(file), "%s%d.png", name, texnum); - return file; + static char file[256]; + snprintf(file, sizeof(file), "%s%d.png", name, texnum); + return file; } const char *texname(const char *name, int texnum) { - static char file[512]; - snprintf(file, sizeof(file), "%s%s", texdir, texfilename(name, texnum)); - return file; + static char file[512]; + snprintf(file, sizeof(file), "%s%s", texdir, texfilename(name, texnum)); + return file; } void writetexs(const char *name, struct fontchar *chars, int numchars, int numtexs, int tw, int th) { - int tex; - uchar *pixels = (uchar *)malloc(tw*th*2); - if(!pixels) fatal("cube2font: failed allocating textures"); - for(tex = 0; tex < numtexs; tex++) - { - const char *file = texfilename(name, tex); - int texchars = 0, i; - uchar *dst, *src; - memset(pixels, 0, tw*th*2); - for(i = 0; i < numchars; i++) - { - struct fontchar *c = &chars[i]; - int x, y; - if(c->tex != tex) continue; - texchars++; - dst = &pixels[2*((c->y + c->offy - c->color->top)*tw + c->x + c->color->left - c->offx)]; - src = (uchar *)c->color->bitmap.buffer; - for(y = 0; y < c->color->bitmap.rows; y++) - { - for(x = 0; x < c->color->bitmap.width; x++) - dst[2*x] = src[x]; - src += c->color->bitmap.pitch; - dst += 2*tw; - } - dst = &pixels[2*((c->y + c->offy - c->alpha->top)*tw + c->x + c->alpha->left - c->offx)]; - src = (uchar *)c->alpha->bitmap.buffer; - for(y = 0; y < c->alpha->bitmap.rows; y++) - { - for(x = 0; x < c->alpha->bitmap.width; x++) - dst[2*x+1] = src[x]; - src += c->alpha->bitmap.pitch; - dst += 2*tw; - } - } - printf("cube2font: writing %d chars to %s\n", texchars, file); - savepng(file, pixels, tw, th, 2, 0); + int tex; + uchar *pixels = (uchar *)malloc(tw*th*2); + if(!pixels) fatal("cube2font: failed allocating textures"); + for(tex = 0; tex < numtexs; tex++) + { + const char *file = texfilename(name, tex); + int texchars = 0, i; + uchar *dst, *src; + memset(pixels, 0, tw*th*2); + for(i = 0; i < numchars; i++) + { + struct fontchar *c = &chars[i]; + int x, y; + if(c->tex != tex) continue; + texchars++; + dst = &pixels[2*((c->y + c->offy - c->color->top)*tw + c->x + c->color->left - c->offx)]; + src = (uchar *)c->color->bitmap.buffer; + for(y = 0; y < c->color->bitmap.rows; y++) + { + for(x = 0; x < c->color->bitmap.width; x++) + dst[2*x] = src[x]; + src += c->color->bitmap.pitch; + dst += 2*tw; + } + dst = &pixels[2*((c->y + c->offy - c->alpha->top)*tw + c->x + c->alpha->left - c->offx)]; + src = (uchar *)c->alpha->bitmap.buffer; + for(y = 0; y < c->alpha->bitmap.rows; y++) + { + for(x = 0; x < c->alpha->bitmap.width; x++) + dst[2*x+1] = src[x]; + src += c->alpha->bitmap.pitch; + dst += 2*tw; + } + } + printf("cube2font: writing %d chars to %s\n", texchars, file); + savepng(file, pixels, tw, th, 2, 0); } free(pixels); } void writecfg(const char *name, struct fontchar *chars, int numchars, int x1, int y1, int x2, int y2, int sw, int sh, int argc, char **argv) { - FILE *f; - char file[256]; - int i, lastcode = 0, lasttex = 0; - snprintf(file, sizeof(file), "%s.cfg", name); - f = fopen(file, "w"); - if(!f) fatal("cube2font: failed writing %s", file); - printf("cube2font: writing %d chars to %s\n", numchars, file); - fprintf(f, "//"); - for(i = 1; i < argc; i++) - fprintf(f, " %s", argv[i]); - fprintf(f, "\n"); - fprintf(f, "font \"%s\" \"%s\" %d %d\n", name, texname(name, 0), sw, sh); - for(i = 0; i < numchars; i++) - { - struct fontchar *c = &chars[i]; - if(!lastcode && lastcode < c->code) - { - fprintf(f, "fontoffset \"%s\"\n", encodeutf8(c->uni)); - lastcode = c->code; - } - else if(lastcode < c->code) - { - if(lastcode + 1 == c->code) - fprintf(f, "fontskip // %d\n", lastcode); - else - fprintf(f, "fontskip %d // %d .. %d\n", c->code - lastcode, lastcode, c->code-1); - lastcode = c->code; - } - if(lasttex != c->tex) - { - fprintf(f, "\nfonttex \"%s\"\n", texname(name, c->tex)); - lasttex = c->tex; - } - if(c->code != c->uni) - fprintf(f, "fontchar %d %d %d %d %d %d %d // %s (%d -> 0x%X)\n", c->x, c->y, c->w, c->h, c->offx+c->offset, y2-c->offy, c->advance, encodeutf8(c->uni), c->code, c->uni); - else - fprintf(f, "fontchar %d %d %d %d %d %d %d // %s (%d)\n", c->x, c->y, c->w, c->h, c->offx+c->offset, y2-c->offy, c->advance, encodeutf8(c->uni), c->code); - lastcode++; - } - fclose(f); + FILE *f; + char file[256]; + int i, lastcode = 0, lasttex = 0; + snprintf(file, sizeof(file), "%s.cfg", name); + f = fopen(file, "w"); + if(!f) fatal("cube2font: failed writing %s", file); + printf("cube2font: writing %d chars to %s\n", numchars, file); + fprintf(f, "//"); + for(i = 1; i < argc; i++) + fprintf(f, " %s", argv[i]); + fprintf(f, "\n"); + fprintf(f, "font \"%s\" \"%s\" %d %d\n", name, texname(name, 0), sw, sh); + for(i = 0; i < numchars; i++) + { + struct fontchar *c = &chars[i]; + if(!lastcode && lastcode < c->code) + { + fprintf(f, "fontoffset \"%s\"\n", encodeutf8(c->uni)); + lastcode = c->code; + } + else if(lastcode < c->code) + { + if(lastcode + 1 == c->code) + fprintf(f, "fontskip // %d\n", lastcode); + else + fprintf(f, "fontskip %d // %d .. %d\n", c->code - lastcode, lastcode, c->code-1); + lastcode = c->code; + } + if(lasttex != c->tex) + { + fprintf(f, "\nfonttex \"%s\"\n", texname(name, c->tex)); + lasttex = c->tex; + } + if(c->code != c->uni) + fprintf(f, "fontchar %d %d %d %d %d %d %d // %s (%d -> 0x%X)\n", c->x, c->y, c->w, c->h, c->offx+c->offset, y2-c->offy, c->advance, encodeutf8(c->uni), c->code, c->uni); + else + fprintf(f, "fontchar %d %d %d %d %d %d %d // %s (%d)\n", c->x, c->y, c->w, c->h, c->offx+c->offset, y2-c->offy, c->advance, encodeutf8(c->uni), c->code); + lastcode++; + } + fclose(f); } int groupchar(int c) { - switch(c) - { - case 0x152: case 0x153: case 0x178: return 1; - } - if(c < 127 || c >= 0x2000) return 0; - if(c < 0x100) return 1; - if(c < 0x400) return 2; - return 3; + switch(c) + { + case 0x152: case 0x153: case 0x178: return 1; + } + if(c < 127 || c >= 0x2000) return 0; + if(c < 0x100) return 1; + if(c < 0x400) return 2; + return 3; } int sortchars(const void *x, const void *y) { - const struct fontchar *xc = *(const struct fontchar **)x, *yc = *(const struct fontchar **)y; - int xg = groupchar(xc->uni), yg = groupchar(yc->uni); - if(xg < yg) return -1; - if(xg > yg) return 1; - if(xc->h != yc->h) return yc->h - xc->h; - if(xc->w != yc->w) return yc->w - xc->w; - return yc->uni - xc->uni; + const struct fontchar *xc = *(const struct fontchar **)x, *yc = *(const struct fontchar **)y; + int xg = groupchar(xc->uni), yg = groupchar(yc->uni); + if(xg < yg) return -1; + if(xg > yg) return 1; + if(xc->h != yc->h) return yc->h - xc->h; + if(xc->w != yc->w) return yc->w - xc->w; + return yc->uni - xc->uni; } int scorechar(struct fontchar *f, int pad, int tw, int th, int rw, int rh, int ry) { - int score = 0; - if(rw + f->w > tw) { ry += rh + pad; score = 1; } - if(ry + f->h > th) score = 2; - return score; + int score = 0; + if(rw + f->w > tw) { ry += rh + pad; score = 1; } + if(ry + f->h > th) score = 2; + return score; } int main(int argc, char **argv) { - FT_Library l; - FT_Face f; - FT_Stroker s, s2; - int i, pad, offset, advance, w, h, tw, th, c, trial = -2, rw = 0, rh = 0, ry = 0, x1 = INT_MAX, x2 = INT_MIN, y1 = INT_MAX, y2 = INT_MIN, w2 = 0, h2 = 0, sw = 0, sh = 0; - float outborder = 0, inborder = 0; - struct fontchar chars[256]; - struct fontchar *order[256]; - int numchars = 0, numtex = 0; - if(argc < 11) - fatal("Usage: cube2font infile outfile outborder[:inborder] pad offset advance charwidth charheight texwidth texheight [spacewidth spaceheight texdir]"); - sscanf(argv[3], "%f:%f", &outborder, &inborder); - pad = atoi(argv[4]); - offset = atoi(argv[5]); - advance = atoi(argv[6]); - w = atoi(argv[7]); - h = atoi(argv[8]); - tw = atoi(argv[9]); - th = atoi(argv[10]); - if(argc > 11) sw = atoi(argv[11]); - if(argc > 12) sh = atoi(argv[12]); - if(argc > 13) texdir = argv[13]; - if(FT_Init_FreeType(&l)) - fatal("cube2font: failed initing freetype"); - if(FT_New_Face(l, argv[1], 0, &f) || - FT_Set_Charmap(f, f->charmaps[0]) || - FT_Set_Pixel_Sizes(f, w, h) || - FT_Stroker_New(l, &s) || - FT_Stroker_New(l, &s2)) - fatal("cube2font: failed loading font %s", argv[1]); - if(outborder > 0) FT_Stroker_Set(s, (FT_Fixed)(outborder * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); - if(inborder > 0) FT_Stroker_Set(s2, (FT_Fixed)(inborder * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); - for(c = 0; c < 256; c++) if(iscubeprint(c)) - { - FT_Glyph p, p2; - FT_BitmapGlyph b, b2; - struct fontchar *dst = &chars[numchars]; - dst->code = c; - dst->uni = cube2uni(c); - if(FT_Load_Char(f, dst->uni, FT_LOAD_DEFAULT)) - fatal("cube2font: failed loading character %s", encodeutf8(dst->uni)); - FT_Get_Glyph(f->glyph, &p); - p2 = p; - if(outborder > 0) FT_Glyph_StrokeBorder(&p, s, 0, 0); - if(inborder > 0) FT_Glyph_StrokeBorder(&p2, s2, 1, 0); - FT_Glyph_To_Bitmap(&p, FT_RENDER_MODE_NORMAL, 0, 1); - if(inborder > 0 || outborder > 0) FT_Glyph_To_Bitmap(&p2, FT_RENDER_MODE_NORMAL, 0, 1); - else p2 = p; - b = (FT_BitmapGlyph)p; - b2 = (FT_BitmapGlyph)p2; - dst->tex = -1; - dst->x = INT_MIN; - dst->y = INT_MIN; - dst->offx = imin(b->left, b2->left); - dst->offy = imax(b->top, b2->top); - dst->offset = offset; - dst->advance = offset + ((p->advance.x+0xFFFF)>>16) + advance; - dst->w = imax(b->left + b->bitmap.width, b2->left + b2->bitmap.width) - dst->offx; - dst->h = dst->offy - imin(b->top - b->bitmap.rows, b2->top - b2->bitmap.rows); - dst->alpha = b; - dst->color = b2; - order[numchars++] = dst; - } - qsort(order, numchars, sizeof(order[0]), sortchars); - for(i = 0; i < numchars;) - { - struct fontchar *dst; - int j, k, trial0, prevscore, dstscore, fitscore; - for(trial0 = trial, prevscore = -1; (trial -= 2) >= trial0-512;) - { - int g, fw = rw, fh = rh, fy = ry, curscore = 0, reused = 0; - for(j = i; j < numchars; j++) - { - dst = order[j]; - if(dst->tex >= 0 || dst->tex <= trial) continue; - g = groupchar(dst->uni); - dstscore = scorechar(dst, pad, tw, th, fw, fh, fy); - for(k = j; k < numchars; k++) - { - struct fontchar *fit = order[k]; - if(fit->tex >= 0 || fit->tex <= trial) continue; - if(fit->tex >= trial0 && groupchar(fit->uni) != g) break; - fitscore = scorechar(fit, pad, tw, th, fw, fh, fy); - if(fitscore < dstscore || (fitscore == dstscore && fit->h > dst->h)) - { - dst = fit; - dstscore = fitscore; - } - } - if(fw + dst->w > tw) - { - fy += fh + pad; - fw = fh = 0; - } - if(fy + dst->h > th) - { - fy = fw = fh = 0; - if(curscore > 0) break; - } - if(dst->tex >= trial+1 && dst->tex <= trial+2) { dst->tex = trial; reused++; } - else dst->tex = trial; - fw += dst->w + pad; - fh = imax(fh, dst->h); - if(dst != order[j]) --j; - curscore++; - } - if(reused < prevscore || curscore <= prevscore) break; - prevscore = curscore; - } - for(; i < numchars; i++) - { - dst = order[i]; - if(dst->tex >= 0) continue; - dstscore = scorechar(dst, pad, tw, th, rw, rh, ry); - for(j = i; j < numchars; j++) - { - struct fontchar *fit = order[j]; - if(fit->tex < trial || fit->tex > trial+2) continue; - fitscore = scorechar(fit, pad, tw, th, rw, rh, ry); - if(fitscore < dstscore || (fitscore == dstscore && fit->h > dst->h)) - { - dst = fit; - dstscore = fitscore; - } - } - if(dst->tex < trial || dst->tex > trial+2) break; - if(rw + dst->w > tw) - { - ry += rh + pad; - rw = rh = 0; - } - if(ry + dst->h > th) - { - ry = rw = rh = 0; - numtex++; - } - dst->tex = numtex; - dst->x = rw; - dst->y = ry; - rw += dst->w + pad; - rh = imax(rh, dst->h); - y1 = imin(y1, dst->offy - dst->h); - y2 = imax(y2, dst->offy); - x1 = imin(x1, dst->offx); - x2 = imax(x2, dst->offx + dst->w); - w2 = imax(w2, dst->w); - h2 = imax(h2, dst->h); - if(dst != order[i]) --i; - } - } - if(rh > 0) numtex++; + FT_Library l; + FT_Face f; + FT_Stroker s, s2; + int i, pad, offset, advance, w, h, tw, th, c, trial = -2, rw = 0, rh = 0, ry = 0, x1 = INT_MAX, x2 = INT_MIN, y1 = INT_MAX, y2 = INT_MIN, w2 = 0, h2 = 0, sw = 0, sh = 0; + float outborder = 0, inborder = 0; + struct fontchar chars[256]; + struct fontchar *order[256]; + int numchars = 0, numtex = 0; + if(argc < 11) + fatal("Usage: cube2font infile outfile outborder[:inborder] pad offset advance charwidth charheight texwidth texheight [spacewidth spaceheight texdir]"); + sscanf(argv[3], "%f:%f", &outborder, &inborder); + pad = atoi(argv[4]); + offset = atoi(argv[5]); + advance = atoi(argv[6]); + w = atoi(argv[7]); + h = atoi(argv[8]); + tw = atoi(argv[9]); + th = atoi(argv[10]); + if(argc > 11) sw = atoi(argv[11]); + if(argc > 12) sh = atoi(argv[12]); + if(argc > 13) texdir = argv[13]; + if(FT_Init_FreeType(&l)) + fatal("cube2font: failed initing freetype"); + if(FT_New_Face(l, argv[1], 0, &f) || + FT_Set_Charmap(f, f->charmaps[0]) || + FT_Set_Pixel_Sizes(f, w, h) || + FT_Stroker_New(l, &s) || + FT_Stroker_New(l, &s2)) + fatal("cube2font: failed loading font %s", argv[1]); + if(outborder > 0) FT_Stroker_Set(s, (FT_Fixed)(outborder * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + if(inborder > 0) FT_Stroker_Set(s2, (FT_Fixed)(inborder * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + for(c = 0; c < 256; c++) if(iscubeprint(c)) + { + FT_Glyph p, p2; + FT_BitmapGlyph b, b2; + struct fontchar *dst = &chars[numchars]; + dst->code = c; + dst->uni = cube2uni(c); + if(FT_Load_Char(f, dst->uni, FT_LOAD_DEFAULT)) + fatal("cube2font: failed loading character %s", encodeutf8(dst->uni)); + FT_Get_Glyph(f->glyph, &p); + p2 = p; + if(outborder > 0) FT_Glyph_StrokeBorder(&p, s, 0, 0); + if(inborder > 0) FT_Glyph_StrokeBorder(&p2, s2, 1, 0); + FT_Glyph_To_Bitmap(&p, FT_RENDER_MODE_NORMAL, 0, 1); + if(inborder > 0 || outborder > 0) FT_Glyph_To_Bitmap(&p2, FT_RENDER_MODE_NORMAL, 0, 1); + else p2 = p; + b = (FT_BitmapGlyph)p; + b2 = (FT_BitmapGlyph)p2; + dst->tex = -1; + dst->x = INT_MIN; + dst->y = INT_MIN; + dst->offx = imin(b->left, b2->left); + dst->offy = imax(b->top, b2->top); + dst->offset = offset; + dst->advance = offset + ((p->advance.x+0xFFFF)>>16) + advance; + dst->w = imax(b->left + b->bitmap.width, b2->left + b2->bitmap.width) - dst->offx; + dst->h = dst->offy - imin(b->top - b->bitmap.rows, b2->top - b2->bitmap.rows); + dst->alpha = b; + dst->color = b2; + order[numchars++] = dst; + } + qsort(order, numchars, sizeof(order[0]), sortchars); + for(i = 0; i < numchars;) + { + struct fontchar *dst; + int j, k, trial0, prevscore, dstscore, fitscore; + for(trial0 = trial, prevscore = -1; (trial -= 2) >= trial0-512;) + { + int g, fw = rw, fh = rh, fy = ry, curscore = 0, reused = 0; + for(j = i; j < numchars; j++) + { + dst = order[j]; + if(dst->tex >= 0 || dst->tex <= trial) continue; + g = groupchar(dst->uni); + dstscore = scorechar(dst, pad, tw, th, fw, fh, fy); + for(k = j; k < numchars; k++) + { + struct fontchar *fit = order[k]; + if(fit->tex >= 0 || fit->tex <= trial) continue; + if(fit->tex >= trial0 && groupchar(fit->uni) != g) break; + fitscore = scorechar(fit, pad, tw, th, fw, fh, fy); + if(fitscore < dstscore || (fitscore == dstscore && fit->h > dst->h)) + { + dst = fit; + dstscore = fitscore; + } + } + if(fw + dst->w > tw) + { + fy += fh + pad; + fw = fh = 0; + } + if(fy + dst->h > th) + { + fy = fw = fh = 0; + if(curscore > 0) break; + } + if(dst->tex >= trial+1 && dst->tex <= trial+2) { dst->tex = trial; reused++; } + else dst->tex = trial; + fw += dst->w + pad; + fh = imax(fh, dst->h); + if(dst != order[j]) --j; + curscore++; + } + if(reused < prevscore || curscore <= prevscore) break; + prevscore = curscore; + } + for(; i < numchars; i++) + { + dst = order[i]; + if(dst->tex >= 0) continue; + dstscore = scorechar(dst, pad, tw, th, rw, rh, ry); + for(j = i; j < numchars; j++) + { + struct fontchar *fit = order[j]; + if(fit->tex < trial || fit->tex > trial+2) continue; + fitscore = scorechar(fit, pad, tw, th, rw, rh, ry); + if(fitscore < dstscore || (fitscore == dstscore && fit->h > dst->h)) + { + dst = fit; + dstscore = fitscore; + } + } + if(dst->tex < trial || dst->tex > trial+2) break; + if(rw + dst->w > tw) + { + ry += rh + pad; + rw = rh = 0; + } + if(ry + dst->h > th) + { + ry = rw = rh = 0; + numtex++; + } + dst->tex = numtex; + dst->x = rw; + dst->y = ry; + rw += dst->w + pad; + rh = imax(rh, dst->h); + y1 = imin(y1, dst->offy - dst->h); + y2 = imax(y2, dst->offy); + x1 = imin(x1, dst->offx); + x2 = imax(x2, dst->offx + dst->w); + w2 = imax(w2, dst->w); + h2 = imax(h2, dst->h); + if(dst != order[i]) --i; + } + } + if(rh > 0) numtex++; #if 0 - if(sw <= 0) - { - if(FT_Load_Char(f, ' ', FT_LOAD_DEFAULT)) - fatal("cube2font: failed loading space character"); - sw = (f->glyph->advance.x+0x3F)>>6; - } + if(sw <= 0) + { + if(FT_Load_Char(f, ' ', FT_LOAD_DEFAULT)) + fatal("cube2font: failed loading space character"); + sw = (f->glyph->advance.x+0x3F)>>6; + } #endif - if(sh <= 0) sh = y2 - y1; - if(sw <= 0) sw = sh/3; - writetexs(argv[2], chars, numchars, numtex, tw, th); - writecfg(argv[2], chars, numchars, x1, y1, x2, y2, sw, sh, argc, argv); - for(i = 0; i < numchars; i++) - { - if(chars[i].alpha != chars[i].color) FT_Done_Glyph((FT_Glyph)chars[i].alpha); - FT_Done_Glyph((FT_Glyph)chars[i].color); - } - FT_Stroker_Done(s); - FT_Stroker_Done(s2); - FT_Done_FreeType(l); - printf("cube2font: (%d, %d) .. (%d, %d) = (%d, %d) / (%d, %d), %d texs\n", x1, y1, x2, y2, x2 - x1, y2 - y1, w2, h2, numtex); - return EXIT_SUCCESS; + if(sh <= 0) sh = y2 - y1; + if(sw <= 0) sw = sh/3; + writetexs(argv[2], chars, numchars, numtex, tw, th); + writecfg(argv[2], chars, numchars, x1, y1, x2, y2, sw, sh, argc, argv); + for(i = 0; i < numchars; i++) + { + if(chars[i].alpha != chars[i].color) FT_Done_Glyph((FT_Glyph)chars[i].alpha); + FT_Done_Glyph((FT_Glyph)chars[i].color); + } + FT_Stroker_Done(s); + FT_Stroker_Done(s2); + FT_Done_FreeType(l); + printf("cube2font: (%d, %d) .. (%d, %d) = (%d, %d) / (%d, %d), %d texs\n", x1, y1, x2, y2, x2 - x1, y2 - y1, w2, h2, numtex); + return EXIT_SUCCESS; } diff --git a/src/shared/ents.h b/src/shared/ents.h index 91ccd8f..ac6fa39 100644 --- a/src/shared/ents.h +++ b/src/shared/ents.h @@ -5,57 +5,57 @@ enum { ET_EMPTY=0, ET_LIGHT, ET_MAPMODEL, ET_PLAYERSTART, ET_ENVMAP, ET_PARTICLES, ET_SOUND, ET_SPOTLIGHT, ET_GAMESPECIFIC }; -struct entity // persistent map entity +struct entity // persistent map entity { - vec o; // position - short attr1, attr2, attr3, attr4, attr5; - uchar type; // type is one of the above - uchar reserved; + vec o; // position + short attr1, attr2, attr3, attr4, attr5; + uchar type; // type is one of the above + uchar reserved; }; struct entitylight { - vec color, dir; - int millis; + vec color, dir; + int millis; - entitylight() : color(1, 1, 1), dir(0, 0, 1), millis(-1) {} + entitylight() : color(1, 1, 1), dir(0, 0, 1), millis(-1) {} }; enum { - EF_NOVIS = 1<<0, - EF_NOSHADOW = 1<<1, - EF_NOCOLLIDE = 1<<2, - EF_ANIM = 1<<3, - EF_OCTA = 1<<4, - EF_RENDER = 1<<5, - EF_SOUND = 1<<6, - EF_SPAWNED = 1<<7, - EF_NOPICKUP = 1<<8 + EF_NOVIS = 1<<0, + EF_NOSHADOW = 1<<1, + EF_NOCOLLIDE = 1<<2, + EF_ANIM = 1<<3, + EF_OCTA = 1<<4, + EF_RENDER = 1<<5, + EF_SOUND = 1<<6, + EF_SPAWNED = 1<<7, + EF_NOPICKUP = 1<<8 }; -struct extentity : entity // part of the entity that doesn't get saved to disk +struct extentity : entity // part of the entity that doesn't get saved to disk { - int flags; // the only dynamic state of a map entity - entitylight light; - extentity *attached; + int flags; // the only dynamic state of a map entity + entitylight light; + extentity *attached; - extentity() : flags(0), attached(NULL) {} + extentity() : flags(0), attached(NULL) {} - bool spawned() const { return (flags&EF_SPAWNED) != 0; } - void setspawned(bool val) { if(val) flags |= EF_SPAWNED; else flags &= ~EF_SPAWNED; } - void setspawned() { flags |= EF_SPAWNED; } - void clearspawned() { flags &= ~EF_SPAWNED; } + bool spawned() const { return (flags&EF_SPAWNED) != 0; } + void setspawned(bool val) { if(val) flags |= EF_SPAWNED; else flags &= ~EF_SPAWNED; } + void setspawned() { flags |= EF_SPAWNED; } + void clearspawned() { flags &= ~EF_SPAWNED; } - bool nopickup() const { return (flags&EF_NOPICKUP) != 0; } - void setnopickup(bool val) { if(val) flags |= EF_NOPICKUP; else flags &= ~EF_NOPICKUP; } - void setnopickup() { flags |= EF_NOPICKUP; } - void clearnopickup() { flags &= ~EF_NOPICKUP; } + bool nopickup() const { return (flags&EF_NOPICKUP) != 0; } + void setnopickup(bool val) { if(val) flags |= EF_NOPICKUP; else flags &= ~EF_NOPICKUP; } + void setnopickup() { flags |= EF_NOPICKUP; } + void clearnopickup() { flags &= ~EF_NOPICKUP; } }; #define MAXENTS 10000 -//extern vector ents; // map entities +//extern vector ents; // map entities enum { CS_ALIVE = 0, CS_DEAD, CS_SPAWNING, CS_LAGGED, CS_EDITING, CS_SPECTATOR }; @@ -65,128 +65,128 @@ enum { ENT_PLAYER = 0, ENT_AI, ENT_INANIMATE, ENT_CAMERA, ENT_BOUNCE }; enum { COLLIDE_NONE = 0, COLLIDE_ELLIPSE, COLLIDE_OBB, COLLIDE_ELLIPSE_PRECISE }; -struct physent // base entity type, can be affected by physics +struct physent // base entity type, can be affected by physics { - vec o, vel, falling; // origin, velocity - vec deltapos, newpos; // movement interpolation - float yaw, pitch, roll; - float maxspeed; // cubes per second, 100 for player - float radius, eyeheight, aboveeye; // bounding box size - float xradius, yradius, zmargin; - vec floor; // the normal of floor the dynent is on - - ushort timeinair; - uchar inwater; - bool jumping; - schar move, strafe; - - uchar physstate; // one of PHYS_* above - uchar state, editstate; // one of CS_* above - uchar type; // one of ENT_* above - uchar collidetype; // one of COLLIDE_* above - - bool blocked; // used by physics to signal ai - - physent() : o(0, 0, 0), deltapos(0, 0, 0), newpos(0, 0, 0), yaw(0), pitch(0), roll(0), maxspeed(100), - radius(4.1f), eyeheight(14), aboveeye(1), xradius(4.1f), yradius(4.1f), zmargin(0), - state(CS_ALIVE), editstate(CS_ALIVE), type(ENT_PLAYER), - collidetype(COLLIDE_ELLIPSE), - blocked(false) - { reset(); } - - void resetinterp() - { - newpos = o; - deltapos = vec(0, 0, 0); - } - - void reset() - { - inwater = 0; - timeinair = 0; - jumping = false; - strafe = move = 0; - physstate = PHYS_FALL; - vel = falling = vec(0, 0, 0); - floor = vec(0, 0, 1); - } - - vec feetpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset - eyeheight)); } - vec headpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset)); } - - bool maymove() const { return timeinair || physstate < PHYS_FLOOR || vel.squaredlen() > 1e-4f || deltapos.squaredlen() > 1e-4f; } + vec o, vel, falling; // origin, velocity + vec deltapos, newpos; // movement interpolation + float yaw, pitch, roll; + float maxspeed; // cubes per second, 100 for player + float radius, eyeheight, aboveeye; // bounding box size + float xradius, yradius, zmargin; + vec floor; // the normal of floor the dynent is on + + ushort timeinair; + uchar inwater; + bool jumping; + schar move, strafe; + + uchar physstate; // one of PHYS_* above + uchar state, editstate; // one of CS_* above + uchar type; // one of ENT_* above + uchar collidetype; // one of COLLIDE_* above + + bool blocked; // used by physics to signal ai + + physent() : o(0, 0, 0), deltapos(0, 0, 0), newpos(0, 0, 0), yaw(0), pitch(0), roll(0), maxspeed(100), + radius(4.1f), eyeheight(14), aboveeye(1), xradius(4.1f), yradius(4.1f), zmargin(0), + state(CS_ALIVE), editstate(CS_ALIVE), type(ENT_PLAYER), + collidetype(COLLIDE_ELLIPSE), + blocked(false) + { reset(); } + + void resetinterp() + { + newpos = o; + deltapos = vec(0, 0, 0); + } + + void reset() + { + inwater = 0; + timeinair = 0; + jumping = false; + strafe = move = 0; + physstate = PHYS_FALL; + vel = falling = vec(0, 0, 0); + floor = vec(0, 0, 1); + } + + vec feetpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset - eyeheight)); } + vec headpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset)); } + + bool maymove() const { return timeinair || physstate < PHYS_FLOOR || vel.squaredlen() > 1e-4f || deltapos.squaredlen() > 1e-4f; } }; enum { - ANIM_DEAD = 0, ANIM_DYING, ANIM_IDLE, - ANIM_FORWARD, ANIM_BACKWARD, ANIM_LEFT, ANIM_RIGHT, - ANIM_HOLD1, ANIM_HOLD2, ANIM_HOLD3, ANIM_HOLD4, ANIM_HOLD5, ANIM_HOLD6, ANIM_HOLD7, - ANIM_ATTACK1, ANIM_ATTACK2, ANIM_ATTACK3, ANIM_ATTACK4, ANIM_ATTACK5, ANIM_ATTACK6, ANIM_ATTACK7, - ANIM_PAIN, - ANIM_JUMP, ANIM_SINK, ANIM_SWIM, - ANIM_EDIT, ANIM_LAG, ANIM_TAUNT, ANIM_WIN, ANIM_LOSE, - ANIM_GUN_IDLE, ANIM_GUN_SHOOT, - ANIM_VWEP_IDLE, ANIM_VWEP_SHOOT, ANIM_SHIELD, ANIM_POWERUP, - ANIM_MAPMODEL, - NUMANIMS + ANIM_DEAD = 0, ANIM_DYING, ANIM_IDLE, + ANIM_FORWARD, ANIM_BACKWARD, ANIM_LEFT, ANIM_RIGHT, + ANIM_HOLD1, ANIM_HOLD2, ANIM_HOLD3, ANIM_HOLD4, ANIM_HOLD5, ANIM_HOLD6, ANIM_HOLD7, + ANIM_ATTACK1, ANIM_ATTACK2, ANIM_ATTACK3, ANIM_ATTACK4, ANIM_ATTACK5, ANIM_ATTACK6, ANIM_ATTACK7, + ANIM_PAIN, + ANIM_JUMP, ANIM_SINK, ANIM_SWIM, + ANIM_EDIT, ANIM_LAG, ANIM_TAUNT, ANIM_WIN, ANIM_LOSE, + ANIM_GUN_IDLE, ANIM_GUN_SHOOT, + ANIM_VWEP_IDLE, ANIM_VWEP_SHOOT, ANIM_SHIELD, ANIM_POWERUP, + ANIM_MAPMODEL, + NUMANIMS }; static const char * const animnames[] = { - "dead", "dying", "idle", - "forward", "backward", "left", "right", - "hold 1", "hold 2", "hold 3", "hold 4", "hold 5", "hold 6", "hold 7", - "attack 1", "attack 2", "attack 3", "attack 4", "attack 5", "attack 6", "attack 7", - "pain", - "jump", "sink", "swim", - "edit", "lag", "taunt", "win", "lose", - "gun idle", "gun shoot", - "vwep idle", "vwep shoot", "shield", "powerup", - "mapmodel" + "dead", "dying", "idle", + "forward", "backward", "left", "right", + "hold 1", "hold 2", "hold 3", "hold 4", "hold 5", "hold 6", "hold 7", + "attack 1", "attack 2", "attack 3", "attack 4", "attack 5", "attack 6", "attack 7", + "pain", + "jump", "sink", "swim", + "edit", "lag", "taunt", "win", "lose", + "gun idle", "gun shoot", + "vwep idle", "vwep shoot", "shield", "powerup", + "mapmodel" }; -#define ANIM_ALL 0x7F -#define ANIM_INDEX 0x7F -#define ANIM_LOOP (1<<7) -#define ANIM_START (1<<8) -#define ANIM_END (1<<9) -#define ANIM_REVERSE (1<<10) -#define ANIM_CLAMP (ANIM_START|ANIM_END) -#define ANIM_DIR 0x780 +#define ANIM_ALL 0x7F +#define ANIM_INDEX 0x7F +#define ANIM_LOOP (1<<7) +#define ANIM_START (1<<8) +#define ANIM_END (1<<9) +#define ANIM_REVERSE (1<<10) +#define ANIM_CLAMP (ANIM_START|ANIM_END) +#define ANIM_DIR 0x780 #define ANIM_SECONDARY 11 -#define ANIM_NOSKIN (1<<22) -#define ANIM_SETTIME (1<<23) +#define ANIM_NOSKIN (1<<22) +#define ANIM_SETTIME (1<<23) #define ANIM_FULLBRIGHT (1<<24) -#define ANIM_REUSE (1<<25) -#define ANIM_NORENDER (1<<26) -#define ANIM_RAGDOLL (1<<27) -#define ANIM_SETSPEED (1<<28) -#define ANIM_NOPITCH (1<<29) -#define ANIM_GHOST (1<<30) -#define ANIM_FLAGS (0x1FF<<22) +#define ANIM_REUSE (1<<25) +#define ANIM_NORENDER (1<<26) +#define ANIM_RAGDOLL (1<<27) +#define ANIM_SETSPEED (1<<28) +#define ANIM_NOPITCH (1<<29) +#define ANIM_GHOST (1<<30) +#define ANIM_FLAGS (0x1FF<<22) struct animinfo // description of a character's animation { - int anim, frame, range, basetime; - float speed; - uint varseed; + int anim, frame, range, basetime; + float speed; + uint varseed; - animinfo() : anim(0), frame(0), range(0), basetime(0), speed(100.0f), varseed(0) { } + animinfo() : anim(0), frame(0), range(0), basetime(0), speed(100.0f), varseed(0) { } - bool operator==(const animinfo &o) const { return frame==o.frame && range==o.range && (anim&(ANIM_SETTIME|ANIM_DIR))==(o.anim&(ANIM_SETTIME|ANIM_DIR)) && (anim&ANIM_SETTIME || basetime==o.basetime) && speed==o.speed; } - bool operator!=(const animinfo &o) const { return frame!=o.frame || range!=o.range || (anim&(ANIM_SETTIME|ANIM_DIR))!=(o.anim&(ANIM_SETTIME|ANIM_DIR)) || (!(anim&ANIM_SETTIME) && basetime!=o.basetime) || speed!=o.speed; } + bool operator==(const animinfo &o) const { return frame==o.frame && range==o.range && (anim&(ANIM_SETTIME|ANIM_DIR))==(o.anim&(ANIM_SETTIME|ANIM_DIR)) && (anim&ANIM_SETTIME || basetime==o.basetime) && speed==o.speed; } + bool operator!=(const animinfo &o) const { return frame!=o.frame || range!=o.range || (anim&(ANIM_SETTIME|ANIM_DIR))!=(o.anim&(ANIM_SETTIME|ANIM_DIR)) || (!(anim&ANIM_SETTIME) && basetime!=o.basetime) || speed!=o.speed; } }; struct animinterpinfo // used for animation blending of animated characters { - animinfo prev, cur; - int lastswitch; - void *lastmodel; + animinfo prev, cur; + int lastswitch; + void *lastmodel; - animinterpinfo() : lastswitch(-1), lastmodel(NULL) {} + animinterpinfo() : lastswitch(-1), lastmodel(NULL) {} - void reset() { lastswitch = -1; } + void reset() { lastswitch = -1; } }; #define MAXANIMPARTS 3 @@ -194,44 +194,44 @@ struct animinterpinfo // used for animation blending of animated characters struct occludequery; struct ragdolldata; -struct dynent : physent // animated characters, or characters that can receive input +struct dynent : physent // animated characters, or characters that can receive input { - bool k_left, k_right, k_up, k_down; // see input code - - entitylight light; - animinterpinfo animinterp[MAXANIMPARTS]; - ragdolldata *ragdoll; - occludequery *query; - int lastrendered; - uchar occluded; - - dynent() : ragdoll(NULL), query(NULL), lastrendered(0), occluded(0) - { - reset(); - } - - ~dynent() - { + bool k_left, k_right, k_up, k_down; // see input code + + entitylight light; + animinterpinfo animinterp[MAXANIMPARTS]; + ragdolldata *ragdoll; + occludequery *query; + int lastrendered; + uchar occluded; + + dynent() : ragdoll(NULL), query(NULL), lastrendered(0), occluded(0) + { + reset(); + } + + ~dynent() + { #ifndef STANDALONE - extern void cleanragdoll(dynent *d); - if(ragdoll) cleanragdoll(this); + extern void cleanragdoll(dynent *d); + if(ragdoll) cleanragdoll(this); #endif - } - - void stopmoving() - { - k_left = k_right = k_up = k_down = jumping = false; - move = strafe = 0; - } - - void reset() - { - physent::reset(); - stopmoving(); - loopi(MAXANIMPARTS) animinterp[i].reset(); - } - - vec abovehead() { return vec(o).add(vec(0, 0, aboveeye+4)); } + } + + void stopmoving() + { + k_left = k_right = k_up = k_down = jumping = false; + move = strafe = 0; + } + + void reset() + { + physent::reset(); + stopmoving(); + loopi(MAXANIMPARTS) animinterp[i].reset(); + } + + vec abovehead() { return vec(o).add(vec(0, 0, aboveeye+4)); } }; diff --git a/src/shared/geom.cpp b/src/shared/geom.cpp index 43206e1..a5192e4 100644 --- a/src/shared/geom.cpp +++ b/src/shared/geom.cpp @@ -3,255 +3,255 @@ static inline double det2x2(double a, double b, double c, double d) { return a*d - b*c; } static inline double det3x3(double a1, double a2, double a3, - double b1, double b2, double b3, - double c1, double c2, double c3) + double b1, double b2, double b3, + double c1, double c2, double c3) { - return a1 * det2x2(b2, b3, c2, c3) - - b1 * det2x2(a2, a3, c2, c3) - + c1 * det2x2(a2, a3, b2, b3); + return a1 * det2x2(b2, b3, c2, c3) + - b1 * det2x2(a2, a3, c2, c3) + + c1 * det2x2(a2, a3, b2, b3); } bool matrix4::invert(const matrix4 &m, double mindet) { - double a1 = m.a.x, a2 = m.a.y, a3 = m.a.z, a4 = m.a.w, - b1 = m.b.x, b2 = m.b.y, b3 = m.b.z, b4 = m.b.w, - c1 = m.c.x, c2 = m.c.y, c3 = m.c.z, c4 = m.c.w, - d1 = m.d.x, d2 = m.d.y, d3 = m.d.z, d4 = m.d.w, - det1 = det3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4), - det2 = -det3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4), - det3 = det3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4), - det4 = -det3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4), - det = a1*det1 + b1*det2 + c1*det3 + d1*det4; + double a1 = m.a.x, a2 = m.a.y, a3 = m.a.z, a4 = m.a.w, + b1 = m.b.x, b2 = m.b.y, b3 = m.b.z, b4 = m.b.w, + c1 = m.c.x, c2 = m.c.y, c3 = m.c.z, c4 = m.c.w, + d1 = m.d.x, d2 = m.d.y, d3 = m.d.z, d4 = m.d.w, + det1 = det3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4), + det2 = -det3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4), + det3 = det3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4), + det4 = -det3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4), + det = a1*det1 + b1*det2 + c1*det3 + d1*det4; - if(fabs(det) < mindet) return false; + if(fabs(det) < mindet) return false; - double invdet = 1/det; + double invdet = 1/det; - a.x = det1 * invdet; - a.y = det2 * invdet; - a.z = det3 * invdet; - a.w = det4 * invdet; + a.x = det1 * invdet; + a.y = det2 * invdet; + a.z = det3 * invdet; + a.w = det4 * invdet; - b.x = -det3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4) * invdet; - b.y = det3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4) * invdet; - b.z = -det3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4) * invdet; - b.w = det3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4) * invdet; + b.x = -det3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4) * invdet; + b.y = det3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4) * invdet; + b.z = -det3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4) * invdet; + b.w = det3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4) * invdet; - c.x = det3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4) * invdet; - c.y = -det3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4) * invdet; - c.z = det3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4) * invdet; - c.w = -det3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4) * invdet; + c.x = det3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4) * invdet; + c.y = -det3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4) * invdet; + c.z = det3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4) * invdet; + c.w = -det3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4) * invdet; - d.x = -det3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3) * invdet; - d.y = det3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3) * invdet; - d.z = -det3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3) * invdet; - d.w = det3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3) * invdet; + d.x = -det3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3) * invdet; + d.y = det3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3) * invdet; + d.z = -det3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3) * invdet; + d.w = det3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3) * invdet; - return true; + return true; } bool raysphereintersect(const vec ¢er, float radius, const vec &o, const vec &ray, float &dist) { - vec c(center); - c.sub(o); - float v = c.dot(ray), - inside = radius*radius - c.squaredlen(); - if(inside<0 && v<0) return false; - float d = inside + v*v; - if(d<0) return false; - dist = v - sqrt(d); - return true; + vec c(center); + c.sub(o); + float v = c.dot(ray), + inside = radius*radius - c.squaredlen(); + if(inside<0 && v<0) return false; + float d = inside + v*v; + if(d<0) return false; + dist = v - sqrt(d); + return true; } bool rayboxintersect(const vec &b, const vec &s, const vec &o, const vec &ray, float &dist, int &orient) { - loop(d, 3) if(ray[d]) - { - int dc = ray[d]<0 ? 1 : 0; - float pdist = (b[d]+s[d]*dc - o[d]) / ray[d]; - vec v(ray); - v.mul(pdist).add(o); - if(v[R[d]] >= b[R[d]] && v[R[d]] <= b[R[d]]+s[R[d]] - && v[C[d]] >= b[C[d]] && v[C[d]] <= b[C[d]]+s[C[d]]) - { - dist = pdist; - orient = 2*d+dc; - return true; - } - } - return false; + loop(d, 3) if(ray[d]) + { + int dc = ray[d]<0 ? 1 : 0; + float pdist = (b[d]+s[d]*dc - o[d]) / ray[d]; + vec v(ray); + v.mul(pdist).add(o); + if(v[R[d]] >= b[R[d]] && v[R[d]] <= b[R[d]]+s[R[d]] + && v[C[d]] >= b[C[d]] && v[C[d]] <= b[C[d]]+s[C[d]]) + { + dist = pdist; + orient = 2*d+dc; + return true; + } + } + return false; } bool linecylinderintersect(const vec &from, const vec &to, const vec &start, const vec &end, float radius, float &dist) { - vec d(end), m(from), n(to); - d.sub(start); - m.sub(start); - n.sub(from); - float md = m.dot(d), - nd = n.dot(d), - dd = d.squaredlen(); - if(md < 0 && md + nd < 0) return false; - if(md > dd && md + nd > dd) return false; - float nn = n.squaredlen(), - mn = m.dot(n), - a = dd*nn - nd*nd, - k = m.squaredlen() - radius*radius, - c = dd*k - md*md; - if(fabs(a) < 0.005f) - { - if(c > 0) return false; - if(md < 0) dist = -mn / nn; - else if(md > dd) dist = (nd - mn) / nn; - else dist = 0; - return true; - } - else if(c > 0) - { - float b = dd*mn - nd*md, - discrim = b*b - a*c; - if(discrim < 0) return false; - dist = (-b - sqrtf(discrim)) / a; - } - else dist = 0; - float offset = md + dist*nd; - if(offset < 0) - { - if(nd <= 0) return false; - dist = -md / nd; - if(k + dist*(2*mn + dist*nn) > 0) return false; - } - else if(offset > dd) - { - if(nd >= 0) return false; - dist = (dd - md) / nd; - if(k + dd - 2*md + dist*(2*(mn-nd) + dist*nn) > 0) return false; - } - return dist >= 0 && dist <= 1; + vec d(end), m(from), n(to); + d.sub(start); + m.sub(start); + n.sub(from); + float md = m.dot(d), + nd = n.dot(d), + dd = d.squaredlen(); + if(md < 0 && md + nd < 0) return false; + if(md > dd && md + nd > dd) return false; + float nn = n.squaredlen(), + mn = m.dot(n), + a = dd*nn - nd*nd, + k = m.squaredlen() - radius*radius, + c = dd*k - md*md; + if(fabs(a) < 0.005f) + { + if(c > 0) return false; + if(md < 0) dist = -mn / nn; + else if(md > dd) dist = (nd - mn) / nn; + else dist = 0; + return true; + } + else if(c > 0) + { + float b = dd*mn - nd*md, + discrim = b*b - a*c; + if(discrim < 0) return false; + dist = (-b - sqrtf(discrim)) / a; + } + else dist = 0; + float offset = md + dist*nd; + if(offset < 0) + { + if(nd <= 0) return false; + dist = -md / nd; + if(k + dist*(2*mn + dist*nn) > 0) return false; + } + else if(offset > dd) + { + if(nd >= 0) return false; + dist = (dd - md) / nd; + if(k + dd - 2*md + dist*(2*(mn-nd) + dist*nn) > 0) return false; + } + return dist >= 0 && dist <= 1; } extern const vec2 sincos360[721] = { - vec2(1.00000000, 0.00000000), vec2(0.99984770, 0.01745241), vec2(0.99939083, 0.03489950), vec2(0.99862953, 0.05233596), vec2(0.99756405, 0.06975647), vec2(0.99619470, 0.08715574), // 0 - vec2(0.99452190, 0.10452846), vec2(0.99254615, 0.12186934), vec2(0.99026807, 0.13917310), vec2(0.98768834, 0.15643447), vec2(0.98480775, 0.17364818), vec2(0.98162718, 0.19080900), // 6 - vec2(0.97814760, 0.20791169), vec2(0.97437006, 0.22495105), vec2(0.97029573, 0.24192190), vec2(0.96592583, 0.25881905), vec2(0.96126170, 0.27563736), vec2(0.95630476, 0.29237170), // 12 - vec2(0.95105652, 0.30901699), vec2(0.94551858, 0.32556815), vec2(0.93969262, 0.34202014), vec2(0.93358043, 0.35836795), vec2(0.92718385, 0.37460659), vec2(0.92050485, 0.39073113), // 18 - vec2(0.91354546, 0.40673664), vec2(0.90630779, 0.42261826), vec2(0.89879405, 0.43837115), vec2(0.89100652, 0.45399050), vec2(0.88294759, 0.46947156), vec2(0.87461971, 0.48480962), // 24 - vec2(0.86602540, 0.50000000), vec2(0.85716730, 0.51503807), vec2(0.84804810, 0.52991926), vec2(0.83867057, 0.54463904), vec2(0.82903757, 0.55919290), vec2(0.81915204, 0.57357644), // 30 - vec2(0.80901699, 0.58778525), vec2(0.79863551, 0.60181502), vec2(0.78801075, 0.61566148), vec2(0.77714596, 0.62932039), vec2(0.76604444, 0.64278761), vec2(0.75470958, 0.65605903), // 36 - vec2(0.74314483, 0.66913061), vec2(0.73135370, 0.68199836), vec2(0.71933980, 0.69465837), vec2(0.70710678, 0.70710678), vec2(0.69465837, 0.71933980), vec2(0.68199836, 0.73135370), // 42 - vec2(0.66913061, 0.74314483), vec2(0.65605903, 0.75470958), vec2(0.64278761, 0.76604444), vec2(0.62932039, 0.77714596), vec2(0.61566148, 0.78801075), vec2(0.60181502, 0.79863551), // 48 - vec2(0.58778525, 0.80901699), vec2(0.57357644, 0.81915204), vec2(0.55919290, 0.82903757), vec2(0.54463904, 0.83867057), vec2(0.52991926, 0.84804810), vec2(0.51503807, 0.85716730), // 54 - vec2(0.50000000, 0.86602540), vec2(0.48480962, 0.87461971), vec2(0.46947156, 0.88294759), vec2(0.45399050, 0.89100652), vec2(0.43837115, 0.89879405), vec2(0.42261826, 0.90630779), // 60 - vec2(0.40673664, 0.91354546), vec2(0.39073113, 0.92050485), vec2(0.37460659, 0.92718385), vec2(0.35836795, 0.93358043), vec2(0.34202014, 0.93969262), vec2(0.32556815, 0.94551858), // 66 - vec2(0.30901699, 0.95105652), vec2(0.29237170, 0.95630476), vec2(0.27563736, 0.96126170), vec2(0.25881905, 0.96592583), vec2(0.24192190, 0.97029573), vec2(0.22495105, 0.97437006), // 72 - vec2(0.20791169, 0.97814760), vec2(0.19080900, 0.98162718), vec2(0.17364818, 0.98480775), vec2(0.15643447, 0.98768834), vec2(0.13917310, 0.99026807), vec2(0.12186934, 0.99254615), // 78 - vec2(0.10452846, 0.99452190), vec2(0.08715574, 0.99619470), vec2(0.06975647, 0.99756405), vec2(0.05233596, 0.99862953), vec2(0.03489950, 0.99939083), vec2(0.01745241, 0.99984770), // 84 - vec2(0.00000000, 1.00000000), vec2(-0.01745241, 0.99984770), vec2(-0.03489950, 0.99939083), vec2(-0.05233596, 0.99862953), vec2(-0.06975647, 0.99756405), vec2(-0.08715574, 0.99619470), // 90 - vec2(-0.10452846, 0.99452190), vec2(-0.12186934, 0.99254615), vec2(-0.13917310, 0.99026807), vec2(-0.15643447, 0.98768834), vec2(-0.17364818, 0.98480775), vec2(-0.19080900, 0.98162718), // 96 - vec2(-0.20791169, 0.97814760), vec2(-0.22495105, 0.97437006), vec2(-0.24192190, 0.97029573), vec2(-0.25881905, 0.96592583), vec2(-0.27563736, 0.96126170), vec2(-0.29237170, 0.95630476), // 102 - vec2(-0.30901699, 0.95105652), vec2(-0.32556815, 0.94551858), vec2(-0.34202014, 0.93969262), vec2(-0.35836795, 0.93358043), vec2(-0.37460659, 0.92718385), vec2(-0.39073113, 0.92050485), // 108 - vec2(-0.40673664, 0.91354546), vec2(-0.42261826, 0.90630779), vec2(-0.43837115, 0.89879405), vec2(-0.45399050, 0.89100652), vec2(-0.46947156, 0.88294759), vec2(-0.48480962, 0.87461971), // 114 - vec2(-0.50000000, 0.86602540), vec2(-0.51503807, 0.85716730), vec2(-0.52991926, 0.84804810), vec2(-0.54463904, 0.83867057), vec2(-0.55919290, 0.82903757), vec2(-0.57357644, 0.81915204), // 120 - vec2(-0.58778525, 0.80901699), vec2(-0.60181502, 0.79863551), vec2(-0.61566148, 0.78801075), vec2(-0.62932039, 0.77714596), vec2(-0.64278761, 0.76604444), vec2(-0.65605903, 0.75470958), // 126 - vec2(-0.66913061, 0.74314483), vec2(-0.68199836, 0.73135370), vec2(-0.69465837, 0.71933980), vec2(-0.70710678, 0.70710678), vec2(-0.71933980, 0.69465837), vec2(-0.73135370, 0.68199836), // 132 - vec2(-0.74314483, 0.66913061), vec2(-0.75470958, 0.65605903), vec2(-0.76604444, 0.64278761), vec2(-0.77714596, 0.62932039), vec2(-0.78801075, 0.61566148), vec2(-0.79863551, 0.60181502), // 138 - vec2(-0.80901699, 0.58778525), vec2(-0.81915204, 0.57357644), vec2(-0.82903757, 0.55919290), vec2(-0.83867057, 0.54463904), vec2(-0.84804810, 0.52991926), vec2(-0.85716730, 0.51503807), // 144 - vec2(-0.86602540, 0.50000000), vec2(-0.87461971, 0.48480962), vec2(-0.88294759, 0.46947156), vec2(-0.89100652, 0.45399050), vec2(-0.89879405, 0.43837115), vec2(-0.90630779, 0.42261826), // 150 - vec2(-0.91354546, 0.40673664), vec2(-0.92050485, 0.39073113), vec2(-0.92718385, 0.37460659), vec2(-0.93358043, 0.35836795), vec2(-0.93969262, 0.34202014), vec2(-0.94551858, 0.32556815), // 156 - vec2(-0.95105652, 0.30901699), vec2(-0.95630476, 0.29237170), vec2(-0.96126170, 0.27563736), vec2(-0.96592583, 0.25881905), vec2(-0.97029573, 0.24192190), vec2(-0.97437006, 0.22495105), // 162 - vec2(-0.97814760, 0.20791169), vec2(-0.98162718, 0.19080900), vec2(-0.98480775, 0.17364818), vec2(-0.98768834, 0.15643447), vec2(-0.99026807, 0.13917310), vec2(-0.99254615, 0.12186934), // 168 - vec2(-0.99452190, 0.10452846), vec2(-0.99619470, 0.08715574), vec2(-0.99756405, 0.06975647), vec2(-0.99862953, 0.05233596), vec2(-0.99939083, 0.03489950), vec2(-0.99984770, 0.01745241), // 174 - vec2(-1.00000000, 0.00000000), vec2(-0.99984770, -0.01745241), vec2(-0.99939083, -0.03489950), vec2(-0.99862953, -0.05233596), vec2(-0.99756405, -0.06975647), vec2(-0.99619470, -0.08715574), // 180 - vec2(-0.99452190, -0.10452846), vec2(-0.99254615, -0.12186934), vec2(-0.99026807, -0.13917310), vec2(-0.98768834, -0.15643447), vec2(-0.98480775, -0.17364818), vec2(-0.98162718, -0.19080900), // 186 - vec2(-0.97814760, -0.20791169), vec2(-0.97437006, -0.22495105), vec2(-0.97029573, -0.24192190), vec2(-0.96592583, -0.25881905), vec2(-0.96126170, -0.27563736), vec2(-0.95630476, -0.29237170), // 192 - vec2(-0.95105652, -0.30901699), vec2(-0.94551858, -0.32556815), vec2(-0.93969262, -0.34202014), vec2(-0.93358043, -0.35836795), vec2(-0.92718385, -0.37460659), vec2(-0.92050485, -0.39073113), // 198 - vec2(-0.91354546, -0.40673664), vec2(-0.90630779, -0.42261826), vec2(-0.89879405, -0.43837115), vec2(-0.89100652, -0.45399050), vec2(-0.88294759, -0.46947156), vec2(-0.87461971, -0.48480962), // 204 - vec2(-0.86602540, -0.50000000), vec2(-0.85716730, -0.51503807), vec2(-0.84804810, -0.52991926), vec2(-0.83867057, -0.54463904), vec2(-0.82903757, -0.55919290), vec2(-0.81915204, -0.57357644), // 210 - vec2(-0.80901699, -0.58778525), vec2(-0.79863551, -0.60181502), vec2(-0.78801075, -0.61566148), vec2(-0.77714596, -0.62932039), vec2(-0.76604444, -0.64278761), vec2(-0.75470958, -0.65605903), // 216 - vec2(-0.74314483, -0.66913061), vec2(-0.73135370, -0.68199836), vec2(-0.71933980, -0.69465837), vec2(-0.70710678, -0.70710678), vec2(-0.69465837, -0.71933980), vec2(-0.68199836, -0.73135370), // 222 - vec2(-0.66913061, -0.74314483), vec2(-0.65605903, -0.75470958), vec2(-0.64278761, -0.76604444), vec2(-0.62932039, -0.77714596), vec2(-0.61566148, -0.78801075), vec2(-0.60181502, -0.79863551), // 228 - vec2(-0.58778525, -0.80901699), vec2(-0.57357644, -0.81915204), vec2(-0.55919290, -0.82903757), vec2(-0.54463904, -0.83867057), vec2(-0.52991926, -0.84804810), vec2(-0.51503807, -0.85716730), // 234 - vec2(-0.50000000, -0.86602540), vec2(-0.48480962, -0.87461971), vec2(-0.46947156, -0.88294759), vec2(-0.45399050, -0.89100652), vec2(-0.43837115, -0.89879405), vec2(-0.42261826, -0.90630779), // 240 - vec2(-0.40673664, -0.91354546), vec2(-0.39073113, -0.92050485), vec2(-0.37460659, -0.92718385), vec2(-0.35836795, -0.93358043), vec2(-0.34202014, -0.93969262), vec2(-0.32556815, -0.94551858), // 246 - vec2(-0.30901699, -0.95105652), vec2(-0.29237170, -0.95630476), vec2(-0.27563736, -0.96126170), vec2(-0.25881905, -0.96592583), vec2(-0.24192190, -0.97029573), vec2(-0.22495105, -0.97437006), // 252 - vec2(-0.20791169, -0.97814760), vec2(-0.19080900, -0.98162718), vec2(-0.17364818, -0.98480775), vec2(-0.15643447, -0.98768834), vec2(-0.13917310, -0.99026807), vec2(-0.12186934, -0.99254615), // 258 - vec2(-0.10452846, -0.99452190), vec2(-0.08715574, -0.99619470), vec2(-0.06975647, -0.99756405), vec2(-0.05233596, -0.99862953), vec2(-0.03489950, -0.99939083), vec2(-0.01745241, -0.99984770), // 264 - vec2(-0.00000000, -1.00000000), vec2(0.01745241, -0.99984770), vec2(0.03489950, -0.99939083), vec2(0.05233596, -0.99862953), vec2(0.06975647, -0.99756405), vec2(0.08715574, -0.99619470), // 270 - vec2(0.10452846, -0.99452190), vec2(0.12186934, -0.99254615), vec2(0.13917310, -0.99026807), vec2(0.15643447, -0.98768834), vec2(0.17364818, -0.98480775), vec2(0.19080900, -0.98162718), // 276 - vec2(0.20791169, -0.97814760), vec2(0.22495105, -0.97437006), vec2(0.24192190, -0.97029573), vec2(0.25881905, -0.96592583), vec2(0.27563736, -0.96126170), vec2(0.29237170, -0.95630476), // 282 - vec2(0.30901699, -0.95105652), vec2(0.32556815, -0.94551858), vec2(0.34202014, -0.93969262), vec2(0.35836795, -0.93358043), vec2(0.37460659, -0.92718385), vec2(0.39073113, -0.92050485), // 288 - vec2(0.40673664, -0.91354546), vec2(0.42261826, -0.90630779), vec2(0.43837115, -0.89879405), vec2(0.45399050, -0.89100652), vec2(0.46947156, -0.88294759), vec2(0.48480962, -0.87461971), // 294 - vec2(0.50000000, -0.86602540), vec2(0.51503807, -0.85716730), vec2(0.52991926, -0.84804810), vec2(0.54463904, -0.83867057), vec2(0.55919290, -0.82903757), vec2(0.57357644, -0.81915204), // 300 - vec2(0.58778525, -0.80901699), vec2(0.60181502, -0.79863551), vec2(0.61566148, -0.78801075), vec2(0.62932039, -0.77714596), vec2(0.64278761, -0.76604444), vec2(0.65605903, -0.75470958), // 306 - vec2(0.66913061, -0.74314483), vec2(0.68199836, -0.73135370), vec2(0.69465837, -0.71933980), vec2(0.70710678, -0.70710678), vec2(0.71933980, -0.69465837), vec2(0.73135370, -0.68199836), // 312 - vec2(0.74314483, -0.66913061), vec2(0.75470958, -0.65605903), vec2(0.76604444, -0.64278761), vec2(0.77714596, -0.62932039), vec2(0.78801075, -0.61566148), vec2(0.79863551, -0.60181502), // 318 - vec2(0.80901699, -0.58778525), vec2(0.81915204, -0.57357644), vec2(0.82903757, -0.55919290), vec2(0.83867057, -0.54463904), vec2(0.84804810, -0.52991926), vec2(0.85716730, -0.51503807), // 324 - vec2(0.86602540, -0.50000000), vec2(0.87461971, -0.48480962), vec2(0.88294759, -0.46947156), vec2(0.89100652, -0.45399050), vec2(0.89879405, -0.43837115), vec2(0.90630779, -0.42261826), // 330 - vec2(0.91354546, -0.40673664), vec2(0.92050485, -0.39073113), vec2(0.92718385, -0.37460659), vec2(0.93358043, -0.35836795), vec2(0.93969262, -0.34202014), vec2(0.94551858, -0.32556815), // 336 - vec2(0.95105652, -0.30901699), vec2(0.95630476, -0.29237170), vec2(0.96126170, -0.27563736), vec2(0.96592583, -0.25881905), vec2(0.97029573, -0.24192190), vec2(0.97437006, -0.22495105), // 342 - vec2(0.97814760, -0.20791169), vec2(0.98162718, -0.19080900), vec2(0.98480775, -0.17364818), vec2(0.98768834, -0.15643447), vec2(0.99026807, -0.13917310), vec2(0.99254615, -0.12186934), // 348 - vec2(0.99452190, -0.10452846), vec2(0.99619470, -0.08715574), vec2(0.99756405, -0.06975647), vec2(0.99862953, -0.05233596), vec2(0.99939083, -0.03489950), vec2(0.99984770, -0.01745241), // 354 - vec2(1.00000000, 0.00000000), vec2(0.99984770, 0.01745241), vec2(0.99939083, 0.03489950), vec2(0.99862953, 0.05233596), vec2(0.99756405, 0.06975647), vec2(0.99619470, 0.08715574), // 360 - vec2(0.99452190, 0.10452846), vec2(0.99254615, 0.12186934), vec2(0.99026807, 0.13917310), vec2(0.98768834, 0.15643447), vec2(0.98480775, 0.17364818), vec2(0.98162718, 0.19080900), // 366 - vec2(0.97814760, 0.20791169), vec2(0.97437006, 0.22495105), vec2(0.97029573, 0.24192190), vec2(0.96592583, 0.25881905), vec2(0.96126170, 0.27563736), vec2(0.95630476, 0.29237170), // 372 - vec2(0.95105652, 0.30901699), vec2(0.94551858, 0.32556815), vec2(0.93969262, 0.34202014), vec2(0.93358043, 0.35836795), vec2(0.92718385, 0.37460659), vec2(0.92050485, 0.39073113), // 378 - vec2(0.91354546, 0.40673664), vec2(0.90630779, 0.42261826), vec2(0.89879405, 0.43837115), vec2(0.89100652, 0.45399050), vec2(0.88294759, 0.46947156), vec2(0.87461971, 0.48480962), // 384 - vec2(0.86602540, 0.50000000), vec2(0.85716730, 0.51503807), vec2(0.84804810, 0.52991926), vec2(0.83867057, 0.54463904), vec2(0.82903757, 0.55919290), vec2(0.81915204, 0.57357644), // 390 - vec2(0.80901699, 0.58778525), vec2(0.79863551, 0.60181502), vec2(0.78801075, 0.61566148), vec2(0.77714596, 0.62932039), vec2(0.76604444, 0.64278761), vec2(0.75470958, 0.65605903), // 396 - vec2(0.74314483, 0.66913061), vec2(0.73135370, 0.68199836), vec2(0.71933980, 0.69465837), vec2(0.70710678, 0.70710678), vec2(0.69465837, 0.71933980), vec2(0.68199836, 0.73135370), // 402 - vec2(0.66913061, 0.74314483), vec2(0.65605903, 0.75470958), vec2(0.64278761, 0.76604444), vec2(0.62932039, 0.77714596), vec2(0.61566148, 0.78801075), vec2(0.60181502, 0.79863551), // 408 - vec2(0.58778525, 0.80901699), vec2(0.57357644, 0.81915204), vec2(0.55919290, 0.82903757), vec2(0.54463904, 0.83867057), vec2(0.52991926, 0.84804810), vec2(0.51503807, 0.85716730), // 414 - vec2(0.50000000, 0.86602540), vec2(0.48480962, 0.87461971), vec2(0.46947156, 0.88294759), vec2(0.45399050, 0.89100652), vec2(0.43837115, 0.89879405), vec2(0.42261826, 0.90630779), // 420 - vec2(0.40673664, 0.91354546), vec2(0.39073113, 0.92050485), vec2(0.37460659, 0.92718385), vec2(0.35836795, 0.93358043), vec2(0.34202014, 0.93969262), vec2(0.32556815, 0.94551858), // 426 - vec2(0.30901699, 0.95105652), vec2(0.29237170, 0.95630476), vec2(0.27563736, 0.96126170), vec2(0.25881905, 0.96592583), vec2(0.24192190, 0.97029573), vec2(0.22495105, 0.97437006), // 432 - vec2(0.20791169, 0.97814760), vec2(0.19080900, 0.98162718), vec2(0.17364818, 0.98480775), vec2(0.15643447, 0.98768834), vec2(0.13917310, 0.99026807), vec2(0.12186934, 0.99254615), // 438 - vec2(0.10452846, 0.99452190), vec2(0.08715574, 0.99619470), vec2(0.06975647, 0.99756405), vec2(0.05233596, 0.99862953), vec2(0.03489950, 0.99939083), vec2(0.01745241, 0.99984770), // 444 - vec2(0.00000000, 1.00000000), vec2(-0.01745241, 0.99984770), vec2(-0.03489950, 0.99939083), vec2(-0.05233596, 0.99862953), vec2(-0.06975647, 0.99756405), vec2(-0.08715574, 0.99619470), // 450 - vec2(-0.10452846, 0.99452190), vec2(-0.12186934, 0.99254615), vec2(-0.13917310, 0.99026807), vec2(-0.15643447, 0.98768834), vec2(-0.17364818, 0.98480775), vec2(-0.19080900, 0.98162718), // 456 - vec2(-0.20791169, 0.97814760), vec2(-0.22495105, 0.97437006), vec2(-0.24192190, 0.97029573), vec2(-0.25881905, 0.96592583), vec2(-0.27563736, 0.96126170), vec2(-0.29237170, 0.95630476), // 462 - vec2(-0.30901699, 0.95105652), vec2(-0.32556815, 0.94551858), vec2(-0.34202014, 0.93969262), vec2(-0.35836795, 0.93358043), vec2(-0.37460659, 0.92718385), vec2(-0.39073113, 0.92050485), // 468 - vec2(-0.40673664, 0.91354546), vec2(-0.42261826, 0.90630779), vec2(-0.43837115, 0.89879405), vec2(-0.45399050, 0.89100652), vec2(-0.46947156, 0.88294759), vec2(-0.48480962, 0.87461971), // 474 - vec2(-0.50000000, 0.86602540), vec2(-0.51503807, 0.85716730), vec2(-0.52991926, 0.84804810), vec2(-0.54463904, 0.83867057), vec2(-0.55919290, 0.82903757), vec2(-0.57357644, 0.81915204), // 480 - vec2(-0.58778525, 0.80901699), vec2(-0.60181502, 0.79863551), vec2(-0.61566148, 0.78801075), vec2(-0.62932039, 0.77714596), vec2(-0.64278761, 0.76604444), vec2(-0.65605903, 0.75470958), // 486 - vec2(-0.66913061, 0.74314483), vec2(-0.68199836, 0.73135370), vec2(-0.69465837, 0.71933980), vec2(-0.70710678, 0.70710678), vec2(-0.71933980, 0.69465837), vec2(-0.73135370, 0.68199836), // 492 - vec2(-0.74314483, 0.66913061), vec2(-0.75470958, 0.65605903), vec2(-0.76604444, 0.64278761), vec2(-0.77714596, 0.62932039), vec2(-0.78801075, 0.61566148), vec2(-0.79863551, 0.60181502), // 498 - vec2(-0.80901699, 0.58778525), vec2(-0.81915204, 0.57357644), vec2(-0.82903757, 0.55919290), vec2(-0.83867057, 0.54463904), vec2(-0.84804810, 0.52991926), vec2(-0.85716730, 0.51503807), // 504 - vec2(-0.86602540, 0.50000000), vec2(-0.87461971, 0.48480962), vec2(-0.88294759, 0.46947156), vec2(-0.89100652, 0.45399050), vec2(-0.89879405, 0.43837115), vec2(-0.90630779, 0.42261826), // 510 - vec2(-0.91354546, 0.40673664), vec2(-0.92050485, 0.39073113), vec2(-0.92718385, 0.37460659), vec2(-0.93358043, 0.35836795), vec2(-0.93969262, 0.34202014), vec2(-0.94551858, 0.32556815), // 516 - vec2(-0.95105652, 0.30901699), vec2(-0.95630476, 0.29237170), vec2(-0.96126170, 0.27563736), vec2(-0.96592583, 0.25881905), vec2(-0.97029573, 0.24192190), vec2(-0.97437006, 0.22495105), // 522 - vec2(-0.97814760, 0.20791169), vec2(-0.98162718, 0.19080900), vec2(-0.98480775, 0.17364818), vec2(-0.98768834, 0.15643447), vec2(-0.99026807, 0.13917310), vec2(-0.99254615, 0.12186934), // 528 - vec2(-0.99452190, 0.10452846), vec2(-0.99619470, 0.08715574), vec2(-0.99756405, 0.06975647), vec2(-0.99862953, 0.05233596), vec2(-0.99939083, 0.03489950), vec2(-0.99984770, 0.01745241), // 534 - vec2(-1.00000000, 0.00000000), vec2(-0.99984770, -0.01745241), vec2(-0.99939083, -0.03489950), vec2(-0.99862953, -0.05233596), vec2(-0.99756405, -0.06975647), vec2(-0.99619470, -0.08715574), // 540 - vec2(-0.99452190, -0.10452846), vec2(-0.99254615, -0.12186934), vec2(-0.99026807, -0.13917310), vec2(-0.98768834, -0.15643447), vec2(-0.98480775, -0.17364818), vec2(-0.98162718, -0.19080900), // 546 - vec2(-0.97814760, -0.20791169), vec2(-0.97437006, -0.22495105), vec2(-0.97029573, -0.24192190), vec2(-0.96592583, -0.25881905), vec2(-0.96126170, -0.27563736), vec2(-0.95630476, -0.29237170), // 552 - vec2(-0.95105652, -0.30901699), vec2(-0.94551858, -0.32556815), vec2(-0.93969262, -0.34202014), vec2(-0.93358043, -0.35836795), vec2(-0.92718385, -0.37460659), vec2(-0.92050485, -0.39073113), // 558 - vec2(-0.91354546, -0.40673664), vec2(-0.90630779, -0.42261826), vec2(-0.89879405, -0.43837115), vec2(-0.89100652, -0.45399050), vec2(-0.88294759, -0.46947156), vec2(-0.87461971, -0.48480962), // 564 - vec2(-0.86602540, -0.50000000), vec2(-0.85716730, -0.51503807), vec2(-0.84804810, -0.52991926), vec2(-0.83867057, -0.54463904), vec2(-0.82903757, -0.55919290), vec2(-0.81915204, -0.57357644), // 570 - vec2(-0.80901699, -0.58778525), vec2(-0.79863551, -0.60181502), vec2(-0.78801075, -0.61566148), vec2(-0.77714596, -0.62932039), vec2(-0.76604444, -0.64278761), vec2(-0.75470958, -0.65605903), // 576 - vec2(-0.74314483, -0.66913061), vec2(-0.73135370, -0.68199836), vec2(-0.71933980, -0.69465837), vec2(-0.70710678, -0.70710678), vec2(-0.69465837, -0.71933980), vec2(-0.68199836, -0.73135370), // 582 - vec2(-0.66913061, -0.74314483), vec2(-0.65605903, -0.75470958), vec2(-0.64278761, -0.76604444), vec2(-0.62932039, -0.77714596), vec2(-0.61566148, -0.78801075), vec2(-0.60181502, -0.79863551), // 588 - vec2(-0.58778525, -0.80901699), vec2(-0.57357644, -0.81915204), vec2(-0.55919290, -0.82903757), vec2(-0.54463904, -0.83867057), vec2(-0.52991926, -0.84804810), vec2(-0.51503807, -0.85716730), // 594 - vec2(-0.50000000, -0.86602540), vec2(-0.48480962, -0.87461971), vec2(-0.46947156, -0.88294759), vec2(-0.45399050, -0.89100652), vec2(-0.43837115, -0.89879405), vec2(-0.42261826, -0.90630779), // 600 - vec2(-0.40673664, -0.91354546), vec2(-0.39073113, -0.92050485), vec2(-0.37460659, -0.92718385), vec2(-0.35836795, -0.93358043), vec2(-0.34202014, -0.93969262), vec2(-0.32556815, -0.94551858), // 606 - vec2(-0.30901699, -0.95105652), vec2(-0.29237170, -0.95630476), vec2(-0.27563736, -0.96126170), vec2(-0.25881905, -0.96592583), vec2(-0.24192190, -0.97029573), vec2(-0.22495105, -0.97437006), // 612 - vec2(-0.20791169, -0.97814760), vec2(-0.19080900, -0.98162718), vec2(-0.17364818, -0.98480775), vec2(-0.15643447, -0.98768834), vec2(-0.13917310, -0.99026807), vec2(-0.12186934, -0.99254615), // 618 - vec2(-0.10452846, -0.99452190), vec2(-0.08715574, -0.99619470), vec2(-0.06975647, -0.99756405), vec2(-0.05233596, -0.99862953), vec2(-0.03489950, -0.99939083), vec2(-0.01745241, -0.99984770), // 624 - vec2(-0.00000000, -1.00000000), vec2(0.01745241, -0.99984770), vec2(0.03489950, -0.99939083), vec2(0.05233596, -0.99862953), vec2(0.06975647, -0.99756405), vec2(0.08715574, -0.99619470), // 630 - vec2(0.10452846, -0.99452190), vec2(0.12186934, -0.99254615), vec2(0.13917310, -0.99026807), vec2(0.15643447, -0.98768834), vec2(0.17364818, -0.98480775), vec2(0.19080900, -0.98162718), // 636 - vec2(0.20791169, -0.97814760), vec2(0.22495105, -0.97437006), vec2(0.24192190, -0.97029573), vec2(0.25881905, -0.96592583), vec2(0.27563736, -0.96126170), vec2(0.29237170, -0.95630476), // 642 - vec2(0.30901699, -0.95105652), vec2(0.32556815, -0.94551858), vec2(0.34202014, -0.93969262), vec2(0.35836795, -0.93358043), vec2(0.37460659, -0.92718385), vec2(0.39073113, -0.92050485), // 648 - vec2(0.40673664, -0.91354546), vec2(0.42261826, -0.90630779), vec2(0.43837115, -0.89879405), vec2(0.45399050, -0.89100652), vec2(0.46947156, -0.88294759), vec2(0.48480962, -0.87461971), // 654 - vec2(0.50000000, -0.86602540), vec2(0.51503807, -0.85716730), vec2(0.52991926, -0.84804810), vec2(0.54463904, -0.83867057), vec2(0.55919290, -0.82903757), vec2(0.57357644, -0.81915204), // 660 - vec2(0.58778525, -0.80901699), vec2(0.60181502, -0.79863551), vec2(0.61566148, -0.78801075), vec2(0.62932039, -0.77714596), vec2(0.64278761, -0.76604444), vec2(0.65605903, -0.75470958), // 666 - vec2(0.66913061, -0.74314483), vec2(0.68199836, -0.73135370), vec2(0.69465837, -0.71933980), vec2(0.70710678, -0.70710678), vec2(0.71933980, -0.69465837), vec2(0.73135370, -0.68199836), // 672 - vec2(0.74314483, -0.66913061), vec2(0.75470958, -0.65605903), vec2(0.76604444, -0.64278761), vec2(0.77714596, -0.62932039), vec2(0.78801075, -0.61566148), vec2(0.79863551, -0.60181502), // 678 - vec2(0.80901699, -0.58778525), vec2(0.81915204, -0.57357644), vec2(0.82903757, -0.55919290), vec2(0.83867057, -0.54463904), vec2(0.84804810, -0.52991926), vec2(0.85716730, -0.51503807), // 684 - vec2(0.86602540, -0.50000000), vec2(0.87461971, -0.48480962), vec2(0.88294759, -0.46947156), vec2(0.89100652, -0.45399050), vec2(0.89879405, -0.43837115), vec2(0.90630779, -0.42261826), // 690 - vec2(0.91354546, -0.40673664), vec2(0.92050485, -0.39073113), vec2(0.92718385, -0.37460659), vec2(0.93358043, -0.35836795), vec2(0.93969262, -0.34202014), vec2(0.94551858, -0.32556815), // 696 - vec2(0.95105652, -0.30901699), vec2(0.95630476, -0.29237170), vec2(0.96126170, -0.27563736), vec2(0.96592583, -0.25881905), vec2(0.97029573, -0.24192190), vec2(0.97437006, -0.22495105), // 702 - vec2(0.97814760, -0.20791169), vec2(0.98162718, -0.19080900), vec2(0.98480775, -0.17364818), vec2(0.98768834, -0.15643447), vec2(0.99026807, -0.13917310), vec2(0.99254615, -0.12186934), // 708 - vec2(0.99452190, -0.10452846), vec2(0.99619470, -0.08715574), vec2(0.99756405, -0.06975647), vec2(0.99862953, -0.05233596), vec2(0.99939083, -0.03489950), vec2(0.99984770, -0.01745241), // 714 - vec2(1.00000000, 0.00000000) // 720 + vec2(1.00000000, 0.00000000), vec2(0.99984770, 0.01745241), vec2(0.99939083, 0.03489950), vec2(0.99862953, 0.05233596), vec2(0.99756405, 0.06975647), vec2(0.99619470, 0.08715574), // 0 + vec2(0.99452190, 0.10452846), vec2(0.99254615, 0.12186934), vec2(0.99026807, 0.13917310), vec2(0.98768834, 0.15643447), vec2(0.98480775, 0.17364818), vec2(0.98162718, 0.19080900), // 6 + vec2(0.97814760, 0.20791169), vec2(0.97437006, 0.22495105), vec2(0.97029573, 0.24192190), vec2(0.96592583, 0.25881905), vec2(0.96126170, 0.27563736), vec2(0.95630476, 0.29237170), // 12 + vec2(0.95105652, 0.30901699), vec2(0.94551858, 0.32556815), vec2(0.93969262, 0.34202014), vec2(0.93358043, 0.35836795), vec2(0.92718385, 0.37460659), vec2(0.92050485, 0.39073113), // 18 + vec2(0.91354546, 0.40673664), vec2(0.90630779, 0.42261826), vec2(0.89879405, 0.43837115), vec2(0.89100652, 0.45399050), vec2(0.88294759, 0.46947156), vec2(0.87461971, 0.48480962), // 24 + vec2(0.86602540, 0.50000000), vec2(0.85716730, 0.51503807), vec2(0.84804810, 0.52991926), vec2(0.83867057, 0.54463904), vec2(0.82903757, 0.55919290), vec2(0.81915204, 0.57357644), // 30 + vec2(0.80901699, 0.58778525), vec2(0.79863551, 0.60181502), vec2(0.78801075, 0.61566148), vec2(0.77714596, 0.62932039), vec2(0.76604444, 0.64278761), vec2(0.75470958, 0.65605903), // 36 + vec2(0.74314483, 0.66913061), vec2(0.73135370, 0.68199836), vec2(0.71933980, 0.69465837), vec2(0.70710678, 0.70710678), vec2(0.69465837, 0.71933980), vec2(0.68199836, 0.73135370), // 42 + vec2(0.66913061, 0.74314483), vec2(0.65605903, 0.75470958), vec2(0.64278761, 0.76604444), vec2(0.62932039, 0.77714596), vec2(0.61566148, 0.78801075), vec2(0.60181502, 0.79863551), // 48 + vec2(0.58778525, 0.80901699), vec2(0.57357644, 0.81915204), vec2(0.55919290, 0.82903757), vec2(0.54463904, 0.83867057), vec2(0.52991926, 0.84804810), vec2(0.51503807, 0.85716730), // 54 + vec2(0.50000000, 0.86602540), vec2(0.48480962, 0.87461971), vec2(0.46947156, 0.88294759), vec2(0.45399050, 0.89100652), vec2(0.43837115, 0.89879405), vec2(0.42261826, 0.90630779), // 60 + vec2(0.40673664, 0.91354546), vec2(0.39073113, 0.92050485), vec2(0.37460659, 0.92718385), vec2(0.35836795, 0.93358043), vec2(0.34202014, 0.93969262), vec2(0.32556815, 0.94551858), // 66 + vec2(0.30901699, 0.95105652), vec2(0.29237170, 0.95630476), vec2(0.27563736, 0.96126170), vec2(0.25881905, 0.96592583), vec2(0.24192190, 0.97029573), vec2(0.22495105, 0.97437006), // 72 + vec2(0.20791169, 0.97814760), vec2(0.19080900, 0.98162718), vec2(0.17364818, 0.98480775), vec2(0.15643447, 0.98768834), vec2(0.13917310, 0.99026807), vec2(0.12186934, 0.99254615), // 78 + vec2(0.10452846, 0.99452190), vec2(0.08715574, 0.99619470), vec2(0.06975647, 0.99756405), vec2(0.05233596, 0.99862953), vec2(0.03489950, 0.99939083), vec2(0.01745241, 0.99984770), // 84 + vec2(0.00000000, 1.00000000), vec2(-0.01745241, 0.99984770), vec2(-0.03489950, 0.99939083), vec2(-0.05233596, 0.99862953), vec2(-0.06975647, 0.99756405), vec2(-0.08715574, 0.99619470), // 90 + vec2(-0.10452846, 0.99452190), vec2(-0.12186934, 0.99254615), vec2(-0.13917310, 0.99026807), vec2(-0.15643447, 0.98768834), vec2(-0.17364818, 0.98480775), vec2(-0.19080900, 0.98162718), // 96 + vec2(-0.20791169, 0.97814760), vec2(-0.22495105, 0.97437006), vec2(-0.24192190, 0.97029573), vec2(-0.25881905, 0.96592583), vec2(-0.27563736, 0.96126170), vec2(-0.29237170, 0.95630476), // 102 + vec2(-0.30901699, 0.95105652), vec2(-0.32556815, 0.94551858), vec2(-0.34202014, 0.93969262), vec2(-0.35836795, 0.93358043), vec2(-0.37460659, 0.92718385), vec2(-0.39073113, 0.92050485), // 108 + vec2(-0.40673664, 0.91354546), vec2(-0.42261826, 0.90630779), vec2(-0.43837115, 0.89879405), vec2(-0.45399050, 0.89100652), vec2(-0.46947156, 0.88294759), vec2(-0.48480962, 0.87461971), // 114 + vec2(-0.50000000, 0.86602540), vec2(-0.51503807, 0.85716730), vec2(-0.52991926, 0.84804810), vec2(-0.54463904, 0.83867057), vec2(-0.55919290, 0.82903757), vec2(-0.57357644, 0.81915204), // 120 + vec2(-0.58778525, 0.80901699), vec2(-0.60181502, 0.79863551), vec2(-0.61566148, 0.78801075), vec2(-0.62932039, 0.77714596), vec2(-0.64278761, 0.76604444), vec2(-0.65605903, 0.75470958), // 126 + vec2(-0.66913061, 0.74314483), vec2(-0.68199836, 0.73135370), vec2(-0.69465837, 0.71933980), vec2(-0.70710678, 0.70710678), vec2(-0.71933980, 0.69465837), vec2(-0.73135370, 0.68199836), // 132 + vec2(-0.74314483, 0.66913061), vec2(-0.75470958, 0.65605903), vec2(-0.76604444, 0.64278761), vec2(-0.77714596, 0.62932039), vec2(-0.78801075, 0.61566148), vec2(-0.79863551, 0.60181502), // 138 + vec2(-0.80901699, 0.58778525), vec2(-0.81915204, 0.57357644), vec2(-0.82903757, 0.55919290), vec2(-0.83867057, 0.54463904), vec2(-0.84804810, 0.52991926), vec2(-0.85716730, 0.51503807), // 144 + vec2(-0.86602540, 0.50000000), vec2(-0.87461971, 0.48480962), vec2(-0.88294759, 0.46947156), vec2(-0.89100652, 0.45399050), vec2(-0.89879405, 0.43837115), vec2(-0.90630779, 0.42261826), // 150 + vec2(-0.91354546, 0.40673664), vec2(-0.92050485, 0.39073113), vec2(-0.92718385, 0.37460659), vec2(-0.93358043, 0.35836795), vec2(-0.93969262, 0.34202014), vec2(-0.94551858, 0.32556815), // 156 + vec2(-0.95105652, 0.30901699), vec2(-0.95630476, 0.29237170), vec2(-0.96126170, 0.27563736), vec2(-0.96592583, 0.25881905), vec2(-0.97029573, 0.24192190), vec2(-0.97437006, 0.22495105), // 162 + vec2(-0.97814760, 0.20791169), vec2(-0.98162718, 0.19080900), vec2(-0.98480775, 0.17364818), vec2(-0.98768834, 0.15643447), vec2(-0.99026807, 0.13917310), vec2(-0.99254615, 0.12186934), // 168 + vec2(-0.99452190, 0.10452846), vec2(-0.99619470, 0.08715574), vec2(-0.99756405, 0.06975647), vec2(-0.99862953, 0.05233596), vec2(-0.99939083, 0.03489950), vec2(-0.99984770, 0.01745241), // 174 + vec2(-1.00000000, 0.00000000), vec2(-0.99984770, -0.01745241), vec2(-0.99939083, -0.03489950), vec2(-0.99862953, -0.05233596), vec2(-0.99756405, -0.06975647), vec2(-0.99619470, -0.08715574), // 180 + vec2(-0.99452190, -0.10452846), vec2(-0.99254615, -0.12186934), vec2(-0.99026807, -0.13917310), vec2(-0.98768834, -0.15643447), vec2(-0.98480775, -0.17364818), vec2(-0.98162718, -0.19080900), // 186 + vec2(-0.97814760, -0.20791169), vec2(-0.97437006, -0.22495105), vec2(-0.97029573, -0.24192190), vec2(-0.96592583, -0.25881905), vec2(-0.96126170, -0.27563736), vec2(-0.95630476, -0.29237170), // 192 + vec2(-0.95105652, -0.30901699), vec2(-0.94551858, -0.32556815), vec2(-0.93969262, -0.34202014), vec2(-0.93358043, -0.35836795), vec2(-0.92718385, -0.37460659), vec2(-0.92050485, -0.39073113), // 198 + vec2(-0.91354546, -0.40673664), vec2(-0.90630779, -0.42261826), vec2(-0.89879405, -0.43837115), vec2(-0.89100652, -0.45399050), vec2(-0.88294759, -0.46947156), vec2(-0.87461971, -0.48480962), // 204 + vec2(-0.86602540, -0.50000000), vec2(-0.85716730, -0.51503807), vec2(-0.84804810, -0.52991926), vec2(-0.83867057, -0.54463904), vec2(-0.82903757, -0.55919290), vec2(-0.81915204, -0.57357644), // 210 + vec2(-0.80901699, -0.58778525), vec2(-0.79863551, -0.60181502), vec2(-0.78801075, -0.61566148), vec2(-0.77714596, -0.62932039), vec2(-0.76604444, -0.64278761), vec2(-0.75470958, -0.65605903), // 216 + vec2(-0.74314483, -0.66913061), vec2(-0.73135370, -0.68199836), vec2(-0.71933980, -0.69465837), vec2(-0.70710678, -0.70710678), vec2(-0.69465837, -0.71933980), vec2(-0.68199836, -0.73135370), // 222 + vec2(-0.66913061, -0.74314483), vec2(-0.65605903, -0.75470958), vec2(-0.64278761, -0.76604444), vec2(-0.62932039, -0.77714596), vec2(-0.61566148, -0.78801075), vec2(-0.60181502, -0.79863551), // 228 + vec2(-0.58778525, -0.80901699), vec2(-0.57357644, -0.81915204), vec2(-0.55919290, -0.82903757), vec2(-0.54463904, -0.83867057), vec2(-0.52991926, -0.84804810), vec2(-0.51503807, -0.85716730), // 234 + vec2(-0.50000000, -0.86602540), vec2(-0.48480962, -0.87461971), vec2(-0.46947156, -0.88294759), vec2(-0.45399050, -0.89100652), vec2(-0.43837115, -0.89879405), vec2(-0.42261826, -0.90630779), // 240 + vec2(-0.40673664, -0.91354546), vec2(-0.39073113, -0.92050485), vec2(-0.37460659, -0.92718385), vec2(-0.35836795, -0.93358043), vec2(-0.34202014, -0.93969262), vec2(-0.32556815, -0.94551858), // 246 + vec2(-0.30901699, -0.95105652), vec2(-0.29237170, -0.95630476), vec2(-0.27563736, -0.96126170), vec2(-0.25881905, -0.96592583), vec2(-0.24192190, -0.97029573), vec2(-0.22495105, -0.97437006), // 252 + vec2(-0.20791169, -0.97814760), vec2(-0.19080900, -0.98162718), vec2(-0.17364818, -0.98480775), vec2(-0.15643447, -0.98768834), vec2(-0.13917310, -0.99026807), vec2(-0.12186934, -0.99254615), // 258 + vec2(-0.10452846, -0.99452190), vec2(-0.08715574, -0.99619470), vec2(-0.06975647, -0.99756405), vec2(-0.05233596, -0.99862953), vec2(-0.03489950, -0.99939083), vec2(-0.01745241, -0.99984770), // 264 + vec2(-0.00000000, -1.00000000), vec2(0.01745241, -0.99984770), vec2(0.03489950, -0.99939083), vec2(0.05233596, -0.99862953), vec2(0.06975647, -0.99756405), vec2(0.08715574, -0.99619470), // 270 + vec2(0.10452846, -0.99452190), vec2(0.12186934, -0.99254615), vec2(0.13917310, -0.99026807), vec2(0.15643447, -0.98768834), vec2(0.17364818, -0.98480775), vec2(0.19080900, -0.98162718), // 276 + vec2(0.20791169, -0.97814760), vec2(0.22495105, -0.97437006), vec2(0.24192190, -0.97029573), vec2(0.25881905, -0.96592583), vec2(0.27563736, -0.96126170), vec2(0.29237170, -0.95630476), // 282 + vec2(0.30901699, -0.95105652), vec2(0.32556815, -0.94551858), vec2(0.34202014, -0.93969262), vec2(0.35836795, -0.93358043), vec2(0.37460659, -0.92718385), vec2(0.39073113, -0.92050485), // 288 + vec2(0.40673664, -0.91354546), vec2(0.42261826, -0.90630779), vec2(0.43837115, -0.89879405), vec2(0.45399050, -0.89100652), vec2(0.46947156, -0.88294759), vec2(0.48480962, -0.87461971), // 294 + vec2(0.50000000, -0.86602540), vec2(0.51503807, -0.85716730), vec2(0.52991926, -0.84804810), vec2(0.54463904, -0.83867057), vec2(0.55919290, -0.82903757), vec2(0.57357644, -0.81915204), // 300 + vec2(0.58778525, -0.80901699), vec2(0.60181502, -0.79863551), vec2(0.61566148, -0.78801075), vec2(0.62932039, -0.77714596), vec2(0.64278761, -0.76604444), vec2(0.65605903, -0.75470958), // 306 + vec2(0.66913061, -0.74314483), vec2(0.68199836, -0.73135370), vec2(0.69465837, -0.71933980), vec2(0.70710678, -0.70710678), vec2(0.71933980, -0.69465837), vec2(0.73135370, -0.68199836), // 312 + vec2(0.74314483, -0.66913061), vec2(0.75470958, -0.65605903), vec2(0.76604444, -0.64278761), vec2(0.77714596, -0.62932039), vec2(0.78801075, -0.61566148), vec2(0.79863551, -0.60181502), // 318 + vec2(0.80901699, -0.58778525), vec2(0.81915204, -0.57357644), vec2(0.82903757, -0.55919290), vec2(0.83867057, -0.54463904), vec2(0.84804810, -0.52991926), vec2(0.85716730, -0.51503807), // 324 + vec2(0.86602540, -0.50000000), vec2(0.87461971, -0.48480962), vec2(0.88294759, -0.46947156), vec2(0.89100652, -0.45399050), vec2(0.89879405, -0.43837115), vec2(0.90630779, -0.42261826), // 330 + vec2(0.91354546, -0.40673664), vec2(0.92050485, -0.39073113), vec2(0.92718385, -0.37460659), vec2(0.93358043, -0.35836795), vec2(0.93969262, -0.34202014), vec2(0.94551858, -0.32556815), // 336 + vec2(0.95105652, -0.30901699), vec2(0.95630476, -0.29237170), vec2(0.96126170, -0.27563736), vec2(0.96592583, -0.25881905), vec2(0.97029573, -0.24192190), vec2(0.97437006, -0.22495105), // 342 + vec2(0.97814760, -0.20791169), vec2(0.98162718, -0.19080900), vec2(0.98480775, -0.17364818), vec2(0.98768834, -0.15643447), vec2(0.99026807, -0.13917310), vec2(0.99254615, -0.12186934), // 348 + vec2(0.99452190, -0.10452846), vec2(0.99619470, -0.08715574), vec2(0.99756405, -0.06975647), vec2(0.99862953, -0.05233596), vec2(0.99939083, -0.03489950), vec2(0.99984770, -0.01745241), // 354 + vec2(1.00000000, 0.00000000), vec2(0.99984770, 0.01745241), vec2(0.99939083, 0.03489950), vec2(0.99862953, 0.05233596), vec2(0.99756405, 0.06975647), vec2(0.99619470, 0.08715574), // 360 + vec2(0.99452190, 0.10452846), vec2(0.99254615, 0.12186934), vec2(0.99026807, 0.13917310), vec2(0.98768834, 0.15643447), vec2(0.98480775, 0.17364818), vec2(0.98162718, 0.19080900), // 366 + vec2(0.97814760, 0.20791169), vec2(0.97437006, 0.22495105), vec2(0.97029573, 0.24192190), vec2(0.96592583, 0.25881905), vec2(0.96126170, 0.27563736), vec2(0.95630476, 0.29237170), // 372 + vec2(0.95105652, 0.30901699), vec2(0.94551858, 0.32556815), vec2(0.93969262, 0.34202014), vec2(0.93358043, 0.35836795), vec2(0.92718385, 0.37460659), vec2(0.92050485, 0.39073113), // 378 + vec2(0.91354546, 0.40673664), vec2(0.90630779, 0.42261826), vec2(0.89879405, 0.43837115), vec2(0.89100652, 0.45399050), vec2(0.88294759, 0.46947156), vec2(0.87461971, 0.48480962), // 384 + vec2(0.86602540, 0.50000000), vec2(0.85716730, 0.51503807), vec2(0.84804810, 0.52991926), vec2(0.83867057, 0.54463904), vec2(0.82903757, 0.55919290), vec2(0.81915204, 0.57357644), // 390 + vec2(0.80901699, 0.58778525), vec2(0.79863551, 0.60181502), vec2(0.78801075, 0.61566148), vec2(0.77714596, 0.62932039), vec2(0.76604444, 0.64278761), vec2(0.75470958, 0.65605903), // 396 + vec2(0.74314483, 0.66913061), vec2(0.73135370, 0.68199836), vec2(0.71933980, 0.69465837), vec2(0.70710678, 0.70710678), vec2(0.69465837, 0.71933980), vec2(0.68199836, 0.73135370), // 402 + vec2(0.66913061, 0.74314483), vec2(0.65605903, 0.75470958), vec2(0.64278761, 0.76604444), vec2(0.62932039, 0.77714596), vec2(0.61566148, 0.78801075), vec2(0.60181502, 0.79863551), // 408 + vec2(0.58778525, 0.80901699), vec2(0.57357644, 0.81915204), vec2(0.55919290, 0.82903757), vec2(0.54463904, 0.83867057), vec2(0.52991926, 0.84804810), vec2(0.51503807, 0.85716730), // 414 + vec2(0.50000000, 0.86602540), vec2(0.48480962, 0.87461971), vec2(0.46947156, 0.88294759), vec2(0.45399050, 0.89100652), vec2(0.43837115, 0.89879405), vec2(0.42261826, 0.90630779), // 420 + vec2(0.40673664, 0.91354546), vec2(0.39073113, 0.92050485), vec2(0.37460659, 0.92718385), vec2(0.35836795, 0.93358043), vec2(0.34202014, 0.93969262), vec2(0.32556815, 0.94551858), // 426 + vec2(0.30901699, 0.95105652), vec2(0.29237170, 0.95630476), vec2(0.27563736, 0.96126170), vec2(0.25881905, 0.96592583), vec2(0.24192190, 0.97029573), vec2(0.22495105, 0.97437006), // 432 + vec2(0.20791169, 0.97814760), vec2(0.19080900, 0.98162718), vec2(0.17364818, 0.98480775), vec2(0.15643447, 0.98768834), vec2(0.13917310, 0.99026807), vec2(0.12186934, 0.99254615), // 438 + vec2(0.10452846, 0.99452190), vec2(0.08715574, 0.99619470), vec2(0.06975647, 0.99756405), vec2(0.05233596, 0.99862953), vec2(0.03489950, 0.99939083), vec2(0.01745241, 0.99984770), // 444 + vec2(0.00000000, 1.00000000), vec2(-0.01745241, 0.99984770), vec2(-0.03489950, 0.99939083), vec2(-0.05233596, 0.99862953), vec2(-0.06975647, 0.99756405), vec2(-0.08715574, 0.99619470), // 450 + vec2(-0.10452846, 0.99452190), vec2(-0.12186934, 0.99254615), vec2(-0.13917310, 0.99026807), vec2(-0.15643447, 0.98768834), vec2(-0.17364818, 0.98480775), vec2(-0.19080900, 0.98162718), // 456 + vec2(-0.20791169, 0.97814760), vec2(-0.22495105, 0.97437006), vec2(-0.24192190, 0.97029573), vec2(-0.25881905, 0.96592583), vec2(-0.27563736, 0.96126170), vec2(-0.29237170, 0.95630476), // 462 + vec2(-0.30901699, 0.95105652), vec2(-0.32556815, 0.94551858), vec2(-0.34202014, 0.93969262), vec2(-0.35836795, 0.93358043), vec2(-0.37460659, 0.92718385), vec2(-0.39073113, 0.92050485), // 468 + vec2(-0.40673664, 0.91354546), vec2(-0.42261826, 0.90630779), vec2(-0.43837115, 0.89879405), vec2(-0.45399050, 0.89100652), vec2(-0.46947156, 0.88294759), vec2(-0.48480962, 0.87461971), // 474 + vec2(-0.50000000, 0.86602540), vec2(-0.51503807, 0.85716730), vec2(-0.52991926, 0.84804810), vec2(-0.54463904, 0.83867057), vec2(-0.55919290, 0.82903757), vec2(-0.57357644, 0.81915204), // 480 + vec2(-0.58778525, 0.80901699), vec2(-0.60181502, 0.79863551), vec2(-0.61566148, 0.78801075), vec2(-0.62932039, 0.77714596), vec2(-0.64278761, 0.76604444), vec2(-0.65605903, 0.75470958), // 486 + vec2(-0.66913061, 0.74314483), vec2(-0.68199836, 0.73135370), vec2(-0.69465837, 0.71933980), vec2(-0.70710678, 0.70710678), vec2(-0.71933980, 0.69465837), vec2(-0.73135370, 0.68199836), // 492 + vec2(-0.74314483, 0.66913061), vec2(-0.75470958, 0.65605903), vec2(-0.76604444, 0.64278761), vec2(-0.77714596, 0.62932039), vec2(-0.78801075, 0.61566148), vec2(-0.79863551, 0.60181502), // 498 + vec2(-0.80901699, 0.58778525), vec2(-0.81915204, 0.57357644), vec2(-0.82903757, 0.55919290), vec2(-0.83867057, 0.54463904), vec2(-0.84804810, 0.52991926), vec2(-0.85716730, 0.51503807), // 504 + vec2(-0.86602540, 0.50000000), vec2(-0.87461971, 0.48480962), vec2(-0.88294759, 0.46947156), vec2(-0.89100652, 0.45399050), vec2(-0.89879405, 0.43837115), vec2(-0.90630779, 0.42261826), // 510 + vec2(-0.91354546, 0.40673664), vec2(-0.92050485, 0.39073113), vec2(-0.92718385, 0.37460659), vec2(-0.93358043, 0.35836795), vec2(-0.93969262, 0.34202014), vec2(-0.94551858, 0.32556815), // 516 + vec2(-0.95105652, 0.30901699), vec2(-0.95630476, 0.29237170), vec2(-0.96126170, 0.27563736), vec2(-0.96592583, 0.25881905), vec2(-0.97029573, 0.24192190), vec2(-0.97437006, 0.22495105), // 522 + vec2(-0.97814760, 0.20791169), vec2(-0.98162718, 0.19080900), vec2(-0.98480775, 0.17364818), vec2(-0.98768834, 0.15643447), vec2(-0.99026807, 0.13917310), vec2(-0.99254615, 0.12186934), // 528 + vec2(-0.99452190, 0.10452846), vec2(-0.99619470, 0.08715574), vec2(-0.99756405, 0.06975647), vec2(-0.99862953, 0.05233596), vec2(-0.99939083, 0.03489950), vec2(-0.99984770, 0.01745241), // 534 + vec2(-1.00000000, 0.00000000), vec2(-0.99984770, -0.01745241), vec2(-0.99939083, -0.03489950), vec2(-0.99862953, -0.05233596), vec2(-0.99756405, -0.06975647), vec2(-0.99619470, -0.08715574), // 540 + vec2(-0.99452190, -0.10452846), vec2(-0.99254615, -0.12186934), vec2(-0.99026807, -0.13917310), vec2(-0.98768834, -0.15643447), vec2(-0.98480775, -0.17364818), vec2(-0.98162718, -0.19080900), // 546 + vec2(-0.97814760, -0.20791169), vec2(-0.97437006, -0.22495105), vec2(-0.97029573, -0.24192190), vec2(-0.96592583, -0.25881905), vec2(-0.96126170, -0.27563736), vec2(-0.95630476, -0.29237170), // 552 + vec2(-0.95105652, -0.30901699), vec2(-0.94551858, -0.32556815), vec2(-0.93969262, -0.34202014), vec2(-0.93358043, -0.35836795), vec2(-0.92718385, -0.37460659), vec2(-0.92050485, -0.39073113), // 558 + vec2(-0.91354546, -0.40673664), vec2(-0.90630779, -0.42261826), vec2(-0.89879405, -0.43837115), vec2(-0.89100652, -0.45399050), vec2(-0.88294759, -0.46947156), vec2(-0.87461971, -0.48480962), // 564 + vec2(-0.86602540, -0.50000000), vec2(-0.85716730, -0.51503807), vec2(-0.84804810, -0.52991926), vec2(-0.83867057, -0.54463904), vec2(-0.82903757, -0.55919290), vec2(-0.81915204, -0.57357644), // 570 + vec2(-0.80901699, -0.58778525), vec2(-0.79863551, -0.60181502), vec2(-0.78801075, -0.61566148), vec2(-0.77714596, -0.62932039), vec2(-0.76604444, -0.64278761), vec2(-0.75470958, -0.65605903), // 576 + vec2(-0.74314483, -0.66913061), vec2(-0.73135370, -0.68199836), vec2(-0.71933980, -0.69465837), vec2(-0.70710678, -0.70710678), vec2(-0.69465837, -0.71933980), vec2(-0.68199836, -0.73135370), // 582 + vec2(-0.66913061, -0.74314483), vec2(-0.65605903, -0.75470958), vec2(-0.64278761, -0.76604444), vec2(-0.62932039, -0.77714596), vec2(-0.61566148, -0.78801075), vec2(-0.60181502, -0.79863551), // 588 + vec2(-0.58778525, -0.80901699), vec2(-0.57357644, -0.81915204), vec2(-0.55919290, -0.82903757), vec2(-0.54463904, -0.83867057), vec2(-0.52991926, -0.84804810), vec2(-0.51503807, -0.85716730), // 594 + vec2(-0.50000000, -0.86602540), vec2(-0.48480962, -0.87461971), vec2(-0.46947156, -0.88294759), vec2(-0.45399050, -0.89100652), vec2(-0.43837115, -0.89879405), vec2(-0.42261826, -0.90630779), // 600 + vec2(-0.40673664, -0.91354546), vec2(-0.39073113, -0.92050485), vec2(-0.37460659, -0.92718385), vec2(-0.35836795, -0.93358043), vec2(-0.34202014, -0.93969262), vec2(-0.32556815, -0.94551858), // 606 + vec2(-0.30901699, -0.95105652), vec2(-0.29237170, -0.95630476), vec2(-0.27563736, -0.96126170), vec2(-0.25881905, -0.96592583), vec2(-0.24192190, -0.97029573), vec2(-0.22495105, -0.97437006), // 612 + vec2(-0.20791169, -0.97814760), vec2(-0.19080900, -0.98162718), vec2(-0.17364818, -0.98480775), vec2(-0.15643447, -0.98768834), vec2(-0.13917310, -0.99026807), vec2(-0.12186934, -0.99254615), // 618 + vec2(-0.10452846, -0.99452190), vec2(-0.08715574, -0.99619470), vec2(-0.06975647, -0.99756405), vec2(-0.05233596, -0.99862953), vec2(-0.03489950, -0.99939083), vec2(-0.01745241, -0.99984770), // 624 + vec2(-0.00000000, -1.00000000), vec2(0.01745241, -0.99984770), vec2(0.03489950, -0.99939083), vec2(0.05233596, -0.99862953), vec2(0.06975647, -0.99756405), vec2(0.08715574, -0.99619470), // 630 + vec2(0.10452846, -0.99452190), vec2(0.12186934, -0.99254615), vec2(0.13917310, -0.99026807), vec2(0.15643447, -0.98768834), vec2(0.17364818, -0.98480775), vec2(0.19080900, -0.98162718), // 636 + vec2(0.20791169, -0.97814760), vec2(0.22495105, -0.97437006), vec2(0.24192190, -0.97029573), vec2(0.25881905, -0.96592583), vec2(0.27563736, -0.96126170), vec2(0.29237170, -0.95630476), // 642 + vec2(0.30901699, -0.95105652), vec2(0.32556815, -0.94551858), vec2(0.34202014, -0.93969262), vec2(0.35836795, -0.93358043), vec2(0.37460659, -0.92718385), vec2(0.39073113, -0.92050485), // 648 + vec2(0.40673664, -0.91354546), vec2(0.42261826, -0.90630779), vec2(0.43837115, -0.89879405), vec2(0.45399050, -0.89100652), vec2(0.46947156, -0.88294759), vec2(0.48480962, -0.87461971), // 654 + vec2(0.50000000, -0.86602540), vec2(0.51503807, -0.85716730), vec2(0.52991926, -0.84804810), vec2(0.54463904, -0.83867057), vec2(0.55919290, -0.82903757), vec2(0.57357644, -0.81915204), // 660 + vec2(0.58778525, -0.80901699), vec2(0.60181502, -0.79863551), vec2(0.61566148, -0.78801075), vec2(0.62932039, -0.77714596), vec2(0.64278761, -0.76604444), vec2(0.65605903, -0.75470958), // 666 + vec2(0.66913061, -0.74314483), vec2(0.68199836, -0.73135370), vec2(0.69465837, -0.71933980), vec2(0.70710678, -0.70710678), vec2(0.71933980, -0.69465837), vec2(0.73135370, -0.68199836), // 672 + vec2(0.74314483, -0.66913061), vec2(0.75470958, -0.65605903), vec2(0.76604444, -0.64278761), vec2(0.77714596, -0.62932039), vec2(0.78801075, -0.61566148), vec2(0.79863551, -0.60181502), // 678 + vec2(0.80901699, -0.58778525), vec2(0.81915204, -0.57357644), vec2(0.82903757, -0.55919290), vec2(0.83867057, -0.54463904), vec2(0.84804810, -0.52991926), vec2(0.85716730, -0.51503807), // 684 + vec2(0.86602540, -0.50000000), vec2(0.87461971, -0.48480962), vec2(0.88294759, -0.46947156), vec2(0.89100652, -0.45399050), vec2(0.89879405, -0.43837115), vec2(0.90630779, -0.42261826), // 690 + vec2(0.91354546, -0.40673664), vec2(0.92050485, -0.39073113), vec2(0.92718385, -0.37460659), vec2(0.93358043, -0.35836795), vec2(0.93969262, -0.34202014), vec2(0.94551858, -0.32556815), // 696 + vec2(0.95105652, -0.30901699), vec2(0.95630476, -0.29237170), vec2(0.96126170, -0.27563736), vec2(0.96592583, -0.25881905), vec2(0.97029573, -0.24192190), vec2(0.97437006, -0.22495105), // 702 + vec2(0.97814760, -0.20791169), vec2(0.98162718, -0.19080900), vec2(0.98480775, -0.17364818), vec2(0.98768834, -0.15643447), vec2(0.99026807, -0.13917310), vec2(0.99254615, -0.12186934), // 708 + vec2(0.99452190, -0.10452846), vec2(0.99619470, -0.08715574), vec2(0.99756405, -0.06975647), vec2(0.99862953, -0.05233596), vec2(0.99939083, -0.03489950), vec2(0.99984770, -0.01745241), // 714 + vec2(1.00000000, 0.00000000) // 720 }; diff --git a/src/shared/geom.h b/src/shared/geom.h index 3adccc6..e01acc2 100644 --- a/src/shared/geom.h +++ b/src/shared/geom.h @@ -3,327 +3,327 @@ struct vec4; struct vec2 { - union - { - struct { float x, y; }; - float v[2]; - }; - - vec2() {} - vec2(float x, float y) : x(x), y(y) {} - explicit vec2(const vec &v); - explicit vec2(const vec4 &v); - - float &operator[](int i) { return v[i]; } - float operator[](int i) const { return v[i]; } - - bool operator==(const vec2 &o) const { return x == o.x && y == o.y; } - bool operator!=(const vec2 &o) const { return x != o.x || y != o.y; } - - bool iszero() const { return x==0 && y==0; } - float dot(const vec2 &o) const { return x*o.x + y*o.y; } - float squaredlen() const { return dot(*this); } - float magnitude() const { return sqrtf(squaredlen()); } - vec2 &normalize() { mul(1/magnitude()); return *this; } - vec2 &safenormalize() { float m = magnitude(); if(m) mul(1/m); return *this; } - float cross(const vec2 &o) const { return x*o.y - y*o.x; } - - vec2 &mul(float f) { x *= f; y *= f; return *this; } - vec2 &mul(const vec2 &o) { x *= o.x; y *= o.y; return *this; } - vec2 &square() { mul(*this); return *this; } - vec2 &div(float f) { x /= f; y /= f; return *this; } - vec2 &div(const vec2 &o) { x /= o.x; y /= o.y; return *this; } - vec2 &recip() { x = 1/x; y = 1/y; return *this; } - vec2 &add(float f) { x += f; y += f; return *this; } - vec2 &add(const vec2 &o) { x += o.x; y += o.y; return *this; } - vec2 &sub(float f) { x -= f; y -= f; return *this; } - vec2 &sub(const vec2 &o) { x -= o.x; y -= o.y; return *this; } - vec2 &neg() { x = -x; y = -y; return *this; } - vec2 &min(const vec2 &o) { x = ::min(x, o.x); y = ::min(y, o.y); return *this; } - vec2 &max(const vec2 &o) { x = ::max(x, o.x); y = ::max(y, o.y); return *this; } - vec2 &min(float f) { x = ::min(x, f); y = ::min(y, f); return *this; } - vec2 &max(float f) { x = ::max(x, f); y = ::max(y, f); return *this; } - vec2 &abs() { x = fabs(x); y = fabs(y); return *this; } - vec2 &clamp(float l, float h) { x = ::clamp(x, l, h); y = ::clamp(y, l, h); return *this; } - vec2 &reflect(const vec2 &n) { float k = 2*dot(n); x -= k*n.x; y -= k*n.y; return *this; } - vec2 &lerp(const vec2 &b, float t) { x += (b.x-x)*t; y += (b.y-y)*t; return *this; } - vec2 &lerp(const vec2 &a, const vec2 &b, float t) { x = a.x + (b.x-a.x)*t; y = a.y + (b.y-a.y)*t; return *this; } - template vec2 &madd(const vec2 &a, const B &b) { return add(vec2(a).mul(b)); } - template vec2 &msub(const vec2 &a, const B &b) { return sub(vec2(a).mul(b)); } + union + { + struct { float x, y; }; + float v[2]; + }; + + vec2() {} + vec2(float x, float y) : x(x), y(y) {} + explicit vec2(const vec &v); + explicit vec2(const vec4 &v); + + float &operator[](int i) { return v[i]; } + float operator[](int i) const { return v[i]; } + + bool operator==(const vec2 &o) const { return x == o.x && y == o.y; } + bool operator!=(const vec2 &o) const { return x != o.x || y != o.y; } + + bool iszero() const { return x==0 && y==0; } + float dot(const vec2 &o) const { return x*o.x + y*o.y; } + float squaredlen() const { return dot(*this); } + float magnitude() const { return sqrtf(squaredlen()); } + vec2 &normalize() { mul(1/magnitude()); return *this; } + vec2 &safenormalize() { float m = magnitude(); if(m) mul(1/m); return *this; } + float cross(const vec2 &o) const { return x*o.y - y*o.x; } + + vec2 &mul(float f) { x *= f; y *= f; return *this; } + vec2 &mul(const vec2 &o) { x *= o.x; y *= o.y; return *this; } + vec2 &square() { mul(*this); return *this; } + vec2 &div(float f) { x /= f; y /= f; return *this; } + vec2 &div(const vec2 &o) { x /= o.x; y /= o.y; return *this; } + vec2 &recip() { x = 1/x; y = 1/y; return *this; } + vec2 &add(float f) { x += f; y += f; return *this; } + vec2 &add(const vec2 &o) { x += o.x; y += o.y; return *this; } + vec2 &sub(float f) { x -= f; y -= f; return *this; } + vec2 &sub(const vec2 &o) { x -= o.x; y -= o.y; return *this; } + vec2 &neg() { x = -x; y = -y; return *this; } + vec2 &min(const vec2 &o) { x = ::min(x, o.x); y = ::min(y, o.y); return *this; } + vec2 &max(const vec2 &o) { x = ::max(x, o.x); y = ::max(y, o.y); return *this; } + vec2 &min(float f) { x = ::min(x, f); y = ::min(y, f); return *this; } + vec2 &max(float f) { x = ::max(x, f); y = ::max(y, f); return *this; } + vec2 &abs() { x = fabs(x); y = fabs(y); return *this; } + vec2 &clamp(float l, float h) { x = ::clamp(x, l, h); y = ::clamp(y, l, h); return *this; } + vec2 &reflect(const vec2 &n) { float k = 2*dot(n); x -= k*n.x; y -= k*n.y; return *this; } + vec2 &lerp(const vec2 &b, float t) { x += (b.x-x)*t; y += (b.y-y)*t; return *this; } + vec2 &lerp(const vec2 &a, const vec2 &b, float t) { x = a.x + (b.x-a.x)*t; y = a.y + (b.y-a.y)*t; return *this; } + template vec2 &madd(const vec2 &a, const B &b) { return add(vec2(a).mul(b)); } + template vec2 &msub(const vec2 &a, const B &b) { return sub(vec2(a).mul(b)); } }; static inline bool htcmp(const vec2 &x, const vec2 &y) { - return x == y; + return x == y; } static inline uint hthash(const vec2 &k) { - union { uint i; float f; } x, y; - x.f = k.x; y.f = k.y; - uint v = x.i^y.i; - return v + (v>>12); + union { uint i; float f; } x, y; + x.f = k.x; y.f = k.y; + uint v = x.i^y.i; + return v + (v>>12); } struct ivec; struct vec { - union - { - struct { float x, y, z; }; - struct { float r, g, b; }; - float v[3]; - }; - - vec() {} - explicit vec(int a) : x(a), y(a), z(a) {} - explicit vec(float a) : x(a), y(a), z(a) {} - vec(float a, float b, float c) : x(a), y(b), z(c) {} - explicit vec(int v[3]) : x(v[0]), y(v[1]), z(v[2]) {} - explicit vec(const float *v) : x(v[0]), y(v[1]), z(v[2]) {} - explicit vec(const vec2 &v, float z = 0) : x(v.x), y(v.y), z(z) {} - explicit vec(const vec4 &v); - explicit vec(const ivec &v); - - vec(float yaw, float pitch) : x(-sinf(yaw)*cosf(pitch)), y(cosf(yaw)*cosf(pitch)), z(sinf(pitch)) {} - - float &operator[](int i) { return v[i]; } - float operator[](int i) const { return v[i]; } - - vec &set(int i, float f) { v[i] = f; return *this; } - - bool operator==(const vec &o) const { return x == o.x && y == o.y && z == o.z; } - bool operator!=(const vec &o) const { return x != o.x || y != o.y || z != o.z; } - - bool iszero() const { return x==0 && y==0 && z==0; } - float squaredlen() const { return x*x + y*y + z*z; } - template float dot2(const T &o) const { return x*o.x + y*o.y; } - float dot(const vec &o) const { return x*o.x + y*o.y + z*o.z; } - float absdot(const vec &o) const { return fabs(x*o.x) + fabs(y*o.y) + fabs(z*o.z); } - vec &pow(float f) { x = ::pow(x, f); y = ::pow(y, f); z = ::pow(z, f); return *this; } - vec &exp() { x = ::exp(x); y = ::exp(y); z = ::exp(z); return *this; } - vec &exp2() { x = ::exp2(x); y = ::exp2(y); z = ::exp2(z); return *this; } - vec &sqrt() { x = sqrtf(x); y = sqrtf(y); z = sqrtf(z); return *this; } - vec &mul(const vec &o) { x *= o.x; y *= o.y; z *= o.z; return *this; } - vec &mul(float f) { x *= f; y *= f; z *= f; return *this; } - vec &square() { mul(*this); return *this; } - vec &div(const vec &o) { x /= o.x; y /= o.y; z /= o.z; return *this; } - vec &div(float f) { x /= f; y /= f; z /= f; return *this; } - vec &recip() { x = 1/x; y = 1/y; z = 1/z; return *this; } - vec &add(const vec &o) { x += o.x; y += o.y; z += o.z; return *this; } - vec &add(float f) { x += f; y += f; z += f; return *this; } - vec &add2(float f) { x += f; y += f; return *this; } - vec &addz(float f) { z += f; return *this; } - vec &sub(const vec &o) { x -= o.x; y -= o.y; z -= o.z; return *this; } - vec &sub(float f) { x -= f; y -= f; z -= f; return *this; } - vec &sub2(float f) { x -= f; y -= f; return *this; } - vec &subz(float f) { z -= f; return *this; } - vec &neg2() { x = -x; y = -y; return *this; } - vec &neg() { x = -x; y = -y; z = -z; return *this; } - vec &min(const vec &o) { x = ::min(x, o.x); y = ::min(y, o.y); z = ::min(z, o.z); return *this; } - vec &max(const vec &o) { x = ::max(x, o.x); y = ::max(y, o.y); z = ::max(z, o.z); return *this; } - vec &min(float f) { x = ::min(x, f); y = ::min(y, f); z = ::min(z, f); return *this; } - vec &max(float f) { x = ::max(x, f); y = ::max(y, f); z = ::max(z, f); return *this; } - vec &clamp(float f, float h) { x = ::clamp(x, f, h); y = ::clamp(y, f, h); z = ::clamp(z, f, h); return *this; } - vec &abs() { x = fabs(x); y = fabs(y); z = fabs(z); return *this; } - float magnitude2() const { return sqrtf(dot2(*this)); } - float magnitude() const { return sqrtf(squaredlen()); } - vec &normalize() { div(magnitude()); return *this; } - vec &safenormalize() { float m = magnitude(); if(m) div(m); return *this; } - bool isnormalized() const { float m = squaredlen(); return (m>0.99f && m<1.01f); } - float squaredist(const vec &e) const { return vec(*this).sub(e).squaredlen(); } - float dist(const vec &e) const { vec t; return dist(e, t); } - float dist(const vec &e, vec &t) const { t = *this; t.sub(e); return t.magnitude(); } - float dist2(const vec &o) const { float dx = x-o.x, dy = y-o.y; return sqrtf(dx*dx + dy*dy); } - bool reject(const vec &o, float r) { return x>o.x+r || xo.y+r || y - vec &cross(const A &a, const B &b) { x = a.y*b.z-a.z*b.y; y = a.z*b.x-a.x*b.z; z = a.x*b.y-a.y*b.x; return *this; } - vec &cross(const vec &o, const vec &a, const vec &b) { return cross(vec(a).sub(o), vec(b).sub(o)); } - float scalartriple(const vec &a, const vec &b) const { return x*(a.y*b.z-a.z*b.y) + y*(a.z*b.x-a.x*b.z) + z*(a.x*b.y-a.y*b.x); } - vec &reflectz(float rz) { z = 2*rz - z; return *this; } - vec &reflect(const vec &n) { float k = 2*dot(n); x -= k*n.x; y -= k*n.y; z -= k*n.z; return *this; } - vec &project(const vec &n) { float k = dot(n); x -= k*n.x; y -= k*n.y; z -= k*n.z; return *this; } - vec &projectxydir(const vec &n) { if(n.z) z = -(x*n.x/n.z + y*n.y/n.z); return *this; } - vec &projectxy(const vec &n) - { - float m = squaredlen(), k = dot(n); - projectxydir(n); - rescale(sqrtf(::max(m - k*k, 0.0f))); - return *this; - } - vec &projectxy(const vec &n, float threshold) - { - float m = squaredlen(), k = ::min(dot(n), threshold); - projectxydir(n); - rescale(sqrtf(::max(m - k*k, 0.0f))); - return *this; - } - vec &lerp(const vec &b, float t) { x += (b.x-x)*t; y += (b.y-y)*t; z += (b.z-z)*t; return *this; } - vec &lerp(const vec &a, const vec &b, float t) { x = a.x + (b.x-a.x)*t; y = a.y + (b.y-a.y)*t; z = a.z + (b.z-a.z)*t; return *this; } - template vec &madd(const vec &a, const B &b) { return add(vec(a).mul(b)); } - template vec &msub(const vec &a, const B &b) { return sub(vec(a).mul(b)); } - - vec &rescale(float k) - { - float mag = magnitude(); - if(mag > 1e-6f) mul(k / mag); - return *this; - } - - vec &rotate_around_z(float c, float s) { float rx = x, ry = y; x = c*rx-s*ry; y = c*ry+s*rx; return *this; } - vec &rotate_around_x(float c, float s) { float ry = y, rz = z; y = c*ry-s*rz; z = c*rz+s*ry; return *this; } - vec &rotate_around_y(float c, float s) { float rx = x, rz = z; x = c*rx+s*rz; z = c*rz-s*rx; return *this; } - - vec &rotate_around_z(float angle) { return rotate_around_z(cosf(angle), sinf(angle)); } - vec &rotate_around_x(float angle) { return rotate_around_x(cosf(angle), sinf(angle)); } - vec &rotate_around_y(float angle) { return rotate_around_y(cosf(angle), sinf(angle)); } - - vec &rotate_around_z(const vec2 &sc) { return rotate_around_z(sc.x, sc.y); } - vec &rotate_around_x(const vec2 &sc) { return rotate_around_x(sc.x, sc.y); } - vec &rotate_around_y(const vec2 &sc) { return rotate_around_y(sc.x, sc.y); } - - vec &rotate(float c, float s, const vec &d) - { - *this = vec(x*(d.x*d.x*(1-c)+c) + y*(d.x*d.y*(1-c)-d.z*s) + z*(d.x*d.z*(1-c)+d.y*s), - x*(d.y*d.x*(1-c)+d.z*s) + y*(d.y*d.y*(1-c)+c) + z*(d.y*d.z*(1-c)-d.x*s), - x*(d.x*d.z*(1-c)-d.y*s) + y*(d.y*d.z*(1-c)+d.x*s) + z*(d.z*d.z*(1-c)+c)); - return *this; - } - vec &rotate(float angle, const vec &d) { return rotate(cosf(angle), sinf(angle), d); } - vec &rotate(const vec2 &sc, const vec &d) { return rotate(sc.x, sc.y, d); } - - void orthogonal(const vec &d) - { - *this = fabs(d.x) > fabs(d.z) ? vec(-d.y, d.x, 0) : vec(0, -d.z, d.y); - } - - void orthonormalize(vec &s, vec &t) const - { - s.sub(vec(*this).mul(dot(s))); - t.sub(vec(*this).mul(dot(t))) - .sub(vec(s).mul(s.dot(t))); - } - - template - bool insidebb(const T &bbmin, const T &bbmax) const - { - return x >= bbmin.x && x <= bbmax.x && y >= bbmin.y && y <= bbmax.y && z >= bbmin.z && z <= bbmax.z; - } - - template - bool insidebb(const T &o, U size) const - { - return x >= o.x && x <= o.x + size && y >= o.y && y <= o.y + size && z >= o.z && z <= o.z + size; - } - - template float dist_to_bb(const T &min, const T &max) const - { - float sqrdist = 0; - loopi(3) - { - if (v[i] < min[i]) { float delta = v[i]-min[i]; sqrdist += delta*delta; } - else if(v[i] > max[i]) { float delta = max[i]-v[i]; sqrdist += delta*delta; } - } - return sqrtf(sqrdist); - } - - template float dist_to_bb(const T &o, S size) const - { - return dist_to_bb(o, T(o).add(size)); - } - - static vec hexcolor(int color) - { - return vec(((color>>16)&0xFF)*(1.0f/255.0f), ((color>>8)&0xFF)*(1.0f/255.0f), (color&0xFF)*(1.0f/255.0f)); - } - int tohexcolor() const { return (int(::clamp(r, 0.0f, 1.0f)*255)<<16)|(int(::clamp(g, 0.0f, 1.0f)*255)<<8)|int(::clamp(b, 0.0f, 1.0f)*255); } + union + { + struct { float x, y, z; }; + struct { float r, g, b; }; + float v[3]; + }; + + vec() {} + explicit vec(int a) : x(a), y(a), z(a) {} + explicit vec(float a) : x(a), y(a), z(a) {} + vec(float a, float b, float c) : x(a), y(b), z(c) {} + explicit vec(int v[3]) : x(v[0]), y(v[1]), z(v[2]) {} + explicit vec(const float *v) : x(v[0]), y(v[1]), z(v[2]) {} + explicit vec(const vec2 &v, float z = 0) : x(v.x), y(v.y), z(z) {} + explicit vec(const vec4 &v); + explicit vec(const ivec &v); + + vec(float yaw, float pitch) : x(-sinf(yaw)*cosf(pitch)), y(cosf(yaw)*cosf(pitch)), z(sinf(pitch)) {} + + float &operator[](int i) { return v[i]; } + float operator[](int i) const { return v[i]; } + + vec &set(int i, float f) { v[i] = f; return *this; } + + bool operator==(const vec &o) const { return x == o.x && y == o.y && z == o.z; } + bool operator!=(const vec &o) const { return x != o.x || y != o.y || z != o.z; } + + bool iszero() const { return x==0 && y==0 && z==0; } + float squaredlen() const { return x*x + y*y + z*z; } + template float dot2(const T &o) const { return x*o.x + y*o.y; } + float dot(const vec &o) const { return x*o.x + y*o.y + z*o.z; } + float absdot(const vec &o) const { return fabs(x*o.x) + fabs(y*o.y) + fabs(z*o.z); } + vec &pow(float f) { x = ::pow(x, f); y = ::pow(y, f); z = ::pow(z, f); return *this; } + vec &exp() { x = ::exp(x); y = ::exp(y); z = ::exp(z); return *this; } + vec &exp2() { x = ::exp2(x); y = ::exp2(y); z = ::exp2(z); return *this; } + vec &sqrt() { x = sqrtf(x); y = sqrtf(y); z = sqrtf(z); return *this; } + vec &mul(const vec &o) { x *= o.x; y *= o.y; z *= o.z; return *this; } + vec &mul(float f) { x *= f; y *= f; z *= f; return *this; } + vec &square() { mul(*this); return *this; } + vec &div(const vec &o) { x /= o.x; y /= o.y; z /= o.z; return *this; } + vec &div(float f) { x /= f; y /= f; z /= f; return *this; } + vec &recip() { x = 1/x; y = 1/y; z = 1/z; return *this; } + vec &add(const vec &o) { x += o.x; y += o.y; z += o.z; return *this; } + vec &add(float f) { x += f; y += f; z += f; return *this; } + vec &add2(float f) { x += f; y += f; return *this; } + vec &addz(float f) { z += f; return *this; } + vec &sub(const vec &o) { x -= o.x; y -= o.y; z -= o.z; return *this; } + vec &sub(float f) { x -= f; y -= f; z -= f; return *this; } + vec &sub2(float f) { x -= f; y -= f; return *this; } + vec &subz(float f) { z -= f; return *this; } + vec &neg2() { x = -x; y = -y; return *this; } + vec &neg() { x = -x; y = -y; z = -z; return *this; } + vec &min(const vec &o) { x = ::min(x, o.x); y = ::min(y, o.y); z = ::min(z, o.z); return *this; } + vec &max(const vec &o) { x = ::max(x, o.x); y = ::max(y, o.y); z = ::max(z, o.z); return *this; } + vec &min(float f) { x = ::min(x, f); y = ::min(y, f); z = ::min(z, f); return *this; } + vec &max(float f) { x = ::max(x, f); y = ::max(y, f); z = ::max(z, f); return *this; } + vec &clamp(float f, float h) { x = ::clamp(x, f, h); y = ::clamp(y, f, h); z = ::clamp(z, f, h); return *this; } + vec &abs() { x = fabs(x); y = fabs(y); z = fabs(z); return *this; } + float magnitude2() const { return sqrtf(dot2(*this)); } + float magnitude() const { return sqrtf(squaredlen()); } + vec &normalize() { div(magnitude()); return *this; } + vec &safenormalize() { float m = magnitude(); if(m) div(m); return *this; } + bool isnormalized() const { float m = squaredlen(); return (m>0.99f && m<1.01f); } + float squaredist(const vec &e) const { return vec(*this).sub(e).squaredlen(); } + float dist(const vec &e) const { vec t; return dist(e, t); } + float dist(const vec &e, vec &t) const { t = *this; t.sub(e); return t.magnitude(); } + float dist2(const vec &o) const { float dx = x-o.x, dy = y-o.y; return sqrtf(dx*dx + dy*dy); } + bool reject(const vec &o, float r) { return x>o.x+r || xo.y+r || y + vec &cross(const A &a, const B &b) { x = a.y*b.z-a.z*b.y; y = a.z*b.x-a.x*b.z; z = a.x*b.y-a.y*b.x; return *this; } + vec &cross(const vec &o, const vec &a, const vec &b) { return cross(vec(a).sub(o), vec(b).sub(o)); } + float scalartriple(const vec &a, const vec &b) const { return x*(a.y*b.z-a.z*b.y) + y*(a.z*b.x-a.x*b.z) + z*(a.x*b.y-a.y*b.x); } + vec &reflectz(float rz) { z = 2*rz - z; return *this; } + vec &reflect(const vec &n) { float k = 2*dot(n); x -= k*n.x; y -= k*n.y; z -= k*n.z; return *this; } + vec &project(const vec &n) { float k = dot(n); x -= k*n.x; y -= k*n.y; z -= k*n.z; return *this; } + vec &projectxydir(const vec &n) { if(n.z) z = -(x*n.x/n.z + y*n.y/n.z); return *this; } + vec &projectxy(const vec &n) + { + float m = squaredlen(), k = dot(n); + projectxydir(n); + rescale(sqrtf(::max(m - k*k, 0.0f))); + return *this; + } + vec &projectxy(const vec &n, float threshold) + { + float m = squaredlen(), k = ::min(dot(n), threshold); + projectxydir(n); + rescale(sqrtf(::max(m - k*k, 0.0f))); + return *this; + } + vec &lerp(const vec &b, float t) { x += (b.x-x)*t; y += (b.y-y)*t; z += (b.z-z)*t; return *this; } + vec &lerp(const vec &a, const vec &b, float t) { x = a.x + (b.x-a.x)*t; y = a.y + (b.y-a.y)*t; z = a.z + (b.z-a.z)*t; return *this; } + template vec &madd(const vec &a, const B &b) { return add(vec(a).mul(b)); } + template vec &msub(const vec &a, const B &b) { return sub(vec(a).mul(b)); } + + vec &rescale(float k) + { + float mag = magnitude(); + if(mag > 1e-6f) mul(k / mag); + return *this; + } + + vec &rotate_around_z(float c, float s) { float rx = x, ry = y; x = c*rx-s*ry; y = c*ry+s*rx; return *this; } + vec &rotate_around_x(float c, float s) { float ry = y, rz = z; y = c*ry-s*rz; z = c*rz+s*ry; return *this; } + vec &rotate_around_y(float c, float s) { float rx = x, rz = z; x = c*rx+s*rz; z = c*rz-s*rx; return *this; } + + vec &rotate_around_z(float angle) { return rotate_around_z(cosf(angle), sinf(angle)); } + vec &rotate_around_x(float angle) { return rotate_around_x(cosf(angle), sinf(angle)); } + vec &rotate_around_y(float angle) { return rotate_around_y(cosf(angle), sinf(angle)); } + + vec &rotate_around_z(const vec2 &sc) { return rotate_around_z(sc.x, sc.y); } + vec &rotate_around_x(const vec2 &sc) { return rotate_around_x(sc.x, sc.y); } + vec &rotate_around_y(const vec2 &sc) { return rotate_around_y(sc.x, sc.y); } + + vec &rotate(float c, float s, const vec &d) + { + *this = vec(x*(d.x*d.x*(1-c)+c) + y*(d.x*d.y*(1-c)-d.z*s) + z*(d.x*d.z*(1-c)+d.y*s), + x*(d.y*d.x*(1-c)+d.z*s) + y*(d.y*d.y*(1-c)+c) + z*(d.y*d.z*(1-c)-d.x*s), + x*(d.x*d.z*(1-c)-d.y*s) + y*(d.y*d.z*(1-c)+d.x*s) + z*(d.z*d.z*(1-c)+c)); + return *this; + } + vec &rotate(float angle, const vec &d) { return rotate(cosf(angle), sinf(angle), d); } + vec &rotate(const vec2 &sc, const vec &d) { return rotate(sc.x, sc.y, d); } + + void orthogonal(const vec &d) + { + *this = fabs(d.x) > fabs(d.z) ? vec(-d.y, d.x, 0) : vec(0, -d.z, d.y); + } + + void orthonormalize(vec &s, vec &t) const + { + s.sub(vec(*this).mul(dot(s))); + t.sub(vec(*this).mul(dot(t))) + .sub(vec(s).mul(s.dot(t))); + } + + template + bool insidebb(const T &bbmin, const T &bbmax) const + { + return x >= bbmin.x && x <= bbmax.x && y >= bbmin.y && y <= bbmax.y && z >= bbmin.z && z <= bbmax.z; + } + + template + bool insidebb(const T &o, U size) const + { + return x >= o.x && x <= o.x + size && y >= o.y && y <= o.y + size && z >= o.z && z <= o.z + size; + } + + template float dist_to_bb(const T &min, const T &max) const + { + float sqrdist = 0; + loopi(3) + { + if (v[i] < min[i]) { float delta = v[i]-min[i]; sqrdist += delta*delta; } + else if(v[i] > max[i]) { float delta = max[i]-v[i]; sqrdist += delta*delta; } + } + return sqrtf(sqrdist); + } + + template float dist_to_bb(const T &o, S size) const + { + return dist_to_bb(o, T(o).add(size)); + } + + static vec hexcolor(int color) + { + return vec(((color>>16)&0xFF)*(1.0f/255.0f), ((color>>8)&0xFF)*(1.0f/255.0f), (color&0xFF)*(1.0f/255.0f)); + } + int tohexcolor() const { return (int(::clamp(r, 0.0f, 1.0f)*255)<<16)|(int(::clamp(g, 0.0f, 1.0f)*255)<<8)|int(::clamp(b, 0.0f, 1.0f)*255); } }; inline vec2::vec2(const vec &v) : x(v.x), y(v.y) {} static inline bool htcmp(const vec &x, const vec &y) { - return x == y; + return x == y; } static inline uint hthash(const vec &k) { - union { uint i; float f; } x, y, z; - x.f = k.x; y.f = k.y; z.f = k.z; - uint v = x.i^y.i^z.i; - return v + (v>>12); + union { uint i; float f; } x, y, z; + x.f = k.x; y.f = k.y; z.f = k.z; + uint v = x.i^y.i^z.i; + return v + (v>>12); } struct vec4 { - union - { - struct { float x, y, z, w; }; - struct { float r, g, b, a; }; - float v[4]; - }; - - vec4() {} - explicit vec4(const vec &p, float w = 0) : x(p.x), y(p.y), z(p.z), w(w) {} - vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} - explicit vec4(const float *v) : x(v[0]), y(v[1]), z(v[2]), w(v[3]) {} - - float &operator[](int i) { return v[i]; } - float operator[](int i) const { return v[i]; } - - template float dot3(const T &o) const { return x*o.x + y*o.y + z*o.z; } - float dot(const vec4 &o) const { return dot3(o) + w*o.w; } - float dot(const vec &o) const { return x*o.x + y*o.y + z*o.z + w; } - float squaredlen() const { return dot(*this); } - float magnitude() const { return sqrtf(squaredlen()); } - float magnitude3() const { return sqrtf(dot3(*this)); } - vec4 &normalize() { mul(1/magnitude()); return *this; } - vec4 &safenormalize() { float m = magnitude(); if(m) mul(1/m); return *this; } - - vec4 &lerp(const vec4 &b, float t) - { - x += (b.x-x)*t; - y += (b.y-y)*t; - z += (b.z-z)*t; - w += (b.w-w)*t; - return *this; - } - vec4 &lerp(const vec4 &a, const vec4 &b, float t) - { - x = a.x+(b.x-a.x)*t; - y = a.y+(b.y-a.y)*t; - z = a.z+(b.z-a.z)*t; - w = a.w+(b.w-a.w)*t; - return *this; - } - - vec4 &mul3(float f) { x *= f; y *= f; z *= f; return *this; } - vec4 &mul(float f) { mul3(f); w *= f; return *this; } - vec4 &mul(const vec4 &o) { x *= o.x; y *= o.y; z *= o.z; w *= o.w; return *this; } - vec4 &square() { mul(*this); return *this; } - vec4 &div3(float f) { x /= f; y /= f; z /= f; return *this; } - vec4 &div(float f) { div3(f); w /= f; return *this; } - vec4 &div(const vec4 &o) { x /= o.x; y /= o.y; z /= o.z; w /= o.w; return *this; } - vec4 &recip() { x = 1/x; y = 1/y; z = 1/z; w = 1/w; return *this; } - vec4 &add(const vec4 &o) { x += o.x; y += o.y; z += o.z; w += o.w; return *this; } - vec4 &addw(float f) { w += f; return *this; } - vec4 &sub(const vec4 &o) { x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this; } - vec4 &subw(float f) { w -= f; return *this; } - vec4 &neg3() { x = -x; y = -y; z = -z; return *this; } - vec4 &neg() { neg3(); w = -w; return *this; } - template vec4 &madd(const vec4 &a, const B &b) { return add(vec4(a).mul(b)); } - template vec4 &msub(const vec4 &a, const B &b) { return sub(vec4(a).mul(b)); } - - void setxyz(const vec &v) { x = v.x; y = v.y; z = v.z; } - - vec4 &rotate_around_z(float c, float s) { float rx = x, ry = y; x = c*rx-s*ry; y = c*ry+s*rx; return *this; } - vec4 &rotate_around_x(float c, float s) { float ry = y, rz = z; y = c*ry-s*rz; z = c*rz+s*ry; return *this; } - vec4 &rotate_around_y(float c, float s) { float rx = x, rz = z; x = c*rx+s*rz; z = c*rz-s*rx; return *this; } - - vec4 &rotate_around_z(float angle) { return rotate_around_z(cosf(angle), sinf(angle)); } - vec4 &rotate_around_x(float angle) { return rotate_around_x(cosf(angle), sinf(angle)); } - vec4 &rotate_around_y(float angle) { return rotate_around_y(cosf(angle), sinf(angle)); } + union + { + struct { float x, y, z, w; }; + struct { float r, g, b, a; }; + float v[4]; + }; + + vec4() {} + explicit vec4(const vec &p, float w = 0) : x(p.x), y(p.y), z(p.z), w(w) {} + vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + explicit vec4(const float *v) : x(v[0]), y(v[1]), z(v[2]), w(v[3]) {} + + float &operator[](int i) { return v[i]; } + float operator[](int i) const { return v[i]; } + + template float dot3(const T &o) const { return x*o.x + y*o.y + z*o.z; } + float dot(const vec4 &o) const { return dot3(o) + w*o.w; } + float dot(const vec &o) const { return x*o.x + y*o.y + z*o.z + w; } + float squaredlen() const { return dot(*this); } + float magnitude() const { return sqrtf(squaredlen()); } + float magnitude3() const { return sqrtf(dot3(*this)); } + vec4 &normalize() { mul(1/magnitude()); return *this; } + vec4 &safenormalize() { float m = magnitude(); if(m) mul(1/m); return *this; } + + vec4 &lerp(const vec4 &b, float t) + { + x += (b.x-x)*t; + y += (b.y-y)*t; + z += (b.z-z)*t; + w += (b.w-w)*t; + return *this; + } + vec4 &lerp(const vec4 &a, const vec4 &b, float t) + { + x = a.x+(b.x-a.x)*t; + y = a.y+(b.y-a.y)*t; + z = a.z+(b.z-a.z)*t; + w = a.w+(b.w-a.w)*t; + return *this; + } + + vec4 &mul3(float f) { x *= f; y *= f; z *= f; return *this; } + vec4 &mul(float f) { mul3(f); w *= f; return *this; } + vec4 &mul(const vec4 &o) { x *= o.x; y *= o.y; z *= o.z; w *= o.w; return *this; } + vec4 &square() { mul(*this); return *this; } + vec4 &div3(float f) { x /= f; y /= f; z /= f; return *this; } + vec4 &div(float f) { div3(f); w /= f; return *this; } + vec4 &div(const vec4 &o) { x /= o.x; y /= o.y; z /= o.z; w /= o.w; return *this; } + vec4 &recip() { x = 1/x; y = 1/y; z = 1/z; w = 1/w; return *this; } + vec4 &add(const vec4 &o) { x += o.x; y += o.y; z += o.z; w += o.w; return *this; } + vec4 &addw(float f) { w += f; return *this; } + vec4 &sub(const vec4 &o) { x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this; } + vec4 &subw(float f) { w -= f; return *this; } + vec4 &neg3() { x = -x; y = -y; z = -z; return *this; } + vec4 &neg() { neg3(); w = -w; return *this; } + template vec4 &madd(const vec4 &a, const B &b) { return add(vec4(a).mul(b)); } + template vec4 &msub(const vec4 &a, const B &b) { return sub(vec4(a).mul(b)); } + + void setxyz(const vec &v) { x = v.x; y = v.y; z = v.z; } + + vec4 &rotate_around_z(float c, float s) { float rx = x, ry = y; x = c*rx-s*ry; y = c*ry+s*rx; return *this; } + vec4 &rotate_around_x(float c, float s) { float ry = y, rz = z; y = c*ry-s*rz; z = c*rz+s*ry; return *this; } + vec4 &rotate_around_y(float c, float s) { float rx = x, rz = z; x = c*rx+s*rz; z = c*rz-s*rx; return *this; } + + vec4 &rotate_around_z(float angle) { return rotate_around_z(cosf(angle), sinf(angle)); } + vec4 &rotate_around_x(float angle) { return rotate_around_x(cosf(angle), sinf(angle)); } + vec4 &rotate_around_y(float angle) { return rotate_around_y(cosf(angle), sinf(angle)); } }; inline vec::vec(const vec4 &v) : x(v.x), y(v.y), z(v.z) {} @@ -334,774 +334,774 @@ struct matrix4; struct quat : vec4 { - quat() {} - quat(float x, float y, float z, float w) : vec4(x, y, z, w) {} - quat(const vec &axis, float angle) - { - w = cosf(angle/2); - float s = sinf(angle/2); - x = s*axis.x; - y = s*axis.y; - z = s*axis.z; - } - explicit quat(const vec &v) - { - x = v.x; - y = v.y; - z = v.z; - restorew(); - } - explicit quat(const matrix3 &m) { convertmatrix(m); } - explicit quat(const matrix4x3 &m) { convertmatrix(m); } - explicit quat(const matrix4 &m) { convertmatrix(m); } - - void restorew() { w = 1.0f-x*x-y*y-z*z; w = w<0 ? 0 : -sqrtf(w); } - - quat &add(const vec4 &o) { vec4::add(o); return *this; } - quat &sub(const vec4 &o) { vec4::sub(o); return *this; } - quat &mul(float k) { vec4::mul(k); return *this; } - - quat &mul(const quat &p, const quat &o) - { - x = p.w*o.x + p.x*o.w + p.y*o.z - p.z*o.y; - y = p.w*o.y - p.x*o.z + p.y*o.w + p.z*o.x; - z = p.w*o.z + p.x*o.y - p.y*o.x + p.z*o.w; - w = p.w*o.w - p.x*o.x - p.y*o.y - p.z*o.z; - return *this; - } - quat &mul(const quat &o) { return mul(quat(*this), o); } - - quat &invert() { neg3(); return *this; } - - void calcangleaxis(float &angle, vec &axis) - { - float rr = dot3(*this); - if(rr>0) - { - angle = 2*acosf(w); - axis = vec(x, y, z).mul(1/rr); - } - else { angle = 0; axis = vec(0, 0, 1); } - } - - vec rotate(const vec &v) const - { - return vec().cross(*this, vec().cross(*this, v).add(vec(v).mul(w))).mul(2).add(v); - } - - vec invertedrotate(const vec &v) const - { - return vec().cross(*this, vec().cross(*this, v).sub(vec(v).mul(w))).mul(2).add(v); - } - - template - void convertmatrix(const M &m) - { - float trace = m.a.x + m.b.y + m.c.z; - if(trace>0) - { - float r = sqrtf(1 + trace), inv = 0.5f/r; - w = 0.5f*r; - x = (m.b.z - m.c.y)*inv; - y = (m.c.x - m.a.z)*inv; - z = (m.a.y - m.b.x)*inv; - } - else if(m.a.x > m.b.y && m.a.x > m.c.z) - { - float r = sqrtf(1 + m.a.x - m.b.y - m.c.z), inv = 0.5f/r; - x = 0.5f*r; - y = (m.a.y + m.b.x)*inv; - z = (m.c.x + m.a.z)*inv; - w = (m.b.z - m.c.y)*inv; - } - else if(m.b.y > m.c.z) - { - float r = sqrtf(1 + m.b.y - m.a.x - m.c.z), inv = 0.5f/r; - x = (m.a.y + m.b.x)*inv; - y = 0.5f*r; - z = (m.b.z + m.c.y)*inv; - w = (m.c.x - m.a.z)*inv; - } - else - { - float r = sqrtf(1 + m.c.z - m.a.x - m.b.y), inv = 0.5f/r; - x = (m.c.x + m.a.z)*inv; - y = (m.b.z + m.c.y)*inv; - z = 0.5f*r; - w = (m.a.y - m.b.x)*inv; - } - } + quat() {} + quat(float x, float y, float z, float w) : vec4(x, y, z, w) {} + quat(const vec &axis, float angle) + { + w = cosf(angle/2); + float s = sinf(angle/2); + x = s*axis.x; + y = s*axis.y; + z = s*axis.z; + } + explicit quat(const vec &v) + { + x = v.x; + y = v.y; + z = v.z; + restorew(); + } + explicit quat(const matrix3 &m) { convertmatrix(m); } + explicit quat(const matrix4x3 &m) { convertmatrix(m); } + explicit quat(const matrix4 &m) { convertmatrix(m); } + + void restorew() { w = 1.0f-x*x-y*y-z*z; w = w<0 ? 0 : -sqrtf(w); } + + quat &add(const vec4 &o) { vec4::add(o); return *this; } + quat &sub(const vec4 &o) { vec4::sub(o); return *this; } + quat &mul(float k) { vec4::mul(k); return *this; } + + quat &mul(const quat &p, const quat &o) + { + x = p.w*o.x + p.x*o.w + p.y*o.z - p.z*o.y; + y = p.w*o.y - p.x*o.z + p.y*o.w + p.z*o.x; + z = p.w*o.z + p.x*o.y - p.y*o.x + p.z*o.w; + w = p.w*o.w - p.x*o.x - p.y*o.y - p.z*o.z; + return *this; + } + quat &mul(const quat &o) { return mul(quat(*this), o); } + + quat &invert() { neg3(); return *this; } + + void calcangleaxis(float &angle, vec &axis) + { + float rr = dot3(*this); + if(rr>0) + { + angle = 2*acosf(w); + axis = vec(x, y, z).mul(1/rr); + } + else { angle = 0; axis = vec(0, 0, 1); } + } + + vec rotate(const vec &v) const + { + return vec().cross(*this, vec().cross(*this, v).add(vec(v).mul(w))).mul(2).add(v); + } + + vec invertedrotate(const vec &v) const + { + return vec().cross(*this, vec().cross(*this, v).sub(vec(v).mul(w))).mul(2).add(v); + } + + template + void convertmatrix(const M &m) + { + float trace = m.a.x + m.b.y + m.c.z; + if(trace>0) + { + float r = sqrtf(1 + trace), inv = 0.5f/r; + w = 0.5f*r; + x = (m.b.z - m.c.y)*inv; + y = (m.c.x - m.a.z)*inv; + z = (m.a.y - m.b.x)*inv; + } + else if(m.a.x > m.b.y && m.a.x > m.c.z) + { + float r = sqrtf(1 + m.a.x - m.b.y - m.c.z), inv = 0.5f/r; + x = 0.5f*r; + y = (m.a.y + m.b.x)*inv; + z = (m.c.x + m.a.z)*inv; + w = (m.b.z - m.c.y)*inv; + } + else if(m.b.y > m.c.z) + { + float r = sqrtf(1 + m.b.y - m.a.x - m.c.z), inv = 0.5f/r; + x = (m.a.y + m.b.x)*inv; + y = 0.5f*r; + z = (m.b.z + m.c.y)*inv; + w = (m.c.x - m.a.z)*inv; + } + else + { + float r = sqrtf(1 + m.c.z - m.a.x - m.b.y), inv = 0.5f/r; + x = (m.c.x + m.a.z)*inv; + y = (m.b.z + m.c.y)*inv; + z = 0.5f*r; + w = (m.a.y - m.b.x)*inv; + } + } }; struct dualquat { - quat real, dual; - - dualquat() {} - dualquat(const quat &q, const vec &p) - : real(q), - dual(0.5f*( p.x*q.w + p.y*q.z - p.z*q.y), - 0.5f*(-p.x*q.z + p.y*q.w + p.z*q.x), - 0.5f*( p.x*q.y - p.y*q.x + p.z*q.w), - -0.5f*( p.x*q.x + p.y*q.y + p.z*q.z)) - { - } - explicit dualquat(const quat &q) : real(q), dual(0, 0, 0, 0) {} - explicit dualquat(const matrix4x3 &m); - - dualquat &mul(float k) { real.mul(k); dual.mul(k); return *this; } - dualquat &add(const dualquat &d) { real.add(d.real); dual.add(d.dual); return *this; } - - dualquat &lerp(const dualquat &to, float t) - { - float k = real.dot(to.real) < 0 ? -t : t; - real.mul(1-t).add(vec4(to.real).mul(k)); - dual.mul(1-t).add(vec4(to.dual).mul(k)); - return *this; - } - dualquat &lerp(const dualquat &from, const dualquat &to, float t) - { - float k = from.real.dot(to.real) < 0 ? -t : t; - (real = from.real).mul(1-t).add(vec4(to.real).mul(k)); - (dual = from.dual).mul(1-t).add(vec4(to.dual).mul(k)); - return *this; - } - - dualquat &invert() - { - real.invert(); - dual.invert(); - dual.sub(quat(real).mul(2*real.dot(dual))); - return *this; - } - - void mul(const dualquat &p, const dualquat &o) - { - real.mul(p.real, o.real); - dual.mul(p.real, o.dual).add(quat().mul(p.dual, o.real)); - } - void mul(const dualquat &o) { mul(dualquat(*this), o); } - - void mulorient(const quat &q) - { - real.mul(q, quat(real)); - dual.mul(quat(q).invert(), quat(dual)); - } - - void mulorient(const quat &q, const dualquat &base) - { - quat trans; - trans.mul(base.dual, quat(base.real).invert()); - dual.mul(quat(q).invert(), quat(real).mul(trans).add(dual)); - - real.mul(q, quat(real)); - dual.add(quat().mul(real, trans.invert())).sub(quat(real).mul(2*base.real.dot(base.dual))); - } - - void normalize() - { - float invlen = 1/real.magnitude(); - real.mul(invlen); - dual.mul(invlen); - } - - void translate(const vec &p) - { - dual.x += 0.5f*( p.x*real.w + p.y*real.z - p.z*real.y); - dual.y += 0.5f*(-p.x*real.z + p.y*real.w + p.z*real.x); - dual.z += 0.5f*( p.x*real.y - p.y*real.x + p.z*real.w); - dual.w += -0.5f*( p.x*real.x + p.y*real.y + p.z*real.z); - } - - void scale(float k) - { - dual.mul(k); - } - - void fixantipodal(const dualquat &d) - { - if(real.dot(d.real) < 0) - { - real.neg(); - dual.neg(); - } - } - - void accumulate(const dualquat &d, float k) - { - if(real.dot(d.real) < 0) k = -k; - real.add(vec4(d.real).mul(k)); - dual.add(vec4(d.dual).mul(k)); - } - - vec transform(const vec &v) const - { - return vec().cross(real, vec().cross(real, v).add(vec(v).mul(real.w)).add(vec(dual))).add(vec(dual).mul(real.w)).sub(vec(real).mul(dual.w)).mul(2).add(v); - } - - quat transform(const quat &q) const - { - return quat().mul(real, q); - } - - vec transposedtransform(const vec &v) const - { - return dualquat(*this).invert().transform(v); - } - - vec transformnormal(const vec &v) const - { - return real.rotate(v); - } - - vec transposedtransformnormal(const vec &v) const - { - return real.invertedrotate(v); - } - - vec gettranslation() const - { - return vec().cross(real, dual).add(vec(dual).mul(real.w)).sub(vec(real).mul(dual.w)).mul(2); - } + quat real, dual; + + dualquat() {} + dualquat(const quat &q, const vec &p) + : real(q), + dual(0.5f*( p.x*q.w + p.y*q.z - p.z*q.y), + 0.5f*(-p.x*q.z + p.y*q.w + p.z*q.x), + 0.5f*( p.x*q.y - p.y*q.x + p.z*q.w), + -0.5f*( p.x*q.x + p.y*q.y + p.z*q.z)) + { + } + explicit dualquat(const quat &q) : real(q), dual(0, 0, 0, 0) {} + explicit dualquat(const matrix4x3 &m); + + dualquat &mul(float k) { real.mul(k); dual.mul(k); return *this; } + dualquat &add(const dualquat &d) { real.add(d.real); dual.add(d.dual); return *this; } + + dualquat &lerp(const dualquat &to, float t) + { + float k = real.dot(to.real) < 0 ? -t : t; + real.mul(1-t).add(vec4(to.real).mul(k)); + dual.mul(1-t).add(vec4(to.dual).mul(k)); + return *this; + } + dualquat &lerp(const dualquat &from, const dualquat &to, float t) + { + float k = from.real.dot(to.real) < 0 ? -t : t; + (real = from.real).mul(1-t).add(vec4(to.real).mul(k)); + (dual = from.dual).mul(1-t).add(vec4(to.dual).mul(k)); + return *this; + } + + dualquat &invert() + { + real.invert(); + dual.invert(); + dual.sub(quat(real).mul(2*real.dot(dual))); + return *this; + } + + void mul(const dualquat &p, const dualquat &o) + { + real.mul(p.real, o.real); + dual.mul(p.real, o.dual).add(quat().mul(p.dual, o.real)); + } + void mul(const dualquat &o) { mul(dualquat(*this), o); } + + void mulorient(const quat &q) + { + real.mul(q, quat(real)); + dual.mul(quat(q).invert(), quat(dual)); + } + + void mulorient(const quat &q, const dualquat &base) + { + quat trans; + trans.mul(base.dual, quat(base.real).invert()); + dual.mul(quat(q).invert(), quat(real).mul(trans).add(dual)); + + real.mul(q, quat(real)); + dual.add(quat().mul(real, trans.invert())).sub(quat(real).mul(2*base.real.dot(base.dual))); + } + + void normalize() + { + float invlen = 1/real.magnitude(); + real.mul(invlen); + dual.mul(invlen); + } + + void translate(const vec &p) + { + dual.x += 0.5f*( p.x*real.w + p.y*real.z - p.z*real.y); + dual.y += 0.5f*(-p.x*real.z + p.y*real.w + p.z*real.x); + dual.z += 0.5f*( p.x*real.y - p.y*real.x + p.z*real.w); + dual.w += -0.5f*( p.x*real.x + p.y*real.y + p.z*real.z); + } + + void scale(float k) + { + dual.mul(k); + } + + void fixantipodal(const dualquat &d) + { + if(real.dot(d.real) < 0) + { + real.neg(); + dual.neg(); + } + } + + void accumulate(const dualquat &d, float k) + { + if(real.dot(d.real) < 0) k = -k; + real.add(vec4(d.real).mul(k)); + dual.add(vec4(d.dual).mul(k)); + } + + vec transform(const vec &v) const + { + return vec().cross(real, vec().cross(real, v).add(vec(v).mul(real.w)).add(vec(dual))).add(vec(dual).mul(real.w)).sub(vec(real).mul(dual.w)).mul(2).add(v); + } + + quat transform(const quat &q) const + { + return quat().mul(real, q); + } + + vec transposedtransform(const vec &v) const + { + return dualquat(*this).invert().transform(v); + } + + vec transformnormal(const vec &v) const + { + return real.rotate(v); + } + + vec transposedtransformnormal(const vec &v) const + { + return real.invertedrotate(v); + } + + vec gettranslation() const + { + return vec().cross(real, dual).add(vec(dual).mul(real.w)).sub(vec(real).mul(dual.w)).mul(2); + } }; struct matrix3 { - vec a, b, c; - - matrix3() {} - matrix3(const vec &a, const vec &b, const vec &c) : a(a), b(b), c(c) {} - explicit matrix3(float angle, const vec &axis) { rotate(angle, axis); } - explicit matrix3(const quat &q) - { - float x = q.x, y = q.y, z = q.z, w = q.w, - tx = 2*x, ty = 2*y, tz = 2*z, - txx = tx*x, tyy = ty*y, tzz = tz*z, - txy = tx*y, txz = tx*z, tyz = ty*z, - twx = w*tx, twy = w*ty, twz = w*tz; - a = vec(1 - (tyy + tzz), txy + twz, txz - twy); - b = vec(txy - twz, 1 - (txx + tzz), tyz + twx); - c = vec(txz + twy, tyz - twx, 1 - (txx + tyy)); - } - explicit matrix3(const matrix4x3 &m); - explicit matrix3(const matrix4 &m); - - void mul(const matrix3 &m, const matrix3 &n) - { - a = vec(m.a).mul(n.a.x).madd(m.b, n.a.y).madd(m.c, n.a.z); - b = vec(m.a).mul(n.b.x).madd(m.b, n.b.y).madd(m.c, n.b.z); - c = vec(m.a).mul(n.c.x).madd(m.b, n.c.y).madd(m.c, n.c.z); - } - void mul(const matrix3 &n) { mul(matrix3(*this), n); } - - void multranspose(const matrix3 &m, const matrix3 &n) - { - a = vec(m.a).mul(n.a.x).madd(m.b, n.b.x).madd(m.c, n.c.x); - b = vec(m.a).mul(n.a.y).madd(m.b, n.b.y).madd(m.c, n.c.y); - c = vec(m.a).mul(n.a.z).madd(m.b, n.b.z).madd(m.c, n.c.z); - } - void multranspose(const matrix3 &n) { multranspose(matrix3(*this), n); } - - void transposemul(const matrix3 &m, const matrix3 &n) - { - a = vec(m.a.dot(n.a), m.b.dot(n.a), m.c.dot(n.a)); - b = vec(m.a.dot(n.b), m.b.dot(n.b), m.c.dot(n.b)); - c = vec(m.a.dot(n.c), m.b.dot(n.c), m.c.dot(n.c)); - } - void transposemul(const matrix3 &n) { transposemul(matrix3(*this), n); } - - void transpose() - { - swap(a.y, b.x); swap(a.z, c.x); - swap(b.z, c.y); - } - - template - void transpose(const M &m) - { - a = vec(m.a.x, m.b.x, m.c.x); - b = vec(m.a.y, m.b.y, m.c.y); - c = vec(m.a.z, m.b.z, m.c.z); - } - - void invert(const matrix3 &o) - { - vec unscale(1/o.a.squaredlen(), 1/o.b.squaredlen(), 1/o.c.squaredlen()); - transpose(o); - a.mul(unscale); - b.mul(unscale); - c.mul(unscale); - } - void invert() { invert(matrix3(*this)); } - - void normalize() - { - a.normalize(); - b.normalize(); - c.normalize(); - } - - void scale(float k) - { - a.mul(k); - b.mul(k); - c.mul(k); - } - - void rotate(float angle, const vec &axis) - { - rotate(cosf(angle), sinf(angle), axis); - } - - void rotate(float ck, float sk, const vec &axis) - { - a = vec(axis.x*axis.x*(1-ck)+ck, axis.x*axis.y*(1-ck)+axis.z*sk, axis.x*axis.z*(1-ck)-axis.y*sk); - b = vec(axis.x*axis.y*(1-ck)-axis.z*sk, axis.y*axis.y*(1-ck)+ck, axis.y*axis.z*(1-ck)+axis.x*sk); - c = vec(axis.x*axis.z*(1-ck)+axis.y*sk, axis.y*axis.z*(1-ck)-axis.x*sk, axis.z*axis.z*(1-ck)+ck); - } - - void setyaw(float ck, float sk) - { - a = vec(ck, sk, 0); - b = vec(-sk, ck, 0); - c = vec(0, 0, 1); - } - - void setyaw(float angle) - { - setyaw(cosf(angle), sinf(angle)); - } - - float trace() const { return a.x + b.y + c.z; } - - bool calcangleaxis(float tr, float &angle, vec &axis, float threshold = 1e-16f) const - { - if(tr <= -1) - { - if(a.x >= b.y && a.x >= c.z) - { - float r = 1 + a.x - b.y - c.z; - if(r <= threshold) return false; - r = sqrtf(r); - axis.x = 0.5f*r; - axis.y = b.x/r; - axis.z = c.x/r; - } - else if(b.y >= c.z) - { - float r = 1 + b.y - a.x - c.z; - if(r <= threshold) return false; - r = sqrtf(r); - axis.y = 0.5f*r; - axis.x = b.x/r; - axis.z = c.y/r; - } - else - { - float r = 1 + b.y - a.x - c.z; - if(r <= threshold) return false; - r = sqrtf(r); - axis.z = 0.5f*r; - axis.x = c.x/r; - axis.y = c.y/r; - } - angle = M_PI; - } - else if(tr >= 3) - { - axis = vec(0, 0, 1); - angle = 0; - } - else - { - axis = vec(b.z - c.y, c.x - a.z, a.y - b.x); - float r = axis.squaredlen(); - if(r <= threshold) return false; - axis.mul(1/sqrtf(r)); - angle = acosf(0.5f*(tr - 1)); - } - return true; - } - - bool calcangleaxis(float &angle, vec &axis, float threshold = 1e-16f) const { return calcangleaxis(trace(), angle, axis, threshold); } - - vec transform(const vec &o) const - { - return vec(a).mul(o.x).madd(b, o.y).madd(c, o.z); - } - vec transposedtransform(const vec &o) const { return vec(a.dot(o), b.dot(o), c.dot(o)); } - vec abstransform(const vec &o) const - { - return vec(a).mul(o.x).abs().add(vec(b).mul(o.y).abs()).add(vec(c).mul(o.z).abs()); - } - vec abstransposedtransform(const vec &o) const - { - return vec(a.absdot(o), b.absdot(o), c.absdot(o)); - } - - void identity() - { - a = vec(1, 0, 0); - b = vec(0, 1, 0); - c = vec(0, 0, 1); - } - - void rotate_around_x(float ck, float sk) - { - vec rb = vec(b).mul(ck).madd(c, sk), - rc = vec(c).mul(ck).msub(b, sk); - b = rb; - c = rc; - } - void rotate_around_x(float angle) { rotate_around_x(cosf(angle), sinf(angle)); } - void rotate_around_x(const vec2 &sc) { rotate_around_x(sc.x, sc.y); } - - void rotate_around_y(float ck, float sk) - { - vec rc = vec(c).mul(ck).madd(a, sk), - ra = vec(a).mul(ck).msub(c, sk); - c = rc; - a = ra; - } - void rotate_around_y(float angle) { rotate_around_y(cosf(angle), sinf(angle)); } - void rotate_around_y(const vec2 &sc) { rotate_around_y(sc.x, sc.y); } - - void rotate_around_z(float ck, float sk) - { - vec ra = vec(a).mul(ck).madd(b, sk), - rb = vec(b).mul(ck).msub(a, sk); - a = ra; - b = rb; - } - void rotate_around_z(float angle) { rotate_around_z(cosf(angle), sinf(angle)); } - void rotate_around_z(const vec2 &sc) { rotate_around_z(sc.x, sc.y); } - - vec transform(const vec2 &o) { return vec(a).mul(o.x).madd(b, o.y); } - vec transposedtransform(const vec2 &o) const { return vec(a.dot2(o), b.dot2(o), c.dot2(o)); } - - vec rowx() const { return vec(a.x, b.x, c.x); } - vec rowy() const { return vec(a.y, b.y, c.y); } - vec rowz() const { return vec(a.z, b.z, c.z); } + vec a, b, c; + + matrix3() {} + matrix3(const vec &a, const vec &b, const vec &c) : a(a), b(b), c(c) {} + explicit matrix3(float angle, const vec &axis) { rotate(angle, axis); } + explicit matrix3(const quat &q) + { + float x = q.x, y = q.y, z = q.z, w = q.w, + tx = 2*x, ty = 2*y, tz = 2*z, + txx = tx*x, tyy = ty*y, tzz = tz*z, + txy = tx*y, txz = tx*z, tyz = ty*z, + twx = w*tx, twy = w*ty, twz = w*tz; + a = vec(1 - (tyy + tzz), txy + twz, txz - twy); + b = vec(txy - twz, 1 - (txx + tzz), tyz + twx); + c = vec(txz + twy, tyz - twx, 1 - (txx + tyy)); + } + explicit matrix3(const matrix4x3 &m); + explicit matrix3(const matrix4 &m); + + void mul(const matrix3 &m, const matrix3 &n) + { + a = vec(m.a).mul(n.a.x).madd(m.b, n.a.y).madd(m.c, n.a.z); + b = vec(m.a).mul(n.b.x).madd(m.b, n.b.y).madd(m.c, n.b.z); + c = vec(m.a).mul(n.c.x).madd(m.b, n.c.y).madd(m.c, n.c.z); + } + void mul(const matrix3 &n) { mul(matrix3(*this), n); } + + void multranspose(const matrix3 &m, const matrix3 &n) + { + a = vec(m.a).mul(n.a.x).madd(m.b, n.b.x).madd(m.c, n.c.x); + b = vec(m.a).mul(n.a.y).madd(m.b, n.b.y).madd(m.c, n.c.y); + c = vec(m.a).mul(n.a.z).madd(m.b, n.b.z).madd(m.c, n.c.z); + } + void multranspose(const matrix3 &n) { multranspose(matrix3(*this), n); } + + void transposemul(const matrix3 &m, const matrix3 &n) + { + a = vec(m.a.dot(n.a), m.b.dot(n.a), m.c.dot(n.a)); + b = vec(m.a.dot(n.b), m.b.dot(n.b), m.c.dot(n.b)); + c = vec(m.a.dot(n.c), m.b.dot(n.c), m.c.dot(n.c)); + } + void transposemul(const matrix3 &n) { transposemul(matrix3(*this), n); } + + void transpose() + { + swap(a.y, b.x); swap(a.z, c.x); + swap(b.z, c.y); + } + + template + void transpose(const M &m) + { + a = vec(m.a.x, m.b.x, m.c.x); + b = vec(m.a.y, m.b.y, m.c.y); + c = vec(m.a.z, m.b.z, m.c.z); + } + + void invert(const matrix3 &o) + { + vec unscale(1/o.a.squaredlen(), 1/o.b.squaredlen(), 1/o.c.squaredlen()); + transpose(o); + a.mul(unscale); + b.mul(unscale); + c.mul(unscale); + } + void invert() { invert(matrix3(*this)); } + + void normalize() + { + a.normalize(); + b.normalize(); + c.normalize(); + } + + void scale(float k) + { + a.mul(k); + b.mul(k); + c.mul(k); + } + + void rotate(float angle, const vec &axis) + { + rotate(cosf(angle), sinf(angle), axis); + } + + void rotate(float ck, float sk, const vec &axis) + { + a = vec(axis.x*axis.x*(1-ck)+ck, axis.x*axis.y*(1-ck)+axis.z*sk, axis.x*axis.z*(1-ck)-axis.y*sk); + b = vec(axis.x*axis.y*(1-ck)-axis.z*sk, axis.y*axis.y*(1-ck)+ck, axis.y*axis.z*(1-ck)+axis.x*sk); + c = vec(axis.x*axis.z*(1-ck)+axis.y*sk, axis.y*axis.z*(1-ck)-axis.x*sk, axis.z*axis.z*(1-ck)+ck); + } + + void setyaw(float ck, float sk) + { + a = vec(ck, sk, 0); + b = vec(-sk, ck, 0); + c = vec(0, 0, 1); + } + + void setyaw(float angle) + { + setyaw(cosf(angle), sinf(angle)); + } + + float trace() const { return a.x + b.y + c.z; } + + bool calcangleaxis(float tr, float &angle, vec &axis, float threshold = 1e-16f) const + { + if(tr <= -1) + { + if(a.x >= b.y && a.x >= c.z) + { + float r = 1 + a.x - b.y - c.z; + if(r <= threshold) return false; + r = sqrtf(r); + axis.x = 0.5f*r; + axis.y = b.x/r; + axis.z = c.x/r; + } + else if(b.y >= c.z) + { + float r = 1 + b.y - a.x - c.z; + if(r <= threshold) return false; + r = sqrtf(r); + axis.y = 0.5f*r; + axis.x = b.x/r; + axis.z = c.y/r; + } + else + { + float r = 1 + b.y - a.x - c.z; + if(r <= threshold) return false; + r = sqrtf(r); + axis.z = 0.5f*r; + axis.x = c.x/r; + axis.y = c.y/r; + } + angle = M_PI; + } + else if(tr >= 3) + { + axis = vec(0, 0, 1); + angle = 0; + } + else + { + axis = vec(b.z - c.y, c.x - a.z, a.y - b.x); + float r = axis.squaredlen(); + if(r <= threshold) return false; + axis.mul(1/sqrtf(r)); + angle = acosf(0.5f*(tr - 1)); + } + return true; + } + + bool calcangleaxis(float &angle, vec &axis, float threshold = 1e-16f) const { return calcangleaxis(trace(), angle, axis, threshold); } + + vec transform(const vec &o) const + { + return vec(a).mul(o.x).madd(b, o.y).madd(c, o.z); + } + vec transposedtransform(const vec &o) const { return vec(a.dot(o), b.dot(o), c.dot(o)); } + vec abstransform(const vec &o) const + { + return vec(a).mul(o.x).abs().add(vec(b).mul(o.y).abs()).add(vec(c).mul(o.z).abs()); + } + vec abstransposedtransform(const vec &o) const + { + return vec(a.absdot(o), b.absdot(o), c.absdot(o)); + } + + void identity() + { + a = vec(1, 0, 0); + b = vec(0, 1, 0); + c = vec(0, 0, 1); + } + + void rotate_around_x(float ck, float sk) + { + vec rb = vec(b).mul(ck).madd(c, sk), + rc = vec(c).mul(ck).msub(b, sk); + b = rb; + c = rc; + } + void rotate_around_x(float angle) { rotate_around_x(cosf(angle), sinf(angle)); } + void rotate_around_x(const vec2 &sc) { rotate_around_x(sc.x, sc.y); } + + void rotate_around_y(float ck, float sk) + { + vec rc = vec(c).mul(ck).madd(a, sk), + ra = vec(a).mul(ck).msub(c, sk); + c = rc; + a = ra; + } + void rotate_around_y(float angle) { rotate_around_y(cosf(angle), sinf(angle)); } + void rotate_around_y(const vec2 &sc) { rotate_around_y(sc.x, sc.y); } + + void rotate_around_z(float ck, float sk) + { + vec ra = vec(a).mul(ck).madd(b, sk), + rb = vec(b).mul(ck).msub(a, sk); + a = ra; + b = rb; + } + void rotate_around_z(float angle) { rotate_around_z(cosf(angle), sinf(angle)); } + void rotate_around_z(const vec2 &sc) { rotate_around_z(sc.x, sc.y); } + + vec transform(const vec2 &o) { return vec(a).mul(o.x).madd(b, o.y); } + vec transposedtransform(const vec2 &o) const { return vec(a.dot2(o), b.dot2(o), c.dot2(o)); } + + vec rowx() const { return vec(a.x, b.x, c.x); } + vec rowy() const { return vec(a.y, b.y, c.y); } + vec rowz() const { return vec(a.z, b.z, c.z); } }; struct matrix4x3 { - vec a, b, c, d; - - matrix4x3() {} - matrix4x3(const vec &a, const vec &b, const vec &c, const vec &d) : a(a), b(b), c(c), d(d) {} - matrix4x3(const matrix3 &rot, const vec &trans) : a(rot.a), b(rot.b), c(rot.c), d(trans) {} - matrix4x3(const dualquat &dq) - { - vec4 r = vec4(dq.real).mul(1/dq.real.squaredlen()), rr = vec4(r).mul(dq.real); - r.mul(2); - float xy = r.x*dq.real.y, xz = r.x*dq.real.z, yz = r.y*dq.real.z, - wx = r.w*dq.real.x, wy = r.w*dq.real.y, wz = r.w*dq.real.z; - a = vec(rr.w + rr.x - rr.y - rr.z, xy + wz, xz - wy); - b = vec(xy - wz, rr.w + rr.y - rr.x - rr.z, yz + wx); - c = vec(xz + wy, yz - wx, rr.w + rr.z - rr.x - rr.y); - d = vec(-(dq.dual.w*r.x - dq.dual.x*r.w + dq.dual.y*r.z - dq.dual.z*r.y), - -(dq.dual.w*r.y - dq.dual.x*r.z - dq.dual.y*r.w + dq.dual.z*r.x), - -(dq.dual.w*r.z + dq.dual.x*r.y - dq.dual.y*r.x - dq.dual.z*r.w)); - - } - explicit matrix4x3(const matrix4 &m); - - void mul(float k) - { - a.mul(k); - b.mul(k); - c.mul(k); - d.mul(k); - } - - void setscale(float x, float y, float z) { a.x = x; b.y = y; c.z = z; } - void setscale(const vec &v) { setscale(v.x, v.y, v.z); } - void setscale(float n) { setscale(n, n, n); } - - void scale(float x, float y, float z) - { - a.mul(x); - b.mul(y); - c.mul(z); - } - void scale(const vec &v) { scale(v.x, v.y, v.z); } - void scale(float n) { scale(n, n, n); } - - void settranslation(const vec &p) { d = p; } - void settranslation(float x, float y, float z) { d = vec(x, y, z); } - - void translate(const vec &p) { d.madd(a, p.x).madd(b, p.y).madd(c, p.z); } - void translate(float x, float y, float z) { translate(vec(x, y, z)); } - void translate(const vec &p, float scale) { translate(vec(p).mul(scale)); } - - void posttranslate(const vec &p) { d.add(p); } - void posttranslate(float x, float y, float z) { posttranslate(vec(x, y, z)); } - void posttranslate(const vec &p, float scale) { d.madd(p, scale); } - - void accumulate(const matrix4x3 &m, float k) - { - a.madd(m.a, k); - b.madd(m.b, k); - c.madd(m.c, k); - d.madd(m.d, k); - } - - void normalize() - { - a.normalize(); - b.normalize(); - c.normalize(); - } - - void lerp(const matrix4x3 &to, float t) - { - a.lerp(to.a, t); - b.lerp(to.b, t); - c.lerp(to.c, t); - d.lerp(to.d, t); - } - void lerp(const matrix4x3 &from, const matrix4x3 &to, float t) - { - a.lerp(from.a, to.a, t); - b.lerp(from.b, to.b, t); - c.lerp(from.c, to.c, t); - d.lerp(from.d, to.d, t); - } - - void identity() - { - a = vec(1, 0, 0); - b = vec(0, 1, 0); - c = vec(0, 0, 1); - d = vec(0, 0, 0); - } - - void mul(const matrix4x3 &m, const matrix4x3 &n) - { - a = vec(m.a).mul(n.a.x).madd(m.b, n.a.y).madd(m.c, n.a.z); - b = vec(m.a).mul(n.b.x).madd(m.b, n.b.y).madd(m.c, n.b.z); - c = vec(m.a).mul(n.c.x).madd(m.b, n.c.y).madd(m.c, n.c.z); - d = vec(m.d).madd(m.a, n.d.x).madd(m.b, n.d.y).madd(m.c, n.d.z); - } - void mul(const matrix4x3 &n) { mul(matrix4x3(*this), n); } - - void mul(const matrix3 &m, const matrix4x3 &n) - { - a = vec(m.a).mul(n.a.x).madd(m.b, n.a.y).madd(m.c, n.a.z); - b = vec(m.a).mul(n.b.x).madd(m.b, n.b.y).madd(m.c, n.b.z); - c = vec(m.a).mul(n.c.x).madd(m.b, n.c.y).madd(m.c, n.c.z); - d = vec(m.a).mul(n.d.x).madd(m.b, n.d.y).madd(m.c, n.d.z); - } - - void mul(const matrix3 &rot, const vec &trans, const matrix4x3 &n) - { - mul(rot, n); - d.add(trans); - } - - void transpose() - { - d = vec(a.dot(d), b.dot(d), c.dot(d)).neg(); - swap(a.y, b.x); swap(a.z, c.x); - swap(b.z, c.y); - } - - void transpose(const matrix4x3 &o) - { - a = vec(o.a.x, o.b.x, o.c.x); - b = vec(o.a.y, o.b.y, o.c.y); - c = vec(o.a.z, o.b.z, o.c.z); - d = vec(o.a.dot(o.d), o.b.dot(o.d), o.c.dot(o.d)).neg(); - } - - void transposemul(const matrix4x3 &m, const matrix4x3 &n) - { - vec t(m.a.dot(m.d), m.b.dot(m.d), m.c.dot(m.d)); - a = vec(m.a.dot(n.a), m.b.dot(n.a), m.c.dot(n.a)); - b = vec(m.a.dot(n.b), m.b.dot(n.b), m.c.dot(n.b)); - c = vec(m.a.dot(n.c), m.b.dot(n.c), m.c.dot(n.c)); - d = vec(m.a.dot(n.d), m.b.dot(n.d), m.c.dot(n.d)).sub(t); - } - - void multranspose(const matrix4x3 &m, const matrix4x3 &n) - { - vec t(n.a.dot(n.d), n.b.dot(n.d), n.c.dot(n.d)); - a = vec(m.a).mul(n.a.x).madd(m.b, n.b.x).madd(m.c, n.c.x); - b = vec(m.a).mul(n.a.y).madd(m.b, n.b.y).madd(m.c, n.c.y); - c = vec(m.a).mul(n.a.z).madd(m.b, n.b.z).madd(m.c, n.c.z); - d = vec(m.d).msub(m.a, t.x).msub(m.b, t.y).msub(m.c, t.z); - } - - void invert(const matrix4x3 &o) - { - vec unscale(1/o.a.squaredlen(), 1/o.b.squaredlen(), 1/o.c.squaredlen()); - transpose(o); - a.mul(unscale); - b.mul(unscale); - c.mul(unscale); - d.mul(unscale); - } - void invert() { invert(matrix4x3(*this)); } - - void rotate(float angle, const vec &d) - { - rotate(cosf(angle), sinf(angle), d); - } - - void rotate(float ck, float sk, const vec &axis) - { - matrix3 m; - m.rotate(ck, sk, axis); - *this = matrix4x3(m, vec(0, 0, 0)); - } - - void rotate_around_x(float ck, float sk) - { - vec rb = vec(b).mul(ck).madd(c, sk), - rc = vec(c).mul(ck).msub(b, sk); - b = rb; - c = rc; - } - void rotate_around_x(float angle) { rotate_around_x(cosf(angle), sinf(angle)); } - void rotate_around_x(const vec2 &sc) { rotate_around_x(sc.x, sc.y); } - - void rotate_around_y(float ck, float sk) - { - vec rc = vec(c).mul(ck).madd(a, sk), - ra = vec(a).mul(ck).msub(c, sk); - c = rc; - a = ra; - } - void rotate_around_y(float angle) { rotate_around_y(cosf(angle), sinf(angle)); } - void rotate_around_y(const vec2 &sc) { rotate_around_y(sc.x, sc.y); } - - void rotate_around_z(float ck, float sk) - { - vec ra = vec(a).mul(ck).madd(b, sk), - rb = vec(b).mul(ck).msub(a, sk); - a = ra; - b = rb; - } - void rotate_around_z(float angle) { rotate_around_z(cosf(angle), sinf(angle)); } - void rotate_around_z(const vec2 &sc) { rotate_around_z(sc.x, sc.y); } - - vec transform(const vec &o) const { return vec(d).madd(a, o.x).madd(b, o.y).madd(c, o.z); } - vec transposedtransform(const vec &o) const { vec p = vec(o).sub(d); return vec(a.dot(p), b.dot(p), c.dot(p)); } - vec transformnormal(const vec &o) const { return vec(a).mul(o.x).madd(b, o.y).madd(c, o.z); } - vec transposedtransformnormal(const vec &o) const { return vec(a.dot(o), b.dot(o), c.dot(o)); } - vec transform(const vec2 &o) const { return vec(d).madd(a, o.x).madd(b, o.y); } - - vec4 rowx() const { return vec4(a.x, b.x, c.x, d.x); } - vec4 rowy() const { return vec4(a.y, b.y, c.y, d.y); } - vec4 rowz() const { return vec4(a.z, b.z, c.z, d.z); } + vec a, b, c, d; + + matrix4x3() {} + matrix4x3(const vec &a, const vec &b, const vec &c, const vec &d) : a(a), b(b), c(c), d(d) {} + matrix4x3(const matrix3 &rot, const vec &trans) : a(rot.a), b(rot.b), c(rot.c), d(trans) {} + matrix4x3(const dualquat &dq) + { + vec4 r = vec4(dq.real).mul(1/dq.real.squaredlen()), rr = vec4(r).mul(dq.real); + r.mul(2); + float xy = r.x*dq.real.y, xz = r.x*dq.real.z, yz = r.y*dq.real.z, + wx = r.w*dq.real.x, wy = r.w*dq.real.y, wz = r.w*dq.real.z; + a = vec(rr.w + rr.x - rr.y - rr.z, xy + wz, xz - wy); + b = vec(xy - wz, rr.w + rr.y - rr.x - rr.z, yz + wx); + c = vec(xz + wy, yz - wx, rr.w + rr.z - rr.x - rr.y); + d = vec(-(dq.dual.w*r.x - dq.dual.x*r.w + dq.dual.y*r.z - dq.dual.z*r.y), + -(dq.dual.w*r.y - dq.dual.x*r.z - dq.dual.y*r.w + dq.dual.z*r.x), + -(dq.dual.w*r.z + dq.dual.x*r.y - dq.dual.y*r.x - dq.dual.z*r.w)); + + } + explicit matrix4x3(const matrix4 &m); + + void mul(float k) + { + a.mul(k); + b.mul(k); + c.mul(k); + d.mul(k); + } + + void setscale(float x, float y, float z) { a.x = x; b.y = y; c.z = z; } + void setscale(const vec &v) { setscale(v.x, v.y, v.z); } + void setscale(float n) { setscale(n, n, n); } + + void scale(float x, float y, float z) + { + a.mul(x); + b.mul(y); + c.mul(z); + } + void scale(const vec &v) { scale(v.x, v.y, v.z); } + void scale(float n) { scale(n, n, n); } + + void settranslation(const vec &p) { d = p; } + void settranslation(float x, float y, float z) { d = vec(x, y, z); } + + void translate(const vec &p) { d.madd(a, p.x).madd(b, p.y).madd(c, p.z); } + void translate(float x, float y, float z) { translate(vec(x, y, z)); } + void translate(const vec &p, float scale) { translate(vec(p).mul(scale)); } + + void posttranslate(const vec &p) { d.add(p); } + void posttranslate(float x, float y, float z) { posttranslate(vec(x, y, z)); } + void posttranslate(const vec &p, float scale) { d.madd(p, scale); } + + void accumulate(const matrix4x3 &m, float k) + { + a.madd(m.a, k); + b.madd(m.b, k); + c.madd(m.c, k); + d.madd(m.d, k); + } + + void normalize() + { + a.normalize(); + b.normalize(); + c.normalize(); + } + + void lerp(const matrix4x3 &to, float t) + { + a.lerp(to.a, t); + b.lerp(to.b, t); + c.lerp(to.c, t); + d.lerp(to.d, t); + } + void lerp(const matrix4x3 &from, const matrix4x3 &to, float t) + { + a.lerp(from.a, to.a, t); + b.lerp(from.b, to.b, t); + c.lerp(from.c, to.c, t); + d.lerp(from.d, to.d, t); + } + + void identity() + { + a = vec(1, 0, 0); + b = vec(0, 1, 0); + c = vec(0, 0, 1); + d = vec(0, 0, 0); + } + + void mul(const matrix4x3 &m, const matrix4x3 &n) + { + a = vec(m.a).mul(n.a.x).madd(m.b, n.a.y).madd(m.c, n.a.z); + b = vec(m.a).mul(n.b.x).madd(m.b, n.b.y).madd(m.c, n.b.z); + c = vec(m.a).mul(n.c.x).madd(m.b, n.c.y).madd(m.c, n.c.z); + d = vec(m.d).madd(m.a, n.d.x).madd(m.b, n.d.y).madd(m.c, n.d.z); + } + void mul(const matrix4x3 &n) { mul(matrix4x3(*this), n); } + + void mul(const matrix3 &m, const matrix4x3 &n) + { + a = vec(m.a).mul(n.a.x).madd(m.b, n.a.y).madd(m.c, n.a.z); + b = vec(m.a).mul(n.b.x).madd(m.b, n.b.y).madd(m.c, n.b.z); + c = vec(m.a).mul(n.c.x).madd(m.b, n.c.y).madd(m.c, n.c.z); + d = vec(m.a).mul(n.d.x).madd(m.b, n.d.y).madd(m.c, n.d.z); + } + + void mul(const matrix3 &rot, const vec &trans, const matrix4x3 &n) + { + mul(rot, n); + d.add(trans); + } + + void transpose() + { + d = vec(a.dot(d), b.dot(d), c.dot(d)).neg(); + swap(a.y, b.x); swap(a.z, c.x); + swap(b.z, c.y); + } + + void transpose(const matrix4x3 &o) + { + a = vec(o.a.x, o.b.x, o.c.x); + b = vec(o.a.y, o.b.y, o.c.y); + c = vec(o.a.z, o.b.z, o.c.z); + d = vec(o.a.dot(o.d), o.b.dot(o.d), o.c.dot(o.d)).neg(); + } + + void transposemul(const matrix4x3 &m, const matrix4x3 &n) + { + vec t(m.a.dot(m.d), m.b.dot(m.d), m.c.dot(m.d)); + a = vec(m.a.dot(n.a), m.b.dot(n.a), m.c.dot(n.a)); + b = vec(m.a.dot(n.b), m.b.dot(n.b), m.c.dot(n.b)); + c = vec(m.a.dot(n.c), m.b.dot(n.c), m.c.dot(n.c)); + d = vec(m.a.dot(n.d), m.b.dot(n.d), m.c.dot(n.d)).sub(t); + } + + void multranspose(const matrix4x3 &m, const matrix4x3 &n) + { + vec t(n.a.dot(n.d), n.b.dot(n.d), n.c.dot(n.d)); + a = vec(m.a).mul(n.a.x).madd(m.b, n.b.x).madd(m.c, n.c.x); + b = vec(m.a).mul(n.a.y).madd(m.b, n.b.y).madd(m.c, n.c.y); + c = vec(m.a).mul(n.a.z).madd(m.b, n.b.z).madd(m.c, n.c.z); + d = vec(m.d).msub(m.a, t.x).msub(m.b, t.y).msub(m.c, t.z); + } + + void invert(const matrix4x3 &o) + { + vec unscale(1/o.a.squaredlen(), 1/o.b.squaredlen(), 1/o.c.squaredlen()); + transpose(o); + a.mul(unscale); + b.mul(unscale); + c.mul(unscale); + d.mul(unscale); + } + void invert() { invert(matrix4x3(*this)); } + + void rotate(float angle, const vec &d) + { + rotate(cosf(angle), sinf(angle), d); + } + + void rotate(float ck, float sk, const vec &axis) + { + matrix3 m; + m.rotate(ck, sk, axis); + *this = matrix4x3(m, vec(0, 0, 0)); + } + + void rotate_around_x(float ck, float sk) + { + vec rb = vec(b).mul(ck).madd(c, sk), + rc = vec(c).mul(ck).msub(b, sk); + b = rb; + c = rc; + } + void rotate_around_x(float angle) { rotate_around_x(cosf(angle), sinf(angle)); } + void rotate_around_x(const vec2 &sc) { rotate_around_x(sc.x, sc.y); } + + void rotate_around_y(float ck, float sk) + { + vec rc = vec(c).mul(ck).madd(a, sk), + ra = vec(a).mul(ck).msub(c, sk); + c = rc; + a = ra; + } + void rotate_around_y(float angle) { rotate_around_y(cosf(angle), sinf(angle)); } + void rotate_around_y(const vec2 &sc) { rotate_around_y(sc.x, sc.y); } + + void rotate_around_z(float ck, float sk) + { + vec ra = vec(a).mul(ck).madd(b, sk), + rb = vec(b).mul(ck).msub(a, sk); + a = ra; + b = rb; + } + void rotate_around_z(float angle) { rotate_around_z(cosf(angle), sinf(angle)); } + void rotate_around_z(const vec2 &sc) { rotate_around_z(sc.x, sc.y); } + + vec transform(const vec &o) const { return vec(d).madd(a, o.x).madd(b, o.y).madd(c, o.z); } + vec transposedtransform(const vec &o) const { vec p = vec(o).sub(d); return vec(a.dot(p), b.dot(p), c.dot(p)); } + vec transformnormal(const vec &o) const { return vec(a).mul(o.x).madd(b, o.y).madd(c, o.z); } + vec transposedtransformnormal(const vec &o) const { return vec(a.dot(o), b.dot(o), c.dot(o)); } + vec transform(const vec2 &o) const { return vec(d).madd(a, o.x).madd(b, o.y); } + + vec4 rowx() const { return vec4(a.x, b.x, c.x, d.x); } + vec4 rowy() const { return vec4(a.y, b.y, c.y, d.y); } + vec4 rowz() const { return vec4(a.z, b.z, c.z, d.z); } }; inline dualquat::dualquat(const matrix4x3 &m) : real(m) { - dual.x = 0.5f*( m.d.x*real.w + m.d.y*real.z - m.d.z*real.y); - dual.y = 0.5f*(-m.d.x*real.z + m.d.y*real.w + m.d.z*real.x); - dual.z = 0.5f*( m.d.x*real.y - m.d.y*real.x + m.d.z*real.w); - dual.w = -0.5f*( m.d.x*real.x + m.d.y*real.y + m.d.z*real.z); + dual.x = 0.5f*( m.d.x*real.w + m.d.y*real.z - m.d.z*real.y); + dual.y = 0.5f*(-m.d.x*real.z + m.d.y*real.w + m.d.z*real.x); + dual.z = 0.5f*( m.d.x*real.y - m.d.y*real.x + m.d.z*real.w); + dual.w = -0.5f*( m.d.x*real.x + m.d.y*real.y + m.d.z*real.z); } inline matrix3::matrix3(const matrix4x3 &m) : a(m.a), b(m.b), c(m.c) {} struct plane : vec { - float offset; - - float dist(const vec &p) const { return dot(p)+offset; } - float dist(const vec4 &p) const { return p.dot3(*this) + p.w*offset; } - bool operator==(const plane &p) const { return x==p.x && y==p.y && z==p.z && offset==p.offset; } - bool operator!=(const plane &p) const { return x!=p.x || y!=p.y || z!=p.z || offset!=p.offset; } - - plane() {} - plane(const vec &c, float off) : vec(c), offset(off) {} - plane(const vec4 &p) : vec(p), offset(p.w) {} - plane(int d, float off) - { - x = y = z = 0.0f; - v[d] = 1.0f; - offset = -off; - } - plane(float a, float b, float c, float d) : vec(a, b, c), offset(d) {} - - void toplane(const vec &n, const vec &p) - { - x = n.x; y = n.y; z = n.z; - offset = -dot(p); - } - - bool toplane(const vec &a, const vec &b, const vec &c) - { - cross(vec(b).sub(a), vec(c).sub(a)); - float mag = magnitude(); - if(!mag) return false; - div(mag); - offset = -dot(a); - return true; - } - - bool rayintersect(const vec &o, const vec &ray, float &dist) - { - float cosalpha = dot(ray); - if(cosalpha==0) return false; - float deltac = offset+dot(o); - dist -= deltac/cosalpha; - return true; - } - - plane &reflectz(float rz) - { - offset += 2*rz*z; - z = -z; - return *this; - } - - plane &invert() - { - neg(); - offset = -offset; - return *this; - } - - plane &scale(float k) - { - mul(k); - return *this; - } - - plane &translate(const vec &p) - { - offset += dot(p); - return *this; - } - - plane &normalize() - { - float mag = magnitude(); - div(mag); - offset /= mag; - return *this; - } - - float zintersect(const vec &p) const { return -(x*p.x+y*p.y+offset)/z; } - float zdelta(const vec &p) const { return -(x*p.x+y*p.y)/z; } - float zdist(const vec &p) const { return p.z-zintersect(p); } + float offset; + + float dist(const vec &p) const { return dot(p)+offset; } + float dist(const vec4 &p) const { return p.dot3(*this) + p.w*offset; } + bool operator==(const plane &p) const { return x==p.x && y==p.y && z==p.z && offset==p.offset; } + bool operator!=(const plane &p) const { return x!=p.x || y!=p.y || z!=p.z || offset!=p.offset; } + + plane() {} + plane(const vec &c, float off) : vec(c), offset(off) {} + plane(const vec4 &p) : vec(p), offset(p.w) {} + plane(int d, float off) + { + x = y = z = 0.0f; + v[d] = 1.0f; + offset = -off; + } + plane(float a, float b, float c, float d) : vec(a, b, c), offset(d) {} + + void toplane(const vec &n, const vec &p) + { + x = n.x; y = n.y; z = n.z; + offset = -dot(p); + } + + bool toplane(const vec &a, const vec &b, const vec &c) + { + cross(vec(b).sub(a), vec(c).sub(a)); + float mag = magnitude(); + if(!mag) return false; + div(mag); + offset = -dot(a); + return true; + } + + bool rayintersect(const vec &o, const vec &ray, float &dist) + { + float cosalpha = dot(ray); + if(cosalpha==0) return false; + float deltac = offset+dot(o); + dist -= deltac/cosalpha; + return true; + } + + plane &reflectz(float rz) + { + offset += 2*rz*z; + z = -z; + return *this; + } + + plane &invert() + { + neg(); + offset = -offset; + return *this; + } + + plane &scale(float k) + { + mul(k); + return *this; + } + + plane &translate(const vec &p) + { + offset += dot(p); + return *this; + } + + plane &normalize() + { + float mag = magnitude(); + div(mag); + offset /= mag; + return *this; + } + + float zintersect(const vec &p) const { return -(x*p.x+y*p.y+offset)/z; } + float zdelta(const vec &p) const { return -(x*p.x+y*p.y)/z; } + float zdist(const vec &p) const { return p.z-zintersect(p); } }; struct triangle { - vec a, b, c; + vec a, b, c; - triangle(const vec &a, const vec &b, const vec &c) : a(a), b(b), c(c) {} - triangle() {} + triangle(const vec &a, const vec &b, const vec &c) : a(a), b(b), c(c) {} + triangle() {} - triangle &add(const vec &o) { a.add(o); b.add(o); c.add(o); return *this; } - triangle &sub(const vec &o) { a.sub(o); b.sub(o); c.sub(o); return *this; } + triangle &add(const vec &o) { a.add(o); b.add(o); c.add(o); return *this; } + triangle &sub(const vec &o) { a.sub(o); b.sub(o); c.sub(o); return *this; } - bool operator==(const triangle &t) const { return a == t.a && b == t.b && c == t.c; } + bool operator==(const triangle &t) const { return a == t.a && b == t.b && c == t.c; } }; /** @@ -1133,680 +1133,680 @@ struct svec; struct ivec { - union - { - struct { int x, y, z; }; - struct { int r, g, b; }; - int v[3]; - }; - - ivec() {} - explicit ivec(const vec &v) : x(int(v.x)), y(int(v.y)), z(int(v.z)) {} - ivec(int a, int b, int c) : x(a), y(b), z(c) {} - ivec(int d, int row, int col, int depth) - { - v[R[d]] = row; - v[C[d]] = col; - v[D[d]] = depth; - } - ivec(int i, const ivec &co, int size) : x(co.x+((i&1)>>0)*size), y(co.y+((i&2)>>1)*size), z(co.z +((i&4)>>2)*size) {} - explicit ivec(const ivec4 &v); - explicit ivec(const ivec2 &v, int z = 0); - explicit ivec(const usvec &v); - explicit ivec(const svec &v); - - int &operator[](int i) { return v[i]; } - int operator[](int i) const { return v[i]; } - - //int idx(int i) { return v[i]; } - bool operator==(const ivec &v) const { return x==v.x && y==v.y && z==v.z; } - bool operator!=(const ivec &v) const { return x!=v.x || y!=v.y || z!=v.z; } - bool iszero() const { return x==0 && y==0 && z==0; } - ivec &shl(int n) { x<<= n; y<<= n; z<<= n; return *this; } - ivec &shr(int n) { x>>= n; y>>= n; z>>= n; return *this; } - ivec &mul(int n) { x *= n; y *= n; z *= n; return *this; } - ivec &div(int n) { x /= n; y /= n; z /= n; return *this; } - ivec &add(int n) { x += n; y += n; z += n; return *this; } - ivec &sub(int n) { x -= n; y -= n; z -= n; return *this; } - ivec &mul(const ivec &v) { x *= v.x; y *= v.y; z *= v.z; return *this; } - ivec &div(const ivec &v) { x /= v.x; y /= v.y; z /= v.z; return *this; } - ivec &add(const ivec &v) { x += v.x; y += v.y; z += v.z; return *this; } - ivec &sub(const ivec &v) { x -= v.x; y -= v.y; z -= v.z; return *this; } - ivec &mask(int n) { x &= n; y &= n; z &= n; return *this; } - ivec &neg() { return mul(-1); } - ivec &min(const ivec &o) { x = ::min(x, o.x); y = ::min(y, o.y); z = ::min(z, o.z); return *this; } - ivec &max(const ivec &o) { x = ::max(x, o.x); y = ::max(y, o.y); z = ::max(z, o.z); return *this; } - ivec &min(int n) { x = ::min(x, n); y = ::min(y, n); z = ::min(z, n); return *this; } - ivec &max(int n) { x = ::max(x, n); y = ::max(y, n); z = ::max(z, n); return *this; } - ivec &abs() { x = ::abs(x); y = ::abs(y); z = ::abs(z); return *this; } - ivec &clamp(int l, int h) { x = ::clamp(x, l, h); y = ::clamp(y, l, h); z = ::clamp(z, l, h); return *this; } - ivec &cross(const ivec &a, const ivec &b) { x = a.y*b.z-a.z*b.y; y = a.z*b.x-a.x*b.z; z = a.x*b.y-a.y*b.x; return *this; } - int dot(const ivec &o) const { return x*o.x + y*o.y + z*o.z; } - float dist(const plane &p) const { return x*p.x + y*p.y + z*p.z + p.offset; } - - static inline ivec floor(const vec &o) { return ivec(int(::floor(o.x)), int(::floor(o.y)), int(::floor(o.z))); } - static inline ivec ceil(const vec &o) { return ivec(int(::ceil(o.x)), int(::ceil(o.y)), int(::ceil(o.z))); } + union + { + struct { int x, y, z; }; + struct { int r, g, b; }; + int v[3]; + }; + + ivec() {} + explicit ivec(const vec &v) : x(int(v.x)), y(int(v.y)), z(int(v.z)) {} + ivec(int a, int b, int c) : x(a), y(b), z(c) {} + ivec(int d, int row, int col, int depth) + { + v[R[d]] = row; + v[C[d]] = col; + v[D[d]] = depth; + } + ivec(int i, const ivec &co, int size) : x(co.x+((i&1)>>0)*size), y(co.y+((i&2)>>1)*size), z(co.z +((i&4)>>2)*size) {} + explicit ivec(const ivec4 &v); + explicit ivec(const ivec2 &v, int z = 0); + explicit ivec(const usvec &v); + explicit ivec(const svec &v); + + int &operator[](int i) { return v[i]; } + int operator[](int i) const { return v[i]; } + + //int idx(int i) { return v[i]; } + bool operator==(const ivec &v) const { return x==v.x && y==v.y && z==v.z; } + bool operator!=(const ivec &v) const { return x!=v.x || y!=v.y || z!=v.z; } + bool iszero() const { return x==0 && y==0 && z==0; } + ivec &shl(int n) { x<<= n; y<<= n; z<<= n; return *this; } + ivec &shr(int n) { x>>= n; y>>= n; z>>= n; return *this; } + ivec &mul(int n) { x *= n; y *= n; z *= n; return *this; } + ivec &div(int n) { x /= n; y /= n; z /= n; return *this; } + ivec &add(int n) { x += n; y += n; z += n; return *this; } + ivec &sub(int n) { x -= n; y -= n; z -= n; return *this; } + ivec &mul(const ivec &v) { x *= v.x; y *= v.y; z *= v.z; return *this; } + ivec &div(const ivec &v) { x /= v.x; y /= v.y; z /= v.z; return *this; } + ivec &add(const ivec &v) { x += v.x; y += v.y; z += v.z; return *this; } + ivec &sub(const ivec &v) { x -= v.x; y -= v.y; z -= v.z; return *this; } + ivec &mask(int n) { x &= n; y &= n; z &= n; return *this; } + ivec &neg() { return mul(-1); } + ivec &min(const ivec &o) { x = ::min(x, o.x); y = ::min(y, o.y); z = ::min(z, o.z); return *this; } + ivec &max(const ivec &o) { x = ::max(x, o.x); y = ::max(y, o.y); z = ::max(z, o.z); return *this; } + ivec &min(int n) { x = ::min(x, n); y = ::min(y, n); z = ::min(z, n); return *this; } + ivec &max(int n) { x = ::max(x, n); y = ::max(y, n); z = ::max(z, n); return *this; } + ivec &abs() { x = ::abs(x); y = ::abs(y); z = ::abs(z); return *this; } + ivec &clamp(int l, int h) { x = ::clamp(x, l, h); y = ::clamp(y, l, h); z = ::clamp(z, l, h); return *this; } + ivec &cross(const ivec &a, const ivec &b) { x = a.y*b.z-a.z*b.y; y = a.z*b.x-a.x*b.z; z = a.x*b.y-a.y*b.x; return *this; } + int dot(const ivec &o) const { return x*o.x + y*o.y + z*o.z; } + float dist(const plane &p) const { return x*p.x + y*p.y + z*p.z + p.offset; } + + static inline ivec floor(const vec &o) { return ivec(int(::floor(o.x)), int(::floor(o.y)), int(::floor(o.z))); } + static inline ivec ceil(const vec &o) { return ivec(int(::ceil(o.x)), int(::ceil(o.y)), int(::ceil(o.z))); } }; inline vec::vec(const ivec &v) : x(v.x), y(v.y), z(v.z) {} static inline bool htcmp(const ivec &x, const ivec &y) { - return x == y; -} + return x == y; +} static inline uint hthash(const ivec &k) { - return k.x^k.y^k.z; -} + return k.x^k.y^k.z; +} struct ivec2 { - union - { - struct { int x, y; }; - int v[2]; - }; - - ivec2() {} - ivec2(int x, int y) : x(x), y(y) {} - explicit ivec2(const vec2 &v) : x(int(v.x)), y(int(v.y)) {} - explicit ivec2(const ivec &v) : x(v.x), y(v.y) {} - - int &operator[](int i) { return v[i]; } - int operator[](int i) const { return v[i]; } - - bool operator==(const ivec2 &o) const { return x == o.x && y == o.y; } - bool operator!=(const ivec2 &o) const { return x != o.x || y != o.y; } - - bool iszero() const { return x==0 && y==0; } - ivec2 &shl(int n) { x<<= n; y<<= n; return *this; } - ivec2 &shr(int n) { x>>= n; y>>= n; return *this; } - ivec2 &mul(int n) { x *= n; y *= n; return *this; } - ivec2 &div(int n) { x /= n; y /= n; return *this; } - ivec2 &add(int n) { x += n; y += n; return *this; } - ivec2 &sub(int n) { x -= n; y -= n; return *this; } - ivec2 &mul(const ivec2 &v) { x *= v.x; y *= v.y; return *this; } - ivec2 &div(const ivec2 &v) { x /= v.x; y /= v.y; return *this; } - ivec2 &add(const ivec2 &v) { x += v.x; y += v.y; return *this; } - ivec2 &sub(const ivec2 &v) { x -= v.x; y -= v.y; return *this; } - ivec2 &mask(int n) { x &= n; y &= n; return *this; } - ivec2 &neg() { x = -x; y = -y; return *this; } - ivec2 &min(const ivec2 &o) { x = ::min(x, o.x); y = ::min(y, o.y); return *this; } - ivec2 &max(const ivec2 &o) { x = ::max(x, o.x); y = ::max(y, o.y); return *this; } - ivec2 &min(int n) { x = ::min(x, n); y = ::min(y, n); return *this; } - ivec2 &max(int n) { x = ::max(x, n); y = ::max(y, n); return *this; } - ivec2 &abs() { x = ::abs(x); y = ::abs(y); return *this; } - int dot(const ivec2 &o) const { return x*o.x + y*o.y; } - int cross(const ivec2 &o) const { return x*o.y - y*o.x; } + union + { + struct { int x, y; }; + int v[2]; + }; + + ivec2() {} + ivec2(int x, int y) : x(x), y(y) {} + explicit ivec2(const vec2 &v) : x(int(v.x)), y(int(v.y)) {} + explicit ivec2(const ivec &v) : x(v.x), y(v.y) {} + + int &operator[](int i) { return v[i]; } + int operator[](int i) const { return v[i]; } + + bool operator==(const ivec2 &o) const { return x == o.x && y == o.y; } + bool operator!=(const ivec2 &o) const { return x != o.x || y != o.y; } + + bool iszero() const { return x==0 && y==0; } + ivec2 &shl(int n) { x<<= n; y<<= n; return *this; } + ivec2 &shr(int n) { x>>= n; y>>= n; return *this; } + ivec2 &mul(int n) { x *= n; y *= n; return *this; } + ivec2 &div(int n) { x /= n; y /= n; return *this; } + ivec2 &add(int n) { x += n; y += n; return *this; } + ivec2 &sub(int n) { x -= n; y -= n; return *this; } + ivec2 &mul(const ivec2 &v) { x *= v.x; y *= v.y; return *this; } + ivec2 &div(const ivec2 &v) { x /= v.x; y /= v.y; return *this; } + ivec2 &add(const ivec2 &v) { x += v.x; y += v.y; return *this; } + ivec2 &sub(const ivec2 &v) { x -= v.x; y -= v.y; return *this; } + ivec2 &mask(int n) { x &= n; y &= n; return *this; } + ivec2 &neg() { x = -x; y = -y; return *this; } + ivec2 &min(const ivec2 &o) { x = ::min(x, o.x); y = ::min(y, o.y); return *this; } + ivec2 &max(const ivec2 &o) { x = ::max(x, o.x); y = ::max(y, o.y); return *this; } + ivec2 &min(int n) { x = ::min(x, n); y = ::min(y, n); return *this; } + ivec2 &max(int n) { x = ::max(x, n); y = ::max(y, n); return *this; } + ivec2 &abs() { x = ::abs(x); y = ::abs(y); return *this; } + int dot(const ivec2 &o) const { return x*o.x + y*o.y; } + int cross(const ivec2 &o) const { return x*o.y - y*o.x; } }; inline ivec::ivec(const ivec2 &v, int z) : x(v.x), y(v.y), z(z) {} static inline bool htcmp(const ivec2 &x, const ivec2 &y) { - return x == y; + return x == y; } static inline uint hthash(const ivec2 &k) { - return k.x^k.y; + return k.x^k.y; } struct ivec4 { - union - { - struct { int x, y, z, w; }; - struct { int r, g, b, a; }; - int v[4]; - }; - - ivec4() {} - explicit ivec4(const ivec &p, int w = 0) : x(p.x), y(p.y), z(p.z), w(w) {} - ivec4(int x, int y, int z, int w) : x(x), y(y), z(z), w(w) {} - explicit ivec4(const vec4 &v) : x(int(v.x)), y(int(v.y)), z(int(v.z)), w(int(v.w)) {} - - bool operator==(const ivec4 &o) const { return x == o.x && y == o.y && z == o.z && w == o.w; } - bool operator!=(const ivec4 &o) const { return x != o.x || y != o.y || z != o.z || w != o.w; } + union + { + struct { int x, y, z, w; }; + struct { int r, g, b, a; }; + int v[4]; + }; + + ivec4() {} + explicit ivec4(const ivec &p, int w = 0) : x(p.x), y(p.y), z(p.z), w(w) {} + ivec4(int x, int y, int z, int w) : x(x), y(y), z(z), w(w) {} + explicit ivec4(const vec4 &v) : x(int(v.x)), y(int(v.y)), z(int(v.z)), w(int(v.w)) {} + + bool operator==(const ivec4 &o) const { return x == o.x && y == o.y && z == o.z && w == o.w; } + bool operator!=(const ivec4 &o) const { return x != o.x || y != o.y || z != o.z || w != o.w; } }; inline ivec::ivec(const ivec4 &v) : x(v.x), y(v.y), z(v.z) {} static inline bool htcmp(const ivec4 &x, const ivec4 &y) { - return x == y; + return x == y; } static inline uint hthash(const ivec4 &k) { - return k.x^k.y^k.z^k.w; + return k.x^k.y^k.z^k.w; } struct bvec4; struct bvec { - union - { - struct { uchar x, y, z; }; - struct { uchar r, g, b; }; - uchar v[3]; - }; + union + { + struct { uchar x, y, z; }; + struct { uchar r, g, b; }; + uchar v[3]; + }; - bvec() {} - bvec(uchar x, uchar y, uchar z) : x(x), y(y), z(z) {} - explicit bvec(const vec &v) : x(uchar((v.x+1)*(255.0f/2.0f))), y(uchar((v.y+1)*(255.0f/2.0f))), z(uchar((v.z+1)*(255.0f/2.0f))) {} - explicit bvec(const bvec4 &v); + bvec() {} + bvec(uchar x, uchar y, uchar z) : x(x), y(y), z(z) {} + explicit bvec(const vec &v) : x(uchar((v.x+1)*(255.0f/2.0f))), y(uchar((v.y+1)*(255.0f/2.0f))), z(uchar((v.z+1)*(255.0f/2.0f))) {} + explicit bvec(const bvec4 &v); - uchar &operator[](int i) { return v[i]; } - uchar operator[](int i) const { return v[i]; } + uchar &operator[](int i) { return v[i]; } + uchar operator[](int i) const { return v[i]; } - bool operator==(const bvec &v) const { return x==v.x && y==v.y && z==v.z; } - bool operator!=(const bvec &v) const { return x!=v.x || y!=v.y || z!=v.z; } + bool operator==(const bvec &v) const { return x==v.x && y==v.y && z==v.z; } + bool operator!=(const bvec &v) const { return x!=v.x || y!=v.y || z!=v.z; } - bool iszero() const { return x==0 && y==0 && z==0; } + bool iszero() const { return x==0 && y==0 && z==0; } - vec tonormal() const { return vec(x*(2.0f/255.0f)-1.0f, y*(2.0f/255.0f)-1.0f, z*(2.0f/255.0f)-1.0f); } + vec tonormal() const { return vec(x*(2.0f/255.0f)-1.0f, y*(2.0f/255.0f)-1.0f, z*(2.0f/255.0f)-1.0f); } - bvec &normalize() - { - vec n(x-127.5f, y-127.5f, z-127.5f); - float mag = 127.5f/n.magnitude(); - x = uchar(n.x*mag+127.5f); - y = uchar(n.y*mag+127.5f); - z = uchar(n.z*mag+127.5f); - return *this; - } + bvec &normalize() + { + vec n(x-127.5f, y-127.5f, z-127.5f); + float mag = 127.5f/n.magnitude(); + x = uchar(n.x*mag+127.5f); + y = uchar(n.y*mag+127.5f); + z = uchar(n.z*mag+127.5f); + return *this; + } - void lerp(const bvec &a, const bvec &b, float t) { x = uchar(a.x + (b.x-a.x)*t); y = uchar(a.y + (b.y-a.y)*t); z = uchar(a.z + (b.z-a.z)*t); } + void lerp(const bvec &a, const bvec &b, float t) { x = uchar(a.x + (b.x-a.x)*t); y = uchar(a.y + (b.y-a.y)*t); z = uchar(a.z + (b.z-a.z)*t); } - void lerp(const bvec &a, const bvec &b, int ka, int kb, int d) - { - x = uchar((a.x*ka + b.x*kb)/d); - y = uchar((a.y*ka + b.y*kb)/d); - z = uchar((a.z*ka + b.z*kb)/d); - } + void lerp(const bvec &a, const bvec &b, int ka, int kb, int d) + { + x = uchar((a.x*ka + b.x*kb)/d); + y = uchar((a.y*ka + b.y*kb)/d); + z = uchar((a.z*ka + b.z*kb)/d); + } - void flip() { x ^= 0x80; y ^= 0x80; z ^= 0x80; } + void flip() { x ^= 0x80; y ^= 0x80; z ^= 0x80; } - void scale(int k, int d) { x = uchar((x*k)/d); y = uchar((y*k)/d); z = uchar((z*k)/d); } + void scale(int k, int d) { x = uchar((x*k)/d); y = uchar((y*k)/d); z = uchar((z*k)/d); } - bvec &shl(int n) { x<<= n; y<<= n; z<<= n; return *this; } - bvec &shr(int n) { x>>= n; y>>= n; z>>= n; return *this; } + bvec &shl(int n) { x<<= n; y<<= n; z<<= n; return *this; } + bvec &shr(int n) { x>>= n; y>>= n; z>>= n; return *this; } - static bvec fromcolor(const vec &v) { return bvec(uchar(v.x*255.0f), uchar(v.y*255.0f), uchar(v.z*255.0f)); } - vec tocolor() const { return vec(x*(1.0f/255.0f), y*(1.0f/255.0f), z*(1.0f/255.0f)); } + static bvec fromcolor(const vec &v) { return bvec(uchar(v.x*255.0f), uchar(v.y*255.0f), uchar(v.z*255.0f)); } + vec tocolor() const { return vec(x*(1.0f/255.0f), y*(1.0f/255.0f), z*(1.0f/255.0f)); } - static bvec from565(ushort c) { return bvec((((c>>11)&0x1F)*527 + 15) >> 6, (((c>>5)&0x3F)*259 + 35) >> 6, ((c&0x1F)*527 + 15) >> 6); } + static bvec from565(ushort c) { return bvec((((c>>11)&0x1F)*527 + 15) >> 6, (((c>>5)&0x3F)*259 + 35) >> 6, ((c&0x1F)*527 + 15) >> 6); } - static bvec hexcolor(int color) - { - return bvec((color>>16)&0xFF, (color>>8)&0xFF, color&0xFF); - } - int tohexcolor() const { return (int(r)<<16)|(int(g)<<8)|int(b); } + static bvec hexcolor(int color) + { + return bvec((color>>16)&0xFF, (color>>8)&0xFF, color&0xFF); + } + int tohexcolor() const { return (int(r)<<16)|(int(g)<<8)|int(b); } }; struct bvec4 { - union - { - struct { uchar x, y, z, w; }; - struct { uchar r, g, b, a; }; - uchar v[4]; - uint mask; - }; - - bvec4() {} - bvec4(uchar x, uchar y, uchar z, uchar w = 0) : x(x), y(y), z(z), w(w) {} - bvec4(const bvec &v, uchar w = 0) : x(v.x), y(v.y), z(v.z), w(w) {} - - uchar &operator[](int i) { return v[i]; } - uchar operator[](int i) const { return v[i]; } - - bool operator==(const bvec4 &v) const { return mask==v.mask; } - bool operator!=(const bvec4 &v) const { return mask!=v.mask; } - - bool iszero() const { return mask==0; } - - vec tonormal() const { return vec(x*(2.0f/255.0f)-1.0f, y*(2.0f/255.0f)-1.0f, z*(2.0f/255.0f)-1.0f); } - - void lerp(const bvec4 &a, const bvec4 &b, float t) - { - x = uchar(a.x + (b.x-a.x)*t); - y = uchar(a.y + (b.y-a.y)*t); - z = uchar(a.z + (b.z-a.z)*t); - w = a.w; - } - - void lerp(const bvec4 &a, const bvec4 &b, int ka, int kb, int d) - { - x = uchar((a.x*ka + b.x*kb)/d); - y = uchar((a.y*ka + b.y*kb)/d); - z = uchar((a.z*ka + b.z*kb)/d); - w = a.w; - } - - void flip() { mask ^= 0x80808080; } + union + { + struct { uchar x, y, z, w; }; + struct { uchar r, g, b, a; }; + uchar v[4]; + uint mask; + }; + + bvec4() {} + bvec4(uchar x, uchar y, uchar z, uchar w = 0) : x(x), y(y), z(z), w(w) {} + bvec4(const bvec &v, uchar w = 0) : x(v.x), y(v.y), z(v.z), w(w) {} + + uchar &operator[](int i) { return v[i]; } + uchar operator[](int i) const { return v[i]; } + + bool operator==(const bvec4 &v) const { return mask==v.mask; } + bool operator!=(const bvec4 &v) const { return mask!=v.mask; } + + bool iszero() const { return mask==0; } + + vec tonormal() const { return vec(x*(2.0f/255.0f)-1.0f, y*(2.0f/255.0f)-1.0f, z*(2.0f/255.0f)-1.0f); } + + void lerp(const bvec4 &a, const bvec4 &b, float t) + { + x = uchar(a.x + (b.x-a.x)*t); + y = uchar(a.y + (b.y-a.y)*t); + z = uchar(a.z + (b.z-a.z)*t); + w = a.w; + } + + void lerp(const bvec4 &a, const bvec4 &b, int ka, int kb, int d) + { + x = uchar((a.x*ka + b.x*kb)/d); + y = uchar((a.y*ka + b.y*kb)/d); + z = uchar((a.z*ka + b.z*kb)/d); + w = a.w; + } + + void flip() { mask ^= 0x80808080; } }; inline bvec::bvec(const bvec4 &v) : x(v.x), y(v.y), z(v.z) {} struct usvec { - union - { - struct { ushort x, y, z; }; - ushort v[3]; - }; - - ushort &operator[](int i) { return v[i]; } - ushort operator[](int i) const { return v[i]; } + union + { + struct { ushort x, y, z; }; + ushort v[3]; + }; + + ushort &operator[](int i) { return v[i]; } + ushort operator[](int i) const { return v[i]; } }; inline ivec::ivec(const usvec &v) : x(v.x), y(v.y), z(v.z) {} struct svec { - union - { - struct { short x, y, z; }; - short v[3]; - }; - - svec() {} - svec(short x, short y, short z) : x(x), y(y), z(z) {} - explicit svec(const ivec &v) : x(v.x), y(v.y), z(v.z) {} - - short &operator[](int i) { return v[i]; } - short operator[](int i) const { return v[i]; } + union + { + struct { short x, y, z; }; + short v[3]; + }; + + svec() {} + svec(short x, short y, short z) : x(x), y(y), z(z) {} + explicit svec(const ivec &v) : x(v.x), y(v.y), z(v.z) {} + + short &operator[](int i) { return v[i]; } + short operator[](int i) const { return v[i]; } }; inline ivec::ivec(const svec &v) : x(v.x), y(v.y), z(v.z) {} struct svec2 { - union - { - struct { short x, y; }; - short v[2]; - }; + union + { + struct { short x, y; }; + short v[2]; + }; - svec2() {} - svec2(short x, short y) : x(x), y(y) {} + svec2() {} + svec2(short x, short y) : x(x), y(y) {} - short &operator[](int i) { return v[i]; } - short operator[](int i) const { return v[i]; } + short &operator[](int i) { return v[i]; } + short operator[](int i) const { return v[i]; } - bool operator==(const svec2 &o) const { return x == o.x && y == o.y; } - bool operator!=(const svec2 &o) const { return x != o.x || y != o.y; } + bool operator==(const svec2 &o) const { return x == o.x && y == o.y; } + bool operator!=(const svec2 &o) const { return x != o.x || y != o.y; } - bool iszero() const { return x==0 && y==0; } + bool iszero() const { return x==0 && y==0; } }; struct dvec4 { - double x, y, z, w; + double x, y, z, w; - dvec4() {} - dvec4(double x, double y, double z, double w) : x(x), y(y), z(z), w(w) {} - dvec4(const vec4 &v) : x(v.x), y(v.y), z(v.z), w(v.w) {} + dvec4() {} + dvec4(double x, double y, double z, double w) : x(x), y(y), z(z), w(w) {} + dvec4(const vec4 &v) : x(v.x), y(v.y), z(v.z), w(v.w) {} - template dvec4 &madd(const dvec4 &a, const B &b) { return add(dvec4(a).mul(b)); } - dvec4 &mul(double f) { x *= f; y *= f; z *= f; w *= f; return *this; } - dvec4 &mul(const dvec4 &o) { x *= o.x; y *= o.y; z *= o.z; w *= o.w; return *this; } - dvec4 &add(double f) { x += f; y += f; z += f; w += f; return *this; } - dvec4 &add(const dvec4 &o) { x += o.x; y += o.y; z += o.z; w += o.w; return *this; } + template dvec4 &madd(const dvec4 &a, const B &b) { return add(dvec4(a).mul(b)); } + dvec4 &mul(double f) { x *= f; y *= f; z *= f; w *= f; return *this; } + dvec4 &mul(const dvec4 &o) { x *= o.x; y *= o.y; z *= o.z; w *= o.w; return *this; } + dvec4 &add(double f) { x += f; y += f; z += f; w += f; return *this; } + dvec4 &add(const dvec4 &o) { x += o.x; y += o.y; z += o.z; w += o.w; return *this; } - operator vec4() const { return vec4(x, y, z, w); } + operator vec4() const { return vec4(x, y, z, w); } }; struct matrix4 { - vec4 a, b, c, d; - - matrix4() {} - matrix4(const float *m) : a(m), b(m+4), c(m+8), d(m+12) {} - matrix4(const vec &a, const vec &b, const vec &c = vec(0, 0, 1)) - : a(a.x, b.x, c.x, 0), b(a.y, b.y, c.y, 0), c(a.z, b.z, c.z, 0), d(0, 0, 0, 1) - {} - matrix4(const vec4 &a, const vec4 &b, const vec4 &c, const vec4 &d = vec4(0, 0, 0, 1)) - : a(a), b(b), c(c), d(d) - {} - matrix4(const matrix4x3 &m) - : a(m.a, 0), b(m.b, 0), c(m.c, 0), d(m.d, 1) - {} - matrix4(const matrix3 &rot, const vec &trans) - : a(rot.a, 0), b(rot.b, 0), c(rot.c, 0), d(trans, 1) - {} - - void mul(const matrix4 &x, const matrix3 &y) - { - a = vec4(x.a).mul(y.a.x).madd(x.b, y.a.y).madd(x.c, y.a.z); - b = vec4(x.a).mul(y.b.x).madd(x.b, y.b.y).madd(x.c, y.b.z); - c = vec4(x.a).mul(y.c.x).madd(x.b, y.c.y).madd(x.c, y.c.z); - d = x.d; - } - void mul(const matrix3 &y) { mul(matrix4(*this), y); } - - template void mult(const matrix4 &x, const matrix4 &y) - { - a = T(x.a).mul(y.a.x).madd(x.b, y.a.y).madd(x.c, y.a.z).madd(x.d, y.a.w); - b = T(x.a).mul(y.b.x).madd(x.b, y.b.y).madd(x.c, y.b.z).madd(x.d, y.b.w); - c = T(x.a).mul(y.c.x).madd(x.b, y.c.y).madd(x.c, y.c.z).madd(x.d, y.c.w); - d = T(x.a).mul(y.d.x).madd(x.b, y.d.y).madd(x.c, y.d.z).madd(x.d, y.d.w); - } - - void mul(const matrix4 &x, const matrix4 &y) { mult(x, y); } - void mul(const matrix4 &y) { mult(matrix4(*this), y); } - - void muld(const matrix4 &x, const matrix4 &y) { mult(x, y); } - void muld(const matrix4 &y) { mult(matrix4(*this), y); } - - void rotate_around_x(float ck, float sk) - { - vec4 rb = vec4(b).mul(ck).madd(c, sk), - rc = vec4(c).mul(ck).msub(b, sk); - b = rb; - c = rc; - } - void rotate_around_x(float angle) { rotate_around_x(cosf(angle), sinf(angle)); } - void rotate_around_x(const vec2 &sc) { rotate_around_x(sc.x, sc.y); } - - void rotate_around_y(float ck, float sk) - { - vec4 rc = vec4(c).mul(ck).madd(a, sk), - ra = vec4(a).mul(ck).msub(c, sk); - c = rc; - a = ra; - } - void rotate_around_y(float angle) { rotate_around_y(cosf(angle), sinf(angle)); } - void rotate_around_y(const vec2 &sc) { rotate_around_y(sc.x, sc.y); } - - void rotate_around_z(float ck, float sk) - { - vec4 ra = vec4(a).mul(ck).madd(b, sk), - rb = vec4(b).mul(ck).msub(a, sk); - a = ra; - b = rb; - } - void rotate_around_z(float angle) { rotate_around_z(cosf(angle), sinf(angle)); } - void rotate_around_z(const vec2 &sc) { rotate_around_z(sc.x, sc.y); } - - void rotate(float ck, float sk, const vec &axis) - { - matrix3 m; - m.rotate(ck, sk, axis); - mul(m); - } - void rotate(float angle, const vec &dir) { rotate(cosf(angle), sinf(angle), dir); } - void rotate(const vec2 &sc, const vec &dir) { rotate(sc.x, sc.y, dir); } - - void identity() - { - a = vec4(1, 0, 0, 0); - b = vec4(0, 1, 0, 0); - c = vec4(0, 0, 1, 0); - d = vec4(0, 0, 0, 1); - } - - void settranslation(const vec &v) { d.setxyz(v); } - void settranslation(float x, float y, float z) { d.x = x; d.y = y; d.z = z; } - - void translate(const vec &p) { d.madd(a, p.x).madd(b, p.y).madd(c, p.z); } - void translate(float x, float y, float z) { translate(vec(x, y, z)); } - void translate(const vec &p, float scale) { translate(vec(p).mul(scale)); } - - void setscale(float x, float y, float z) { a.x = x; b.y = y; c.z = z; } - void setscale(const vec &v) { setscale(v.x, v.y, v.z); } - void setscale(float n) { setscale(n, n, n); } - - void scale(float x, float y, float z) - { - a.mul(x); - b.mul(y); - c.mul(z); - } - void scale(const vec &v) { scale(v.x, v.y, v.z); } - void scale(float n) { scale(n, n, n); } - - void scalexy(float x, float y) - { - a.x *= x; a.y *= y; - b.x *= x; b.y *= y; - c.x *= x; c.y *= y; - d.x *= x; d.y *= y; - } - - void scalez(float k) - { - a.z *= k; - b.z *= k; - c.z *= k; - d.z *= k; - } - - void reflectz(float z) - { - d.add(vec4(c).mul(2*z)); - c.neg(); - } - - void projective(float zscale = 0.5f, float zoffset = 0.5f) - { - a.x = 0.5f*(a.x + a.w); - a.y = 0.5f*(a.y + a.w); - b.x = 0.5f*(b.x + b.w); - b.y = 0.5f*(b.y + b.w); - c.x = 0.5f*(c.x + c.w); - c.y = 0.5f*(c.y + c.w); - d.x = 0.5f*(d.x + d.w); - d.y = 0.5f*(d.y + d.w); - a.z = zscale*a.z + zoffset*a.w; - b.z = zscale*b.z + zoffset*b.w; - c.z = zscale*c.z + zoffset*c.w; - d.z = zscale*d.z + zoffset*d.w; - } - - void jitter(float x, float y) - { - a.x += x * a.w; - a.y += y * a.w; - b.x += x * b.w; - b.y += y * b.w; - c.x += x * c.w; - c.y += y * c.w; - d.x += x * d.w; - d.y += y * d.w; - } - - void transpose() - { - swap(a.y, b.x); swap(a.z, c.x); swap(a.w, d.x); - swap(b.z, c.y); swap(b.w, d.y); - swap(c.w, d.z); - } - - void transpose(const matrix4 &m) - { - a = vec4(m.a.x, m.b.x, m.c.x, m.d.x); - b = vec4(m.a.y, m.b.y, m.c.y, m.d.y); - c = vec4(m.a.z, m.b.z, m.c.z, m.d.z); - d = vec4(m.a.w, m.b.w, m.c.w, m.d.w); - } - - void frustum(float left, float right, float bottom, float top, float znear, float zfar) - { - float width = right - left, height = top - bottom, zrange = znear - zfar; - a = vec4(2*znear/width, 0, 0, 0); - b = vec4(0, 2*znear/height, 0, 0); - c = vec4((right + left)/width, (top + bottom)/height, (zfar + znear)/zrange, -1); - d = vec4(0, 0, 2*znear*zfar/zrange, 0); - } - - void perspective(float fovy, float aspect, float znear, float zfar) - { - float ydist = znear * tan(fovy/2*RAD), xdist = ydist * aspect; - frustum(-xdist, xdist, -ydist, ydist, znear, zfar); - } - - void ortho(float left, float right, float bottom, float top, float znear, float zfar) - { - float width = right - left, height = top - bottom, zrange = znear - zfar; - a = vec4(2/width, 0, 0, 0); - b = vec4(0, 2/height, 0, 0); - c = vec4(0, 0, 2/zrange, 0); - d = vec4(-(right+left)/width, -(top+bottom)/height, (zfar+znear)/zrange, 1); - } - - void clip(const plane &p, const matrix4 &m) - { - float x = ((p.x<0 ? -1 : (p.x>0 ? 1 : 0)) + m.c.x) / m.a.x, - y = ((p.y<0 ? -1 : (p.y>0 ? 1 : 0)) + m.c.y) / m.b.y, - w = (1 + m.c.z) / m.d.z, - scale = 2 / (x*p.x + y*p.y - p.z + w*p.offset); - a = vec4(m.a.x, m.a.y, p.x*scale, m.a.w); - b = vec4(m.b.x, m.b.y, p.y*scale, m.b.w); - c = vec4(m.c.x, m.c.y, p.z*scale + 1.0f, m.c.w); - d = vec4(m.d.x, m.d.y, p.offset*scale, m.d.w); - } - - void transform(const vec &in, vec &out) const - { - out = vec(a).mul(in.x).add(vec(b).mul(in.y)).add(vec(c).mul(in.z)).add(vec(d)); - } - - void transform(const vec4 &in, vec &out) const - { - out = vec(a).mul(in.x).add(vec(b).mul(in.y)).add(vec(c).mul(in.z)).add(vec(d).mul(in.w)); - } - - void transform(const vec &in, vec4 &out) const - { - out = vec4(a).mul(in.x).madd(b, in.y).madd(c, in.z).add(d); - } - - void transform(const vec4 &in, vec4 &out) const - { - out = vec4(a).mul(in.x).madd(b, in.y).madd(c, in.z).madd(d, in.w); - } - - template T transform(const U &in) const - { - T v; - transform(in, v); - return v; - } - - template vec perspectivetransform(const T &in) const - { - vec4 v; - transform(in, v); - return vec(v).div(v.w); - } - - void transformnormal(const vec &in, vec &out) const - { - out = vec(a).mul(in.x).add(vec(b).mul(in.y)).add(vec(c).mul(in.z)); - } - - void transformnormal(const vec &in, vec4 &out) const - { - out = vec4(a).mul(in.x).madd(b, in.y).madd(c, in.z); - } - - template T transformnormal(const U &in) const - { - T v; - transformnormal(in, v); - return v; - } - - void transposedtransform(const vec &in, vec &out) const - { - vec p = vec(in).sub(vec(d)); - out.x = a.dot3(p); - out.y = b.dot3(p); - out.z = c.dot3(p); - } - - void transposedtransformnormal(const vec &in, vec &out) const - { - out.x = a.dot3(in); - out.y = b.dot3(in); - out.z = c.dot3(in); - } - - void transposedtransform(const plane &in, plane &out) const - { - out.x = in.dist(a); - out.y = in.dist(b); - out.z = in.dist(c); - out.offset = in.dist(d); - } - - float getscale() const - { - return sqrtf(a.x*a.y + b.x*b.x + c.x*c.x); - } - - vec gettranslation() const - { - return vec(d); - } - - vec4 rowx() const { return vec4(a.x, b.x, c.x, d.x); } - vec4 rowy() const { return vec4(a.y, b.y, c.y, d.y); } - vec4 rowz() const { return vec4(a.z, b.z, c.z, d.z); } - vec4 roww() const { return vec4(a.w, b.w, c.w, d.w); } - - bool invert(const matrix4 &m, double mindet = 1.0e-12); - - vec2 lineardepthscale() const - { - return vec2(d.w, -d.z).div(c.z*d.w - d.z*c.w); - } + vec4 a, b, c, d; + + matrix4() {} + matrix4(const float *m) : a(m), b(m+4), c(m+8), d(m+12) {} + matrix4(const vec &a, const vec &b, const vec &c = vec(0, 0, 1)) + : a(a.x, b.x, c.x, 0), b(a.y, b.y, c.y, 0), c(a.z, b.z, c.z, 0), d(0, 0, 0, 1) + {} + matrix4(const vec4 &a, const vec4 &b, const vec4 &c, const vec4 &d = vec4(0, 0, 0, 1)) + : a(a), b(b), c(c), d(d) + {} + matrix4(const matrix4x3 &m) + : a(m.a, 0), b(m.b, 0), c(m.c, 0), d(m.d, 1) + {} + matrix4(const matrix3 &rot, const vec &trans) + : a(rot.a, 0), b(rot.b, 0), c(rot.c, 0), d(trans, 1) + {} + + void mul(const matrix4 &x, const matrix3 &y) + { + a = vec4(x.a).mul(y.a.x).madd(x.b, y.a.y).madd(x.c, y.a.z); + b = vec4(x.a).mul(y.b.x).madd(x.b, y.b.y).madd(x.c, y.b.z); + c = vec4(x.a).mul(y.c.x).madd(x.b, y.c.y).madd(x.c, y.c.z); + d = x.d; + } + void mul(const matrix3 &y) { mul(matrix4(*this), y); } + + template void mult(const matrix4 &x, const matrix4 &y) + { + a = T(x.a).mul(y.a.x).madd(x.b, y.a.y).madd(x.c, y.a.z).madd(x.d, y.a.w); + b = T(x.a).mul(y.b.x).madd(x.b, y.b.y).madd(x.c, y.b.z).madd(x.d, y.b.w); + c = T(x.a).mul(y.c.x).madd(x.b, y.c.y).madd(x.c, y.c.z).madd(x.d, y.c.w); + d = T(x.a).mul(y.d.x).madd(x.b, y.d.y).madd(x.c, y.d.z).madd(x.d, y.d.w); + } + + void mul(const matrix4 &x, const matrix4 &y) { mult(x, y); } + void mul(const matrix4 &y) { mult(matrix4(*this), y); } + + void muld(const matrix4 &x, const matrix4 &y) { mult(x, y); } + void muld(const matrix4 &y) { mult(matrix4(*this), y); } + + void rotate_around_x(float ck, float sk) + { + vec4 rb = vec4(b).mul(ck).madd(c, sk), + rc = vec4(c).mul(ck).msub(b, sk); + b = rb; + c = rc; + } + void rotate_around_x(float angle) { rotate_around_x(cosf(angle), sinf(angle)); } + void rotate_around_x(const vec2 &sc) { rotate_around_x(sc.x, sc.y); } + + void rotate_around_y(float ck, float sk) + { + vec4 rc = vec4(c).mul(ck).madd(a, sk), + ra = vec4(a).mul(ck).msub(c, sk); + c = rc; + a = ra; + } + void rotate_around_y(float angle) { rotate_around_y(cosf(angle), sinf(angle)); } + void rotate_around_y(const vec2 &sc) { rotate_around_y(sc.x, sc.y); } + + void rotate_around_z(float ck, float sk) + { + vec4 ra = vec4(a).mul(ck).madd(b, sk), + rb = vec4(b).mul(ck).msub(a, sk); + a = ra; + b = rb; + } + void rotate_around_z(float angle) { rotate_around_z(cosf(angle), sinf(angle)); } + void rotate_around_z(const vec2 &sc) { rotate_around_z(sc.x, sc.y); } + + void rotate(float ck, float sk, const vec &axis) + { + matrix3 m; + m.rotate(ck, sk, axis); + mul(m); + } + void rotate(float angle, const vec &dir) { rotate(cosf(angle), sinf(angle), dir); } + void rotate(const vec2 &sc, const vec &dir) { rotate(sc.x, sc.y, dir); } + + void identity() + { + a = vec4(1, 0, 0, 0); + b = vec4(0, 1, 0, 0); + c = vec4(0, 0, 1, 0); + d = vec4(0, 0, 0, 1); + } + + void settranslation(const vec &v) { d.setxyz(v); } + void settranslation(float x, float y, float z) { d.x = x; d.y = y; d.z = z; } + + void translate(const vec &p) { d.madd(a, p.x).madd(b, p.y).madd(c, p.z); } + void translate(float x, float y, float z) { translate(vec(x, y, z)); } + void translate(const vec &p, float scale) { translate(vec(p).mul(scale)); } + + void setscale(float x, float y, float z) { a.x = x; b.y = y; c.z = z; } + void setscale(const vec &v) { setscale(v.x, v.y, v.z); } + void setscale(float n) { setscale(n, n, n); } + + void scale(float x, float y, float z) + { + a.mul(x); + b.mul(y); + c.mul(z); + } + void scale(const vec &v) { scale(v.x, v.y, v.z); } + void scale(float n) { scale(n, n, n); } + + void scalexy(float x, float y) + { + a.x *= x; a.y *= y; + b.x *= x; b.y *= y; + c.x *= x; c.y *= y; + d.x *= x; d.y *= y; + } + + void scalez(float k) + { + a.z *= k; + b.z *= k; + c.z *= k; + d.z *= k; + } + + void reflectz(float z) + { + d.add(vec4(c).mul(2*z)); + c.neg(); + } + + void projective(float zscale = 0.5f, float zoffset = 0.5f) + { + a.x = 0.5f*(a.x + a.w); + a.y = 0.5f*(a.y + a.w); + b.x = 0.5f*(b.x + b.w); + b.y = 0.5f*(b.y + b.w); + c.x = 0.5f*(c.x + c.w); + c.y = 0.5f*(c.y + c.w); + d.x = 0.5f*(d.x + d.w); + d.y = 0.5f*(d.y + d.w); + a.z = zscale*a.z + zoffset*a.w; + b.z = zscale*b.z + zoffset*b.w; + c.z = zscale*c.z + zoffset*c.w; + d.z = zscale*d.z + zoffset*d.w; + } + + void jitter(float x, float y) + { + a.x += x * a.w; + a.y += y * a.w; + b.x += x * b.w; + b.y += y * b.w; + c.x += x * c.w; + c.y += y * c.w; + d.x += x * d.w; + d.y += y * d.w; + } + + void transpose() + { + swap(a.y, b.x); swap(a.z, c.x); swap(a.w, d.x); + swap(b.z, c.y); swap(b.w, d.y); + swap(c.w, d.z); + } + + void transpose(const matrix4 &m) + { + a = vec4(m.a.x, m.b.x, m.c.x, m.d.x); + b = vec4(m.a.y, m.b.y, m.c.y, m.d.y); + c = vec4(m.a.z, m.b.z, m.c.z, m.d.z); + d = vec4(m.a.w, m.b.w, m.c.w, m.d.w); + } + + void frustum(float left, float right, float bottom, float top, float znear, float zfar) + { + float width = right - left, height = top - bottom, zrange = znear - zfar; + a = vec4(2*znear/width, 0, 0, 0); + b = vec4(0, 2*znear/height, 0, 0); + c = vec4((right + left)/width, (top + bottom)/height, (zfar + znear)/zrange, -1); + d = vec4(0, 0, 2*znear*zfar/zrange, 0); + } + + void perspective(float fovy, float aspect, float znear, float zfar) + { + float ydist = znear * tan(fovy/2*RAD), xdist = ydist * aspect; + frustum(-xdist, xdist, -ydist, ydist, znear, zfar); + } + + void ortho(float left, float right, float bottom, float top, float znear, float zfar) + { + float width = right - left, height = top - bottom, zrange = znear - zfar; + a = vec4(2/width, 0, 0, 0); + b = vec4(0, 2/height, 0, 0); + c = vec4(0, 0, 2/zrange, 0); + d = vec4(-(right+left)/width, -(top+bottom)/height, (zfar+znear)/zrange, 1); + } + + void clip(const plane &p, const matrix4 &m) + { + float x = ((p.x<0 ? -1 : (p.x>0 ? 1 : 0)) + m.c.x) / m.a.x, + y = ((p.y<0 ? -1 : (p.y>0 ? 1 : 0)) + m.c.y) / m.b.y, + w = (1 + m.c.z) / m.d.z, + scale = 2 / (x*p.x + y*p.y - p.z + w*p.offset); + a = vec4(m.a.x, m.a.y, p.x*scale, m.a.w); + b = vec4(m.b.x, m.b.y, p.y*scale, m.b.w); + c = vec4(m.c.x, m.c.y, p.z*scale + 1.0f, m.c.w); + d = vec4(m.d.x, m.d.y, p.offset*scale, m.d.w); + } + + void transform(const vec &in, vec &out) const + { + out = vec(a).mul(in.x).add(vec(b).mul(in.y)).add(vec(c).mul(in.z)).add(vec(d)); + } + + void transform(const vec4 &in, vec &out) const + { + out = vec(a).mul(in.x).add(vec(b).mul(in.y)).add(vec(c).mul(in.z)).add(vec(d).mul(in.w)); + } + + void transform(const vec &in, vec4 &out) const + { + out = vec4(a).mul(in.x).madd(b, in.y).madd(c, in.z).add(d); + } + + void transform(const vec4 &in, vec4 &out) const + { + out = vec4(a).mul(in.x).madd(b, in.y).madd(c, in.z).madd(d, in.w); + } + + template T transform(const U &in) const + { + T v; + transform(in, v); + return v; + } + + template vec perspectivetransform(const T &in) const + { + vec4 v; + transform(in, v); + return vec(v).div(v.w); + } + + void transformnormal(const vec &in, vec &out) const + { + out = vec(a).mul(in.x).add(vec(b).mul(in.y)).add(vec(c).mul(in.z)); + } + + void transformnormal(const vec &in, vec4 &out) const + { + out = vec4(a).mul(in.x).madd(b, in.y).madd(c, in.z); + } + + template T transformnormal(const U &in) const + { + T v; + transformnormal(in, v); + return v; + } + + void transposedtransform(const vec &in, vec &out) const + { + vec p = vec(in).sub(vec(d)); + out.x = a.dot3(p); + out.y = b.dot3(p); + out.z = c.dot3(p); + } + + void transposedtransformnormal(const vec &in, vec &out) const + { + out.x = a.dot3(in); + out.y = b.dot3(in); + out.z = c.dot3(in); + } + + void transposedtransform(const plane &in, plane &out) const + { + out.x = in.dist(a); + out.y = in.dist(b); + out.z = in.dist(c); + out.offset = in.dist(d); + } + + float getscale() const + { + return sqrtf(a.x*a.y + b.x*b.x + c.x*c.x); + } + + vec gettranslation() const + { + return vec(d); + } + + vec4 rowx() const { return vec4(a.x, b.x, c.x, d.x); } + vec4 rowy() const { return vec4(a.y, b.y, c.y, d.y); } + vec4 rowz() const { return vec4(a.z, b.z, c.z, d.z); } + vec4 roww() const { return vec4(a.w, b.w, c.w, d.w); } + + bool invert(const matrix4 &m, double mindet = 1.0e-12); + + vec2 lineardepthscale() const + { + return vec2(d.w, -d.z).div(c.z*d.w - d.z*c.w); + } }; inline matrix3::matrix3(const matrix4 &m) - : a(m.a), b(m.b), c(m.c) + : a(m.a), b(m.b), c(m.c) {} inline matrix4x3::matrix4x3(const matrix4 &m) - : a(m.a), b(m.b), c(m.c), d(m.d) + : a(m.a), b(m.b), c(m.c), d(m.d) {} struct matrix2 { - vec2 a, b; + vec2 a, b; - matrix2() {} - matrix2(const vec2 &a, const vec2 &b) : a(a), b(b) {} - explicit matrix2(const matrix4 &m) : a(m.a), b(m.b) {} - explicit matrix2(const matrix3 &m) : a(m.a), b(m.b) {} + matrix2() {} + matrix2(const vec2 &a, const vec2 &b) : a(a), b(b) {} + explicit matrix2(const matrix4 &m) : a(m.a), b(m.b) {} + explicit matrix2(const matrix3 &m) : a(m.a), b(m.b) {} }; struct squat { - short x, y, z, w; - - squat() {} - squat(const vec4 &q) { convert(q); } - - void convert(const vec4 &q) - { - x = short(q.x*32767.5f-0.5f); - y = short(q.y*32767.5f-0.5f); - z = short(q.z*32767.5f-0.5f); - w = short(q.w*32767.5f-0.5f); - } - - void lerp(const vec4 &a, const vec4 &b, float t) - { - vec4 q; - q.lerp(a, b, t); - convert(q); - } + short x, y, z, w; + + squat() {} + squat(const vec4 &q) { convert(q); } + + void convert(const vec4 &q) + { + x = short(q.x*32767.5f-0.5f); + y = short(q.y*32767.5f-0.5f); + z = short(q.z*32767.5f-0.5f); + w = short(q.w*32767.5f-0.5f); + } + + void lerp(const vec4 &a, const vec4 &b, float t) + { + vec4 q; + q.lerp(a, b, t); + convert(q); + } }; extern bool raysphereintersect(const vec ¢er, float radius, const vec &o, const vec &ray, float &dist); @@ -1816,9 +1816,9 @@ extern bool linecylinderintersect(const vec &from, const vec &to, const vec &sta extern const vec2 sincos360[]; static inline int mod360(int angle) { - if(angle < 0) angle = 360 + (angle <= -360 ? angle%360 : angle); - else if(angle >= 360) angle %= 360; - return angle; + if(angle < 0) angle = 360 + (angle <= -360 ? angle%360 : angle); + else if(angle >= 360) angle %= 360; + return angle; } static inline const vec2 &sincosmod360(int angle) { return sincos360[mod360(angle)]; } static inline float cos360(int angle) { return sincos360[angle].x; } diff --git a/src/shared/glemu.cpp b/src/shared/glemu.cpp index 5658eb8..58e071d 100644 --- a/src/shared/glemu.cpp +++ b/src/shared/glemu.cpp @@ -5,351 +5,351 @@ extern int intel_mapbufferrange_bug; namespace gle { - struct attribinfo - { - int type, size, formatsize, offset; - GLenum format; - - attribinfo() : type(0), size(0), formatsize(0), offset(0), format(GL_FALSE) {} - - bool operator==(const attribinfo &a) const - { - return type == a.type && size == a.size && format == a.format && offset == a.offset; - } - bool operator!=(const attribinfo &a) const - { - return type != a.type || size != a.size || format != a.format || offset != a.offset; - } - }; - - extern const char * const attribnames[MAXATTRIBS] = { "vvertex", "vcolor", "vtexcoord0", "vtexcoord1", "vnormal", "vtangent", "vboneweight", "vboneindex" }; - ucharbuf attribbuf; - static uchar *attribdata; - static attribinfo attribdefs[MAXATTRIBS], lastattribs[MAXATTRIBS]; - int enabled = 0; - static int numattribs = 0, attribmask = 0, numlastattribs = 0, lastattribmask = 0, vertexsize = 0, lastvertexsize = 0; - static GLenum primtype = GL_TRIANGLES; - static uchar *lastbuf = NULL; - static bool changedattribs = false; - static vector multidrawstart; - static vector multidrawcount; - - #define MAXQUADS (0x10000/4) - static GLuint quadindexes = 0; - static bool quadsenabled = false; - - #define MAXVBOSIZE (4*1024*1024) - static GLuint vbo = 0; - static int vbooffset = MAXVBOSIZE; - - static GLuint defaultvao = 0; - - void enablequads() - { - quadsenabled = true; - - if(glversion < 300) return; - - if(quadindexes) - { - glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, quadindexes); - return; - } - - glGenBuffers_(1, &quadindexes); - ushort *data = new ushort[MAXQUADS*6], *dst = data; - for(int idx = 0; idx < MAXQUADS*4; idx += 4, dst += 6) - { - dst[0] = idx; - dst[1] = idx + 1; - dst[2] = idx + 2; - dst[3] = idx + 0; - dst[4] = idx + 2; - dst[5] = idx + 3; - } - glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, quadindexes); - glBufferData_(GL_ELEMENT_ARRAY_BUFFER, MAXQUADS*6*sizeof(ushort), data, GL_STATIC_DRAW); - delete[] data; - } - - void disablequads() - { - quadsenabled = false; - - if(glversion < 300) return; - - glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - void drawquads(int offset, int count) - { - if(count <= 0) return; - if(glversion < 300) - { - glDrawArrays(GL_QUADS, offset*4, count*4); - return; - } - if(offset + count > MAXQUADS) - { - if(offset >= MAXQUADS) return; - count = MAXQUADS - offset; - } - glDrawRangeElements_(GL_TRIANGLES, offset*4, (offset + count)*4-1, count*6, GL_UNSIGNED_SHORT, (ushort *)0 + offset*6); - } - - void defattrib(int type, int size, int format) - { - if(type == ATTRIB_VERTEX) - { - numattribs = attribmask = 0; - vertexsize = 0; - } - changedattribs = true; - attribmask |= 1<= 300 && !intel_mapbufferrange_bug) - { - int len = numverts * vertexsize; - if(vbooffset + len >= MAXVBOSIZE) - { - len = min(len, MAXVBOSIZE); - if(!vbo) glGenBuffers_(1, &vbo); - glBindBuffer_(GL_ARRAY_BUFFER, vbo); - glBufferData_(GL_ARRAY_BUFFER, MAXVBOSIZE, NULL, GL_STREAM_DRAW); - vbooffset = 0; - } - else if(!lastvertexsize) glBindBuffer_(GL_ARRAY_BUFFER, vbo); - void *buf = glMapBufferRange_(GL_ARRAY_BUFFER, vbooffset, len, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); - if(buf) attribbuf.reset((uchar *)buf, len); - } - } - - void multidraw() - { - int start = multidrawstart.length() ? multidrawstart.last() + multidrawcount.last() : 0, - count = attribbuf.length()/vertexsize - start; - if(count > 0) - { - multidrawstart.add(start); - multidrawcount.add(count); - } - } - - int end() - { - uchar *buf = attribbuf.getbuf(); - if(attribbuf.empty()) - { - if(buf != attribdata) - { - glUnmapBuffer_(GL_ARRAY_BUFFER); - attribbuf.reset(attribdata, MAXVBOSIZE); - } - return 0; - } - int start = 0; - if(glversion >= 300) - { - if(buf == attribdata) - { - if(vbooffset + attribbuf.length() >= MAXVBOSIZE) - { - if(!vbo) glGenBuffers_(1, &vbo); - glBindBuffer_(GL_ARRAY_BUFFER, vbo); - glBufferData_(GL_ARRAY_BUFFER, MAXVBOSIZE, NULL, GL_STREAM_DRAW); - vbooffset = 0; - } - else if(!lastvertexsize) glBindBuffer_(GL_ARRAY_BUFFER, vbo); - void *dst = intel_mapbufferrange_bug ? NULL : - glMapBufferRange_(GL_ARRAY_BUFFER, vbooffset, attribbuf.length(), GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); - if(dst) - { - memcpy(dst, attribbuf.getbuf(), attribbuf.length()); - glUnmapBuffer_(GL_ARRAY_BUFFER); - } - else glBufferSubData_(GL_ARRAY_BUFFER, vbooffset, attribbuf.length(), attribbuf.getbuf()); - } - else glUnmapBuffer_(GL_ARRAY_BUFFER); - buf = (uchar *)0 + vbooffset; - if(vertexsize == lastvertexsize && buf >= lastbuf) - { - start = int(buf - lastbuf)/vertexsize; - if(primtype == GL_QUADS && (start%4 || start + attribbuf.length()/vertexsize >= 4*MAXQUADS)) - start = 0; - else buf = lastbuf; - } - vbooffset += attribbuf.length(); - } - setattribs(buf); - int numvertexes = attribbuf.length()/vertexsize; - if(primtype == GL_QUADS) - { - if(!quadsenabled) enablequads(); - for(int quads = numvertexes/4;;) - { - int count = min(quads, MAXQUADS); - drawquads(start/4, count); - quads -= count; - if(quads <= 0) break; - setattribs(buf + 4*count*vertexsize); - start = 0; - } - } - else - { - if(multidrawstart.length()) - { - multidraw(); - if(start) loopv(multidrawstart) multidrawstart[i] += start; - glMultiDrawArrays_(primtype, multidrawstart.getbuf(), multidrawcount.getbuf(), multidrawstart.length()); - multidrawstart.setsize(0); - multidrawcount.setsize(0); - } - else glDrawArrays(primtype, start, numvertexes); - } - attribbuf.reset(attribdata, MAXVBOSIZE); - return numvertexes; - } - - void forcedisable() - { - for(int i = 0; enabled; i++) if(enabled&(1<= 300) glBindBuffer_(GL_ARRAY_BUFFER, 0); - } - - void setup() - { - if(glversion >= 300) - { - if(!defaultvao) glGenVertexArrays_(1, &defaultvao); - glBindVertexArray_(defaultvao); - } - attribdata = new uchar[MAXVBOSIZE]; - attribbuf.reset(attribdata, MAXVBOSIZE); - } - - void cleanup() - { - disable(); - - if(quadindexes) { glDeleteBuffers_(1, &quadindexes); quadindexes = 0; } - - if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } - vbooffset = MAXVBOSIZE; - - if(defaultvao) { glDeleteVertexArrays_(1, &defaultvao); defaultvao = 0; } - } + struct attribinfo + { + int type, size, formatsize, offset; + GLenum format; + + attribinfo() : type(0), size(0), formatsize(0), offset(0), format(GL_FALSE) {} + + bool operator==(const attribinfo &a) const + { + return type == a.type && size == a.size && format == a.format && offset == a.offset; + } + bool operator!=(const attribinfo &a) const + { + return type != a.type || size != a.size || format != a.format || offset != a.offset; + } + }; + + extern const char * const attribnames[MAXATTRIBS] = { "vvertex", "vcolor", "vtexcoord0", "vtexcoord1", "vnormal", "vtangent", "vboneweight", "vboneindex" }; + ucharbuf attribbuf; + static uchar *attribdata; + static attribinfo attribdefs[MAXATTRIBS], lastattribs[MAXATTRIBS]; + int enabled = 0; + static int numattribs = 0, attribmask = 0, numlastattribs = 0, lastattribmask = 0, vertexsize = 0, lastvertexsize = 0; + static GLenum primtype = GL_TRIANGLES; + static uchar *lastbuf = NULL; + static bool changedattribs = false; + static vector multidrawstart; + static vector multidrawcount; + + #define MAXQUADS (0x10000/4) + static GLuint quadindexes = 0; + static bool quadsenabled = false; + + #define MAXVBOSIZE (4*1024*1024) + static GLuint vbo = 0; + static int vbooffset = MAXVBOSIZE; + + static GLuint defaultvao = 0; + + void enablequads() + { + quadsenabled = true; + + if(glversion < 300) return; + + if(quadindexes) + { + glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, quadindexes); + return; + } + + glGenBuffers_(1, &quadindexes); + ushort *data = new ushort[MAXQUADS*6], *dst = data; + for(int idx = 0; idx < MAXQUADS*4; idx += 4, dst += 6) + { + dst[0] = idx; + dst[1] = idx + 1; + dst[2] = idx + 2; + dst[3] = idx + 0; + dst[4] = idx + 2; + dst[5] = idx + 3; + } + glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, quadindexes); + glBufferData_(GL_ELEMENT_ARRAY_BUFFER, MAXQUADS*6*sizeof(ushort), data, GL_STATIC_DRAW); + delete[] data; + } + + void disablequads() + { + quadsenabled = false; + + if(glversion < 300) return; + + glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + void drawquads(int offset, int count) + { + if(count <= 0) return; + if(glversion < 300) + { + glDrawArrays(GL_QUADS, offset*4, count*4); + return; + } + if(offset + count > MAXQUADS) + { + if(offset >= MAXQUADS) return; + count = MAXQUADS - offset; + } + glDrawRangeElements_(GL_TRIANGLES, offset*4, (offset + count)*4-1, count*6, GL_UNSIGNED_SHORT, (ushort *)0 + offset*6); + } + + void defattrib(int type, int size, int format) + { + if(type == ATTRIB_VERTEX) + { + numattribs = attribmask = 0; + vertexsize = 0; + } + changedattribs = true; + attribmask |= 1<= 300 && !intel_mapbufferrange_bug) + { + int len = numverts * vertexsize; + if(vbooffset + len >= MAXVBOSIZE) + { + len = min(len, MAXVBOSIZE); + if(!vbo) glGenBuffers_(1, &vbo); + glBindBuffer_(GL_ARRAY_BUFFER, vbo); + glBufferData_(GL_ARRAY_BUFFER, MAXVBOSIZE, NULL, GL_STREAM_DRAW); + vbooffset = 0; + } + else if(!lastvertexsize) glBindBuffer_(GL_ARRAY_BUFFER, vbo); + void *buf = glMapBufferRange_(GL_ARRAY_BUFFER, vbooffset, len, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); + if(buf) attribbuf.reset((uchar *)buf, len); + } + } + + void multidraw() + { + int start = multidrawstart.length() ? multidrawstart.last() + multidrawcount.last() : 0, + count = attribbuf.length()/vertexsize - start; + if(count > 0) + { + multidrawstart.add(start); + multidrawcount.add(count); + } + } + + int end() + { + uchar *buf = attribbuf.getbuf(); + if(attribbuf.empty()) + { + if(buf != attribdata) + { + glUnmapBuffer_(GL_ARRAY_BUFFER); + attribbuf.reset(attribdata, MAXVBOSIZE); + } + return 0; + } + int start = 0; + if(glversion >= 300) + { + if(buf == attribdata) + { + if(vbooffset + attribbuf.length() >= MAXVBOSIZE) + { + if(!vbo) glGenBuffers_(1, &vbo); + glBindBuffer_(GL_ARRAY_BUFFER, vbo); + glBufferData_(GL_ARRAY_BUFFER, MAXVBOSIZE, NULL, GL_STREAM_DRAW); + vbooffset = 0; + } + else if(!lastvertexsize) glBindBuffer_(GL_ARRAY_BUFFER, vbo); + void *dst = intel_mapbufferrange_bug ? NULL : + glMapBufferRange_(GL_ARRAY_BUFFER, vbooffset, attribbuf.length(), GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); + if(dst) + { + memcpy(dst, attribbuf.getbuf(), attribbuf.length()); + glUnmapBuffer_(GL_ARRAY_BUFFER); + } + else glBufferSubData_(GL_ARRAY_BUFFER, vbooffset, attribbuf.length(), attribbuf.getbuf()); + } + else glUnmapBuffer_(GL_ARRAY_BUFFER); + buf = (uchar *)0 + vbooffset; + if(vertexsize == lastvertexsize && buf >= lastbuf) + { + start = int(buf - lastbuf)/vertexsize; + if(primtype == GL_QUADS && (start%4 || start + attribbuf.length()/vertexsize >= 4*MAXQUADS)) + start = 0; + else buf = lastbuf; + } + vbooffset += attribbuf.length(); + } + setattribs(buf); + int numvertexes = attribbuf.length()/vertexsize; + if(primtype == GL_QUADS) + { + if(!quadsenabled) enablequads(); + for(int quads = numvertexes/4;;) + { + int count = min(quads, MAXQUADS); + drawquads(start/4, count); + quads -= count; + if(quads <= 0) break; + setattribs(buf + 4*count*vertexsize); + start = 0; + } + } + else + { + if(multidrawstart.length()) + { + multidraw(); + if(start) loopv(multidrawstart) multidrawstart[i] += start; + glMultiDrawArrays_(primtype, multidrawstart.getbuf(), multidrawcount.getbuf(), multidrawstart.length()); + multidrawstart.setsize(0); + multidrawcount.setsize(0); + } + else glDrawArrays(primtype, start, numvertexes); + } + attribbuf.reset(attribdata, MAXVBOSIZE); + return numvertexes; + } + + void forcedisable() + { + for(int i = 0; enabled; i++) if(enabled&(1<= 300) glBindBuffer_(GL_ARRAY_BUFFER, 0); + } + + void setup() + { + if(glversion >= 300) + { + if(!defaultvao) glGenVertexArrays_(1, &defaultvao); + glBindVertexArray_(defaultvao); + } + attribdata = new uchar[MAXVBOSIZE]; + attribbuf.reset(attribdata, MAXVBOSIZE); + } + + void cleanup() + { + disable(); + + if(quadindexes) { glDeleteBuffers_(1, &quadindexes); quadindexes = 0; } + + if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } + vbooffset = MAXVBOSIZE; + + if(defaultvao) { glDeleteVertexArrays_(1, &defaultvao); defaultvao = 0; } + } } diff --git a/src/shared/glemu.h b/src/shared/glemu.h index 94af8f9..e4f4d26 100644 --- a/src/shared/glemu.h +++ b/src/shared/glemu.h @@ -1,180 +1,180 @@ namespace gle { - enum - { - ATTRIB_VERTEX = 0, - ATTRIB_COLOR = 1, - ATTRIB_TEXCOORD0 = 2, - ATTRIB_TEXCOORD1 = 3, - ATTRIB_NORMAL = 4, - ATTRIB_TANGENT = 5, - ATTRIB_BONEWEIGHT = 6, - ATTRIB_BONEINDEX = 7, - MAXATTRIBS = 8 - }; - - extern const char * const attribnames[MAXATTRIBS]; - extern ucharbuf attribbuf; - - extern int enabled; - extern void forcedisable(); - static inline void disable() { if(enabled) forcedisable(); } - - extern void begin(GLenum mode); - extern void begin(GLenum mode, int numverts); - extern void defattribs(const char *fmt); - extern void defattrib(int type, int size, int format); - - #define GLE_DEFATTRIB(name, type, defaultsize, defaultformat) \ - static inline void def##name(int size = defaultsize, int format = defaultformat) { defattrib(type, size, format); } - - GLE_DEFATTRIB(vertex, ATTRIB_VERTEX, 3, GL_FLOAT) - GLE_DEFATTRIB(color, ATTRIB_COLOR, 3, GL_FLOAT) - GLE_DEFATTRIB(texcoord0, ATTRIB_TEXCOORD0, 2, GL_FLOAT) - GLE_DEFATTRIB(texcoord1, ATTRIB_TEXCOORD1, 2, GL_FLOAT) - GLE_DEFATTRIB(normal, ATTRIB_NORMAL, 3, GL_FLOAT) - GLE_DEFATTRIB(tangent, ATTRIB_TANGENT, 4, GL_FLOAT) - GLE_DEFATTRIB(boneweight, ATTRIB_BONEWEIGHT, 4, GL_UNSIGNED_BYTE) - GLE_DEFATTRIB(boneindex, ATTRIB_BONEINDEX, 4, GL_UNSIGNED_BYTE) - - #define GLE_INITATTRIB(name, index, suffix, type) \ - static inline void name##suffix(type x) { glVertexAttrib1##suffix##_(index, x); } \ - static inline void name##suffix(type x, type y) { glVertexAttrib2##suffix##_(index, x, y); } \ - static inline void name##suffix(type x, type y, type z) { glVertexAttrib3##suffix##_(index, x, y, z); } \ - static inline void name##suffix(type x, type y, type z, type w) { glVertexAttrib4##suffix##_(index, x, y, z, w); } - #define GLE_INITATTRIBF(name, index) \ - GLE_INITATTRIB(name, index, f, float) \ - static inline void name(const vec &v) { glVertexAttrib3fv_(index, v.v); } \ - static inline void name(const vec &v, float w) { glVertexAttrib4f_(index, v.x, v.y, v.z, w); } \ - static inline void name(const vec2 &v) { glVertexAttrib2fv_(index, v.v); } \ - static inline void name(const vec4 &v) { glVertexAttrib4fv_(index, v.v); } - #define GLE_INITATTRIBN(name, index, suffix, type, defaultw) \ - static inline void name##suffix(type x, type y, type z, type w = defaultw) { glVertexAttrib4N##suffix##_(index, x, y, z, w); } - - GLE_INITATTRIBF(vertex, ATTRIB_VERTEX) - GLE_INITATTRIBF(color, ATTRIB_COLOR) - static inline void color(const bvec4 &v) { glVertexAttrib4Nubv_(ATTRIB_COLOR, v.v); } - static inline void color(const bvec &v, uchar alpha = 255) { color(bvec4(v, alpha)); } - static inline void colorub(uchar x, uchar y, uchar z, uchar w = 255) { color(bvec4(x, y, z, w)); } - GLE_INITATTRIBF(texcoord0, ATTRIB_TEXCOORD0) - GLE_INITATTRIBF(texcoord1, ATTRIB_TEXCOORD1) - static inline void normal(float x, float y, float z) { glVertexAttrib4f_(ATTRIB_NORMAL, x, y, z, 0.0f); } - static inline void normal(const vec &v) { glVertexAttrib4f_(ATTRIB_NORMAL, v.x, v.y, v.z, 0.0f); } - static inline void tangent(float x, float y, float z, float w = 1.0f) { glVertexAttrib4f_(ATTRIB_TANGENT, x, y, z, w); } - static inline void tangent(const vec &v, float w = 1.0f) { glVertexAttrib4f_(ATTRIB_TANGENT, v.x, v.y, v.z, w); } - static inline void tangent(const vec4 &v) { glVertexAttrib4fv_(ATTRIB_TANGENT, v.v); } - - #define GLE_ATTRIBPOINTER(name, index, defaultnormalized, defaultsize, defaulttype, prepare) \ - static inline void enable##name() { prepare; glEnableVertexAttribArray_(index); } \ - static inline void disable##name() { glDisableVertexAttribArray_(index); } \ - static inline void name##pointer(int stride, const void *data, GLenum type = defaulttype, int size = defaultsize, GLenum normalized = defaultnormalized) { \ - prepare; \ - glVertexAttribPointer_(index, size, type, normalized, stride, data); \ - } - - static inline void enableattrib(int index) { disable(); glEnableVertexAttribArray_(index); } - static inline void disableattrib(int index) { glDisableVertexAttribArray_(index); } - GLE_ATTRIBPOINTER(vertex, ATTRIB_VERTEX, GL_FALSE, 3, GL_FLOAT, disable()) - GLE_ATTRIBPOINTER(color, ATTRIB_COLOR, GL_TRUE, 4, GL_UNSIGNED_BYTE, ) - GLE_ATTRIBPOINTER(texcoord0, ATTRIB_TEXCOORD0, GL_FALSE, 2, GL_FLOAT, ) - GLE_ATTRIBPOINTER(texcoord1, ATTRIB_TEXCOORD1, GL_FALSE, 2, GL_FLOAT, ) - GLE_ATTRIBPOINTER(normal, ATTRIB_NORMAL, GL_TRUE, 3, GL_FLOAT, ) - GLE_ATTRIBPOINTER(tangent, ATTRIB_TANGENT, GL_TRUE, 4, GL_FLOAT, ) - GLE_ATTRIBPOINTER(boneweight, ATTRIB_BONEWEIGHT, GL_TRUE, 4, GL_UNSIGNED_BYTE, ) - GLE_ATTRIBPOINTER(boneindex, ATTRIB_BONEINDEX, GL_FALSE, 4, GL_UNSIGNED_BYTE, ) - - static inline void bindebo(GLuint ebo) { disable(); glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, ebo); } - static inline void clearebo() { glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0); } - static inline void bindvbo(GLuint vbo) { disable(); glBindBuffer_(GL_ARRAY_BUFFER, vbo); } - static inline void clearvbo() { glBindBuffer_(GL_ARRAY_BUFFER, 0); } - - template - static inline void attrib(T x) - { - if(attribbuf.check(sizeof(T))) - { - T *buf = (T *)attribbuf.pad(sizeof(T)); - buf[0] = x; - } - } - - template - static inline void attrib(T x, T y) - { - if(attribbuf.check(2*sizeof(T))) - { - T *buf = (T *)attribbuf.pad(2*sizeof(T)); - buf[0] = x; - buf[1] = y; - } - } - - template - static inline void attrib(T x, T y, T z) - { - if(attribbuf.check(3*sizeof(T))) - { - T *buf = (T *)attribbuf.pad(3*sizeof(T)); - buf[0] = x; - buf[1] = y; - buf[2] = z; - } - } - - template - static inline void attrib(T x, T y, T z, T w) - { - if(attribbuf.check(4*sizeof(T))) - { - T *buf = (T *)attribbuf.pad(4*sizeof(T)); - buf[0] = x; - buf[1] = y; - buf[2] = z; - buf[3] = w; - } - } - - template - static inline void attribv(const T *v) - { - attribbuf.put((const uchar *)v, N*sizeof(T)); - } - - #define GLE_ATTRIB(suffix, type) \ - static inline void attrib##suffix(type x) { attrib(x); } \ - static inline void attrib##suffix(type x, type y) { attrib(x, y); } \ - static inline void attrib##suffix(type x, type y, type z) { attrib(x, y, z); } \ - static inline void attrib##suffix(type x, type y, type z, type w) { attrib(x, y, z, w); } - - GLE_ATTRIB(f, float) - GLE_ATTRIB(d, double) - GLE_ATTRIB(b, char) - GLE_ATTRIB(ub, uchar) - GLE_ATTRIB(s, short) - GLE_ATTRIB(us, ushort) - GLE_ATTRIB(i, int) - GLE_ATTRIB(ui, uint) - - static inline void attrib(const vec &v) { attribf(v.x, v.y, v.z); } - static inline void attrib(const vec &v, float w) { attribf(v.x, v.y, v.z, w); } - static inline void attrib(const vec2 &v) { attribf(v.x, v.y); } - static inline void attrib(const vec4 &v) { attribf(v.x, v.y, v.z, v.w); } - static inline void attrib(const ivec &v) { attribi(v.x, v.y, v.z); } - static inline void attrib(const ivec &v, int w) { attribi(v.x, v.y, v.z, w); } - static inline void attrib(const ivec2 &v) { attribi(v.x, v.y); } - static inline void attrib(const ivec4 &v) { attribi(v.x, v.y, v.z, v.w); } - static inline void attrib(const bvec &b) { attribub(b.x, b.y, b.z); } - static inline void attrib(const bvec &b, uchar w) { attribub(b.x, b.y, b.z, w); } - static inline void attrib(const bvec4 &b) { attribub(b.x, b.y, b.z, b.w); } - - extern void multidraw(); - extern int end(); - - extern void enablequads(); - extern void disablequads(); - extern void drawquads(int offset, int count); - - extern void setup(); - extern void cleanup(); + enum + { + ATTRIB_VERTEX = 0, + ATTRIB_COLOR = 1, + ATTRIB_TEXCOORD0 = 2, + ATTRIB_TEXCOORD1 = 3, + ATTRIB_NORMAL = 4, + ATTRIB_TANGENT = 5, + ATTRIB_BONEWEIGHT = 6, + ATTRIB_BONEINDEX = 7, + MAXATTRIBS = 8 + }; + + extern const char * const attribnames[MAXATTRIBS]; + extern ucharbuf attribbuf; + + extern int enabled; + extern void forcedisable(); + static inline void disable() { if(enabled) forcedisable(); } + + extern void begin(GLenum mode); + extern void begin(GLenum mode, int numverts); + extern void defattribs(const char *fmt); + extern void defattrib(int type, int size, int format); + + #define GLE_DEFATTRIB(name, type, defaultsize, defaultformat) \ + static inline void def##name(int size = defaultsize, int format = defaultformat) { defattrib(type, size, format); } + + GLE_DEFATTRIB(vertex, ATTRIB_VERTEX, 3, GL_FLOAT) + GLE_DEFATTRIB(color, ATTRIB_COLOR, 3, GL_FLOAT) + GLE_DEFATTRIB(texcoord0, ATTRIB_TEXCOORD0, 2, GL_FLOAT) + GLE_DEFATTRIB(texcoord1, ATTRIB_TEXCOORD1, 2, GL_FLOAT) + GLE_DEFATTRIB(normal, ATTRIB_NORMAL, 3, GL_FLOAT) + GLE_DEFATTRIB(tangent, ATTRIB_TANGENT, 4, GL_FLOAT) + GLE_DEFATTRIB(boneweight, ATTRIB_BONEWEIGHT, 4, GL_UNSIGNED_BYTE) + GLE_DEFATTRIB(boneindex, ATTRIB_BONEINDEX, 4, GL_UNSIGNED_BYTE) + + #define GLE_INITATTRIB(name, index, suffix, type) \ + static inline void name##suffix(type x) { glVertexAttrib1##suffix##_(index, x); } \ + static inline void name##suffix(type x, type y) { glVertexAttrib2##suffix##_(index, x, y); } \ + static inline void name##suffix(type x, type y, type z) { glVertexAttrib3##suffix##_(index, x, y, z); } \ + static inline void name##suffix(type x, type y, type z, type w) { glVertexAttrib4##suffix##_(index, x, y, z, w); } + #define GLE_INITATTRIBF(name, index) \ + GLE_INITATTRIB(name, index, f, float) \ + static inline void name(const vec &v) { glVertexAttrib3fv_(index, v.v); } \ + static inline void name(const vec &v, float w) { glVertexAttrib4f_(index, v.x, v.y, v.z, w); } \ + static inline void name(const vec2 &v) { glVertexAttrib2fv_(index, v.v); } \ + static inline void name(const vec4 &v) { glVertexAttrib4fv_(index, v.v); } + #define GLE_INITATTRIBN(name, index, suffix, type, defaultw) \ + static inline void name##suffix(type x, type y, type z, type w = defaultw) { glVertexAttrib4N##suffix##_(index, x, y, z, w); } + + GLE_INITATTRIBF(vertex, ATTRIB_VERTEX) + GLE_INITATTRIBF(color, ATTRIB_COLOR) + static inline void color(const bvec4 &v) { glVertexAttrib4Nubv_(ATTRIB_COLOR, v.v); } + static inline void color(const bvec &v, uchar alpha = 255) { color(bvec4(v, alpha)); } + static inline void colorub(uchar x, uchar y, uchar z, uchar w = 255) { color(bvec4(x, y, z, w)); } + GLE_INITATTRIBF(texcoord0, ATTRIB_TEXCOORD0) + GLE_INITATTRIBF(texcoord1, ATTRIB_TEXCOORD1) + static inline void normal(float x, float y, float z) { glVertexAttrib4f_(ATTRIB_NORMAL, x, y, z, 0.0f); } + static inline void normal(const vec &v) { glVertexAttrib4f_(ATTRIB_NORMAL, v.x, v.y, v.z, 0.0f); } + static inline void tangent(float x, float y, float z, float w = 1.0f) { glVertexAttrib4f_(ATTRIB_TANGENT, x, y, z, w); } + static inline void tangent(const vec &v, float w = 1.0f) { glVertexAttrib4f_(ATTRIB_TANGENT, v.x, v.y, v.z, w); } + static inline void tangent(const vec4 &v) { glVertexAttrib4fv_(ATTRIB_TANGENT, v.v); } + + #define GLE_ATTRIBPOINTER(name, index, defaultnormalized, defaultsize, defaulttype, prepare) \ + static inline void enable##name() { prepare; glEnableVertexAttribArray_(index); } \ + static inline void disable##name() { glDisableVertexAttribArray_(index); } \ + static inline void name##pointer(int stride, const void *data, GLenum type = defaulttype, int size = defaultsize, GLenum normalized = defaultnormalized) { \ + prepare; \ + glVertexAttribPointer_(index, size, type, normalized, stride, data); \ + } + + static inline void enableattrib(int index) { disable(); glEnableVertexAttribArray_(index); } + static inline void disableattrib(int index) { glDisableVertexAttribArray_(index); } + GLE_ATTRIBPOINTER(vertex, ATTRIB_VERTEX, GL_FALSE, 3, GL_FLOAT, disable()) + GLE_ATTRIBPOINTER(color, ATTRIB_COLOR, GL_TRUE, 4, GL_UNSIGNED_BYTE, ) + GLE_ATTRIBPOINTER(texcoord0, ATTRIB_TEXCOORD0, GL_FALSE, 2, GL_FLOAT, ) + GLE_ATTRIBPOINTER(texcoord1, ATTRIB_TEXCOORD1, GL_FALSE, 2, GL_FLOAT, ) + GLE_ATTRIBPOINTER(normal, ATTRIB_NORMAL, GL_TRUE, 3, GL_FLOAT, ) + GLE_ATTRIBPOINTER(tangent, ATTRIB_TANGENT, GL_TRUE, 4, GL_FLOAT, ) + GLE_ATTRIBPOINTER(boneweight, ATTRIB_BONEWEIGHT, GL_TRUE, 4, GL_UNSIGNED_BYTE, ) + GLE_ATTRIBPOINTER(boneindex, ATTRIB_BONEINDEX, GL_FALSE, 4, GL_UNSIGNED_BYTE, ) + + static inline void bindebo(GLuint ebo) { disable(); glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, ebo); } + static inline void clearebo() { glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, 0); } + static inline void bindvbo(GLuint vbo) { disable(); glBindBuffer_(GL_ARRAY_BUFFER, vbo); } + static inline void clearvbo() { glBindBuffer_(GL_ARRAY_BUFFER, 0); } + + template + static inline void attrib(T x) + { + if(attribbuf.check(sizeof(T))) + { + T *buf = (T *)attribbuf.pad(sizeof(T)); + buf[0] = x; + } + } + + template + static inline void attrib(T x, T y) + { + if(attribbuf.check(2*sizeof(T))) + { + T *buf = (T *)attribbuf.pad(2*sizeof(T)); + buf[0] = x; + buf[1] = y; + } + } + + template + static inline void attrib(T x, T y, T z) + { + if(attribbuf.check(3*sizeof(T))) + { + T *buf = (T *)attribbuf.pad(3*sizeof(T)); + buf[0] = x; + buf[1] = y; + buf[2] = z; + } + } + + template + static inline void attrib(T x, T y, T z, T w) + { + if(attribbuf.check(4*sizeof(T))) + { + T *buf = (T *)attribbuf.pad(4*sizeof(T)); + buf[0] = x; + buf[1] = y; + buf[2] = z; + buf[3] = w; + } + } + + template + static inline void attribv(const T *v) + { + attribbuf.put((const uchar *)v, N*sizeof(T)); + } + + #define GLE_ATTRIB(suffix, type) \ + static inline void attrib##suffix(type x) { attrib(x); } \ + static inline void attrib##suffix(type x, type y) { attrib(x, y); } \ + static inline void attrib##suffix(type x, type y, type z) { attrib(x, y, z); } \ + static inline void attrib##suffix(type x, type y, type z, type w) { attrib(x, y, z, w); } + + GLE_ATTRIB(f, float) + GLE_ATTRIB(d, double) + GLE_ATTRIB(b, char) + GLE_ATTRIB(ub, uchar) + GLE_ATTRIB(s, short) + GLE_ATTRIB(us, ushort) + GLE_ATTRIB(i, int) + GLE_ATTRIB(ui, uint) + + static inline void attrib(const vec &v) { attribf(v.x, v.y, v.z); } + static inline void attrib(const vec &v, float w) { attribf(v.x, v.y, v.z, w); } + static inline void attrib(const vec2 &v) { attribf(v.x, v.y); } + static inline void attrib(const vec4 &v) { attribf(v.x, v.y, v.z, v.w); } + static inline void attrib(const ivec &v) { attribi(v.x, v.y, v.z); } + static inline void attrib(const ivec &v, int w) { attribi(v.x, v.y, v.z, w); } + static inline void attrib(const ivec2 &v) { attribi(v.x, v.y); } + static inline void attrib(const ivec4 &v) { attribi(v.x, v.y, v.z, v.w); } + static inline void attrib(const bvec &b) { attribub(b.x, b.y, b.z); } + static inline void attrib(const bvec &b, uchar w) { attribub(b.x, b.y, b.z, w); } + static inline void attrib(const bvec4 &b) { attribub(b.x, b.y, b.z, b.w); } + + extern void multidraw(); + extern int end(); + + extern void enablequads(); + extern void disablequads(); + extern void drawquads(int offset, int count); + + extern void setup(); + extern void cleanup(); } diff --git a/src/shared/glexts.h b/src/shared/glexts.h index 0b8e823..edc9e04 100644 --- a/src/shared/glexts.h +++ b/src/shared/glexts.h @@ -7,50 +7,49 @@ // OpenGL deprecated functionality #ifndef GL_QUADS -#define GL_QUADS 0x0007 +#define GL_QUADS 0x0007 #endif #ifndef GL_ALPHA -#define GL_ALPHA 0x1906 +#define GL_ALPHA 0x1906 #endif #ifndef GL_ALPHA8 -#define GL_ALPHA8 0x803C +#define GL_ALPHA8 0x803C #endif #ifndef GL_ALPHA16 -#define GL_ALPHA16 0x803E +#define GL_ALPHA16 0x803E #endif #ifndef GL_COMPRESSED_ALPHA -#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_ALPHA 0x84E9 #endif #ifndef GL_LUMINANCE -#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE 0x1909 #endif #ifndef GL_LUMINANCE8 -#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE8 0x8040 #endif #ifndef GL_LUMINANCE16 -#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE16 0x8042 #endif #ifndef GL_COMPRESSED_LUMINANCE -#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE 0x84EA #endif #ifndef GL_LUMINANCE_ALPHA -#define GL_LUMINANCE_ALPHA 0x190A +#define GL_LUMINANCE_ALPHA 0x190A #endif #ifndef GL_LUMINANCE8_ALPHA8 -#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE8_ALPHA8 0x8045 #endif #ifndef GL_LUMINANCE16_ALPHA16 -#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_LUMINANCE16_ALPHA16 0x8048 #endif #ifndef GL_COMPRESSED_LUMINANCE_ALPHA #define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB #endif // OpenGL 1.3 -#ifndef WIN32 #define glActiveTexture_ glActiveTexture #define glBlendEquation_ glBlendEquation @@ -67,122 +66,7 @@ #define glGetCompressedTexImage_ glGetCompressedTexImage #define glDrawRangeElements_ glDrawRangeElements -#else -extern PFNGLACTIVETEXTUREPROC glActiveTexture_; -extern PFNGLBLENDEQUATIONPROC glBlendEquation_; -extern PFNGLBLENDCOLORPROC glBlendColor_; - -extern PFNGLTEXIMAGE3DPROC glTexImage3D_; -extern PFNGLTEXSUBIMAGE3DPROC glTexSubImage3D_; -extern PFNGLCOPYTEXSUBIMAGE3DPROC glCopyTexSubImage3D_; - -extern PFNGLCOMPRESSEDTEXIMAGE3DPROC glCompressedTexImage3D_; -extern PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D_; -extern PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glCompressedTexSubImage3D_; -extern PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glCompressedTexSubImage2D_; -extern PFNGLGETCOMPRESSEDTEXIMAGEPROC glGetCompressedTexImage_; - -extern PFNGLDRAWRANGEELEMENTSPROC glDrawRangeElements_; -#endif - -// OpenGL 2.0 -#ifdef __APPLE__ -#define glMultiDrawArrays_ glMultiDrawArrays -#define glMultiDrawElements_ glMultiDrawElements - -#define glBlendFuncSeparate_ glBlendFuncSeparate -#define glBlendEquationSeparate_ glBlendEquationSeparate -#define glStencilOpSeparate_ glStencilOpSeparate -#define glStencilFuncSeparate_ glStencilFuncSeparate -#define glStencilMaskSeparate_ glStencilMaskSeparate - -#define glGenBuffers_ glGenBuffers -#define glBindBuffer_ glBindBuffer -#define glMapBuffer_ glMapBuffer -#define glUnmapBuffer_ glUnmapBuffer -#define glBufferData_ glBufferData -#define glBufferSubData_ glBufferSubData -#define glDeleteBuffers_ glDeleteBuffers -#define glGetBufferSubData_ glGetBufferSubData - -#define glGenQueries_ glGenQueries -#define glDeleteQueries_ glDeleteQueries -#define glBeginQuery_ glBeginQuery -#define glEndQuery_ glEndQuery -#define glGetQueryiv_ glGetQueryiv -#define glGetQueryObjectiv_ glGetQueryObjectiv -#define glGetQueryObjectuiv_ glGetQueryObjectuiv - -#define glCreateProgram_ glCreateProgram -#define glDeleteProgram_ glDeleteProgram -#define glUseProgram_ glUseProgram -#define glCreateShader_ glCreateShader -#define glDeleteShader_ glDeleteShader -#define glShaderSource_ glShaderSource -#define glCompileShader_ glCompileShader -#define glGetShaderiv_ glGetShaderiv -#define glGetProgramiv_ glGetProgramiv -#define glAttachShader_ glAttachShader -#define glGetProgramInfoLog_ glGetProgramInfoLog -#define glGetShaderInfoLog_ glGetShaderInfoLog -#define glLinkProgram_ glLinkProgram -#define glGetUniformLocation_ glGetUniformLocation -#define glUniform1f_ glUniform1f -#define glUniform2f_ glUniform2f -#define glUniform3f_ glUniform3f -#define glUniform4f_ glUniform4f -#define glUniform1fv_ glUniform1fv -#define glUniform2fv_ glUniform2fv -#define glUniform3fv_ glUniform3fv -#define glUniform4fv_ glUniform4fv -#define glUniform1i_ glUniform1i -#define glUniform2i_ glUniform2i -#define glUniform3i_ glUniform3i -#define glUniform4i_ glUniform4i -#define glUniform1iv_ glUniform1iv -#define glUniform2iv_ glUniform2iv -#define glUniform3iv_ glUniform3iv -#define glUniform4iv_ glUniform4iv -#define glUniformMatrix2fv_ glUniformMatrix2fv -#define glUniformMatrix3fv_ glUniformMatrix3fv -#define glUniformMatrix4fv_ glUniformMatrix4fv -#define glBindAttribLocation_ glBindAttribLocation -#define glGetActiveUniform_ glGetActiveUniform -#define glEnableVertexAttribArray_ glEnableVertexAttribArray -#define glDisableVertexAttribArray_ glDisableVertexAttribArray - -#define glVertexAttrib1f_ glVertexAttrib1f -#define glVertexAttrib1fv_ glVertexAttrib1fv -#define glVertexAttrib1s_ glVertexAttrib1s -#define glVertexAttrib1sv_ glVertexAttrib1sv -#define glVertexAttrib2f_ glVertexAttrib2f -#define glVertexAttrib2fv_ glVertexAttrib2fv -#define glVertexAttrib2s_ glVertexAttrib2s -#define glVertexAttrib2sv_ glVertexAttrib2sv -#define glVertexAttrib3f_ glVertexAttrib3f -#define glVertexAttrib3fv_ glVertexAttrib3fv -#define glVertexAttrib3s_ glVertexAttrib3s -#define glVertexAttrib3sv_ glVertexAttrib3sv -#define glVertexAttrib4f_ glVertexAttrib4f -#define glVertexAttrib4fv_ glVertexAttrib4fv -#define glVertexAttrib4s_ glVertexAttrib4s -#define glVertexAttrib4sv_ glVertexAttrib4sv -#define glVertexAttrib4bv_ glVertexAttrib4bv -#define glVertexAttrib4iv_ glVertexAttrib4iv -#define glVertexAttrib4ubv_ glVertexAttrib4ubv -#define glVertexAttrib4uiv_ glVertexAttrib4uiv -#define glVertexAttrib4usv_ glVertexAttrib4usv -#define glVertexAttrib4Nbv_ glVertexAttrib4Nbv -#define glVertexAttrib4Niv_ glVertexAttrib4Niv -#define glVertexAttrib4Nub_ glVertexAttrib4Nub -#define glVertexAttrib4Nubv_ glVertexAttrib4Nubv -#define glVertexAttrib4Nuiv_ glVertexAttrib4Nuiv -#define glVertexAttrib4Nusv_ glVertexAttrib4Nusv -#define glVertexAttribPointer_ glVertexAttribPointer - -#define glDrawBuffers_ glDrawBuffers -#else extern PFNGLMULTIDRAWARRAYSPROC glMultiDrawArrays_; extern PFNGLMULTIDRAWELEMENTSPROC glMultiDrawElements_; @@ -192,102 +76,101 @@ extern PFNGLSTENCILOPSEPARATEPROC glStencilOpSeparate_; extern PFNGLSTENCILFUNCSEPARATEPROC glStencilFuncSeparate_; extern PFNGLSTENCILMASKSEPARATEPROC glStencilMaskSeparate_; -extern PFNGLGENBUFFERSPROC glGenBuffers_; -extern PFNGLBINDBUFFERPROC glBindBuffer_; -extern PFNGLMAPBUFFERPROC glMapBuffer_; -extern PFNGLUNMAPBUFFERPROC glUnmapBuffer_; -extern PFNGLBUFFERDATAPROC glBufferData_; -extern PFNGLBUFFERSUBDATAPROC glBufferSubData_; -extern PFNGLDELETEBUFFERSPROC glDeleteBuffers_; +extern PFNGLGENBUFFERSPROC glGenBuffers_; +extern PFNGLBINDBUFFERPROC glBindBuffer_; +extern PFNGLMAPBUFFERPROC glMapBuffer_; +extern PFNGLUNMAPBUFFERPROC glUnmapBuffer_; +extern PFNGLBUFFERDATAPROC glBufferData_; +extern PFNGLBUFFERSUBDATAPROC glBufferSubData_; +extern PFNGLDELETEBUFFERSPROC glDeleteBuffers_; extern PFNGLGETBUFFERSUBDATAPROC glGetBufferSubData_; -extern PFNGLGENQUERIESPROC glGenQueries_; -extern PFNGLDELETEQUERIESPROC glDeleteQueries_; -extern PFNGLBEGINQUERYPROC glBeginQuery_; -extern PFNGLENDQUERYPROC glEndQuery_; -extern PFNGLGETQUERYIVPROC glGetQueryiv_; +extern PFNGLGENQUERIESPROC glGenQueries_; +extern PFNGLDELETEQUERIESPROC glDeleteQueries_; +extern PFNGLBEGINQUERYPROC glBeginQuery_; +extern PFNGLENDQUERYPROC glEndQuery_; +extern PFNGLGETQUERYIVPROC glGetQueryiv_; extern PFNGLGETQUERYOBJECTIVPROC glGetQueryObjectiv_; extern PFNGLGETQUERYOBJECTUIVPROC glGetQueryObjectuiv_; -extern PFNGLCREATEPROGRAMPROC glCreateProgram_; -extern PFNGLDELETEPROGRAMPROC glDeleteProgram_; -extern PFNGLUSEPROGRAMPROC glUseProgram_; -extern PFNGLCREATESHADERPROC glCreateShader_; -extern PFNGLDELETESHADERPROC glDeleteShader_; -extern PFNGLSHADERSOURCEPROC glShaderSource_; -extern PFNGLCOMPILESHADERPROC glCompileShader_; -extern PFNGLGETSHADERIVPROC glGetShaderiv_; -extern PFNGLGETPROGRAMIVPROC glGetProgramiv_; -extern PFNGLATTACHSHADERPROC glAttachShader_; -extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog_; -extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog_; -extern PFNGLLINKPROGRAMPROC glLinkProgram_; -extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation_; -extern PFNGLUNIFORM1FPROC glUniform1f_; -extern PFNGLUNIFORM2FPROC glUniform2f_; -extern PFNGLUNIFORM3FPROC glUniform3f_; -extern PFNGLUNIFORM4FPROC glUniform4f_; -extern PFNGLUNIFORM1FVPROC glUniform1fv_; -extern PFNGLUNIFORM2FVPROC glUniform2fv_; -extern PFNGLUNIFORM3FVPROC glUniform3fv_; -extern PFNGLUNIFORM4FVPROC glUniform4fv_; -extern PFNGLUNIFORM1IPROC glUniform1i_; -extern PFNGLUNIFORM2IPROC glUniform2i_; -extern PFNGLUNIFORM3IPROC glUniform3i_; -extern PFNGLUNIFORM4IPROC glUniform4i_; -extern PFNGLUNIFORM1IVPROC glUniform1iv_; -extern PFNGLUNIFORM2IVPROC glUniform2iv_; -extern PFNGLUNIFORM3IVPROC glUniform3iv_; -extern PFNGLUNIFORM4IVPROC glUniform4iv_; -extern PFNGLUNIFORMMATRIX2FVPROC glUniformMatrix2fv_; -extern PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv_; -extern PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv_; -extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation_; -extern PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform_; +extern PFNGLCREATEPROGRAMPROC glCreateProgram_; +extern PFNGLDELETEPROGRAMPROC glDeleteProgram_; +extern PFNGLUSEPROGRAMPROC glUseProgram_; +extern PFNGLCREATESHADERPROC glCreateShader_; +extern PFNGLDELETESHADERPROC glDeleteShader_; +extern PFNGLSHADERSOURCEPROC glShaderSource_; +extern PFNGLCOMPILESHADERPROC glCompileShader_; +extern PFNGLGETSHADERIVPROC glGetShaderiv_; +extern PFNGLGETPROGRAMIVPROC glGetProgramiv_; +extern PFNGLATTACHSHADERPROC glAttachShader_; +extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog_; +extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog_; +extern PFNGLLINKPROGRAMPROC glLinkProgram_; +extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation_; +extern PFNGLUNIFORM1FPROC glUniform1f_; +extern PFNGLUNIFORM2FPROC glUniform2f_; +extern PFNGLUNIFORM3FPROC glUniform3f_; +extern PFNGLUNIFORM4FPROC glUniform4f_; +extern PFNGLUNIFORM1FVPROC glUniform1fv_; +extern PFNGLUNIFORM2FVPROC glUniform2fv_; +extern PFNGLUNIFORM3FVPROC glUniform3fv_; +extern PFNGLUNIFORM4FVPROC glUniform4fv_; +extern PFNGLUNIFORM1IPROC glUniform1i_; +extern PFNGLUNIFORM2IPROC glUniform2i_; +extern PFNGLUNIFORM3IPROC glUniform3i_; +extern PFNGLUNIFORM4IPROC glUniform4i_; +extern PFNGLUNIFORM1IVPROC glUniform1iv_; +extern PFNGLUNIFORM2IVPROC glUniform2iv_; +extern PFNGLUNIFORM3IVPROC glUniform3iv_; +extern PFNGLUNIFORM4IVPROC glUniform4iv_; +extern PFNGLUNIFORMMATRIX2FVPROC glUniformMatrix2fv_; +extern PFNGLUNIFORMMATRIX3FVPROC glUniformMatrix3fv_; +extern PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv_; +extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation_; +extern PFNGLGETACTIVEUNIFORMPROC glGetActiveUniform_; extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray_; extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray_; -extern PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f_; -extern PFNGLVERTEXATTRIB1FVPROC glVertexAttrib1fv_; -extern PFNGLVERTEXATTRIB1SPROC glVertexAttrib1s_; -extern PFNGLVERTEXATTRIB1SVPROC glVertexAttrib1sv_; -extern PFNGLVERTEXATTRIB2FPROC glVertexAttrib2f_; -extern PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv_; -extern PFNGLVERTEXATTRIB2SPROC glVertexAttrib2s_; -extern PFNGLVERTEXATTRIB2SVPROC glVertexAttrib2sv_; -extern PFNGLVERTEXATTRIB3FPROC glVertexAttrib3f_; -extern PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv_; -extern PFNGLVERTEXATTRIB3SPROC glVertexAttrib3s_; -extern PFNGLVERTEXATTRIB3SVPROC glVertexAttrib3sv_; -extern PFNGLVERTEXATTRIB4FPROC glVertexAttrib4f_; -extern PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv_; -extern PFNGLVERTEXATTRIB4SPROC glVertexAttrib4s_; -extern PFNGLVERTEXATTRIB4SVPROC glVertexAttrib4sv_; -extern PFNGLVERTEXATTRIB4BVPROC glVertexAttrib4bv_; -extern PFNGLVERTEXATTRIB4IVPROC glVertexAttrib4iv_; -extern PFNGLVERTEXATTRIB4UBVPROC glVertexAttrib4ubv_; -extern PFNGLVERTEXATTRIB4UIVPROC glVertexAttrib4uiv_; -extern PFNGLVERTEXATTRIB4USVPROC glVertexAttrib4usv_; -extern PFNGLVERTEXATTRIB4NBVPROC glVertexAttrib4Nbv_; -extern PFNGLVERTEXATTRIB4NIVPROC glVertexAttrib4Niv_; -extern PFNGLVERTEXATTRIB4NUBPROC glVertexAttrib4Nub_; -extern PFNGLVERTEXATTRIB4NUBVPROC glVertexAttrib4Nubv_; -extern PFNGLVERTEXATTRIB4NUIVPROC glVertexAttrib4Nuiv_; -extern PFNGLVERTEXATTRIB4NUSVPROC glVertexAttrib4Nusv_; -extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer_; +extern PFNGLVERTEXATTRIB1FPROC glVertexAttrib1f_; +extern PFNGLVERTEXATTRIB1FVPROC glVertexAttrib1fv_; +extern PFNGLVERTEXATTRIB1SPROC glVertexAttrib1s_; +extern PFNGLVERTEXATTRIB1SVPROC glVertexAttrib1sv_; +extern PFNGLVERTEXATTRIB2FPROC glVertexAttrib2f_; +extern PFNGLVERTEXATTRIB2FVPROC glVertexAttrib2fv_; +extern PFNGLVERTEXATTRIB2SPROC glVertexAttrib2s_; +extern PFNGLVERTEXATTRIB2SVPROC glVertexAttrib2sv_; +extern PFNGLVERTEXATTRIB3FPROC glVertexAttrib3f_; +extern PFNGLVERTEXATTRIB3FVPROC glVertexAttrib3fv_; +extern PFNGLVERTEXATTRIB3SPROC glVertexAttrib3s_; +extern PFNGLVERTEXATTRIB3SVPROC glVertexAttrib3sv_; +extern PFNGLVERTEXATTRIB4FPROC glVertexAttrib4f_; +extern PFNGLVERTEXATTRIB4FVPROC glVertexAttrib4fv_; +extern PFNGLVERTEXATTRIB4SPROC glVertexAttrib4s_; +extern PFNGLVERTEXATTRIB4SVPROC glVertexAttrib4sv_; +extern PFNGLVERTEXATTRIB4BVPROC glVertexAttrib4bv_; +extern PFNGLVERTEXATTRIB4IVPROC glVertexAttrib4iv_; +extern PFNGLVERTEXATTRIB4UBVPROC glVertexAttrib4ubv_; +extern PFNGLVERTEXATTRIB4UIVPROC glVertexAttrib4uiv_; +extern PFNGLVERTEXATTRIB4USVPROC glVertexAttrib4usv_; +extern PFNGLVERTEXATTRIB4NBVPROC glVertexAttrib4Nbv_; +extern PFNGLVERTEXATTRIB4NIVPROC glVertexAttrib4Niv_; +extern PFNGLVERTEXATTRIB4NUBPROC glVertexAttrib4Nub_; +extern PFNGLVERTEXATTRIB4NUBVPROC glVertexAttrib4Nubv_; +extern PFNGLVERTEXATTRIB4NUIVPROC glVertexAttrib4Nuiv_; +extern PFNGLVERTEXATTRIB4NUSVPROC glVertexAttrib4Nusv_; +extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer_; extern PFNGLDRAWBUFFERSPROC glDrawBuffers_; -#endif #ifndef GL_VERSION_2_1 #define GL_VERSION_2_1 1 -#define GL_PIXEL_PACK_BUFFER 0x88EB -#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC #endif #ifndef GL_EXT_texture_filter_anisotropic #define GL_EXT_texture_filter_anisotropic 1 -#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF #endif @@ -301,20 +184,20 @@ extern PFNGLDRAWBUFFERSPROC glDrawBuffers_; #ifndef GL_ARB_framebuffer_object #define GL_ARB_framebuffer_object 1 -#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A -#define GL_DEPTH_STENCIL 0x84F9 -#define GL_UNSIGNED_INT_24_8 0x84FA -#define GL_DEPTH24_STENCIL8 0x88F0 -#define GL_FRAMEBUFFER_BINDING 0x8CA6 -#define GL_READ_FRAMEBUFFER 0x8CA8 -#define GL_DRAW_FRAMEBUFFER 0x8CA9 -#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 -#define GL_COLOR_ATTACHMENT0 0x8CE0 -#define GL_COLOR_ATTACHMENT1 0x8CE1 -#define GL_DEPTH_ATTACHMENT 0x8D00 -#define GL_STENCIL_ATTACHMENT 0x8D20 -#define GL_FRAMEBUFFER 0x8D40 -#define GL_RENDERBUFFER 0x8D41 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); @@ -330,95 +213,95 @@ typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLi #endif // GL_EXT_framebuffer_object -extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer_; -extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers_; -extern PFNGLGENFRAMEBUFFERSPROC glGenRenderbuffers_; -extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage_; +extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer_; +extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers_; +extern PFNGLGENFRAMEBUFFERSPROC glGenRenderbuffers_; +extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage_; extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus_; -extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer_; -extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers_; -extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers_; -extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D_; +extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer_; +extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers_; +extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers_; +extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D_; extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer_; -extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap_; +extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap_; // GL_EXT_framebuffer_blit -extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer_; +extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer_; #ifndef GL_ARB_texture_rg #define GL_ARB_texture_rg 1 -#define GL_RG 0x8227 -#define GL_R8 0x8229 -#define GL_R16 0x822A -#define GL_RG8 0x822B -#define GL_RG16 0x822C -#define GL_R16F 0x822D -#define GL_R32F 0x822E -#define GL_RG16F 0x822F -#define GL_RG32F 0x8230 +#define GL_RG 0x8227 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 #endif #ifndef GL_EXT_texture_compression_latc #define GL_EXT_texture_compression_latc 1 -#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 -#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 #endif #ifndef GL_ARB_texture_compression_rgtc #define GL_ARB_texture_compression_rgtc 1 -#define GL_COMPRESSED_RED_RGTC1 0x8DBB -#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_RG_RGTC2 0x8DBD #endif #ifndef GL_ARB_map_buffer_range #define GL_ARB_map_buffer_range 1 -#define GL_MAP_READ_BIT 0x0001 -#define GL_MAP_WRITE_BIT 0x0002 -#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 -#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 -#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 -#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); #endif -extern PFNGLMAPBUFFERRANGEPROC glMapBufferRange_; +extern PFNGLMAPBUFFERRANGEPROC glMapBufferRange_; extern PFNGLFLUSHMAPPEDBUFFERRANGEPROC glFlushMappedBufferRange_; #ifndef GL_ARB_uniform_buffer_object #define GL_ARB_uniform_buffer_object 1 -#define GL_UNIFORM_BUFFER 0x8A11 -#define GL_UNIFORM_BUFFER_BINDING 0x8A28 -#define GL_UNIFORM_BUFFER_START 0x8A29 -#define GL_UNIFORM_BUFFER_SIZE 0x8A2A -#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B -#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C -#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D -#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E -#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F -#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 #define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 #define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 #define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 #define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 #define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 -#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 -#define GL_UNIFORM_TYPE 0x8A37 -#define GL_UNIFORM_SIZE 0x8A38 -#define GL_UNIFORM_NAME_LENGTH 0x8A39 -#define GL_UNIFORM_BLOCK_INDEX 0x8A3A -#define GL_UNIFORM_OFFSET 0x8A3B -#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C -#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D -#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E -#define GL_UNIFORM_BLOCK_BINDING 0x8A3F -#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 -#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 #define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 #define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 #define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 -#define GL_INVALID_INDEX 0xFFFFFFFFu +#define GL_INVALID_INDEX 0xFFFFFFFFu typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC) (GLuint program, GLsizei uniformCount, const GLchar* *uniformNames, GLuint *uniformIndices); typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar *uniformBlockName); @@ -426,25 +309,25 @@ typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuin typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); #endif #ifndef GL_INVALID_INDEX -#define GL_INVALID_INDEX 0xFFFFFFFFu +#define GL_INVALID_INDEX 0xFFFFFFFFu #endif -extern PFNGLGETUNIFORMINDICESPROC glGetUniformIndices_; -extern PFNGLGETACTIVEUNIFORMSIVPROC glGetActiveUniformsiv_; -extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex_; +extern PFNGLGETUNIFORMINDICESPROC glGetUniformIndices_; +extern PFNGLGETACTIVEUNIFORMSIVPROC glGetActiveUniformsiv_; +extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex_; extern PFNGLGETACTIVEUNIFORMBLOCKIVPROC glGetActiveUniformBlockiv_; -extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding_; +extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding_; #ifndef GL_VERSION_3_0 #define GL_VERSION_3_0 1 -#define GL_NUM_EXTENSIONS 0x821D -#define GL_COMPARE_REF_TO_TEXTURE 0x884E -#define GL_MAX_VARYING_COMPONENTS 0x8B4B -#define GL_RGBA32F 0x8814 -#define GL_RGB32F 0x8815 -#define GL_RGBA16F 0x881A -#define GL_RGB16F 0x881B -#define GL_COMPRESSED_RED 0x8225 -#define GL_COMPRESSED_RG 0x8226 +#define GL_NUM_EXTENSIONS 0x821D +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name); @@ -454,34 +337,34 @@ typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); #endif -extern PFNGLGETSTRINGIPROC glGetStringi_; +extern PFNGLGETSTRINGIPROC glGetStringi_; extern PFNGLBINDFRAGDATALOCATIONPROC glBindFragDataLocation_; -extern PFNGLBINDBUFFERBASEPROC glBindBufferBase_; -extern PFNGLBINDBUFFERRANGEPROC glBindBufferRange_; +extern PFNGLBINDBUFFERBASEPROC glBindBufferBase_; +extern PFNGLBINDBUFFERRANGEPROC glBindBufferRange_; #ifndef GL_VERSION_3_1 #define GL_VERSION_3_1 1 -#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_RECTANGLE 0x84F5 #endif #ifndef GL_ARB_vertex_array_object #define GL_ARB_vertex_array_object 1 -#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#define GL_VERTEX_ARRAY_BINDING 0x85B5 typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC) (GLuint array); #endif -extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray_; +extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray_; extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays_; -extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays_; -extern PFNGLISVERTEXARRAYPROC glIsVertexArray_; +extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays_; +extern PFNGLISVERTEXARRAYPROC glIsVertexArray_; #ifndef GL_ARB_texture_swizzle #define GL_ARB_texture_swizzle 1 -#define GL_TEXTURE_SWIZZLE_R 0x8E42 -#define GL_TEXTURE_SWIZZLE_G 0x8E43 -#define GL_TEXTURE_SWIZZLE_B 0x8E44 -#define GL_TEXTURE_SWIZZLE_A 0x8E45 -#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 #endif diff --git a/src/shared/iengine.h b/src/shared/iengine.h index 80bc9c5..bc2f7a9 100644 --- a/src/shared/iengine.h +++ b/src/shared/iengine.h @@ -1,38 +1,38 @@ // the interface the game uses to access the engine -extern int curtime; // current frame time -extern int lastmillis; // last time -extern int elapsedtime; // elapsed frame time -extern int totalmillis; // total elapsed time +extern int curtime; // current frame time +extern int lastmillis; // last time +extern int elapsedtime; // elapsed frame time +extern int totalmillis; // total elapsed time extern uint totalsecs; extern int gamespeed, paused; enum { - MATF_INDEX_SHIFT = 0, - MATF_VOLUME_SHIFT = 2, - MATF_CLIP_SHIFT = 5, - MATF_FLAG_SHIFT = 8, - - MATF_INDEX = 3 << MATF_INDEX_SHIFT, - MATF_VOLUME = 7 << MATF_VOLUME_SHIFT, - MATF_CLIP = 7 << MATF_CLIP_SHIFT, - MATF_FLAGS = 0xFF << MATF_FLAG_SHIFT + MATF_INDEX_SHIFT = 0, + MATF_VOLUME_SHIFT = 2, + MATF_CLIP_SHIFT = 5, + MATF_FLAG_SHIFT = 8, + + MATF_INDEX = 3 << MATF_INDEX_SHIFT, + MATF_VOLUME = 7 << MATF_VOLUME_SHIFT, + MATF_CLIP = 7 << MATF_CLIP_SHIFT, + MATF_FLAGS = 0xFF << MATF_FLAG_SHIFT }; enum // cube empty-space materials { - MAT_AIR = 0, // the default, fill the empty space with air - MAT_WATER = 1 << MATF_VOLUME_SHIFT, // fill with water, showing waves at the surface - MAT_LAVA = 2 << MATF_VOLUME_SHIFT, // fill with lava - MAT_GLASS = 3 << MATF_VOLUME_SHIFT, // behaves like clip but is blended blueish + MAT_AIR = 0, // the default, fill the empty space with air + MAT_WATER = 1 << MATF_VOLUME_SHIFT, // fill with water, showing waves at the surface + MAT_LAVA = 2 << MATF_VOLUME_SHIFT, // fill with lava + MAT_GLASS = 3 << MATF_VOLUME_SHIFT, // behaves like clip but is blended blueish - MAT_NOCLIP = 1 << MATF_CLIP_SHIFT, // collisions always treat cube as empty - MAT_CLIP = 2 << MATF_CLIP_SHIFT, // collisions always treat cube as solid - MAT_GAMECLIP = 3 << MATF_CLIP_SHIFT, // game specific clip material + MAT_NOCLIP = 1 << MATF_CLIP_SHIFT, // collisions always treat cube as empty + MAT_CLIP = 2 << MATF_CLIP_SHIFT, // collisions always treat cube as solid + MAT_GAMECLIP = 3 << MATF_CLIP_SHIFT, // game specific clip material - MAT_DEATH = 1 << MATF_FLAG_SHIFT, // force player suicide - MAT_ALPHA = 4 << MATF_FLAG_SHIFT // alpha blended + MAT_DEATH = 1 << MATF_FLAG_SHIFT, // force player suicide + MAT_ALPHA = 4 << MATF_FLAG_SHIFT // alpha blended }; #define isliquid(mat) ((mat)==MAT_WATER || (mat)==MAT_LAVA) @@ -45,7 +45,7 @@ extern entity *brightestlight(const vec &target, const vec &dir); enum { RAY_BB = 1, RAY_POLY = 3, RAY_ALPHAPOLY = 7, RAY_ENTS = 9, RAY_CLIPMAT = 16, RAY_SKIPFIRST = 32, RAY_EDITMAT = 64, RAY_SHADOW = 128, RAY_PASS = 256, RAY_SKIPSKY = 512, RAY_SKYTEX = 1024 }; -extern float raycube (const vec &o, const vec &ray, float radius = 0, int mode = RAY_CLIPMAT, int size = 0, extentity *t = 0); +extern float raycube (const vec &o, const vec &ray, float radius = 0, int mode = RAY_CLIPMAT, int size = 0, extentity *t = 0); extern float raycubepos(const vec &o, const vec &ray, vec &hit, float radius = 0, int mode = RAY_CLIPMAT, int size = 0); extern float rayfloor (const vec &o, vec &floor, int mode = 0, float radius = 0); extern bool raycubelos(const vec &o, const vec &dest, vec &hitpos); @@ -61,27 +61,27 @@ enum { EDIT_FACE = 0, EDIT_TEX, EDIT_MAT, EDIT_FLIP, EDIT_COPY, EDIT_PASTE, EDIT struct selinfo { - int corner; - int cx, cxs, cy, cys; - ivec o, s; - int grid, orient; - selinfo() : corner(0), cx(0), cxs(0), cy(0), cys(0), o(0, 0, 0), s(0, 0, 0), grid(8), orient(0) {} - int size() const { return s.x*s.y*s.z; } - int us(int d) const { return s[d]*grid; } - bool operator==(const selinfo &sel) const { return o==sel.o && s==sel.s && grid==sel.grid && orient==sel.orient; } - bool validate() - { - extern int worldsize; - if(grid <= 0 || grid >= worldsize) return false; - if(o.x >= worldsize || o.y >= worldsize || o.z >= worldsize) return false; - if(o.x < 0) { s.x -= (grid - 1 - o.x)/grid; o.x = 0; } - if(o.y < 0) { s.y -= (grid - 1 - o.y)/grid; o.y = 0; } - if(o.z < 0) { s.z -= (grid - 1 - o.z)/grid; o.z = 0; } - s.x = clamp(s.x, 0, (worldsize - o.x)/grid); - s.y = clamp(s.y, 0, (worldsize - o.y)/grid); - s.z = clamp(s.z, 0, (worldsize - o.z)/grid); - return s.x > 0 && s.y > 0 && s.z > 0; - } + int corner; + int cx, cxs, cy, cys; + ivec o, s; + int grid, orient; + selinfo() : corner(0), cx(0), cxs(0), cy(0), cys(0), o(0, 0, 0), s(0, 0, 0), grid(8), orient(0) {} + int size() const { return s.x*s.y*s.z; } + int us(int d) const { return s[d]*grid; } + bool operator==(const selinfo &sel) const { return o==sel.o && s==sel.s && grid==sel.grid && orient==sel.orient; } + bool validate() + { + extern int worldsize; + if(grid <= 0 || grid >= worldsize) return false; + if(o.x >= worldsize || o.y >= worldsize || o.z >= worldsize) return false; + if(o.x < 0) { s.x -= (grid - 1 - o.x)/grid; o.x = 0; } + if(o.y < 0) { s.y -= (grid - 1 - o.y)/grid; o.y = 0; } + if(o.z < 0) { s.z -= (grid - 1 - o.z)/grid; o.z = 0; } + s.x = clamp(s.x, 0, (worldsize - o.x)/grid); + s.y = clamp(s.y, 0, (worldsize - o.y)/grid); + s.z = clamp(s.z, 0, (worldsize - o.z)/grid); + return s.x > 0 && s.y > 0 && s.z > 0; + } }; struct editinfo; @@ -187,16 +187,16 @@ static inline void loopiter(ident *id, identstack &stack, const char *s) { tagva enum { - CON_INFO = 1<<0, - CON_WARN = 1<<1, - CON_ERROR = 1<<2, - CON_DEBUG = 1<<3, - CON_INIT = 1<<4, - CON_ECHO = 1<<5, - - CON_FLAGS = 0xFFFF, - CON_TAG_SHIFT = 16, - CON_TAG_MASK = (0x7FFF << CON_TAG_SHIFT) + CON_INFO = 1<<0, + CON_WARN = 1<<1, + CON_ERROR = 1<<2, + CON_DEBUG = 1<<3, + CON_INIT = 1<<4, + CON_ECHO = 1<<5, + + CON_FLAGS = 0xFFFF, + CON_TAG_SHIFT = 16, + CON_TAG_MASK = (0x7FFF << CON_TAG_SHIFT) }; extern void conoutf(const char *s, ...) PRINTFARGS(1, 2); @@ -221,14 +221,14 @@ extern int lookupmaterial(const vec &o); static inline bool insideworld(const vec &o) { - extern int worldsize; - return o.x>=0 && o.x=0 && o.y=0 && o.z=0 && o.x=0 && o.y=0 && o.z &getents(); - extern const char *entmodel(const entity &e); + extern void editent(int i, bool local); + extern const char *entnameinfo(entity &e); + extern const char *entname(int i); + extern int extraentinfosize(); + extern void writeent(entity &e, char *buf); + extern void readent(entity &e, char *buf, int ver); + extern void fixentity(extentity &e); + extern void entradius(extentity &e, bool color); + extern bool mayattach(extentity &e); + extern bool attachent(extentity &e, extentity &a); + extern bool printent(extentity &e, char *buf, int len); + extern extentity *newentity(); + extern void deleteentity(extentity *e); + extern void clearents(); + extern vector &getents(); + extern const char *entmodel(const entity &e); } namespace game { - extern void parseoptions(vector &args); + extern void parseoptions(vector &args); - extern void gamedisconnect(bool cleanup); - extern void parsepacketclient(int chan, packetbuf &p); - extern void connectattempt(const char *name, const char *password, const ENetAddress &address); - extern void connectfail(); - extern void gameconnect(bool _remote); - extern bool allowedittoggle(); - extern void edittoggled(bool on); - extern void writeclientinfo(stream *f); - extern void toserver(char *text); - extern void changemap(const char *name); - extern void forceedit(const char *name); - extern bool ispaused(); - extern int scaletime(int t); - extern bool allowmouselook(); + extern void gamedisconnect(bool cleanup); + extern void parsepacketclient(int chan, packetbuf &p); + extern void connectattempt(const char *name, const char *password, const ENetAddress &address); + extern void connectfail(); + extern void gameconnect(bool _remote); + extern bool allowedittoggle(); + extern void edittoggled(bool on); + extern void writeclientinfo(stream *f); + extern void toserver(char *text); + extern void changemap(const char *name); + extern void forceedit(const char *name); + extern bool ispaused(); + extern int scaletime(int t); + extern bool allowmouselook(); - extern const char *gameident(); - extern const char *savedconfig(); - extern const char *restoreconfig(); - extern const char *defaultconfig(); - extern const char *autoexec(); - extern const char *savedservers(); - extern void loadconfigs(); + extern const char *gameident(); + extern const char *savedconfig(); + extern const char *restoreconfig(); + extern const char *defaultconfig(); + extern const char *autoexec(); + extern const char *savedservers(); + extern void loadconfigs(); - extern void updateworld(); - extern void initclient(); - extern void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material = 0); - extern void bounced(physent *d, const vec &surface); - extern void edittrigger(const selinfo &sel, int op, int arg1 = 0, int arg2 = 0, int arg3 = 0, const VSlot *vs = NULL); - extern void vartrigger(ident *id); - extern void dynentcollide(physent *d, physent *o, const vec &dir); - extern const char *getclientmap(); - extern const char *getmapinfo(); - extern const char *getscreenshotinfo(); - extern void resetgamestate(); - extern void suicide(physent *d); - extern float ratespawn(dynent *d, const extentity &e); - extern void newmap(int size); - extern void loadingmap(const char *name); - extern void startmap(const char *name); - extern void preload(); - extern float abovegameplayhud(int w, int h); - extern void gameplayhud(int w, int h); - extern bool canjump(); - extern bool allowmove(physent *d); - extern void doattack(bool on); - extern dynent *iterdynents(int i); - extern int numdynents(); - extern void rendergame(bool mainpass); - extern void renderavatar(); - extern void renderplayerpreview(int model, int team, int weap); - extern void writegamedata(vector &extras); - extern void readgamedata(vector &extras); - extern int clipconsole(int w, int h); - extern void g3d_gamemenus(); - extern const char *defaultcrosshair(int index); - extern int selectcrosshair(vec &color); - extern void lighteffects(dynent *d, vec &color, vec &dir); - extern void setupcamera(); - extern bool allowthirdperson(bool msg = false); - extern bool detachcamera(); - extern bool collidecamera(); - extern void adddynlights(); - extern void particletrack(physent *owner, vec &o, vec &d); - extern void dynlighttrack(physent *owner, vec &o, vec &hud); - extern int maxsoundradius(int n); - extern bool serverinfostartcolumn(g3d_gui *g, int i); - extern void serverinfoendcolumn(g3d_gui *g, int i); - extern bool serverinfoentry(g3d_gui *g, int i, const char *name, int port, const char *desc, const char *map, int ping, const vector &attr, int np); + extern void updateworld(); + extern void initclient(); + extern void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material = 0); + extern void bounced(physent *d, const vec &surface); + extern void edittrigger(const selinfo &sel, int op, int arg1 = 0, int arg2 = 0, int arg3 = 0, const VSlot *vs = NULL); + extern void vartrigger(ident *id); + extern void dynentcollide(physent *d, physent *o, const vec &dir); + extern const char *getclientmap(); + extern const char *getmapinfo(); + extern const char *getscreenshotinfo(); + extern void resetgamestate(); + extern void suicide(physent *d); + extern float ratespawn(dynent *d, const extentity &e); + extern void newmap(int size); + extern void loadingmap(const char *name); + extern void startmap(const char *name); + extern void preload(); + extern float abovegameplayhud(int w, int h); + extern void gameplayhud(int w, int h); + extern bool canjump(); + extern bool allowmove(physent *d); + extern void doattack(bool on); + extern dynent *iterdynents(int i); + extern int numdynents(); + extern void rendergame(bool mainpass); + extern void renderavatar(); + extern void renderplayerpreview(int model, int team, int weap); + extern void writegamedata(vector &extras); + extern void readgamedata(vector &extras); + extern int clipconsole(int w, int h); + extern void g3d_gamemenus(); + extern const char *defaultcrosshair(int index); + extern int selectcrosshair(vec &color); + extern void lighteffects(dynent *d, vec &color, vec &dir); + extern void setupcamera(); + extern bool allowthirdperson(bool msg = false); + extern bool detachcamera(); + extern bool collidecamera(); + extern void adddynlights(); + extern void particletrack(physent *owner, vec &o, vec &d); + extern void dynlighttrack(physent *owner, vec &o, vec &hud); + extern int maxsoundradius(int n); + extern bool serverinfostartcolumn(g3d_gui *g, int i); + extern void serverinfoendcolumn(g3d_gui *g, int i); + extern bool serverinfoentry(g3d_gui *g, int i, const char *name, int port, const char *desc, const char *map, int ping, const vector &attr, int np); } namespace server { - extern void *newclientinfo(); - extern void deleteclientinfo(void *ci); - extern void serverinit(); - extern int reserveclients(); - extern int numchannels(); - extern void clientdisconnect(int n); - extern int clientconnect(int n); - extern void localdisconnect(int n); - extern void localconnect(int n); - extern bool allowbroadcast(int n); - extern void recordpacket(int chan, void *data, int len); - extern void parsepacket(int sender, int chan, packetbuf &p); - extern void sendservmsg(const char *s); - extern bool sendpackets(bool force = false); - extern void serverinforeply(ucharbuf &req, ucharbuf &p); - extern void serverupdate(); - extern int laninfoport(); - extern int serverinfoport(int servport = -1); - extern int serverport(int infoport = -1); - extern const char *defaultmaster(); - extern int masterport(); - extern void processmasterinput(const char *cmd, int cmdlen, const char *args); - extern void masterconnected(); - extern void masterdisconnected(); - extern bool ispaused(); - extern int scaletime(int t); + extern void *newclientinfo(); + extern void deleteclientinfo(void *ci); + extern void serverinit(); + extern int reserveclients(); + extern int numchannels(); + extern void clientdisconnect(int n); + extern int clientconnect(int n); + extern void localdisconnect(int n); + extern void localconnect(int n); + extern bool allowbroadcast(int n); + extern void recordpacket(int chan, void *data, int len); + extern void parsepacket(int sender, int chan, packetbuf &p); + extern void sendservmsg(const char *s); + extern bool sendpackets(bool force = false); + extern void serverinforeply(ucharbuf &req, ucharbuf &p); + extern void serverupdate(); + extern int laninfoport(); + extern int serverinfoport(int servport = -1); + extern int serverport(int infoport = -1); + extern const char *defaultmaster(); + extern int masterport(); + extern void processmasterinput(const char *cmd, int cmdlen, const char *args); + extern void masterconnected(); + extern void masterdisconnected(); + extern bool ispaused(); + extern int scaletime(int t); } diff --git a/src/shared/stream.cpp b/src/shared/stream.cpp index 888caa8..8f218ad 100644 --- a/src/shared/stream.cpp +++ b/src/shared/stream.cpp @@ -4,1215 +4,1173 @@ void conoutf(const char *fmt, ...) { - va_list args; - va_start(args, fmt); - conoutfv(CON_INFO, fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + conoutfv(CON_INFO, fmt, args); + va_end(args); } void conoutf(int type, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - conoutfv(type, fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + conoutfv(type, fmt, args); + va_end(args); } void conoutf(int type, int tag, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - conoutfv(type | ((tag << CON_TAG_SHIFT) & CON_TAG_MASK), fmt, args); - va_end(args); + va_list args; + va_start(args, fmt); + conoutfv(type | ((tag << CON_TAG_SHIFT) & CON_TAG_MASK), fmt, args); + va_end(args); } ///////////////////////// character conversion /////////////// #define CUBECTYPE(s, p, d, a, A, u, U) \ - 0, U, U, U, U, U, U, U, U, s, s, s, s, s, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - s, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, \ - d, d, d, d, d, d, d, d, d, d, p, p, p, p, p, p, \ - p, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, \ - A, A, A, A, A, A, A, A, A, A, A, p, p, p, p, p, \ - p, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, \ - a, a, a, a, a, a, a, a, a, a, a, p, p, p, p, U, \ - U, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ - u, U, u, U, u, U, u, U, U, u, U, u, U, u, U, U, \ - U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ - U, U, U, U, u, u, u, u, u, u, u, u, u, u, u, u, \ - u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, u + 0, U, U, U, U, U, U, U, U, s, s, s, s, s, U, U, \ + U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ + s, p, p, p, p, p, p, p, p, p, p, p, p, p, p, p, \ + d, d, d, d, d, d, d, d, d, d, p, p, p, p, p, p, \ + p, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, \ + A, A, A, A, A, A, A, A, A, A, A, p, p, p, p, p, \ + p, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, \ + a, a, a, a, a, a, a, a, a, a, a, p, p, p, p, U, \ + U, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, \ + u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, \ + u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ + u, U, u, U, u, U, u, U, u, U, u, U, u, U, u, U, \ + u, U, u, U, u, U, u, U, U, u, U, u, U, u, U, U, \ + U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, \ + U, U, U, U, u, u, u, u, u, u, u, u, u, u, u, u, \ + u, u, u, u, u, u, u, u, u, u, u, u, u, u, U, u extern const uchar cubectype[256] = { - CUBECTYPE(CT_SPACE, - CT_PRINT, - CT_PRINT|CT_DIGIT, - CT_PRINT|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_ALPHA|CT_UPPER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_LOWER, - CT_PRINT|CT_UNICODE|CT_ALPHA|CT_UPPER) + CUBECTYPE(CT_SPACE, + CT_PRINT, + CT_PRINT|CT_DIGIT, + CT_PRINT|CT_ALPHA|CT_LOWER, + CT_PRINT|CT_ALPHA|CT_UPPER, + CT_PRINT|CT_UNICODE|CT_ALPHA|CT_LOWER, + CT_PRINT|CT_UNICODE|CT_ALPHA|CT_UPPER) }; extern const int cube2unichars[256] = { - 0, 192, 193, 194, 195, 196, 197, 198, 199, 9, 10, 11, 12, 13, 200, 201, - 202, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 220, - 221, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, - 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 0x104, - 0x105, 0x106, 0x107, 0x10C, 0x10D, 0x10E, 0x10F, 0x118, 0x119, 0x11A, 0x11B, 0x11E, 0x11F, 0x130, 0x131, 0x141, - 0x142, 0x143, 0x144, 0x147, 0x148, 0x150, 0x151, 0x152, 0x153, 0x158, 0x159, 0x15A, 0x15B, 0x15E, 0x15F, 0x160, - 0x161, 0x164, 0x165, 0x16E, 0x16F, 0x170, 0x171, 0x178, 0x179, 0x17A, 0x17B, 0x17C, 0x17D, 0x17E, 0x404, 0x411, - 0x413, 0x414, 0x416, 0x417, 0x418, 0x419, 0x41B, 0x41F, 0x423, 0x424, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, - 0x42C, 0x42D, 0x42E, 0x42F, 0x431, 0x432, 0x433, 0x434, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, - 0x43F, 0x442, 0x444, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F, 0x454, 0x490, 0x491 + 0, 192, 193, 194, 195, 196, 197, 198, 199, 9, 10, 11, 12, 13, 200, 201, + 202, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 220, + 221, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, + 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 0x104, + 0x105, 0x106, 0x107, 0x10C, 0x10D, 0x10E, 0x10F, 0x118, 0x119, 0x11A, 0x11B, 0x11E, 0x11F, 0x130, 0x131, 0x141, + 0x142, 0x143, 0x144, 0x147, 0x148, 0x150, 0x151, 0x152, 0x153, 0x158, 0x159, 0x15A, 0x15B, 0x15E, 0x15F, 0x160, + 0x161, 0x164, 0x165, 0x16E, 0x16F, 0x170, 0x171, 0x178, 0x179, 0x17A, 0x17B, 0x17C, 0x17D, 0x17E, 0x404, 0x411, + 0x413, 0x414, 0x416, 0x417, 0x418, 0x419, 0x41B, 0x41F, 0x423, 0x424, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, + 0x42C, 0x42D, 0x42E, 0x42F, 0x431, 0x432, 0x433, 0x434, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, + 0x43F, 0x442, 0x444, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F, 0x454, 0x490, 0x491 }; extern const int uni2cubeoffsets[8] = { - 0, 256, 658, 658, 512, 658, 658, 658 + 0, 256, 658, 658, 512, 658, 658, 658 }; extern const uchar uni2cubechars[878] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, 20, 21, 0, 22, 23, 24, 25, 26, 27, 0, 28, 29, 30, 31, 127, 128, 0, 129, - 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 0, 146, 147, 148, 149, 150, 151, 0, 152, 153, 154, 155, 156, 157, 0, 158, - 0, 0, 0, 0, 159, 160, 161, 162, 0, 0, 0, 0, 163, 164, 165, 166, 0, 0, 0, 0, 0, 0, 0, 0, 167, 168, 169, 170, 0, 0, 171, 172, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 174, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 175, 176, 177, 178, 0, 0, 179, 180, 0, 0, 0, 0, 0, 0, 0, 181, 182, 183, 184, 0, 0, 0, 0, 185, 186, 187, 188, 0, 0, 189, 190, - 191, 192, 0, 0, 193, 194, 0, 0, 0, 0, 0, 0, 0, 0, 195, 196, 197, 198, 0, 0, 0, 0, 0, 0, 199, 200, 201, 202, 203, 204, 205, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 17, 0, 0, 206, 83, 73, 21, 74, 0, 0, 0, 0, 0, 0, 0, 65, 207, 66, 208, 209, 69, 210, 211, 212, 213, 75, 214, 77, 72, 79, 215, - 80, 67, 84, 216, 217, 88, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 97, 228, 229, 230, 231, 101, 232, 233, 234, 235, 236, 237, 238, 239, 111, 240, - 112, 99, 241, 121, 242, 120, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 0, 141, 0, 0, 253, 115, 105, 145, 106, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 11, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, 20, 21, 0, 22, 23, 24, 25, 26, 27, 0, 28, 29, 30, 31, 127, 128, 0, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 0, 146, 147, 148, 149, 150, 151, 0, 152, 153, 154, 155, 156, 157, 0, 158, + 0, 0, 0, 0, 159, 160, 161, 162, 0, 0, 0, 0, 163, 164, 165, 166, 0, 0, 0, 0, 0, 0, 0, 0, 167, 168, 169, 170, 0, 0, 171, 172, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 174, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 175, 176, 177, 178, 0, 0, 179, 180, 0, 0, 0, 0, 0, 0, 0, 181, 182, 183, 184, 0, 0, 0, 0, 185, 186, 187, 188, 0, 0, 189, 190, + 191, 192, 0, 0, 193, 194, 0, 0, 0, 0, 0, 0, 0, 0, 195, 196, 197, 198, 0, 0, 0, 0, 0, 0, 199, 200, 201, 202, 203, 204, 205, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 17, 0, 0, 206, 83, 73, 21, 74, 0, 0, 0, 0, 0, 0, 0, 65, 207, 66, 208, 209, 69, 210, 211, 212, 213, 75, 214, 77, 72, 79, 215, + 80, 67, 84, 216, 217, 88, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 97, 228, 229, 230, 231, 101, 232, 233, 234, 235, 236, 237, 238, 239, 111, 240, + 112, 99, 241, 121, 242, 120, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 0, 141, 0, 0, 253, 115, 105, 145, 106, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; extern const uchar cubelowerchars[256] = { - 0, 130, 131, 132, 133, 134, 135, 136, 137, 9, 10, 11, 12, 13, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 156, - 157, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 160, - 160, 162, 162, 164, 164, 166, 166, 168, 168, 170, 170, 172, 172, 105, 174, 176, - 176, 178, 178, 180, 180, 182, 182, 184, 184, 186, 186, 188, 188, 190, 190, 192, - 192, 194, 194, 196, 196, 198, 198, 158, 201, 201, 203, 203, 205, 205, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 + 0, 130, 131, 132, 133, 134, 135, 136, 137, 9, 10, 11, 12, 13, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 156, + 157, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 160, + 160, 162, 162, 164, 164, 166, 166, 168, 168, 170, 170, 172, 172, 105, 174, 176, + 176, 178, 178, 180, 180, 182, 182, 184, 184, 186, 186, 188, 188, 190, 190, 192, + 192, 194, 194, 196, 196, 198, 198, 158, 201, 201, 203, 203, 205, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; extern const uchar cubeupperchars[256] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123, 124, 125, 126, 127, - 128, 129, 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127, 128, 199, 159, - 159, 161, 161, 163, 163, 165, 165, 167, 167, 169, 169, 171, 171, 173, 73, 175, - 175, 177, 177, 179, 179, 181, 181, 183, 183, 185, 185, 187, 187, 189, 189, 191, - 191, 193, 193, 195, 195, 197, 197, 199, 200, 200, 202, 202, 204, 204, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123, 124, 125, 126, 127, + 128, 129, 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127, 128, 199, 159, + 159, 161, 161, 163, 163, 165, 165, 167, 167, 169, 169, 171, 171, 173, 73, 175, + 175, 177, 177, 179, 179, 181, 181, 183, 183, 185, 185, 187, 187, 189, 189, 191, + 191, 193, 193, 195, 195, 197, 197, 199, 200, 200, 202, 202, 204, 204, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; size_t decodeutf8(uchar *dstbuf, size_t dstlen, const uchar *srcbuf, size_t srclen, size_t *carry) { - uchar *dst = dstbuf, *dstend = &dstbuf[dstlen]; - const uchar *src = srcbuf, *srcend = &srcbuf[srclen]; - if(dstbuf == srcbuf) - { - int len = min(dstlen, srclen); - for(const uchar *end4 = &srcbuf[len&~3]; src < end4; src += 4) if(*(const int *)src & 0x80808080) goto decode; - for(const uchar *end = &srcbuf[len]; src < end; src++) if(*src & 0x80) goto decode; - if(carry) *carry += len; - return len; - } + uchar *dst = dstbuf, *dstend = &dstbuf[dstlen]; + const uchar *src = srcbuf, *srcend = &srcbuf[srclen]; + if(dstbuf == srcbuf) + { + int len = min(dstlen, srclen); + for(const uchar *end4 = &srcbuf[len&~3]; src < end4; src += 4) if(*(const int *)src & 0x80808080) goto decode; + for(const uchar *end = &srcbuf[len]; src < end; src++) if(*src & 0x80) goto decode; + if(carry) *carry += len; + return len; + } decode: - dst += src - srcbuf; - while(src < srcend && dst < dstend) - { - int c = *src++; - if(c < 0x80) *dst++ = c; - else if(c >= 0xC0) - { - int uni; - if(c >= 0xE0) - { - if(c >= 0xF0) - { - if(c >= 0xF8) - { - if(c >= 0xFC) - { - if(c >= 0xFE) continue; - uni = c&1; if(srcend - src < 5) break; - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&3; if(srcend - src < 4) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&7; if(srcend - src < 3) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&0xF; if(srcend - src < 2) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - } - else { uni = c&0x1F; if(srcend - src < 1) break; } - c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); - c = uni2cube(uni); - if(!c) continue; - *dst++ = c; - } - } - if(carry) *carry += src - srcbuf; - return dst - dstbuf; + dst += src - srcbuf; + while(src < srcend && dst < dstend) + { + int c = *src++; + if(c < 0x80) *dst++ = c; + else if(c >= 0xC0) + { + int uni; + if(c >= 0xE0) + { + if(c >= 0xF0) + { + if(c >= 0xF8) + { + if(c >= 0xFC) + { + if(c >= 0xFE) continue; + uni = c&1; if(srcend - src < 5) break; + c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); + } + else { uni = c&3; if(srcend - src < 4) break; } + c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); + } + else { uni = c&7; if(srcend - src < 3) break; } + c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); + } + else { uni = c&0xF; if(srcend - src < 2) break; } + c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); + } + else { uni = c&0x1F; if(srcend - src < 1) break; } + c = *src; if((c&0xC0) != 0x80) continue; src++; uni = (uni<<6) | (c&0x3F); + c = uni2cube(uni); + if(!c) continue; + *dst++ = c; + } + } + if(carry) *carry += src - srcbuf; + return dst - dstbuf; } size_t encodeutf8(uchar *dstbuf, size_t dstlen, const uchar *srcbuf, size_t srclen, size_t *carry) { - uchar *dst = dstbuf, *dstend = &dstbuf[dstlen]; - const uchar *src = srcbuf, *srcend = &srcbuf[srclen]; - if(src < srcend && dst < dstend) do - { - int uni = cube2uni(*src); - if(uni <= 0x7F) - { - if(dst >= dstend) goto done; - const uchar *end = min(srcend, &src[dstend-dst]); - do - { - if(uni == '\f') - { - if(++src >= srcend) goto done; - goto uni1; - } - *dst++ = uni; - if(++src >= end) goto done; - uni = cube2uni(*src); - } - while(uni <= 0x7F); - } - if(uni <= 0x7FF) { if(dst + 2 > dstend) goto done; *dst++ = 0xC0 | (uni>>6); goto uni2; } - else if(uni <= 0xFFFF) { if(dst + 3 > dstend) goto done; *dst++ = 0xE0 | (uni>>12); goto uni3; } - else if(uni <= 0x1FFFFF) { if(dst + 4 > dstend) goto done; *dst++ = 0xF0 | (uni>>18); goto uni4; } - else if(uni <= 0x3FFFFFF) { if(dst + 5 > dstend) goto done; *dst++ = 0xF8 | (uni>>24); goto uni5; } - else if(uni <= 0x7FFFFFFF) { if(dst + 6 > dstend) goto done; *dst++ = 0xFC | (uni>>30); goto uni6; } - else goto uni1; - uni6: *dst++ = 0x80 | ((uni>>24)&0x3F); - uni5: *dst++ = 0x80 | ((uni>>18)&0x3F); - uni4: *dst++ = 0x80 | ((uni>>12)&0x3F); - uni3: *dst++ = 0x80 | ((uni>>6)&0x3F); - uni2: *dst++ = 0x80 | (uni&0x3F); - uni1:; - } - while(++src < srcend); + uchar *dst = dstbuf, *dstend = &dstbuf[dstlen]; + const uchar *src = srcbuf, *srcend = &srcbuf[srclen]; + if(src < srcend && dst < dstend) do + { + int uni = cube2uni(*src); + if(uni <= 0x7F) + { + if(dst >= dstend) goto done; + const uchar *end = min(srcend, &src[dstend-dst]); + do + { + if(uni == '\f') + { + if(++src >= srcend) goto done; + goto uni1; + } + *dst++ = uni; + if(++src >= end) goto done; + uni = cube2uni(*src); + } + while(uni <= 0x7F); + } + if(uni <= 0x7FF) { if(dst + 2 > dstend) goto done; *dst++ = 0xC0 | (uni>>6); goto uni2; } + else if(uni <= 0xFFFF) { if(dst + 3 > dstend) goto done; *dst++ = 0xE0 | (uni>>12); goto uni3; } + else if(uni <= 0x1FFFFF) { if(dst + 4 > dstend) goto done; *dst++ = 0xF0 | (uni>>18); goto uni4; } + else if(uni <= 0x3FFFFFF) { if(dst + 5 > dstend) goto done; *dst++ = 0xF8 | (uni>>24); goto uni5; } + else if(uni <= 0x7FFFFFFF) { if(dst + 6 > dstend) goto done; *dst++ = 0xFC | (uni>>30); goto uni6; } + else goto uni1; + uni6: *dst++ = 0x80 | ((uni>>24)&0x3F); + uni5: *dst++ = 0x80 | ((uni>>18)&0x3F); + uni4: *dst++ = 0x80 | ((uni>>12)&0x3F); + uni3: *dst++ = 0x80 | ((uni>>6)&0x3F); + uni2: *dst++ = 0x80 | (uni&0x3F); + uni1:; + } + while(++src < srcend); done: - if(carry) *carry += src - srcbuf; - return dst - dstbuf; + if(carry) *carry += src - srcbuf; + return dst - dstbuf; } ///////////////////////// file system /////////////////////// -#ifdef WIN32 -#include -#else #include #include #include #include -#endif string homedir = ""; struct packagedir { - char *dir, *filter; - size_t dirlen, filterlen; + char *dir, *filter; + size_t dirlen, filterlen; }; vector packagedirs; char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd) { - static string tmp; - if(prefix) copystring(tmp, prefix); - else tmp[0] = '\0'; - if(file[0]=='<') - { - const char *end = strrchr(file, '>'); - if(end) - { - size_t len = strlen(tmp); - copystring(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file))); - file = end+1; - } - } - if(cmd) concatstring(tmp, cmd); - if(dir) - { - defformatstring(pname, "%s/%s", dir, file); - concatstring(tmp, pname); - } - else concatstring(tmp, file); - return tmp; + static string tmp; + if(prefix) copystring(tmp, prefix); + else tmp[0] = '\0'; + if(file[0]=='<') + { + const char *end = strrchr(file, '>'); + if(end) + { + size_t len = strlen(tmp); + copystring(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file))); + file = end+1; + } + } + if(cmd) concatstring(tmp, cmd); + if(dir) + { + defformatstring(pname, "%s/%s", dir, file); + concatstring(tmp, pname); + } + else concatstring(tmp, file); + return tmp; } char *path(char *s) { - for(char *curpart = s;;) - { - char *endpart = strchr(curpart, '&'); - if(endpart) *endpart = '\0'; - if(curpart[0]=='<') - { - char *file = strrchr(curpart, '>'); - if(!file) return s; - curpart = file+1; - } - for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV); - for(char *prevdir = NULL, *curdir = curpart;;) - { - prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir; - curdir = strchr(prevdir, PATHDIV); - if(!curdir) break; - if(prevdir+1==curdir && prevdir[0]=='.') - { - memmove(prevdir, curdir+1, strlen(curdir+1)+1); - curdir = prevdir; - } - else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV) - { - if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue; - memmove(prevdir, curdir+4, strlen(curdir+4)+1); - if(prevdir-2 >= curpart && prevdir[-1]==PATHDIV) - { - prevdir -= 2; - while(prevdir-1 >= curpart && prevdir[-1] != PATHDIV) --prevdir; - } - curdir = prevdir; - } - } - if(endpart) - { - *endpart = '&'; - curpart = endpart+1; - } - else break; - } - return s; + for(char *curpart = s;;) + { + char *endpart = strchr(curpart, '&'); + if(endpart) *endpart = '\0'; + if(curpart[0]=='<') + { + char *file = strrchr(curpart, '>'); + if(!file) return s; + curpart = file+1; + } + for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV); + for(char *prevdir = NULL, *curdir = curpart;;) + { + prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir; + curdir = strchr(prevdir, PATHDIV); + if(!curdir) break; + if(prevdir+1==curdir && prevdir[0]=='.') + { + memmove(prevdir, curdir+1, strlen(curdir+1)+1); + curdir = prevdir; + } + else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV) + { + if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue; + memmove(prevdir, curdir+4, strlen(curdir+4)+1); + if(prevdir-2 >= curpart && prevdir[-1]==PATHDIV) + { + prevdir -= 2; + while(prevdir-1 >= curpart && prevdir[-1] != PATHDIV) --prevdir; + } + curdir = prevdir; + } + } + if(endpart) + { + *endpart = '&'; + curpart = endpart+1; + } + else break; + } + return s; } char *path(const char *s, bool copy) { - static string tmp; - copystring(tmp, s); - path(tmp); - return tmp; + static string tmp; + copystring(tmp, s); + path(tmp); + return tmp; } const char *parentdir(const char *directory) { - const char *p = directory + strlen(directory); - while(p > directory && *p != '/' && *p != '\\') p--; - static string parent; - size_t len = p-directory+1; - copystring(parent, directory, len); - return parent; + const char *p = directory + strlen(directory); + while(p > directory && *p != '/' && *p != '\\') p--; + static string parent; + size_t len = p-directory+1; + copystring(parent, directory, len); + return parent; } bool fileexists(const char *path, const char *mode) { - bool exists = true; - if(mode[0]=='w' || mode[0]=='a') path = parentdir(path); -#ifdef WIN32 - if(GetFileAttributes(path[0] ? path : ".\\") == INVALID_FILE_ATTRIBUTES) exists = false; -#else - if(access(path[0] ? path : ".", mode[0]=='w' || mode[0]=='a' ? W_OK : (mode[0]=='d' ? X_OK : R_OK)) == -1) exists = false; -#endif - return exists; + bool exists = true; + if(mode[0]=='w' || mode[0]=='a') path = parentdir(path); + if(access(path[0] ? path : ".", mode[0]=='w' || mode[0]=='a' ? W_OK : (mode[0]=='d' ? X_OK : R_OK)) == -1) exists = false; + return exists; } bool createdir(const char *path) { - size_t len = strlen(path); - if(path[len-1]==PATHDIV) - { - static string strip; - path = copystring(strip, path, len); - } -#ifdef WIN32 - return CreateDirectory(path, NULL)!=0; -#else - return mkdir(path, 0777)==0; -#endif + size_t len = strlen(path); + if(path[len-1]==PATHDIV) + { + static string strip; + path = copystring(strip, path, len); + } + return mkdir(path, 0777)==0; } size_t fixpackagedir(char *dir) { - path(dir); - size_t len = strlen(dir); - if(len > 0 && dir[len-1] != PATHDIV) - { - dir[len] = PATHDIV; - dir[len+1] = '\0'; - } - return len; + path(dir); + size_t len = strlen(dir); + if(len > 0 && dir[len-1] != PATHDIV) + { + dir[len] = PATHDIV; + dir[len+1] = '\0'; + } + return len; } bool subhomedir(char *dst, int len, const char *src) { - const char *sub = strstr(src, "$HOME"); - if(!sub) sub = strchr(src, '~'); - if(sub && sub-src < len) - { -#ifdef WIN32 - char home[MAX_PATH+1]; - home[0] = '\0'; - if(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, home) != S_OK || !home[0]) return false; -#else - const char *home = getenv("HOME"); - if(!home || !home[0]) return false; -#endif - dst[sub-src] = '\0'; - concatstring(dst, home, len); - concatstring(dst, sub+(*sub == '~' ? 1 : strlen("$HOME")), len); - } - return true; + const char *sub = strstr(src, "$HOME"); + if(!sub) sub = strchr(src, '~'); + if(sub && sub-src < len) + { + const char *home = getenv("HOME"); + if(!home || !home[0]) return false; + dst[sub-src] = '\0'; + concatstring(dst, home, len); + concatstring(dst, sub+(*sub == '~' ? 1 : strlen("$HOME")), len); + } + return true; } const char *sethomedir(const char *dir) { - string pdir; - copystring(pdir, dir); - if(!subhomedir(pdir, sizeof(pdir), dir) || !fixpackagedir(pdir)) return NULL; - copystring(homedir, pdir); - return homedir; + string pdir; + copystring(pdir, dir); + if(!subhomedir(pdir, sizeof(pdir), dir) || !fixpackagedir(pdir)) return NULL; + copystring(homedir, pdir); + return homedir; } const char *addpackagedir(const char *dir) { - string pdir; - copystring(pdir, dir); - if(!subhomedir(pdir, sizeof(pdir), dir) || !fixpackagedir(pdir)) return NULL; - char *filter = pdir; - for(;;) - { - static int len = strlen("packages"); - filter = strstr(filter, "packages"); - if(!filter) break; - if(filter > pdir && filter[-1] == PATHDIV && filter[len] == PATHDIV) break; - filter += len; - } - packagedir &pf = packagedirs.add(); - pf.dir = filter ? newstring(pdir, filter-pdir) : newstring(pdir); - pf.dirlen = filter ? filter-pdir : strlen(pdir); - pf.filter = filter ? newstring(filter) : NULL; - pf.filterlen = filter ? strlen(filter) : 0; - return pf.dir; + string pdir; + copystring(pdir, dir); + if(!subhomedir(pdir, sizeof(pdir), dir) || !fixpackagedir(pdir)) return NULL; + char *filter = pdir; + for(;;) + { + static int len = strlen("packages"); + filter = strstr(filter, "packages"); + if(!filter) break; + if(filter > pdir && filter[-1] == PATHDIV && filter[len] == PATHDIV) break; + filter += len; + } + packagedir &pf = packagedirs.add(); + pf.dir = filter ? newstring(pdir, filter-pdir) : newstring(pdir); + pf.dirlen = filter ? filter-pdir : strlen(pdir); + pf.filter = filter ? newstring(filter) : NULL; + pf.filterlen = filter ? strlen(filter) : 0; + return pf.dir; } const char *findfile(const char *filename, const char *mode) { - static string s; - if(homedir[0]) - { - formatstring(s, "%s%s", homedir, filename); - if(fileexists(s, mode)) return s; - if(mode[0]=='w' || mode[0]=='a') - { - string dirs; - copystring(dirs, s); - char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV); - while(dir) - { - *dir = '\0'; - if(!fileexists(dirs, "d") && !createdir(dirs)) return s; - *dir = PATHDIV; - dir = strchr(dir+1, PATHDIV); - } - return s; - } - } - if(mode[0]=='w' || mode[0]=='a') return filename; - loopv(packagedirs) - { - packagedir &pf = packagedirs[i]; - if(pf.filter && strncmp(filename, pf.filter, pf.filterlen)) continue; - formatstring(s, "%s%s", pf.dir, filename); - if(fileexists(s, mode)) return s; - } - if(mode[0]=='e') return NULL; - return filename; + static string s; + if(homedir[0]) + { + formatstring(s, "%s%s", homedir, filename); + if(fileexists(s, mode)) return s; + if(mode[0]=='w' || mode[0]=='a') + { + string dirs; + copystring(dirs, s); + char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV); + while(dir) + { + *dir = '\0'; + if(!fileexists(dirs, "d") && !createdir(dirs)) return s; + *dir = PATHDIV; + dir = strchr(dir+1, PATHDIV); + } + return s; + } + } + if(mode[0]=='w' || mode[0]=='a') return filename; + loopv(packagedirs) + { + packagedir &pf = packagedirs[i]; + if(pf.filter && strncmp(filename, pf.filter, pf.filterlen)) continue; + formatstring(s, "%s%s", pf.dir, filename); + if(fileexists(s, mode)) return s; + } + if(mode[0]=='e') return NULL; + return filename; } bool listdir(const char *dirname, bool rel, const char *ext, vector &files) { - size_t extsize = ext ? strlen(ext)+1 : 0; -#ifdef WIN32 - defformatstring(pathname, rel ? ".\\%s\\*.%s" : "%s\\*.%s", dirname, ext ? ext : "*"); - WIN32_FIND_DATA FindFileData; - HANDLE Find = FindFirstFile(pathname, &FindFileData); - if(Find != INVALID_HANDLE_VALUE) - { - do { - if(!ext) files.add(newstring(FindFileData.cFileName)); - else - { - size_t namelen = strlen(FindFileData.cFileName); - if(namelen > extsize) - { - namelen -= extsize; - if(FindFileData.cFileName[namelen] == '.' && strncmp(FindFileData.cFileName+namelen+1, ext, extsize-1)==0) - files.add(newstring(FindFileData.cFileName, namelen)); - } - } - } while(FindNextFile(Find, &FindFileData)); - FindClose(Find); - return true; - } -#else - defformatstring(pathname, rel ? "./%s" : "%s", dirname); - DIR *d = opendir(pathname); - if(d) - { - struct dirent *de; - while((de = readdir(d)) != NULL) - { - if(!ext) files.add(newstring(de->d_name)); - else - { - size_t namelen = strlen(de->d_name); - if(namelen > extsize) - { - namelen -= extsize; - if(de->d_name[namelen] == '.' && strncmp(de->d_name+namelen+1, ext, extsize-1)==0) - files.add(newstring(de->d_name, namelen)); - } - } - } - closedir(d); - return true; - } -#endif - else return false; + size_t extsize = ext ? strlen(ext)+1 : 0; + defformatstring(pathname, rel ? "./%s" : "%s", dirname); + DIR *d = opendir(pathname); + if(d) + { + struct dirent *de; + while((de = readdir(d)) != NULL) + { + if(!ext) files.add(newstring(de->d_name)); + else + { + size_t namelen = strlen(de->d_name); + if(namelen > extsize) + { + namelen -= extsize; + if(de->d_name[namelen] == '.' && strncmp(de->d_name+namelen+1, ext, extsize-1)==0) + files.add(newstring(de->d_name, namelen)); + } + } + } + closedir(d); + return true; + } + else return false; } int listfiles(const char *dir, const char *ext, vector &files) { - string dirname; - copystring(dirname, dir); - path(dirname); - size_t dirlen = strlen(dirname); - while(dirlen > 1 && dirname[dirlen-1] == PATHDIV) dirname[--dirlen] = '\0'; - int dirs = 0; - if(listdir(dirname, true, ext, files)) dirs++; - string s; - if(homedir[0]) - { - formatstring(s, "%s%s", homedir, dirname); - if(listdir(s, false, ext, files)) dirs++; - } - loopv(packagedirs) - { - packagedir &pf = packagedirs[i]; - if(pf.filter && strncmp(dirname, pf.filter, dirlen == pf.filterlen-1 ? dirlen : pf.filterlen)) - continue; - formatstring(s, "%s%s", pf.dir, dirname); - if(listdir(s, false, ext, files)) dirs++; - } + string dirname; + copystring(dirname, dir); + path(dirname); + size_t dirlen = strlen(dirname); + while(dirlen > 1 && dirname[dirlen-1] == PATHDIV) dirname[--dirlen] = '\0'; + int dirs = 0; + if(listdir(dirname, true, ext, files)) dirs++; + string s; + if(homedir[0]) + { + formatstring(s, "%s%s", homedir, dirname); + if(listdir(s, false, ext, files)) dirs++; + } + loopv(packagedirs) + { + packagedir &pf = packagedirs[i]; + if(pf.filter && strncmp(dirname, pf.filter, dirlen == pf.filterlen-1 ? dirlen : pf.filterlen)) + continue; + formatstring(s, "%s%s", pf.dir, dirname); + if(listdir(s, false, ext, files)) dirs++; + } #ifndef STANDALONE - dirs += listzipfiles(dirname, ext, files); + dirs += listzipfiles(dirname, ext, files); #endif - return dirs; + return dirs; } #ifndef STANDALONE static Sint64 rwopsseek(SDL_RWops *rw, Sint64 pos, int whence) { - stream *f = (stream *)rw->hidden.unknown.data1; - if((!pos && whence==SEEK_CUR) || f->seek(pos, whence)) return (int)f->tell(); - return -1; + stream *f = (stream *)rw->hidden.unknown.data1; + if((!pos && whence==SEEK_CUR) || f->seek(pos, whence)) return (int)f->tell(); + return -1; } static size_t rwopsread(SDL_RWops *rw, void *buf, size_t size, size_t nmemb) { - stream *f = (stream *)rw->hidden.unknown.data1; - return f->read(buf, size*nmemb)/size; + stream *f = (stream *)rw->hidden.unknown.data1; + return f->read(buf, size*nmemb)/size; } static size_t rwopswrite(SDL_RWops *rw, const void *buf, size_t size, size_t nmemb) { - stream *f = (stream *)rw->hidden.unknown.data1; - return f->write(buf, size*nmemb)/size; + stream *f = (stream *)rw->hidden.unknown.data1; + return f->write(buf, size*nmemb)/size; } static int rwopsclose(SDL_RWops *rw) { - return 0; + return 0; } SDL_RWops *stream::rwops() { - SDL_RWops *rw = SDL_AllocRW(); - if(!rw) return NULL; - rw->hidden.unknown.data1 = this; - rw->seek = rwopsseek; - rw->read = rwopsread; - rw->write = rwopswrite; - rw->close = rwopsclose; - return rw; + SDL_RWops *rw = SDL_AllocRW(); + if(!rw) return NULL; + rw->hidden.unknown.data1 = this; + rw->seek = rwopsseek; + rw->read = rwopsread; + rw->write = rwopswrite; + rw->close = rwopsclose; + return rw; } #endif stream::offset stream::size() { - offset pos = tell(), endpos; - if(pos < 0 || !seek(0, SEEK_END)) return -1; - endpos = tell(); - return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1; + offset pos = tell(), endpos; + if(pos < 0 || !seek(0, SEEK_END)) return -1; + endpos = tell(); + return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1; } bool stream::getline(char *str, size_t len) { - loopi(len-1) - { - if(read(&str[i], 1) != 1) { str[i] = '\0'; return i > 0; } - else if(str[i] == '\n') { str[i+1] = '\0'; return true; } - } - if(len > 0) str[len-1] = '\0'; - return true; + loopi(len-1) + { + if(read(&str[i], 1) != 1) { str[i] = '\0'; return i > 0; } + else if(str[i] == '\n') { str[i+1] = '\0'; return true; } + } + if(len > 0) str[len-1] = '\0'; + return true; } size_t stream::printf(const char *fmt, ...) { - char buf[512]; - char *str = buf; - va_list args; - va_start(args, fmt); - int len = vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); - if(len <= 0) return 0; - if(len >= (int)sizeof(buf)) - { - str = new char[len+1]; - va_start(args, fmt); - vsnprintf(str, len+1, fmt, args); - va_end(args); - } - size_t n = write(str, len); - if(str != buf) delete[] str; - return n; + char buf[512]; + char *str = buf; + va_list args; + va_start(args, fmt); + int len = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + if(len <= 0) return 0; + if(len >= (int)sizeof(buf)) + { + str = new char[len+1]; + va_start(args, fmt); + vsnprintf(str, len+1, fmt, args); + va_end(args); + } + size_t n = write(str, len); + if(str != buf) delete[] str; + return n; } struct filestream : stream { - FILE *file; - - filestream() : file(NULL) {} - ~filestream() { close(); } - - bool open(const char *name, const char *mode) - { - if(file) return false; - file = fopen(name, mode); - return file!=NULL; - } - - bool opentemp(const char *name, const char *mode) - { - if(file) return false; - file = tmpfile(); - return file!=NULL; - } - - void close() - { - if(file) { fclose(file); file = NULL; } - } - - bool end() { return feof(file)!=0; } - offset tell() - { - offset off = ftello(file); - return off + 1 >= 0 ? off : -1; - } - bool seek(offset pos, int whence = SEEK_SET) - { - return fseeko(file, pos, whence) >= 0; - } - - size_t read(void *buf, size_t len) { return fread(buf, 1, len, file); } - size_t write(const void *buf, size_t len) { return fwrite(buf, 1, len, file); } - bool flush() { return !fflush(file); } - int getchar() { return fgetc(file); } - bool putchar(int c) { return fputc(c, file)!=EOF; } - bool getline(char *str, size_t len) { return fgets(str, len, file)!=NULL; } - bool putstring(const char *str) { return fputs(str, file)!=EOF; } - - size_t printf(const char *fmt, ...) - { - va_list v; - va_start(v, fmt); - int result = vfprintf(file, fmt, v); - va_end(v); - return max(result, 0); - } + FILE *file; + + filestream() : file(NULL) {} + ~filestream() { close(); } + + bool open(const char *name, const char *mode) + { + if(file) return false; + file = fopen(name, mode); + return file!=NULL; + } + + bool opentemp(const char *name, const char *mode) + { + if(file) return false; + file = tmpfile(); + return file!=NULL; + } + + void close() + { + if(file) { fclose(file); file = NULL; } + } + + bool end() { return feof(file)!=0; } + offset tell() + { + offset off = ftello(file); + return off + 1 >= 0 ? off : -1; + } + bool seek(offset pos, int whence = SEEK_SET) + { + return fseeko(file, pos, whence) >= 0; + } + + size_t read(void *buf, size_t len) { return fread(buf, 1, len, file); } + size_t write(const void *buf, size_t len) { return fwrite(buf, 1, len, file); } + bool flush() { return !fflush(file); } + int getchar() { return fgetc(file); } + bool putchar(int c) { return fputc(c, file)!=EOF; } + bool getline(char *str, size_t len) { return fgets(str, len, file)!=NULL; } + bool putstring(const char *str) { return fputs(str, file)!=EOF; } + + size_t printf(const char *fmt, ...) + { + va_list v; + va_start(v, fmt); + int result = vfprintf(file, fmt, v); + va_end(v); + return max(result, 0); + } }; struct gzstream : stream { - enum - { - MAGIC1 = 0x1F, - MAGIC2 = 0x8B, - BUFSIZE = 16384, - OS_UNIX = 0x03 - }; - - enum - { - F_ASCII = 0x01, - F_CRC = 0x02, - F_EXTRA = 0x04, - F_NAME = 0x08, - F_COMMENT = 0x10, - F_RESERVED = 0xE0 - }; - - stream *file; - z_stream zfile; - uchar *buf; - bool reading, writing, autoclose; - uint crc; - size_t headersize; - - gzstream() : file(NULL), buf(NULL), reading(false), writing(false), autoclose(false), crc(0), headersize(0) - { - zfile.zalloc = NULL; - zfile.zfree = NULL; - zfile.opaque = NULL; - zfile.next_in = zfile.next_out = NULL; - zfile.avail_in = zfile.avail_out = 0; - } - - ~gzstream() - { - close(); - } - - void writeheader() - { - uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX }; - file->write(header, sizeof(header)); - } - - void readbuf(size_t size = BUFSIZE) - { - if(!zfile.avail_in) zfile.next_in = (Bytef *)buf; - size = min(size, size_t(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in])); - size_t n = file->read(zfile.next_in + zfile.avail_in, size); - if(n > 0) zfile.avail_in += n; - } - - uchar readbyte(size_t size = BUFSIZE) - { - if(!zfile.avail_in) readbuf(size); - if(!zfile.avail_in) return 0; - zfile.avail_in--; - return *(uchar *)zfile.next_in++; - } - - void skipbytes(size_t n) - { - while(n > 0 && zfile.avail_in > 0) - { - size_t skipped = min(n, size_t(zfile.avail_in)); - zfile.avail_in -= skipped; - zfile.next_in += skipped; - n -= skipped; - } - if(n <= 0) return; - file->seek(n, SEEK_CUR); - } - - bool checkheader() - { - readbuf(10); - if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED) return false; - uchar flags = readbyte(); - if(flags & F_RESERVED) return false; - skipbytes(6); - if(flags & F_EXTRA) - { - size_t len = readbyte(512); - len |= size_t(readbyte(512))<<8; - skipbytes(len); - } - if(flags & F_NAME) while(readbyte(512)); - if(flags & F_COMMENT) while(readbyte(512)); - if(flags & F_CRC) skipbytes(2); - headersize = size_t(file->tell() - zfile.avail_in); - return zfile.avail_in > 0 || !file->end(); - } - - bool open(stream *f, const char *mode, bool needclose, int level) - { - if(file) return false; - for(; *mode; mode++) - { - if(*mode=='r') { reading = true; break; } - else if(*mode=='w') { writing = true; break; } - } - if(reading) - { - if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK) reading = false; - } - else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, min(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK) writing = false; - if(!reading && !writing) return false; - - file = f; - crc = crc32(0, NULL, 0); - buf = new uchar[BUFSIZE]; - - if(reading) - { - if(!checkheader()) { stopreading(); return false; } - } - else if(writing) writeheader(); - - autoclose = needclose; - return true; - } - - uint getcrc() { return crc; } - - void finishreading() - { - if(!reading) return; - } - - void stopreading() - { - if(!reading) return; - inflateEnd(&zfile); - reading = false; - } - - void finishwriting() - { - if(!writing) return; - for(;;) - { - int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK; - if(err != Z_OK && err != Z_STREAM_END) break; - flushbuf(); - if(err == Z_STREAM_END) break; - } - uchar trailer[8] = - { - uchar(crc&0xFF), uchar((crc>>8)&0xFF), uchar((crc>>16)&0xFF), uchar((crc>>24)&0xFF), - uchar(zfile.total_in&0xFF), uchar((zfile.total_in>>8)&0xFF), uchar((zfile.total_in>>16)&0xFF), uchar((zfile.total_in>>24)&0xFF) - }; - file->write(trailer, sizeof(trailer)); - } - - void stopwriting() - { - if(!writing) return; - deflateEnd(&zfile); - writing = false; - } - - void close() - { - if(reading) finishreading(); - stopreading(); - if(writing) finishwriting(); - stopwriting(); - DELETEA(buf); - if(autoclose) DELETEP(file); - } - - bool end() { return !reading && !writing; } - offset tell() { return reading ? zfile.total_out : (writing ? zfile.total_in : offset(-1)); } - offset rawtell() { return file ? file->tell() : offset(-1); } - - offset size() - { - if(!file) return -1; - offset pos = tell(); - if(!file->seek(-4, SEEK_END)) return -1; - uint isize = file->getlil(); - return file->seek(pos, SEEK_SET) ? isize : offset(-1); - } - - offset rawsize() { return file ? file->size() : offset(-1); } - - bool seek(offset pos, int whence) - { - if(writing || !reading) return false; - - if(whence == SEEK_END) - { - uchar skip[512]; - while(read(skip, sizeof(skip)) == sizeof(skip)); - return !pos; - } - else if(whence == SEEK_CUR) pos += zfile.total_out; - - if(pos >= (offset)zfile.total_out) pos -= zfile.total_out; - else if(pos < 0 || !file->seek(headersize, SEEK_SET)) return false; - else - { - if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf)) - { - zfile.avail_in += zfile.total_in; - zfile.next_in -= zfile.total_in; - } - else - { - zfile.avail_in = 0; - zfile.next_in = NULL; - } - inflateReset(&zfile); - crc = crc32(0, NULL, 0); - } - - uchar skip[512]; - while(pos > 0) - { - size_t skipped = (size_t)min(pos, (offset)sizeof(skip)); - if(read(skip, skipped) != skipped) { stopreading(); return false; } - pos -= skipped; - } - - return true; - } - - size_t read(void *buf, size_t len) - { - if(!reading || !buf || !len) return 0; - zfile.next_out = (Bytef *)buf; - zfile.avail_out = len; - while(zfile.avail_out > 0) - { - if(!zfile.avail_in) - { - readbuf(BUFSIZE); - if(!zfile.avail_in) { stopreading(); break; } - } - int err = inflate(&zfile, Z_NO_FLUSH); - if(err == Z_STREAM_END) { crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); finishreading(); stopreading(); return len - zfile.avail_out; } - else if(err != Z_OK) { stopreading(); break; } - } - crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); - return len - zfile.avail_out; - } - - bool flushbuf(bool full = false) - { - if(full) deflate(&zfile, Z_SYNC_FLUSH); - if(zfile.next_out && zfile.avail_out < BUFSIZE) - { - if(file->write(buf, BUFSIZE - zfile.avail_out) != BUFSIZE - zfile.avail_out || (full && !file->flush())) - return false; - } - zfile.next_out = buf; - zfile.avail_out = BUFSIZE; - return true; - } - - bool flush() { return flushbuf(true); } - - size_t write(const void *buf, size_t len) - { - if(!writing || !buf || !len) return 0; - zfile.next_in = (Bytef *)buf; - zfile.avail_in = len; - while(zfile.avail_in > 0) - { - if(!zfile.avail_out && !flushbuf()) { stopwriting(); break; } - int err = deflate(&zfile, Z_NO_FLUSH); - if(err != Z_OK) { stopwriting(); break; } - } - crc = crc32(crc, (Bytef *)buf, len - zfile.avail_in); - return len - zfile.avail_in; - } + enum + { + MAGIC1 = 0x1F, + MAGIC2 = 0x8B, + BUFSIZE = 16384, + OS_UNIX = 0x03 + }; + + enum + { + F_ASCII = 0x01, + F_CRC = 0x02, + F_EXTRA = 0x04, + F_NAME = 0x08, + F_COMMENT = 0x10, + F_RESERVED = 0xE0 + }; + + stream *file; + z_stream zfile; + uchar *buf; + bool reading, writing, autoclose; + uint crc; + size_t headersize; + + gzstream() : file(NULL), buf(NULL), reading(false), writing(false), autoclose(false), crc(0), headersize(0) + { + zfile.zalloc = NULL; + zfile.zfree = NULL; + zfile.opaque = NULL; + zfile.next_in = zfile.next_out = NULL; + zfile.avail_in = zfile.avail_out = 0; + } + + ~gzstream() + { + close(); + } + + void writeheader() + { + uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX }; + file->write(header, sizeof(header)); + } + + void readbuf(size_t size = BUFSIZE) + { + if(!zfile.avail_in) zfile.next_in = (Bytef *)buf; + size = min(size, size_t(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in])); + size_t n = file->read(zfile.next_in + zfile.avail_in, size); + if(n > 0) zfile.avail_in += n; + } + + uchar readbyte(size_t size = BUFSIZE) + { + if(!zfile.avail_in) readbuf(size); + if(!zfile.avail_in) return 0; + zfile.avail_in--; + return *(uchar *)zfile.next_in++; + } + + void skipbytes(size_t n) + { + while(n > 0 && zfile.avail_in > 0) + { + size_t skipped = min(n, size_t(zfile.avail_in)); + zfile.avail_in -= skipped; + zfile.next_in += skipped; + n -= skipped; + } + if(n <= 0) return; + file->seek(n, SEEK_CUR); + } + + bool checkheader() + { + readbuf(10); + if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED) return false; + uchar flags = readbyte(); + if(flags & F_RESERVED) return false; + skipbytes(6); + if(flags & F_EXTRA) + { + size_t len = readbyte(512); + len |= size_t(readbyte(512))<<8; + skipbytes(len); + } + if(flags & F_NAME) while(readbyte(512)); + if(flags & F_COMMENT) while(readbyte(512)); + if(flags & F_CRC) skipbytes(2); + headersize = size_t(file->tell() - zfile.avail_in); + return zfile.avail_in > 0 || !file->end(); + } + + bool open(stream *f, const char *mode, bool needclose, int level) + { + if(file) return false; + for(; *mode; mode++) + { + if(*mode=='r') { reading = true; break; } + else if(*mode=='w') { writing = true; break; } + } + if(reading) + { + if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK) reading = false; + } + else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, min(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK) writing = false; + if(!reading && !writing) return false; + + file = f; + crc = crc32(0, NULL, 0); + buf = new uchar[BUFSIZE]; + + if(reading) + { + if(!checkheader()) { stopreading(); return false; } + } + else if(writing) writeheader(); + + autoclose = needclose; + return true; + } + + uint getcrc() { return crc; } + + void finishreading() + { + if(!reading) return; + } + + void stopreading() + { + if(!reading) return; + inflateEnd(&zfile); + reading = false; + } + + void finishwriting() + { + if(!writing) return; + for(;;) + { + int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK; + if(err != Z_OK && err != Z_STREAM_END) break; + flushbuf(); + if(err == Z_STREAM_END) break; + } + uchar trailer[8] = + { + uchar(crc&0xFF), uchar((crc>>8)&0xFF), uchar((crc>>16)&0xFF), uchar((crc>>24)&0xFF), + uchar(zfile.total_in&0xFF), uchar((zfile.total_in>>8)&0xFF), uchar((zfile.total_in>>16)&0xFF), uchar((zfile.total_in>>24)&0xFF) + }; + file->write(trailer, sizeof(trailer)); + } + + void stopwriting() + { + if(!writing) return; + deflateEnd(&zfile); + writing = false; + } + + void close() + { + if(reading) finishreading(); + stopreading(); + if(writing) finishwriting(); + stopwriting(); + DELETEA(buf); + if(autoclose) DELETEP(file); + } + + bool end() { return !reading && !writing; } + offset tell() { return reading ? zfile.total_out : (writing ? zfile.total_in : offset(-1)); } + offset rawtell() { return file ? file->tell() : offset(-1); } + + offset size() + { + if(!file) return -1; + offset pos = tell(); + if(!file->seek(-4, SEEK_END)) return -1; + uint isize = file->getlil(); + return file->seek(pos, SEEK_SET) ? isize : offset(-1); + } + + offset rawsize() { return file ? file->size() : offset(-1); } + + bool seek(offset pos, int whence) + { + if(writing || !reading) return false; + + if(whence == SEEK_END) + { + uchar skip[512]; + while(read(skip, sizeof(skip)) == sizeof(skip)); + return !pos; + } + else if(whence == SEEK_CUR) pos += zfile.total_out; + + if(pos >= (offset)zfile.total_out) pos -= zfile.total_out; + else if(pos < 0 || !file->seek(headersize, SEEK_SET)) return false; + else + { + if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf)) + { + zfile.avail_in += zfile.total_in; + zfile.next_in -= zfile.total_in; + } + else + { + zfile.avail_in = 0; + zfile.next_in = NULL; + } + inflateReset(&zfile); + crc = crc32(0, NULL, 0); + } + + uchar skip[512]; + while(pos > 0) + { + size_t skipped = (size_t)min(pos, (offset)sizeof(skip)); + if(read(skip, skipped) != skipped) { stopreading(); return false; } + pos -= skipped; + } + + return true; + } + + size_t read(void *buf, size_t len) + { + if(!reading || !buf || !len) return 0; + zfile.next_out = (Bytef *)buf; + zfile.avail_out = len; + while(zfile.avail_out > 0) + { + if(!zfile.avail_in) + { + readbuf(BUFSIZE); + if(!zfile.avail_in) { stopreading(); break; } + } + int err = inflate(&zfile, Z_NO_FLUSH); + if(err == Z_STREAM_END) { crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); finishreading(); stopreading(); return len - zfile.avail_out; } + else if(err != Z_OK) { stopreading(); break; } + } + crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); + return len - zfile.avail_out; + } + + bool flushbuf(bool full = false) + { + if(full) deflate(&zfile, Z_SYNC_FLUSH); + if(zfile.next_out && zfile.avail_out < BUFSIZE) + { + if(file->write(buf, BUFSIZE - zfile.avail_out) != BUFSIZE - zfile.avail_out || (full && !file->flush())) + return false; + } + zfile.next_out = buf; + zfile.avail_out = BUFSIZE; + return true; + } + + bool flush() { return flushbuf(true); } + + size_t write(const void *buf, size_t len) + { + if(!writing || !buf || !len) return 0; + zfile.next_in = (Bytef *)buf; + zfile.avail_in = len; + while(zfile.avail_in > 0) + { + if(!zfile.avail_out && !flushbuf()) { stopwriting(); break; } + int err = deflate(&zfile, Z_NO_FLUSH); + if(err != Z_OK) { stopwriting(); break; } + } + crc = crc32(crc, (Bytef *)buf, len - zfile.avail_in); + return len - zfile.avail_in; + } }; struct utf8stream : stream { - enum - { - BUFSIZE = 4096 - }; - stream *file; - offset pos; - size_t bufread, bufcarry, buflen; - bool reading, writing, autoclose; - uchar buf[BUFSIZE]; - - utf8stream() : file(NULL), pos(0), bufread(0), bufcarry(0), buflen(0), reading(false), writing(false), autoclose(false) - { - } - - ~utf8stream() - { - close(); - } - - bool readbuf(size_t size = BUFSIZE) - { - if(bufread >= bufcarry) { if(bufcarry > 0 && bufcarry < buflen) memmove(buf, &buf[bufcarry], buflen - bufcarry); buflen -= bufcarry; bufread = bufcarry = 0; } - size_t n = file->read(&buf[buflen], min(size, BUFSIZE - buflen)); - if(n <= 0) return false; - buflen += n; - size_t carry = bufcarry; - bufcarry += decodeutf8(&buf[bufcarry], BUFSIZE-bufcarry, &buf[bufcarry], buflen-bufcarry, &carry); - if(carry > bufcarry && carry < buflen) { memmove(&buf[bufcarry], &buf[carry], buflen - carry); buflen -= carry - bufcarry; } - return true; - } - - bool checkheader() - { - size_t n = file->read(buf, 3); - if(n == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) return true; - buflen = n; - return false; - } - - bool open(stream *f, const char *mode, bool needclose) - { - if(file) return false; - for(; *mode; mode++) - { - if(*mode=='r') { reading = true; break; } - else if(*mode=='w') { writing = true; break; } - } - if(!reading && !writing) return false; - - file = f; - - if(reading) checkheader(); - - autoclose = needclose; - return true; - } - - void finishreading() - { - if(!reading) return; - } - - void stopreading() - { - if(!reading) return; - reading = false; - } - - void stopwriting() - { - if(!writing) return; - writing = false; - } - - void close() - { - stopreading(); - stopwriting(); - if(autoclose) DELETEP(file); - } - - bool end() { return !reading && !writing; } - offset tell() { return reading || writing ? pos : offset(-1); } - - bool seek(offset off, int whence) - { - if(writing || !reading) return false; - - if(whence == SEEK_END) - { - uchar skip[512]; - while(read(skip, sizeof(skip)) == sizeof(skip)); - return !off; - } - else if(whence == SEEK_CUR) off += pos; - - if(off >= pos) off -= pos; - else if(off < 0 || !file->seek(0, SEEK_SET)) return false; - else - { - bufread = bufcarry = buflen = 0; - pos = 0; - checkheader(); - } - - uchar skip[512]; - while(off > 0) - { - size_t skipped = (size_t)min(off, (offset)sizeof(skip)); - if(read(skip, skipped) != skipped) { stopreading(); return false; } - off -= skipped; - } - - return true; - } - - size_t read(void *dst, size_t len) - { - if(!reading || !dst || !len) return 0; - size_t next = 0; - while(next < len) - { - if(bufread >= bufcarry) { if(readbuf(BUFSIZE)) continue; stopreading(); break; } - size_t n = min(len - next, bufcarry - bufread); - memcpy(&((uchar *)dst)[next], &buf[bufread], n); - next += n; - bufread += n; - } - pos += next; - return next; - } - - bool getline(char *dst, size_t len) - { - if(!reading || !dst || !len) return false; - --len; - size_t next = 0; - while(next < len) - { - if(bufread >= bufcarry) { if(readbuf(BUFSIZE)) continue; stopreading(); if(!next) return false; break; } - size_t n = min(len - next, bufcarry - bufread); - uchar *endline = (uchar *)memchr(&buf[bufread], '\n', n); - if(endline) { n = endline+1 - &buf[bufread]; len = next + n; } - memcpy(&((uchar *)dst)[next], &buf[bufread], n); - next += n; - bufread += n; - } - dst[next] = '\0'; - pos += next; - return true; - } - - size_t write(const void *src, size_t len) - { - if(!writing || !src || !len) return 0; - uchar dst[512]; - size_t next = 0; - while(next < len) - { - size_t carry = 0, n = encodeutf8(dst, sizeof(dst), &((uchar *)src)[next], len - next, &carry); - if(n > 0 && file->write(dst, n) != n) { stopwriting(); break; } - next += carry; - } - pos += next; - return next; - } - - bool flush() { return file->flush(); } + enum + { + BUFSIZE = 4096 + }; + stream *file; + offset pos; + size_t bufread, bufcarry, buflen; + bool reading, writing, autoclose; + uchar buf[BUFSIZE]; + + utf8stream() : file(NULL), pos(0), bufread(0), bufcarry(0), buflen(0), reading(false), writing(false), autoclose(false) + { + } + + ~utf8stream() + { + close(); + } + + bool readbuf(size_t size = BUFSIZE) + { + if(bufread >= bufcarry) { if(bufcarry > 0 && bufcarry < buflen) memmove(buf, &buf[bufcarry], buflen - bufcarry); buflen -= bufcarry; bufread = bufcarry = 0; } + size_t n = file->read(&buf[buflen], min(size, BUFSIZE - buflen)); + if(n <= 0) return false; + buflen += n; + size_t carry = bufcarry; + bufcarry += decodeutf8(&buf[bufcarry], BUFSIZE-bufcarry, &buf[bufcarry], buflen-bufcarry, &carry); + if(carry > bufcarry && carry < buflen) { memmove(&buf[bufcarry], &buf[carry], buflen - carry); buflen -= carry - bufcarry; } + return true; + } + + bool checkheader() + { + size_t n = file->read(buf, 3); + if(n == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) return true; + buflen = n; + return false; + } + + bool open(stream *f, const char *mode, bool needclose) + { + if(file) return false; + for(; *mode; mode++) + { + if(*mode=='r') { reading = true; break; } + else if(*mode=='w') { writing = true; break; } + } + if(!reading && !writing) return false; + + file = f; + + if(reading) checkheader(); + + autoclose = needclose; + return true; + } + + void finishreading() + { + if(!reading) return; + } + + void stopreading() + { + if(!reading) return; + reading = false; + } + + void stopwriting() + { + if(!writing) return; + writing = false; + } + + void close() + { + stopreading(); + stopwriting(); + if(autoclose) DELETEP(file); + } + + bool end() { return !reading && !writing; } + offset tell() { return reading || writing ? pos : offset(-1); } + + bool seek(offset off, int whence) + { + if(writing || !reading) return false; + + if(whence == SEEK_END) + { + uchar skip[512]; + while(read(skip, sizeof(skip)) == sizeof(skip)); + return !off; + } + else if(whence == SEEK_CUR) off += pos; + + if(off >= pos) off -= pos; + else if(off < 0 || !file->seek(0, SEEK_SET)) return false; + else + { + bufread = bufcarry = buflen = 0; + pos = 0; + checkheader(); + } + + uchar skip[512]; + while(off > 0) + { + size_t skipped = (size_t)min(off, (offset)sizeof(skip)); + if(read(skip, skipped) != skipped) { stopreading(); return false; } + off -= skipped; + } + + return true; + } + + size_t read(void *dst, size_t len) + { + if(!reading || !dst || !len) return 0; + size_t next = 0; + while(next < len) + { + if(bufread >= bufcarry) { if(readbuf(BUFSIZE)) continue; stopreading(); break; } + size_t n = min(len - next, bufcarry - bufread); + memcpy(&((uchar *)dst)[next], &buf[bufread], n); + next += n; + bufread += n; + } + pos += next; + return next; + } + + bool getline(char *dst, size_t len) + { + if(!reading || !dst || !len) return false; + --len; + size_t next = 0; + while(next < len) + { + if(bufread >= bufcarry) { if(readbuf(BUFSIZE)) continue; stopreading(); if(!next) return false; break; } + size_t n = min(len - next, bufcarry - bufread); + uchar *endline = (uchar *)memchr(&buf[bufread], '\n', n); + if(endline) { n = endline+1 - &buf[bufread]; len = next + n; } + memcpy(&((uchar *)dst)[next], &buf[bufread], n); + next += n; + bufread += n; + } + dst[next] = '\0'; + pos += next; + return true; + } + + size_t write(const void *src, size_t len) + { + if(!writing || !src || !len) return 0; + uchar dst[512]; + size_t next = 0; + while(next < len) + { + size_t carry = 0, n = encodeutf8(dst, sizeof(dst), &((uchar *)src)[next], len - next, &carry); + if(n > 0 && file->write(dst, n) != n) { stopwriting(); break; } + next += carry; + } + pos += next; + return next; + } + + bool flush() { return file->flush(); } }; stream *openrawfile(const char *filename, const char *mode) { - const char *found = findfile(filename, mode); - if(!found) return NULL; - filestream *file = new filestream; - if(!file->open(found, mode)) { delete file; return NULL; } - return file; + const char *found = findfile(filename, mode); + if(!found) return NULL; + filestream *file = new filestream; + if(!file->open(found, mode)) { delete file; return NULL; } + return file; } stream *openfile(const char *filename, const char *mode) { #ifndef STANDALONE - stream *s = openzipfile(filename, mode); - if(s) return s; + stream *s = openzipfile(filename, mode); + if(s) return s; #endif - return openrawfile(filename, mode); + return openrawfile(filename, mode); } stream *opentempfile(const char *name, const char *mode) { - const char *found = findfile(name, mode); - filestream *file = new filestream; - if(!file->opentemp(found ? found : name, mode)) { delete file; return NULL; } - return file; + const char *found = findfile(name, mode); + filestream *file = new filestream; + if(!file->opentemp(found ? found : name, mode)) { delete file; return NULL; } + return file; } stream *opengzfile(const char *filename, const char *mode, stream *file, int level) { - stream *source = file ? file : openfile(filename, mode); - if(!source) return NULL; - gzstream *gz = new gzstream; - if(!gz->open(source, mode, !file, level)) { if(!file) delete source; delete gz; return NULL; } - return gz; + stream *source = file ? file : openfile(filename, mode); + if(!source) return NULL; + gzstream *gz = new gzstream; + if(!gz->open(source, mode, !file, level)) { if(!file) delete source; delete gz; return NULL; } + return gz; } stream *openutf8file(const char *filename, const char *mode, stream *file) { - stream *source = file ? file : openfile(filename, mode); - if(!source) return NULL; - utf8stream *utf8 = new utf8stream; - if(!utf8->open(source, mode, !file)) { if(!file) delete source; delete utf8; return NULL; } - return utf8; + stream *source = file ? file : openfile(filename, mode); + if(!source) return NULL; + utf8stream *utf8 = new utf8stream; + if(!utf8->open(source, mode, !file)) { if(!file) delete source; delete utf8; return NULL; } + return utf8; } char *loadfile(const char *fn, size_t *size, bool utf8) { - stream *f = openfile(fn, "rb"); - if(!f) return NULL; - stream::offset fsize = f->size(); - if(fsize <= 0) { delete f; return NULL; } - size_t len = fsize; - char *buf = new (false) char[len+1]; - if(!buf) { delete f; return NULL; } - size_t offset = 0; - if(utf8 && len >= 3) - { - if(f->read(buf, 3) != 3) { delete f; delete[] buf; return NULL; } - if(((uchar *)buf)[0] == 0xEF && ((uchar *)buf)[1] == 0xBB && ((uchar *)buf)[2] == 0xBF) len -= 3; - else offset += 3; - } - size_t rlen = f->read(&buf[offset], len-offset); - delete f; - if(rlen != len-offset) { delete[] buf; return NULL; } - if(utf8) len = decodeutf8((uchar *)buf, len, (uchar *)buf, len); - buf[len] = '\0'; - if(size!=NULL) *size = len; - return buf; + stream *f = openfile(fn, "rb"); + if(!f) return NULL; + stream::offset fsize = f->size(); + if(fsize <= 0) { delete f; return NULL; } + size_t len = fsize; + char *buf = new (false) char[len+1]; + if(!buf) { delete f; return NULL; } + size_t offset = 0; + if(utf8 && len >= 3) + { + if(f->read(buf, 3) != 3) { delete f; delete[] buf; return NULL; } + if(((uchar *)buf)[0] == 0xEF && ((uchar *)buf)[1] == 0xBB && ((uchar *)buf)[2] == 0xBF) len -= 3; + else offset += 3; + } + size_t rlen = f->read(&buf[offset], len-offset); + delete f; + if(rlen != len-offset) { delete[] buf; return NULL; } + if(utf8) len = decodeutf8((uchar *)buf, len, (uchar *)buf, len); + buf[len] = '\0'; + if(size!=NULL) *size = len; + return buf; } diff --git a/src/shared/tools.cpp b/src/shared/tools.cpp index 0ae7559..1b49a8c 100644 --- a/src/shared/tools.cpp +++ b/src/shared/tools.cpp @@ -4,16 +4,16 @@ void *operator new(size_t size) { - void *p = malloc(size); - if(!p) abort(); - return p; + void *p = malloc(size); + if(!p) abort(); + return p; } void *operator new[](size_t size) { - void *p = malloc(size); - if(!p) abort(); - return p; + void *p = malloc(size); + if(!p) abort(); + return p; } void operator delete(void *p) { if(p) free(p); } @@ -24,16 +24,16 @@ void operator delete[](void *p, size_t n) { (void) n; if(p) free(p); } void *operator new(size_t size, bool err) { - void *p = malloc(size); - if(!p && err) abort(); - return p; + void *p = malloc(size); + if(!p && err) abort(); + return p; } void *operator new[](size_t size, bool err) { - void *p = malloc(size); - if(!p && err) abort(); - return p; + void *p = malloc(size); + if(!p && err) abort(); + return p; } ////////////////////////// rnd numbers //////////////////////////////////////// @@ -47,27 +47,27 @@ static int next = N; void seedMT(uint seed) { - state[0] = seed; - for(uint i = 1; i < N; i++) - state[i] = seed = 1812433253U * (seed ^ (seed >> 30)) + i; - next = 0; + state[0] = seed; + for(uint i = 1; i < N; i++) + state[i] = seed = 1812433253U * (seed ^ (seed >> 30)) + i; + next = 0; } uint randomMT() { - int cur = next; - if(++next >= N) - { - if(next > N) { seedMT(5489U + time(NULL)); cur = next++; } - else next = 0; - } - uint y = (state[cur] & 0x80000000U) | (state[next] & 0x7FFFFFFFU); - state[cur] = y = state[cur < N-M ? cur + M : cur + M-N] ^ (y >> 1) ^ (-int(y & 1U) & K); - y ^= (y >> 11); - y ^= (y << 7) & 0x9D2C5680U; - y ^= (y << 15) & 0xEFC60000U; - y ^= (y >> 18); - return y; + int cur = next; + if(++next >= N) + { + if(next > N) { seedMT(5489U + time(NULL)); cur = next++; } + else next = 0; + } + uint y = (state[cur] & 0x80000000U) | (state[next] & 0x7FFFFFFFU); + state[cur] = y = state[cur < N-M ? cur + M : cur + M-N] ^ (y >> 1) ^ (-int(y & 1U) & K); + y ^= (y >> 11); + y ^= (y << 7) & 0x9D2C5680U; + y ^= (y << 15) & 0xEFC60000U; + y ^= (y >> 18); + return y; } ///////////////////////// network /////////////////////// @@ -77,9 +77,9 @@ uint randomMT() template static inline void putint_(T &p, int n) { - if(n<128 && n>-127) p.put(n); - else if(n<0x8000 && n>=-0x8000) { p.put(0x80); p.put(n); p.put(n>>8); } - else { p.put(0x81); p.put(n); p.put(n>>8); p.put(n>>16); p.put(n>>24); } + if(n<128 && n>-127) p.put(n); + else if(n<0x8000 && n>=-0x8000) { p.put(0x80); p.put(n); p.put(n>>8); } + else { p.put(0x81); p.put(n); p.put(n>>8); p.put(n>>16); p.put(n>>24); } } void putint(ucharbuf &p, int n) { putint_(p, n); } void putint(packetbuf &p, int n) { putint_(p, n); } @@ -87,35 +87,35 @@ void putint(vector &p, int n) { putint_(p, n); } int getint(ucharbuf &p) { - int c = (schar)p.get(); - if(c==-128) { int n = p.get(); n |= ((schar)p.get())<<8; return n; } - else if(c==-127) { int n = p.get(); n |= p.get()<<8; n |= p.get()<<16; return n|(p.get()<<24); } - else return c; + int c = (schar)p.get(); + if(c==-128) { int n = p.get(); n |= ((schar)p.get())<<8; return n; } + else if(c==-127) { int n = p.get(); n |= p.get()<<8; n |= p.get()<<16; return n|(p.get()<<24); } + else return c; } // much smaller encoding for unsigned integers up to 28 bits, but can handle signed template static inline void putuint_(T &p, int n) { - if(n < 0 || n >= (1<<21)) - { - p.put(0x80 | (n & 0x7F)); - p.put(0x80 | ((n >> 7) & 0x7F)); - p.put(0x80 | ((n >> 14) & 0x7F)); - p.put(n >> 21); - } - else if(n < (1<<7)) p.put(n); - else if(n < (1<<14)) - { - p.put(0x80 | (n & 0x7F)); - p.put(n >> 7); - } - else - { - p.put(0x80 | (n & 0x7F)); - p.put(0x80 | ((n >> 7) & 0x7F)); - p.put(n >> 14); - } + if(n < 0 || n >= (1<<21)) + { + p.put(0x80 | (n & 0x7F)); + p.put(0x80 | ((n >> 7) & 0x7F)); + p.put(0x80 | ((n >> 14) & 0x7F)); + p.put(n >> 21); + } + else if(n < (1<<7)) p.put(n); + else if(n < (1<<14)) + { + p.put(0x80 | (n & 0x7F)); + p.put(n >> 7); + } + else + { + p.put(0x80 | (n & 0x7F)); + p.put(0x80 | ((n >> 7) & 0x7F)); + p.put(n >> 14); + } } void putuint(ucharbuf &p, int n) { putuint_(p, n); } void putuint(packetbuf &p, int n) { putuint_(p, n); } @@ -123,22 +123,22 @@ void putuint(vector &p, int n) { putuint_(p, n); } int getuint(ucharbuf &p) { - int n = p.get(); - if(n & 0x80) - { - n += (p.get() << 7) - 0x80; - if(n & (1<<14)) n += (p.get() << 14) - (1<<14); - if(n & (1<<21)) n += (p.get() << 21) - (1<<21); - if(n & (1<<28)) n |= ~0U<<28; - } - return n; + int n = p.get(); + if(n & 0x80) + { + n += (p.get() << 7) - 0x80; + if(n & (1<<14)) n += (p.get() << 14) - (1<<14); + if(n & (1<<21)) n += (p.get() << 21) - (1<<21); + if(n & (1<<28)) n |= ~0U<<28; + } + return n; } template static inline void putfloat_(T &p, float f) { - lilswap(&f, 1); - p.put((uchar *)&f, sizeof(float)); + lilswap(&f, 1); + p.put((uchar *)&f, sizeof(float)); } void putfloat(ucharbuf &p, float f) { putfloat_(p, f); } void putfloat(packetbuf &p, float f) { putfloat_(p, f); } @@ -146,16 +146,16 @@ void putfloat(vector &p, float f) { putfloat_(p, f); } float getfloat(ucharbuf &p) { - float f; - p.get((uchar *)&f, sizeof(float)); - return lilswap(f); + float f; + p.get((uchar *)&f, sizeof(float)); + return lilswap(f); } template static inline void sendstring_(const char *t, T &p) { - while(*t) putint(p, *t++); - putint(p, 0); + while(*t) putint(p, *t++); + putint(p, 0); } void sendstring(const char *t, ucharbuf &p) { sendstring_(t, p); } void sendstring(const char *t, packetbuf &p) { sendstring_(t, p); } @@ -163,84 +163,84 @@ void sendstring(const char *t, vector &p) { sendstring_(t, p); } void getstring(char *text, ucharbuf &p, size_t len) { - char *t = text; - do - { - if(t>=&text[len]) { text[len-1] = 0; return; } - if(!p.remaining()) { *t = 0; return; } - *t = getint(p); - } - while(*t++); + char *t = text; + do + { + if(t>=&text[len]) { text[len-1] = 0; return; } + if(!p.remaining()) { *t = 0; return; } + *t = getint(p); + } + while(*t++); } void filtertext(char *dst, const char *src, bool whitespace, bool forcespace, size_t len) { - for(int c = uchar(*src); c; c = uchar(*++src)) - { - if(c == '\f') - { - if(!*++src) break; - continue; - } - if(!iscubeprint(c)) - { - if(!iscubespace(c) || !whitespace) continue; - if(forcespace) c = ' '; - } - *dst++ = c; - if(!--len) break; - } - *dst = '\0'; + for(int c = uchar(*src); c; c = uchar(*++src)) + { + if(c == '\f') + { + if(!*++src) break; + continue; + } + if(!iscubeprint(c)) + { + if(!iscubespace(c) || !whitespace) continue; + if(forcespace) c = ' '; + } + *dst++ = c; + if(!--len) break; + } + *dst = '\0'; } void ipmask::parse(const char *name) { - union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ipconv, maskconv; - ipconv.i = 0; - maskconv.i = 0; - loopi(4) - { - char *end = NULL; - int n = strtol(name, &end, 10); - if(!end) break; - if(end > name) { ipconv.b[i] = n; maskconv.b[i] = 0xFF; } - name = end; - while(int c = *name) - { - ++name; - if(c == '.') break; - if(c == '/') - { - int range = clamp(int(strtol(name, NULL, 10)), 0, 32); - mask = range ? ENET_HOST_TO_NET_32(0xFFffFFff << (32 - range)) : maskconv.i; - ip = ipconv.i & mask; - return; - } - } - } - ip = ipconv.i; - mask = maskconv.i; + union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ipconv, maskconv; + ipconv.i = 0; + maskconv.i = 0; + loopi(4) + { + char *end = NULL; + int n = strtol(name, &end, 10); + if(!end) break; + if(end > name) { ipconv.b[i] = n; maskconv.b[i] = 0xFF; } + name = end; + while(int c = *name) + { + ++name; + if(c == '.') break; + if(c == '/') + { + int range = clamp(int(strtol(name, NULL, 10)), 0, 32); + mask = range ? ENET_HOST_TO_NET_32(0xFFffFFff << (32 - range)) : maskconv.i; + ip = ipconv.i & mask; + return; + } + } + } + ip = ipconv.i; + mask = maskconv.i; } int ipmask::print(char *buf) const { - char *start = buf; - union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ipconv, maskconv; - ipconv.i = ip; - maskconv.i = mask; - int lastdigit = -1; - loopi(4) if(maskconv.b[i]) - { - if(lastdigit >= 0) *buf++ = '.'; - loopj(i - lastdigit - 1) { *buf++ = '*'; *buf++ = '.'; } - buf += sprintf(buf, "%d", ipconv.b[i]); - lastdigit = i; - } - enet_uint32 bits = ~ENET_NET_TO_HOST_32(mask); - int range = 32; - for(; (bits&0xFF) == 0xFF; bits >>= 8) range -= 8; - for(; bits&1; bits >>= 1) --range; - if(!bits && range%8) buf += sprintf(buf, "/%d", range); - return int(buf-start); + char *start = buf; + union { uchar b[sizeof(enet_uint32)]; enet_uint32 i; } ipconv, maskconv; + ipconv.i = ip; + maskconv.i = mask; + int lastdigit = -1; + loopi(4) if(maskconv.b[i]) + { + if(lastdigit >= 0) *buf++ = '.'; + loopj(i - lastdigit - 1) { *buf++ = '*'; *buf++ = '.'; } + buf += sprintf(buf, "%d", ipconv.b[i]); + lastdigit = i; + } + enet_uint32 bits = ~ENET_NET_TO_HOST_32(mask); + int range = 32; + for(; (bits&0xFF) == 0xFF; bits >>= 8) range -= 8; + for(; bits&1; bits >>= 1) --range; + if(!bits && range%8) buf += sprintf(buf, "/%d", range); + return int(buf-start); } diff --git a/src/shared/tools.h b/src/shared/tools.h index c66f5bd..0550668 100644 --- a/src/shared/tools.h +++ b/src/shared/tools.h @@ -16,23 +16,9 @@ typedef unsigned long ulong; typedef signed long long int llong; typedef unsigned long long int ullong; -#ifdef _DEBUG -#define ASSERT(c) assert(c) -#else #define ASSERT(c) if(c) {} -#endif - -#if defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1400) #define RESTRICT __restrict -#else -#define RESTRICT -#endif - -#ifdef __GNUC__ #define UNUSED __attribute__((unused)) -#else -#define UNUSED -#endif void *operator new(size_t, bool); void *operator new[](size_t, bool); @@ -47,9 +33,9 @@ inline void operator delete[](void *, void *) {} template static inline void swap(T &a, T &b) { - T t = a; - a = b; - b = t; + T t = a; + a = b; + b = t; } #ifdef max #undef max @@ -60,42 +46,20 @@ static inline void swap(T &a, T &b) template static inline T max(T a, T b) { - return a > b ? a : b; + return a > b ? a : b; } template static inline T min(T a, T b) { - return a < b ? a : b; + return a < b ? a : b; } template static inline T clamp(T a, U b, U c) { - return max(T(b), min(a, T(c))); + return max(T(b), min(a, T(c))); } -#ifdef __GNUC__ #define bitscan(mask) (__builtin_ffs(mask)-1) -#else -#ifdef WIN32 -#pragma intrinsic(_BitScanForward) -static inline int bitscan(uint mask) -{ - ulong i; - return _BitScanForward(&i, mask) ? i : -1; -} -#else -static inline int bitscan(uint mask) -{ - if(!mask) return -1; - int i = 1; - if(!(mask&0xFFFF)) { i += 16; mask >>= 16; } - if(!(mask&0xFF)) { i += 8; mask >>= 8; } - if(!(mask&0xF)) { i += 4; mask >>= 4; } - if(!(mask&3)) { i += 2; mask >>= 2; } - return i - (mask&1); -} -#endif -#endif #define rnd(x) ((int)(randomMT()&0x7FFFFFFF)%(x)) #define rndscale(x) (float((randomMT()&0x7FFFFFFF)*double(x)/double(0x7FFFFFFF))) @@ -121,37 +85,11 @@ static inline int bitscan(uint mask) #define SQRT3 (1.7320508f) #define RAD (PI / 180.0f) -#ifdef WIN32 -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif -#ifndef M_LN2 -#define M_LN2 0.693147180559945309417 -#endif - -#ifndef __GNUC__ -#pragma warning (3: 4189) // local variable is initialized but not referenced -#pragma warning (disable: 4244) // conversion from 'int' to 'float', possible loss of data -#pragma warning (disable: 4267) // conversion from 'size_t' to 'int', possible loss of data -#pragma warning (disable: 4355) // 'this' : used in base member initializer list -#pragma warning (disable: 4996) // 'strncpy' was declared deprecated -#endif - -#define strcasecmp _stricmp -#define strncasecmp _strnicmp -#define PATHDIV '\\' - -#else #define __cdecl #define _vsnprintf vsnprintf #define PATHDIV '/' -#endif -#ifdef __GNUC__ #define PRINTFARGS(fmt, args) __attribute__((format(printf, fmt, args))) -#else -#define PRINTFARGS(fmt, args) -#endif // easy safe strings @@ -163,10 +101,10 @@ template inline void vformatstring(char (&d)[N], const char *fmt, va_l inline char *copystring(char *d, const char *s, size_t len) { - size_t slen = min(strlen(s), len-1); - memcpy(d, s, slen); - d[slen] = 0; - return d; + size_t slen = min(strlen(s), len-1); + memcpy(d, s, slen); + d[slen] = 0; + return d; } template inline char *copystring(char (&d)[N], const char *s) { return copystring(d, s, N); } @@ -175,40 +113,40 @@ template inline char *concatstring(char (&d)[N], const char *s) { retu inline char *prependstring(char *d, const char *s, size_t len) { - size_t slen = min(strlen(s), len); - memmove(&d[slen], d, min(len - slen, strlen(d) + 1)); - memcpy(d, s, slen); - d[len-1] = 0; - return d; + size_t slen = min(strlen(s), len); + memmove(&d[slen], d, min(len - slen, strlen(d) + 1)); + memcpy(d, s, slen); + d[len-1] = 0; + return d; } template inline char *prependstring(char (&d)[N], const char *s) { return prependstring(d, s, N); } inline void nformatstring(char *d, int len, const char *fmt, ...) PRINTFARGS(3, 4); inline void nformatstring(char *d, int len, const char *fmt, ...) { - va_list v; - va_start(v, fmt); - vformatstring(d, fmt, v, len); - va_end(v); + va_list v; + va_start(v, fmt); + vformatstring(d, fmt, v, len); + va_end(v); } template inline void formatstring(char (&d)[N], const char *fmt, ...) PRINTFARGS(2, 3); template inline void formatstring(char (&d)[N], const char *fmt, ...) { - va_list v; - va_start(v, fmt); - vformatstring(d, fmt, v, int(N)); - va_end(v); + va_list v; + va_start(v, fmt); + vformatstring(d, fmt, v, int(N)); + va_end(v); } template inline void concformatstring(char (&d)[N], const char *fmt, ...) PRINTFARGS(2, 3); template inline void concformatstring(char (&d)[N], const char *fmt, ...) { - va_list v; - va_start(v, fmt); - int len = strlen(d); - vformatstring(d + len, fmt, v, int(N) - len); - va_end(v); + va_list v; + va_start(v, fmt); + int len = strlen(d); + vformatstring(d + len, fmt, v, int(N) - len); + va_end(v); } #define defformatstring(d,...) string d; formatstring(d, __VA_ARGS__) @@ -216,14 +154,14 @@ template inline void concformatstring(char (&d)[N], const char *fmt, . template inline bool matchstring(const char *s, size_t len, const char (&d)[N]) { - return len == N-1 && !memcmp(s, d, N-1); + return len == N-1 && !memcmp(s, d, N-1); } -inline char *newstring(size_t l) { return new char[l+1]; } +inline char *newstring(size_t l) { return new char[l+1]; } inline char *newstring(const char *s, size_t l) { return copystring(newstring(l), s, l+1); } -inline char *newstring(const char *s) { size_t l = strlen(s); char *d = newstring(l); memcpy(d, s, l+1); return d; } +inline char *newstring(const char *s) { size_t l = strlen(s); char *d = newstring(l); memcpy(d, s, l+1); return d; } -#define loopv(v) for(int i = 0; i<(v).length(); i++) +#define loopv(v) for(int i = 0; i<(v).length(); i++) #define loopvj(v) for(int j = 0; j<(v).length(); j++) #define loopvk(v) for(int k = 0; k<(v).length(); k++) #define loopvrev(v) for(int i = (v).length()-1; i>=0; i--) @@ -235,100 +173,100 @@ template inline void memclear(T (&p)[N]) { memset((void *)p, template struct databuf { - enum - { - OVERREAD = 1<<0, - OVERWROTE = 1<<1 - }; - - T *buf; - int len, maxlen; - uchar flags; - - databuf() : buf(NULL), len(0), maxlen(0), flags(0) {} - - template - databuf(T *buf, U maxlen) : buf(buf), len(0), maxlen((int)maxlen), flags(0) {} - - void reset() - { - len = 0; - flags = 0; - } - - void reset(T *buf_, int maxlen_) - { - reset(); - buf = buf_; - maxlen = maxlen_; - } - - const T &get() - { - static T overreadval = 0; - if(len= n; } - - void forceoverread() - { - len = maxlen; - flags |= OVERREAD; - } + enum + { + OVERREAD = 1<<0, + OVERWROTE = 1<<1 + }; + + T *buf; + int len, maxlen; + uchar flags; + + databuf() : buf(NULL), len(0), maxlen(0), flags(0) {} + + template + databuf(T *buf, U maxlen) : buf(buf), len(0), maxlen((int)maxlen), flags(0) {} + + void reset() + { + len = 0; + flags = 0; + } + + void reset(T *buf_, int maxlen_) + { + reset(); + buf = buf_; + maxlen = maxlen_; + } + + const T &get() + { + static T overreadval = 0; + if(len= n; } + + void forceoverread() + { + len = maxlen; + flags |= OVERREAD; + } }; typedef databuf charbuf; @@ -336,60 +274,60 @@ typedef databuf ucharbuf; struct packetbuf : ucharbuf { - ENetPacket *packet; - int growth; - - packetbuf(ENetPacket *packet) : ucharbuf(packet->data, packet->dataLength), packet(packet), growth(0) {} - packetbuf(int growth, int pflags = 0) : growth(growth) - { - packet = enet_packet_create(NULL, growth, pflags); - buf = (uchar *)packet->data; - maxlen = packet->dataLength; - } - ~packetbuf() { cleanup(); } - - void reliable() { packet->flags |= ENET_PACKET_FLAG_RELIABLE; } - - void resize(int n) - { - enet_packet_resize(packet, n); - buf = (uchar *)packet->data; - maxlen = packet->dataLength; - } - - void checkspace(int n) - { - if(len + n > maxlen && packet && growth > 0) resize(max(len + n, maxlen + growth)); - } - - ucharbuf subbuf(int sz) - { - checkspace(sz); - return ucharbuf::subbuf(sz); - } - - void put(const uchar &val) - { - checkspace(1); - ucharbuf::put(val); - } - - void put(const uchar *vals, int numvals) - { - checkspace(numvals); - ucharbuf::put(vals, numvals); - } - - ENetPacket *finalize() - { - resize(len); - return packet; - } - - void cleanup() - { - if(growth > 0 && packet && !packet->referenceCount) { enet_packet_destroy(packet); packet = NULL; buf = NULL; len = maxlen = 0; } - } + ENetPacket *packet; + int growth; + + packetbuf(ENetPacket *packet) : ucharbuf(packet->data, packet->dataLength), packet(packet), growth(0) {} + packetbuf(int growth, int pflags = 0) : growth(growth) + { + packet = enet_packet_create(NULL, growth, pflags); + buf = (uchar *)packet->data; + maxlen = packet->dataLength; + } + ~packetbuf() { cleanup(); } + + void reliable() { packet->flags |= ENET_PACKET_FLAG_RELIABLE; } + + void resize(int n) + { + enet_packet_resize(packet, n); + buf = (uchar *)packet->data; + maxlen = packet->dataLength; + } + + void checkspace(int n) + { + if(len + n > maxlen && packet && growth > 0) resize(max(len + n, maxlen + growth)); + } + + ucharbuf subbuf(int sz) + { + checkspace(sz); + return ucharbuf::subbuf(sz); + } + + void put(const uchar &val) + { + checkspace(1); + ucharbuf::put(val); + } + + void put(const uchar *vals, int numvals) + { + checkspace(numvals); + ucharbuf::put(vals, numvals); + } + + ENetPacket *finalize() + { + resize(len); + return packet; + } + + void cleanup() + { + if(growth > 0 && packet && !packet->referenceCount) { enet_packet_destroy(packet); packet = NULL; buf = NULL; len = maxlen = 0; } + } }; template @@ -397,130 +335,130 @@ static inline float heapscore(const T &n) { return n; } struct sortless { - template bool operator()(const T &x, const T &y) const { return x < y; } - bool operator()(char *x, char *y) const { return strcmp(x, y) < 0; } - bool operator()(const char *x, const char *y) const { return strcmp(x, y) < 0; } + template bool operator()(const T &x, const T &y) const { return x < y; } + bool operator()(char *x, char *y) const { return strcmp(x, y) < 0; } + bool operator()(const char *x, const char *y) const { return strcmp(x, y) < 0; } }; struct sortnameless { - template bool operator()(const T &x, const T &y) const { return sortless()(x.name, y.name); } - template bool operator()(T *x, T *y) const { return sortless()(x->name, y->name); } - template bool operator()(const T *x, const T *y) const { return sortless()(x->name, y->name); } + template bool operator()(const T &x, const T &y) const { return sortless()(x.name, y.name); } + template bool operator()(T *x, T *y) const { return sortless()(x->name, y->name); } + template bool operator()(const T *x, const T *y) const { return sortless()(x->name, y->name); } }; template static inline void insertionsort(T *start, T *end, F fun) { - for(T *i = start+1; i < end; i++) - { - if(fun(*i, i[-1])) - { - T tmp = *i; - *i = i[-1]; - T *j = i-1; - for(; j > start && fun(tmp, j[-1]); --j) - *j = j[-1]; - *j = tmp; - } - } + for(T *i = start+1; i < end; i++) + { + if(fun(*i, i[-1])) + { + T tmp = *i; + *i = i[-1]; + T *j = i-1; + for(; j > start && fun(tmp, j[-1]); --j) + *j = j[-1]; + *j = tmp; + } + } } template static inline void insertionsort(T *buf, int n, F fun) { - insertionsort(buf, buf+n, fun); + insertionsort(buf, buf+n, fun); } template static inline void insertionsort(T *buf, int n) { - insertionsort(buf, buf+n, sortless()); + insertionsort(buf, buf+n, sortless()); } template static inline void quicksort(T *start, T *end, F fun) { - while(end-start > 10) - { - T *mid = &start[(end-start)/2], *i = start+1, *j = end-2, pivot; - if(fun(*start, *mid)) /* start < mid */ - { - if(fun(end[-1], *start)) { pivot = *start; *start = end[-1]; end[-1] = *mid; } /* end < start < mid */ - else if(fun(end[-1], *mid)) { pivot = end[-1]; end[-1] = *mid; } /* start <= end < mid */ - else { pivot = *mid; } /* start < mid <= end */ - } - else if(fun(*start, end[-1])) { pivot = *start; *start = *mid; } /*mid <= start < end */ - else if(fun(*mid, end[-1])) { pivot = end[-1]; end[-1] = *start; *start = *mid; } /* mid < end <= start */ - else { pivot = *mid; swap(*start, end[-1]); } /* end <= mid <= start */ - *mid = end[-2]; - do - { - while(fun(*i, pivot)) if(++i >= j) goto partitioned; - while(fun(pivot, *--j)) if(i >= j) goto partitioned; - swap(*i, *j); - } - while(++i < j); - partitioned: - end[-2] = *i; - *i = pivot; - - if(i-start < end-(i+1)) - { - quicksort(start, i, fun); - start = i+1; - } - else - { - quicksort(i+1, end, fun); - end = i; - } - } - - insertionsort(start, end, fun); + while(end-start > 10) + { + T *mid = &start[(end-start)/2], *i = start+1, *j = end-2, pivot; + if(fun(*start, *mid)) /* start < mid */ + { + if(fun(end[-1], *start)) { pivot = *start; *start = end[-1]; end[-1] = *mid; } /* end < start < mid */ + else if(fun(end[-1], *mid)) { pivot = end[-1]; end[-1] = *mid; } /* start <= end < mid */ + else { pivot = *mid; } /* start < mid <= end */ + } + else if(fun(*start, end[-1])) { pivot = *start; *start = *mid; } /*mid <= start < end */ + else if(fun(*mid, end[-1])) { pivot = end[-1]; end[-1] = *start; *start = *mid; } /* mid < end <= start */ + else { pivot = *mid; swap(*start, end[-1]); } /* end <= mid <= start */ + *mid = end[-2]; + do + { + while(fun(*i, pivot)) if(++i >= j) goto partitioned; + while(fun(pivot, *--j)) if(i >= j) goto partitioned; + swap(*i, *j); + } + while(++i < j); + partitioned: + end[-2] = *i; + *i = pivot; + + if(i-start < end-(i+1)) + { + quicksort(start, i, fun); + start = i+1; + } + else + { + quicksort(i+1, end, fun); + end = i; + } + } + + insertionsort(start, end, fun); } template static inline void quicksort(T *buf, int n, F fun) { - quicksort(buf, buf+n, fun); + quicksort(buf, buf+n, fun); } template static inline void quicksort(T *buf, int n) { - quicksort(buf, buf+n, sortless()); + quicksort(buf, buf+n, sortless()); } template struct isclass { - template static char test(void (C::*)(void)); - template static int test(...); - enum { yes = sizeof(test(0)) == 1 ? 1 : 0, no = yes^1 }; + template static char test(void (C::*)(void)); + template static int test(...); + enum { yes = sizeof(test(0)) == 1 ? 1 : 0, no = yes^1 }; }; static inline uint hthash(const char *key) { - uint h = 5381; - for(int i = 0, k; (k = key[i]); i++) h = ((h<<5)+h)^k; // bernstein k=33 xor - return h; + uint h = 5381; + for(int i = 0, k; (k = key[i]); i++) h = ((h<<5)+h)^k; // bernstein k=33 xor + return h; } static inline bool htcmp(const char *x, const char *y) { - return !strcmp(x, y); + return !strcmp(x, y); } struct stringslice { - const char *str; - int len; - stringslice() {} - stringslice(const char *str, int len) : str(str), len(len) {} - stringslice(const char *str, const char *end) : str(str), len(int(end-str)) {} + const char *str; + int len; + stringslice() {} + stringslice(const char *str, int len) : str(str), len(len) {} + stringslice(const char *str, const char *end) : str(str), len(int(end-str)) {} - const char *end() const { return &str[len]; } + const char *end() const { return &str[len]; } }; inline char *newstring(const stringslice &s) { return newstring(s.str, s.len); } @@ -531,668 +469,668 @@ inline int stringlen(const stringslice &s) { return s.len; } inline char *copystring(char *d, const stringslice &s, size_t len) { - size_t slen = min(size_t(s.len), len-1); - memcpy(d, s.str, slen); - d[slen] = 0; - return d; + size_t slen = min(size_t(s.len), len-1); + memcpy(d, s.str, slen); + d[slen] = 0; + return d; } template inline char *copystring(char (&d)[N], const stringslice &s) { return copystring(d, s, N); } static inline uint memhash(const void *ptr, int len) { - const uchar *data = (const uchar *)ptr; - uint h = 5381; - loopi(len) h = ((h<<5)+h)^data[i]; - return h; + const uchar *data = (const uchar *)ptr; + uint h = 5381; + loopi(len) h = ((h<<5)+h)^data[i]; + return h; } static inline uint hthash(const stringslice &s) { return memhash(s.str, s.len); } static inline bool htcmp(const stringslice &x, const char *y) { - return x.len == (int)strlen(y) && !memcmp(x.str, y, x.len); + return x.len == (int)strlen(y) && !memcmp(x.str, y, x.len); } static inline uint hthash(int key) { - return key; + return key; } static inline bool htcmp(int x, int y) { - return x==y; + return x==y; } #ifndef STANDALONE static inline uint hthash(GLuint key) { - return key; + return key; } static inline bool htcmp(GLuint x, GLuint y) { - return x==y; + return x==y; } #endif template struct vector { - static const int MINSIZE = 8; - - T *buf; - int alen, ulen; - - vector() : buf(NULL), alen(0), ulen(0) - { - } - - vector(const vector &v) : buf(NULL), alen(0), ulen(0) - { - *this = v; - } - - ~vector() { shrink(0); if(buf) delete[] (uchar *)buf; } - - vector &operator=(const vector &v) - { - shrink(0); - if(v.length() > alen) growbuf(v.length()); - loopv(v) add(v[i]); - return *this; - } - - T &add(const T &x) - { - if(ulen==alen) growbuf(ulen+1); - new (&buf[ulen]) T(x); - return buf[ulen++]; - } - - T &add() - { - if(ulen==alen) growbuf(ulen+1); - new (&buf[ulen]) T; - return buf[ulen++]; - } - - T &dup() - { - if(ulen==alen) growbuf(ulen+1); - new (&buf[ulen]) T(buf[ulen-1]); - return buf[ulen++]; - } - - void move(vector &v) - { - if(!ulen) - { - swap(buf, v.buf); - swap(ulen, v.ulen); - swap(alen, v.alen); - } - else - { - growbuf(ulen+v.ulen); - if(v.ulen) memcpy(&buf[ulen], (void *)v.buf, v.ulen*sizeof(T)); - ulen += v.ulen; - v.ulen = 0; - } - } - - bool inrange(size_t i) const { return i=0 && i=0 && i= 0 && i::no) ulen = i; else while(ulen>i) drop(); } - void setsize(int i) { ASSERT(i<=ulen); ulen = i; } - - void deletecontents() { while(!empty()) delete pop(); } - void deletearrays() { while(!empty()) delete[] pop(); } - - T *getbuf() { return buf; } - const T *getbuf() const { return buf; } - bool inbuf(const T *e) const { return e >= buf && e < &buf[ulen]; } - - template - void sort(F fun, int i = 0, int n = -1) - { - quicksort(&buf[i], n < 0 ? ulen-i : n, fun); - } - - void sort() { sort(sortless()); } - void sortname() { sort(sortnameless()); } - - void growbuf(int sz) - { - int olen = alen; - if(alen <= 0) alen = max(MINSIZE, sz); - else while(alen < sz) alen += alen/2; - if(alen <= olen) return; - uchar *newbuf = new uchar[alen*sizeof(T)]; - if(olen > 0) - { - if(ulen > 0) memcpy(newbuf, (void *)buf, ulen*sizeof(T)); - delete[] (uchar *)buf; - } - buf = (T *)newbuf; - } - - databuf reserve(int sz) - { - if(alen-ulen < sz) growbuf(ulen+sz); - return databuf(&buf[ulen], sz); - } - - void advance(int sz) - { - ulen += sz; - } - - void addbuf(const databuf &p) - { - advance(p.length()); - } - - T *pad(int n) - { - T *buf = reserve(n).buf; - advance(n); - return buf; - } - - void put(const T &v) { add(v); } - - void put(const T *v, int n) - { - databuf buf = reserve(n); - buf.put(v, n); - addbuf(buf); - } - - void remove(int i, int n) - { - for(int p = i+n; p0) buf[i] = buf[ulen]; - return e; - } - - template - int find(const U &o) - { - loopi(ulen) if(buf[i]==o) return i; - return -1; - } - - void addunique(const T &o) - { - if(find(o) < 0) add(o); - } - - void removeobj(const T &o) - { - loopi(ulen) if(buf[i] == o) - { - int dst = i; - for(int j = i+1; j < ulen; j++) if(!(buf[j] == o)) buf[dst++] = buf[j]; - setsize(dst); - break; - } - } - - void replacewithlast(const T &o) - { - if(!ulen) return; - loopi(ulen-1) if(buf[i]==o) - { - buf[i] = buf[ulen-1]; - break; - } - ulen--; - } - - T &insert(int i, const T &e) - { - add(T()); - for(int p = ulen-1; p>i; p--) buf[p] = buf[p-1]; - buf[i] = e; - return buf[i]; - } - - T *insert(int i, const T *e, int n) - { - if(alen-ulen < n) growbuf(ulen+n); - loopj(n) add(T()); - for(int p = ulen-1; p>=i+n; p--) buf[p] = buf[p-n]; - loopj(n) buf[i+j] = e[j]; - return &buf[i]; - } - - void reverse() - { - loopi(ulen/2) swap(buf[i], buf[ulen-1-i]); - } - - static int heapparent(int i) { return (i - 1) >> 1; } - static int heapchild(int i) { return (i << 1) + 1; } - - void buildheap() - { - for(int i = ulen/2; i >= 0; i--) downheap(i); - } - - int upheap(int i) - { - float score = heapscore(buf[i]); - while(i > 0) - { - int pi = heapparent(i); - if(score >= heapscore(buf[pi])) break; - swap(buf[i], buf[pi]); - i = pi; - } - return i; - } - - T &addheap(const T &x) - { - add(x); - return buf[upheap(ulen-1)]; - } - - int downheap(int i) - { - float score = heapscore(buf[i]); - for(;;) - { - int ci = heapchild(i); - if(ci >= ulen) break; - float cscore = heapscore(buf[ci]); - if(score > cscore) - { - if(ci+1 < ulen && heapscore(buf[ci+1]) < cscore) { swap(buf[ci+1], buf[i]); i = ci+1; } - else { swap(buf[ci], buf[i]); i = ci; } - } - else if(ci+1 < ulen && heapscore(buf[ci+1]) < score) { swap(buf[ci+1], buf[i]); i = ci+1; } - else break; - } - return i; - } - - T removeheap() - { - T e = removeunordered(0); - if(ulen) downheap(0); - return e; - } - - template - int htfind(const K &key) - { - loopi(ulen) if(htcmp(key, buf[i])) return i; - return -1; - } + static const int MINSIZE = 8; + + T *buf; + int alen, ulen; + + vector() : buf(NULL), alen(0), ulen(0) + { + } + + vector(const vector &v) : buf(NULL), alen(0), ulen(0) + { + *this = v; + } + + ~vector() { shrink(0); if(buf) delete[] (uchar *)buf; } + + vector &operator=(const vector &v) + { + shrink(0); + if(v.length() > alen) growbuf(v.length()); + loopv(v) add(v[i]); + return *this; + } + + T &add(const T &x) + { + if(ulen==alen) growbuf(ulen+1); + new (&buf[ulen]) T(x); + return buf[ulen++]; + } + + T &add() + { + if(ulen==alen) growbuf(ulen+1); + new (&buf[ulen]) T; + return buf[ulen++]; + } + + T &dup() + { + if(ulen==alen) growbuf(ulen+1); + new (&buf[ulen]) T(buf[ulen-1]); + return buf[ulen++]; + } + + void move(vector &v) + { + if(!ulen) + { + swap(buf, v.buf); + swap(ulen, v.ulen); + swap(alen, v.alen); + } + else + { + growbuf(ulen+v.ulen); + if(v.ulen) memcpy(&buf[ulen], (void *)v.buf, v.ulen*sizeof(T)); + ulen += v.ulen; + v.ulen = 0; + } + } + + bool inrange(size_t i) const { return i=0 && i=0 && i= 0 && i::no) ulen = i; else while(ulen>i) drop(); } + void setsize(int i) { ASSERT(i<=ulen); ulen = i; } + + void deletecontents() { while(!empty()) delete pop(); } + void deletearrays() { while(!empty()) delete[] pop(); } + + T *getbuf() { return buf; } + const T *getbuf() const { return buf; } + bool inbuf(const T *e) const { return e >= buf && e < &buf[ulen]; } + + template + void sort(F fun, int i = 0, int n = -1) + { + quicksort(&buf[i], n < 0 ? ulen-i : n, fun); + } + + void sort() { sort(sortless()); } + void sortname() { sort(sortnameless()); } + + void growbuf(int sz) + { + int olen = alen; + if(alen <= 0) alen = max(MINSIZE, sz); + else while(alen < sz) alen += alen/2; + if(alen <= olen) return; + uchar *newbuf = new uchar[alen*sizeof(T)]; + if(olen > 0) + { + if(ulen > 0) memcpy(newbuf, (void *)buf, ulen*sizeof(T)); + delete[] (uchar *)buf; + } + buf = (T *)newbuf; + } + + databuf reserve(int sz) + { + if(alen-ulen < sz) growbuf(ulen+sz); + return databuf(&buf[ulen], sz); + } + + void advance(int sz) + { + ulen += sz; + } + + void addbuf(const databuf &p) + { + advance(p.length()); + } + + T *pad(int n) + { + T *buf = reserve(n).buf; + advance(n); + return buf; + } + + void put(const T &v) { add(v); } + + void put(const T *v, int n) + { + databuf buf = reserve(n); + buf.put(v, n); + addbuf(buf); + } + + void remove(int i, int n) + { + for(int p = i+n; p0) buf[i] = buf[ulen]; + return e; + } + + template + int find(const U &o) + { + loopi(ulen) if(buf[i]==o) return i; + return -1; + } + + void addunique(const T &o) + { + if(find(o) < 0) add(o); + } + + void removeobj(const T &o) + { + loopi(ulen) if(buf[i] == o) + { + int dst = i; + for(int j = i+1; j < ulen; j++) if(!(buf[j] == o)) buf[dst++] = buf[j]; + setsize(dst); + break; + } + } + + void replacewithlast(const T &o) + { + if(!ulen) return; + loopi(ulen-1) if(buf[i]==o) + { + buf[i] = buf[ulen-1]; + break; + } + ulen--; + } + + T &insert(int i, const T &e) + { + add(T()); + for(int p = ulen-1; p>i; p--) buf[p] = buf[p-1]; + buf[i] = e; + return buf[i]; + } + + T *insert(int i, const T *e, int n) + { + if(alen-ulen < n) growbuf(ulen+n); + loopj(n) add(T()); + for(int p = ulen-1; p>=i+n; p--) buf[p] = buf[p-n]; + loopj(n) buf[i+j] = e[j]; + return &buf[i]; + } + + void reverse() + { + loopi(ulen/2) swap(buf[i], buf[ulen-1-i]); + } + + static int heapparent(int i) { return (i - 1) >> 1; } + static int heapchild(int i) { return (i << 1) + 1; } + + void buildheap() + { + for(int i = ulen/2; i >= 0; i--) downheap(i); + } + + int upheap(int i) + { + float score = heapscore(buf[i]); + while(i > 0) + { + int pi = heapparent(i); + if(score >= heapscore(buf[pi])) break; + swap(buf[i], buf[pi]); + i = pi; + } + return i; + } + + T &addheap(const T &x) + { + add(x); + return buf[upheap(ulen-1)]; + } + + int downheap(int i) + { + float score = heapscore(buf[i]); + for(;;) + { + int ci = heapchild(i); + if(ci >= ulen) break; + float cscore = heapscore(buf[ci]); + if(score > cscore) + { + if(ci+1 < ulen && heapscore(buf[ci+1]) < cscore) { swap(buf[ci+1], buf[i]); i = ci+1; } + else { swap(buf[ci], buf[i]); i = ci; } + } + else if(ci+1 < ulen && heapscore(buf[ci+1]) < score) { swap(buf[ci+1], buf[i]); i = ci+1; } + else break; + } + return i; + } + + T removeheap() + { + T e = removeunordered(0); + if(ulen) downheap(0); + return e; + } + + template + int htfind(const K &key) + { + loopi(ulen) if(htcmp(key, buf[i])) return i; + return -1; + } }; template struct hashbase { - typedef E elemtype; - typedef K keytype; - typedef T datatype; - - enum { CHUNKSIZE = 64 }; - - struct chain { E elem; chain *next; }; - struct chainchunk { chain chains[CHUNKSIZE]; chainchunk *next; }; - - int size; - int numelems; - chain **chains; - - chainchunk *chunks; - chain *unused; - - enum { DEFAULTSIZE = 1<<10 }; - - hashbase(int size = DEFAULTSIZE) - : size(size) - { - numelems = 0; - chunks = NULL; - unused = NULL; - chains = new chain *[size]; - memset(chains, 0, size*sizeof(chain *)); - } - - ~hashbase() - { - DELETEA(chains); - deletechunks(); - } - - chain *insert(uint h) - { - if(!unused) - { - chainchunk *chunk = new chainchunk; - chunk->next = chunks; - chunks = chunk; - loopi(CHUNKSIZE-1) chunk->chains[i].next = &chunk->chains[i+1]; - chunk->chains[CHUNKSIZE-1].next = unused; - unused = chunk->chains; - } - chain *c = unused; - unused = unused->next; - c->next = chains[h]; - chains[h] = c; - numelems++; - return c; - } - - template - T &insert(uint h, const U &key) - { - chain *c = insert(h); - H::setkey(c->elem, key); - return H::getdata(c->elem); - } - - #define HTFIND(success, fail) \ - uint h = hthash(key)&(this->size-1); \ - for(chain *c = this->chains[h]; c; c = c->next) \ - { \ - if(htcmp(key, H::getkey(c->elem))) return success H::getdata(c->elem); \ - } \ - return (fail); - - template - T *access(const U &key) - { - HTFIND(&, NULL); - } - - template - T &access(const U &key, const V &elem) - { - HTFIND( , insert(h, key) = elem); - } - - template - T &operator[](const U &key) - { - HTFIND( , insert(h, key)); - } - - template - T &find(const U &key, T ¬found) - { - HTFIND( , notfound); - } - - template - const T &find(const U &key, const T ¬found) - { - HTFIND( , notfound); - } - - template - bool remove(const U &key) - { - uint h = hthash(key)&(size-1); - for(chain **p = &chains[h], *c = chains[h]; c; p = &c->next, c = c->next) - { - if(htcmp(key, H::getkey(c->elem))) - { - *p = c->next; - c->elem.~E(); - new (&c->elem) E; - c->next = unused; - unused = c; - numelems--; - return true; - } - } - return false; - } - - void deletechunks() - { - for(chainchunk *nextchunk; chunks; chunks = nextchunk) - { - nextchunk = chunks->next; - delete chunks; - } - } - - void clear() - { - if(!numelems) return; - memset(chains, 0, size*sizeof(chain *)); - numelems = 0; - unused = NULL; - deletechunks(); - } - - static inline chain *enumnext(void *i) { return ((chain *)i)->next; } - static inline K &enumkey(void *i) { return H::getkey(((chain *)i)->elem); } - static inline T &enumdata(void *i) { return H::getdata(((chain *)i)->elem); } + typedef E elemtype; + typedef K keytype; + typedef T datatype; + + enum { CHUNKSIZE = 64 }; + + struct chain { E elem; chain *next; }; + struct chainchunk { chain chains[CHUNKSIZE]; chainchunk *next; }; + + int size; + int numelems; + chain **chains; + + chainchunk *chunks; + chain *unused; + + enum { DEFAULTSIZE = 1<<10 }; + + hashbase(int size = DEFAULTSIZE) + : size(size) + { + numelems = 0; + chunks = NULL; + unused = NULL; + chains = new chain *[size]; + memset(chains, 0, size*sizeof(chain *)); + } + + ~hashbase() + { + DELETEA(chains); + deletechunks(); + } + + chain *insert(uint h) + { + if(!unused) + { + chainchunk *chunk = new chainchunk; + chunk->next = chunks; + chunks = chunk; + loopi(CHUNKSIZE-1) chunk->chains[i].next = &chunk->chains[i+1]; + chunk->chains[CHUNKSIZE-1].next = unused; + unused = chunk->chains; + } + chain *c = unused; + unused = unused->next; + c->next = chains[h]; + chains[h] = c; + numelems++; + return c; + } + + template + T &insert(uint h, const U &key) + { + chain *c = insert(h); + H::setkey(c->elem, key); + return H::getdata(c->elem); + } + + #define HTFIND(success, fail) \ + uint h = hthash(key)&(this->size-1); \ + for(chain *c = this->chains[h]; c; c = c->next) \ + { \ + if(htcmp(key, H::getkey(c->elem))) return success H::getdata(c->elem); \ + } \ + return (fail); + + template + T *access(const U &key) + { + HTFIND(&, NULL); + } + + template + T &access(const U &key, const V &elem) + { + HTFIND( , insert(h, key) = elem); + } + + template + T &operator[](const U &key) + { + HTFIND( , insert(h, key)); + } + + template + T &find(const U &key, T ¬found) + { + HTFIND( , notfound); + } + + template + const T &find(const U &key, const T ¬found) + { + HTFIND( , notfound); + } + + template + bool remove(const U &key) + { + uint h = hthash(key)&(size-1); + for(chain **p = &chains[h], *c = chains[h]; c; p = &c->next, c = c->next) + { + if(htcmp(key, H::getkey(c->elem))) + { + *p = c->next; + c->elem.~E(); + new (&c->elem) E; + c->next = unused; + unused = c; + numelems--; + return true; + } + } + return false; + } + + void deletechunks() + { + for(chainchunk *nextchunk; chunks; chunks = nextchunk) + { + nextchunk = chunks->next; + delete chunks; + } + } + + void clear() + { + if(!numelems) return; + memset(chains, 0, size*sizeof(chain *)); + numelems = 0; + unused = NULL; + deletechunks(); + } + + static inline chain *enumnext(void *i) { return ((chain *)i)->next; } + static inline K &enumkey(void *i) { return H::getkey(((chain *)i)->elem); } + static inline T &enumdata(void *i) { return H::getdata(((chain *)i)->elem); } }; template struct hashset : hashbase, T, T, T> { - typedef hashbase, T, T, T> basetype; + typedef hashbase, T, T, T> basetype; - hashset(int size = basetype::DEFAULTSIZE) : basetype(size) {} + hashset(int size = basetype::DEFAULTSIZE) : basetype(size) {} - static inline const T &getkey(const T &elem) { return elem; } - static inline T &getdata(T &elem) { return elem; } - template static inline void setkey(T &elem, const K &key) {} + static inline const T &getkey(const T &elem) { return elem; } + static inline T &getdata(T &elem) { return elem; } + template static inline void setkey(T &elem, const K &key) {} - template - T &add(const V &elem) - { - return basetype::access(elem, elem); - } + template + T &add(const V &elem) + { + return basetype::access(elem, elem); + } }; template struct hashnameset : hashbase, T, const char *, T> { - typedef hashbase, T, const char *, T> basetype; + typedef hashbase, T, const char *, T> basetype; - hashnameset(int size = basetype::DEFAULTSIZE) : basetype(size) {} + hashnameset(int size = basetype::DEFAULTSIZE) : basetype(size) {} - template static inline const char *getkey(const U &elem) { return elem.name; } - template static inline const char *getkey(U *elem) { return elem->name; } - static inline T &getdata(T &elem) { return elem; } - template static inline void setkey(T &elem, const K &key) {} + template static inline const char *getkey(const U &elem) { return elem.name; } + template static inline const char *getkey(U *elem) { return elem->name; } + static inline T &getdata(T &elem) { return elem; } + template static inline void setkey(T &elem, const K &key) {} - template - T &add(const V &elem) - { - return basetype::access(getkey(elem), elem); - } + template + T &add(const V &elem) + { + return basetype::access(getkey(elem), elem); + } }; template struct hashtableentry { - K key; - T data; + K key; + T data; }; template struct hashtable : hashbase, hashtableentry, K, T> { - typedef hashbase, hashtableentry, K, T> basetype; - typedef typename basetype::elemtype elemtype; + typedef hashbase, hashtableentry, K, T> basetype; + typedef typename basetype::elemtype elemtype; - hashtable(int size = basetype::DEFAULTSIZE) : basetype(size) {} + hashtable(int size = basetype::DEFAULTSIZE) : basetype(size) {} - static inline K &getkey(elemtype &elem) { return elem.key; } - static inline T &getdata(elemtype &elem) { return elem.data; } - template static inline void setkey(elemtype &elem, const U &key) { elem.key = key; } + static inline K &getkey(elemtype &elem) { return elem.key; } + static inline T &getdata(elemtype &elem) { return elem.data; } + template static inline void setkey(elemtype &elem, const U &key) { elem.key = key; } }; #define enumeratekt(ht,k,e,t,f,b) loopi((ht).size) for(void *ec = (ht).chains[i]; ec;) { k &e = (ht).enumkey(ec); t &f = (ht).enumdata(ec); ec = (ht).enumnext(ec); b; } -#define enumerate(ht,t,e,b) loopi((ht).size) for(void *ec = (ht).chains[i]; ec;) { t &e = (ht).enumdata(ec); ec = (ht).enumnext(ec); b; } +#define enumerate(ht,t,e,b) loopi((ht).size) for(void *ec = (ht).chains[i]; ec;) { t &e = (ht).enumdata(ec); ec = (ht).enumnext(ec); b; } struct unionfind { - struct ufval - { - int rank, next; - - ufval() : rank(0), next(-1) {} - }; - - vector ufvals; - - int find(int k) - { - if(k>=ufvals.length()) return k; - while(ufvals[k].next>=0) k = ufvals[k].next; - return k; - } - - int compressfind(int k) - { - if(ufvals[k].next<0) return k; - return ufvals[k].next = compressfind(ufvals[k].next); - } - - void unite (int x, int y) - { - while(ufvals.length() <= max(x, y)) ufvals.add(); - x = compressfind(x); - y = compressfind(y); - if(x==y) return; - ufval &xval = ufvals[x], &yval = ufvals[y]; - if(xval.rank < yval.rank) xval.next = y; - else - { - yval.next = x; - if(xval.rank==yval.rank) yval.rank++; - } - } + struct ufval + { + int rank, next; + + ufval() : rank(0), next(-1) {} + }; + + vector ufvals; + + int find(int k) + { + if(k>=ufvals.length()) return k; + while(ufvals[k].next>=0) k = ufvals[k].next; + return k; + } + + int compressfind(int k) + { + if(ufvals[k].next<0) return k; + return ufvals[k].next = compressfind(ufvals[k].next); + } + + void unite (int x, int y) + { + while(ufvals.length() <= max(x, y)) ufvals.add(); + x = compressfind(x); + y = compressfind(y); + if(x==y) return; + ufval &xval = ufvals[x], &yval = ufvals[y]; + if(xval.rank < yval.rank) xval.next = y; + else + { + yval.next = x; + if(xval.rank==yval.rank) yval.rank++; + } + } }; template struct queue { - int head, tail, len; - T data[SIZE]; - - queue() { clear(); } - - void clear() { head = tail = len = 0; } - - int capacity() const { return SIZE; } - int length() const { return len; } - bool empty() const { return !len; } - bool full() const { return len == SIZE; } - - bool inrange(size_t i) const { return i=0 && i 0 ? tail-1 : SIZE-1]; } - T &added(int offset) { return data[tail-offset > 0 ? tail-offset-1 : tail-offset-1 + SIZE]; } - T &adding() { return data[tail]; } - T &adding(int offset) { return data[tail+offset >= SIZE ? tail+offset - SIZE : tail+offset]; } - T &add() - { - T &t = data[tail]; - tail++; - if(tail >= SIZE) tail -= SIZE; - if(len < SIZE) len++; - return t; - } - T &add(const T &e) { return add() = e; } - - databuf reserve(int sz) - { - if(!len) head = tail = 0; - return databuf(&data[tail], min(sz, SIZE-tail)); - } - - void advance(int sz) - { - if(len + sz > SIZE) sz = SIZE - len; - tail += sz; - if(tail >= SIZE) tail -= SIZE; - len += sz; - } - - void addbuf(const databuf &p) - { - advance(p.length()); - } - - T &pop() - { - tail--; - if(tail < 0) tail += SIZE; - len--; - return data[tail]; - } - - T &removing() { return data[head]; } - T &removing(int offset) { return data[head+offset >= SIZE ? head+offset - SIZE : head+offset]; } - T &remove() - { - T &t = data[head]; - head++; - if(head >= SIZE) head -= SIZE; - len--; - return t; - } - - T remove(int offset) - { - T val = removing(offset); - if(head+offset >= SIZE) for(int i = head+offset - SIZE + 1; i < tail; i++) data[i-1] = data[i]; - else if(head < tail) for(int i = head+offset + 1; i < tail; i++) data[i-1] = data[i]; - else - { - for(int i = head+offset + 1; i < SIZE; i++) data[i-1] = data[i]; - data[SIZE-1] = data[0]; - for(int i = 1; i < tail; i++) data[i-1] = data[i]; - } - tail--; - if(tail < 0) tail += SIZE; - len--; - return val; - } - - T &operator[](int offset) { return removing(offset); } - const T &operator[](int offset) const { return removing(offset); } + int head, tail, len; + T data[SIZE]; + + queue() { clear(); } + + void clear() { head = tail = len = 0; } + + int capacity() const { return SIZE; } + int length() const { return len; } + bool empty() const { return !len; } + bool full() const { return len == SIZE; } + + bool inrange(size_t i) const { return i=0 && i 0 ? tail-1 : SIZE-1]; } + T &added(int offset) { return data[tail-offset > 0 ? tail-offset-1 : tail-offset-1 + SIZE]; } + T &adding() { return data[tail]; } + T &adding(int offset) { return data[tail+offset >= SIZE ? tail+offset - SIZE : tail+offset]; } + T &add() + { + T &t = data[tail]; + tail++; + if(tail >= SIZE) tail -= SIZE; + if(len < SIZE) len++; + return t; + } + T &add(const T &e) { return add() = e; } + + databuf reserve(int sz) + { + if(!len) head = tail = 0; + return databuf(&data[tail], min(sz, SIZE-tail)); + } + + void advance(int sz) + { + if(len + sz > SIZE) sz = SIZE - len; + tail += sz; + if(tail >= SIZE) tail -= SIZE; + len += sz; + } + + void addbuf(const databuf &p) + { + advance(p.length()); + } + + T &pop() + { + tail--; + if(tail < 0) tail += SIZE; + len--; + return data[tail]; + } + + T &removing() { return data[head]; } + T &removing(int offset) { return data[head+offset >= SIZE ? head+offset - SIZE : head+offset]; } + T &remove() + { + T &t = data[head]; + head++; + if(head >= SIZE) head -= SIZE; + len--; + return t; + } + + T remove(int offset) + { + T val = removing(offset); + if(head+offset >= SIZE) for(int i = head+offset - SIZE + 1; i < tail; i++) data[i-1] = data[i]; + else if(head < tail) for(int i = head+offset + 1; i < tail; i++) data[i-1] = data[i]; + else + { + for(int i = head+offset + 1; i < SIZE; i++) data[i-1] = data[i]; + data[SIZE-1] = data[0]; + for(int i = 1; i < tail; i++) data[i-1] = data[i]; + } + tail--; + if(tail < 0) tail += SIZE; + len--; + return val; + } + + T &operator[](int offset) { return removing(offset); } + const T &operator[](int offset) const { return removing(offset); } }; template struct reversequeue : queue { - T &operator[](int offset) { return queue::added(offset); } - const T &operator[](int offset) const { return queue::added(offset); } + T &operator[](int offset) { return queue::added(offset); } + const T &operator[](int offset) const { return queue::added(offset); } }; const int islittleendian = 1; @@ -1239,83 +1177,67 @@ template inline T bigswap(T n) { return *(const uchar *)&islittleendian template inline void bigswap(T *buf, size_t len) { if(*(const uchar *)&islittleendian) endianswap(buf, len); } #endif -/* workaround for some C platforms that have these two functions as macros - not used anywhere */ -#ifdef getchar -#undef getchar -#endif -#ifdef putchar -#undef putchar -#endif - struct stream { -#ifdef WIN32 -#if defined(__GNUC__) && !defined(__MINGW32__) - typedef off64_t offset; -#else - typedef __int64 offset; -#endif -#else - typedef off_t offset; -#endif - - virtual ~stream() {} - virtual void close() = 0; - virtual bool end() = 0; - virtual offset tell() { return -1; } - virtual offset rawtell() { return tell(); } - virtual bool seek(offset pos, int whence = SEEK_SET) { return false; } - virtual offset size(); - virtual offset rawsize() { return size(); } - virtual size_t read(void *buf, size_t len) { return 0; } - virtual size_t write(const void *buf, size_t len) { return 0; } - virtual bool flush() { return true; } - virtual int getchar() { uchar c; return read(&c, 1) == 1 ? c : -1; } - virtual bool putchar(int n) { uchar c = n; return write(&c, 1) == 1; } - virtual bool getline(char *str, size_t len); - virtual bool putstring(const char *str) { size_t len = strlen(str); return write(str, len) == len; } - virtual bool putline(const char *str) { return putstring(str) && putchar('\n'); } - virtual size_t printf(const char *fmt, ...) PRINTFARGS(2, 3); - virtual uint getcrc() { return 0; } - - template size_t put(const T *v, size_t n) { return write(v, n*sizeof(T))/sizeof(T); } - template bool put(T n) { return write(&n, sizeof(n)) == sizeof(n); } - template bool putlil(T n) { return put(lilswap(n)); } - template bool putbig(T n) { return put(bigswap(n)); } - - template size_t get(T *v, size_t n) { return read(v, n*sizeof(T))/sizeof(T); } - template T get() { T n; return read(&n, sizeof(n)) == sizeof(n) ? n : 0; } - template T getlil() { return lilswap(get()); } - template T getbig() { return bigswap(get()); } + typedef off_t offset; + + virtual ~stream() {} + virtual void close() = 0; + virtual bool end() = 0; + virtual offset tell() { return -1; } + virtual offset rawtell() { return tell(); } + virtual bool seek(offset pos, int whence = SEEK_SET) { return false; } + virtual offset size(); + virtual offset rawsize() { return size(); } + virtual size_t read(void *buf, size_t len) { return 0; } + virtual size_t write(const void *buf, size_t len) { return 0; } + virtual bool flush() { return true; } + virtual int getchar() { uchar c; return read(&c, 1) == 1 ? c : -1; } + virtual bool putchar(int n) { uchar c = n; return write(&c, 1) == 1; } + virtual bool getline(char *str, size_t len); + virtual bool putstring(const char *str) { size_t len = strlen(str); return write(str, len) == len; } + virtual bool putline(const char *str) { return putstring(str) && putchar('\n'); } + virtual size_t printf(const char *fmt, ...) PRINTFARGS(2, 3); + virtual uint getcrc() { return 0; } + + template size_t put(const T *v, size_t n) { return write(v, n*sizeof(T))/sizeof(T); } + template bool put(T n) { return write(&n, sizeof(n)) == sizeof(n); } + template bool putlil(T n) { return put(lilswap(n)); } + template bool putbig(T n) { return put(bigswap(n)); } + + template size_t get(T *v, size_t n) { return read(v, n*sizeof(T))/sizeof(T); } + template T get() { T n; return read(&n, sizeof(n)) == sizeof(n) ? n : 0; } + template T getlil() { return lilswap(get()); } + template T getbig() { return bigswap(get()); } #ifndef STANDALONE - SDL_RWops *rwops(); + SDL_RWops *rwops(); #endif }; template struct streambuf { - stream *s; - - streambuf(stream *s) : s(s) {} - - T get() { return s->get(); } - size_t get(T *vals, size_t numvals) { return s->get(vals, numvals); } - void put(const T &val) { s->put(&val, 1); } - void put(const T *vals, size_t numvals) { s->put(vals, numvals); } - size_t length() { return s->size(); } + stream *s; + + streambuf(stream *s) : s(s) {} + + T get() { return s->get(); } + size_t get(T *vals, size_t numvals) { return s->get(vals, numvals); } + void put(const T &val) { s->put(&val, 1); } + void put(const T *vals, size_t numvals) { s->put(vals, numvals); } + size_t length() { return s->size(); } }; enum { - CT_PRINT = 1<<0, - CT_SPACE = 1<<1, - CT_DIGIT = 1<<2, - CT_ALPHA = 1<<3, - CT_LOWER = 1<<4, - CT_UPPER = 1<<5, - CT_UNICODE = 1<<6 + CT_PRINT = 1<<0, + CT_SPACE = 1<<1, + CT_DIGIT = 1<<2, + CT_ALPHA = 1<<3, + CT_LOWER = 1<<4, + CT_UPPER = 1<<5, + CT_UNICODE = 1<<6 }; extern const uchar cubectype[256]; static inline int iscubeprint(uchar c) { return cubectype[c]&CT_PRINT; } @@ -1326,25 +1248,25 @@ static inline int iscubelower(uchar c) { return cubectype[c]&CT_LOWER; } static inline int iscubeupper(uchar c) { return cubectype[c]&CT_UPPER; } static inline int iscubepunct(uchar c) { return cubectype[c] == CT_PRINT; } static inline int cube2uni(uchar c) -{ - extern const int cube2unichars[256]; - return cube2unichars[c]; +{ + extern const int cube2unichars[256]; + return cube2unichars[c]; } static inline uchar uni2cube(int c) { - extern const int uni2cubeoffsets[8]; - extern const uchar uni2cubechars[]; - return uint(c) <= 0x7FF ? uni2cubechars[uni2cubeoffsets[c>>8] + (c&0xFF)] : 0; + extern const int uni2cubeoffsets[8]; + extern const uchar uni2cubechars[]; + return uint(c) <= 0x7FF ? uni2cubechars[uni2cubeoffsets[c>>8] + (c&0xFF)] : 0; } static inline uchar cubelower(uchar c) { - extern const uchar cubelowerchars[256]; - return cubelowerchars[c]; + extern const uchar cubelowerchars[256]; + return cubelowerchars[c]; } static inline uchar cubeupper(uchar c) { - extern const uchar cubeupperchars[256]; - return cubeupperchars[c]; + extern const uchar cubeupperchars[256]; + return cubeupperchars[c]; } extern size_t decodeutf8(uchar *dst, size_t dstlen, const uchar *src, size_t srclen, size_t *carry = NULL); extern size_t encodeutf8(uchar *dstbuf, size_t dstlen, const uchar *srcbuf, size_t srclen, size_t *carry = NULL); @@ -1397,11 +1319,11 @@ template static inline void filtertext(char (&dst)[N], const char *src struct ipmask { - enet_uint32 ip, mask; + enet_uint32 ip, mask; - void parse(const char *name); - int print(char *buf) const; - bool check(enet_uint32 host) const { return (host & mask) == ip; } + void parse(const char *name); + int print(char *buf) const; + bool check(enet_uint32 host) const { return (host & mask) == ip; } }; #endif diff --git a/src/shared/zip.cpp b/src/shared/zip.cpp index 40a3e9c..83b2952 100644 --- a/src/shared/zip.cpp +++ b/src/shared/zip.cpp @@ -2,583 +2,567 @@ enum { - ZIP_LOCAL_FILE_SIGNATURE = 0x04034B50, - ZIP_LOCAL_FILE_SIZE = 30, - ZIP_FILE_SIGNATURE = 0x02014B50, - ZIP_FILE_SIZE = 46, - ZIP_DIRECTORY_SIGNATURE = 0x06054B50, - ZIP_DIRECTORY_SIZE = 22 + ZIP_LOCAL_FILE_SIGNATURE = 0x04034B50, + ZIP_LOCAL_FILE_SIZE = 30, + ZIP_FILE_SIGNATURE = 0x02014B50, + ZIP_FILE_SIZE = 46, + ZIP_DIRECTORY_SIGNATURE = 0x06054B50, + ZIP_DIRECTORY_SIZE = 22 }; struct ziplocalfileheader { - uint signature; - ushort version, flags, compression, modtime, moddate; - uint crc32, compressedsize, uncompressedsize; - ushort namelength, extralength; + uint signature; + ushort version, flags, compression, modtime, moddate; + uint crc32, compressedsize, uncompressedsize; + ushort namelength, extralength; }; struct zipfileheader { - uint signature; - ushort version, needversion, flags, compression, modtime, moddate; - uint crc32, compressedsize, uncompressedsize; - ushort namelength, extralength, commentlength, disknumber, internalattribs; - uint externalattribs, offset; + uint signature; + ushort version, needversion, flags, compression, modtime, moddate; + uint crc32, compressedsize, uncompressedsize; + ushort namelength, extralength, commentlength, disknumber, internalattribs; + uint externalattribs, offset; }; struct zipdirectoryheader { - uint signature; - ushort disknumber, directorydisk, diskentries, entries; - uint size, offset; - ushort commentlength; + uint signature; + ushort disknumber, directorydisk, diskentries, entries; + uint size, offset; + ushort commentlength; }; struct zipfile { - char *name; - uint header, offset, size, compressedsize; - - zipfile() : name(NULL), header(0), offset(~0U), size(0), compressedsize(0) - { - } - ~zipfile() - { - DELETEA(name); - } + char *name; + uint header, offset, size, compressedsize; + + zipfile() : name(NULL), header(0), offset(~0U), size(0), compressedsize(0) + { + } + ~zipfile() + { + DELETEA(name); + } }; struct zipstream; struct ziparchive { - char *name; - FILE *data; - hashnameset files; - int openfiles; - zipstream *owner; - - ziparchive() : name(NULL), data(NULL), files(512), openfiles(0), owner(NULL) - { - } - ~ziparchive() - { - DELETEA(name); - if(data) { fclose(data); data = NULL; } - } + char *name; + FILE *data; + hashnameset files; + int openfiles; + zipstream *owner; + + ziparchive() : name(NULL), data(NULL), files(512), openfiles(0), owner(NULL) + { + } + ~ziparchive() + { + DELETEA(name); + if(data) { fclose(data); data = NULL; } + } }; static bool findzipdirectory(FILE *f, zipdirectoryheader &hdr) { - if(fseek(f, 0, SEEK_END) < 0) return false; - - long offset = ftell(f); - if(offset < 0) return false; - - uchar buf[1024], *src = NULL; - long end = max(offset - 0xFFFFL - ZIP_DIRECTORY_SIZE, 0L); - size_t len = 0; - const uint signature = lilswap(ZIP_DIRECTORY_SIGNATURE); - - while(offset > end) - { - size_t carry = min(len, size_t(ZIP_DIRECTORY_SIZE-1)), next = min(sizeof(buf) - carry, size_t(offset - end)); - offset -= next; - memmove(&buf[next], buf, carry); - if(next + carry < ZIP_DIRECTORY_SIZE || fseek(f, offset, SEEK_SET) < 0 || fread(buf, 1, next, f) != next) return false; - len = next + carry; - uchar *search = &buf[next-1]; - for(; search >= buf; search--) if(*(uint *)search == signature) break; - if(search >= buf) { src = search; break; } - } - - if(!src || &buf[len] - src < ZIP_DIRECTORY_SIZE) return false; - - hdr.signature = lilswap(*(uint *)src); src += 4; - hdr.disknumber = lilswap(*(ushort *)src); src += 2; - hdr.directorydisk = lilswap(*(ushort *)src); src += 2; - hdr.diskentries = lilswap(*(ushort *)src); src += 2; - hdr.entries = lilswap(*(ushort *)src); src += 2; - hdr.size = lilswap(*(uint *)src); src += 4; - hdr.offset = lilswap(*(uint *)src); src += 4; - hdr.commentlength = lilswap(*(ushort *)src); src += 2; - - if(hdr.signature != ZIP_DIRECTORY_SIGNATURE || hdr.disknumber != hdr.directorydisk || hdr.diskentries != hdr.entries) return false; - - return true; + if(fseek(f, 0, SEEK_END) < 0) return false; + + long offset = ftell(f); + if(offset < 0) return false; + + uchar buf[1024], *src = NULL; + long end = max(offset - 0xFFFFL - ZIP_DIRECTORY_SIZE, 0L); + size_t len = 0; + const uint signature = lilswap(ZIP_DIRECTORY_SIGNATURE); + + while(offset > end) + { + size_t carry = min(len, size_t(ZIP_DIRECTORY_SIZE-1)), next = min(sizeof(buf) - carry, size_t(offset - end)); + offset -= next; + memmove(&buf[next], buf, carry); + if(next + carry < ZIP_DIRECTORY_SIZE || fseek(f, offset, SEEK_SET) < 0 || fread(buf, 1, next, f) != next) return false; + len = next + carry; + uchar *search = &buf[next-1]; + for(; search >= buf; search--) if(*(uint *)search == signature) break; + if(search >= buf) { src = search; break; } + } + + if(!src || &buf[len] - src < ZIP_DIRECTORY_SIZE) return false; + + hdr.signature = lilswap(*(uint *)src); src += 4; + hdr.disknumber = lilswap(*(ushort *)src); src += 2; + hdr.directorydisk = lilswap(*(ushort *)src); src += 2; + hdr.diskentries = lilswap(*(ushort *)src); src += 2; + hdr.entries = lilswap(*(ushort *)src); src += 2; + hdr.size = lilswap(*(uint *)src); src += 4; + hdr.offset = lilswap(*(uint *)src); src += 4; + hdr.commentlength = lilswap(*(ushort *)src); src += 2; + + if(hdr.signature != ZIP_DIRECTORY_SIGNATURE || hdr.disknumber != hdr.directorydisk || hdr.diskentries != hdr.entries) return false; + + return true; } -#ifndef STANDALONE -VAR(dbgzip, 0, 0, 1); -#endif - static bool readzipdirectory(const char *archname, FILE *f, int entries, int offset, uint size, vector &files) { - uchar *buf = new (false) uchar[size], *src = buf; - if(!buf || fseek(f, offset, SEEK_SET) < 0 || fread(buf, 1, size, f) != size) { delete[] buf; return false; } - loopi(entries) - { - if(src + ZIP_FILE_SIZE > &buf[size]) break; - - zipfileheader hdr; - hdr.signature = lilswap(*(uint *)src); src += 4; - hdr.version = lilswap(*(ushort *)src); src += 2; - hdr.needversion = lilswap(*(ushort *)src); src += 2; - hdr.flags = lilswap(*(ushort *)src); src += 2; - hdr.compression = lilswap(*(ushort *)src); src += 2; - hdr.modtime = lilswap(*(ushort *)src); src += 2; - hdr.moddate = lilswap(*(ushort *)src); src += 2; - hdr.crc32 = lilswap(*(uint *)src); src += 4; - hdr.compressedsize = lilswap(*(uint *)src); src += 4; - hdr.uncompressedsize = lilswap(*(uint *)src); src += 4; - hdr.namelength = lilswap(*(ushort *)src); src += 2; - hdr.extralength = lilswap(*(ushort *)src); src += 2; - hdr.commentlength = lilswap(*(ushort *)src); src += 2; - hdr.disknumber = lilswap(*(ushort *)src); src += 2; - hdr.internalattribs = lilswap(*(ushort *)src); src += 2; - hdr.externalattribs = lilswap(*(uint *)src); src += 4; - hdr.offset = lilswap(*(uint *)src); src += 4; - if(hdr.signature != ZIP_FILE_SIGNATURE) break; - if(!hdr.namelength || !hdr.uncompressedsize || (hdr.compression && (hdr.compression != Z_DEFLATED || !hdr.compressedsize))) - { - src += hdr.namelength + hdr.extralength + hdr.commentlength; - continue; - } - if(src + hdr.namelength > &buf[size]) break; - - string pname; - int namelen = min((int)hdr.namelength, (int)sizeof(pname)-1); - memcpy(pname, src, namelen); - pname[namelen] = '\0'; - path(pname); - char *name = newstring(pname); - - zipfile &f = files.add(); - f.name = name; - f.header = hdr.offset; - f.size = hdr.uncompressedsize; - f.compressedsize = hdr.compression ? hdr.compressedsize : 0; -#ifndef STANDALONE - if(dbgzip) conoutf(CON_DEBUG, "%s: file %s, size %d, compress %d, flags %x", archname, name, hdr.uncompressedsize, hdr.compression, hdr.flags); -#endif - - src += hdr.namelength + hdr.extralength + hdr.commentlength; - } - delete[] buf; - - return files.length() > 0; + uchar *buf = new (false) uchar[size], *src = buf; + if(!buf || fseek(f, offset, SEEK_SET) < 0 || fread(buf, 1, size, f) != size) { delete[] buf; return false; } + loopi(entries) + { + if(src + ZIP_FILE_SIZE > &buf[size]) break; + + zipfileheader hdr; + hdr.signature = lilswap(*(uint *)src); src += 4; + hdr.version = lilswap(*(ushort *)src); src += 2; + hdr.needversion = lilswap(*(ushort *)src); src += 2; + hdr.flags = lilswap(*(ushort *)src); src += 2; + hdr.compression = lilswap(*(ushort *)src); src += 2; + hdr.modtime = lilswap(*(ushort *)src); src += 2; + hdr.moddate = lilswap(*(ushort *)src); src += 2; + hdr.crc32 = lilswap(*(uint *)src); src += 4; + hdr.compressedsize = lilswap(*(uint *)src); src += 4; + hdr.uncompressedsize = lilswap(*(uint *)src); src += 4; + hdr.namelength = lilswap(*(ushort *)src); src += 2; + hdr.extralength = lilswap(*(ushort *)src); src += 2; + hdr.commentlength = lilswap(*(ushort *)src); src += 2; + hdr.disknumber = lilswap(*(ushort *)src); src += 2; + hdr.internalattribs = lilswap(*(ushort *)src); src += 2; + hdr.externalattribs = lilswap(*(uint *)src); src += 4; + hdr.offset = lilswap(*(uint *)src); src += 4; + if(hdr.signature != ZIP_FILE_SIGNATURE) break; + if(!hdr.namelength || !hdr.uncompressedsize || (hdr.compression && (hdr.compression != Z_DEFLATED || !hdr.compressedsize))) + { + src += hdr.namelength + hdr.extralength + hdr.commentlength; + continue; + } + if(src + hdr.namelength > &buf[size]) break; + + string pname; + int namelen = min((int)hdr.namelength, (int)sizeof(pname)-1); + memcpy(pname, src, namelen); + pname[namelen] = '\0'; + path(pname); + char *name = newstring(pname); + + zipfile &f = files.add(); + f.name = name; + f.header = hdr.offset; + f.size = hdr.uncompressedsize; + f.compressedsize = hdr.compression ? hdr.compressedsize : 0; + + src += hdr.namelength + hdr.extralength + hdr.commentlength; + } + delete[] buf; + + return files.length() > 0; } static bool readlocalfileheader(FILE *f, ziplocalfileheader &h, uint offset) { - uchar buf[ZIP_LOCAL_FILE_SIZE]; - if(fseek(f, offset, SEEK_SET) < 0 || fread(buf, 1, ZIP_LOCAL_FILE_SIZE, f) != ZIP_LOCAL_FILE_SIZE) - return false; - uchar *src = buf; - h.signature = lilswap(*(uint *)src); src += 4; - h.version = lilswap(*(ushort *)src); src += 2; - h.flags = lilswap(*(ushort *)src); src += 2; - h.compression = lilswap(*(ushort *)src); src += 2; - h.modtime = lilswap(*(ushort *)src); src += 2; - h.moddate = lilswap(*(ushort *)src); src += 2; - h.crc32 = lilswap(*(uint *)src); src += 4; - h.compressedsize = lilswap(*(uint *)src); src += 4; - h.uncompressedsize = lilswap(*(uint *)src); src += 4; - h.namelength = lilswap(*(ushort *)src); src += 2; - h.extralength = lilswap(*(ushort *)src); src += 2; - if(h.signature != ZIP_LOCAL_FILE_SIGNATURE) return false; - // h.uncompressedsize or h.compressedsize may be zero - so don't validate - return true; + uchar buf[ZIP_LOCAL_FILE_SIZE]; + if(fseek(f, offset, SEEK_SET) < 0 || fread(buf, 1, ZIP_LOCAL_FILE_SIZE, f) != ZIP_LOCAL_FILE_SIZE) + return false; + uchar *src = buf; + h.signature = lilswap(*(uint *)src); src += 4; + h.version = lilswap(*(ushort *)src); src += 2; + h.flags = lilswap(*(ushort *)src); src += 2; + h.compression = lilswap(*(ushort *)src); src += 2; + h.modtime = lilswap(*(ushort *)src); src += 2; + h.moddate = lilswap(*(ushort *)src); src += 2; + h.crc32 = lilswap(*(uint *)src); src += 4; + h.compressedsize = lilswap(*(uint *)src); src += 4; + h.uncompressedsize = lilswap(*(uint *)src); src += 4; + h.namelength = lilswap(*(ushort *)src); src += 2; + h.extralength = lilswap(*(ushort *)src); src += 2; + if(h.signature != ZIP_LOCAL_FILE_SIGNATURE) return false; + // h.uncompressedsize or h.compressedsize may be zero - so don't validate + return true; } static vector archives; ziparchive *findzip(const char *name) { - loopv(archives) if(!strcmp(name, archives[i]->name)) return archives[i]; - return NULL; + loopv(archives) if(!strcmp(name, archives[i]->name)) return archives[i]; + return NULL; } static bool checkprefix(vector &files, const char *prefix, int prefixlen) { - loopv(files) - { - if(!strncmp(files[i].name, prefix, prefixlen)) return false; - } - return true; + loopv(files) + { + if(!strncmp(files[i].name, prefix, prefixlen)) return false; + } + return true; } static void mountzip(ziparchive &arch, vector &files, const char *mountdir, const char *stripdir) { - string packagesdir = "packages/"; - path(packagesdir); - size_t striplen = stripdir ? strlen(stripdir) : 0; - if(!mountdir && !stripdir) loopv(files) - { - zipfile &f = files[i]; - const char *foundpackages = strstr(f.name, packagesdir); - if(foundpackages) - { - if(foundpackages > f.name) - { - stripdir = f.name; - striplen = foundpackages - f.name; - } - break; - } - const char *foundogz = strstr(f.name, ".ogz"); - if(foundogz) - { - const char *ogzdir = foundogz; - while(--ogzdir >= f.name && *ogzdir != PATHDIV); - if(ogzdir < f.name || checkprefix(files, f.name, ogzdir + 1 - f.name)) - { - if(ogzdir >= f.name) - { - stripdir = f.name; - striplen = ogzdir + 1 - f.name; - } - if(!mountdir) mountdir = "packages/maps/"; - break; - } - } - } - string mdir = "", fname; - if(mountdir) - { - copystring(mdir, mountdir); - if(fixpackagedir(mdir) <= 1) mdir[0] = '\0'; - } - loopv(files) - { - zipfile &f = files[i]; - formatstring(fname, "%s%s", mdir, striplen && !strncmp(f.name, stripdir, striplen) ? &f.name[striplen] : f.name); - if(arch.files.access(fname)) continue; - char *mname = newstring(fname); - zipfile &mf = arch.files[mname]; - mf = f; - mf.name = mname; - } + string packagesdir = "packages/"; + path(packagesdir); + size_t striplen = stripdir ? strlen(stripdir) : 0; + if(!mountdir && !stripdir) loopv(files) + { + zipfile &f = files[i]; + const char *foundpackages = strstr(f.name, packagesdir); + if(foundpackages) + { + if(foundpackages > f.name) + { + stripdir = f.name; + striplen = foundpackages - f.name; + } + break; + } + const char *foundogz = strstr(f.name, ".ogz"); + if(foundogz) + { + const char *ogzdir = foundogz; + while(--ogzdir >= f.name && *ogzdir != PATHDIV); + if(ogzdir < f.name || checkprefix(files, f.name, ogzdir + 1 - f.name)) + { + if(ogzdir >= f.name) + { + stripdir = f.name; + striplen = ogzdir + 1 - f.name; + } + if(!mountdir) mountdir = "packages/maps/"; + break; + } + } + } + string mdir = "", fname; + if(mountdir) + { + copystring(mdir, mountdir); + if(fixpackagedir(mdir) <= 1) mdir[0] = '\0'; + } + loopv(files) + { + zipfile &f = files[i]; + formatstring(fname, "%s%s", mdir, striplen && !strncmp(f.name, stripdir, striplen) ? &f.name[striplen] : f.name); + if(arch.files.access(fname)) continue; + char *mname = newstring(fname); + zipfile &mf = arch.files[mname]; + mf = f; + mf.name = mname; + } } bool addzip(const char *name, const char *mount = NULL, const char *strip = NULL) { - string pname; - copystring(pname, name); - path(pname); - size_t plen = strlen(pname); - if(plen < 4 || !strchr(&pname[plen-4], '.')) concatstring(pname, ".zip"); - - ziparchive *exists = findzip(pname); - if(exists) - { - conoutf(CON_ERROR, "already added zip %s", pname); - return true; - } - - FILE *f = fopen(findfile(pname, "rb"), "rb"); - if(!f) - { - conoutf(CON_ERROR, "could not open file %s", pname); - return false; - } - zipdirectoryheader h; - vector files; - if(!findzipdirectory(f, h) || !readzipdirectory(pname, f, h.entries, h.offset, h.size, files)) - { - conoutf(CON_ERROR, "could not read directory in zip %s", pname); - fclose(f); - return false; - } - - ziparchive *arch = new ziparchive; - arch->name = newstring(pname); - arch->data = f; - mountzip(*arch, files, mount, strip); - archives.add(arch); - - conoutf("added zip %s", pname); - return true; + string pname; + copystring(pname, name); + path(pname); + size_t plen = strlen(pname); + if(plen < 4 || !strchr(&pname[plen-4], '.')) concatstring(pname, ".zip"); + + ziparchive *exists = findzip(pname); + if(exists) + { + conoutf(CON_ERROR, "already added zip %s", pname); + return true; + } + + FILE *f = fopen(findfile(pname, "rb"), "rb"); + if(!f) + { + conoutf(CON_ERROR, "could not open file %s", pname); + return false; + } + zipdirectoryheader h; + vector files; + if(!findzipdirectory(f, h) || !readzipdirectory(pname, f, h.entries, h.offset, h.size, files)) + { + conoutf(CON_ERROR, "could not read directory in zip %s", pname); + fclose(f); + return false; + } + + ziparchive *arch = new ziparchive; + arch->name = newstring(pname); + arch->data = f; + mountzip(*arch, files, mount, strip); + archives.add(arch); + + conoutf("added zip %s", pname); + return true; } bool removezip(const char *name) { - string pname; - copystring(pname, name); - path(pname); - int plen = (int)strlen(pname); - if(plen < 4 || !strchr(&pname[plen-4], '.')) concatstring(pname, ".zip"); - ziparchive *exists = findzip(pname); - if(!exists) - { - conoutf(CON_ERROR, "zip %s is not loaded", pname); - return false; - } - if(exists->openfiles) - { - conoutf(CON_ERROR, "zip %s has open files", pname); - return false; - } - conoutf("removed zip %s", exists->name); - archives.removeobj(exists); - delete exists; - return true; + string pname; + copystring(pname, name); + path(pname); + int plen = (int)strlen(pname); + if(plen < 4 || !strchr(&pname[plen-4], '.')) concatstring(pname, ".zip"); + ziparchive *exists = findzip(pname); + if(!exists) + { + conoutf(CON_ERROR, "zip %s is not loaded", pname); + return false; + } + if(exists->openfiles) + { + conoutf(CON_ERROR, "zip %s has open files", pname); + return false; + } + conoutf("removed zip %s", exists->name); + archives.removeobj(exists); + delete exists; + return true; } struct zipstream : stream { - enum - { - BUFSIZE = 16384 - }; - - ziparchive *arch; - zipfile *info; - z_stream zfile; - uchar *buf; - uint reading; - bool ended; - - zipstream() : arch(NULL), info(NULL), buf(NULL), reading(~0U), ended(false) - { - zfile.zalloc = NULL; - zfile.zfree = NULL; - zfile.opaque = NULL; - zfile.next_in = zfile.next_out = NULL; - zfile.avail_in = zfile.avail_out = 0; - } - - ~zipstream() - { - close(); - } - - void readbuf(uint size = BUFSIZE) - { - if(!zfile.avail_in) zfile.next_in = (Bytef *)buf; - size = min(size, uint(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in])); - if(arch->owner != this) - { - arch->owner = NULL; - if(fseek(arch->data, reading, SEEK_SET) >= 0) arch->owner = this; - else return; - } - uint remaining = info->offset + info->compressedsize - reading, - n = arch->owner == this ? fread(zfile.next_in + zfile.avail_in, 1, min(size, remaining), arch->data) : 0U; - zfile.avail_in += n; - reading += n; - } - - bool open(ziparchive *a, zipfile *f) - { - if(f->offset == ~0U) - { - ziplocalfileheader h; - a->owner = NULL; - if(!readlocalfileheader(a->data, h, f->header)) return false; - f->offset = f->header + ZIP_LOCAL_FILE_SIZE + h.namelength + h.extralength; - } - - if(f->compressedsize && inflateInit2(&zfile, -MAX_WBITS) != Z_OK) return false; - - a->openfiles++; - arch = a; - info = f; - reading = f->offset; - ended = false; - if(f->compressedsize) buf = new uchar[BUFSIZE]; - return true; - } - - void stopreading() - { - if(reading == ~0U) return; -#ifndef STANDALONE - if(dbgzip) conoutf(CON_DEBUG, info->compressedsize ? "%s: zfile.total_out %u, info->size %u" : "%s: reading %u, info->size %u", info->name, info->compressedsize ? uint(zfile.total_out) : reading - info->offset, info->size); -#endif - if(info->compressedsize) inflateEnd(&zfile); - reading = ~0U; - } - - void close() - { - stopreading(); - DELETEA(buf); - if(arch) { arch->owner = NULL; arch->openfiles--; arch = NULL; } - } - - offset size() { return info->size; } - bool end() { return reading == ~0U || ended; } - offset tell() { return reading != ~0U ? (info->compressedsize ? zfile.total_out : reading - info->offset) : offset(-1); } - - bool seek(offset pos, int whence) - { - if(reading == ~0U) return false; - if(!info->compressedsize) - { - switch(whence) - { - case SEEK_END: pos += info->offset + info->size; break; - case SEEK_CUR: pos += reading; break; - case SEEK_SET: pos += info->offset; break; - default: return false; - } - pos = clamp(pos, offset(info->offset), offset(info->offset + info->size)); - arch->owner = NULL; - if(fseek(arch->data, int(pos), SEEK_SET) < 0) return false; - arch->owner = this; - reading = pos; - ended = false; - return true; - } - - switch(whence) - { - case SEEK_END: pos += info->size; break; - case SEEK_CUR: pos += zfile.total_out; break; - case SEEK_SET: break; - default: return false; - } - - if(pos >= (offset)info->size) - { - reading = info->offset + info->compressedsize; - zfile.next_in += zfile.avail_in; - zfile.avail_in = 0; - zfile.total_in = info->compressedsize; - zfile.total_out = info->size; - arch->owner = NULL; - ended = false; - return true; - } - - if(pos < 0) return false; - if(pos >= (offset)zfile.total_out) pos -= zfile.total_out; - else - { - if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf)) - { - zfile.avail_in += zfile.total_in; - zfile.next_in -= zfile.total_in; - } - else - { - arch->owner = NULL; - zfile.avail_in = 0; - zfile.next_in = NULL; - reading = info->offset; - } - inflateReset(&zfile); - } - - uchar skip[512]; - while(pos > 0) - { - size_t skipped = (size_t)min(pos, (offset)sizeof(skip)); - if(read(skip, skipped) != skipped) return false; - pos -= skipped; - } - - ended = false; - return true; - } - - size_t read(void *buf, size_t len) - { - if(reading == ~0U || !buf || !len) return 0; - if(!info->compressedsize) - { - if(arch->owner != this) - { - arch->owner = NULL; - if(fseek(arch->data, reading, SEEK_SET) < 0) { stopreading(); return 0; } - arch->owner = this; - } - - size_t n = fread(buf, 1, min(len, size_t(info->size + info->offset - reading)), arch->data); - reading += n; - if(n < len) ended = true; - return n; - } - - zfile.next_out = (Bytef *)buf; - zfile.avail_out = len; - while(zfile.avail_out > 0) - { - if(!zfile.avail_in) readbuf(BUFSIZE); - int err = inflate(&zfile, Z_NO_FLUSH); - if(err != Z_OK) - { - if(err == Z_STREAM_END) ended = true; - else - { -#ifndef STANDALONE - if(dbgzip) conoutf(CON_DEBUG, "inflate error: %s", zError(err)); -#endif - stopreading(); - } - break; - } - } - return len - zfile.avail_out; - } + enum + { + BUFSIZE = 16384 + }; + + ziparchive *arch; + zipfile *info; + z_stream zfile; + uchar *buf; + uint reading; + bool ended; + + zipstream() : arch(NULL), info(NULL), buf(NULL), reading(~0U), ended(false) + { + zfile.zalloc = NULL; + zfile.zfree = NULL; + zfile.opaque = NULL; + zfile.next_in = zfile.next_out = NULL; + zfile.avail_in = zfile.avail_out = 0; + } + + ~zipstream() + { + close(); + } + + void readbuf(uint size = BUFSIZE) + { + if(!zfile.avail_in) zfile.next_in = (Bytef *)buf; + size = min(size, uint(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in])); + if(arch->owner != this) + { + arch->owner = NULL; + if(fseek(arch->data, reading, SEEK_SET) >= 0) arch->owner = this; + else return; + } + uint remaining = info->offset + info->compressedsize - reading, + n = arch->owner == this ? fread(zfile.next_in + zfile.avail_in, 1, min(size, remaining), arch->data) : 0U; + zfile.avail_in += n; + reading += n; + } + + bool open(ziparchive *a, zipfile *f) + { + if(f->offset == ~0U) + { + ziplocalfileheader h; + a->owner = NULL; + if(!readlocalfileheader(a->data, h, f->header)) return false; + f->offset = f->header + ZIP_LOCAL_FILE_SIZE + h.namelength + h.extralength; + } + + if(f->compressedsize && inflateInit2(&zfile, -MAX_WBITS) != Z_OK) return false; + + a->openfiles++; + arch = a; + info = f; + reading = f->offset; + ended = false; + if(f->compressedsize) buf = new uchar[BUFSIZE]; + return true; + } + + void stopreading() + { + if(reading == ~0U) return; + if(info->compressedsize) inflateEnd(&zfile); + reading = ~0U; + } + + void close() + { + stopreading(); + DELETEA(buf); + if(arch) { arch->owner = NULL; arch->openfiles--; arch = NULL; } + } + + offset size() { return info->size; } + bool end() { return reading == ~0U || ended; } + offset tell() { return reading != ~0U ? (info->compressedsize ? zfile.total_out : reading - info->offset) : offset(-1); } + + bool seek(offset pos, int whence) + { + if(reading == ~0U) return false; + if(!info->compressedsize) + { + switch(whence) + { + case SEEK_END: pos += info->offset + info->size; break; + case SEEK_CUR: pos += reading; break; + case SEEK_SET: pos += info->offset; break; + default: return false; + } + pos = clamp(pos, offset(info->offset), offset(info->offset + info->size)); + arch->owner = NULL; + if(fseek(arch->data, int(pos), SEEK_SET) < 0) return false; + arch->owner = this; + reading = pos; + ended = false; + return true; + } + + switch(whence) + { + case SEEK_END: pos += info->size; break; + case SEEK_CUR: pos += zfile.total_out; break; + case SEEK_SET: break; + default: return false; + } + + if(pos >= (offset)info->size) + { + reading = info->offset + info->compressedsize; + zfile.next_in += zfile.avail_in; + zfile.avail_in = 0; + zfile.total_in = info->compressedsize; + zfile.total_out = info->size; + arch->owner = NULL; + ended = false; + return true; + } + + if(pos < 0) return false; + if(pos >= (offset)zfile.total_out) pos -= zfile.total_out; + else + { + if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf)) + { + zfile.avail_in += zfile.total_in; + zfile.next_in -= zfile.total_in; + } + else + { + arch->owner = NULL; + zfile.avail_in = 0; + zfile.next_in = NULL; + reading = info->offset; + } + inflateReset(&zfile); + } + + uchar skip[512]; + while(pos > 0) + { + size_t skipped = (size_t)min(pos, (offset)sizeof(skip)); + if(read(skip, skipped) != skipped) return false; + pos -= skipped; + } + + ended = false; + return true; + } + + size_t read(void *buf, size_t len) + { + if(reading == ~0U || !buf || !len) return 0; + if(!info->compressedsize) + { + if(arch->owner != this) + { + arch->owner = NULL; + if(fseek(arch->data, reading, SEEK_SET) < 0) { stopreading(); return 0; } + arch->owner = this; + } + + size_t n = fread(buf, 1, min(len, size_t(info->size + info->offset - reading)), arch->data); + reading += n; + if(n < len) ended = true; + return n; + } + + zfile.next_out = (Bytef *)buf; + zfile.avail_out = len; + while(zfile.avail_out > 0) + { + if(!zfile.avail_in) readbuf(BUFSIZE); + int err = inflate(&zfile, Z_NO_FLUSH); + if(err != Z_OK) + { + if(err == Z_STREAM_END) ended = true; + else stopreading(); + break; + } + } + return len - zfile.avail_out; + } }; stream *openzipfile(const char *name, const char *mode) { - for(; *mode; mode++) if(*mode=='w' || *mode=='a') return NULL; - loopvrev(archives) - { - ziparchive *arch = archives[i]; - zipfile *f = arch->files.access(name); - if(!f) continue; - zipstream *s = new zipstream; - if(s->open(arch, f)) return s; - delete s; - } - return NULL; + for(; *mode; mode++) if(*mode=='w' || *mode=='a') return NULL; + loopvrev(archives) + { + ziparchive *arch = archives[i]; + zipfile *f = arch->files.access(name); + if(!f) continue; + zipstream *s = new zipstream; + if(s->open(arch, f)) return s; + delete s; + } + return NULL; } bool findzipfile(const char *name) { - loopvrev(archives) - { - ziparchive *arch = archives[i]; - if(arch->files.access(name)) return true; - } - return false; + loopvrev(archives) + { + ziparchive *arch = archives[i]; + if(arch->files.access(name)) return true; + } + return false; } int listzipfiles(const char *dir, const char *ext, vector &files) { - size_t extsize = ext ? strlen(ext)+1 : 0, dirsize = strlen(dir); - int dirs = 0; - loopvrev(archives) - { - ziparchive *arch = archives[i]; - int oldsize = files.length(); - enumerate(arch->files, zipfile, f, - { - if(strncmp(f.name, dir, dirsize)) continue; - const char *name = f.name + dirsize; - if(name[0] == PATHDIV) name++; - if(strchr(name, PATHDIV)) continue; - if(!ext) files.add(newstring(name)); - else - { - size_t namelen = strlen(name); - if(namelen > extsize) - { - namelen -= extsize; - if(name[namelen] == '.' && strncmp(name+namelen+1, ext, extsize-1)==0) - files.add(newstring(name, namelen)); - } - } - }); - if(files.length() > oldsize) dirs++; - } - return dirs; + size_t extsize = ext ? strlen(ext)+1 : 0, dirsize = strlen(dir); + int dirs = 0; + loopvrev(archives) + { + ziparchive *arch = archives[i]; + int oldsize = files.length(); + enumerate(arch->files, zipfile, f, + { + if(strncmp(f.name, dir, dirsize)) continue; + const char *name = f.name + dirsize; + if(name[0] == PATHDIV) name++; + if(strchr(name, PATHDIV)) continue; + if(!ext) files.add(newstring(name)); + else + { + size_t namelen = strlen(name); + if(namelen > extsize) + { + namelen -= extsize; + if(name[namelen] == '.' && strncmp(name+namelen+1, ext, extsize-1)==0) + files.add(newstring(name, namelen)); + } + } + }); + if(files.length() > oldsize) dirs++; + } + return dirs; } #ifndef STANDALONE