fteqw/quakec/csaddon/src/editor_brushes.qc

908 lines
28 KiB
Plaintext

#if 0
typedef struct
{
string shadername; //aka: texture name
vector planenormal;
float planedist;
vector sdir;
float sbias;
vector tdir;
float tbias;
} brushface_t;
#define out //for annotation only
#define in //for annotation only
#define inout //for annotation only
int brush_get(float modelid, int brushid, out brushface_t *faces, int maxfaces, int *contents) = #0; //returns number of faces, or 0 on error.
int brush_create(float modelid, in brushface_t *faces, int numfaces, int contents) = #0; //returns newly created brushid.
void brush_delete(float modelid, int brushid) = #0; //destroys brush
//float brush_editplane(float modelid, int brushid, int faceid, in brushface_t *face) = #0; //allows changing shader/s/t info, but ignores plane changes. returns success
float brush_selected(float modelid, int brushid, int faceid, float selectedstate) = #0; //allows you to easily set visual properties of a brush. if brush/face is -1, applies to all. returns old value. selectedstate=-1 changes nothing (called for its return value).
int(float modelid, int brushid, int faceid, vector *points, int maxpoints) brush_getfacepoints = #0;
int(float modelid, vector *planes, float *dists, int numplanes, int *out_brushes, int *out_faces, int maxresults) brush_findinvolume = #0;
#endif
int trace_brush_id; //set by traceline
int trace_brush_faceid;
/*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.
*/
int selectedbrush;
int selectedbrushface;
float selectedbrushmodel;
vector facepoints[64];
//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[128];
int history_min; //oldest we can go
int history; //updated on each change
int history_max; //max value for redo
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)
h->id = brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents);
else
{
brush_delete(h->brushmodel, h->id);
h->id = 0;
}
} 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 doing. yay.
if (h->wasdelete)
{
brush_delete(h->brushmodel, h->id);
h->id = 0;
}
else
{
h->id = brush_create(h->brushmodel, h->brushdata, h->numfaces, h->contents);
}
history++;
} while (historyring[history & (historyring.length-1)].timestamp == h->timestamp);
};
//create and journal.
int(int mod, brushface_t *faces, int numfaces, int contents) brush_history_create =
{
history_t *h = &historyring[history & (historyring.length-1)];
h->timestamp = time;
memfree(h->brushdata);
h->brushdata = memalloc(sizeof(brushface_t) * h->numfaces);
memcpy(h->brushdata, faces, sizeof(brushface_t) * h->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);
history++;
history_max = history; //always break any pending redos
if (history_min < history_max - historyring.length)
history_min = history_max - historyring.length;
return h->id;
};
//delete and journal.
void(int mod, int id) brush_history_delete =
{
int numfaces = brush_get(mod, id, __NULL__, 0, __NULL__);
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 = 0;
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;
};
brushface_t tmp_faces[64];
int tmp_numfaces;
int tmp_contents;
//int selected;
int brushlist[64];
var string autocvar_ca_newbrushtexture = "metal4_2";
var float autocvar_ca_newbrushheight = 64;
var float autocvar_ca_brushdisplacement = 16;
var float autocvar_ca_brushrotation = 45.0/2;
var float autocvar_ca_grid = 16;
enum
{
BT_NONE, //selection
BT_MOVE,
BT_MERGE,
BT_MOVEFACE,
BT_CREATE,
BT_CLONEDISPLACE,
BT_SLICE
};
int brushtool;
int bt_points;
vector bt_point[64];
//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];
vector(vector a, vector b) cross =
{
return [ a_y * b_z - a_z * b_y,
a_z * b_x - a_x * b_z,
a_x * b_y - a_y * b_x];
};
#define dot(a,b) ((a)*(b))
vector(vector guess) brush_snappoint =
{
int facenum, points, point;
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 brushnum, numbrushes = brush_findinvolume(selectedbrushmodel, &axis[0], &dists[0], 6, &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
//walk the faces we found and use the point if its nearer than our previous guess.
for (point = 0; point < points; point++)
{
vector disp = facepoints[point] - guess;
float dist = disp*disp;
if (dist < bestdist)
best = facepoints[point];
}
}
}
return best;
};
void(float brushid) editor_drawbbox =
{
static vector bbox[2];
int p = brush_getfacepoints(selectedbrushmodel, brushid, 0, &bbox[0], 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
}
};
void(vector mousepos) editor_brushes_add =
{
vector col = '0 0 0';
int points, point;
int facenum = 0;
float intensity = (sin(gettime(5)*4)+1)*0.05;
//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"
"}");
//draw the selected brush.
for(;;)
{
points = brush_getfacepoints(selectedbrushmodel, selectedbrush, ++facenum, &facepoints[0], facepoints.length);
if (!points)
break; //end of face list, I guess
if (facenum == selectedbrushface)
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();
}
editor_drawbbox(selectedbrush);
vector t = mousefar;
vector o = mousenear;
if (vlen(o - t) > 8192)
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_point[showpoints++] = brush_snappoint(trace_endpos);
showpoints = 3;
}
if (brushtool == BT_CREATE || brushtool == BT_CLONEDISPLACE)
bt_point[showpoints++] = brush_snappoint(trace_endpos);
//FIXME: if slicing, draw the intersection
if (showpoints)
{
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 =
{
switch(brushtool)
{
case BT_CLONEDISPLACE:
drawrawstring('0 32 0', "Clone+Displace", '8 8 0', '1 1 1', 1);
if (!selectedbrush)
brushtool = BT_NONE;
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 (!selectedbrush)
brushtool = BT_NONE;
break;
}
};
//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)
{
int i;
for (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;
}
};
//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;
};
void() editor_brushes_registercommands =
{
registercommand("brushedit_undo");
registercommand("brushedit_redo");
registercommand("brushedit_create");
registercommand("brushedit_clone");
registercommand("brushedit_slice");
registercommand("brushedit_matchface");
registercommand("brushedit_resettexcoords");
registercommand("brushedit_settexture");
registercommand("brushedit_rotate");
registercommand("brushedit_binds");
};
float() editor_brushes_command =
{
switch(argv(0))
{
case "brushedit_undo":
brush_undo();
break;
case "brushedit_redo":
brush_undo();
break;
case "brushedit_create":
brushtool = BT_CREATE;
bt_points = 0;
break;
case "brushedit_clone":
brushtool = BT_CLONEDISPLACE;
bt_points = 0;
break;
case "brushedit_slice":
brushtool = BT_SLICE;
bt_points = 0;
break;
case "brushedit_matchface":
//FIXME
brushtool = BT_NONE;
bt_points = 0;
break;
case "brushedit_resettexcoords":
//FIXME
brushtool = BT_NONE;
bt_points = 0;
break;
case "brushedit_settexture":
//FIXME
brushtool = BT_NONE;
bt_points = 0;
break;
case "brushedit_rotate":
//FIXME
brushtool = BT_NONE;
bt_points = 0;
break;
case "brushedit_binds":
localcmd("conmenu\n");
localcmd("menubind 0 0 \"Creation Tool\" \"brushedit_create\"\n");
localcmd("menubind 0 8 \"Clone Tool\" \"brushedit_clone\"\n");
localcmd("menubind 0 16 \"Slice Tool\" \"brushedit_slice\"\n");
localcmd("menubind 0 24 \"Undo\" \"brushedit_undo\"\n");
localcmd("menubind 0 32 \"Redo\" \"brushedit_redo\"\n");
break;
default:
return FALSE;
}
//just in case.
cvar_set("ca_show", "1");
cvar_set("ca_editormode", ftos(MODE_BRUSHEDIT));
return TRUE;
};
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';
};
float(float key, float unic, vector mousepos) editor_brushes_key =
{
brushface_t *fa;
brushface_t *infa;
vector t = mousefar;
vector o = mousenear;
vector displacement;
int i, p;
if (vlen(o - t) > 8192)
t = o + normalize(t)*8192;
if (key == K_ESCAPE)
{
if (brushtool)
brushtool = BT_NONE;
return TRUE;
}
if (key == K_MOUSE1)
{
traceline(o, t, TRUE, world);
if (brushtool == BT_SLICE || brushtool == BT_CREATE || brushtool == BT_CLONEDISPLACE)
{
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);
brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE);
static vector bbox[2];
p = brush_getfacepoints(selectedbrushmodel, selectedbrush, 0, &bbox[0], 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];
}
if (brushtool == BT_CLONEDISPLACE && bt_points == 2)
{
displacement = bt_point[1] - bt_point[0];
if (selectedbrush && displacement != '0 0 0')
{
bt_points = 0;
tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length, &tmp_contents);
brushface_translate(&tmp_faces[0], tmp_numfaces, displacement);
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, tmp_contents);
}
}
}
else if (trace_brush_id != selectedbrush || selectedbrushface != trace_brush_faceid || selectedbrushmodel != trace_ent.modelindex)
{
brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE);
selectedbrush = trace_brush_id;
selectedbrushface = trace_brush_faceid;
selectedbrushmodel = trace_ent.modelindex;
brush_selected(selectedbrushmodel, selectedbrush, trace_brush_faceid, TRUE);
}
return TRUE;
}
if (key == K_ENTER)
{
if (brushtool == BT_CREATE && bt_points >= 3)
{
fa = &tmp_faces[0];
fa->planenormal = normalize(cross(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0]));
fa->planedist = bt_point[0] * fa->planenormal + autocvar_ca_newbrushheight;
fa->shadername = autocvar_ca_newbrushtexture;
reset_texturecoords(fa);
fa++;
fa->planenormal = -normalize(cross(bt_point[2] - bt_point[0], bt_point[1] - bt_point[0]));
fa->planedist = (bt_point[0] * fa->planenormal);
fa->shadername = autocvar_ca_newbrushtexture;
reset_texturecoords(fa);
fa++;
tmp_numfaces = 2;
for (p = 0; p < bt_points; p++)
{
int n = p + 1i;
if (n == bt_points)
n = 0;
fa->planenormal = normalize(cross(bt_point[p] - bt_point[n], tmp_faces[0].planenormal));
fa->planedist = bt_point[p] * fa->planenormal;
fa->shadername = autocvar_ca_newbrushtexture;
reset_texturecoords(fa);
fa++;
tmp_numfaces++;
}
// tmp_numfaces = fa - &tmp_faces[0];
bt_points = 0;
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, 1i);
}
else if (brushtool == BT_SLICE)
{
//get the current faces
tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length-1, &tmp_contents);
//generate a new face plane
fa = &tmp_faces[tmp_numfaces];
fa->planenormal = normalize(cross(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 okay
for (i = 0; i < tmp_numfaces; i++)
{
if (tmp_faces[i].planenormal == fa->planenormal && tmp_faces[i].planedist == fa->planedist)
{
print("that would be co-planar\n");
return TRUE;
}
}
bt_points = 0;
tmp_numfaces++;
reset_texturecoords(fa);
//delete the old one and insert the new
brush_history_delete(selectedbrushmodel, selectedbrush);
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], 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;
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, tmp_contents);
selectedbrushface = 0;
}
return TRUE;
}
if (key == 'p')
{
brushtool = BT_CREATE;
bt_points = 0;
return TRUE;
}
if (key == 'l')
{
brushtool = BT_CLONEDISPLACE;
bt_points = 0;
return TRUE;
}
//begin the cut tool
if (key == 'o') //I'd use #, but that would cause a problem for americans
{
brushtool = BT_SLICE;
bt_points = 0;
return TRUE;
}
if (key == 'm') //I'd use #, but that would cause a problem for americans
{
traceline(o, t, TRUE, world);
int extrafaces;
float found = FALSE;
if (selectedbrush == trace_brush_id)
{
print("cannot merge brush with itself\n");
return TRUE;
}
if (!trace_brush_id || !selectedbrush)
{
print("no brush targetted\n");
return TRUE;
}
tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length, &tmp_contents);
infa = &tmp_faces[tmp_numfaces];
extrafaces = brush_get(selectedbrushmodel, trace_brush_id, &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[0], i = 0; i < tmp_numfaces; i++, fa++)
{
//print(sprintf("%v %g vs %v %g\n", infa->planenormal, infa->planedist, fa->planenormal, fa->planedist));
if (fa->planenormal == -infa->planenormal && fa->planedist == -infa->planedist)
{
//print(sprintf("inverted (%i)\n", ((tmp_numfaces-i)-1i)));
//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 && 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");
//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(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length, &tmp_contents);
brush_get(selectedbrushmodel, trace_brush_id, &tmp_faces[tmp_numfaces], tmp_faces.length-tmp_numfaces, &tmp_contents);
infa = &tmp_faces[tmp_numfaces + trace_brush_faceid-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;
brush_history_delete(selectedbrushmodel, selectedbrush);
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, tmp_contents);
}
return TRUE;
}
//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(selectedbrushmodel, trace_brush_id);
brush_history_delete(selectedbrushmodel, selectedbrush);
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, tmp_contents);
selectedbrushface = 0;
brush_selected(selectedbrushmodel, selectedbrush, -1, FALSE);
return TRUE;
}
if (key == K_DEL || key == K_BACKSPACE)
{
if (brushtool)
brushtool = BT_NONE;
if (selectedbrush)
{
brush_history_delete(selectedbrushmodel, selectedbrush);
selectedbrush = 0;
}
return TRUE;
}
displacement = '0 0 0';
if (key == K_PGUP)
displacement = autocvar_ca_brushdisplacement * axialize(v_up);
if (key == K_PGDN)
displacement = autocvar_ca_brushdisplacement * axialize(-v_up);
if (key == K_KP_UPARROW)
displacement = autocvar_ca_brushdisplacement * axialize(v_forward);
if (key == K_KP_DOWNARROW)
displacement = autocvar_ca_brushdisplacement * axialize(-v_forward);
if (key == K_KP_RIGHTARROW)
displacement = autocvar_ca_brushdisplacement * axialize(v_right);
if (key == K_KP_LEFTARROW)
displacement = autocvar_ca_brushdisplacement * axialize(-v_right);
if (displacement != '0 0 0')
{
//this is just an example to show how to move a brush without hurting texture coords at all.
//note: due to precision loss, this is probably NOT a good way to move brushes around. cache the original position if possible and express transformations relative to that.
if (selectedbrush)
{
tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length, &tmp_contents);
brushface_translate(&tmp_faces[0], tmp_numfaces, displacement);
brush_history_delete(selectedbrushmodel, selectedbrush);
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, tmp_contents);
}
return TRUE;
}
if (key == '[' || key == ']')
{
//this is just an example to show how to rotate a brush.
//note: due to precision loss, this is probably NOT a good way to move brushes around. cache the original position if possible and express transformations relative to that.
//determine rotation matrix
if (key == '[')
makevectors([0, -autocvar_ca_brushrotation, 0]);
else
makevectors([0, autocvar_ca_brushrotation, 0]);
//rotate the selected brush
if (selectedbrush)
{
//find the brush's middle (based on its bbox)
brush_getfacepoints(selectedbrushmodel, selectedbrush, 0, &displacement, 1);
//grab the existing brush's planes
tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length, &tmp_contents);
//move it so its pivot is at the origin
brushface_translate(&tmp_faces[0], tmp_numfaces, -displacement);
//rotate it
brushface_rotate(&tmp_faces[0], tmp_numfaces);
//reposition it around its pivot again
brushface_translate(&tmp_faces[0], tmp_numfaces, displacement);
//delete the old version of the brush and insert the newly translated/rotated version of it.
brush_history_delete(selectedbrushmodel, selectedbrush);
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, tmp_contents);
}
return TRUE;
}
if (key == K_KP_PLUS || key == K_KP_MINUS)
{
//push a plane along its normal. yes, this will deform it if its not a cube, as the adjacent planes will not be changed, allowing them to cut into it differently.
if (selectedbrush && selectedbrushface)
{
fa = &tmp_faces[selectedbrushface-1];
i = tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length, &tmp_contents);
displacement = fa->planenormal;
if (key == K_KP_MINUS)
displacement *= -1;
fa->planedist += fa->planenormal * displacement;
fa->sbias -= fa->sdir * displacement;
fa->tbias -= fa->tdir * displacement;
brush_history_delete(selectedbrushmodel, selectedbrush);
selectedbrush = brush_history_create(selectedbrushmodel, &tmp_faces[0], tmp_numfaces, tmp_contents);
tmp_numfaces = brush_get(selectedbrushmodel, selectedbrush, &tmp_faces[0], tmp_faces.length, &tmp_contents);
if (i != tmp_numfaces)
selectedbrushface = 0; //if the engine rejected a plane, then it's because something became invalid. don't edit a different face.
}
return TRUE;
}
if (key == 'u')
{
brush_undo();
return TRUE;
}
if (key == 'i')
{
brush_redo();
return TRUE;
}
/* if (key == 's')
{
//CSG subtraction is actually quite easy...
//for each brush that intersects us, split it by every single one of our planes that intesect
//drop the resulting brushes if they contain contain points only within the subtraction region
}
*/ return FALSE;
};