/*traceline/tracebox returns trace_brush_id and trace_brush_faceid mod should track selected brush list instead of polling. to move 50 brushes, mod needs to get+delete+transform+create brush ids are ints. this allows different clients to use different ranges without float problems. */ #define FIXME typedef struct { int model; int id; int face; //fixme: do we need an array of faces here? } selbrush_t; selbrush_t *selectedbrushes; int selectedbrushcount; var int selectedbrushmodel = 1; //by default, the worldmodel. this is only really needed for inserts. vector facepoints[64]; var float autocvar_ca_brush_view = 0; //0=normal, 1=x, 2=y, 3=z var float autocvar_ca_brush_viewsize = 1024; //for different views. #define EPSILON (1.0 / 32) //inprecision sucks. //history is implemented using a ringbuffer typedef struct { float timestamp; int brushmodel; int wasdelete; int id; brushface_t *brushdata; int numfaces; int contents; } history_t; nosave history_t historyring[512]; int history_min; //oldest we can go int history; //updated on each change int history_max; //max value for redo int(int modelidx, int brushid) brush_isselected = { for (int i = 0; i < selectedbrushcount; i++) if (selectedbrushes[i].id == brushid) if (selectedbrushes[i].model == modelidx) return i+1; return 0; }; int(int modelidx, int brushid) brush_deselect = { int i = brush_isselected(modelidx, brushid); if (!i) return FALSE; brush_selected(modelidx, brushid, -1, FALSE); memcpy(&selectedbrushes[i-1], &selectedbrushes[i], sizeof(selbrush_t)*(selectedbrushcount-i)); selectedbrushcount--; return TRUE; }; void() brush_deselectall = { for (int i = 0; i < selectedbrushcount; i++) brush_selected(selectedbrushes[i].model, selectedbrushes[i].id, -1, FALSE); selectedbrushcount = 0; }; void(int modelidx, int brushid) brush_select = { if (!brush_isselected(modelidx, brushid)) { selbrush_t *n = memalloc(sizeof(selbrush_t) * (selectedbrushcount+1)); memcpy(n, selectedbrushes, sizeof(selbrush_t)*selectedbrushcount); memfree(selectedbrushes); selectedbrushes = n; n[selectedbrushcount].model = modelidx; n[selectedbrushcount].id = brushid; selectedbrushcount++; brush_selected(modelidx, brushid, -1, TRUE); } selectedbrushmodel = modelidx; }; //when replaying history, ids get changed, which makes things fun static void(int modelidx, int old, int new) history_remap = { for (int i = history_min; i < history_max; i++) { history_t *h = &historyring[i & (historyring.length-1)]; if (h->id == old) h->id = new; } int i = brush_isselected(modelidx, old); if (i) { i--; brush_selected(modelidx, old, -1, FALSE); brush_selected(modelidx, new, -1, TRUE); selectedbrushes[i].id = new; } }; void() brush_undo = { do { if (history <= history_min) return; history--; history_t *h = &historyring[history & (historyring.length-1)]; //we're undoing, so deletes create and creates delete. weird, yes. if (h->wasdelete) history_remap(h->brushmodel, h->id, brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents)); else { brush_delete(h->brushmodel, h->id); } if (history == history_min) break; //as far back as we can go, more timestamps are invalid } while (historyring[(history-1) & (historyring.length-1)].timestamp == h->timestamp); }; void() brush_redo = { do { if (history >= history_max) return; history_t *h = &historyring[history & (historyring.length-1)]; //we're redoing stuff that has previously been done. yay. if (h->wasdelete) { brush_delete(h->brushmodel, h->id); } else { history_remap(h->brushmodel, h->id, brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents)); } history++; if (history == history_max) return; //don't poke beyond the end... } while (historyring[history & (historyring.length-1)].timestamp == h->timestamp); }; //create and journal. int(int mod, brushface_t *faces, int numfaces, int contents, float selectnow) brush_history_create = { history_t *h = &historyring[history & (historyring.length-1)]; h->timestamp = time; memfree(h->brushdata); h->brushdata = memalloc(sizeof(brushface_t) * numfaces); memcpy(h->brushdata, faces, sizeof(brushface_t) * numfaces); h->numfaces = numfaces; h->contents = contents; h->brushmodel = mod; h->wasdelete = FALSE; h->id = brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents); if (h->id) history++; history_max = history; //always break any pending redos if (history_min < history_max - historyring.length) history_min = history_max - historyring.length; if (selectnow) brush_select(mod, h->id); return h->id; }; //delete and journal. void(int mod, int id) brush_history_delete = { int numfaces = brush_get(mod, id, __NULL__, 0, __NULL__); brush_deselect(mod, id); if (!numfaces) return; //doesn't exist or something, some kind of error. we can't log a delete if it didn't delete anything, if only because we can't recreate it if its undone. history_t *h = &historyring[history & (historyring.length-1)]; h->timestamp = time; h->brushmodel = mod; h->id = id; h->contents = 0; h->wasdelete = TRUE; memfree(h->brushdata); h->brushdata = memalloc(sizeof(brushface_t) * numfaces); h->numfaces = brush_get(mod, id, h->brushdata, numfaces, &h->contents); brush_delete(mod, id); history++; history_max = history; //always break any pending redos if (history_min < history_max - historyring.length) history_min = history_max - historyring.length; }; int(int mod, int id, brushface_t *faces, int numfaces, int contents) brush_history_edit = { int wasselected = brush_isselected(mod, id); if (wasselected) selectedbrushes[wasselected-1].id = -id; //hack so that brush_history_delete won't remove this entry. brush_history_delete(mod, id); id = brush_history_create(mod, faces, numfaces, contents, FALSE); if (wasselected) { selectedbrushes[wasselected-1].id = id; brush_selected(mod, id, -1, TRUE); } return id; }; typedef struct { int numverts; int numidx; vector *p; int *i; int selectedvert1; int selectedvert2; int model; int brush; } vertsoup_t; vertsoup_t vertedit; brushface_t tmp_faces[64]; int tmp_numfaces; int tmp_contents; //int selected; int brushlist[2048]; var string autocvar_ca_newbrushtexture = "metal4_2"; var float autocvar_ca_newbrushheight = 64; var float autocvar_ca_grid = 16; enum int { BT_NONE, //selection BT_MOVE = BT_NONE, BT_ROTATE, BT_MERGE, BT_PUSHFACE, BT_CREATE, BT_CREATEDRAG, BT_CLONEDISPLACE, BT_SLICE, BT_MOVETEXTURE, BT_VERTEXEDIT }; int mousetool; int brushtool; int bt_points; vector bt_displace; vector bt_point[64]; int nogrid; //+nogrid, temporarily disables grid locks //basic cube plane normals, for selection. static nosave const vector axis[6] = { '-1 0 0', '0 -1 0', '0 0 -1', '1 0 0', '0 1 0', '0 0 1' }; float dists[6]; //generates default quakeed-style texture mapping for the given surface. //this sucks for cylinders, but keeps slopes and things easy. void(brushface_t *fa) reset_texturecoords = { //figure out some default texture coords fa->sdir = '0 0 0'; fa->sbias = 0; fa->tdir = '0 0 0'; fa->tbias = 0; float a=fabs(fa->planenormal[0]),b=fabs(fa->planenormal[1]),c=fabs(fa->planenormal[2]); if (a>=b&&a>=c) fa->sdir[1] = 1; else fa->sdir[0] = 1; if (c>a&&c>b) fa->tdir[1] = -1; else fa->tdir[2] = -1; }; static int(brushface_t *fa, int famax, vector *points, int numpoints, float height) BrushFromPoints = { int count = 0; fa->planenormal = normalize(crossproduct(points[2] - points[0], points[1] - points[0])); fa->planedist = bt_point[0] * fa->planenormal + height; fa->shadername = autocvar_ca_newbrushtexture; reset_texturecoords(fa); fa++; fa->planenormal = -normalize(crossproduct(points[2] - points[0], points[1] - points[0])); fa->planedist = (bt_point[0] * fa->planenormal); fa->shadername = autocvar_ca_newbrushtexture; reset_texturecoords(fa); fa++; count = 2; for (int p = 0; p < numpoints; p++) { int n = p + 1; if (n == numpoints) n = 0; fa->planenormal = normalize(crossproduct(points[p] - bt_point[n], tmp_faces[0].planenormal)); fa->planedist = points[p] * fa->planenormal; fa->shadername = autocvar_ca_newbrushtexture; reset_texturecoords(fa); fa++; count++; } return count; } vector(vector guess) brush_snappoint = { if (nogrid) return guess; int facenum, points; float bestdist = autocvar_ca_grid*autocvar_ca_grid; //worst case value so we don't snap to grid when there's a vertex 0.001qu away from the grid. vector best = guess * (1.0/autocvar_ca_grid); best_x = rint(best_x); //snap to grid best_y = rint(best_y); best_z = rint(best_z); best *= autocvar_ca_grid; //find surfaces within 32qu of the point (via plane volume). use a tetrahedron instead if you want something more circular for (facenum = 0; facenum < axis.length; facenum++) dists[facenum] = (guess * axis[facenum]) + autocvar_ca_grid; int numbrushes = brush_findinvolume(selectedbrushmodel, axis, dists, 6, brushlist, __NULL__, brushlist.length); for (int brushnum = 0; brushnum < numbrushes; brushnum++) { for (facenum = 0; ; ) { points = brush_getfacepoints(selectedbrushmodel, brushlist[brushnum], ++facenum, facepoints, facepoints.length); if (!points) break; //end of face list, I guess //walk the faces we found and use the point if its nearer than our previous guess. for (int point = 0; point < points; point++) { vector disp = facepoints[point] - guess; float dist = disp*disp; if (dist < bestdist) best = facepoints[point]; } } } return best; }; void(int brushid) editor_drawbbox = { static vector bbox[2]; int p = brush_getfacepoints(selectedbrushmodel, brushid, 0, bbox, bbox.length); if (p == 2) { R_BeginPolygon("chop"); #define line(x,y) R_PolygonVertex(x, '0 0', '1 0 0', 1); R_PolygonVertex(y, '0 0', '1 0 0', 1); R_EndPolygon() line(bbox[0], ([bbox[1][0], bbox[0][1], bbox[0][2]])); line(bbox[0], ([bbox[0][0], bbox[1][1], bbox[0][2]])); line(bbox[0], ([bbox[0][0], bbox[0][1], bbox[1][2]])); line(bbox[1], ([bbox[0][0], bbox[1][1], bbox[1][2]])); line(bbox[1], ([bbox[1][0], bbox[0][1], bbox[1][2]])); line(bbox[1], ([bbox[1][0], bbox[1][1], bbox[0][2]])); line(([bbox[1][0], bbox[0][1], bbox[0][2]]), ([bbox[1][0], bbox[1][1], bbox[0][2]])); line(([bbox[1][0], bbox[0][1], bbox[0][2]]), ([bbox[1][0], bbox[0][1], bbox[1][2]])); line(([bbox[0][0], bbox[1][1], bbox[0][2]]), ([bbox[0][0], bbox[1][1], bbox[1][2]])); line(([bbox[0][0], bbox[1][1], bbox[0][2]]), ([bbox[1][0], bbox[1][1], bbox[0][2]])); line(([bbox[0][0], bbox[0][1], bbox[1][2]]), ([bbox[0][0], bbox[1][1], bbox[1][2]])); line(([bbox[0][0], bbox[0][1], bbox[1][2]]), ([bbox[1][0], bbox[0][1], bbox[1][2]])); #undef line } }; //yay pointers! //move a brush so that its planes all move without any translations in positions or texcoords static void brushface_translate(brushface_t *fa, int numfaces, vector displacement) { int i; for (i = 0; i < numfaces; i++, fa++) { fa->planedist += fa->planenormal * displacement; fa->sbias -= fa->sdir * displacement; fa->tbias -= fa->tdir * displacement; } }; //rotates a list of faces by the current v_* vectors, around the origin. //translate before+after first in order to deal with pivots. static void brushface_rotate(brushface_t *fa, int numfaces) { for (int i = 0; i < numfaces; i++, fa++) { vector orig = fa->planenormal; fa->planenormal[0] = orig * v_forward; fa->planenormal[1] = orig * -v_right; //quake just isn't right... fa->planenormal[2] = orig * v_up; orig = fa->sdir; fa->sdir[0] = orig * v_forward; fa->sdir[1] = orig * -v_right; //quake just isn't right... fa->sdir[2] = orig * v_up; orig = fa->tdir; fa->tdir[0] = orig * v_forward; fa->tdir[1] = orig * -v_right; //quake just isn't right... fa->tdir[2] = orig * v_up; } }; vector(vector dir) axialize = { vector a; a_x = fabs(dir_x); a_y = fabs(dir_y); a_z = fabs(dir_z); if (a_x > a_y && a_x > a_z) return (dir_x > 0)?'1 0 0':'-1 0 0'; if (a_y > a_x && a_y > a_z) return (dir_y > 0)?'0 1 0':'0 -1 0'; return (dir_z > 0)?'0 0 1':'0 0 -1'; }; vector(vector in) channelizeangle = { in_x = anglemod(in_x); in_y = anglemod(in_y); in_z = anglemod(in_z); if (in_x > 180) in_x -= 360; if (in_y > 180) in_y -= 360; if (in_z > 180) in_z -= 360; float fx = fabs(in_x); float fy = fabs(in_y); float fz = fabs(in_z); cprint(sprintf("%v", in)); if (fx > fy && fx > fz) return [in_x,0,0]; if (fy > fz) return [0,in_y,0]; return [0,0,in_z]; } vector(vector p1, vector p2, vector norm, float dist) planelinepoint = { float d1 = p1*norm - dist; float d2 = p2*norm - dist; float frac = d1 / (d2-d1); // frac = bound(0, frac, 1); return p2 + (p1-p2)*frac; //convert that frac into an actual position }; int(brushface_t *faces, int numfaces, vector *points, int numpoints) isconcave = { int result = 0; //if any of the points are outside the brush, then we know that one of the planes cut straight through in a concavey kind of way for(int f = 0; f < numfaces; f++) { vector n = faces[f].planenormal; float d = faces[f].planedist + EPSILON; //epsilon to cover precision issues for (int p = 0; p < numpoints; p++) { if (points[p] * n > d) { result++; break; } } } return result; }; //returns the resulting brush id int(int model, int brush1, int brush2, int face1, int face2) mergebrushes = { int extrafaces; float found = FALSE; brushface_t *fa; brushface_t *infa; int i; if (brush1 == brush2) { print("cannot merge brush with itself\n"); return 0; } if (!brush1 || !brush2) { print("no brush targetted\n"); return 0; } tmp_numfaces = brush_get(model, brush1, tmp_faces, tmp_faces.length, &tmp_contents); infa = &tmp_faces[tmp_numfaces]; extrafaces = brush_get(model, brush2, &tmp_faces[tmp_numfaces], tmp_faces.length-tmp_numfaces, &tmp_contents); //merge the two sets of planes together. for(infa = &tmp_faces[tmp_numfaces]; extrafaces > 0; infa++, extrafaces--) { for (fa = tmp_faces, i = 0; i < tmp_numfaces; i++, fa++) { //fixme: needs some tolerance / epsilon if (fa->planenormal == -infa->planenormal && fa->planedist == -infa->planedist) { //inverted. this is the plane we're merging over, so strip it out memcpy(fa, &fa[1], sizeof(brushface_t) * ((tmp_numfaces-i)-1)); tmp_numfaces--; i--; fa--; found = TRUE; break; } else if (fa->planenormal * infa->planenormal > 0.999 && fa->planedist == infa->planedist) { //print("coplanar\n"); //coplanar surfaces are considered safe to ignore. we use the selected brush's texturing info break; } } if (i == tmp_numfaces) { //okay, this plane is important, merge it into the selected brush. //print("merge plane\n"); memcpy(fa, infa, sizeof(brushface_t)); tmp_numfaces++; } } if (!found) { print("Brushes do not share a plane\n"); #if 0 //try to find a surface to move to match to the given plane float val; float bestval = -0.707; //must be within 45 degrees int bestface = -1; tmp_numfaces = brush_get(model, brush1, tmp_faces, tmp_faces.length, &tmp_contents); brush_get(model, brush2, &tmp_faces[tmp_numfaces], tmp_faces.length-tmp_numfaces, &tmp_contents); infa = &tmp_faces[tmp_numfaces + face2-1i]; for (i = 0; i < tmp_numfaces; i++) { val = tmp_faces[i].planenormal * infa->planenormal; if (val < bestval) { bestval = val; bestface = i; } } if (bestface != -1) { //FIXME: determine volume and reject it if we shrink? tmp_faces[bestface].planenormal = -infa->planenormal; tmp_faces[bestface].planedist = -infa->planedist; // if (isconcave(tmp_faces, tmp_numfaces)) // { // print("Resulting brush would be concave\n"); // return 0; // } brush_history_delete(model, brush1); return brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents); } else #endif return 0; } else { vector *points = memalloc(sizeof(vector)*64*64); int numpoints = 0, f; for(f = 0; (i = brush_getfacepoints(model, brush1, ++f, points+numpoints, 64*64-numpoints)); ) numpoints += i; for(f = 0; (i = brush_getfacepoints(model, brush2, ++f, points+numpoints, 64*64-numpoints)); ) numpoints += i; if (isconcave(tmp_faces, tmp_numfaces, points, numpoints)) { print("Resulting brush would be concave\n"); memfree(points); return 0; } memfree(points); //FIXME: verify that no planes got dropped, as this indicates that the region wasn't convex, and we probably just destroyed the entire thing. brush_history_delete(model, brush1); brush_history_delete(model, brush2); return brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents, TRUE); } }; void(int mod, int id) DrawEngineBrushWireframe = { const vector col = '1 0 0'; for(int facenum = 0;;) { int points = brush_getfacepoints(mod, id, ++facenum, &facepoints[0], facepoints.length); if (!points) break; //end of face list, I guess R_BeginPolygon("chop"); R_PolygonVertex(facepoints[0], '0 0', col, 1); for (int point = 1; point < points; point++) { R_PolygonVertex(facepoints[point], '0 0', col, 1); R_EndPolygon(); R_PolygonVertex(facepoints[point], '0 0', col, 1); } R_PolygonVertex(facepoints[0], '0 0', col, 1); R_EndPolygon(); } }; void(brushface_t *faces, int numfaces, string shader, vector col, float alpha) DrawQCBrushWireframe = { int f; int point, points; for(f = 0; f < numfaces;) { points = brush_calcfacepoints(++f, faces, numfaces, facepoints, facepoints.length); if (!points) continue; //should probably warn somehow about this R_BeginPolygon(shader); R_PolygonVertex(facepoints[0], '0 0', col, alpha); for (point = 0; point < points-1; ) { point++; R_PolygonVertex(facepoints[point], '0 0', col, alpha); R_EndPolygon(); R_PolygonVertex(facepoints[point], '0 0', col, alpha); } R_PolygonVertex(facepoints[0], '0 0', col, alpha); R_EndPolygon(); } }; void(brushface_t *faces, int numfaces, string shader, vector col, float alpha) DrawQCBrushSolid = { int f; int point, points; for(f = 0; f < numfaces;) { points = brush_calcfacepoints(++f, faces, numfaces, facepoints, facepoints.length); if (!points) continue; //should probably warn somehow about this R_BeginPolygon(shader); for (point = 0; point < points; point++) R_PolygonVertex(facepoints[point], '0 0', col, alpha); R_EndPolygon(); } }; void(brushface_t *faces, int numfaces, vector col, float alpha) DrawQCBrushTextured = { int f; int point, points; for(f = 0; f < numfaces; f++) { points = brush_calcfacepoints(1+f, faces, numfaces, facepoints, facepoints.length); if (points) { //this is unfortunate. the built in shaders expect to use lightmaps. we don't have those. //because lightmaps are special things, we end up in a real mess. so lets just make sure there's a shader now, because we can. //FIXME: we don't manage to pick up the size of the original wad image shaderforname(faces[f].shadername, sprintf("{" "{\n" "map \"%s\"\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" "}", faces[f].shadername)); vector sz = drawgetimagesize(faces[f].shadername); R_BeginPolygon(faces[f].shadername); for (point = 0; point < points; point++) R_PolygonVertex(facepoints[point], [(facepoints[point] * faces[f].sdir + faces[f].sbias)/sz_x, (facepoints[point] * faces[f].tdir + faces[f].tbias)/sz_y], col, alpha); R_EndPolygon(); } } }; void(vector *p, int points, string shader, vector col, float alpha) DrawAxisExtensions = { R_BeginPolygon(shader); for (int point = 0; point < points; point++) { R_PolygonVertex(p[point] + [ 64, 0, 0], '0 0', col, alpha); R_PolygonVertex(p[point] + [-64, 0, 0], '0 0', col, alpha); R_EndPolygon(); R_PolygonVertex(p[point] + [0, 64, 0], '0 0', col, alpha); R_PolygonVertex(p[point] + [0, -64, 0], '0 0', col, alpha); R_EndPolygon(); R_PolygonVertex(p[point] + [0, 0, 64], '0 0', col, alpha); R_PolygonVertex(p[point] + [0, 0, -64], '0 0', col, alpha); R_EndPolygon(); } }; static float(vertsoup_t *soup, int *idx, __inout vector norm, __inout float dist) planenormal = { vector v1 = soup->p[idx[0]]; vector v2 = soup->p[idx[1]]; vector v3 = soup->p[idx[2]]; vector d1 = v3 - v1; vector d2 = v2 - v1; vector d3 = v3 - v2; norm = normalize(crossproduct(d1, d2)); dist = norm * v1; if (!d1 || !d2 || !d3 || !norm) return FALSE; return TRUE; }; void(vertsoup_t *soup, int drawit) Rebrushify = { brushface_t faces[64]; int numfaces, f, point; string shader = 0; vector n; float d; tmp_numfaces = brush_get(soup->model, soup->brush, tmp_faces, tmp_faces.length, &tmp_contents); //if any triangle's neighbour opposes it, reform the edge across the quad to try to keep it convex. for(point = 0; point+2 < soup->numidx; point+=3) { int p1 = soup->i[point+0]; int p2 = soup->i[point+1]; int p3 = soup->i[point+2]; if (!planenormal(soup, &soup->i[point], n, d)) continue; //degenerate d += EPSILON; for(f = point+3; f+2 < soup->numidx; f+=3) { int o1 = soup->i[f+0]; int o2 = soup->i[f+1]; int o3 = soup->i[f+2]; int o; p1p2edge: if (o2 == p1 && o1 == p2) o = o3; else if (o3 == p1 && o2 == p2) o = o1; else if (o1 == p1 && o3 == p2) o = o2; else goto p2p3edge; if (soup->p[o] * n > d) { soup->i[f+0] = p3; soup->i[f+1] = o; soup->i[f+2] = p2; p2 = o; } continue; p2p3edge: if (o2 == p2 && o1 == p3) o = o3; else if (o3 == p2 && o2 == p3) o = o1; else if (o1 == p2 && o3 == p3) o = o2; else goto p3p1edge; if (soup->p[o] * n > d) { soup->i[f+0] = p1; soup->i[f+1] = o; soup->i[f+2] = p3; p3 = o; } continue; p3p1edge: if (o2 == p3 && o1 == p1) o = o3; else if (o3 == p3 && o2 == p1) o = o1; else if (o1 == p3 && o3 == p1) o = o2; else continue; if (soup->p[o] * n > d) { soup->i[f+0] = p2; soup->i[f+1] = o; soup->i[f+2] = p1; p1 = o; } continue; } soup->i[point+0] = p1; soup->i[point+1] = p2; soup->i[point+2] = p3; } //generate the plane info numfaces = 0; for(point = 0; point+2 < soup->numidx; point+=3) { if (!planenormal(soup, &soup->i[point], n, d)) continue; //a degenerate triangle is one that probably got merged or something for (f = 0; f < numfaces; f++) { if (faces[f].planenormal == n && faces[f].planedist == d) break; } if (f < numfaces) continue; //duplicate plane for (f = 0; f < tmp_numfaces; f++) { if (tmp_faces[f].planenormal * n > 0.999) //stupid inprecisions { if (numfaces == 64) return; //note that we only care about the normal, not the dist. this means you can move the entire face forward+back without loosing textures. faces[numfaces].shadername = shader = tmp_faces[f].shadername; faces[numfaces].planenormal = n; faces[numfaces].planedist = d; faces[numfaces].sdir = tmp_faces[f].sdir; faces[numfaces].sbias = tmp_faces[f].sbias; faces[numfaces].tdir = tmp_faces[f].tdir; faces[numfaces].tbias = tmp_faces[f].tbias; numfaces++; break; } } if (f < tmp_numfaces) continue; //matched a plane in the original brush if (numfaces == 64) return; //FIXME: find aproximate faces to give corners or something //okay, it appears to be new. that's a pain. faces[numfaces].shadername = 0; faces[numfaces].planenormal = n; faces[numfaces].planedist = d; reset_texturecoords(&faces[numfaces]); numfaces++; } //any surface without a texture/shader yet should inherit one from some other surface if (!shader) shader = autocvar_ca_newbrushtexture; for(f = 0; f < numfaces; f++) { if (!faces[f].shadername) faces[f].shadername = shader; } int internals = 0; //If we have a point outside a plane, then we know we have a concave volume. //chop the points for(f = 0; f < numfaces; f++) { n = faces[f].planenormal; d = faces[f].planedist; for (point = 0; point < soup->numidx; point++) //would ideally use points, but I want to cover dead ones { if (soup->p[soup->i[point]] * n > d + EPSILON) { internals++; break; } } } // cprint(sprintf("%i internal splits, %i faces\n", internals, numfaces)); if (numfaces <= 3) return; //can't possibly be valid. if (drawit) { //draw it wireframe WITH depth testing //DrawQCBrushWireframe(faces, numfaces, "terrainedit", '1 0 0', 1); //draw textured preview (yay for block lighting) DrawQCBrushTextured(faces, numfaces, '0.7 0.7 0.7', 1); //draw it wireframe without depth testing DrawQCBrushWireframe(faces, numfaces, "chop", internals?'1 0 0':'0 0 1', 1); } else { brush_history_edit(soup->model, soup->brush, faces, numfaces, 1i); soup->numidx = 0; soup->numverts = 0; } }; //take a brush apart and generate trisoup from it. //note that the trisoup does not have textures. they will be reconciled when reforming the brush. void(vertsoup_t *vertedit, int model, int brush) Debrushify = { int *ni; int i, j, k; int points; vector p; vector *np; static int fi[64]; vertedit->model = model; vertedit->brush = brush; vertedit->numidx = 0; vertedit->numverts = 0; for (i = 0; ; ) { points = brush_getfacepoints(vertedit->model, vertedit->brush, ++i, facepoints, facepoints.length); if (!points) break; //allocate a few new indexes ni = memalloc(sizeof(*ni) * (vertedit->numidx+3*(points-2))); memcpy(ni, vertedit->i, sizeof(*ni) * vertedit->numidx); memfree(vertedit->i); vertedit->i = ni; ni += vertedit->numidx; vertedit->numidx += 3*(points-2); for (j = 0; j < points; j++) { p = facepoints[j]; p_x = floor(p_x + 0.5); //gah, bloomin inprecision. p_y = floor(p_y + 0.5); p_z = floor(p_z + 0.5); for (k = 0; k < vertedit->numverts; k++) { //try to be nice and re-use verts. if (vertedit->p[k] == p) break; } if (k == vertedit->numverts) { //if it wasn't found, we need to allocate a new one np = memalloc(sizeof(*np) * (vertedit->numverts+1)); memcpy(np, vertedit->p, sizeof(*np) * vertedit->numverts); memfree(vertedit->p); vertedit->p = np; np += vertedit->numverts; vertedit->numverts += 1; *np = p; } fi[j] = k; } for (j = 2; j < points; j++) { *ni++ = fi[0]; *ni++ = fi[j-1]; *ni++ = fi[j]; } } }; //determines only the various points of the brush, without duplicates. doesn't care about indexes. void(brushface_t *faces, int numfaces) DebrushifyLite = { int points, k; vector p; vector *np; int fi[64]; vertedit.numidx = 0; vertedit.numverts = 0; for (int i = 0; ; ) { points = brush_calcfacepoints(++i, faces, numfaces, facepoints, facepoints.length); if (!points) break; for (int j = 0; j < points; j++) { p = facepoints[j]; p_x = floor(p_x + 0.5); //gah, bloomin inprecision. p_y = floor(p_y + 0.5); p_z = floor(p_z + 0.5); for (k = 0; k < vertedit.numverts; k++) { //try to be nice and re-use verts. if (vertedit.p[k] == p) break; } if (k == vertedit.numverts) { //if it wasn't found, we need to allocate a new one np = memalloc(sizeof(*np) * (vertedit.numverts+1)); memcpy(np, vertedit.p, sizeof(*np) * vertedit.numverts); memfree(vertedit.p); vertedit.p = np; np += vertedit.numverts; vertedit.numverts += 1; *np = p; } fi[j] = k; } } }; /* void(vector org, vector ang) editor_brushes_simpleclone = { vector midpoint; if (!selectedbrush) return; tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); if (ang != '0 0 0') { brush_getfacepoints(selectedbrushmodel, selectedbrush, 0, &midpoint, 1); brushface_translate(tmp_faces, tmp_numfaces, -midpoint); makevectors(ang); brushface_rotate(tmp_faces, tmp_numfaces); brushface_translate(tmp_faces, tmp_numfaces, midpoint + org); } else brushface_translate(tmp_faces, tmp_numfaces, org); brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents, TRUE); }; */ void() brushedit_subtract = { int discard; vector selnormals[tmp_faces.length]; float seldists[tmp_faces.length]; for (int sb = 0; sb < selectedbrushcount; sb++) { int mod = selectedbrushes[sb].model; int brush = selectedbrushes[sb].id; int planecount = brush_get(mod, brush, tmp_faces, tmp_faces.length, &tmp_contents); for (int i = 0; i < planecount; i++) { selnormals[i] = tmp_faces[i].planenormal; seldists[i] = tmp_faces[i].planedist; } int numbrushes = brush_findinvolume(mod, selnormals, seldists, planecount, brushlist, __NULL__, brushlist.length); while (numbrushes --> 0) { int br = brushlist[numbrushes]; if (brush_isselected(mod, br)) continue; int counto = brush_get(mod, br, tmp_faces, tmp_faces.length, &tmp_contents); int counts = counto + brush_get(mod, brush, tmp_faces+counto, tmp_faces.length-counto, &discard); brush_history_delete(mod, br); while(counts --> counto) { //only consider the resulting brush if the new face actually contributed anything. //this reduces dupes. if (brush_calcfacepoints(1+counts, tmp_faces, counts+1, facepoints, facepoints.length)) { //determine the brush defined by this plane tmp_faces[counts].planenormal *= -1; tmp_faces[counts].planedist *= -1; brush_history_create(mod, tmp_faces, counts+1, tmp_contents, FALSE); } } } } }; static void() brushedit_resettextures = { for (int sb = 0; sb < selectedbrushcount; sb++) { int model = selectedbrushes[sb].model; int brush = selectedbrushes[sb].id; int face = selectedbrushes[sb].face; int planecount = brush_get(model, brush, tmp_faces, tmp_faces.length, &tmp_contents); for (int i = 0; i < planecount; i++) if (!face || face == planecount+1) reset_texturecoords(&tmp_faces[i]); brush_history_edit(model, brush, tmp_faces, planecount, tmp_contents); } }; //when we're drawing sideviews, we normally need them wireframe in order to actually see anything static void(vector sizes) drawwireframeview = { //figure out the planes that we care about. vector org = getviewprop(VF_ORIGIN); vector vaxis[6]; vaxis[0] = v_right; vaxis[1] = v_up; vaxis[2] = v_forward; for (int i = 0; i < 3; i++) dists[i] = (org*vaxis[i]) + sizes[i]; for (int i = 0; i < 3; i++) { vaxis[i+3] = -vaxis[i]; dists[i+3] = (org*vaxis[i+3]) + sizes[i]; } int numbrushes = brush_findinvolume(selectedbrushmodel, vaxis, dists, dists.length, &brushlist[0], __NULL__, brushlist.length); //this can be a bit slow with lots of brushes. would be nice to batch them a bit better. while(numbrushes --> 0) DrawEngineBrushWireframe(selectedbrushmodel, brushlist[numbrushes]); setviewprop(VF_DRAWWORLD, FALSE); }; static void() editor_brushes_drawselected = { float intensity = (sin(gettime(5)*4)+1)*0.05; int facenum, point, points; vector col; //draw all selected brush faces. for (int sb = 0; sb < selectedbrushcount; sb++) { int model = selectedbrushes[sb].model; int brush = selectedbrushes[sb].id; int face = selectedbrushes[sb].face; for(facenum = 0;;) { points = brush_getfacepoints(model, brush, ++facenum, facepoints, facepoints.length); if (!points) break; //end of face list, I guess if (facenum == face) col = [0,intensity,0]; else col = [intensity,0,0]; R_BeginPolygon("terrainedit"); for (point = 0; point < points; point++) R_PolygonVertex(facepoints[point], '0 0', col, 1); R_EndPolygon(); } } for (int sb = 0; sb < selectedbrushcount; sb++) { int model = selectedbrushes[sb].model; int brush = selectedbrushes[sb].id; //now draw wireframe DrawEngineBrushWireframe(model, brush); } // tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, tmp_faces, tmp_faces.length, &tmp_contents); // DebrushifyLite(tmp_faces, tmp_numfaces); DrawAxisExtensions(vertedit.p, vertedit.numverts, "terrainedit", '0 0 0.3', 1); }; void(vector mousepos) editor_brushes_addentities = { vector col = '0 0 0'; int points, point; int facenum; float intensity = (sin(gettime(5)*4)+1)*0.05; vector displace, tmp; vector mid; autocvar_ca_brush_view = fabs(autocvar_ca_brush_view) % 4f; if (autocvar_ca_brush_view) { vector vmn = (vector)getproperty(VF_MIN); vector vsz = (vector)getproperty(VF_SIZE); vector ang = vectoangles([autocvar_ca_brush_view==1,autocvar_ca_brush_view==2,autocvar_ca_brush_view==3]); vector fov; if (vsz_x > vsz_y) fov = [autocvar_ca_brush_viewsize, autocvar_ca_brush_viewsize * (vsz_y/vsz_x), 8192]; else fov = [autocvar_ca_brush_viewsize * (vsz_x/vsz_y), autocvar_ca_brush_viewsize, 8192]; makevectors(ang); drawfill(vmn, vsz, '0 0 0', 1, 0); setviewprop(VF_VIEWENTITY, 0); setviewprop(VF_PERSPECTIVE, FALSE); setviewprop(VF_ANGLES, ang); setviewprop(VF_MAXDIST, fov_z*0.5); setviewprop(VF_MINDIST, -fov_z*0.5); //I'm not entirely sure where the near clip plane should be. oh well. if (vsz_x > vsz_y) setviewprop(VF_FOV, fov); else setviewprop(VF_FOV, fov); if (curmousepos_x >= vmn_x && curmousepos_x <= vmn_x+vsz_x && curmousepos_y >= vmn_y && curmousepos_y <= vmn_y+vsz_y) { mousefar = unproject(curmousepos + '0 0 1'); mousenear = unproject(curmousepos); } drawwireframeview(fov*0.5); } else makevectors(input_angles); if ((mousetool == BT_VERTEXEDIT || mousetool == BT_CREATEDRAG || brushtool == BT_PUSHFACE || brushtool == BT_CLONEDISPLACE || brushtool == BT_MOVE || brushtool == BT_MOVETEXTURE) && bt_points) { vector fwd = v_forward; vector rgt = v_right; vector up = v_up; fwd = axialize(fwd); rgt -= (rgt * fwd) * rgt; up -= (up * fwd) * up; rgt = axialize(rgt); up = axialize(up); tmp = normalize(mousefar-mousenear) + mousenear; tmp = planelinepoint(mousenear, tmp, v_forward, bt_point[0] * v_forward); //find where the cursor impacts the screen grid (moved along the view vector to match where the drag was last frame) displace = tmp - bt_point[0]; if (mousetool != BT_VERTEXEDIT && mousetool != BT_CREATEDRAG) displace = brush_snappoint(displace); if (altdown) bt_displace_x = displace * fwd; else { bt_displace_y = displace * rgt; bt_displace_z = displace * up; } displace = bt_displace_x * fwd + bt_displace_y * rgt + bt_displace_z * up; if (mousetool == BT_VERTEXEDIT) displace = brush_snappoint(displace+bt_point[0]); } else if (brushtool == BT_ROTATE) { te_lightning2(world, bt_point[0], bt_point[0]+-bt_displace); col = vectoangles(bt_point[0]+bt_displace - tmp) - vectoangles(bt_point[0] - tmp); col = channelizeangle([col_x*-1,col_y,col_z]); } if (vertedit.numidx && mousetool != BT_VERTEXEDIT) { vertedit.numidx = 0; vertedit.numverts = 0; } //make sure this shader was generated already. shaderforname("terrainedit", "{" "{\n" "map terrainedit\n" "blendfunc add\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" "}"); //make sure this shader was generated already. shaderforname("chop", "{" "cull disable\n" "{\n" "map terrainedit\n" "blendfunc add\n" "rgbgen vertex\n" "alphagen vertex\n" "sort nearest\n" "nodepthtest\n" "}\n" "}"); if (mousetool == BT_VERTEXEDIT && vertedit.numidx) { #if 0 //draw it solid WITH depth testing R_BeginPolygon("terrainedit"); for(point = 0; point+2 < vertedit.numidx; point+=3) { col = '0 0 0.1'; R_PolygonVertex(vertedit.p[vertedit.i[point+0]], '0 0', col, 1); R_PolygonVertex(vertedit.p[vertedit.i[point+1]], '0 0', col, 1); R_PolygonVertex(vertedit.p[vertedit.i[point+2]], '0 0', col, 1); R_EndPolygon(); } #endif #if 1 //draw the wires (no depth) R_BeginPolygon("chop"); for(point = 0; point+2 < vertedit.numidx; point+=3) { col = '0 0.1 0'; R_PolygonVertex(vertedit.p[vertedit.i[point+0]], '0 0', col, 1); R_PolygonVertex(vertedit.p[vertedit.i[point+1]], '0 0', col, 1); R_EndPolygon(); R_PolygonVertex(vertedit.p[vertedit.i[point+1]], '0 0', col, 1); R_PolygonVertex(vertedit.p[vertedit.i[point+2]], '0 0', col, 1); R_EndPolygon(); R_PolygonVertex(vertedit.p[vertedit.i[point+2]], '0 0', col, 1); R_PolygonVertex(vertedit.p[vertedit.i[point+0]], '0 0', col, 1); R_EndPolygon(); } #endif DrawAxisExtensions(vertedit.p, vertedit.numverts, "terrainedit", '1 0.3 0.3', 1); if (mousedown) { if (bt_points == 1 && vertedit.selectedvert1 != -1) { if (vertedit.selectedvert2 != -1) vertedit.p[vertedit.selectedvert2] = displace + bt_point[2]; vertedit.p[vertedit.selectedvert1] = displace + bt_point[1]; } } else bt_points = 0; Rebrushify(&vertedit, TRUE); } else if (brushtool == BT_CREATE) { editor_brushes_drawselected(); if (bt_points > 2) { tmp_numfaces = BrushFromPoints(tmp_faces, tmp_faces.length, bt_point, bt_points, autocvar_ca_newbrushheight); DrawQCBrushWireframe(tmp_faces, tmp_numfaces, "chop", '1 0 0', 1); } } else if (mousetool == BT_CREATEDRAG) { if (bt_points) { displace -= axialize(v_forward)*autocvar_ca_grid; mid = bt_point[0]; displace = brush_snappoint(mid + displace); mid = brush_snappoint(mid); displace = displace - mid; tmp_faces[0].planenormal = '1 0 0'; tmp_faces[1].planenormal = '-1 0 0'; tmp_faces[2].planenormal = '0 1 0'; tmp_faces[3].planenormal = '0 -1 0'; tmp_faces[4].planenormal = '0 0 1'; tmp_faces[5].planenormal = '0 0 -1'; tmp_faces[0].planedist = mid[0]; tmp_faces[1].planedist = -mid[0]; tmp_faces[2].planedist = mid[1]; tmp_faces[3].planedist = -mid[1]; tmp_faces[4].planedist = mid[2]; tmp_faces[5].planedist = -mid[2]; tmp_numfaces = 6; tmp_contents = 1; tmp_faces[0 + (displace_x<0.0)].planedist += fabs(displace_x); tmp_faces[2 + (displace_y<0.0)].planedist += fabs(displace_y); tmp_faces[4 + (displace_z<0.0)].planedist += fabs(displace_z); for (facenum = 0; facenum < tmp_numfaces; facenum++) { tmp_faces[facenum].shadername = autocvar_ca_newbrushtexture; reset_texturecoords(&tmp_faces[facenum]); } DrawQCBrushWireframe(tmp_faces, tmp_numfaces, "chop", '1 0 0', 1); if (!mousedown) { brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, tmp_contents, TRUE); bt_points = 0; mousetool = BT_NONE; } } else mousedown = FALSE; } else if (brushtool == BT_SLICE) { editor_brushes_drawselected(); if (bt_points) { for (int sb = 0; sb < selectedbrushcount; sb++) { int model = selectedbrushes[sb].model; int brush = selectedbrushes[sb].id; tmp_numfaces = brush_get(model, brush, tmp_faces, tmp_faces.length, &tmp_contents); tmp_faces[tmp_numfaces].planenormal = normalize(crossproduct(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0])); tmp_faces[tmp_numfaces].planedist = bt_point[0] * tmp_faces[tmp_numfaces].planenormal; //draw it wireframe DrawQCBrushWireframe(tmp_faces, tmp_numfaces+1, "chop", '1 0 0', 1); //flip the split tmp_faces[tmp_numfaces].planenormal *= -1; tmp_faces[tmp_numfaces].planedist *= -1; //draw the other side wireframe DrawQCBrushWireframe(tmp_faces, tmp_numfaces+1, "chop", '0 1 0', 1); } } } else if ((mousetool == BT_PUSHFACE || mousetool == BT_MOVETEXTURE || mousetool == BT_MOVE || mousetool == BT_CLONEDISPLACE || mousetool == BT_ROTATE) && bt_points) { int oldselectedbrushcount = selectedbrushcount; selbrush_t *oldselectedbrushes = memalloc(sizeof(selbrush_t)*selectedbrushcount); memcpy(oldselectedbrushes, selectedbrushes, selectedbrushcount*sizeof(selbrush_t)); for (int sb = 0; sb < oldselectedbrushcount; sb++) { int model = oldselectedbrushes[sb].model; int brush = oldselectedbrushes[sb].id; int face = oldselectedbrushes[sb].face; if (!brush) continue; tmp_numfaces = brush_get(model, brush, tmp_faces, tmp_faces.length, &tmp_contents); if (mousetool == BT_PUSHFACE) { if (!face) continue; //FIXME: should not happen. tmp_faces[face-1].planedist += tmp_faces[face-1].planenormal * displace; } else if (mousetool == BT_MOVETEXTURE) { if (!face) continue; //FIXME: should not happen. tmp_faces[face-1].sbias -= tmp_faces[face-1].sdir * displace; tmp_faces[face-1].tbias -= tmp_faces[face-1].tdir * displace; } else if (mousetool == BT_MOVE || mousetool == BT_CLONEDISPLACE) brushface_translate(tmp_faces, tmp_numfaces, displace); else if (mousetool == BT_ROTATE) { //find the brush's middle (based on its bbox) brush_getfacepoints(model, brush, 0, &tmp, 1); makevectors(col); //move it so its pivot is at the origin brushface_translate(tmp_faces, tmp_numfaces, -tmp); //rotate it brushface_rotate(tmp_faces, tmp_numfaces); //reposition it around its pivot again brushface_translate(tmp_faces, tmp_numfaces, tmp); } else continue; if (mousetool == BT_MOVETEXTURE) { points = brush_calcfacepoints(model, tmp_faces, tmp_numfaces, facepoints, facepoints.length); if (points) { //this is unfortunate. the built in shaders expect to use lightmaps. we don't have those. //because lightmaps are special things, we end up in a real mess. so lets just make sure there's a shader now, because we can. shaderforname(tmp_faces[face-1].shadername, sprintf("{" "{\n" "map \"%s.lmp\"\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" "}", tmp_faces[face-1].shadername)); col = '0.7 0.7 0.7'; //fullbright is typically TOO bright. overbrights? meh! vector sz = drawgetimagesize(tmp_faces[face-1].shadername); R_BeginPolygon(tmp_faces[face-1].shadername); for (point = 0; point < points; point++) R_PolygonVertex(facepoints[point] + tmp_faces[face-1].planenormal*0.1, [(facepoints[point] * tmp_faces[face-1].sdir + tmp_faces[face-1].sbias)/sz_x, (facepoints[point] * tmp_faces[face-1].tdir + tmp_faces[face-1].tbias)/sz_y], col, 1); R_EndPolygon(); } } else { //draw it wireframe for(facenum = 0; facenum < tmp_numfaces;) { points = brush_calcfacepoints(++facenum, tmp_faces, tmp_numfaces, facepoints, facepoints.length); if (!points) continue; //should probably warn somehow about this //should we use two colour channels? one depth one not? if (facenum == face) col = '1 0 0'; else col = '0 0.5 0'; R_BeginPolygon("chop"); R_PolygonVertex(facepoints[0], '0 0', col, 1); for (point = 0; point < points-1; ) { point++; R_PolygonVertex(facepoints[point], '0 0', col, 1); R_EndPolygon(); R_PolygonVertex(facepoints[point], '0 0', col, 1); } R_PolygonVertex(facepoints[0], '0 0', col, 1); R_EndPolygon(); } } //draw it wireframe WITH depth testing DrawQCBrushWireframe(tmp_faces, tmp_numfaces, "terrainedit", '0 0 1', 1); DebrushifyLite(tmp_faces, tmp_numfaces); DrawAxisExtensions(vertedit.p, vertedit.numverts, "terrainedit", '0 0 0.3', 1); if (!mousedown) { if (mousetool == BT_CLONEDISPLACE) //doesn't affect the original brush. { if (displace*displace > 1) brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents, TRUE); } else brush_history_edit(model, brush, tmp_faces, tmp_numfaces, tmp_contents); bt_points = 0; mousetool = BT_NONE; } } memfree(oldselectedbrushes); } else if (selectedbrushcount) { /*if (brushtool == BT_PUSHFACE) { //selected face (not brush) follows cursor when we're not actively dragging a face for(facenum = 0;;) { points = brush_getfacepoints(selectedbrushmodel, selectedbrush, ++facenum, facepoints, facepoints.length); if (!points) break; //end of face list, I guess mid = 0; for (point = 0; point < points; point++) mid += facepoints[point]; //FIXME: find the centroid for greater accuracy mid = project(mid * (1.0/points)); dist = (mousepos_x - mid_x) * (mousepos_x - mid_x) + (mousepos_y - mid_y) * (mousepos_y - mid_y); if (facenum == 1 || dist < bestdist) { bestdist = dist; selectedbrushface = facenum; } } }*/ editor_brushes_drawselected(); } // editor_drawbbox(selectedbrush); vector t = mousefar; vector o = mousenear; if (vlen(o - t) > 8192 && !autocvar_ca_brush_view) t = o + normalize(t)*8192; traceline(o, t, TRUE, world); #if 0 //find all the brushes within 32qu of the mouse cursor's impact point //you can tweak this to find the nearest brush vertex efficiently (or snap it to a grid or so). //you can then slice through a brush by generating a plane between three points found this way and inserting it into the brush, clipping away the extra part. //(remember, the engine will automatically discard any degenerate planes) col = '0 0 1'; //generate the volume for (facenum = 0; facenum < 6; facenum++) dists[facenum] = (trace_endpos * axis[facenum]) + 32; int brushnum, numbrushes = brush_findinvolume(selectedbrushmodel, axis, dists, dists.length, &brushlist[0], __NULL__, brushlist.length); for (brushnum = 0; brushnum < numbrushes; brushnum++) { for (facenum = 0; ; ) { points = brush_getfacepoints(selectedbrushmodel, brushlist[brushnum], ++facenum, &facepoints[0], facepoints.length); if (!points) break; //end of face list, I guess R_BeginPolygon("terrainedit"); for (point = 0; point < points; point++) R_PolygonVertex(facepoints[point], '0 0', col, 1); R_EndPolygon(); } } #endif //draw a line/triangle to show the selection. int showpoints = bt_points; if (brushtool == BT_SLICE && bt_points) { // bt_point[showpoints++] = brush_snappoint(trace_endpos); showpoints = 3; } if (brushtool == BT_CREATE) bt_point[showpoints++] = brush_snappoint(trace_endpos); //FIXME: if slicing, draw the intersection if (showpoints > 1) { col = '0 0 1'; R_BeginPolygon("chop"); for (point = 0; point < showpoints; point++) R_PolygonVertex(bt_point[point], '0 0', col, 1); R_EndPolygon(); } //FIXME: if creating, draw a wireframe brush. }; void(vector mousepos) editor_brushes_overlay = { int point; vector mid; float dist, bestdist; if (vertedit.numidx) { if (!mousedown) { for(vertedit.selectedvert1 = -1, vertedit.selectedvert2 = -1, point = 0, bestdist = 16*16; point < vertedit.numverts; point++) { mid = project(vertedit.p[point]); dist = (mousepos_x - mid_x) * (mousepos_x - mid_x) + (mousepos_y - mid_y) * (mousepos_y - mid_y); if (dist < bestdist && mid_z > 0) { bestdist = dist; vertedit.selectedvert1 = point; } } for(point = 0; point < vertedit.numidx; point+=3) { mid = project(0.5*(vertedit.p[vertedit.i[point+0]] + vertedit.p[vertedit.i[point+1]])); dist = (mousepos_x - mid_x) * (mousepos_x - mid_x) + (mousepos_y - mid_y) * (mousepos_y - mid_y); if (dist < bestdist && mid_z > 0) { bestdist = dist; vertedit.selectedvert1 = vertedit.i[point+0]; vertedit.selectedvert2 = vertedit.i[point+1]; } mid = project(0.5*(vertedit.p[vertedit.i[point+1]] + vertedit.p[vertedit.i[point+2]])); dist = (mousepos_x - mid_x) * (mousepos_x - mid_x) + (mousepos_y - mid_y) * (mousepos_y - mid_y); if (dist < bestdist && mid_z > 0) { bestdist = dist; vertedit.selectedvert1 = vertedit.i[point+1]; vertedit.selectedvert2 = vertedit.i[point+2]; } mid = project(0.5*(vertedit.p[vertedit.i[point+2]] + vertedit.p[vertedit.i[point+0]])); dist = (mousepos_x - mid_x) * (mousepos_x - mid_x) + (mousepos_y - mid_y) * (mousepos_y - mid_y); if (dist < bestdist && mid_z > 0) { bestdist = dist; vertedit.selectedvert1 = vertedit.i[point+2]; vertedit.selectedvert2 = vertedit.i[point+0]; } } } if (vertedit.selectedvert1 != -1) { mid = project(vertedit.p[vertedit.selectedvert1]); if (mid_z >= 0) drawfill([mid_x,mid_y] - '2 2', [4,4], [0.2,0.2,1], 1, 0); } if (vertedit.selectedvert2 != -1) { mid = project(vertedit.p[vertedit.selectedvert2]); if (mid_z >= 0) drawfill([mid_x,mid_y] - '2 2', [4,4], [0.2,0.2,1], 1, 0); } drawrawstring('0 32 0', "Vertex Editor", '8 8 0', '1 1 1', 1); return; } switch(brushtool) { case BT_CLONEDISPLACE: drawrawstring('0 32 0', "Clone", '8 8 0', '1 1 1', 1); break; case BT_CREATEDRAG: drawrawstring('0 32 0', "Drag+Create", '8 8 0', '1 1 1', 1); break; case BT_CREATE: drawrawstring('0 32 0', "Paint+Create", '8 8 0', '1 1 1', 1); break; case BT_SLICE: drawrawstring('0 32 0', "Slice Tool", '8 8 0', '1 1 1', 1); if (!selectedbrushcount) brushtool = BT_NONE; break; case BT_PUSHFACE: drawrawstring('0 32 0', "Push Face", '8 8 0', '1 1 1', 1); break; case BT_MOVE: drawrawstring('0 32 0', "Move Brush", '8 8 0', '1 1 1', 1); break; case BT_ROTATE: drawrawstring('0 32 0', "Rotate Brush", '8 8 0', '1 1 1', 1); break; case BT_MOVETEXTURE: drawrawstring('0 32 0', "Move Texture", '8 8 0', '1 1 1', 1); break; case BT_VERTEXEDIT: drawrawstring('0 32 0', "Vertex Editor", '8 8 0', '1 1 1', 1); break; } }; #define brusheditormodes //#append brusheditormodes brusheditormode("move", BT_MOVE) #append brusheditormodes brusheditormode("clone", BT_CLONEDISPLACE) #append brusheditormodes brusheditormode("draw", BT_CREATEDRAG) #append brusheditormodes brusheditormode("rotate", BT_ROTATE) #append brusheditormodes brusheditormode("pushface", BT_PUSHFACE) #append brusheditormodes brusheditormode("scrolltex", BT_MOVETEXTURE) #append brusheditormodes brusheditormode("vertex", BT_VERTEXEDIT) void() editor_brushes_registercommands = { #define brusheditormode(n,v) registercommand("+brushedit_"n);registercommand("-brushedit_"n); brusheditormodes #undef brusheditormode registercommand("brushedit_undo"); registercommand("brushedit_redo"); registercommand("brushedit_delete"); registercommand("brushedit_create"); registercommand("brushedit_slice"); registercommand("brushedit_matchface"); registercommand("brushedit_resettexcoords"); registercommand("brushedit_settexture"); registercommand("brushedit_subtract"); registercommand("brushedit_binds"); registercommand("brushedit_binds_default"); registercommand("+brushedit_nogrid"); registercommand("-brushedit_nogrid"); }; float() editor_brushes_command = { switch(argv(0)) { #define brusheditormode(n,v) case "+brushedit_"n: brushtool = v; bt_points = 0; break; case "-brushedit_"n: if (brushtool == v) brushtool = BT_NONE; break; brusheditormodes #undef brusheditormode case "brushedit_undo": brush_undo(); break; case "brushedit_redo": brush_undo(); break; case "brushedit_create": brushtool = BT_CREATE; bt_points = 0; break; case "brushedit_slice": brushtool = BT_SLICE; bt_points = 0; break; case "+brushedit_nogrid": nogrid = TRUE; break; case "-brushedit_nogrid": nogrid = FALSE; break; /* case "brushedit_matchface": brushtool = BT_NONE; bt_points = 0; { vector t = mousefar; vector o = mousenear; int i; if (vlen(o - t) > 8192 && !autocvar_ca_brush_view) t = o + normalize(t)*8192; traceline(o, t, TRUE, world); i = mergebrushes(selectedbrushmodel, selectedbrush, trace_brush_id, selectedbrushface, trace_brush_faceid); if (i) { brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE); selectedbrush = i; selectedbrushface = 0; brush_selected(selectedbrushmodel, selectedbrush, selectedbrushface, TRUE); } } break; */ case "brushedit_resettexcoords": brushedit_resettextures(); break; case "brushedit_settexture": //IMPLEMENTME brushtool = BT_NONE; bt_points = 0; break; case "brushedit_subtract": brushedit_subtract(); break; case "brushedit_delete": while(selectedbrushcount) { brush_history_delete(selectedbrushes[0].model, selectedbrushes[0].id); } break; case "brushedit_binds_default": localcmd("echo Setting default brusheditor bindings\n"); localcmd("bind ctrl+z brushedit_undo\n"); localcmd("bind ctrl+y brushedit_redo\n"); localcmd("bind ctrl+i brushedit_create\n"); localcmd("bind ctrl+s brushedit_slice\n"); localcmd("bind ctrl+m brushedit_matchface\n"); localcmd("bind ctrl+delete brushedit_delete\n"); localcmd("bind ctrl+backspace brushedit_subtract\n"); localcmd("bind ctrl+c +brushedit_clone\n"); localcmd("bind ctrl+d +brushedit_draw\n"); localcmd("bind ctrl+r +brushedit_rotate\n"); localcmd("bind ctrl+f +brushedit_pushface\n"); localcmd("bind ctrl+t +brushedit_scrolltex\n"); localcmd("bind ctrl+v +brushedit_vertex\n"); break; case "brushedit_binds": localcmd("conmenu \"\"\n"); float foo = 0; localcmd(sprintf("menutext 0 %g \"Set Default Bindings\" \"brushedit_binds_default\"\n", foo+=8)); foo+=8; localcmd(sprintf("menubind 0 %g \"brushedit_create\" \"Creation Tool\"\n", foo+=8)); localcmd(sprintf("menubind 0 %g \"brushedit_slice\" \"Slice Tool\"\n", foo+=8)); localcmd(sprintf("menubind 0 %g \"brushedit_delete\" \"Delete\"\n", foo+=8)); localcmd(sprintf("menubind 0 %g \"brushedit_subtract\" \"Subtract\"\n", foo+=8)); localcmd(sprintf("menubind 0 %g \"brushedit_undo\" \"Undo\"\n", foo+=8)); localcmd(sprintf("menubind 0 %g \"brushedit_redo\" \"Redo\"\n", foo+=8)); localcmd(sprintf("menubind 0 %g \"brushedit_nogrid\" \"Disable Grid\"\n", foo+=8)); #define brusheditormode(n,v) localcmd(sprintf("menubind 0 %g \"+brushedit_"n"\" \""n"\"\n", foo+=8)); brusheditormodes #undef brusheditormode break; default: return FALSE; } //just in case. cvar_set("ca_show", "1"); cvar_set("ca_editormode", ftos(MODE_BRUSHEDIT)); return TRUE; }; void(entity e) editor_brush_set_entity { // faesf = e; }; float(float key, float unic, vector mousepos) editor_brushes_key = { brushface_t *fa; vector t = mousefar; vector o = mousenear; int i; if (vlen(o - t) > 8192 && !autocvar_ca_brush_view) t = o + normalize(t)*8192; if (key == K_ESCAPE) { bt_points = 0; vertedit.numidx = 0; vertedit.numverts = 0; if (brushtool) brushtool = BT_NONE; else brush_deselectall(); return TRUE; } if (key == K_MOUSE1) { if (vertedit.numidx) { if (vertedit.selectedvert1 != -1) { mousedown = TRUE; mousetool = BT_VERTEXEDIT; if (vertedit.selectedvert2 != -1) { bt_point[0] = 0.5 * (vertedit.p[vertedit.selectedvert1] + vertedit.p[vertedit.selectedvert2]); bt_point[1] = vertedit.p[vertedit.selectedvert1] - bt_point[0]; bt_point[2] = vertedit.p[vertedit.selectedvert2] - bt_point[0]; } else bt_point[0] = vertedit.p[vertedit.selectedvert1]; bt_point[1] = vertedit.p[vertedit.selectedvert1] - bt_point[0]; bt_points = 1; bt_displace = '0 0 0'; } return TRUE; } traceline(o, t, TRUE, world); float tracemodel = trace_ent.modelindex; /*if (brushtool == BT_CREATEDRAG || (brushtool == BT_PUSHFACE && selectedbrushface)) { trace_brush_faceid = selectedbrushface; trace_brush_id = selectedbrush; tracemodel = selectedbrushmodel; } else*/ if (brushtool != BT_PUSHFACE && brushtool != BT_MOVETEXTURE) trace_brush_faceid = 0; if (brushtool == BT_SLICE || brushtool == BT_CREATE) { if (brushtool == BT_SLICE && bt_points > 2) bt_points = 2; //FIXME: BT_CREATE is planar. so keep the points planar too. //FIXME: need a way to move points already placed //FIXME: create needs to ensure verts are clockwise and convex. if (bt_points < bt_point.length) { bt_point[bt_points] = brush_snappoint(trace_endpos); bt_points++; } if (brushtool == BT_SLICE && bt_points == 1) { //slice makes assumptions about the brush, so that you don't have to move to the other side of it first. //it ALWAYS draws 3 points if any are defined, so make sure 2+3 have valid locations once point 1 is defined. int majoraxis; traceline(o, t, TRUE, world); static vector bbox[2]; brush_getfacepoints(tracemodel, trace_brush_id, 0, bbox, bbox.length); t[0] = fabs(trace_plane_normal[0]); t[1] = fabs(trace_plane_normal[1]); t[2] = fabs(trace_plane_normal[2]); if (t[2] > t[0] && t[2] > t[1]) majoraxis = 2; else if (t[1] > t[0]) majoraxis = 1; else majoraxis = 0; bt_point[1] = bt_point[0]; bt_point[1][majoraxis] = bbox[trace_plane_normal[majoraxis]<0][majoraxis]; majoraxis = !majoraxis; bt_point[2] = bt_point[0]; bt_point[2][majoraxis] = bbox[trace_plane_normal[majoraxis]<0][majoraxis]; } } //FIXME: selecting a brush by face should select either the front or the back. ideally depending on which one is closest to its respective face center, I suppose. else { if (shiftdown) { //simple selection toggle of the clicked brush if (!tracemodel || !trace_brush_id) { } else if (brush_isselected(tracemodel, trace_brush_id)) brush_deselect(tracemodel, trace_brush_id); else brush_select(tracemodel, trace_brush_id); } else { //deselect everything but the targetted brush. if (!tracemodel || !trace_brush_id) brush_deselectall(); else if (!brush_isselected(tracemodel, trace_brush_id)) { brush_deselectall(); brush_select(tracemodel, trace_brush_id); } else { //its already selected, start doing whatever the current mouse action is meant to be mousedown = TRUE; mousetool = brushtool; vertedit.selectedvert1 = -1; vertedit.selectedvert2 = -1; bt_point[0] = brush_snappoint(trace_endpos); bt_points = 1; bt_displace = '0 0 0'; } if (brushtool == BT_VERTEXEDIT && !vertedit.numidx && trace_brush_id) { mousedown = FALSE; if (brush_isselected(tracemodel, trace_brush_id)) Debrushify(&vertedit, tracemodel, trace_brush_id); } } /* if (trace_brush_id != selectedbrush || selectedbrushface != trace_brush_faceid || selectedbrushmodel != tracemodel) { brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE); if (!shiftdown) { if (!brush_isselected(tracemodel, trace_brush_id)) brush_deselectall(); } else if (brush_deselect(tracemodel, trace_brush_id)) return TRUE; brush_select(tracemodel, trace_brush_id); selectedbrush = trace_brush_id; selectedbrushface = trace_brush_faceid; selectedbrushmodel = tracemodel; if (trace_brush_faceid) brush_selected(selectedbrushmodel, selectedbrush, selectedbrushface, TRUE); vertedit.numidx = 0; vertedit.numverts = 0; if (selectedbrushface && (brushtool == BT_PUSHFACE || brushtool == BT_MOVETEXTURE)) { mousedown = TRUE; mousetool = brushtool; vertedit.selectedvert1 = -1; vertedit.selectedvert2 = -1; bt_point[0] = brush_snappoint(trace_endpos); bt_points = 1; bt_displace = '0 0 0'; } } else if (selectedbrush == trace_brush_id && selectedbrushface == trace_brush_faceid && selectedbrushmodel == tracemodel) { mousedown = TRUE; mousetool = brushtool; vertedit.selectedvert1 = -1; vertedit.selectedvert2 = -1; bt_point[0] = brush_snappoint(trace_endpos); bt_points = 1; bt_displace = '0 0 0'; } */ } return TRUE; } if (key == K_ENTER) { if (vertedit.numidx) { Rebrushify(&vertedit, FALSE); mousetool = BT_NONE; bt_points = 0; vertedit.numidx = 0; vertedit.numverts = 0; mousedown = FALSE; return TRUE; } else if (brushtool == BT_CREATE) { if (bt_points >= 3) { tmp_numfaces = BrushFromPoints(tmp_faces, tmp_faces.length, bt_point, bt_points, autocvar_ca_newbrushheight); bt_points = 0; brush_deselectall(); brush_history_create(selectedbrushmodel, tmp_faces, tmp_numfaces, 1i, TRUE); } return TRUE; } else if (brushtool == BT_SLICE) { //save off the selected brushes so we can butcher the list, and select ONLY the sliced brushes on one side of the slice plane. int oldselectedbrushcount = selectedbrushcount; selbrush_t *oldselectedbrushes = memalloc(sizeof(selbrush_t)*selectedbrushcount); memcpy(oldselectedbrushes, selectedbrushes, selectedbrushcount*sizeof(selbrush_t)); brush_deselectall(); for (int sb = 0; sb < oldselectedbrushcount; sb++) { int model = oldselectedbrushes[sb].model; int brush = oldselectedbrushes[sb].id; //get the current faces tmp_numfaces = brush_get(model, brush, tmp_faces, tmp_faces.length-1, &tmp_contents); //generate a new face plane fa = &tmp_faces[tmp_numfaces]; fa->planenormal = normalize(crossproduct(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0])); fa->planedist = bt_point[0] * fa->planenormal; fa->shadername = tmp_faces[0].shadername; //find a neighbour? //make sure its not coplanar with some other surface. such slices are silly for (i = 0; i < tmp_numfaces; i++) { if (tmp_faces[i].planenormal == fa->planenormal && tmp_faces[i].planedist == fa->planedist) break; } if (i < tmp_numfaces) continue; tmp_numfaces++; reset_texturecoords(fa); //delete the old one and insert the new brush_history_edit(model, brush, tmp_faces, tmp_numfaces, tmp_contents); //and insert another new one too, because inserting a plane like this generates two fragments and I'm too lazy to work out which is the front and which is the back. fa->planenormal *= -1; fa->planedist *= -1; brush_history_create(model, tmp_faces, tmp_numfaces, tmp_contents, TRUE); } memfree(oldselectedbrushes); bt_points = 0; return TRUE; } else return FALSE; } return FALSE; };