quadrilateralcowboy/tools/radiant/EditorBrush.cpp
2020-06-12 14:06:25 -07:00

5220 lines
120 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "qe3.h"
#include <GL/glu.h>
#include "../../renderer/tr_local.h"
#include "../../renderer/model_local.h" // for idRenderModelMD5
void Brush_UpdateLightPoints(brush_t *b, const idVec3 &offset);
void Brush_DrawCurve( brush_t *b, bool bSelected, bool cam );
// globals
int g_nBrushId = 0;
bool g_bShowLightVolumes = false;
bool g_bShowLightTextures = false;
void GLCircle(float x, float y, float z, float r);
const int POINTS_PER_KNOT = 50;
/*
================
DrawRenderModel
================
*/
void DrawRenderModel( idRenderModel *model, idVec3 &origin, idMat3 &axis, bool cameraView ) {
for ( int i = 0; i < model->NumSurfaces(); i++ ) {
const modelSurface_t *surf = model->Surface( i );
const idMaterial *material = surf->shader;
int nDrawMode = g_pParentWnd->GetCamera()->Camera().draw_mode;
if ( cameraView && (nDrawMode == cd_texture || nDrawMode == cd_light) ) {
material->GetEditorImage()->Bind();
}
qglBegin( GL_TRIANGLES );
const srfTriangles_t *tri = surf->geometry;
for ( int j = 0; j < tri->numIndexes; j += 3 ) {
for ( int k = 0; k < 3; k++ ) {
int index = tri->indexes[j + k];
idVec3 v;
v = tri->verts[index].xyz * axis + origin;
qglTexCoord2f( tri->verts[index].st.x, tri->verts[index].st.y );
qglVertex3fv( v.ToFloatPtr() );
}
}
qglEnd();
}
}
/*
================
SnapVectorToGrid
================
*/
void SnapVectorToGrid(idVec3 &v) {
v.x = floor(v.x / g_qeglobals.d_gridsize + 0.5f) * g_qeglobals.d_gridsize;
v.y = floor(v.y / g_qeglobals.d_gridsize + 0.5f) * g_qeglobals.d_gridsize;
v.z = floor(v.z / g_qeglobals.d_gridsize + 0.5f) * g_qeglobals.d_gridsize;
}
/*
================
Brush_Name
================
*/
const char *Brush_Name( brush_t *b ) {
static char cBuff[1024];
b->numberId = g_nBrushId++;
if (g_qeglobals.m_bBrushPrimitMode) {
sprintf(cBuff, "Brush %i", b->numberId);
Brush_SetEpair(b, "Name", cBuff);
}
return cBuff;
}
/*
================
Brush_Alloc
================
*/
brush_t *Brush_Alloc( void ) {
brush_t *b = new brush_t;
b->prev = b->next = NULL;
b->oprev = b->onext = NULL;
b->owner = NULL;
b->mins.Zero();
b->maxs.Zero();
b->lightCenter.Zero();
b->lightRight.Zero();
b->lightTarget.Zero();
b->lightUp.Zero();
b->lightRadius.Zero();
b->lightOffset.Zero();
b->lightColor.Zero();
b->lightStart.Zero();
b->lightEnd.Zero();
b->pointLight = false;
b->startEnd = false;
b->lightTexture = 0;
b->trackLightOrigin = false;
b->entityModel = false;
b->brush_faces = NULL;
b->hiddenBrush = false;
b->pPatch = NULL;
b->pUndoOwner = NULL;
b->undoId = 0;
b->redoId = 0;
b->ownerId = 0;
b->numberId = 0;
b->itemOwner = 0;
b->bModelFailed = false;
b->modelHandle = NULL;
b->forceVisibile = false;
b->forceWireFrame = false;
return b;
}
/*
================
TextureAxisFromPlane
================
*/
idVec3 baseaxis[18] = {
idVec3(0, 0, 1),
idVec3(1, 0, 0),
idVec3(0, -1, 0),
// floor
idVec3(0, 0, -1),
idVec3(1, 0, 0),
idVec3(0, -1, 0),
// ceiling
idVec3(1, 0, 0),
idVec3(0, 1, 0),
idVec3(0, 0, -1),
// west wall
idVec3(-1, 0, 0),
idVec3(0, 1, 0),
idVec3(0, 0, -1),
// east wall
idVec3(0, 1, 0),
idVec3(1, 0, 0),
idVec3(0, 0, -1),
// south wall
idVec3(0, -1, 0),
idVec3(1, 0, 0),
idVec3(0, 0, -1) // north wall
};
void TextureAxisFromPlane( const idPlane &pln, idVec3 &xv, idVec3 &yv) {
int bestaxis;
float dot, best;
int i;
best = 0;
bestaxis = 0;
for (i = 0; i < 6; i++) {
dot = DotProduct(pln, baseaxis[i * 3]);
if (dot > best) {
best = dot;
bestaxis = i;
}
}
VectorCopy(baseaxis[bestaxis * 3 + 1], xv);
VectorCopy(baseaxis[bestaxis * 3 + 2], yv);
}
/*
================
ShadeForNormal
Light different planes differently to improve recognition
================
*/
float lightaxis[3] = { 0.6f, 0.8f, 1.0f };
float ShadeForNormal(idVec3 normal) {
int i;
float f;
// axial plane
for (i = 0; i < 3; i++) {
if ( idMath::Fabs(normal[i]) > 0.9f ) {
f = lightaxis[i];
return f;
}
}
// between two axial planes
for (i = 0; i < 3; i++) {
if ( idMath::Fabs(normal[i]) < 0.1f ) {
f = (lightaxis[(i + 1) % 3] + lightaxis[(i + 2) % 3]) / 2;
return f;
}
}
// other
f = (lightaxis[0] + lightaxis[1] + lightaxis[2]) / 3;
return f;
}
/*
================
Face_Alloc
================
*/
face_t *Face_Alloc(void) {
brushprimit_texdef_t bp;
face_t *f = (face_t *) Mem_ClearedAlloc(sizeof(*f));
bp.coords[0][0] = 0.0f;
bp.coords[1][1] = 0.0f;
f->brushprimit_texdef = bp;
f->dirty = true;
return f;
}
/*
================
Face_Free
================
*/
void Face_Free(face_t *f) {
assert(f != 0);
if (f->face_winding) {
delete f->face_winding;
}
f->texdef.~texdef_t();
Mem_Free(f);
}
/*
================
Face_Clone
================
*/
face_t *Face_Clone(face_t *f) {
face_t *n;
n = Face_Alloc();
n->texdef = f->texdef;
n->brushprimit_texdef = f->brushprimit_texdef;
memcpy(n->planepts, f->planepts, sizeof(n->planepts));
n->plane = f->plane;
n->originalPlane = f->originalPlane;
n->dirty = f->dirty;
// all other fields are derived, and will be set by Brush_Build
return n;
}
/*
================
Face_FullClone
Used by Undo.
Makes an exact copy of the face.
================
*/
face_t *Face_FullClone(face_t *f) {
face_t *n;
n = Face_Alloc();
n->texdef = f->texdef;
n->brushprimit_texdef = f->brushprimit_texdef;
memcpy(n->planepts, f->planepts, sizeof(n->planepts));
n->plane = f->plane;
n->originalPlane = f->originalPlane;
n->dirty = f->dirty;
if (f->face_winding) {
n->face_winding = f->face_winding->Copy();
}
else {
n->face_winding = NULL;
}
n->d_texture = Texture_ForName(n->texdef.name);
return n;
}
/*
================
Clamp
================
*/
void Clamp(float &f, int nClamp) {
float fFrac = f - static_cast<int>(f);
f = static_cast<int>(f) % nClamp;
f += fFrac;
}
/*
================
Face_MoveTexture
================
*/
void Face_MoveTexture(face_t *f, idVec3 delta) {
idVec3 vX, vY;
/*
* #ifdef _DEBUG if (g_PrefsDlg.m_bBrushPrimitMode) common->Printf("Warning :
* Face_MoveTexture not done in brush primitive mode\n"); #endif
*/
if (g_qeglobals.m_bBrushPrimitMode) {
Face_MoveTexture_BrushPrimit(f, delta);
}
else {
TextureAxisFromPlane( f->plane, vX, vY );
idVec3 vDP, vShift;
vDP[0] = DotProduct(delta, vX);
vDP[1] = DotProduct(delta, vY);
double fAngle = DEG2RAD( f->texdef.rotate );
double c = cos(fAngle);
double s = sin(fAngle);
vShift[0] = vDP[0] * c - vDP[1] * s;
vShift[1] = vDP[0] * s + vDP[1] * c;
if (!f->texdef.scale[0]) {
f->texdef.scale[0] = 1;
}
if (!f->texdef.scale[1]) {
f->texdef.scale[1] = 1;
}
f->texdef.shift[0] -= vShift[0] / f->texdef.scale[0];
f->texdef.shift[1] -= vShift[1] / f->texdef.scale[1];
// clamp the shifts
Clamp(f->texdef.shift[0], f->d_texture->GetEditorImage()->uploadWidth);
Clamp(f->texdef.shift[1], f->d_texture->GetEditorImage()->uploadHeight);
}
}
/*
================
Face_SetColor
================
*/
void Face_SetColor(brush_t *b, face_t *f, float fCurveColor) {
float shade;
const idMaterial *q;
q = f->d_texture;
// set shading for face
shade = ShadeForNormal( f->plane.Normal() );
if (g_pParentWnd->GetCamera()->Camera().draw_mode == cd_texture && (b->owner && !b->owner->eclass->fixedsize)) {
// if (b->curveBrush) shade = fCurveColor;
f->d_color[0] = f->d_color[1] = f->d_color[2] = shade;
}
else if ( f && b && b->owner ) {
f->d_color[0] = shade * b->owner->eclass->color.x;
f->d_color[1] = shade * b->owner->eclass->color.y;
f->d_color[2] = shade * b->owner->eclass->color.z;
}
}
/*
================
Face_TextureVectors
NOTE: this is never to get called while in brush primitives mode
================
*/
void Face_TextureVectors(face_t *f, float STfromXYZ[2][4]) {
idVec3 pvecs[2];
int sv, tv;
float ang, sinv, cosv;
float ns, nt;
int i, j;
const idMaterial *q;
texdef_t *td;
#ifdef _DEBUG
//
// ++timo when playing with patches, this sometimes get called and the Warning is
// displayed find some way out ..
//
if (g_qeglobals.m_bBrushPrimitMode && !g_qeglobals.bNeedConvert) {
common->Printf("Warning : illegal call of Face_TextureVectors in brush primitive mode\n");
}
#endif
td = &f->texdef;
q = f->d_texture;
memset(STfromXYZ, 0, 8 * sizeof (float));
if (!td->scale[0]) {
td->scale[0] = (g_PrefsDlg.m_bHiColorTextures) ? 2 : 1;
}
if (!td->scale[1]) {
td->scale[1] = (g_PrefsDlg.m_bHiColorTextures) ? 2 : 1;
}
// get natural texture axis
TextureAxisFromPlane( f->plane, pvecs[0], pvecs[1]);
// rotate axis
if (td->rotate == 0) {
sinv = 0;
cosv = 1;
}
else if (td->rotate == 90) {
sinv = 1;
cosv = 0;
}
else if (td->rotate == 180) {
sinv = 0;
cosv = -1;
}
else if (td->rotate == 270) {
sinv = -1;
cosv = 0;
}
else {
ang = DEG2RAD( td->rotate );
sinv = sin(ang);
cosv = cos(ang);
}
if (pvecs[0][0]) {
sv = 0;
}
else if (pvecs[0][1]) {
sv = 1;
}
else {
sv = 2;
}
if (pvecs[1][0]) {
tv = 0;
}
else if (pvecs[1][1]) {
tv = 1;
}
else {
tv = 2;
}
for (i = 0; i < 2; i++) {
ns = cosv * pvecs[i][sv] - sinv * pvecs[i][tv];
nt = sinv * pvecs[i][sv] + cosv * pvecs[i][tv];
STfromXYZ[i][sv] = ns;
STfromXYZ[i][tv] = nt;
}
// scale
for (i = 0; i < 2; i++) {
for (j = 0; j < 3; j++) {
STfromXYZ[i][j] = STfromXYZ[i][j] / td->scale[i];
}
}
// shift
STfromXYZ[0][3] = td->shift[0];
STfromXYZ[1][3] = td->shift[1];
for (j = 0; j < 4; j++) {
STfromXYZ[0][j] /= q->GetEditorImage()->uploadWidth;
STfromXYZ[1][j] /= q->GetEditorImage()->uploadHeight;
}
}
/*
================
Face_MakePlane
================
*/
void Face_MakePlane(face_t *f) {
int j;
idVec3 t1, t2, t3;
idPlane oldPlane = f->plane;
// convert to a vector / dist plane
for (j = 0; j < 3; j++) {
t1[j] = f->planepts[0][j] - f->planepts[1][j];
t2[j] = f->planepts[2][j] - f->planepts[1][j];
t3[j] = f->planepts[1][j];
}
f->plane = t1.Cross( t2 );
//if ( f->plane.Compare( vec3_origin ) ) {
// printf("WARNING: brush plane with no normal\n");
//}
f->plane.Normalize(false);
f->plane[3] = - (t3 * f->plane.Normal());
if ( !f->dirty && !f->plane.Compare( oldPlane, 0.01f ) ) {
f->dirty = true;
}
}
/*
================
EmitTextureCoordinates
================
*/
void EmitTextureCoordinates(idVec5 &xyzst, const idMaterial *q, face_t *f, bool force) {
float STfromXYZ[2][4];
if (g_qeglobals.m_bBrushPrimitMode && !force) {
EmitBrushPrimitTextureCoordinates(f, f->face_winding);
}
else {
Face_TextureVectors(f, STfromXYZ);
xyzst[3] = DotProduct(xyzst, STfromXYZ[0]) + STfromXYZ[0][3];
xyzst[4] = DotProduct(xyzst, STfromXYZ[1]) + STfromXYZ[1][3];
}
}
/*
================
Brush_MakeFacePlanes
================
*/
void Brush_MakeFacePlanes(brush_t *b) {
face_t *f;
for (f = b->brush_faces; f; f = f->next) {
Face_MakePlane(f);
}
}
/*
================
DrawBrushEntityName
================
*/
void DrawBrushEntityName(brush_t *b) {
const char *name;
// float a, s, c; vec3_t mid; int i;
if (!b->owner) {
return; // during contruction
}
if (b->owner == world_entity) {
return;
}
if (b != b->owner->brushes.onext) {
return; // not key brush
}
if (!(g_qeglobals.d_savedinfo.exclude & EXCLUDE_ANGLES)) {
// draw the angle pointer
float a = FloatForKey(b->owner, "angle");
if (a) {
float s = sin( DEG2RAD( a ) );
float c = cos( DEG2RAD( a ) );
idVec3 mid = (b->mins + b->maxs) / 2.0f;
qglBegin(GL_LINE_STRIP);
qglVertex3fv(mid.ToFloatPtr());
mid[0] += c * 8;
mid[1] += s * 8;
mid[2] += s * 8;
qglVertex3fv(mid.ToFloatPtr());
mid[0] -= c * 4;
mid[1] -= s * 4;
mid[2] -= s * 4;
mid[0] -= s * 4;
mid[1] += c * 4;
mid[2] += c * 4;
qglVertex3fv(mid.ToFloatPtr());
mid[0] += c * 4;
mid[1] += s * 4;
mid[2] += s * 4;
mid[0] += s * 4;
mid[1] -= c * 4;
mid[2] -= c * 4;
qglVertex3fv(mid.ToFloatPtr());
mid[0] -= c * 4;
mid[1] -= s * 4;
mid[2] -= s * 4;
mid[0] += s * 4;
mid[1] -= c * 4;
mid[2] -= c * 4;
qglVertex3fv(mid.ToFloatPtr());
qglEnd();
}
}
int viewType = g_pParentWnd->ActiveXY()->GetViewType();
float scale = g_pParentWnd->ActiveXY()->Scale();
if (g_qeglobals.d_savedinfo.show_names && scale >= 1.0f) {
name = ValueForKey(b->owner, "name");
int nameLen = strlen(name);
if ( nameLen == 0 ) {
name = ValueForKey(b->owner, "classname");
nameLen = strlen(name);
}
if ( nameLen > 0 ) {
idVec3 origin = b->owner->origin;
float halfWidth = ( (nameLen / 2) * (7.0f / scale) );
float halfHeight = 4.0f / scale;
switch (viewType) {
case XY:
origin.x -= halfWidth;
origin.y += halfHeight;
break;
case XZ:
origin.x -= halfWidth;
origin.z += halfHeight;
break;
case YZ:
origin.y -= halfWidth;
origin.z += halfHeight;
break;
}
qglRasterPos3fv( origin.ToFloatPtr() );
qglCallLists(nameLen, GL_UNSIGNED_BYTE, name);
}
}
}
/*
================
Brush_MakeFaceWinding
returns the visible winding
================
*/
idWinding *Brush_MakeFaceWinding(brush_t *b, face_t *face, bool keepOnPlaneWinding) {
idWinding *w;
face_t *clip;
idPlane plane;
bool past;
// get a poly that covers an effectively infinite area
w = new idWinding( face->plane );
// chop the poly by all of the other faces
past = false;
for (clip = b->brush_faces; clip && w; clip = clip->next) {
if (clip == face) {
past = true;
continue;
}
if ( DotProduct(face->plane, clip->plane) > 0.999f &&
idMath::Fabs(face->plane[3] - clip->plane[3]) < 0.01f ) { // identical plane, use the later one
if (past) {
delete w;
common->Printf("Unable to create face winding on brush\n");
return NULL;
}
continue;
}
// flip the plane, because we want to keep the back side
VectorSubtract(vec3_origin, clip->plane, plane );
plane[3] = -clip->plane[3];
w = w->Clip( plane, ON_EPSILON, keepOnPlaneWinding );
if ( !w ) {
return w;
}
}
if ( w->GetNumPoints() < 3) {
delete w;
w = NULL;
}
if (!w) {
Sys_Status("Unable to create face winding on brush\n");
}
return w;
}
/*
================
Brush_Build
Builds a brush rendering data and also sets the min/max bounds
TTimo added a bConvert flag to convert between old and new brush texture formats
TTimo brush grouping: update the group treeview if necessary
================
*/
void Brush_Build(brush_t *b, bool bSnap, bool bMarkMap, bool bConvert, bool updateLights) {
bool bLocalConvert = false;
#ifdef _DEBUG
if (!g_qeglobals.m_bBrushPrimitMode && bConvert) {
common->Printf("Warning : conversion from brush primitive to old brush format not implemented\n");
}
#endif
//
// if bConvert is set and g_qeglobals.bNeedConvert is not, that just means we need
// convert for this brush only
//
if (bConvert && !g_qeglobals.bNeedConvert) {
bLocalConvert = true;
g_qeglobals.bNeedConvert = true;
}
/* build the windings and generate the bounding box */
Brush_BuildWindings(b, bSnap, EntityHasModel(b->owner) || b->pPatch, updateLights);
/* move the points and edges if in select mode */
if (g_qeglobals.d_select_mode == sel_vertex || g_qeglobals.d_select_mode == sel_edge) {
SetupVertexSelection();
}
if (bMarkMap) {
Sys_MarkMapModified();
g_pParentWnd->GetCamera()->MarkWorldDirty();
}
if (bLocalConvert) {
g_qeglobals.bNeedConvert = false;
}
}
/*
================
Brush_SplitBrushByFace
The incoming brush is NOT freed. The incoming face is NOT left referenced.
================
*/
void Brush_SplitBrushByFace(brush_t *in, face_t *f, brush_t **front, brush_t **back) {
brush_t *b;
face_t *nf;
idVec3 temp;
b = Brush_Clone(in);
nf = Face_Clone(f);
nf->texdef = b->brush_faces->texdef;
nf->brushprimit_texdef = b->brush_faces->brushprimit_texdef;
nf->next = b->brush_faces;
b->brush_faces = nf;
Brush_Build(b);
Brush_RemoveEmptyFaces(b);
if (!b->brush_faces) { // completely clipped away
Brush_Free(b);
*back = NULL;
}
else {
Entity_LinkBrush(in->owner, b);
*back = b;
}
b = Brush_Clone(in);
nf = Face_Clone(f);
// swap the plane winding
VectorCopy(nf->planepts[0], temp);
VectorCopy(nf->planepts[1], nf->planepts[0]);
VectorCopy(temp, nf->planepts[1]);
nf->texdef = b->brush_faces->texdef;
nf->brushprimit_texdef = b->brush_faces->brushprimit_texdef;
nf->next = b->brush_faces;
b->brush_faces = nf;
Brush_Build(b);
Brush_RemoveEmptyFaces(b);
if (!b->brush_faces) { // completely clipped away
Brush_Free(b);
*front = NULL;
}
else {
Entity_LinkBrush(in->owner, b);
*front = b;
}
}
/*
================
Brush_BestSplitFace
returns the best face to split the brush with. return NULL if the brush is convex
================
*/
face_t *Brush_BestSplitFace(brush_t *b) {
face_t *face, *f, *bestface;
idWinding *front, *back;
int splits, tinywindings, value, bestvalue;
bestvalue = 999999;
bestface = NULL;
for ( face = b->brush_faces; face; face = face->next ) {
splits = 0;
tinywindings = 0;
for ( f = b->brush_faces; f; f = f->next ) {
if ( f == face ) {
continue;
}
f->face_winding->Split( face->plane, 0.1f, &front, &back );
if ( !front ) {
delete back;
}
else if ( !back ) {
delete front;
}
else {
splits++;
if ( front->IsTiny() ) {
tinywindings++;
}
if ( back->IsTiny() ) {
tinywindings++;
}
delete front;
delete back;
}
}
if ( splits ) {
value = splits + 50 * tinywindings;
if ( value < bestvalue ) {
bestvalue = value;
bestface = face;
}
}
}
return bestface;
}
/*
================
Brush_MakeConvexBrushes
MrE FIXME: this doesn't work because the old Brush_SplitBrushByFace is used
Turns the brush into a minimal number of convex brushes.
If the input brush is convex then it will be returned. Otherwise the input
brush will be freed.
NOTE: the input brush should have windings for the faces.
================
*/
brush_t *Brush_MakeConvexBrushes(brush_t *b) {
brush_t *front, *back, *end;
face_t *face;
b->next = NULL;
face = Brush_BestSplitFace(b);
if (!face) {
return b;
}
Brush_SplitBrushByFace(b, face, &front, &back);
// this should never happen
if (!front && !back) {
return b;
}
Brush_Free(b);
if (!front) {
return Brush_MakeConvexBrushes(back);
}
b = Brush_MakeConvexBrushes(front);
if (back) {
for (end = b; end->next; end = end->next);
end->next = Brush_MakeConvexBrushes(back);
}
return b;
}
/*
================
Brush_Convex
returns true if the brush is convex
================
*/
int Brush_Convex(brush_t *b) {
face_t *face1, *face2;
for (face1 = b->brush_faces; face1; face1 = face1->next) {
if (!face1->face_winding) {
continue;
}
for (face2 = b->brush_faces; face2; face2 = face2->next) {
if (face1 == face2) {
continue;
}
if (!face2->face_winding) {
continue;
}
if ( face1->face_winding->PlanesConcave( *face2->face_winding,
face1->plane.Normal(), face2->plane.Normal(), -face1->plane[3], -face2->plane[3] ) ) {
return false;
}
}
}
return true;
}
/*
================
Brush_MoveVertexes
The input brush must be convex.
The input brush must have face windings.
The output brush will be convex.
Returns true if the WHOLE vertex movement is performed.
================
*/
#define MAX_MOVE_FACES 64
#define TINY_EPSILON 0.0325f
int Brush_MoveVertex(brush_t *b, const idVec3 &vertex, const idVec3 &delta, idVec3 &end, bool bSnap) {
face_t *f, *face, *newface, *lastface, *nextface;
face_t *movefaces[MAX_MOVE_FACES];
int movefacepoints[MAX_MOVE_FACES];
idWinding *w, tmpw(3);
idVec3 start, mid;
idPlane plane;
int i, j, k, nummovefaces, result, done;
float dot, front, back, frac, smallestfrac;
result = true;
tmpw.SetNumPoints( 3 );
VectorCopy(vertex, start);
VectorAdd(vertex, delta, end);
// snap or not?
//
if (bSnap) {
for (i = 0; i < 3; i++) {
end[i] = floor( end[i] / 0.125f + 0.5f ) * 0.125f;
}
}
VectorCopy(end, mid);
// if the start and end are the same
if ( start.Compare( end, TINY_EPSILON ) ) {
return false;
}
// the end point may not be the same as another vertex
for ( face = b->brush_faces; face; face = face->next ) {
w = face->face_winding;
if (!w) {
continue;
}
for (i = 0; i < w->GetNumPoints(); i++) {
if ( end.Compare( (*w)[i].ToVec3(), TINY_EPSILON ) ) {
VectorCopy(vertex, end);
return false;
}
}
}
done = false;
while (!done) {
//
// chop off triangles from all brush faces that use the to be moved vertex store
// pointers to these chopped off triangles in movefaces[]
//
nummovefaces = 0;
for (face = b->brush_faces; face; face = face->next) {
w = face->face_winding;
if (!w) {
continue;
}
for (i = 0; i < w->GetNumPoints(); i++) {
if ( start.Compare( (*w)[i].ToVec3(), TINY_EPSILON ) ) {
if (face->face_winding->GetNumPoints() <= 3) {
movefacepoints[nummovefaces] = i;
movefaces[nummovefaces++] = face;
break;
}
dot = DotProduct(end, face->plane) + face->plane[3];
// if the end point is in front of the face plane
//if ( dot > 0.1f ) {
if ( dot > TINY_EPSILON ) {
// fanout triangle subdivision
for (k = i; k < i + w->GetNumPoints() - 3; k++) {
VectorCopy((*w)[i], tmpw[0]);
VectorCopy((*w)[(k + 1) % w->GetNumPoints()], tmpw[1]);
VectorCopy((*w)[(k + 2) % w->GetNumPoints()], tmpw[2]);
newface = Face_Clone(face);
// get the original
for (f = face; f->original; f = f->original) {};
newface->original = f;
// store the new winding
if (newface->face_winding) {
delete newface->face_winding;
}
newface->face_winding = tmpw.Copy();
// get the texture
newface->d_texture = Texture_ForName(newface->texdef.name);
// add the face to the brush
newface->next = b->brush_faces;
b->brush_faces = newface;
// add this new triangle to the move faces
movefacepoints[nummovefaces] = 0;
movefaces[nummovefaces++] = newface;
}
// give the original face a new winding
VectorCopy((*w)[(i - 2 + w->GetNumPoints()) % w->GetNumPoints()], tmpw[0]);
VectorCopy((*w)[(i - 1 + w->GetNumPoints()) % w->GetNumPoints()], tmpw[1]);
VectorCopy((*w)[i], tmpw[2]);
delete face->face_winding;
face->face_winding = tmpw.Copy();
// add the original face to the move faces
movefacepoints[nummovefaces] = 2;
movefaces[nummovefaces++] = face;
}
else {
// chop a triangle off the face
VectorCopy((*w)[(i - 1 + w->GetNumPoints()) % w->GetNumPoints()], tmpw[0]);
VectorCopy((*w)[i], tmpw[1]);
VectorCopy((*w)[(i + 1) % w->GetNumPoints()], tmpw[2]);
// remove the point from the face winding
w->RemovePoint( i );
// get texture crap right
Face_SetColor(b, face, 1.0);
for (j = 0; j < w->GetNumPoints(); j++) {
EmitTextureCoordinates( (*w)[j], face->d_texture, face );
}
// make a triangle face
newface = Face_Clone(face);
// get the original
for (f = face; f->original; f = f->original) {};
newface->original = f;
// store the new winding
if (newface->face_winding) {
delete newface->face_winding;
}
newface->face_winding = tmpw.Copy();
// get the texture
newface->d_texture = Texture_ForName(newface->texdef.name);
// add the face to the brush
newface->next = b->brush_faces;
b->brush_faces = newface;
movefacepoints[nummovefaces] = 1;
movefaces[nummovefaces++] = newface;
}
break;
}
}
}
//
// now movefaces contains pointers to triangle faces that contain the to be moved
// vertex
//
done = true;
VectorCopy(end, mid);
smallestfrac = 1;
for (face = b->brush_faces; face; face = face->next) {
// check if there is a move face that has this face as the original
for (i = 0; i < nummovefaces; i++) {
if (movefaces[i]->original == face) {
break;
}
}
if (i >= nummovefaces) {
continue;
}
// check if the original is not a move face itself
for (j = 0; j < nummovefaces; j++) {
if (face == movefaces[j]) {
break;
}
}
// if the original is not a move face itself
if (j >= nummovefaces) {
memcpy(&plane, &movefaces[i]->original->plane, sizeof(plane));
}
else {
k = movefacepoints[j];
w = movefaces[j]->face_winding;
VectorCopy((*w)[(k + 1) % w->GetNumPoints()], tmpw[0]);
VectorCopy((*w)[(k + 2) % w->GetNumPoints()], tmpw[1]);
k = movefacepoints[i];
w = movefaces[i]->face_winding;
VectorCopy((*w)[(k + 1) % w->GetNumPoints()], tmpw[2]);
if ( !plane.FromPoints( tmpw[0].ToVec3(), tmpw[1].ToVec3(), tmpw[2].ToVec3(), false ) ) {
VectorCopy((*w)[(k + 2) % w->GetNumPoints()], tmpw[2]);
if ( !plane.FromPoints( tmpw[0].ToVec3(), tmpw[1].ToVec3(), tmpw[2].ToVec3() ), false ) {
// this should never happen otherwise the face merge did
// a crappy job a previous pass
continue;
}
}
plane[0] = -plane[0];
plane[1] = -plane[1];
plane[2] = -plane[2];
plane[3] = -plane[3];
}
// now we've got the plane to check against
front = DotProduct(start, plane) + plane[3];
back = DotProduct(end, plane) + plane[3];
// if the whole move is at one side of the plane
if (front < TINY_EPSILON && back < TINY_EPSILON) {
continue;
}
if (front > -TINY_EPSILON && back > -TINY_EPSILON) {
continue;
}
// if there's no movement orthogonal to this plane at all
if ( idMath::Fabs(front - back) < 0.001f ) {
continue;
}
// ok first only move till the plane is hit
frac = front / (front - back);
if (frac < smallestfrac) {
mid[0] = start[0] + (end[0] - start[0]) * frac;
mid[1] = start[1] + (end[1] - start[1]) * frac;
mid[2] = start[2] + (end[2] - start[2]) * frac;
smallestfrac = frac;
}
done = false;
}
// move the vertex
for (i = 0; i < nummovefaces; i++) {
// move vertex to end position
VectorCopy( mid, (*movefaces[i]->face_winding)[movefacepoints[i]] );
// create new face plane
for (j = 0; j < 3; j++) {
VectorCopy( (*movefaces[i]->face_winding)[j], movefaces[i]->planepts[j] );
}
Face_MakePlane( movefaces[i] );
if ( movefaces[i]->plane.Normal().Length() < TINY_EPSILON ) {
result = false;
}
}
// if the brush is no longer convex
if (!result || !Brush_Convex(b)) {
for (i = 0; i < nummovefaces; i++) {
// move the vertex back to the initial position
VectorCopy( start, (*movefaces[i]->face_winding)[movefacepoints[i]] );
// create new face plane
for (j = 0; j < 3; j++) {
VectorCopy( (*movefaces[i]->face_winding)[j], movefaces[i]->planepts[j] );
}
Face_MakePlane(movefaces[i]);
}
result = false;
VectorCopy(start, end);
done = true;
}
else {
VectorCopy(mid, start);
}
// get texture crap right
for (i = 0; i < nummovefaces; i++) {
Face_SetColor( b, movefaces[i], 1.0f );
for (j = 0; j < movefaces[i]->face_winding->GetNumPoints(); j++) {
EmitTextureCoordinates( (*movefaces[i]->face_winding)[j], movefaces[i]->d_texture, movefaces[i] );
}
}
// now try to merge faces with their original faces
lastface = NULL;
for (face = b->brush_faces; face; face = nextface) {
nextface = face->next;
if (!face->original) {
lastface = face;
continue;
}
if ( !face->plane.Compare( face->original->plane, 0.0001f ) ) {
lastface = face;
continue;
}
w = face->face_winding->TryMerge( *face->original->face_winding, face->plane.Normal(), true );
if (!w) {
lastface = face;
continue;
}
delete face->original->face_winding;
face->original->face_winding = w;
// get texture crap right
Face_SetColor( b, face->original, 1.0f );
for (j = 0; j < face->original->face_winding->GetNumPoints(); j++) {
EmitTextureCoordinates( (*face->original->face_winding)[j], face->original->d_texture, face->original);
}
// remove the face that was merged with the original
if (lastface) {
lastface->next = face->next;
}
else {
b->brush_faces = face->next;
}
Face_Free(face);
}
}
return result;
}
/*
================
Brush_InsertVertexBetween
Adds a vertex to the brush windings between the given two points.
================
*/
int Brush_InsertVertexBetween(brush_t *b, idVec3 p1, idVec3 p2) {
face_t *face;
idWinding *w, *neww;
idVec3 point;
int i, insert;
if ( p1.Compare( p2, TINY_EPSILON ) ) {
return false;
}
VectorAdd( p1, p2, point );
VectorScale( point, 0.5f, point );
insert = false;
// the end point may not be the same as another vertex
for (face = b->brush_faces; face; face = face->next) {
w = face->face_winding;
if (!w) {
continue;
}
neww = NULL;
for (i = 0; i < w->GetNumPoints(); i++) {
if (! p1.Compare((*w)[i].ToVec3(), TINY_EPSILON)) {
continue;
}
if ( p2.Compare( (*w)[(i + 1) % w->GetNumPoints()].ToVec3(), TINY_EPSILON ) ) {
neww = new idWinding( *w );
neww->InsertPoint( point, (i + 1) % w->GetNumPoints() );
break;
}
else if ( p2.Compare( (*w)[(i - 1 + w->GetNumPoints()) % w->GetNumPoints()].ToVec3(), TINY_EPSILON ) ) {
neww = new idWinding( *w );
neww->InsertPoint( point, i );
break;
}
}
if (neww) {
delete face->face_winding;
face->face_winding = neww;
insert = true;
}
}
return insert;
}
/*
================
Brush_ResetFaceOriginals
reset points to original faces to NULL
================
*/
void Brush_ResetFaceOriginals(brush_t *b) {
face_t *face;
for (face = b->brush_faces; face; face = face->next) {
face->original = NULL;
}
}
/*
================
Brush_Parse
The brush is NOT linked to any list
FIXME: when using old brush primitives, the test loop for "Brush" and "patchDef2" "patchDef3"
run before each face parsing. It works, but it's a performance hit
================
*/
brush_t *Brush_Parse(idVec3 origin) {
brush_t *b;
face_t *f;
int i, j;
idVec3 useOrigin = origin;
g_qeglobals.d_parsed_brushes++;
b = Brush_Alloc();
do {
if (!GetToken(true)) {
break;
}
if (!strcmp(token, "}")) {
break;
}
// handle "Brush" primitive
if ( idStr::Icmp(token, "brushDef") == 0 || idStr::Icmp(token, "brushDef2") == 0 || idStr::Icmp(token, "brushDef3") == 0 ) {
// Timo parsing new brush format
g_qeglobals.bPrimitBrushes = true;
// check the map is not mixing the two kinds of brushes
if (g_qeglobals.m_bBrushPrimitMode) {
if (g_qeglobals.bOldBrushes) {
common->Printf("Warning : old brushes and brush primitive in the same file are not allowed ( Brush_Parse )\n");
}
}
else {
// ++Timo write new brush primitive -> old conversion code for Q3->Q2 conversions ?
common->Printf("Warning : conversion code from brush primitive not done ( Brush_Parse )\n");
}
bool newFormat = false;
if ( idStr::Icmp(token, "brushDef2") == 0 ) {
newFormat = true;
// useOrigin.Zero();
}
else if ( idStr::Icmp(token, "brushDef3") == 0 ) {
newFormat = true;
}
BrushPrimit_Parse(b, newFormat, useOrigin);
if (newFormat) {
//Brush_BuildWindings(b, true, true, false, false);
}
if (b == NULL) {
Warning("parsing brush primitive");
return NULL;
}
else {
continue;
}
}
if ( idStr::Icmp(token, "patchDef2") == 0 || idStr::Icmp(token, "patchDef3") == 0 ) {
Brush_Free(b);
// double string compare but will go away soon
b = Patch_Parse( idStr::Icmp(token, "patchDef2") == 0 );
if (b == NULL) {
Warning("parsing patch/brush");
return NULL;
}
else {
continue;
}
// handle inline patch
}
else {
// Timo parsing old brush format
g_qeglobals.bOldBrushes = true;
if (g_qeglobals.m_bBrushPrimitMode) {
// check the map is not mixing the two kinds of brushes
if (g_qeglobals.bPrimitBrushes) {
common->Printf("Warning : old brushes and brush primitive in the same file are not allowed ( Brush_Parse )\n");
}
// set the "need" conversion flag
g_qeglobals.bNeedConvert = true;
}
f = Face_Alloc();
//
// add the brush to the end of the chain, so loading and saving a map doesn't
// reverse the order
//
f->next = NULL;
if (!b->brush_faces) {
b->brush_faces = f;
}
else {
face_t *scan;
for (scan = b->brush_faces; scan->next; scan = scan->next)
;
scan->next = f;
}
// read the three point plane definition
for (i = 0; i < 3; i++) {
if (i != 0) {
GetToken(true);
}
if (strcmp(token, "(")) {
Warning("parsing brush");
return NULL;
}
for (j = 0; j < 3; j++) {
GetToken(false);
f->planepts[i][j] = atof(token);
}
GetToken(false);
if (strcmp(token, ")")) {
Warning("parsing brush");
return NULL;
}
}
}
// read the texturedef
GetToken(false);
f->texdef.SetName(token);
if (token[0] == '(') {
int i = 32;
}
GetToken(false);
f->texdef.shift[0] = atoi(token);
GetToken(false);
f->texdef.shift[1] = atoi(token);
GetToken(false);
f->texdef.rotate = atoi(token);
GetToken(false);
f->texdef.scale[0] = atof(token);
GetToken(false);
f->texdef.scale[1] = atof(token);
// the flags and value field aren't necessarily present
f->d_texture = Texture_ForName(f->texdef.name);
//
// FIXME: idMaterial f->texdef.flags = f->d_texture->flags; f->texdef.value =
// f->d_texture->value; f->texdef.contents = f->d_texture->contents;
//
if (TokenAvailable()) {
GetToken(false);
GetToken(false);
GetToken(false);
f->texdef.value = atoi(token);
}
} while (1);
return b;
}
/*
================
QERApp_MapPrintf_FILE
callback for surface properties plugin must fit a PFN_QERAPP_MAPPRINTF ( see isurfaceplugin.h )
carefully initialize !
================
*/
FILE *g_File;
void WINAPI QERApp_MapPrintf_FILE(char *text, ...) {
va_list argptr;
char buf[32768];
va_start(argptr, text);
vsprintf(buf, text, argptr);
va_end(argptr);
fprintf(g_File, buf);
}
/*
================
Brush_SetEpair
sets an epair for the given brush
================
*/
void Brush_SetEpair(brush_t *b, const char *pKey, const char *pValue) {
if (g_qeglobals.m_bBrushPrimitMode) {
if (b->pPatch) {
Patch_SetEpair(b->pPatch, pKey, pValue);
}
else {
b->epairs.Set(pKey, pValue);
}
}
else {
Sys_Status("Can only set key/values in Brush primitive mode\n");
}
}
/*
================
Brush_GetKeyValue
================
*/
const char *Brush_GetKeyValue(brush_t *b, const char *pKey) {
if (g_qeglobals.m_bBrushPrimitMode) {
if (b->pPatch) {
return Patch_GetKeyValue(b->pPatch, pKey);
}
else {
return b->epairs.GetString(pKey);
}
}
else {
Sys_Status("Can only set brush/patch key/values in Brush primitive mode\n");
}
return "";
}
/*
================
Brush_Write
save all brushes as Brush primitive format
================
*/
void Brush_Write(brush_t *b, FILE *f, const idVec3 &origin, bool newFormat) {
face_t *fa;
char *pname;
int i;
if (b->pPatch) {
Patch_Write(b->pPatch, f);
return;
}
if (g_qeglobals.m_bBrushPrimitMode) {
// save brush primitive format
if (newFormat) {
WriteFileString(f, "{\nbrushDef3\n{\n");
}
else {
WriteFileString(f, "{\nbrushDef\n{\n");
}
// brush epairs
int count = b->epairs.GetNumKeyVals();
for (int j = 0; j < count; j++) {
WriteFileString(f, "\"%s\" \"%s\"\n", b->epairs.GetKeyVal(j)->GetKey().c_str(), b->epairs.GetKeyVal(j)->GetValue().c_str());
}
for (fa = b->brush_faces; fa; fa = fa->next) {
// save planepts
if (newFormat) {
idPlane plane;
if (fa->dirty) {
fa->planepts[0] -= origin;
fa->planepts[1] -= origin;
fa->planepts[2] -= origin;
plane.FromPoints( fa->planepts[0], fa->planepts[1], fa->planepts[2], false );
fa->planepts[0] += origin;
fa->planepts[1] += origin;
fa->planepts[2] += origin;
} else {
plane = fa->originalPlane;
}
WriteFileString(f, " ( ");
for (i = 0; i < 4; i++) {
if (plane[i] == (int)plane[i]) {
WriteFileString(f, "%i ", (int)plane[i]);
}
else {
WriteFileString(f, "%f ", plane[i]);
}
}
WriteFileString(f, ") ");
}
else {
for (i = 0; i < 3; i++) {
WriteFileString(f, "( ");
for (int j = 0; j < 3; j++) {
if (fa->planepts[i][j] == static_cast<int>(fa->planepts[i][j])) {
WriteFileString(f, "%i ", static_cast<int>(fa->planepts[i][j]));
}
else {
WriteFileString(f, "%f ", fa->planepts[i][j]);
}
}
WriteFileString(f, ") ");
}
}
// save texture coordinates
WriteFileString(f, "( ( ");
for (i = 0; i < 3; i++) {
if (fa->brushprimit_texdef.coords[0][i] == static_cast<int>(fa->brushprimit_texdef.coords[0][i])) {
WriteFileString(f, "%i ", static_cast<int>(fa->brushprimit_texdef.coords[0][i]));
}
else {
WriteFileString(f, "%f ", fa->brushprimit_texdef.coords[0][i]);
}
}
WriteFileString(f, ") ( ");
for (i = 0; i < 3; i++) {
if (fa->brushprimit_texdef.coords[1][i] == static_cast<int>(fa->brushprimit_texdef.coords[1][i])) {
WriteFileString(f, "%i ", static_cast<int>(fa->brushprimit_texdef.coords[1][i]));
}
else {
WriteFileString(f, "%f ", fa->brushprimit_texdef.coords[1][i]);
}
}
WriteFileString(f, ") ) ");
char *pName = strlen(fa->texdef.name) > 0 ? fa->texdef.name : "notexture";
WriteFileString(f, "\"%s\" ", pName);
WriteFileString(f, "%i %i %i\n", 0, 0, 0);
}
WriteFileString(f, "}\n}\n");
}
else {
WriteFileString(f, "{\n");
for (fa = b->brush_faces; fa; fa = fa->next) {
for (i = 0; i < 3; i++) {
WriteFileString(f, "( ");
for (int j = 0; j < 3; j++) {
if (fa->planepts[i][j] == static_cast<int>(fa->planepts[i][j])) {
WriteFileString(f, "%i ", static_cast<int>(fa->planepts[i][j]));
}
else {
WriteFileString(f, "%f ", fa->planepts[i][j]);
}
}
WriteFileString(f, ") ");
}
pname = fa->texdef.name;
if (pname[0] == 0) {
pname = "unnamed";
}
WriteFileString
(
f,
"%s %i %i %i ",
pname,
(int)fa->texdef.shift[0],
(int)fa->texdef.shift[1],
(int)fa->texdef.rotate
);
if (fa->texdef.scale[0] == (int)fa->texdef.scale[0]) {
WriteFileString(f, "%i ", (int)fa->texdef.scale[0]);
}
else {
WriteFileString(f, "%f ", (float)fa->texdef.scale[0]);
}
if (fa->texdef.scale[1] == (int)fa->texdef.scale[1]) {
WriteFileString(f, "%i", (int)fa->texdef.scale[1]);
}
else {
WriteFileString(f, "%f", (float)fa->texdef.scale[1]);
}
WriteFileString(f, " %i %i %i",0, 0, 0);
WriteFileString(f, "\n");
}
WriteFileString(f, "}\n");
}
}
/*
================
QERApp_MapPrintf_MEMFILE
callback for surface properties plugin must fit a PFN_QERAPP_MAPPRINTF ( see isurfaceplugin.h )
carefully initialize !
================
*/
CMemFile *g_pMemFile;
void WINAPI QERApp_MapPrintf_MEMFILE(char *text, ...) {
va_list argptr;
char buf[32768];
va_start(argptr, text);
vsprintf(buf, text, argptr);
va_end(argptr);
MemFile_fprintf(g_pMemFile, buf);
}
/*
================
Brush_Write
save all brushes as Brush primitive format to a CMemFile*
================
*/
void Brush_Write(brush_t *b, CMemFile *pMemFile, const idVec3 &origin, bool newFormat) {
face_t *fa;
char *pname;
int i;
if (b->pPatch) {
Patch_Write(b->pPatch, pMemFile);
return;
}
if (g_qeglobals.m_bBrushPrimitMode) {
// brush primitive format
if (newFormat) {
MemFile_fprintf(pMemFile, "{\nBrushDef2\n{\n");
}
else {
MemFile_fprintf(pMemFile, "{\nBrushDef\n{\n");
}
// brush epairs
// brush epairs
int count = b->epairs.GetNumKeyVals();
for (int j = 0; j < count; j++) {
MemFile_fprintf(pMemFile, "\"%s\" \"%s\"\n", b->epairs.GetKeyVal(j)->GetKey().c_str(), b->epairs.GetKeyVal(j)->GetValue().c_str());
}
for (fa = b->brush_faces; fa; fa = fa->next) {
if (newFormat) {
// save planepts
idPlane plane;
if (fa->dirty) {
fa->planepts[0] -= origin;
fa->planepts[1] -= origin;
fa->planepts[2] -= origin;
plane.FromPoints( fa->planepts[0], fa->planepts[1], fa->planepts[2], false );
fa->planepts[0] += origin;
fa->planepts[1] += origin;
fa->planepts[2] += origin;
} else {
plane = fa->originalPlane;
}
MemFile_fprintf(pMemFile, " ( ");
for (i = 0; i < 4; i++) {
if (plane[i] == (int)plane[i]) {
MemFile_fprintf(pMemFile, "%i ", (int)plane[i]);
}
else {
MemFile_fprintf(pMemFile, "%f ", plane[i]);
}
}
MemFile_fprintf(pMemFile, ") ");
}
else {
for (i = 0; i < 3; i++) {
MemFile_fprintf(pMemFile, "( ");
for (int j = 0; j < 3; j++) {
if (fa->planepts[i][j] == static_cast<int>(fa->planepts[i][j])) {
MemFile_fprintf(pMemFile, "%i ", static_cast<int>(fa->planepts[i][j]));
}
else {
MemFile_fprintf(pMemFile, "%f ", fa->planepts[i][j]);
}
}
MemFile_fprintf(pMemFile, ") ");
}
}
// save texture coordinates
MemFile_fprintf(pMemFile, "( ( ");
for (i = 0; i < 3; i++) {
if (fa->brushprimit_texdef.coords[0][i] == static_cast<int>(fa->brushprimit_texdef.coords[0][i])) {
MemFile_fprintf(pMemFile, "%i ", static_cast<int>(fa->brushprimit_texdef.coords[0][i]));
}
else {
MemFile_fprintf(pMemFile, "%f ", fa->brushprimit_texdef.coords[0][i]);
}
}
MemFile_fprintf(pMemFile, ") ( ");
for (i = 0; i < 3; i++) {
if (fa->brushprimit_texdef.coords[1][i] == static_cast<int>(fa->brushprimit_texdef.coords[1][i])) {
MemFile_fprintf(pMemFile, "%i ", static_cast<int>(fa->brushprimit_texdef.coords[1][i]));
}
else {
MemFile_fprintf(pMemFile, "%f ", fa->brushprimit_texdef.coords[1][i]);
}
}
MemFile_fprintf(pMemFile, ") ) ");
// save texture attribs
char *pName = strlen(fa->texdef.name) > 0 ? fa->texdef.name : "unnamed";
MemFile_fprintf(pMemFile, "\"%s\" ", pName);
MemFile_fprintf(pMemFile, "%i %i %i\n", 0, 0, 0);
}
MemFile_fprintf(pMemFile, "}\n}\n");
}
else {
// old brushes format also handle surface properties plugin
MemFile_fprintf(pMemFile, "{\n");
for (fa = b->brush_faces; fa; fa = fa->next) {
for (i = 0; i < 3; i++) {
MemFile_fprintf(pMemFile, "( ");
for (int j = 0; j < 3; j++) {
if (fa->planepts[i][j] == static_cast<int>(fa->planepts[i][j])) {
MemFile_fprintf(pMemFile, "%i ", static_cast<int>(fa->planepts[i][j]));
}
else {
MemFile_fprintf(pMemFile, "%f ", fa->planepts[i][j]);
}
}
MemFile_fprintf(pMemFile, ") ");
}
pname = fa->texdef.name;
if (pname[0] == 0) {
pname = "unnamed";
}
MemFile_fprintf
(
pMemFile,
"%s %i %i %i ",
pname,
(int)fa->texdef.shift[0],
(int)fa->texdef.shift[1],
(int)fa->texdef.rotate
);
if (fa->texdef.scale[0] == (int)fa->texdef.scale[0]) {
MemFile_fprintf(pMemFile, "%i ", (int)fa->texdef.scale[0]);
}
else {
MemFile_fprintf(pMemFile, "%f ", (float)fa->texdef.scale[0]);
}
if (fa->texdef.scale[1] == (int)fa->texdef.scale[1]) {
MemFile_fprintf(pMemFile, "%i", (int)fa->texdef.scale[1]);
}
else {
MemFile_fprintf(pMemFile, "%f", (float)fa->texdef.scale[1]);
}
MemFile_fprintf(pMemFile, " %i %i %i", 0, 0, 0);
MemFile_fprintf(pMemFile, "\n");
}
MemFile_fprintf(pMemFile, "}\n");
}
}
/*
================
Brush_Create
Create non-textured blocks for entities The brush is NOT linked to any list
================
*/
brush_t *Brush_Create(idVec3 mins, idVec3 maxs, texdef_t *texdef) {
int i, j;
idVec3 pts[4][2];
face_t *f;
brush_t *b;
//
// brush primitive mode : convert texdef to brushprimit_texdef ? most of the time
// texdef is empty
//
for (i = 0; i < 3; i++) {
if (maxs[i] < mins[i]) {
Error("Brush_InitSolid: backwards");
}
}
b = Brush_Alloc();
pts[0][0][0] = mins[0];
pts[0][0][1] = mins[1];
pts[1][0][0] = mins[0];
pts[1][0][1] = maxs[1];
pts[2][0][0] = maxs[0];
pts[2][0][1] = maxs[1];
pts[3][0][0] = maxs[0];
pts[3][0][1] = mins[1];
for (i = 0; i < 4; i++) {
pts[i][0][2] = mins[2];
pts[i][1][0] = pts[i][0][0];
pts[i][1][1] = pts[i][0][1];
pts[i][1][2] = maxs[2];
}
for (i = 0; i < 4; i++) {
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
j = (i + 1) % 4;
VectorCopy(pts[j][1], f->planepts[0]);
VectorCopy(pts[i][1], f->planepts[1]);
VectorCopy(pts[i][0], f->planepts[2]);
}
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
VectorCopy(pts[0][1], f->planepts[0]);
VectorCopy(pts[1][1], f->planepts[1]);
VectorCopy(pts[2][1], f->planepts[2]);
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
VectorCopy(pts[2][0], f->planepts[0]);
VectorCopy(pts[1][0], f->planepts[1]);
VectorCopy(pts[0][0], f->planepts[2]);
return b;
}
/*
=============
Brush_Scale
=============
*/
void Brush_Scale(brush_t* b) {
for ( face_t *f = b->brush_faces; f; f = f->next ) {
for ( int i = 0; i < 3; i++ ) {
VectorScale( f->planepts[i], g_qeglobals.d_gridsize, f->planepts[i] );
}
}
}
/*
================
Brush_CreatePyramid
Create non-textured pyramid for light entities The brush is NOT linked to any list
================
*/
brush_t *Brush_CreatePyramid(idVec3 mins, idVec3 maxs, texdef_t *texdef) {
// ++timo handle new brush primitive ? return here ??
return Brush_Create(mins, maxs, texdef);
int i;
for (i = 0; i < 3; i++) {
if (maxs[i] < mins[i]) {
Error("Brush_InitSolid: backwards");
}
}
brush_t *b = Brush_Alloc();
idVec3 corners[4];
float fMid = idMath::Rint(mins[2] + (idMath::Rint((maxs[2] - mins[2]) / 2)));
corners[0][0] = mins[0];
corners[0][1] = mins[1];
corners[0][2] = fMid;
corners[1][0] = mins[0];
corners[1][1] = maxs[1];
corners[1][2] = fMid;
corners[2][0] = maxs[0];
corners[2][1] = maxs[1];
corners[2][2] = fMid;
corners[3][0] = maxs[0];
corners[3][1] = mins[1];
corners[3][2] = fMid;
idVec3 top, bottom;
top[0] = idMath::Rint(mins[0] + ((maxs[0] - mins[0]) / 2));
top[1] = idMath::Rint(mins[1] + ((maxs[1] - mins[1]) / 2));
top[2] = idMath::Rint(maxs[2]);
VectorCopy(top, bottom);
bottom[2] = mins[2];
// sides
for (i = 0; i < 4; i++) {
face_t *f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
int j = (i + 1) % 4;
VectorCopy(top, f->planepts[0]);
VectorCopy(corners[i], f->planepts[1]);
VectorCopy(corners[j], f->planepts[2]);
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
VectorCopy(bottom, f->planepts[2]);
VectorCopy(corners[i], f->planepts[1]);
VectorCopy(corners[j], f->planepts[0]);
}
return b;
}
/*
================
Brush_MakeSided
Makes the current brush have the given number of 2d sides
================
*/
void Brush_MakeSided(int sides) {
int i, axis;
idVec3 mins, maxs;
brush_t *b;
texdef_t *texdef;
face_t *f;
idVec3 mid;
float width;
float sv, cv;
if (sides < 3) {
Sys_Status("Bad sides number", 0);
return;
}
if (sides >= MAX_POINTS_ON_WINDING - 4) {
Sys_Status("too many sides.\n");
return;
}
if (!QE_SingleBrush()) {
Sys_Status("Must have a single brush selected", 0);
return;
}
b = selected_brushes.next;
VectorCopy(b->mins, mins);
VectorCopy(b->maxs, maxs);
texdef = &g_qeglobals.d_texturewin.texdef;
Brush_Free(b);
if (g_pParentWnd->ActiveXY()) {
switch (g_pParentWnd->ActiveXY()->GetViewType())
{
case XY:
axis = 2;
break;
case XZ:
axis = 1;
break;
case YZ:
axis = 0;
break;
}
}
else {
axis = 2;
}
// find center of brush
width = 8;
for (i = 0; i < 3; i++) {
mid[i] = (maxs[i] + mins[i]) * 0.5f;
if (i == axis) {
continue;
}
if ((maxs[i] - mins[i]) * 0.5f > width) {
width = (maxs[i] - mins[i]) * 0.5f;
}
}
b = Brush_Alloc();
// create top face
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
f->planepts[2][(axis + 1) % 3] = mins[(axis + 1) % 3];
f->planepts[2][(axis + 2) % 3] = mins[(axis + 2) % 3];
f->planepts[2][axis] = maxs[axis];
f->planepts[1][(axis + 1) % 3] = maxs[(axis + 1) % 3];
f->planepts[1][(axis + 2) % 3] = mins[(axis + 2) % 3];
f->planepts[1][axis] = maxs[axis];
f->planepts[0][(axis + 1) % 3] = maxs[(axis + 1) % 3];
f->planepts[0][(axis + 2) % 3] = maxs[(axis + 2) % 3];
f->planepts[0][axis] = maxs[axis];
// create bottom face
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
f->planepts[0][(axis + 1) % 3] = mins[(axis + 1) % 3];
f->planepts[0][(axis + 2) % 3] = mins[(axis + 2) % 3];
f->planepts[0][axis] = mins[axis];
f->planepts[1][(axis + 1) % 3] = maxs[(axis + 1) % 3];
f->planepts[1][(axis + 2) % 3] = mins[(axis + 2) % 3];
f->planepts[1][axis] = mins[axis];
f->planepts[2][(axis + 1) % 3] = maxs[(axis + 1) % 3];
f->planepts[2][(axis + 2) % 3] = maxs[(axis + 2) % 3];
f->planepts[2][axis] = mins[axis];
for (i = 0; i < sides; i++) {
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
sv = sin(i * 3.14159265 * 2 / sides);
cv = cos(i * 3.14159265 * 2 / sides);
f->planepts[0][(axis + 1) % 3] = floor(mid[(axis + 1) % 3] + width * cv + 0.5f);
f->planepts[0][(axis + 2) % 3] = floor(mid[(axis + 2) % 3] + width * sv + 0.5f);
f->planepts[0][axis] = mins[axis];
f->planepts[1][(axis + 1) % 3] = f->planepts[0][(axis + 1) % 3];
f->planepts[1][(axis + 2) % 3] = f->planepts[0][(axis + 2) % 3];
f->planepts[1][axis] = maxs[axis];
f->planepts[2][(axis + 1) % 3] = floor(f->planepts[0][(axis + 1) % 3] - width * sv + 0.5f);
f->planepts[2][(axis + 2) % 3] = floor(f->planepts[0][(axis + 2) % 3] + width * cv + 0.5f);
f->planepts[2][axis] = maxs[axis];
}
Brush_AddToList(b, &selected_brushes);
Entity_LinkBrush(world_entity, b);
Brush_Build(b);
Sys_UpdateWindows(W_ALL);
}
/*
================
Brush_Free
Frees the brush with all of its faces and display list.
Unlinks the brush from whichever chain it is in.
Decrements the owner entity's brushcount.
Removes owner entity if this was the last brush unless owner is the world.
Removes from groups
set bRemoveNode to false to avoid trying to delete the item in group view tree control
================
*/
void Brush_Free(brush_t *b, bool bRemoveNode) {
face_t *f, *next;
// free the patch if it's there
if ( b->pPatch ) {
Patch_Delete(b->pPatch);
}
// free faces
for ( f = b->brush_faces; f; f = next ) {
next = f->next;
Face_Free(f);
}
b->epairs.Clear();
// unlink from active/selected list
if ( b->next ) {
Brush_RemoveFromList(b);
}
// unlink from entity list
if ( b->onext ) {
Entity_UnlinkBrush(b);
}
delete b;
}
/*
================
Face_MemorySize
returns the size in memory of the face
================
*/
int Face_MemorySize(face_t *f) {
int size = 0;
if ( f->face_winding ) {
size += sizeof( idWinding ) + f->face_winding->GetNumPoints() * sizeof( (f->face_winding)[0] );
}
size += sizeof( face_t );
return size;
}
/*
================
Brush_MemorySize
returns the size in memory of the brush
================
*/
int Brush_MemorySize( brush_t *b ) {
face_t *f;
int size = 0;
if ( b->pPatch ) {
size += Patch_MemorySize( b->pPatch );
}
for ( f = b->brush_faces; f; f = f->next ) {
size += Face_MemorySize(f);
}
size += sizeof( brush_t ) + b->epairs.Size();
return size;
}
/*
================
Brush_Clone
does not add the brush to any lists
================
*/
brush_t *Brush_Clone(brush_t *b) {
brush_t *n = NULL;
face_t *f, *nf;
if (b->pPatch) {
patchMesh_t *p = Patch_Duplicate(b->pPatch);
Brush_RemoveFromList(p->pSymbiot);
Entity_UnlinkBrush(p->pSymbiot);
n = p->pSymbiot;
}
else {
n = Brush_Alloc();
n->numberId = g_nBrushId++;
n->owner = b->owner;
n->lightColor = b->lightColor;
n->lightEnd = b->lightEnd;
n->lightOffset = b->lightOffset;
n->lightRadius = b->lightRadius;
n->lightRight = b->lightRight;
n->lightStart = b->lightStart;
n->lightTarget = b->lightTarget;
n->lightCenter = b->lightCenter;
n->lightTexture = b->lightTexture;
n->lightUp = b->lightUp;
n->modelHandle = b->modelHandle;
n->pointLight = b->pointLight;
for (f = b->brush_faces; f; f = f->next) {
nf = Face_Clone(f);
nf->next = n->brush_faces;
n->brush_faces = nf;
}
}
return n;
}
/*
================
Brush_FullClone
Used by Undo.
Makes an exact copy of the brush.
Does NOT add the new brush to any lists.
================
*/
brush_t *Brush_FullClone(brush_t *b) {
brush_t *n = NULL;
face_t *f, *nf, *f2, *nf2;
int j;
if (b->pPatch) {
patchMesh_t *p = Patch_Duplicate(b->pPatch);
Brush_RemoveFromList(p->pSymbiot);
Entity_UnlinkBrush(p->pSymbiot);
n = p->pSymbiot;
n->owner = b->owner;
Brush_Build(n);
}
else {
n = Brush_Alloc();
n->numberId = g_nBrushId++;
n->owner = b->owner;
n->lightColor = b->lightColor;
n->lightEnd = b->lightEnd;
n->lightOffset = b->lightOffset;
n->lightRadius = b->lightRadius;
n->lightRight = b->lightRight;
n->lightStart = b->lightStart;
n->lightTarget = b->lightTarget;
n->lightCenter = b->lightCenter;
n->lightTexture = b->lightTexture;
n->lightUp = b->lightUp;
n->modelHandle = b->modelHandle;
n->pointLight = b->pointLight;
VectorCopy(b->mins, n->mins);
VectorCopy(b->maxs, n->maxs);
for (f = b->brush_faces; f; f = f->next) {
if (f->original) {
continue;
}
nf = Face_FullClone(f);
nf->next = n->brush_faces;
n->brush_faces = nf;
// copy all faces that have the original set to this face
for (f2 = b->brush_faces; f2; f2 = f2->next) {
if (f2->original == f) {
nf2 = Face_FullClone(f2);
nf2->next = n->brush_faces;
n->brush_faces = nf2;
// set original
nf2->original = nf;
}
}
}
for (nf = n->brush_faces; nf; nf = nf->next) {
Face_SetColor( n, nf, 1.0f );
if (nf->face_winding) {
if (g_qeglobals.m_bBrushPrimitMode) {
EmitBrushPrimitTextureCoordinates(nf, nf->face_winding);
}
else {
for (j = 0; j < nf->face_winding->GetNumPoints(); j++) {
EmitTextureCoordinates( (*nf->face_winding)[j], nf->d_texture, nf );
}
}
}
}
}
return n;
}
extern bool GetMatrixForKey(entity_t *ent, const char *key, idMat3 &mat);
extern bool Patch_Intersect(patchMesh_t *pm, idVec3 origin, idVec3 direction , float &scale);
extern bool RayIntersectsTri
(
const idVec3 &origin,
const idVec3 &direction,
const idVec3 &vert0,
const idVec3 &vert1,
const idVec3 &vert2,
float &scale
);
/*
================
RotateVector
================
*/
void RotateVector(idVec3 &v, idVec3 origin, float a, float c, float s) {
float x = v[0];
float y = v[1];
if (a) {
float x2 = (((x - origin[0]) * c) - ((y - origin[1]) * s)) + origin[0];
float y2 = (((x - origin[0]) * s) + ((y - origin[1]) * c)) + origin[1];
x = x2;
y = y2;
}
v[0] = x;
v[1] = y;
}
/*
================
Brush_ModelIntersect
================
*/
bool Brush_ModelIntersect(brush_t *b, idVec3 origin, idVec3 dir,float &scale) {
idRenderModel *model = b->modelHandle;
idRenderModel *md5;
if ( !model )
model = b->owner->eclass->entityModel;
scale = 0;
if (model) {
if ( model->IsDynamicModel() != DM_STATIC ) {
if ( dynamic_cast<idRenderModelMD5 *>( model ) ) {
// take care of animated models
md5 = b->owner->eclass->entityModel;
const char *classname = ValueForKey( b->owner, "classname" );
if (stricmp(classname, "func_static") == 0) {
classname = ValueForKey(b->owner, "animclass");
}
const char *anim = ValueForKey( b->owner, "anim" );
int frame = IntForKey( b->owner, "frame" ) + 1;
if ( frame < 1 ) {
frame = 1;
}
if ( !anim || !anim[ 0 ] ) {
anim = "idle";
}
model = gameEdit->ANIM_CreateMeshForAnim( md5, classname, anim, frame, false );
if ( !model ) {
model = renderModelManager->DefaultModel();
}
}
}
bool matrix = false;
idMat3 mat;
float a, s, c;
if (GetMatrixForKey(b->owner, "rotation", mat)) {
matrix = true;
} else {
a = FloatForKey(b->owner, "angle");
if (a) {
s = sin( DEG2RAD( a ) );
c = cos( DEG2RAD( a ) );
}
else {
s = c = 0;
}
}
for (int i = 0; i < model->NumSurfaces() ; i++) {
const modelSurface_t *surf = model->Surface( i );
srfTriangles_t *tri = surf->geometry;
for (int j = 0; j < tri->numIndexes; j += 3) {
idVec3 v1, v2, v3;
v1 = tri->verts[tri->indexes[j]].xyz;
v2 = tri->verts[tri->indexes[j + 1]].xyz;
v3 = tri->verts[tri->indexes[j + 2]].xyz;
if (matrix) {
v1 *= b->owner->rotation;
v1 += b->owner->origin;
v2 *= b->owner->rotation;
v2 += b->owner->origin;
v3 *= b->owner->rotation;
v3 += b->owner->origin;
} else {
v1 += b->owner->origin;
v2 += b->owner->origin;
v3 += b->owner->origin;
RotateVector(v1, b->owner->origin, a, c, s);
RotateVector(v2, b->owner->origin, a, c, s);
RotateVector(v3, b->owner->origin, a, c, s);
}
if (RayIntersectsTri(origin, dir, v1, v2, v3,scale)) {
return true;
}
}
}
}
return false;
}
face_t *Brush_Ray(idVec3 origin, idVec3 dir, brush_t *b, float *dist, bool testPrimitive) {
face_t *f, *firstface = NULL;
idVec3 p1, p2;
float frac, d1, d2;
int i;
float scale = HUGE_DISTANCE * 2;
VectorCopy(origin, p1);
for (i = 0; i < 3; i++) {
p2[i] = p1[i] + dir[i] * HUGE_DISTANCE * 2;
}
for (f = b->brush_faces; f; f = f->next) {
d1 = DotProduct(p1, f->plane) + f->plane[3];
d2 = DotProduct(p2, f->plane) + f->plane[3];
if (d1 >= 0 && d2 >= 0) {
*dist = 0;
return NULL; // ray is on front side of face
}
if (d1 <= 0 && d2 <= 0) {
continue;
}
// clip the ray to the plane
frac = d1 / (d1 - d2);
if (d1 > 0) {
firstface = f;
for (i = 0; i < 3; i++) {
p1[i] = p1[i] + frac * (p2[i] - p1[i]);
}
}
else {
for (i = 0; i < 3; i++) {
p2[i] = p1[i] + frac * (p2[i] - p1[i]);
}
}
}
// find distance p1 is along dir
VectorSubtract(p1, origin, p1);
d1 = DotProduct(p1, dir);
if (testPrimitive && !g_PrefsDlg.m_selectByBoundingBrush) {
if (b->pPatch) {
if (!Patch_Intersect(b->pPatch, origin, dir, scale)) {
*dist = 0;
return NULL;
}
}
else if ( b->modelHandle != NULL && dynamic_cast<idRenderModelPrt*>( b->modelHandle ) == NULL && dynamic_cast< idRenderModelLiquid*> ( b->modelHandle ) == NULL ) {
if (!Brush_ModelIntersect(b, origin, dir, scale)) {
*dist = 0;
return NULL;
}
}
}
*dist = d1;
return firstface;
}
/*
================
Brush_Point
================
*/
face_t *Brush_Point(idVec3 origin, brush_t *b) {
face_t *f;
float d1;
for (f = b->brush_faces; f; f = f->next) {
d1 = DotProduct(origin, f->plane) + f->plane[3];
if (d1 > 0) {
return NULL; // point is on front side of face
}
}
return b->brush_faces;
}
/*
================
Brush_AddToList
================
*/
void Brush_AddToList(brush_t *b, brush_t *list) {
if (b->next || b->prev) {
Error("Brush_AddToList: allready linked");
}
if (list == &selected_brushes || list == &active_brushes) {
if (b->pPatch && list == &selected_brushes) {
Patch_Select(b->pPatch);
}
}
b->list = list;
b->next = list->next;
list->next->prev = b;
list->next = b;
b->prev = list;
}
/*
================
Brush_RemoveFromList
================
*/
void Brush_RemoveFromList(brush_t *b) {
if (!b->next || !b->prev) {
Error("Brush_RemoveFromList: not linked");
}
if (b->pPatch) {
Patch_Deselect(b->pPatch);
// Patch_Deselect(b->nPatchID);
}
b->list = NULL;
b->next->prev = b->prev;
b->prev->next = b->next;
b->next = b->prev = NULL;
}
/*
================
SetFaceTexdef
Doesn't set the curve flags.
NOTE: never trust f->d_texture here, f->texdef and f->d_texture are out of sync when
called by Brush_SetTexture use Texture_ForName() to find the right shader
FIXME: send the right shader ( qtexture_t * ) in the parameters ?
TTimo: surface plugin, added an IPluginTexdef* parameter if not NULL,
get ->Copy() of it into the face ( and remember to hook ) if NULL, ask for a default
================
*/
void SetFaceTexdef( brush_t *b, face_t *f, texdef_t *texdef, brushprimit_texdef_t *brushprimit_texdef, bool bFitScale ) {
if (g_qeglobals.m_bBrushPrimitMode) {
f->texdef = *texdef;
ConvertTexMatWithQTexture(brushprimit_texdef, NULL, &f->brushprimit_texdef, Texture_ForName(f->texdef.name));
}
else if (bFitScale) {
f->texdef = *texdef;
// fit the scaling of the texture on the actual plane
idVec3 p1, p2, p3; // absolute coordinates
// compute absolute coordinates
ComputeAbsolute(f, p1, p2, p3);
// compute the scale
idVec3 vx, vy;
VectorSubtract(p2, p1, vx);
vx.Normalize();
VectorSubtract(p3, p1, vy);
vy.Normalize();
// assign scale
VectorScale(vx, texdef->scale[0], vx);
VectorScale(vy, texdef->scale[1], vy);
VectorAdd(p1, vx, p2);
VectorAdd(p1, vy, p3);
// compute back shift scale rot
AbsoluteToLocal(f->plane, f, p1, p2, p3);
}
else {
f->texdef = *texdef;
}
}
/*
================
Brush_SetTexture
================
*/
void Brush_SetTexture(brush_t *b, texdef_t *texdef, brushprimit_texdef_t *brushprimit_texdef, bool bFitScale) {
if (b->pPatch) {
Patch_SetTexture(b->pPatch, texdef);
}
else {
for (face_t * f = b->brush_faces; f; f = f->next) {
SetFaceTexdef(b, f, texdef, brushprimit_texdef, bFitScale);
}
Brush_Build(b);
}
}
/*
====================
Brush_SetTextureName
====================
*/
void Brush_SetTextureName(brush_t *b, const char *name) {
if (b->pPatch) {
Patch_SetTextureName(b->pPatch, name);
}
else {
for (face_t * f = b->brush_faces; f; f = f->next) {
f->texdef.SetName(name);
}
Brush_Build(b);
}
}
/*
================
ClipLineToFace
================
*/
bool ClipLineToFace(idVec3 &p1, idVec3 &p2, face_t *f) {
float d1, d2, fr;
int i;
float *v;
d1 = DotProduct(p1, f->plane) + f->plane[3];
d2 = DotProduct(p2, f->plane) + f->plane[3];
if (d1 >= 0 && d2 >= 0) {
return false; // totally outside
}
if (d1 <= 0 && d2 <= 0) {
return true; // totally inside
}
fr = d1 / (d1 - d2);
if (d1 > 0) {
v = p1.ToFloatPtr();
}
else {
v = p2.ToFloatPtr();
}
for (i = 0; i < 3; i++) {
v[i] = p1[i] + fr * (p2[i] - p1[i]);
}
return true;
}
/*
================
AddPlanept
================
*/
int AddPlanept(idVec3 *f) {
int i;
for (i = 0; i < g_qeglobals.d_num_move_points; i++) {
if (g_qeglobals.d_move_points[i] == f) {
return 0;
}
}
if (g_qeglobals.d_num_move_points < MAX_MOVE_POINTS) {
g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = f;
} else {
Sys_Status("Trying to move too many points\n");
return 0;
}
return 1;
}
/*
================
AddMovePlane
================
*/
void AddMovePlane( idPlane *p ) {
for (int i = 0; i < g_qeglobals.d_num_move_planes; i++) {
if (g_qeglobals.d_move_planes[i] == p) {
return;
}
}
if (g_qeglobals.d_num_move_planes < MAX_MOVE_PLANES) {
g_qeglobals.d_move_planes[g_qeglobals.d_num_move_planes++] = p;
} else {
Sys_Status("Trying to move too many planes\n");
}
}
/*
================
Brush_SelectFaceForDragging
Adds the faces planepts to move_points, and rotates and adds the planepts of adjacent face if shear is set
================
*/
void Brush_SelectFaceForDragging(brush_t *b, face_t *f, bool shear) {
int i;
face_t *f2;
idWinding *w;
float d;
brush_t *b2;
int c;
if (b->owner->eclass->fixedsize || EntityHasModel(b->owner)) {
return;
}
c = 0;
for (i = 0; i < 3; i++) {
c += AddPlanept(&f->planepts[i]);
}
//AddMovePlane(&f->plane);
if (c == 0) {
return; // allready completely added
}
// select all points on this plane in all brushes the selection
for (b2 = selected_brushes.next; b2 != &selected_brushes; b2 = b2->next) {
if (b2 == b) {
continue;
}
for (f2 = b2->brush_faces; f2; f2 = f2->next) {
for (i = 0; i < 3; i++) {
if (idMath::Fabs(DotProduct(f2->planepts[i], f->plane) + f->plane[3]) > ON_EPSILON) {
break;
}
}
if (i == 3) { // move this face as well
Brush_SelectFaceForDragging(b2, f2, shear);
break;
}
}
}
//
// if shearing, take all the planes adjacent to selected faces and rotate their
// points so the edge clipped by a selcted face has two of the points
//
if (!shear) {
return;
}
for (f2 = b->brush_faces; f2; f2 = f2->next) {
if (f2 == f) {
continue;
}
w = Brush_MakeFaceWinding(b, f2, false);
if (!w) {
continue;
}
// any points on f will become new control points
for (i = 0; i < w->GetNumPoints(); i++) {
d = DotProduct( (*w)[i], f->plane ) + f->plane[3];
if (d > -ON_EPSILON && d < ON_EPSILON) {
break;
}
}
// if none of the points were on the plane, leave it alone
if (i != w->GetNumPoints()) {
if (i == 0) { // see if the first clockwise point was the
///
/// last point on the winding
d = DotProduct( (*w)[w->GetNumPoints() - 1], f->plane ) + f->plane[3];
if (d > -ON_EPSILON && d < ON_EPSILON) {
i = w->GetNumPoints() - 1;
}
}
AddPlanept(&f2->planepts[0]);
//AddMovePlane(&f2->plane);
VectorCopy((*w)[i], f2->planepts[0]);
if (++i == w->GetNumPoints()) {
i = 0;
}
// see if the next point is also on the plane
d = DotProduct( (*w)[i], f->plane ) + f->plane[3];
if (d > -ON_EPSILON && d < ON_EPSILON) {
AddPlanept(&f2->planepts[1]);
}
VectorCopy( (*w)[i], f2->planepts[1] );
if (++i == w->GetNumPoints()) {
i = 0;
}
// the third point is never on the plane
VectorCopy( (*w)[i], f2->planepts[2] );
}
delete w;
}
}
/*
================
Brush_SideSelect
The mouse click did not hit the brush, so grab one or more side planes for dragging.
================
*/
void Brush_SideSelect(brush_t *b, idVec3 origin, idVec3 dir, bool shear) {
face_t *f, *f2;
idVec3 p1, p2;
if (g_moveOnly) {
return;
}
// if (b->pPatch) return; Patch_SideSelect(b->nPatchID, origin, dir);
for (f = b->brush_faces; f; f = f->next) {
VectorCopy(origin, p1);
VectorMA(origin, MAX_WORLD_SIZE, dir, p2);
for (f2 = b->brush_faces; f2; f2 = f2->next) {
if (f2 == f) {
continue;
}
ClipLineToFace(p1, p2, f2);
}
if (f2) {
continue;
}
if ( p1.Compare( origin ) ) {
continue;
}
if (ClipLineToFace(p1, p2, f)) {
continue;
}
Brush_SelectFaceForDragging(b, f, shear);
}
}
extern void UpdateSelectablePoint(brush_t *b, idVec3 v, int type);
extern void AddSelectablePoint(brush_t *b, idVec3 v, int type, bool priority);
extern void ClearSelectablePoints(brush_t *b);
/*
================
Brush_TransformedPoint
================
*/
extern void VectorSnapGrid(idVec3 &v);
idMat3 Brush_RotationMatrix(brush_t *b) {
idMat3 mat;
mat.Identity();
if (!GetMatrixForKey(b->owner, "light_rotation", mat)) {
GetMatrixForKey(b->owner, "rotation", mat);
}
return mat;
}
idVec3 Brush_TransformedPoint(brush_t *b, const idVec3 &in) {
idVec3 out = in;
out -= b->owner->origin;
out *= Brush_RotationMatrix(b);
out += b->owner->origin;
return out;
}
/*
================
Brush_UpdateLightPoints
================
*/
void Brush_UpdateLightPoints(brush_t *b, const idVec3 &offset) {
if (!(b->owner->eclass->nShowFlags & ECLASS_LIGHT)) {
if (b->modelHandle) {
g_bScreenUpdates = false;
g_pParentWnd->GetCamera()->BuildEntityRenderState(b->owner, true);
g_bScreenUpdates = true;
}
return;
}
if (b->entityModel) {
return;
}
idVec3 vCenter;
idVec3 *origin = (b->trackLightOrigin) ? &b->owner->lightOrigin : &b->owner->origin;
if (!GetVectorForKey(b->owner, "_color", b->lightColor)) {
b->lightColor[0] = b->lightColor[1] = b->lightColor[2] = 1;
}
const char *str = ValueForKey(b->owner, "texture");
b->lightTexture = -1;
if (str && strlen(str) > 0) {
const idMaterial *q = Texture_LoadLight(str);
if (q) {
b->lightTexture = q->GetEditorImage()->texnum;
}
}
str = ValueForKey(b->owner, "light_right");
if (str && *str) {
idVec3 vRight, vUp, vTarget, vTemp;
if (GetVectorForKey(b->owner, "light_start", b->lightStart)) {
b->startEnd = true;
if (!GetVectorForKey(b->owner, "light_end", b->lightEnd)) {
GetVectorForKey(b->owner, "light_target", b->lightEnd);
}
VectorAdd(b->lightEnd, *origin, b->lightEnd);
VectorAdd(b->lightStart, *origin, b->lightStart);
VectorAdd(b->lightStart, offset, b->lightStart);
}
else {
b->startEnd = false;
}
GetVectorForKey(b->owner, "light_right", vRight);
GetVectorForKey(b->owner, "light_up", vUp);
GetVectorForKey(b->owner, "light_target", vTarget);
if (offset.x || offset.y || offset.z) {
CString str;
VectorAdd(vTarget, offset, vTarget);
SetKeyVec3(b->owner, "light_target", vTarget);
}
VectorAdd(vTarget, *origin, b->lightTarget);
VectorAdd(b->lightTarget, vRight, b->lightRight);
VectorAdd(b->lightTarget, vUp, b->lightUp);
UpdateSelectablePoint(b, Brush_TransformedPoint(b, b->lightUp), LIGHT_UP);
UpdateSelectablePoint(b, Brush_TransformedPoint(b, b->lightRight), LIGHT_RIGHT);
UpdateSelectablePoint(b, Brush_TransformedPoint(b, b->lightTarget), LIGHT_TARGET);
UpdateSelectablePoint(b, Brush_TransformedPoint(b, b->lightStart), LIGHT_START);
UpdateSelectablePoint(b, Brush_TransformedPoint(b, b->lightEnd), LIGHT_END);
b->pointLight = false;
}
else {
b->pointLight = true;
if (GetVectorForKey(b->owner, "light_center", vCenter)) {
if (offset.x || offset.y || offset.z) {
CString str;
VectorAdd(vCenter, offset, vCenter);
SetKeyVec3(b->owner, "light_center", vCenter);
}
VectorAdd(vCenter, *origin, b->lightCenter);
UpdateSelectablePoint(b, b->lightCenter, LIGHT_CENTER);
}
if (!GetVectorForKey(b->owner, "light_radius", b->lightRadius)) {
float f = FloatForKey(b->owner, "light");
if (f == 0) {
f = 300;
}
b->lightRadius[0] = b->lightRadius[1] = b->lightRadius[2] = f;
}
else {
}
}
g_bScreenUpdates = false;
g_pParentWnd->GetCamera()->BuildEntityRenderState(b->owner, true);
g_bScreenUpdates = true;
}
/*
================
Brush_BuildWindings
================
*/
void Brush_BuildWindings(brush_t *b, bool bSnap, bool keepOnPlaneWinding, bool updateLights, bool makeFacePlanes) {
idWinding *w;
face_t *face;
float v;
// clear the mins/maxs bounds
b->mins[0] = b->mins[1] = b->mins[2] = 999999;
b->maxs[0] = b->maxs[1] = b->maxs[2] = -999999;
if (makeFacePlanes) {
Brush_MakeFacePlanes(b);
}
face = b->brush_faces;
float fCurveColor = 1.0f;
for (; face; face = face->next) {
int i, j;
delete face->face_winding;
w = face->face_winding = Brush_MakeFaceWinding(b, face, keepOnPlaneWinding);
face->d_texture = Texture_ForName(face->texdef.name);
if (!w) {
continue;
}
for (i = 0; i < w->GetNumPoints(); i++) {
// add to bounding box
for (j = 0; j < 3; j++) {
v = (*w)[i][j];
if (v > b->maxs[j]) {
b->maxs[j] = v;
}
if (v < b->mins[j]) {
b->mins[j] = v;
}
}
}
// setup s and t vectors, and set color if (!g_PrefsDlg.m_bGLLighting) {
if (makeFacePlanes) {
Face_SetColor(b, face, fCurveColor);
// }
fCurveColor -= 0.1f;
if ( fCurveColor <= 0.0f ) {
fCurveColor = 1.0f;
}
// computing ST coordinates for the windings
if (g_qeglobals.m_bBrushPrimitMode) {
if (g_qeglobals.bNeedConvert) {
//
// we have parsed old brushes format and need conversion convert old brush texture
// representation to new format
//
FaceToBrushPrimitFace(face);
#ifdef _DEBUG
// use old texture coordinates code to check against
for (i = 0; i < w->GetNumPoints(); i++) {
EmitTextureCoordinates((*w)[i], face->d_texture, face);
}
#endif
}
//
// use new texture representation to compute texture coordinates in debug mode we
// will check against old code and warn if there are differences
//
EmitBrushPrimitTextureCoordinates(face, w);
}
else {
for (i = 0; i < w->GetNumPoints(); i++) {
EmitTextureCoordinates((*w)[i], face->d_texture, face);
}
}
}
}
if (updateLights) {
idVec3 offset;
offset.Zero();
Brush_UpdateLightPoints(b, offset);
}
}
/*
================
Brush_RemoveEmptyFaces
Frees any overconstraining faces
================
*/
void Brush_RemoveEmptyFaces(brush_t *b) {
face_t *f, *next;
f = b->brush_faces;
b->brush_faces = NULL;
for (; f; f = next) {
next = f->next;
if (!f->face_winding) {
Face_Free(f);
}
else {
f->next = b->brush_faces;
b->brush_faces = f;
}
}
}
/*
================
Brush_SnapToGrid
================
*/
void Brush_SnapToGrid(brush_t *pb) {
int i;
for (face_t * f = pb->brush_faces; f; f = f->next) {
idWinding *w = f->face_winding;
if (!w) {
continue; // freed face
}
for (i = 0; i < w->GetNumPoints(); i++) {
SnapVectorToGrid( (*w)[i].ToVec3() );
}
for (i = 0; i < 3; i++) {
f->planepts[i].x = (*w)[i].x;
f->planepts[i].y = (*w)[i].y;
f->planepts[i].z = (*w)[i].z;
}
}
idVec3 v;
idStr str;
if (GetVectorForKey(pb->owner, "origin", v)) {
SnapVectorToGrid(pb->owner->origin);
sprintf(str, "%i %i %i", (int)pb->owner->origin.x, (int)pb->owner->origin.y, (int)pb->owner->origin.z);
SetKeyValue(pb->owner, "origin", str);
}
if (pb->owner->eclass->nShowFlags & ECLASS_LIGHT) {
if (GetVectorForKey(pb->owner, "light_right", v)) {
// projected
SnapVectorToGrid(v);
pb->lightRight = v;
SetKeyVec3(pb->owner, "light_right", v);
GetVectorForKey(pb->owner, "light_up", v);
SnapVectorToGrid(v);
pb->lightUp = v;
SetKeyVec3(pb->owner, "light_up", v);
GetVectorForKey(pb->owner, "light_target", v);
SnapVectorToGrid(v);
pb->lightTarget = v;
SetKeyVec3(pb->owner, "light_target", v);
if (GetVectorForKey(pb->owner, "light_start", v)) {
SnapVectorToGrid(v);
pb->lightStart = v;
SetKeyVec3(pb->owner, "light_start", v);
GetVectorForKey(pb->owner, "light_end", v);
SnapVectorToGrid(v);
pb->lightEnd = v;
SetKeyVec3(pb->owner, "light_end", v);
}
} else {
// point
if (GetVectorForKey(pb->owner, "light_center", v)) {
SnapVectorToGrid(v);
SetKeyVec3(pb->owner, "light_center", v);
}
}
}
if ( pb->owner->curve ) {
int c = pb->owner->curve->GetNumValues();
for ( i = 0; i < c; i++ ) {
v = pb->owner->curve->GetValue( i );
SnapVectorToGrid( v );
pb->owner->curve->SetValue( i, v );
}
}
Brush_Build(pb);
}
/*
================
Brush_Rotate
================
*/
void Brush_Rotate(brush_t *b, idMat3 matrix, idVec3 origin, bool bBuild) {
for (face_t * f = b->brush_faces; f; f = f->next) {
for (int i = 0; i < 3; i++) {
f->planepts[i] -= origin;
f->planepts[i] *= matrix;
f->planepts[i] += origin;
}
}
if (bBuild) {
Brush_Build(b, false, false);
}
}
extern void VectorRotate3Origin( const idVec3 &vIn, const idVec3 &vRotation, const idVec3 &vOrigin, idVec3 &out );
/*
================
Brush_Rotate
================
*/
void Brush_Rotate(brush_t *b, idVec3 vAngle, idVec3 vOrigin, bool bBuild) {
for (face_t * f = b->brush_faces; f; f = f->next) {
for (int i = 0; i < 3; i++) {
VectorRotate3Origin(f->planepts[i], vAngle, vOrigin, f->planepts[i]);
}
}
if (bBuild) {
Brush_Build(b, false, false);
}
}
/*
================
Brush_Center
================
*/
void Brush_Center(brush_t *b, idVec3 vNewCenter) {
idVec3 vMid;
// get center of the brush
for (int j = 0; j < 3; j++) {
vMid[j] = b->mins[j] + abs((b->maxs[j] - b->mins[j]) * 0.5f);
}
// calc distance between centers
VectorSubtract(vNewCenter, vMid, vMid);
Brush_Move(b, vMid, true);
}
/*
================
Brush_Resize
the brush must be a true axial box
================
*/
void Brush_Resize( brush_t *b, idVec3 vMin, idVec3 vMax ) {
int i, j;
face_t *f;
assert( vMin[0] < vMax[0] && vMin[1] < vMax[1] && vMin[2] < vMax[2] );
Brush_MakeFacePlanes( b );
for( f = b->brush_faces; f; f = f->next ) {
for ( i = 0; i < 3; i++ ) {
if ( f->plane.Normal()[i] >= 0.999f ) {
for ( j = 0; j < 3; j++ ) {
f->planepts[j][i] = vMax[i];
}
break;
}
if ( f->plane.Normal()[i] <= -0.999f ) {
for ( j = 0; j < 3; j++ ) {
f->planepts[j][i] = vMin[i];
}
break;
}
}
//assert( i < 3 );
}
Brush_Build( b, true );
}
/*
================
HasModel
================
*/
eclass_t *HasModel(brush_t *b) {
idVec3 vMin, vMax;
vMin[0] = vMin[1] = vMin[2] = 999999;
vMax[0] = vMax[1] = vMax[2] = -999999;
if (b->owner->md3Class != NULL) {
return b->owner->md3Class;
}
if (b->owner->eclass->modelHandle > 0) {
return b->owner->eclass;
}
eclass_t *e = NULL;
// FIXME: entity needs to track whether a cache hit failed and not ask again
if (b->owner->eclass->nShowFlags & ECLASS_MISCMODEL) {
const char *pModel = ValueForKey(b->owner, "model");
if (pModel != NULL && strlen(pModel) > 0) {
e = GetCachedModel(b->owner, pModel, vMin, vMax);
if (e != NULL) {
//
// we need to scale the brush to the proper size based on the model load recreate
// brush just like in load/save
//
VectorAdd(vMin, b->owner->origin, vMin);
VectorAdd(vMax, b->owner->origin, vMax);
Brush_Resize(b, vMin, vMax);
b->bModelFailed = false;
}
else {
b->bModelFailed = true;
}
}
}
return e;
}
/*
================
Entity_GetRotationMatrixAngles
================
*/
bool Entity_GetRotationMatrixAngles( entity_t *e, idMat3 &mat, idAngles &angles ) {
int angle;
/* the angle keyword is a yaw value, except for two special markers */
if ( GetMatrixForKey( e, "rotation", mat ) ) {
angles = mat.ToAngles();
return true;
}
else if ( e->epairs.GetInt( "angle", "0", angle ) ) {
if ( angle == -1 ) { // up
angles.Set( 270, 0, 0 );
}
else if ( angle == -2 ) { // down
angles.Set( 90, 0, 0 );
}
else {
angles.Set( 0, angle, 0 );
}
mat = angles.ToMat3();
return true;
}
else {
mat.Identity();
angles.Zero();
return false;
}
}
/*
================
FacingVectors
================
*/
static void FacingVectors(entity_t *e, idVec3 &forward, idVec3 &right, idVec3 &up) {
idAngles angles;
idMat3 mat;
Entity_GetRotationMatrixAngles(e, mat, angles);
angles.ToVectors( &forward, &right, &up);
}
/*
================
Brush_DrawFacingAngle
================
*/
void Brush_DrawFacingAngle( brush_t *b, entity_t *e, bool particle ) {
idVec3 forward, right, up;
idVec3 endpoint, tip1, tip2;
idVec3 start;
float dist;
VectorAdd(e->brushes.onext->mins, e->brushes.onext->maxs, start);
VectorScale(start, 0.5f, start);
dist = (b->maxs[0] - start[0]) * 2.5f;
FacingVectors(e, forward, right, up);
VectorMA(start, dist, ( particle ) ? up : forward, endpoint);
dist = (b->maxs[0] - start[0]) * 0.5f;
VectorMA(endpoint, -dist, ( particle ) ? up : forward, tip1);
VectorMA(tip1, -dist, ( particle ) ? forward : up, tip1);
VectorMA(tip1, 2 * dist, ( particle ) ? forward : up, tip2);
globalImages->BindNull();
qglColor4f(1, 1, 1, 1);
qglLineWidth(2);
qglBegin(GL_LINES);
qglVertex3fv(start.ToFloatPtr());
qglVertex3fv(endpoint.ToFloatPtr());
qglVertex3fv(endpoint.ToFloatPtr());
qglVertex3fv(tip1.ToFloatPtr());
qglVertex3fv(endpoint.ToFloatPtr());
qglVertex3fv(tip2.ToFloatPtr());
qglEnd();
qglLineWidth(0.5f);
}
/*
================
DrawProjectedLight
================
*/
void DrawProjectedLight(brush_t *b, bool bSelected, bool texture) {
int i;
idVec3 v1, v2, cross, vieworg, edge[8][2], v[4];
idVec3 target, start;
if (!bSelected && !g_bShowLightVolumes) {
return;
}
// use the renderer to get the volume outline
idPlane lightProject[4];
idPlane planes[6];
srfTriangles_t *tri;
// use the game's epair parsing code so
// we can use the same renderLight generation
entity_t *ent = b->owner;
idDict spawnArgs;
renderLight_t parms;
spawnArgs = ent->epairs;
gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &parms );
R_RenderLightFrustum( parms, planes );
tri = R_PolytopeSurface(6, planes, NULL);
qglColor3f(1, 0, 1);
for (i = 0; i < tri->numIndexes; i += 3) {
qglBegin(GL_LINE_LOOP);
glVertex3fv(tri->verts[tri->indexes[i]].xyz.ToFloatPtr());
glVertex3fv(tri->verts[tri->indexes[i + 1]].xyz.ToFloatPtr());
glVertex3fv(tri->verts[tri->indexes[i + 2]].xyz.ToFloatPtr());
qglEnd();
}
R_FreeStaticTriSurf(tri);
// draw different selection points for point lights or projected
// lights (FIXME: rotate these based on parms!)
if ( !bSelected ) {
return;
}
idMat3 mat;
bool transform = GetMatrixForKey(b->owner, "light_rotation", mat);
if (!transform) {
transform = GetMatrixForKey(b->owner, "rotation", mat);
}
idVec3 tv;
idVec3 *origin = (b->trackLightOrigin) ? &b->owner->lightOrigin : &b->owner->origin;
if (b->pointLight) {
if ( b->lightCenter[0] || b->lightCenter[1] || b->lightCenter[2] ) {
qglPointSize(8);
qglColor3f( 1.0f, 0.4f, 0.8f );
qglBegin(GL_POINTS);
tv = b->lightCenter;
if (transform) {
tv -= *origin;
tv *= mat;
tv += *origin;
}
qglVertex3fv(tv.ToFloatPtr());
qglEnd();
qglPointSize(1);
}
return;
}
// projected light
qglPointSize(8);
qglColor3f( 1.0f, 0.4f, 0.8f );
qglBegin(GL_POINTS);
tv = b->lightRight;
if (transform) {
tv -= *origin;
tv *= mat;
tv += *origin;
}
qglVertex3fv(tv.ToFloatPtr());
tv = b->lightTarget;
if (transform) {
tv -= *origin;
tv *= mat;
tv += *origin;
}
qglVertex3fv(tv.ToFloatPtr());
tv = b->lightUp;
if (transform) {
tv -= *origin;
tv *= mat;
tv += *origin;
}
qglVertex3fv(tv.ToFloatPtr());
qglEnd();
if (b->startEnd) {
qglColor3f( 0.4f, 1.0f, 0.8f );
qglBegin(GL_POINTS);
qglVertex3fv(b->lightStart.ToFloatPtr());
qglVertex3fv(b->lightEnd.ToFloatPtr());
qglEnd();
}
qglPointSize(1);
}
/*
================
GLCircle
================
*/
void GLCircle(float x, float y, float z, float r)
{
float ix = 0;
float iy = r;
float ig = 3 - 2 * r;
float idgr = -6;
float idgd = 4 * r - 10;
qglPointSize(0.5f);
qglBegin(GL_POINTS);
while (ix <= iy) {
if (ig < 0) {
ig += idgd;
idgd -= 8;
iy--;
} else {
ig += idgr;
idgd -= 4;
}
idgr -= 4;
ix++;
qglVertex3f(x + ix, y + iy, z);
qglVertex3f(x - ix, y + iy, z);
qglVertex3f(x + ix, y - iy, z);
qglVertex3f(x - ix, y - iy, z);
qglVertex3f(x + iy, y + ix, z);
qglVertex3f(x - iy, y + ix, z);
qglVertex3f(x + iy, y - ix, z);
qglVertex3f(x - iy, y - ix, z);
}
qglEnd();
}
/*
================
DrawSpeaker
================
*/
void DrawSpeaker(brush_t *b, bool bSelected, bool twoD) {
if (!(g_qeglobals.d_savedinfo.showSoundAlways || (g_qeglobals.d_savedinfo.showSoundWhenSelected && bSelected))) {
return;
}
// convert to units ( inches )
float min = FloatForKey(b->owner, "s_mindistance");
float max = FloatForKey(b->owner, "s_maxdistance");
const char *s = b->owner->epairs.GetString("s_shader");
if (s && *s) {
const idSoundShader *shader = declManager->FindSound( s, false );
if ( shader ) {
if ( !min ) {
min = shader->GetMinDistance();
}
if ( !max ) {
max = shader->GetMaxDistance();
}
}
}
if (min == 0 && max == 0) {
return;
}
// convert from meters to doom units
min *= METERS_TO_DOOM;
max *= METERS_TO_DOOM;
if (twoD) {
if (bSelected) {
qglColor4f(g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].x, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].y, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].z, .5);
} else {
qglColor4f(b->owner->eclass->color.x, b->owner->eclass->color.y, b->owner->eclass->color.z, .5);
}
qglPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
GLCircle(b->owner->origin.x, b->owner->origin.y, b->owner->origin.z, min);
if (bSelected) {
qglColor4f(g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].x, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].y, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].z, 1);
} else {
qglColor4f(b->owner->eclass->color.x, b->owner->eclass->color.y, b->owner->eclass->color.z, 1);
}
GLCircle(b->owner->origin.x, b->owner->origin.y, b->owner->origin.z, max);
} else {
qglPushMatrix();
qglTranslatef(b->owner->origin.x, b->owner->origin.y, b->owner->origin.z );
qglColor3f( 0.4f, 0.4f, 0.4f );
qglPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
GLUquadricObj* qobj = gluNewQuadric();
gluSphere(qobj, min, 8, 8);
qglColor3f( 0.8f, 0.8f, 0.8f );
gluSphere(qobj, max, 8, 8);
qglEnable(GL_BLEND);
qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
globalImages->BindNull();
if (bSelected) {
qglColor4f( g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].x, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].y, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].z, 0.35f );
} else {
qglColor4f( b->owner->eclass->color.x, b->owner->eclass->color.y, b->owner->eclass->color.z, 0.35f );
}
gluSphere(qobj, min, 8, 8);
if (bSelected) {
qglColor4f( g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].x, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].y, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].z, 0.1f );
} else {
qglColor4f( b->owner->eclass->color.x, b->owner->eclass->color.y, b->owner->eclass->color.z, 0.1f );
}
gluSphere(qobj, max, 8, 8);
gluDeleteQuadric(qobj);
qglPopMatrix();
}
}
/*
================
DrawLight
================
*/
void DrawLight(brush_t *b, bool bSelected) {
idVec3 vTriColor;
bool bTriPaint = false;
vTriColor[0] = vTriColor[2] = 1.0f;
vTriColor[1] = 1.0f;
bTriPaint = true;
CString strColor = ValueForKey(b->owner, "_color");
if (strColor.GetLength() > 0) {
float fR, fG, fB;
int n = sscanf(strColor, "%f %f %f", &fR, &fG, &fB);
if (n == 3) {
vTriColor[0] = fR;
vTriColor[1] = fG;
vTriColor[2] = fB;
}
}
qglColor3f(vTriColor[0], vTriColor[1], vTriColor[2]);
idVec3 vCorners[4];
float fMid = b->mins[2] + (b->maxs[2] - b->mins[2]) / 2;
vCorners[0][0] = b->mins[0];
vCorners[0][1] = b->mins[1];
vCorners[0][2] = fMid;
vCorners[1][0] = b->mins[0];
vCorners[1][1] = b->maxs[1];
vCorners[1][2] = fMid;
vCorners[2][0] = b->maxs[0];
vCorners[2][1] = b->maxs[1];
vCorners[2][2] = fMid;
vCorners[3][0] = b->maxs[0];
vCorners[3][1] = b->mins[1];
vCorners[3][2] = fMid;
idVec3 vTop, vBottom;
vTop[0] = b->mins[0] + ((b->maxs[0] - b->mins[0]) / 2);
vTop[1] = b->mins[1] + ((b->maxs[1] - b->mins[1]) / 2);
vTop[2] = b->maxs[2];
VectorCopy(vTop, vBottom);
vBottom[2] = b->mins[2];
idVec3 vSave;
VectorCopy(vTriColor, vSave);
globalImages->BindNull();
qglBegin(GL_TRIANGLE_FAN);
qglVertex3fv(vTop.ToFloatPtr());
int i;
for (i = 0; i <= 3; i++) {
vTriColor[0] *= 0.95f;
vTriColor[1] *= 0.95f;
vTriColor[2] *= 0.95f;
qglColor3f(vTriColor[0], vTriColor[1], vTriColor[2]);
qglVertex3fv(vCorners[i].ToFloatPtr());
}
qglVertex3fv(vCorners[0].ToFloatPtr());
qglEnd();
VectorCopy(vSave, vTriColor);
vTriColor[0] *= 0.95f;
vTriColor[1] *= 0.95f;
vTriColor[2] *= 0.95f;
qglBegin(GL_TRIANGLE_FAN);
qglVertex3fv(vBottom.ToFloatPtr());
qglVertex3fv(vCorners[0].ToFloatPtr());
for (i = 3; i >= 0; i--) {
vTriColor[0] *= 0.95f;
vTriColor[1] *= 0.95f;
vTriColor[2] *= 0.95f;
qglColor3f(vTriColor[0], vTriColor[1], vTriColor[2]);
qglVertex3fv(vCorners[i].ToFloatPtr());
}
qglEnd();
DrawProjectedLight(b, bSelected, true);
}
/*
================
Control_Draw
================
*/
void Control_Draw(brush_t *b) {
face_t *face;
int i, order;
qtexture_t *prev = 0;
idWinding *w;
// guarantee the texture will be set first
prev = NULL;
for ( face = b->brush_faces, order = 0; face; face = face->next, order++ ) {
w = face->face_winding;
if (!w) {
continue; // freed face
}
qglColor4f(1, 1, .5, 1);
qglBegin(GL_POLYGON);
for (i = 0; i < w->GetNumPoints(); i++) {
qglVertex3fv( (*w)[i].ToFloatPtr() );
}
qglEnd();
}
}
/*
================
Brush_DrawModel
================
*/
void Brush_DrawModel( brush_t *b, bool camera, bool bSelected ) {
idMat3 axis;
idAngles angles;
int nDrawMode = g_pParentWnd->GetCamera()->Camera().draw_mode;
if ( camera && g_PrefsDlg.m_nEntityShowState != ENTITY_WIREFRAME && nDrawMode != cd_wire ) {
qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
else {
qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
}
idRenderModel *model = b->modelHandle;
if ( model == NULL ) {
model = b->owner->eclass->entityModel;
}
if ( model ) {
idRenderModel *model2;
model2 = NULL;
bool fixedBounds = false;
if ( model->IsDynamicModel() != DM_STATIC ) {
if ( dynamic_cast<idRenderModelMD5 *>( model ) ) {
const char *classname = ValueForKey( b->owner, "classname" );
if (stricmp(classname, "func_static") == 0) {
classname = ValueForKey(b->owner, "animclass");
}
const char *anim = ValueForKey( b->owner, "anim" );
int frame = IntForKey( b->owner, "frame" ) + 1;
if ( frame < 1 ) {
frame = 1;
}
if ( !anim || !anim[ 0 ] ) {
anim = "idle";
}
model2 = gameEdit->ANIM_CreateMeshForAnim( model, classname, anim, frame, false );
} else if ( dynamic_cast<idRenderModelPrt*>( model ) || dynamic_cast<idRenderModelLiquid*>( model ) ) {
fixedBounds = true;
}
if ( !model2 ) {
idBounds bounds;
if (fixedBounds) {
bounds.Zero();
bounds.ExpandSelf(12.0f);
} else {
bounds = model->Bounds( NULL );
}
idVec4 color;
color.w = 1.0f;
if (bSelected) {
color.x = g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].x;
color.y = g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].y;
color.z = g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].z;
} else {
color.x = b->owner->eclass->color.x;
color.y = b->owner->eclass->color.y;
color.z = b->owner->eclass->color.z;
}
idVec3 center = bounds.GetCenter();
glBox(color, b->owner->origin + center, bounds.GetRadius( center ) );
model = renderModelManager->DefaultModel();
} else {
model = model2;
}
}
Entity_GetRotationMatrixAngles( b->owner, axis, angles );
idVec4 colorSave;
qglGetFloatv(GL_CURRENT_COLOR, colorSave.ToFloatPtr());
if ( bSelected ) {
qglColor3fv( g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].ToFloatPtr() );
}
DrawRenderModel( model, b->owner->origin, axis, camera );
qglColor4fv( colorSave.ToFloatPtr() );
if ( bSelected && camera )
{
//draw selection tints
/*
if ( camera && g_PrefsDlg.m_nEntityShowState != ENTITY_WIREFRAME ) {
qglPolygonMode ( GL_FRONT_AND_BACK , GL_FILL );
qglColor3fv ( g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].ToFloatPtr () );
qglEnable ( GL_BLEND );
qglBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
DrawRenderModel( model, b->owner->origin, axis, camera );
}
*/
//draw white triangle outlines
globalImages->BindNull();
qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
qglDisable( GL_BLEND );
qglDisable( GL_DEPTH_TEST );
qglColor3f( 1.0f, 1.0f, 1.0f );
qglPolygonOffset( 1.0f, 3.0f );
DrawRenderModel( model, b->owner->origin, axis, false );
qglEnable( GL_DEPTH_TEST );
}
if ( model2 ) {
delete model2;
model2 = NULL;
}
}
if ( bSelected && camera ) {
qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
else if ( camera ) {
globalImages->BindNull();
}
if ( g_bPatchShowBounds ) {
for ( face_t *face = b->brush_faces; face; face = face->next ) {
// only draw polygons facing in a direction we care about
idWinding *w = face->face_winding;
if (!w) {
continue;
}
//
// if (b->alphaBrush && !(face->texdef.flags & SURF_ALPHA)) continue;
// draw the polygon
//
qglBegin(GL_LINE_LOOP);
for (int i = 0; i < w->GetNumPoints(); i++) {
qglVertex3fv( (*w)[i].ToFloatPtr() );
}
qglEnd();
}
}
}
/*
================
GLTransformedVertex
================
*/
void GLTransformedVertex(float x, float y, float z, idMat3 mat, idVec3 origin, idVec3 color, float maxDist) {
idVec3 v(x,y,z);
v -= origin;
v *= mat;
v += origin;
idVec3 n = v - g_pParentWnd->GetCamera()->Camera().origin;
float max = n.Length() / maxDist;
if (color.x) {
color.x = max;
} else if (color.y) {
color.y = max;
} else {
color.z = max;
}
qglColor3f(color.x, color.y, color.z);
qglVertex3f(v.x, v.y, v.z);
}
/*
================
GLTransformedCircle
================
*/
void GLTransformedCircle(int type, idVec3 origin, float r, idMat3 mat, float pointSize, idVec3 color, float maxDist) {
qglPointSize(pointSize);
qglBegin(GL_POINTS);
for (int i = 0; i < 360; i++) {
float cx = origin.x;
float cy = origin.y;
float cz = origin.z;
switch (type) {
case 0:
cx += r * cos((float)i);
cy += r * sin((float)i);
break;
case 1:
cx += r * cos((float)i);
cz += r * sin((float)i);
break;
case 2:
cy += r * sin((float)i);
cz += r * cos((float)i);
break;
default:
break;
}
GLTransformedVertex(cx, cy, cz, mat, origin, color, maxDist);
}
qglEnd();
}
/*
================
Brush_DrawAxis
================
*/
void Brush_DrawAxis(brush_t *b) {
if ( g_pParentWnd->ActiveXY()->RotateMode() && b->modelHandle ) {
bool matrix = false;
idMat3 mat;
float a, s, c;
if (GetMatrixForKey(b->owner, "rotation", mat)) {
matrix = true;
} else {
a = FloatForKey(b->owner, "angle");
if (a) {
s = sin( DEG2RAD( a ) );
c = cos( DEG2RAD( a ) );
}
else {
s = c = 0;
}
}
idBounds bo;
bo.FromTransformedBounds(b->modelHandle->Bounds(), b->owner->origin, b->owner->rotation);
float dist = (g_pParentWnd->GetCamera()->Camera().origin - bo[0]).Length();
float dist2 = (g_pParentWnd->GetCamera()->Camera().origin - bo[1]).Length();
if (dist2 > dist) {
dist = dist2;
}
float xr, yr, zr;
xr = (b->modelHandle->Bounds()[1].x > b->modelHandle->Bounds()[0].x) ? b->modelHandle->Bounds()[1].x - b->modelHandle->Bounds()[0].x : b->modelHandle->Bounds()[0].x - b->modelHandle->Bounds()[1].x;
yr = (b->modelHandle->Bounds()[1].y > b->modelHandle->Bounds()[0].y) ? b->modelHandle->Bounds()[1].y - b->modelHandle->Bounds()[0].y : b->modelHandle->Bounds()[0].y - b->modelHandle->Bounds()[1].y;
zr = (b->modelHandle->Bounds()[1].z > b->modelHandle->Bounds()[0].z) ? b->modelHandle->Bounds()[1].z - b->modelHandle->Bounds()[0].z : b->modelHandle->Bounds()[0].z - b->modelHandle->Bounds()[1].z;
globalImages->BindNull();
GLTransformedCircle(0, b->owner->origin, xr, mat, 1.25, idVec3(0, 0, 1), dist);
GLTransformedCircle(1, b->owner->origin, yr, mat, 1.25, idVec3(0, 1, 0), dist);
GLTransformedCircle(2, b->owner->origin, zr, mat, 1.25, idVec3(1, 0, 0), dist);
float wr = xr;
int type = 0;
idVec3 org = b->owner->origin;
if (g_qeglobals.rotateAxis == 0) {
wr = zr;
type = 2;
} else if (g_qeglobals.rotateAxis == 1) {
wr = yr;
type = 1;
}
if (g_qeglobals.flatRotation) {
if (yr > wr) {
wr = yr;
}
if (zr > wr) {
wr = zr;
}
idVec3 vec = vec3_origin;
vec[g_qeglobals.rotateAxis] = 1.0f;
if (g_qeglobals.flatRotation == 1) {
org = g_pParentWnd->ActiveXY()->RotateOrigin();
float t = (org - bo.GetCenter()).Length();
if (t > wr) {
wr = t;
}
} else {
org = bo.GetCenter();
}
idRotation rot(org, vec, 0);
mat = rot.ToMat3();
}
GLTransformedCircle(type, org, wr * 1.03f, mat, 1.45f, idVec3(1, 1, 1), dist);
}
}
/*
================
Brush_DrawModelInfo
================
*/
void Brush_DrawModelInfo(brush_t *b, bool selected) {
if (b->modelHandle > 0) {
GLfloat color[4];
qglGetFloatv(GL_CURRENT_COLOR, &color[0]);
if (selected) {
qglColor3fv(g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].ToFloatPtr());
}
else {
qglColor3fv(b->owner->eclass->color.ToFloatPtr());
}
Brush_DrawModel(b, true, selected);
qglColor4fv(color);
if ( selected ) {
Brush_DrawAxis(b);
}
return;
}
}
/*
================
Brush_DrawEmitter
================
*/
void Brush_DrawEmitter(brush_t *b, bool bSelected, bool cam) {
if ( !( b->owner->eclass->nShowFlags & ECLASS_PARTICLE ) ) {
return;
}
if (bSelected) {
qglColor4f(g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].x, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].y, g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].z, .5);
} else {
qglColor4f(b->owner->eclass->color.x, b->owner->eclass->color.y, b->owner->eclass->color.z, .5);
}
if ( cam ) {
Brush_DrawFacingAngle( b, b->owner, true );
}
}
/*
================
Brush_DrawEnv
================
*/
void Brush_DrawEnv( brush_t *b, bool cameraView, bool bSelected ) {
idVec3 origin, newOrigin;
idMat3 axis, newAxis;
idAngles newAngles;
bool poseIsSet;
idRenderModel *model = gameEdit->AF_CreateMesh( b->owner->epairs, origin, axis, poseIsSet );
if ( !poseIsSet ) {
if ( Entity_GetRotationMatrixAngles( b->owner, newAxis, newAngles ) ) {
axis = newAxis;
}
if ( b->owner->epairs.GetVector( "origin", "0 0 0", newOrigin ) ) {
origin = newOrigin;
}
}
if ( model ) {
if ( cameraView && g_PrefsDlg.m_nEntityShowState != ENTITY_WIREFRAME ) {
qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
else {
qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
}
idVec4 colorSave;
qglGetFloatv(GL_CURRENT_COLOR, colorSave.ToFloatPtr());
if ( bSelected ) {
qglColor3fv( g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES].ToFloatPtr() );
} else {
qglColor3f( 1.f, 1.f, 1.f );
}
DrawRenderModel( model, origin, axis, true );
globalImages->BindNull();
delete model;
model = NULL;
qglColor4fv( colorSave.ToFloatPtr() );
}
}
/*
================
Brush_DrawCombatNode
================
*/
void Brush_DrawCombatNode( brush_t *b, bool cameraView, bool bSelected ) {
float min_dist = b->owner->epairs.GetFloat( "min" );
float max_dist = b->owner->epairs.GetFloat( "max" );
float fov = b->owner->epairs.GetFloat( "fov", "60" );
float yaw = b->owner->epairs.GetFloat("angle");
idVec3 offset = b->owner->epairs.GetVector("offset");
idAngles leftang( 0.0f, yaw + fov * 0.5f - 90.0f, 0.0f );
idVec3 cone_left = leftang.ToForward();
idAngles rightang( 0.0f, yaw - fov * 0.5f + 90.0f, 0.0f );
idVec3 cone_right = rightang.ToForward();
bool disabled = b->owner->epairs.GetBool( "start_off" );
idVec4 color;
if ( bSelected ) {
color = colorRed;
} else {
color = colorBlue;
}
idVec3 leftDir( -cone_left.y, cone_left.x, 0.0f );
idVec3 rightDir( cone_right.y, -cone_right.x, 0.0f );
leftDir.NormalizeFast();
rightDir.NormalizeFast();
idMat3 axis = idAngles(0, yaw, 0).ToMat3();
idVec3 org = b->owner->origin + offset;
idVec3 entorg = b->owner->origin;
float cone_dot = cone_right * axis[ 1 ];
if ( idMath::Fabs( cone_dot ) > 0.1 ) {
idVec3 pt, pt1, pt2, pt3, pt4;
float cone_dist = max_dist / cone_dot;
pt1 = org + leftDir * min_dist;
pt2 = org + leftDir * cone_dist;
pt3 = org + rightDir * cone_dist;
pt4 = org + rightDir * min_dist;
qglColor4fv(color.ToFloatPtr());
qglBegin(GL_LINE_STRIP);
qglVertex3fv( pt1.ToFloatPtr());
qglVertex3fv( pt2.ToFloatPtr());
qglVertex3fv( pt3.ToFloatPtr());
qglVertex3fv( pt4.ToFloatPtr());
qglVertex3fv( pt1.ToFloatPtr());
qglEnd();
qglColor4fv(colorGreen.ToFloatPtr());
qglBegin(GL_LINE_STRIP);
qglVertex3fv( entorg.ToFloatPtr());
pt = (pt1 + pt4) * 0.5f;
qglVertex3fv( pt.ToFloatPtr());
pt = (pt2 + pt3) * 0.5f;
qglVertex3fv( pt.ToFloatPtr());
idVec3 tip = pt;
idVec3 dir = ((pt1 + pt2) * 0.5f) - tip;
dir.Normalize();
pt = tip + dir * 15.0f;
qglVertex3fv( pt.ToFloatPtr());
qglVertex3fv( tip.ToFloatPtr());
dir = ((pt4 + pt3) * 0.5f) - tip;
dir.Normalize();
pt = tip + dir * 15.0f;
qglVertex3fv( pt.ToFloatPtr());
qglEnd();
}
}
/*
================
Brush_Draw
================
*/
void Brush_Draw(brush_t *b, bool bSelected) {
face_t *face;
int i, order;
const idMaterial *prev = NULL;
idWinding *w;
bool model = false;
//
// (TTimo) NOTE: added by build 173, I check after pPlugEnt so it doesn't
// interfere ?
//
if ( b->hiddenBrush ) {
return;
}
Brush_DrawCurve( b, bSelected, true );
if (b->pPatch) {
Patch_DrawCam(b->pPatch, bSelected);
return;
}
int nDrawMode = g_pParentWnd->GetCamera()->Camera().draw_mode;
if (!(g_qeglobals.d_savedinfo.exclude & EXCLUDE_ANGLES) && (b->owner->eclass->nShowFlags & ECLASS_ANGLE)) {
Brush_DrawFacingAngle(b, b->owner, false);
}
if ( b->owner->eclass->fixedsize ) {
DrawSpeaker( b, bSelected, false );
if ( g_PrefsDlg.m_bNewLightDraw && (b->owner->eclass->nShowFlags & ECLASS_LIGHT) && !(b->modelHandle || b->entityModel) ) {
DrawLight( b, bSelected );
return;
}
if ( b->owner->eclass->nShowFlags & ECLASS_ENV ) {
Brush_DrawEnv( b, true, bSelected );
}
if ( b->owner->eclass->nShowFlags & ECLASS_COMBATNODE ) {
Brush_DrawCombatNode( b, true, bSelected );
}
}
if (!(b->owner && (b->owner->eclass->nShowFlags & ECLASS_WORLDSPAWN))) {
qglColor4f( 1.0f, 0.0f, 0.0f, 0.8f );
qglPointSize(4);
qglBegin(GL_POINTS);
qglVertex3fv(b->owner->origin.ToFloatPtr());
qglEnd();
}
if ( b->owner->eclass->entityModel ) {
qglColor3fv( b->owner->eclass->color.ToFloatPtr() );
Brush_DrawModel( b, true, bSelected );
return;
}
Brush_DrawEmitter( b, bSelected, true );
if ( b->modelHandle > 0 && !model ) {
Brush_DrawModelInfo( b, bSelected );
return;
}
// guarantee the texture will be set first
prev = NULL;
for (face = b->brush_faces, order = 0; face; face = face->next, order++) {
w = face->face_winding;
if (!w) {
continue; // freed face
}
if (g_qeglobals.d_savedinfo.exclude & EXCLUDE_CAULK) {
if (strstr(face->texdef.name, "caulk")) {
continue;
}
}
if (g_qeglobals.d_savedinfo.exclude & EXCLUDE_VISPORTALS) {
if (strstr(face->texdef.name, "visportal")) {
continue;
}
}
if (g_qeglobals.d_savedinfo.exclude & EXCLUDE_NODRAW) {
if (strstr(face->texdef.name, "nodraw")) {
continue;
}
}
if ( (nDrawMode == cd_texture || nDrawMode == cd_light) && face->d_texture != prev && !b->forceWireFrame ) {
// set the texture for this face
prev = face->d_texture;
face->d_texture->GetEditorImage()->Bind();
}
if (model) {
qglEnable(GL_BLEND);
qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
qglColor4f( face->d_color.x, face->d_color.y, face->d_color.z, 0.1f );
} else {
qglColor4f( face->d_color.x, face->d_color.y, face->d_color.z, face->d_texture->GetEditorAlpha() );
}
qglBegin(GL_POLYGON);
for (i = 0; i < w->GetNumPoints(); i++) {
if ( !b->forceWireFrame && ( nDrawMode == cd_texture || nDrawMode == cd_light ) ) {
qglTexCoord2fv( &(*w)[i][3] );
}
qglVertex3fv( (*w)[i].ToFloatPtr() );
}
qglEnd();
if (model) {
qglDisable(GL_BLEND);
}
}
globalImages->BindNull();
}
/*
================
Face_Draw
================
*/
void Face_Draw(face_t *f) {
int i;
if (f->face_winding == NULL) {
return;
}
qglBegin(GL_POLYGON);
for (i = 0; i < f->face_winding->GetNumPoints(); i++) {
qglVertex3fv( (*f->face_winding)[i].ToFloatPtr() );
}
qglEnd();
}
idSurface_SweptSpline *SplineToSweptSpline( idCurve<idVec3> *curve ) {
// expects a vec3 curve and creates a vec4 based swept spline
// must be either nurbs or catmull
idCurve_Spline<idVec4> *newCurve = NULL;
if ( dynamic_cast<idCurve_NURBS<idVec3>*>( curve ) ) {
newCurve = new idCurve_NURBS<idVec4>;
} else if ( dynamic_cast<idCurve_CatmullRomSpline<idVec3>*>( curve ) ) {
newCurve = new idCurve_CatmullRomSpline<idVec4>;
}
if ( curve == NULL || newCurve == NULL ) {
return NULL;
}
int c = curve->GetNumValues();
float len = 0.0f;
for ( int i = 0; i < c; i++ ) {
idVec3 v = curve->GetValue( i );
newCurve->AddValue( curve->GetTime( i ), idVec4( v.x, v.y, v.z, len ) );
if ( i < c - 1 ) {
len += curve->GetLengthBetweenKnots( i, i + 1 ) * 0.1f;
}
}
idSurface_SweptSpline *ss = new idSurface_SweptSpline;
ss->SetSpline( newCurve );
ss->SetSweptCircle( 10.0f );
ss->Tessellate( newCurve->GetNumValues() * 6, 6 );
return ss;
}
/*
================
Brush_DrawCurve
================
*/
void Brush_DrawCurve( brush_t *b, bool bSelected, bool cam ) {
if ( b == NULL || b->owner->curve == NULL ) {
return;
}
int maxage = b->owner->curve->GetNumValues();
int i, time = 0;
qglColor3f( 0.0f, 0.0f, 1.0f );
for ( i = 0; i < maxage; i++) {
if ( bSelected && g_qeglobals.d_select_mode == sel_editpoint ) {
idVec3 v = b->owner->curve->GetValue( i );
if ( cam ) {
glBox( colorBlue, v, 6.0f );
if ( PointInMoveList( b->owner->curve->GetValueAddress( i ) ) >= 0 ) {
glBox(colorBlue, v, 8.0f );
}
} else {
qglPointSize( 4.0f );
qglBegin( GL_POINTS );
qglVertex3f( v.x, v.y, v.z );
qglEnd();
if ( PointInMoveList( b->owner->curve->GetValueAddress( i ) ) >= 0 ) {
glBox(colorBlue, v, 4.0f );
}
}
}
/*
if ( cam ) {
idSurface_SweptSpline *ss = SplineToSweptSpline( b->owner->curve );
if ( ss ) {
idMaterial *mat = declManager->FindMaterial( "_default" );
mat->GetEditorImage()->Bind();
qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
qglBegin( GL_TRIANGLES );
const int *indexes = ss->GetIndexes();
const idDrawVert *verts = ss->GetVertices();
for ( j = 0; j < ss->GetNumIndexes(); j += 3 ) {
for ( k = 0; k < 3; k++ ) {
int index = indexes[ j + 2 - k ];
float f = ShadeForNormal( verts[index].normal );
qglColor3f( f, f, f );
qglTexCoord2fv( verts[index].st.ToFloatPtr() );
qglVertex3fv( verts[index].xyz.ToFloatPtr() );
}
}
qglEnd();
delete ss;
}
} else {
*/
/* qglPointSize( 1.0f );
qglBegin( GL_POINTS );
if ( i + 1 < maxage ) {
int start = b->owner->curve->GetTime( i );
int end = b->owner->curve->GetTime( i + 1 );
int inc = (end - start) / POINTS_PER_KNOT;
for ( int j = 0; j < POINTS_PER_KNOT; j++ ) {
idVec3 v = b->owner->curve->GetCurrentValue( start );
qglVertex3f( v.x, v.y, v.z );
start += inc;
}
}*/
// DHM - _D3XP : Makes it easier to see curve
qglBegin( GL_LINE_STRIP );
if ( i + 1 < maxage ) {
int start = b->owner->curve->GetTime( i );
int end = b->owner->curve->GetTime( i + 1 );
int inc = (end - start) / POINTS_PER_KNOT;
for ( int j = 0; j <= POINTS_PER_KNOT; j++ ) {
idVec3 v = b->owner->curve->GetCurrentValue( start );
qglVertex3f( v.x, v.y, v.z );
start += inc;
}
}
qglEnd();
/*
}
*/
}
qglPointSize(1);
}
/*
================
Brush_DrawXY
================
*/
void Brush_DrawXY(brush_t *b, int nViewType, bool bSelected, bool ignoreViewType) {
face_t *face;
int order;
idWinding *w;
int i;
if ( b->hiddenBrush ) {
return;
}
idVec4 colorSave;
qglGetFloatv(GL_CURRENT_COLOR, colorSave.ToFloatPtr());
if (!(b->owner && (b->owner->eclass->nShowFlags & ECLASS_WORLDSPAWN))) {
qglColor4f( 1.0f, 0.0f, 0.0f, 0.8f );
qglPointSize(4);
qglBegin(GL_POINTS);
qglVertex3fv(b->owner->origin.ToFloatPtr());
qglEnd();
}
Brush_DrawCurve( b, bSelected, false );
qglColor4fv(colorSave.ToFloatPtr());
if (b->pPatch) {
Patch_DrawXY(b->pPatch);
if (!g_bPatchShowBounds) {
return;
}
}
if (b->owner->eclass->fixedsize) {
DrawSpeaker(b, bSelected, true);
if (g_PrefsDlg.m_bNewLightDraw && (b->owner->eclass->nShowFlags & ECLASS_LIGHT) && !(b->modelHandle || b->entityModel)) {
idVec3 vCorners[4];
float fMid = b->mins[2] + (b->maxs[2] - b->mins[2]) / 2;
vCorners[0][0] = b->mins[0];
vCorners[0][1] = b->mins[1];
vCorners[0][2] = fMid;
vCorners[1][0] = b->mins[0];
vCorners[1][1] = b->maxs[1];
vCorners[1][2] = fMid;
vCorners[2][0] = b->maxs[0];
vCorners[2][1] = b->maxs[1];
vCorners[2][2] = fMid;
vCorners[3][0] = b->maxs[0];
vCorners[3][1] = b->mins[1];
vCorners[3][2] = fMid;
idVec3 vTop, vBottom;
vTop[0] = b->mins[0] + ((b->maxs[0] - b->mins[0]) / 2);
vTop[1] = b->mins[1] + ((b->maxs[1] - b->mins[1]) / 2);
vTop[2] = b->maxs[2];
VectorCopy(vTop, vBottom);
vBottom[2] = b->mins[2];
qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
qglBegin(GL_TRIANGLE_FAN);
qglVertex3fv(vTop.ToFloatPtr());
qglVertex3fv(vCorners[0].ToFloatPtr());
qglVertex3fv(vCorners[1].ToFloatPtr());
qglVertex3fv(vCorners[2].ToFloatPtr());
qglVertex3fv(vCorners[3].ToFloatPtr());
qglVertex3fv(vCorners[0].ToFloatPtr());
qglEnd();
qglBegin(GL_TRIANGLE_FAN);
qglVertex3fv(vBottom.ToFloatPtr());
qglVertex3fv(vCorners[0].ToFloatPtr());
qglVertex3fv(vCorners[3].ToFloatPtr());
qglVertex3fv(vCorners[2].ToFloatPtr());
qglVertex3fv(vCorners[1].ToFloatPtr());
qglVertex3fv(vCorners[0].ToFloatPtr());
qglEnd();
DrawBrushEntityName(b);
DrawProjectedLight(b, bSelected, false);
return;
} else if (b->owner->eclass->nShowFlags & ECLASS_MISCMODEL) {
// if (PaintedModel(b, false)) return;
} else if (b->owner->eclass->nShowFlags & ECLASS_ENV) {
Brush_DrawEnv( b, false, bSelected );
} else if (b->owner->eclass->nShowFlags & ECLASS_COMBATNODE) {
Brush_DrawCombatNode(b, false, bSelected);
}
if (b->owner->eclass->entityModel) {
Brush_DrawModel( b, false, bSelected );
DrawBrushEntityName(b);
qglColor4fv(colorSave.ToFloatPtr());
return;
}
}
qglColor4fv(colorSave.ToFloatPtr());
if (b->modelHandle > 0) {
Brush_DrawEmitter( b, bSelected, false );
Brush_DrawModel(b, false, bSelected);
qglColor4fv(colorSave.ToFloatPtr());
return;
}
for (face = b->brush_faces, order = 0; face; face = face->next, order++) {
// only draw polygons facing in a direction we care about
if (!ignoreViewType) {
if (nViewType == XY) {
if (face->plane[2] <= 0) {
continue;
}
} else {
if (nViewType == XZ) {
if (face->plane[1] <= 0) {
continue;
}
} else {
if (face->plane[0] <= 0) {
continue;
}
}
}
}
w = face->face_winding;
if (!w) {
continue;
}
//
// if (b->alphaBrush && !(face->texdef.flags & SURF_ALPHA)) continue;
// draw the polygon
//
qglBegin(GL_LINE_LOOP);
for (i = 0; i < w->GetNumPoints(); i++) {
qglVertex3fv( (*w)[i].ToFloatPtr() );
}
qglEnd();
/*
for (i = 0; i < 3; i++) {
glLabeledPoint(idVec4(1, 0, 0, 1), face->planepts[i], 3, va("%i", i));
}
*/
}
DrawBrushEntityName(b);
}
/*
==================
PointValueInPointList
==================
*/
static int PointValueInPointList( idVec3 v ) {
for ( int i = 0; i < g_qeglobals.d_numpoints; i++ ) {
if ( v == g_qeglobals.d_points[i] ) {
return i;
}
}
return -1;
}
extern bool Sys_KeyDown(int key);
/*
================
Brush_Move
================
*/
void Brush_Move(brush_t *b, const idVec3 move, bool bSnap, bool updateOrigin) {
int i;
face_t *f;
char text[128];
for (f = b->brush_faces; f; f = f->next) {
idVec3 vTemp;
VectorCopy(move, vTemp);
if (g_PrefsDlg.m_bTextureLock) {
Face_MoveTexture(f, vTemp);
}
for (i = 0; i < 3; i++) {
VectorAdd(f->planepts[i], move, f->planepts[i]);
}
}
bool controlDown = Sys_KeyDown(VK_CONTROL);
Brush_Build(b, bSnap, true, false, !controlDown);
if (b->pPatch) {
Patch_Move(b->pPatch, move);
}
if ( b->owner->curve ) {
b->owner->curve->Translate( move );
Entity_UpdateCurveData( b->owner );
}
idVec3 temp;
// PGM - keep the origin vector up to date on fixed size entities.
if (b->owner->eclass->fixedsize || EntityHasModel(b->owner) || (updateOrigin && GetVectorForKey(b->owner, "origin", temp))) {
// if (!b->entityModel) {
bool adjustOrigin = true;
if(b->trackLightOrigin) {
b->owner->lightOrigin += move;
sprintf(text, "%i %i %i", (int)b->owner->lightOrigin[0], (int)b->owner->lightOrigin[1], (int)b->owner->lightOrigin[2]);
SetKeyValue(b->owner, "light_origin", text);
if (QE_SingleBrush(true, true)) {
adjustOrigin = false;
}
}
if (adjustOrigin && updateOrigin) {
b->owner->origin += move;
if (g_moveOnly) {
sprintf(text, "%g %g %g", b->owner->origin[0], b->owner->origin[1], b->owner->origin[2]);
} else {
sprintf(text, "%i %i %i", (int)b->owner->origin[0], (int)b->owner->origin[1], (int)b->owner->origin[2]);
}
SetKeyValue(b->owner, "origin", text);
}
// rebuild the light dragging points now that the origin has changed
idVec3 offset;
offset.Zero();
if (controlDown) {
offset.x = -move.x;
offset.y = -move.y;
offset.z = -move.z;
Brush_UpdateLightPoints(b, offset);
} else {
offset.Zero();
Brush_UpdateLightPoints(b, offset);
}
//}
if (b->owner->eclass->nShowFlags & ECLASS_ENV) {
const idKeyValue *arg = b->owner->epairs.MatchPrefix( "body ", NULL );
idStr val;
idVec3 org;
idAngles ang;
while ( arg ) {
sscanf( arg->GetValue(), "%f %f %f %f %f %f", &org.x, &org.y, &org.z, &ang.pitch, &ang.yaw, &ang.roll );
org += move;
val = org.ToString(8);
val += " ";
val += ang.ToString(8);
b->owner->epairs.Set(arg->GetKey(), val);
arg = b->owner->epairs.MatchPrefix( "body ", arg );
}
}
}
}
/*
================
Select_AddProjectedLight
================
*/
void Select_AddProjectedLight() {
idVec3 vTemp;
CString str;
// if (!QE_SingleBrush ()) return;
brush_t *b = selected_brushes.next;
if (b->owner->eclass->nShowFlags & ECLASS_LIGHT) {
vTemp[0] = vTemp[1] = 0;
vTemp[2] = -256;
str.Format("%f %f %f", vTemp[0], vTemp[1], vTemp[2]);
SetKeyValue(b->owner, "light_target", str);
vTemp[2] = 0;
vTemp[1] = -128;
str.Format("%f %f %f", vTemp[0], vTemp[1], vTemp[2]);
SetKeyValue(b->owner, "light_up", str);
vTemp[1] = 0;
vTemp[0] = -128;
str.Format("%f %f %f", vTemp[0], vTemp[1], vTemp[2]);
SetKeyValue(b->owner, "light_right", str);
Brush_Build(b);
}
}
/*
================
Brush_Print
================
*/
void Brush_Print(brush_t *b) {
int nFace = 0;
for (face_t * f = b->brush_faces; f; f = f->next) {
common->Printf("Face %i\n", nFace++);
common->Printf("%f %f %f\n", f->planepts[0][0], f->planepts[0][1], f->planepts[0][2]);
common->Printf("%f %f %f\n", f->planepts[1][0], f->planepts[1][1], f->planepts[1][2]);
common->Printf("%f %f %f\n", f->planepts[2][0], f->planepts[2][1], f->planepts[2][2]);
}
}
/*
================
Brush_MakeSidedCone
Makes the current brush have the given number of 2d sides and turns it into a cone
================
*/
void Brush_MakeSidedCone(int sides) {
int i;
idVec3 mins, maxs;
brush_t *b;
texdef_t *texdef;
face_t *f;
idVec3 mid;
float width;
float sv, cv;
if (sides < 3) {
Sys_Status("Bad sides number", 0);
return;
}
if (!QE_SingleBrush()) {
Sys_Status("Must have a single brush selected", 0);
return;
}
b = selected_brushes.next;
VectorCopy(b->mins, mins);
VectorCopy(b->maxs, maxs);
texdef = &g_qeglobals.d_texturewin.texdef;
Brush_Free(b);
// find center of brush
width = 8;
for (i = 0; i < 2; i++) {
mid[i] = (maxs[i] + mins[i]) * 0.5f;
if (maxs[i] - mins[i] > width) {
width = maxs[i] - mins[i];
}
}
width *= 0.5f;
b = Brush_Alloc();
// create bottom face
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
f->planepts[0][0] = mins[0];
f->planepts[0][1] = mins[1];
f->planepts[0][2] = mins[2];
f->planepts[1][0] = maxs[0];
f->planepts[1][1] = mins[1];
f->planepts[1][2] = mins[2];
f->planepts[2][0] = maxs[0];
f->planepts[2][1] = maxs[1];
f->planepts[2][2] = mins[2];
for (i = 0; i < sides; i++) {
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
sv = sin(i * idMath::TWO_PI / sides);
cv = cos(i * idMath::TWO_PI / sides);
f->planepts[0][0] = floor( mid[0] + width * cv + 0.5f );
f->planepts[0][1] = floor( mid[1] + width * sv + 0.5f );
f->planepts[0][2] = mins[2];
f->planepts[1][0] = mid[0];
f->planepts[1][1] = mid[1];
f->planepts[1][2] = maxs[2];
f->planepts[2][0] = floor( f->planepts[0][0] - width * sv + 0.5f );
f->planepts[2][1] = floor( f->planepts[0][1] + width * cv + 0.5f );
f->planepts[2][2] = maxs[2];
}
Brush_AddToList(b, &selected_brushes);
Entity_LinkBrush(world_entity, b);
Brush_Build(b);
Sys_UpdateWindows(W_ALL);
}
/*
================
Brush_MakeSidedSphere
Makes the current brushhave the given number of 2d sides and turns it into a sphere
================
*/
void Brush_MakeSidedSphere(int sides) {
int i, j;
idVec3 mins, maxs;
brush_t *b;
texdef_t *texdef;
face_t *f;
idVec3 mid;
float radius;
if (sides < 4) {
Sys_Status("Bad sides number", 0);
return;
}
if (!QE_SingleBrush()) {
Sys_Status("Must have a single brush selected", 0);
return;
}
b = selected_brushes.next;
mins = b->mins;
maxs = b->maxs;
texdef = &g_qeglobals.d_texturewin.texdef;
Brush_Free(b);
// find center of brush
radius = 8;
for ( i = 0; i < 3; i++ ) {
mid[i] = (maxs[i] + mins[i]) * 0.5f;
if (maxs[i] - mins[i] > radius) {
radius = maxs[i] - mins[i];
}
}
radius *= 0.5f;
b = Brush_Alloc();
for (i = 0; i < sides; i++) {
for (j = 0; j < sides - 1; j++) {
f = Face_Alloc();
f->texdef = *texdef;
f->next = b->brush_faces;
b->brush_faces = f;
f->planepts[0] = idPolar3(radius, idMath::TWO_PI * i / sides, idMath::PI * ((float)(j) / sides - 0.5f) ).ToVec3() + mid;
f->planepts[1] = idPolar3(radius, idMath::TWO_PI * i / sides, idMath::PI * ((float)(j+1) / sides - 0.5f) ).ToVec3() + mid;
f->planepts[2] = idPolar3(radius, idMath::TWO_PI * (i+1) / sides, idMath::PI * ((float)(j+1) / sides - 0.5f) ).ToVec3() + mid;
}
}
Brush_AddToList(b, &selected_brushes);
Entity_LinkBrush(world_entity, b);
Brush_Build(b);
Sys_UpdateWindows(W_ALL);
}
extern void Face_FitTexture_BrushPrimit(face_t *f, idVec3 mins, idVec3 maxs, float nHeight, float nWidth);
/*
================
Face_FitTexture
================
*/
void Face_FitTexture(face_t *face, float nHeight, float nWidth) {
if (g_qeglobals.m_bBrushPrimitMode) {
idVec3 mins, maxs;
mins[0] = maxs[0] = 0;
Face_FitTexture_BrushPrimit(face, mins, maxs, nHeight, nWidth);
}
else {
/*
* winding_t *w; idBounds bounds; int i; float width, height, temp; float rot_width,
* rot_height; float cosv,sinv,ang; float min_t, min_s, max_t, max_s; float s,t;
* idVec3 vecs[2]; idVec3 coords[4]; texdef_t *td; if (nHeight < 1) { nHeight = 1;
* } if (nWidth < 1) { nWidth = 1; } bounds.Clear(); td = &face->texdef; w =
* face->face_winding; if (!w) { return; } for (i=0 ; i<w->numpoints ; i++) {
* bounds.AddPoint( w->p[i] ); } // // get the current angle // ang = td->rotate /
* 180 * Q_PI; sinv = sin(ang); cosv = cos(ang); // get natural texture axis
* TextureAxisFromPlane(&face->plane, vecs[0], vecs[1]); min_s = DotProduct(
* bounds.b[0], vecs[0] ); min_t = DotProduct( bounds.b[0], vecs[1] ); max_s =
* DotProduct( bounds.b[1], vecs[0] ); max_t = DotProduct( bounds.b[1], vecs[1] );
* width = max_s - min_s; height = max_t - min_t; coords[0][0] = min_s;
* coords[0][1] = min_t; coords[1][0] = max_s; coords[1][1] = min_t; coords[2][0]
* = min_s; coords[2][1] = max_t; coords[3][0] = max_s; coords[3][1] = max_t;
* min_s = min_t = 999999; max_s = max_t = -999999; for (i=0; i<4; i++) { s = cosv
* * coords[i][0] - sinv * coords[i][1]; t = sinv * coords[i][0] + cosv *
* coords[i][1]; if (i&1) { if (s > max_s) { max_s = s; } } else { if (s < min_s)
* { min_s = s; } if (i<2) { if (t < min_t) { min_t = t; } } else { if (t > max_t)
* { max_t = t; } } } } rot_width = (max_s - min_s); rot_height = (max_t - min_t);
* td->scale[0] =
* -(rot_width/((float)(face->d_texture->GetEditorImage()->uploadWidth*nWidth)));
* td->scale[1] =
* -(rot_height/((float)(face->d_texture->GetEditorImage()->uploadHeight*nHeight)));
* td->shift[0] = min_s/td->scale[0]; temp = (int)(td->shift[0] /
* (face->d_texture->GetEditorImage()->uploadWidth*nWidth)); temp =
* (temp+1)*face->d_texture->GetEditorImage()->uploadWidth*nWidth; td->shift[0] =
* (int)(temp -
* td->shift[0])%(face->d_texture->GetEditorImage()->uploadWidth*nWidth);
* td->shift[1] = min_t/td->scale[1]; temp = (int)(td->shift[1] /
* (face->d_texture->GetEditorImage()->uploadHeight*nHeight)); temp =
* (temp+1)*(face->d_texture->GetEditorImage()->uploadHeight*nHeight);
* td->shift[1] = (int)(temp -
* td->shift[1])%(face->d_texture->GetEditorImage()->uploadHeight*nHeight);
*/
}
}
/*
================
Brush_FitTexture
================
*/
void Brush_FitTexture(brush_t *b, float nHeight, float nWidth) {
face_t *face;
for (face = b->brush_faces; face; face = face->next) {
Face_FitTexture(face, nHeight, nWidth);
}
}
void Brush_GetBounds( brush_t *b, idBounds &bo ) {
if ( b == NULL ) {
return;
}
bo.Clear();
bo.AddPoint( b->mins );
bo.AddPoint( b->maxs );
if ( b->owner->curve ) {
int c = b->owner->curve->GetNumValues();
for ( int i = 0; i < c; i++ ) {
bo.AddPoint ( b->owner->curve->GetValue( i ) );
}
}
}