2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 // Preliminary patch stuff
30 #include "gtkr_list.h"
33 extern void MemFile_fprintf( MemStream* pMemFile, const char* pText, ... );
34 extern face_t *Face_Alloc( void );
35 extern void DrawAlternatePoint( vec3_t v, float scale );
37 void _Write3DMatrix( FILE *f, int y, int x, int z, float *m );
38 void _Write3DMatrix( MemStream *f, int y, int x, int z, float *m );
40 void Patch_InitialiseLODPointers( patchMesh_t *p ){
42 int rowcount = ( ( MAX_PATCH_WIDTH - 1 ) / 2 ) * MAX_PATCH_HEIGHT;
43 for ( i = 0; i < rowcount; i++ )
45 int colcount = ( ( MAX_PATCH_HEIGHT - 1 ) / 2 ) * MAX_PATCH_WIDTH;
46 for ( i = 0; i < colcount; i++ )
50 patchMesh_t* Patch_Alloc(){
51 patchMesh_t *pPatch = (patchMesh_t *)malloc( sizeof( patchMesh_t ) );
52 pPatch->pShader = NULL;
53 pPatch->pSymbiot = NULL; // Hydra: added missing initialiser.
54 // spog - initialise patch LOD pointers
55 Patch_InitialiseLODPointers( pPatch );
56 pPatch->drawLists = NULL;
57 pPatch->bDirty = true;
59 pPatch->bSelected = false;
60 pPatch->bOverlay = false;
61 pPatch->bDirty = true;
62 pPatch->LODUpdated = false;
65 for ( i = 0; i < ( ( ( MAX_PATCH_WIDTH - 1 ) - 1 ) / 2 ); i++ )
66 pPatch->rowDirty[i] = false;
67 for ( i = 0; i < ( ( ( MAX_PATCH_HEIGHT - 1 ) - 1 ) / 2 ); i++ )
68 pPatch->colDirty[i] = false;
73 patchMesh_t* MakeNewPatch(){
74 patchMesh_t *pm = reinterpret_cast<patchMesh_t*>( qmalloc( sizeof( patchMesh_t ) ) );
76 // spog - initialise patch LOD pointers
77 Patch_InitialiseLODPointers( pm );
84 // FIXME: this needs to be dynamic
85 //#define MAX_PATCH_MESHES 4096
86 //patchMesh_t patchMeshes[MAX_PATCH_MESHES];
87 //int numPatchMeshes = 0;
89 // used for a save spot
90 patchMesh_t patchSave;
92 // Tracks the selected patch for point manipulation/update. FIXME: Need to revert back to a generalized
94 //--int g_nSelectedPatch = -1;
96 // HACK: for tracking which view generated the click
97 // as we dont want to deselect a point on a same point
98 // click if it is from a different view
99 int g_nPatchClickedView = -1;
100 bool g_bSameView = false;
102 //typedef enum XFormType { TRANSLATE, SCALE, ROTATE };
106 bool g_bPatchShowBounds = true;
107 bool g_bPatchWireFrame = false;
108 bool g_bPatchWeld = true;
109 bool g_bPatchDrillDown = true;
110 //bool g_bPatchInsertMode = false;
111 bool g_bPatchBendMode = false;
112 int g_nPatchBendState = -1;
113 int g_nPatchInsertState = -1;
114 int g_nBendOriginIndex = 0;
115 vec3_t g_vBendOrigin;
117 bool g_bPatchAxisOnRow = true;
118 int g_nPatchAxisIndex = 0;
119 bool g_bPatchLowerEdge = true;
121 vec3_t g_vCycleCapNormal;
122 // cycles when we use Patch_CycleCapSelected
123 VIEWTYPE g_nCycleCapIndex = XY;
128 BEND_SELECT_ROTATION = 0,
135 const char *g_pBendStateMsg[] =
137 "Use TAB to cycle through available bend axis. Press ENTER when the desired one is highlighted.",
138 "Use TAB to cycle through available rotation axis. This will LOCK around that point. You may also use Shift + Middle Click to select an arbitrary point. Press ENTER when the desired one is highlighted",
139 "Use TAB to choose which side to bend. Press ENTER when the desired one is highlighted.",
140 "Use the MOUSE to bend the patch. It uses the same ui rules as Free Rotation. Press ENTER to accept the bend, press ESC to abandon it and exit Bend mode",
147 INSERT_SELECT_EDGE = 0,
151 const char* g_pInsertStateMsg[] =
153 "Use TAB to cycle through available rows/columns for insertion/deletion. Press INS to insert at the highlight, DEL to remove the pair"
157 float *g_InversePoints[1024];
159 const float fFullBright = 1.0;
160 const float fLowerLimit = .50;
161 const float fDec = .05f;
162 void _SetColor( face_t* f, float fColor[3] ){
164 fColor[0] = f->d_color[0];
165 fColor[1] = f->d_color[1];
166 fColor[2] = f->d_color[2];
167 qglColor3fv( fColor );
171 void _DecColor( float fColor[3] ){
176 for ( int i = 0; i < 3; i++ )
178 if ( fColor[i] <= fLowerLimit ) {
179 fColor[0] = fFullBright;
180 fColor[1] = fFullBright;
181 fColor[2] = fFullBright;
185 qglColor3fv( fColor );
188 vec_t __VectorNormalize( vec3_t in, vec3_t out ){
189 vec_t length, ilength;
191 length = sqrt( in[0] * in[0] + in[1] * in[1] + in[2] * in[2] );
197 ilength = 1.0 / length;
198 out[0] = in[0] * ilength;
199 out[1] = in[1] * ilength;
200 out[2] = in[2] * ilength;
206 void Patch_SetType( patchMesh_t *p, int nType ){
207 p->type = ( p->type & PATCH_STYLEMASK ) | nType;
210 void Patch_SetStyle( patchMesh_t *p, int nStyle ){
211 p->type = ( p->type & PATCH_TYPEMASK ) | nStyle;
219 int Patch_MemorySize( patchMesh_t *p ){
227 InterpolateInteriorPoints
230 void InterpolateInteriorPoints( patchMesh_t *p ){
234 for ( i = 0 ; i < p->width ; i += 2 )
237 next = ( i == p->width - 1 ) ? 1 : ( i + 1 ) % p->width;
238 prev = ( i == 0 ) ? p->width - 2 : i - 1;
242 next = ( i + 1 ) % p->width;
243 prev = p->width - 2; // joined wrap case
245 else if ( i == p->width - 1 ) {
251 next = ( i + 1 ) % p->width;
256 for ( j = 0 ; j < p->height ; j++ )
258 for ( k = 0 ; k < 3 ; k++ )
260 p->ctrl[i][j].xyz[k] = ( p->ctrl[next][j].xyz[k] + p->ctrl[prev][j].xyz[k] ) * 0.5;
272 int neighbors[8][2] = {
273 {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1}
276 void Patch_MeshNormals( patchMesh_t *in ){
285 vec3_t around[8], temp;
287 qboolean wrapWidth, wrapHeight;
291 for ( i = 0 ; i < in->height ; i++ )
294 VectorSubtract( in->ctrl[0][i].xyz,
295 in->ctrl[in->width - 1][i].xyz, delta );
296 len = VectorLength( delta );
301 if ( i == in->height ) {
306 for ( i = 0 ; i < in->width ; i++ )
308 VectorSubtract( in->ctrl[i][0].xyz,
309 in->ctrl[i][in->height - 1].xyz, delta );
310 len = VectorLength( delta );
315 if ( i == in->width ) {
320 for ( i = 0 ; i < in->width ; i++ )
322 for ( j = 0 ; j < in->height ; j++ )
325 //--dv = reinterpret_cast<drawVert_t*>(in.ctrl[j*in.width+i]);
326 dv = &in->ctrl[i][j];
327 VectorCopy( dv->xyz, base );
328 for ( k = 0 ; k < 8 ; k++ )
330 VectorClear( around[k] );
333 for ( dist = 1 ; dist <= 3 ; dist++ )
335 x = i + neighbors[k][0] * dist;
336 y = j + neighbors[k][1] * dist;
339 x = in->width - 1 + x;
341 else if ( x >= in->width ) {
342 x = 1 + x - in->width;
347 y = in->height - 1 + y;
349 else if ( y >= in->height ) {
350 y = 1 + y - in->height;
354 if ( x < 0 || x >= in->width || y < 0 || y >= in->height ) {
355 break; // edge of patch
357 //--VectorSubtract( in.ctrl[y*in.width+x]->xyz, base, temp );
358 VectorSubtract( in->ctrl[x][y].xyz, base, temp );
359 if ( __VectorNormalize( temp, temp ) == 0 ) {
360 continue; // degenerate edge, get more dist
365 VectorCopy( temp, around[k] );
372 for ( k = 0 ; k < 8 ; k++ )
374 if ( !good[k] || !good[( k + 1 ) & 7] ) {
375 continue; // didn't get two points
377 CrossProduct( around[( k + 1 ) & 7], around[k], normal );
378 if ( __VectorNormalize( normal, normal ) == 0 ) {
381 VectorAdd( normal, sum, sum );
385 //printf("bad normal\n");
389 __VectorNormalize( sum, dv->normal );
402 void Patch_CalcBounds( patchMesh_t *p, vec3_t& vMin, vec3_t& vMax ){
403 vMin[0] = vMin[1] = vMin[2] = 99999;
404 vMax[0] = vMax[1] = vMax[2] = -99999;
407 for ( int w = 0; w < p->width; w++ )
409 for ( int h = 0; h < p->height; h++ )
411 for ( int j = 0; j < 3; j++ )
413 float f = p->ctrl[w][h].xyz[j];
430 void Brush_RebuildBrush( brush_t *b, vec3_t vMins, vec3_t vMaxs ){
440 for ( j = 0; j < 3; j++ )
442 if ( (int)vMins[j] == (int)vMaxs[j] ) {
449 for ( f = b->brush_faces ; f ; f = next )
458 b->brush_faces = NULL;
460 // left the last face so we can use its texdef
462 for ( i = 0 ; i < 3 ; i++ )
463 if ( vMaxs[i] < vMins[i] ) {
464 Error( "Brush_RebuildBrush: backwards" );
467 pts[0][0][0] = vMins[0];
468 pts[0][0][1] = vMins[1];
470 pts[1][0][0] = vMins[0];
471 pts[1][0][1] = vMaxs[1];
473 pts[2][0][0] = vMaxs[0];
474 pts[2][0][1] = vMaxs[1];
476 pts[3][0][0] = vMaxs[0];
477 pts[3][0][1] = vMins[1];
479 for ( i = 0 ; i < 4 ; i++ )
481 pts[i][0][2] = vMins[2];
482 pts[i][1][0] = pts[i][0][0];
483 pts[i][1][1] = pts[i][0][1];
484 pts[i][1][2] = vMaxs[2];
487 for ( i = 0 ; i < 4 ; i++ )
491 f->texdef.flags &= ~SURF_KEEP;
492 f->texdef.contents &= ~CONTENTS_KEEP;
493 // f->texdef.flags |= SURF_PATCH;
494 f->next = b->brush_faces;
498 VectorCopy( pts[j][1], f->planepts[0] );
499 VectorCopy( pts[i][1], f->planepts[1] );
500 VectorCopy( pts[i][0], f->planepts[2] );
505 f->texdef.flags &= ~SURF_KEEP;
506 f->texdef.contents &= ~CONTENTS_KEEP;
507 // f->texdef.flags |= SURF_PATCH;
508 f->next = b->brush_faces;
511 VectorCopy( pts[0][1], f->planepts[0] );
512 VectorCopy( pts[1][1], f->planepts[1] );
513 VectorCopy( pts[2][1], f->planepts[2] );
517 f->texdef.flags &= ~SURF_KEEP;
518 f->texdef.contents &= ~CONTENTS_KEEP;
519 // f->texdef.flags |= SURF_PATCH;
520 f->next = b->brush_faces;
523 VectorCopy( pts[2][0], f->planepts[0] );
524 VectorCopy( pts[1][0], f->planepts[1] );
525 VectorCopy( pts[0][0], f->planepts[2] );
530 void WINAPI Patch_Rebuild( patchMesh_t *p ){
532 Patch_CalcBounds( p, vMin, vMax );
533 Brush_RebuildBrush( p->pSymbiot, vMin, vMax );
541 adds a patch brush and ties it to this patch id
543 brush_t* AddBrushForPatch( patchMesh_t *pm, bool bLinkToWorld ){
544 // find the farthest points in x,y,z
546 Patch_CalcBounds( pm, vMin, vMax );
548 for ( int j = 0; j < 3; j++ )
550 if ( vMin[j] == vMax[j] ) {
556 brush_t *b = Brush_Create( vMin, vMax, &g_qeglobals.d_texturewin.texdef );
558 // FIXME: this entire type of linkage needs to be fixed
559 b->patchBrush = true;
562 pm->bSelected = false;
563 pm->bOverlay = false;
567 if ( bLinkToWorld ) {
568 Brush_AddToList( b, &active_brushes );
569 Entity_LinkBrush( world_entity, b );
576 void Patch_SetPointIntensities( int n ){
578 patchMesh_t *p = patchMeshes[n];
579 for ( int i = 0; i < p->width; i++ )
581 for ( int j = 0; j < p->height; j++ )
589 // very approximate widths and heights
596 float Patch_Width( patchMesh_t *p ){
598 for ( int i = 0; i < p->width - 1; i++ )
601 VectorSubtract( p->ctrl[i][0].xyz, p->ctrl[i + 1][0].xyz, vTemp );
602 f += VectorLength( vTemp );
607 float Patch_WidthDistanceTo( patchMesh_t *p, int j ){
609 for ( int i = 0; i < j; i++ )
612 VectorSubtract( p->ctrl[i][0].xyz, p->ctrl[i + 1][0].xyz, vTemp );
613 f += VectorLength( vTemp );
625 float Patch_Height( patchMesh_t *p ){
627 for ( int i = 0; i < p->height - 1; i++ )
630 VectorSubtract( p->ctrl[0][i].xyz, p->ctrl[0][i + 1].xyz, vTemp );
631 f += VectorLength( vTemp );
636 float Patch_HeightDistanceTo( patchMesh_t *p, int j ){
638 for ( int i = p->height - 1; i > j; i-- )
641 VectorSubtract( p->ctrl[0][i].xyz, p->ctrl[0][i - 1].xyz, vTemp ); // reverse order for T coords
642 f += VectorLength( vTemp );
653 texture = TotalTexture * LengthToThisControlPoint / TotalControlPointLength
655 dist( this control point to first control point ) / dist ( last control pt to first)
657 void WINAPI Patch_Naturalize( patchMesh_t *p ){
658 int nWidth = (int)( p->d_texture->width * g_PrefsDlg.m_fDefTextureScale );
659 int nHeight = (int)( p->d_texture->height * g_PrefsDlg.m_fDefTextureScale );
660 float fPWidth = Patch_Width( p );
661 float fPHeight = Patch_Height( p );
664 for ( int i = 0; i < p->width ; i++ )
667 for ( int j = p->height - 1; j >= 0 ; j-- )
669 p->ctrl[i][j].st[0] = ( fPWidth / nWidth ) * xAccum / fPWidth;
670 p->ctrl[i][j].st[1] = ( fPHeight / nHeight ) * yAccum / fPHeight;
671 yAccum = Patch_HeightDistanceTo( p,j - 1 );
672 //p->ctrl[i][j][3] = (fPWidth / nWidth) * (float)i / (p->width - 1);
673 //p->ctrl[i][j][4] = (fPHeight/ nHeight) * (float)j / (p->height - 1);
675 xAccum = Patch_WidthDistanceTo( p,i + 1 );
683 VectorCopy(p->ctrl[1][0], p->ctrl[1][1]);
688 VectorCopy(p->ctrl[3][0], p->ctrl[4][1]);
689 VectorCopy(p->ctrl[2][0], p->ctrl[3][1]);
690 VectorCopy(p->ctrl[2][0], p->ctrl[2][1]);
691 VectorCopy(p->ctrl[2][0], p->ctrl[1][1]);
692 VectorCopy(p->ctrl[1][0], p->ctrl[0][1]);
693 VectorCopy(p->ctrl[1][0], p->ctrl[0][2]);
694 VectorCopy(p->ctrl[1][0], p->ctrl[1][2]);
695 VectorCopy(p->ctrl[2][0], p->ctrl[2][2]);
696 VectorCopy(p->ctrl[3][0], p->ctrl[3][2]);
697 VectorCopy(p->ctrl[3][0], p->ctrl[4][2]);
742 int Interior3By[][2] =
747 int Interior5By[][2] =
760 int Interior3ByCount = sizeof( Interior3By ) / sizeof( int[2] );
761 int Interior5ByCount = sizeof( Interior5By ) / sizeof( int[2] );
763 extern int Plane_FromPoints( vec3_t p1, vec3_t p2, vec3_t p3, plane_t *plane );
764 // the bFaceCycle only means we are going through a patch cycling loop
765 // then we rely on g_vCycleCapNormal to compute the cap
767 void Patch_CapTexture( patchMesh_t *p, bool bFaceCycle = false ){
768 vec3_t vProjection, vX, vY;
769 qtexture_t *texture = p->pShader->getTexture();
770 plane_t Plane1, Plane2, Plane3;
774 VectorCopy( g_vCycleCapNormal, vProjection );
779 VectorClear( vProjection );
781 // find normal for plane from first 3 corner points
782 if ( !Plane_FromPoints( p->ctrl[0][0].xyz,p->ctrl[0][p->height - 1].xyz,p->ctrl[p->width - 1][p->height - 1].xyz,&Plane1 ) ) {
783 VectorClear( Plane3.normal );
787 // find normal for plane from next 3 corner points
788 if ( !Plane_FromPoints( p->ctrl[p->width - 1][p->height - 1].xyz,p->ctrl[p->width - 1][0].xyz,p->ctrl[0][0].xyz,&Plane2 ) ) {
790 VectorCopy( Plane1.normal, Plane3.normal );
791 Plane3.dist = Plane1.dist;
798 // find average plane for all 4 corner points
799 for ( int n = 0; n <= 2; n++ )
801 Plane3.normal[n] = ( Plane1.normal[n] + Plane2.normal[n] ) / 2;
803 Plane3.dist = ( Plane1.dist + Plane2.dist ) / 2;
807 VectorCopy( Plane2.normal, Plane3.normal );
808 Plane3.dist = Plane2.dist;
812 // get best axis for projection from average plane
813 //Sys_Printf("surface normal1: (%f,%f,%f)\n",Plane1.normal[0],Plane1.normal[1],Plane1.normal[0]);
814 //Sys_Printf("surface normal2: (%f,%f,%f)\n",Plane2.normal[0],Plane2.normal[1],Plane2.normal[0]);
815 //Sys_Printf("surface normal3: (%f,%f,%f)\n",Plane3.normal[0],Plane3.normal[1],Plane3.normal[0]);
816 TextureAxisFromPlane( &Plane3, vX, vY );
819 for ( int w = 0; w < p->width; w++ )
821 for ( int h = 0; h < p->height; h++ )
823 if ( vProjection[2] == 1.0f || ( vX[0] == 1.0f && vY[1] == -1.0f ) ) {
824 p->ctrl[w][h].st[0] = p->ctrl[w][h].xyz[0] / ( texture->width * g_PrefsDlg.m_fDefTextureScale );
825 p->ctrl[w][h].st[1] = p->ctrl[w][h].xyz[1] / ( texture->height * g_PrefsDlg.m_fDefTextureScale ) * -1;
827 else if ( vProjection[0] == 1.0f || ( vX[1] == 1.0f && vY[2] == -1.0f ) ) {
828 p->ctrl[w][h].st[0] = p->ctrl[w][h].xyz[1] / ( texture->width * g_PrefsDlg.m_fDefTextureScale );
829 p->ctrl[w][h].st[1] = p->ctrl[w][h].xyz[2] / ( texture->height * g_PrefsDlg.m_fDefTextureScale ) * -1;
831 else if ( vProjection[1] == 1.0f || ( vX[0] == 1.0f && vY[2] == -1.0f ) ) {
832 p->ctrl[w][h].st[0] = p->ctrl[w][h].xyz[0] / ( texture->width * g_PrefsDlg.m_fDefTextureScale );
833 p->ctrl[w][h].st[1] = p->ctrl[w][h].xyz[2] / ( texture->height * g_PrefsDlg.m_fDefTextureScale ) * -1;
835 //Sys_Printf("(%i,%i) (%f,%f,%f) (%f,%f) %f\n",w,h,
836 // p->ctrl[w][h].xyz[0],p->ctrl[w][h].xyz[1],p->ctrl[w][h].xyz[2],
837 // p->ctrl[w][h].st[0],p->ctrl[w][h].st[1],p->ctrl[w][h].normal);
840 // make sure it will rebuild
844 void FillPatch( patchMesh_t *p, vec3_t v ){
845 for ( int i = 0; i < p->width; i++ )
847 for ( int j = 0; j < p->height; j++ )
849 VectorCopy( v, p->ctrl[i][j].xyz );
854 // temporarily moved function to allow use in Cap() and CapSpecial()
855 void patchInvert( patchMesh_t *p ){
858 for ( int i = 0 ; i < p->width ; i++ )
860 for ( int j = 0; j < p->height / 2; j++ )
862 memcpy( &vertTemp, &p->ctrl[i][p->height - 1 - j], sizeof( drawVert_t ) );
863 memcpy( &p->ctrl[i][p->height - 1 - j], &p->ctrl[i][j], sizeof( drawVert_t ) );
864 memcpy( &p->ctrl[i][j], &vertTemp, sizeof( drawVert_t ) );
869 brush_t* Cap( patchMesh_t *pParent, bool bByColumn, bool bFirst ){
876 // make a generic patch
877 if ( pParent->width <= 9 ) {
878 b = Patch_GenericMesh( 3, 3, 2, false );
882 b = Patch_GenericMesh( 5, 5, 2, false );
887 Sys_Printf( "Unable to cap. You may need to ungroup the patch.\n" );
892 p->type |= PATCH_CAP;
894 vMin[0] = vMin[1] = vMin[2] = 9999;
895 vMax[0] = vMax[1] = vMax[2] = -9999;
897 // we seam the column edge, FIXME: this might need to be able to seem either edge
899 int nSize = ( bByColumn ) ? pParent->width : pParent->height;
900 int nIndex = ( bFirst ) ? 0 : ( bByColumn ) ? pParent->height - 1 : pParent->width - 1;
902 FillPatch( p, pParent->ctrl[0][nIndex].xyz );
904 for ( i = 0; i < nSize; i++ )
908 VectorCopy( pParent->ctrl[i][nIndex].xyz, p->ctrl[Index3By[i][0]][Index3By[i][1]].xyz );
912 VectorCopy( pParent->ctrl[i][nIndex].xyz, p->ctrl[Index5By[i][0]][Index5By[i][1]].xyz );
918 VectorCopy( pParent->ctrl[nIndex][i].xyz, p->ctrl[Index3By[i][0]][Index3By[i][1]].xyz );
922 VectorCopy( pParent->ctrl[nIndex][i].xyz, p->ctrl[Index5By[i][0]][Index5By[i][1]].xyz );
926 for ( j = 0; j < 3; j++ )
928 float f = ( bSmall ) ? p->ctrl[Index3By[i][0]][Index3By[i][1]].xyz[j] : p->ctrl[Index5By[i][0]][Index5By[i][1]].xyz[j];
939 for ( j = 0; j < 3; j++ )
941 vTemp[j] = vMin[j] + fabs( ( vMax[j] - vMin[j] ) * 0.5 );
943 int nCount = ( bSmall ) ? Interior3ByCount : Interior5ByCount;
944 for ( j = 0; j < nCount; j++ )
947 VectorCopy( vTemp, p->ctrl[Interior3By[j][0]][Interior3By[j][1]].xyz );
951 VectorCopy( vTemp, p->ctrl[Interior5By[j][0]][Interior5By[j][1]].xyz );
961 for (i = 0; i < p->width; i++)
963 for (j = 0; j < p->height / 2; j++)
965 memcpy(&vertTemp, &p->ctrl[i][p->height - 1- j], sizeof (drawVert_t));
966 memcpy(&p->ctrl[i][p->height - 1 - j], &p->ctrl[i][j], sizeof(drawVert_t));
967 memcpy(&p->ctrl[i][j], &vertTemp, sizeof(drawVert_t));
974 Patch_CapTexture( p );
978 brush_t* CapSpecial( patchMesh_t *pParent, int nType, bool bFirst ){
982 vec3_t vMin, vMax, vTemp;
985 if ( nType == IENDCAP ) {
986 b = Patch_GenericMesh( 5, 3, 2, false );
989 b = Patch_GenericMesh( 3, 3, 2, false );
993 Sys_Printf( "Unable to cap. Make sure you ungroup before re-capping." );
998 p->type |= PATCH_CAP;
1000 vMin[0] = vMin[1] = vMin[2] = 9999;
1001 vMax[0] = vMax[1] = vMax[2] = -9999;
1003 // int nSize = pParent->width;
1004 int nIndex = ( bFirst ) ? 0 : pParent->height - 1;
1006 // parent bounds are used for some things
1007 Patch_CalcBounds( pParent, vMin, vMax );
1009 for ( j = 0; j < 3; j++ )
1011 vTemp[j] = vMin[j] + fabs( ( vMax[j] - vMin[j] ) * 0.5 );
1014 if ( nType == IBEVEL ) {
1015 VectorCopy( pParent->ctrl[0][nIndex].xyz, p->ctrl[0][0].xyz );
1016 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[0][2].xyz );
1017 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[0][1].xyz );
1018 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[2][2].xyz );
1019 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[1][0].xyz );
1020 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[1][1].xyz );
1021 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[1][2].xyz );
1022 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[2][0].xyz );
1023 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[2][1].xyz );
1025 else if ( nType == BEVEL ) {
1026 vec3_t p1, p2, p3, p4; //, temp, dir;
1028 VectorCopy( pParent->ctrl[0][nIndex].xyz, p3 );
1029 VectorCopy( pParent->ctrl[1][nIndex].xyz, p1 );
1030 VectorCopy( pParent->ctrl[2][nIndex].xyz, p2 );
1032 //Sys_Printf("CapSpecial() p1: %f %f %f\n",p1[0],p1[1],p1[2]);
1033 //Sys_Printf("CapSpecial() p2: %f %f %f\n",p2[0],p2[1],p2[2]);
1034 //Sys_Printf("CapSpecial() p3: %f %f %f\n",p3[0],p3[1],p3[2]);
1036 VectorSubtract( p2, p1, p4 );
1037 VectorAdd( p3, p4, p4 );
1038 // spog - use opposite-point-on-parallelogram to find p4
1040 VectorSubtract(p3, p2, dir);
1041 VectorNormalize(dir);
1042 VectorSubtract(p1, p2, temp);
1043 vec_t dist = _DotProduct(temp, dir);
1044 VectorScale(dir, dist, temp);
1045 VectorAdd(p2, temp, temp);
1046 VectorSubtract(temp, p1, temp);
1047 VectorScale(temp, 2, temp);
1048 VectorAdd(p1, temp, p4);
1051 //Sys_Printf("CapSpecial() p1: %f %f %f\n",p1[0],p1[1],p1[2]);
1052 //Sys_Printf("CapSpecial() p2: %f %f %f\n",p2[0],p2[1],p2[2]);
1053 //Sys_Printf("CapSpecial() p3: %f %f %f\n",p3[0],p3[1],p3[2]);
1054 //Sys_Printf("CapSpecial() p4: %f %f %f\n",p4[0],p4[1],p4[2]);
1056 VectorCopy( p4, p->ctrl[0][0].xyz );
1057 VectorCopy( p4, p->ctrl[1][0].xyz );
1058 VectorCopy( p4, p->ctrl[0][1].xyz );
1059 VectorCopy( p4, p->ctrl[1][1].xyz );
1060 VectorCopy( p4, p->ctrl[0][2].xyz );
1061 VectorCopy( p4, p->ctrl[1][2].xyz );
1062 VectorCopy( p2, p->ctrl[2][0].xyz );
1063 VectorCopy( p1, p->ctrl[2][1].xyz );
1064 VectorCopy( p3, p->ctrl[2][2].xyz );
1067 else if ( nType == ENDCAP ) {
1068 VectorAdd( pParent->ctrl[4][nIndex].xyz, pParent->ctrl[0][nIndex].xyz, vTemp );
1069 VectorScale( vTemp, 0.5, vTemp );
1070 VectorCopy( pParent->ctrl[0][nIndex].xyz, p->ctrl[0][0].xyz );
1071 VectorCopy( vTemp, p->ctrl[1][0].xyz );
1072 VectorCopy( pParent->ctrl[4][nIndex].xyz, p->ctrl[2][0].xyz );
1074 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[0][2].xyz );
1075 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[1][2].xyz );
1076 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[2][2].xyz );
1077 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[1][1].xyz );
1079 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[0][1].xyz );
1080 VectorCopy( pParent->ctrl[3][nIndex].xyz, p->ctrl[2][1].xyz );
1084 VectorCopy( pParent->ctrl[4][nIndex].xyz, p->ctrl[0][0].xyz );
1085 VectorCopy( pParent->ctrl[3][nIndex].xyz, p->ctrl[1][0].xyz );
1086 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[2][0].xyz );
1087 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[3][0].xyz );
1088 VectorCopy( pParent->ctrl[0][nIndex].xyz, p->ctrl[4][0].xyz );
1090 VectorCopy( pParent->ctrl[3][nIndex].xyz, p->ctrl[0][1].xyz );
1091 VectorCopy( pParent->ctrl[3][nIndex].xyz, p->ctrl[1][1].xyz );
1092 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[2][1].xyz );
1093 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[3][1].xyz );
1094 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[4][1].xyz );
1096 VectorCopy( pParent->ctrl[3][nIndex].xyz, p->ctrl[0][2].xyz );
1097 VectorCopy( pParent->ctrl[3][nIndex].xyz, p->ctrl[1][2].xyz );
1098 VectorCopy( pParent->ctrl[2][nIndex].xyz, p->ctrl[2][2].xyz );
1099 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[3][2].xyz );
1100 VectorCopy( pParent->ctrl[1][nIndex].xyz, p->ctrl[4][2].xyz );
1105 drawVert_t vertTemp;
1106 for ( i = 0; i < p->width; i++ )
1108 for ( j = 0; j < p->height / 2; j++ )
1110 memcpy( &vertTemp, &p->ctrl[i][p->height - 1 - j], sizeof( drawVert_t ) );
1111 memcpy( &p->ctrl[i][p->height - 1 - j], &p->ctrl[i][j], sizeof( drawVert_t ) );
1112 memcpy( &p->ctrl[i][j], &vertTemp, sizeof( drawVert_t ) );
1117 //--Patch_CalcBounds(p, vMin, vMax);
1118 //--Brush_RebuildBrush(p->pSymbiot, vMin, vMax);
1120 Patch_CapTexture( p );
1124 void Patch_CapCurrent(){
1125 patchMesh_t *pParent = NULL;
1127 brush_t *pCap = NULL;
1128 b[0] = b[1] = b[2] = b[3] = NULL;
1130 bool b_GroupResult = TRUE;
1132 if ( !QE_SingleBrush( true ) ) {
1133 Sys_Printf( "Patch_CapCurrent: you must have a single patch selected\n" );
1138 for ( brush_t *pb = selected_brushes.next ; pb != NULL && pb != &selected_brushes ; pb = pb->next )
1140 if ( pb->patchBrush ) {
1141 pParent = pb->pPatch;
1142 // decide which if any ends we are going to cap
1143 // if any of these compares hit, it is a closed patch and as such
1144 // the generic capping will work.. if we do not find a closed edge
1145 // then we need to ask which kind of cap to add
1146 if ( VectorCompare( pParent->ctrl[0][0].xyz, pParent->ctrl[pParent->width - 1][0].xyz ) ) {
1147 pCap = Cap( pParent, true, false );
1148 if ( pCap != NULL ) {
1152 if ( VectorCompare( pParent->ctrl[0][pParent->height - 1].xyz, pParent->ctrl[pParent->width - 1][pParent->height - 1].xyz ) ) {
1153 pCap = Cap( pParent, true, true );
1154 if ( pCap != NULL ) {
1158 if ( VectorCompare( pParent->ctrl[0][0].xyz, pParent->ctrl[0][pParent->height - 1].xyz ) ) {
1159 pCap = Cap( pParent, false, false );
1160 if ( pCap != NULL ) {
1164 if ( VectorCompare( pParent->ctrl[pParent->width - 1][0].xyz, pParent->ctrl[pParent->width - 1][pParent->height - 1].xyz ) ) {
1165 pCap = Cap( pParent, false, true );
1166 if ( pCap != NULL ) {
1174 // if we did not cap anything with the above tests
1175 if ( nIndex == 0 ) {
1178 if ( DoCapDlg( &type, &b_GroupResult ) == IDOK ) {
1179 b[nIndex++] = CapSpecial( pParent, type, false );
1180 b[nIndex++] = CapSpecial( pParent, type, true );
1185 while ( nIndex > 0 )
1189 Select_Brush( b[nIndex] );
1192 // Gef: Added toggle for capped patch func_group
1193 if ( b_GroupResult ) {
1194 entity_t *e = Entity_Alloc();
1195 SetKeyValue( e, "classname", "func_group" );
1196 SetKeyValue( e, "type", "patchCapped" );
1197 Select_GroupEntity( e );
1198 Entity_AddToList( e, &entities );
1209 void Patch_BrushToMesh( bool bCone, bool bBevel, bool bEndcap, bool bSquare, int nHeight ){
1214 if ( !QE_SingleBrush() ) {
1218 b = selected_brushes.next;
1222 p->d_texture = b->brush_faces->d_texture;
1223 p->pShader = b->brush_faces->pShader;
1225 p->height = nHeight;
1227 p->type = PATCH_CYLINDER;
1228 if ( bBevel & !bSquare ) {
1229 p->type = PATCH_BEVEL;
1231 int nStep = (int)( ( b->maxs[2] - b->mins[2] ) / ( p->height - 1 ) );
1232 int nStart = (int)( b->mins[2] );
1233 for ( i = 0; i < p->height; i++ )
1235 p->ctrl[0][i].xyz[0] = b->mins[0];
1236 p->ctrl[0][i].xyz[1] = b->mins[1];
1237 p->ctrl[0][i].xyz[2] = nStart;
1239 p->ctrl[1][i].xyz[0] = b->maxs[0];
1240 p->ctrl[1][i].xyz[1] = b->mins[1];
1241 p->ctrl[1][i].xyz[2] = nStart;
1243 p->ctrl[2][i].xyz[0] = b->maxs[0];
1244 p->ctrl[2][i].xyz[1] = b->maxs[1];
1245 p->ctrl[2][i].xyz[2] = nStart;
1249 else if ( bEndcap & !bSquare ) {
1250 p->type = PATCH_ENDCAP;
1252 int nStep = (int)( ( b->maxs[2] - b->mins[2] ) / ( p->height - 1 ) );
1253 int nStart = (int)( b->mins[2] );
1254 for ( i = 0; i < p->height; i++ )
1256 p->ctrl[0][i].xyz[0] = b->mins[0];
1257 p->ctrl[0][i].xyz[1] = b->mins[1];
1258 p->ctrl[0][i].xyz[2] = nStart;
1260 p->ctrl[1][i].xyz[0] = b->mins[0];
1261 p->ctrl[1][i].xyz[1] = b->maxs[1];
1262 p->ctrl[1][i].xyz[2] = nStart;
1264 p->ctrl[2][i].xyz[0] = b->mins[0] + ( ( b->maxs[0] - b->mins[0] ) * 0.5 );
1265 p->ctrl[2][i].xyz[1] = b->maxs[1];
1266 p->ctrl[2][i].xyz[2] = nStart;
1268 p->ctrl[3][i].xyz[0] = b->maxs[0];
1269 p->ctrl[3][i].xyz[1] = b->maxs[1];
1270 p->ctrl[3][i].xyz[2] = nStart;
1272 p->ctrl[4][i].xyz[0] = b->maxs[0];
1273 p->ctrl[4][i].xyz[1] = b->mins[1];
1274 p->ctrl[4][i].xyz[2] = nStart;
1281 p->ctrl[1][0].xyz[0] = b->mins[0];
1282 p->ctrl[1][0].xyz[1] = b->mins[1];
1284 p->ctrl[3][0].xyz[0] = b->maxs[0];
1285 p->ctrl[3][0].xyz[1] = b->mins[1];
1287 p->ctrl[5][0].xyz[0] = b->maxs[0];
1288 p->ctrl[5][0].xyz[1] = b->maxs[1];
1290 p->ctrl[7][0].xyz[0] = b->mins[0];
1291 p->ctrl[7][0].xyz[1] = b->maxs[1];
1293 for ( i = 1 ; i < p->width - 1 ; i += 2 )
1296 p->ctrl[i][0].xyz[2] = b->mins[2];
1298 VectorCopy( p->ctrl[i][0].xyz, p->ctrl[i][2].xyz );
1300 p->ctrl[i][2].xyz[2] = b->maxs[2];
1302 p->ctrl[i][1].xyz[0] = ( p->ctrl[i][0].xyz[0] + p->ctrl[i][2].xyz[0] ) * 0.5;
1303 p->ctrl[i][1].xyz[1] = ( p->ctrl[i][0].xyz[1] + p->ctrl[i][2].xyz[1] ) * 0.5;
1304 p->ctrl[i][1].xyz[2] = ( p->ctrl[i][0].xyz[2] + p->ctrl[i][2].xyz[2] ) * 0.5;
1306 InterpolateInteriorPoints( p );
1309 if ( bBevel || bEndcap ) {
1311 for ( i = 0; i < p->height; i++ )
1313 VectorCopy( p->ctrl[1][i].xyz, p->ctrl[2][i].xyz );
1314 VectorCopy( p->ctrl[7][i].xyz, p->ctrl[6][i].xyz );
1319 for ( i = 0; i < p->height; i++ )
1321 VectorCopy( p->ctrl[5][i].xyz, p->ctrl[4][i].xyz );
1322 VectorCopy( p->ctrl[1][i].xyz, p->ctrl[2][i].xyz );
1323 VectorCopy( p->ctrl[7][i].xyz, p->ctrl[6][i].xyz );
1324 VectorCopy( p->ctrl[8][i].xyz, p->ctrl[7][i].xyz );
1330 for ( i = 0; i < p->width - 1; i++ )
1332 for ( j = 0; j < p->height; j++ )
1334 VectorCopy( p->ctrl[i + 1][j].xyz, p->ctrl[i][j].xyz );
1337 for ( j = 0; j < p->height; j++ )
1339 VectorCopy( p->ctrl[0][j].xyz, p->ctrl[8][j].xyz );
1346 Patch_Naturalize( p );
1349 p->type = PATCH_CONE;
1350 float xc = ( b->maxs[0] + b->mins[0] ) * 0.5;
1351 float yc = ( b->maxs[1] + b->mins[1] ) * 0.5;
1353 for ( i = 0 ; i < p->width ; i++ )
1355 p->ctrl[i][2].xyz[0] = xc;
1356 p->ctrl[i][2].xyz[1] = yc;
1360 b = AddBrushForPatch( p );
1372 brush_t* Patch_GenericMesh( int nWidth, int nHeight, int nOrientation, bool bDeleteSource, bool bOverride ){
1375 if ( nHeight < 3 || nHeight > 15 || nWidth < 3 || nWidth > 15 ) {
1376 Sys_Printf( "Invalid patch width or height.\n" );
1380 if ( !bOverride && !QE_SingleBrush() ) {
1381 Sys_Printf( "Error: you must have a single brush selected\n" );
1385 patchMesh_t* p = MakeNewPatch();
1386 p->pShader = g_qeglobals.d_texturewin.pShader;
1387 p->d_texture = g_qeglobals.d_texturewin.pShader->getTexture();
1390 p->height = nHeight;
1391 p->type = PATCH_GENERIC;
1395 if ( nOrientation == 0 ) {
1399 else if ( nOrientation == 1 ) {
1403 brush_t *b = selected_brushes.next;
1404 // set the workzone to this brush, use it later to create the patch points
1405 UpdateWorkzone_ForBrush( b );
1407 int xStep = (int)( b->mins[nFirst] );
1408 float xAdj = fabs( ( b->maxs[nFirst] - b->mins[nFirst] ) / ( nWidth - 1 ) );
1409 float yAdj = fabs( ( b->maxs[nSecond] - b->mins[nSecond] ) / ( nHeight - 1 ) );
1411 for ( i = 0; i < nWidth; i++ )
1413 int yStep = (int)( b->mins[nSecond] );
1414 for ( j = 0; j < nHeight; j++ )
1416 p->ctrl[i][j].xyz[nFirst] = xStep;
1417 p->ctrl[i][j].xyz[nSecond] = yStep;
1418 // create patch based on workzone
1419 p->ctrl[i][j].xyz[nOrientation] = g_qeglobals.d_work_max[nOrientation];
1425 Patch_Naturalize( p );
1427 b = AddBrushForPatch( p );
1428 if ( bDeleteSource ) {
1434 //g_qeglobals.d_select_mode = sel_curvepoint;
1442 int PointInMoveList( float *pf ){
1443 for ( int i = 0; i < g_qeglobals.d_num_move_points; i++ )
1445 if ( pf == &g_qeglobals.d_move_points[i][0] ) {
1454 PointValueInMoveList
1457 int PointValueInMoveList( vec3_t v ){
1458 for ( int i = 0; i < g_qeglobals.d_num_move_points; i++ )
1460 if ( VectorCompare( v, g_qeglobals.d_move_points[i] ) ) {
1470 RemovePointFromMoveList
1473 void RemovePointFromMoveList( vec3_t v ){
1475 while ( ( n = PointValueInMoveList( v ) ) >= 0 )
1477 for ( int i = n; i < g_qeglobals.d_num_move_points - 1; i++ )
1479 g_qeglobals.d_move_points[i] = g_qeglobals.d_move_points[i + 1];
1481 g_qeglobals.d_num_move_points--;
1490 bool ColumnSelected( patchMesh_t* p, int nCol ){
1491 for ( int i = 0; i < p->height; i++ )
1493 if ( PointInMoveList( p->ctrl[nCol][i].xyz ) == -1 ) {
1505 void AddPoint( patchMesh_t* p, vec3_t v, bool bWeldOrDrill = true ){
1506 int nDim1 = ( g_pParentWnd->ActiveXY()->GetViewType() == YZ ) ? 1 : 0;
1507 int nDim2 = ( g_pParentWnd->ActiveXY()->GetViewType() == XY ) ? 1 : 2;
1508 g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = v;
1509 if ( ( g_bPatchWeld || g_bPatchDrillDown ) && bWeldOrDrill ) {
1510 for ( int i = 0 ; i < p->width ; i++ )
1512 for ( int j = 0 ; j < p->height ; j++ )
1514 if ( g_bPatchWeld ) {
1515 if ( VectorCompare( v, p->ctrl[i][j].xyz )
1516 && PointInMoveList( p->ctrl[i][j].xyz ) == -1 ) {
1517 g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = p->ctrl[i][j].xyz;
1521 if ( g_bPatchDrillDown && g_nPatchClickedView != W_CAMERA ) {
1522 if ( ( fabs( v[nDim1] - p->ctrl[i][j].xyz[nDim1] ) <= EQUAL_EPSILON )
1523 && ( fabs( v[nDim2] - p->ctrl[i][j].xyz[nDim2] ) <= EQUAL_EPSILON ) ) {
1524 if ( PointInMoveList( p->ctrl[i][j].xyz ) == -1 ) {
1525 g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = p->ctrl[i][j].xyz;
1540 void SelectRow( patchMesh_t* p, int nRow, bool bMulti ){
1542 g_qeglobals.d_num_move_points = 0;
1544 for ( int i = 0; i < p->width; i++ )
1546 AddPoint( p, p->ctrl[i][nRow].xyz, false );
1548 //Sys_Printf("Selected Row %d\n", nRow);
1556 void SelectColumn( patchMesh_t* p, int nCol, bool bMulti ){
1558 g_qeglobals.d_num_move_points = 0;
1560 for ( int i = 0; i < p->height; i++ )
1562 AddPoint( p, p->ctrl[nCol][i].xyz, false );
1564 //Sys_Printf("Selected Col %d\n", nCol);
1573 void AddPatchMovePoint( vec3_t v, bool bMulti, bool bFull ){
1574 if ( !g_bSameView && !bMulti && !bFull ) {
1576 //return; // was causing odd behaviour on patch vertex selection
1579 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
1581 if ( pb->patchBrush ) {
1582 patchMesh_t* p = pb->pPatch;
1583 for ( int i = 0 ; i < p->width ; i++ )
1585 for ( int j = 0 ; j < p->height ; j++ )
1587 if ( VectorCompare( v, p->ctrl[i][j].xyz ) ) {
1588 if ( PointInMoveList( p->ctrl[i][j].xyz ) == -1 ) {
1589 if ( bFull ) { // if we want the full row/col this is on
1590 SelectColumn( p, i, bMulti );
1595 g_qeglobals.d_num_move_points = 0;
1597 AddPoint( p, p->ctrl[i][j].xyz );
1598 //Sys_Printf("Selected col:row %d:%d\n", i, j);
1606 if ( ColumnSelected( p, i ) ) {
1607 SelectRow( p, j, bMulti );
1611 SelectColumn( p, i, bMulti );
1617 // g_qeglobals.d_num_move_points = 0;
1618 // AddPoint(p, p->ctrl[i][j].xyz);
1620 if ( bMulti ) { // if (g_bSameView) // this is not having desired effect
1621 RemovePointFromMoveList( v );
1634 Patch_UpdateSelected
1637 void Patch_UpdateSelected( vec3_t vMove ){
1639 for ( i = 0 ; i < g_qeglobals.d_num_move_points ; i++ )
1641 VectorAdd( g_qeglobals.d_move_points[i], vMove, g_qeglobals.d_move_points[i] );
1642 if ( g_qeglobals.d_num_move_points == 1 ) {
1646 //--patchMesh_t* p = &patchMeshes[g_nSelectedPatch];
1647 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
1649 if ( pb->patchBrush ) {
1650 patchMesh_t* p = pb->pPatch;
1652 #if 0 //moving to SelectCurvePointByRay
1653 g_qeglobals.d_numpoints = 0;
1654 for ( i = 0 ; i < p->width ; i++ )
1656 for ( j = 0 ; j < p->height ; j++ )
1658 VectorCopy( p->ctrl[i][j].xyz, g_qeglobals.d_points[g_qeglobals.d_numpoints] );
1659 if ( g_qeglobals.d_numpoints < MAX_POINTS - 1 ) {
1660 g_qeglobals.d_numpoints++;
1666 Patch_CalcBounds( p, vMin, vMax );
1667 Brush_RebuildBrush( p->pSymbiot, vMin, vMax );
1670 //Brush_Free(p->pSymbiot);
1671 //Select_Brush(AddBrushForPatch(g_nSelectedPatch));
1681 void SampleSinglePatch( float ctrl[3][3][5], float u, float v, float out[5] ) {
1686 // find the control points for the v coordinate
1687 for ( vPoint = 0 ; vPoint < 3 ; vPoint++ )
1689 for ( axis = 0 ; axis < 5 ; axis++ )
1694 a = ctrl[0][vPoint][axis];
1695 b = ctrl[1][vPoint][axis];
1696 c = ctrl[2][vPoint][axis];
1701 vCtrl[vPoint][axis] = qA * u * u + qB * u + qC;
1705 // interpolate the v value
1706 for ( axis = 0 ; axis < 5 ; axis++ )
1718 out[axis] = qA * v * v + qB * v + qC;
1722 //spog - Curve LOD stuff starts
1724 float ShadeForNormal( vec3_t normal ){
1733 // quick diffuse shading
1734 f = DotProduct( L, normal );
1737 f = ( f + 1 ) / 4.0f;
1738 //if (f < 0.0f) f = 0.0f;
1745 void ShadeVertex( drawVert_t &p ){
1746 p.lightmap[0] = ShadeForNormal( p.normal );
1750 void Patch_DrawNormals( patchMesh_t *patch ){
1754 qglBegin( GL_LINES );
1755 for ( col = 0; col < patch->width; col++ )
1757 for ( row = 0; row < patch->height; row++ )
1759 VectorAdd( patch->ctrl[col][row].xyz, patch->ctrl[col][row].normal, vNormal );
1760 qglVertex3fv( patch->ctrl[col][row].xyz );
1761 qglVertex3fv( vNormal );
1768 // take an array of three drawVerts, and the addresses of three more drawVerts
1769 // interpolate new XYZST values from the three drawVerts, these are:
1770 // the left sub-control-point, the right sub-control-point and the midpoint of the curve respectively
1771 // store these values in the drawVerts passed to the function
1772 void Patch_CurveSplit( drawVert_t *vCurve[3], drawVert_t &pLeft, drawVert_t &pRight, drawVert_t &pMid, float u ){
1776 drawVert_t v1, v2, v3;
1779 for ( i = 0; i < 3; i++ )
1782 v1.xyz[i] = vCurve[1]->xyz[i] - vCurve[0]->xyz[i];
1783 v2.xyz[i] = vCurve[2]->xyz[i] - vCurve[1]->xyz[i];
1786 pLeft.xyz[i] = vCurve[0]->xyz[i] + v1.xyz[i];
1787 pRight.xyz[i] = vCurve[1]->xyz[i] + v2.xyz[i];
1789 v3.xyz[i] = pRight.xyz[i] - pLeft.xyz[i];
1791 pMid.xyz[i] = pLeft.xyz[i] + v3.xyz[i];
1793 // normal (weighted average) // no, that's b0rked
1794 //a = 1 / u; // total
1795 //b = u * a; // component 2
1796 //a = u - b; // component 1
1797 //pMid.normal[i] = u * ((vCurve[0]->normal[i] * b) + (vCurve[2]->normal[i] * a));
1804 v1.st[i] = vCurve[1]->st[i] - vCurve[0]->st[i];
1805 v2.st[i] = vCurve[2]->st[i] - vCurve[1]->st[i];
1808 pLeft.st[i] = vCurve[0]->st[i] + v1.st[i];
1809 pRight.st[i] = vCurve[1]->st[i] + v2.st[i];
1811 v3.st[i] = pRight.st[i] - pLeft.st[i];
1813 pMid.st[i] = pLeft.st[i] + v3.st[i];
1817 // take an array of three points, return an index representing the curvature of those three points
1818 // return zero if the curve is a straight line, unless the midpoint is not between the endpoints
1819 float Patch_CurveIndex( vec3_t vCurve[] ){
1820 vec3_t vTemp, v1, v2, v3, vClear;
1825 VectorClear( vClear );
1827 VectorSubtract( vCurve[2], vCurve[0], vTemp );
1828 VectorSubtract( vCurve[1], vCurve[0], v1 );
1829 VectorSubtract( vCurve[2], vCurve[1], v2 );
1831 if ( VectorCompare( v1, vClear ) || VectorCompare( vTemp, v1 ) ) { // return 0 if 1->2 == 0 or 1->2 == 1->3
1835 VectorNormalize( v1, v1 );
1836 VectorNormalize( v2, v2 );
1837 if ( VectorCompare( v1, v2 ) ) {
1841 VectorCopy( vTemp, v3 );
1842 width = VectorNormalize( v3, v3 );
1844 if ( VectorCompare( v1, v3 ) && VectorCompare( v2, v3 ) ) {
1848 dot = DotProduct( v1, v2 );
1850 angle = acos( dot ) / Q_PI;
1852 index = width * angle;
1858 // create a new tree root, give it the coordinate values of the drawVert
1859 // return a pointer to the new tree root
1860 BTNode_t *BTree_Create( drawVert_t info ){
1861 BTNode_t *BTree = new BTNode_t;
1862 BTree->left = BTree->right = NULL;
1863 VectorCopy( info.xyz, BTree->info.xyz );
1864 VectorCopy( info.xyz, BTree->vMid.xyz );
1865 for ( int i = 0; i < 2; i++ )
1867 BTree->info.st[i] = info.st[i];
1868 BTree->vMid.st[i] = info.st[i];
1873 // take ownership of the subtree
1874 // delete the entire subtree
1875 // return a NULL pointer
1876 BTNode_t *BTree_Delete( BTNode_t *pBT ){
1877 if ( pBT != NULL ) {
1878 BTree_Delete( pBT->left );
1879 BTree_Delete( pBT->right );
1885 // NOT currently used
1886 BTNode_t *BTree_Clear( BTNode_t *pBT, bool bFirst = true ){
1887 if ( pBT != NULL ) {
1888 BTree_Clear( pBT->left, false );
1889 BTree_Clear( pBT->right, false );
1897 // take a pointer to the last item added to the list (this can also be a NULL pointer)
1898 // take a pointer to the root of a subtree, and the patch points to the left and right of it
1899 // add a new item to the subtree list, and add the subtree and its adjacent points to the new item
1900 // return a pointer to the last item added to the subtree list
1901 BTreeList_t *BTree_AddToList( BTreeList_t *pBTList, BTNode_t *pBT, drawVert_t &pLeft, drawVert_t &pRight ){
1902 BTreeList_t *newBTList = new BTreeList_t;
1903 newBTList->next = pBTList;
1904 newBTList->pBT = pBT;
1905 VectorCopy( pLeft.xyz, newBTList->vLeft.xyz );
1906 VectorCopy( pRight.xyz, newBTList->vRight.xyz );
1907 VectorCopy( pLeft.normal, newBTList->vLeft.normal );
1908 VectorCopy( pRight.normal, newBTList->vRight.normal );
1909 for ( int i = 0; i < 2; i++ )
1911 newBTList->vLeft.st[i] = pLeft.st[i];
1912 newBTList->vRight.st[i] = pRight.st[i];
1917 // NOT currently used, subtrees are now stored on the patch
1918 // take ownership of the subtree list
1919 // delete the entire list and the subtrees it points to
1920 // return a NULL pointer
1921 BTreeList_t *BTree_DeleteList( BTreeList_t *pBTList ){
1922 if ( pBTList != NULL ) {
1923 BTree_DeleteList( pBTList->next );
1924 pBTList->pBT = BTree_Delete( pBTList->pBT );
1930 // take ownership of the subtree list
1931 // delete the entire subtree list, but not the subtrees themselves
1932 // return a NULL pointer
1933 BTreeList_t *BTree_DeletePointerList( BTreeList_t *pBTList ){
1934 if ( pBTList != NULL ) {
1935 BTree_DeletePointerList( pBTList->next );
1941 // take a pointer to the last item added to the list of subtree lists
1942 // add a subtree list to the list
1943 // return a pointer to the last item added
1944 BTListList_t *BTree_AddListToList( BTListList_t *pBTListList, BTreeList_t *pBTList ){
1945 BTListList_t *newBTListList = new BTListList_t;
1946 newBTListList->next = pBTListList;
1947 newBTListList->list = pBTList;
1948 return newBTListList;
1952 // take ownership of the list of subtree lists
1953 // delete the entire list of lists, but not the subtrees themselves
1954 // return a NULL pointer
1955 BTListList_t *BTree_DeleteListFromList( BTListList_t *pBTListList ){
1956 if ( pBTListList != NULL ) {
1957 BTree_DeleteListFromList( pBTListList->next );
1958 pBTListList->list = BTree_DeletePointerList( pBTListList->list );
1964 // take a pointer to the last item in the list
1965 // add a NULL linker subtree to the list, setting the "flipped" flag using the left curvepoint normal .. er.. hacky?
1966 BTreeList_t *BTree_AddLinkToList( BTreeList_t *pBTList, bool bFlipped = false ){
1967 BTreeList_t *linkBTList = new BTreeList_t;
1968 linkBTList->pBT = NULL;
1969 linkBTList->next = pBTList;
1970 linkBTList->vLeft.normal[0] = ( bFlipped ) ? 1.0f : 0.0f;
1975 // take an array of three points and the address of a vector
1976 // store midpoint of the bezier curve formed by the three points, in the vector
1977 void Patch_BezierInterpolate( vec3_t vCurve[], vec3_t &pMid ){
1980 VectorSubtract( vCurve[2], vCurve[0], vTemp ); // Start->End
1981 for ( i = 0; i < 3; i++ )
1983 VectorAdd( vCurve[0], vTemp, vTemp ); // midpoint of Start->End
1985 VectorSubtract( vTemp, vCurve[1], vTemp ); // Mid->(midpoint of Start->End)
1986 for ( i = 0; i < 3; i++ )
1988 VectorAdd( vCurve[1], vTemp, pMid ); // midpoint of Mid->(midpoint of Start->End)
1992 // take a pointer to the list of subtrees, and a threshold value
1993 // generate REAL surface curvature for the subtree curves, using bezier interpolation
1994 // if any of the real curves has an index greater than the threshold, return true
1995 bool Patch_MostCurvedRow( BTreeList_t *pBTList, int threshold ){
1997 float index; //, bestindex = 0;
2002 for ( p = pBTList; p != NULL; p = p->next->next )
2005 VectorCopy( p->vLeft.xyz, vCurve[0] );
2006 VectorCopy( p->pBT->info.xyz, vCurve[1] );
2007 VectorCopy( p->vRight.xyz, vCurve[2] );
2009 index = Patch_CurveIndex( vCurve );
2010 if ( index > threshold ) {
2014 if ( p->next == NULL ) {
2018 if ( p->next->pBT == NULL ) {
2022 VectorCopy( p->vLeft.xyz, vCurve[0] );
2023 VectorCopy( p->next->vLeft.xyz, vCurve[1] );
2024 VectorCopy( p->next->next->vLeft.xyz, vCurve[2] );
2025 Patch_BezierInterpolate( vCurve, vRow[0] );
2027 VectorCopy( p->pBT->info.xyz, vCurve[0] );
2028 VectorCopy( p->next->pBT->info.xyz, vCurve[1] );
2029 VectorCopy( p->next->next->pBT->info.xyz, vCurve[2] );
2030 Patch_BezierInterpolate( vCurve, vRow[1] );
2032 VectorCopy( p->vRight.xyz, vCurve[0] );
2033 VectorCopy( p->next->vRight.xyz, vCurve[1] );
2034 VectorCopy( p->next->next->vRight.xyz, vCurve[2] );
2035 Patch_BezierInterpolate( vCurve, vRow[2] );
2037 index = Patch_CurveIndex( vRow );
2038 if ( index > threshold ) {
2046 // take a pointer to a list of subtrees.. each subtree in the list is a 3-point bezier curve formed by two endpoints owned by the list, and a midpoint subtree node owned by a patch.
2047 // if any of the subtrees are curved above a threshold, create a left and right subsubtree for each subtree in the list.
2048 // if a NULL linker subtree is found, check for an orientation flip - ie. an inverted LOD-match - and create a NULL subsubtree with the same orientation flip
2049 // this effectively generates trees for multiple patches at the same time.. the subtrees are always owned by their respective patches though
2050 void BTree_ListCurveRecurse( BTreeList_t *pBTList ){
2052 BTreeList_t *leftBTList, *rightBTList;
2053 //drawVert_t pLeft, pRight, pMid;
2054 drawVert_t *vCurve[3];
2057 bool bFlipped = false;
2059 if ( g_PrefsDlg.m_nSubdivisions >= 1 ) {
2060 threshold = g_PrefsDlg.m_nSubdivisions;
2066 leftBTList = rightBTList = NULL;
2068 if ( Patch_MostCurvedRow( pBTList, threshold ) ) { // split all subtrees in list if any subtree is above threshold
2070 // traverse nodes in list
2071 for ( p = pBTList; p != NULL; p = p->next )
2073 if ( p->pBT == NULL ) {
2074 leftBTList = BTree_AddLinkToList( leftBTList, ( p->vLeft.normal[0] == 1.0f ) );
2075 rightBTList = BTree_AddLinkToList( rightBTList, ( p->vLeft.normal[0] == 1.0f ) );
2076 if ( p->vLeft.normal[0] == 1.0f ) {
2077 bFlipped = ( !bFlipped ) ? true : false; // switch bFlipped if true
2082 // create left node for this subtree
2083 BTNode_t *newLeft = new BTNode_t;
2084 p->pBT->left = newLeft;
2085 newLeft->left = newLeft->right = NULL;
2087 // create right node for this subtree
2088 BTNode_t *newRight = new BTNode_t;
2089 p->pBT->right = newRight;
2090 newRight->left = newRight->right = NULL;
2093 vCurve[0] = &p->vLeft;
2094 vCurve[1] = &p->pBT->info;
2095 vCurve[2] = &p->vRight;
2096 Patch_CurveSplit( vCurve, newLeft->info, newRight->info, p->pBT->vMid, 0.5 );
2098 memcpy( &newLeft->vMid, &newLeft->info, sizeof( drawVert_t ) );
2099 memcpy( &newRight->vMid, &newRight->info, sizeof( drawVert_t ) );
2103 // add new left subtree to left subtree list
2104 leftBTList = BTree_AddToList( leftBTList, newLeft, p->vLeft, p->pBT->vMid );
2106 // add new right subtree to right subtree list
2107 rightBTList = BTree_AddToList( rightBTList, newRight, p->pBT->vMid, p->vRight );
2111 // add new left subtree to right subtree list
2112 rightBTList = BTree_AddToList( rightBTList, newLeft, p->vLeft, p->pBT->vMid );
2114 // add new right subtree to left subtree list
2115 leftBTList = BTree_AddToList( leftBTList, newRight, p->pBT->vMid, p->vRight );
2119 // continue tree left
2120 BTree_ListCurveRecurse( leftBTList );
2121 leftBTList = BTree_DeletePointerList( leftBTList );
2123 // continue tree right
2124 BTree_ListCurveRecurse( rightBTList );
2125 rightBTList = BTree_DeletePointerList( rightBTList );
2129 // take mins and maxs values from two brushes
2130 // return true if they intersect on every axis
2131 bool TouchingAABBs( vec3_t mins1, vec3_t maxs1, vec3_t mins2, vec3_t maxs2 ){
2133 vec3_t v1, v2, p1, p2, T;
2134 for ( int i = 0; i < 3; i++ )
2136 v1[i] = maxs1[i] - mins1[i];
2137 v2[i] = maxs2[i] - mins2[i];
2140 p1[i] = mins1[i] + v1[i];
2141 p2[i] = mins2[i] + v2[i];
2142 // p1 == origin of aabb1
2143 // p2 == origin of aabb1
2144 // v1 == displacement of aabb1
2145 // v1 == displacement of aabb2
2146 T[i] = p2[i] - p1[i]; // T == vector from aabb1 to aabb2
2147 if ( fabs( T[i] ) > ( fabs( v1[i] ) + fabs( v2[i] ) ) ) {
2154 // take a pointer to the last item added to pBTList, a pointer to the patch, a row index (start) and a column index
2155 // generate a row of row-curve tree roots, owned by the patch and add the entire column of row-curves to the list, using the row index to decide the order to add
2156 // return a pointer to the last item added to the list
2157 BTreeList_t *Patch_CreateBTListForRows( BTreeList_t *pBTList, patchMesh_t *patch, int start, int col ){
2159 patch->colDirty[( col - 1 ) / 2] = true;
2162 for ( row = 0; row < patch->height; row++ )
2164 pos = ( ( ( col - 1 ) / 2 ) * patch->height ) + row;
2165 patch->rowLOD[pos] = BTree_Delete( patch->rowLOD[pos] );
2166 patch->rowLOD[pos] = BTree_Create( patch->ctrl[col][row] );
2167 pBTList = BTree_AddToList( pBTList, patch->rowLOD[pos], patch->ctrl[col - 1][row], patch->ctrl[col + 1][row] );
2172 for ( row = patch->height - 1; row >= 0; row-- )
2174 pos = ( ( ( col - 1 ) / 2 ) * patch->height ) + row;
2175 patch->rowLOD[pos] = BTree_Delete( patch->rowLOD[pos] );
2176 patch->rowLOD[pos] = BTree_Create( patch->ctrl[col][row] );
2177 pBTList = BTree_AddToList( pBTList, patch->rowLOD[pos], patch->ctrl[col - 1][row], patch->ctrl[col + 1][row] );
2183 // take a pointer to the last item added to pBTList, a pointer to the patch, a row index and a column index (start)
2184 // generate a row of column-curve tree roots, owned by the patch and add the entire row of column-curves to the list, using the column index to decide the order to add
2185 // return a pointer to the last item added to the list
2186 BTreeList_t *Patch_CreateBTListForCols( BTreeList_t *pBTList, patchMesh_t *patch, int row, int start ){
2188 patch->rowDirty[( row - 1 ) / 2] = true;
2191 for ( col = 0; col < patch->width; col++ )
2193 pos = ( ( ( row - 1 ) / 2 ) * patch->width ) + col;
2194 patch->colLOD[pos] = BTree_Delete( patch->colLOD[pos] );
2195 patch->colLOD[pos] = BTree_Create( patch->ctrl[col][row] );
2196 pBTList = BTree_AddToList( pBTList, patch->colLOD[pos], patch->ctrl[col][row - 1], patch->ctrl[col][row + 1] );
2201 for ( col = patch->width - 1; col >= 0; col-- )
2203 pos = ( ( ( row - 1 ) / 2 ) * patch->width ) + col;
2204 patch->colLOD[pos] = BTree_Delete( patch->colLOD[pos] );
2205 patch->colLOD[pos] = BTree_Create( patch->ctrl[col][row] );
2206 pBTList = BTree_AddToList( pBTList, patch->colLOD[pos], patch->ctrl[col][row - 1], patch->ctrl[col][row + 1] );
2213 bool BTree_IsInList( BTreeList_t *pBTList, BTNode_t *pBT ){
2215 if ( pBTList == NULL ) {
2219 for ( p = pBTList; p != NULL; p = p->next )
2221 if ( p->pBT != NULL ) {
2222 if ( p->pBT == pBT ) {
2230 int Patch_DegenCurve( vec3_t &start, vec3_t &mid, vec3_t &end ){
2231 if ( VectorCompare( start, mid ) || VectorCompare( end, mid ) ) {
2232 if ( VectorCompare( start, end ) ) {
2240 // take a pointer to the last item added to the list, and a pointer to a patch (this patch is the owner of the three drawverts)
2241 // take the addresses of three drawVerts, and compare them with the edges of all patches that touch the patch
2242 // if they match an edge, add the tree roots for that section of the matched patch to the list, and recurse for the opposite edge of that patch section. Also, set the matched patch Dirty, so that its drawlists will be rebuilt
2243 // return a pointer to the last item added
2244 BTreeList_t *Patch_FindLODMatches( patchMesh_t *patch, BTreeList_t *pBTList, drawVert_t &pMid, drawVert_t &pLeft, drawVert_t &pRight ){
2245 brush_t *pb, *brushlist;
2246 int row, col, i; //, pos;
2247 vec3_t vTemp, v1, v2; //, vClear;
2250 //Sys_Printf("Patch_FindLODMatches: called\n");
2252 if ( VectorCompare( pMid.xyz, pLeft.xyz ) && VectorCompare( pMid.xyz, pRight.xyz ) ) {
2256 //VectorClear(vClear);
2257 VectorSubtract( pRight.xyz, pLeft.xyz, vTemp );
2258 VectorSubtract( pMid.xyz, pLeft.xyz, v1 );
2259 VectorSubtract( pRight.xyz, pMid.xyz, v2 );
2261 //if (VectorCompare(v1, vClear) || VectorCompare(vTemp, v1)) // return null if 1->2 == 0 or 1->2 == 1->3
2264 VectorNormalize( v1, v1 );
2265 VectorNormalize( v2, v2 );
2266 if ( VectorCompare( v1, v2 ) ) {
2270 VectorNormalize( vTemp, vTemp );
2271 if ( VectorCompare( v1, vTemp ) && VectorCompare( v2, vTemp ) ) {
2275 brushlist = &active_brushes;
2276 for ( i = 0; i < 2; i++ )
2278 for ( pb = brushlist->next; pb != brushlist; pb = pb->next )
2280 if ( !pb->patchBrush || pb->pPatch == patch ) {
2284 // ignore this patch if its AABB does not touch the subject patch
2285 if ( !TouchingAABBs( patch->pSymbiot->maxs, patch->pSymbiot->mins, pb->maxs, pb->mins ) ) {
2289 // all columns of curves
2290 for ( col = 1; col < pb->pPatch->width; col += 2 )
2292 if ( pb->pPatch->colDirty[( col - 1 ) / 2] ) {
2296 bAlreadyAdded = false;
2298 // top and bottom curves of this column
2299 for ( row = 0; row < pb->pPatch->height; row += pb->pPatch->height - 1 )
2301 if ( bAlreadyAdded ) {
2304 //if (!BTree_IsInList(pBTList, pb->pPatch->rowLOD[(((col-1)/2)*patch->height)+row]))
2306 // ignore this curve if it shares no mid ctrl point with the test curve
2307 if ( !VectorCompare( pb->pPatch->ctrl[col][row].xyz, pMid.xyz ) ) {
2310 // ignore this curve if it is degenerate
2311 if ( VectorCompare( pb->pPatch->ctrl[col][row].xyz, pb->pPatch->ctrl[col - 1][row].xyz ) || VectorCompare( pb->pPatch->ctrl[col][row].xyz, pb->pPatch->ctrl[col + 1][row].xyz ) ) {
2314 // if curve matches the test curve directly
2315 if ( VectorCompare( pb->pPatch->ctrl[col - 1][row].xyz, pLeft.xyz ) && VectorCompare( pb->pPatch->ctrl[col + 1][row].xyz, pRight.xyz ) ) {
2316 // add a blank link as separator
2317 pBTList = BTree_AddLinkToList( pBTList );
2318 // add this entire column, if top, top-to-bottom, else bottom to top
2319 pBTList = Patch_CreateBTListForRows( pBTList, pb->pPatch, row, col );
2320 // continue checking from last curve added to list
2321 pBTList = Patch_FindLODMatches( pb->pPatch, pBTList, pBTList->pBT->info, pBTList->vLeft, pBTList->vRight );
2323 pb->pPatch->LODUpdated = true;
2324 bAlreadyAdded = true;
2326 // if curve matches test curve but flipped
2327 else if ( VectorCompare( pb->pPatch->ctrl[col - 1][row].xyz, pRight.xyz ) && VectorCompare( pb->pPatch->ctrl[col + 1][row].xyz, pLeft.xyz ) ) {
2328 pBTList = BTree_AddLinkToList( pBTList, true ); // flip
2329 pBTList = Patch_CreateBTListForRows( pBTList, pb->pPatch, row, col );
2330 pBTList = Patch_FindLODMatches( pb->pPatch, pBTList, pBTList->pBT->info, pBTList->vLeft, pBTList->vRight );
2331 pb->pPatch->LODUpdated = true;
2332 bAlreadyAdded = true;
2337 // all rows of curves
2338 for ( row = 1; row < pb->pPatch->height; row += 2 )
2340 if ( pb->pPatch->rowDirty[( row - 1 ) / 2] ) {
2344 bAlreadyAdded = false;
2346 for ( col = 0; col < pb->pPatch->width; col += pb->pPatch->width - 1 )
2348 if ( bAlreadyAdded ) {
2351 //if (BTree_IsInList(pBTList, pb->pPatch->colLOD[(((row-1)/2)*patch->width)+col]))
2353 if ( !VectorCompare( pb->pPatch->ctrl[col][row].xyz, pMid.xyz ) ) {
2356 if ( VectorCompare( pb->pPatch->ctrl[col][row].xyz, pb->pPatch->ctrl[col][row - 1].xyz ) || VectorCompare( pb->pPatch->ctrl[col][row].xyz, pb->pPatch->ctrl[col][row + 1].xyz ) ) {
2359 if ( VectorCompare( pb->pPatch->ctrl[col][row - 1].xyz, pLeft.xyz ) && VectorCompare( pb->pPatch->ctrl[col][row + 1].xyz, pRight.xyz ) ) {
2360 pBTList = BTree_AddLinkToList( pBTList );
2361 pBTList = Patch_CreateBTListForCols( pBTList, pb->pPatch, row, col );
2362 pBTList = Patch_FindLODMatches( pb->pPatch, pBTList, pBTList->pBT->info, pBTList->vLeft, pBTList->vRight );
2363 pb->pPatch->LODUpdated = true;
2364 bAlreadyAdded = true;
2366 else if ( VectorCompare( pb->pPatch->ctrl[col][row - 1].xyz, pRight.xyz ) && VectorCompare( pb->pPatch->ctrl[col][row + 1].xyz, pLeft.xyz ) ) {
2367 pBTList = BTree_AddLinkToList( pBTList, true ); // flip
2368 pBTList = Patch_CreateBTListForCols( pBTList, pb->pPatch, row, col );
2369 pBTList = Patch_FindLODMatches( pb->pPatch, pBTList, pBTList->pBT->info, pBTList->vLeft, pBTList->vRight );
2370 pb->pPatch->LODUpdated = true;
2371 bAlreadyAdded = true;
2376 brushlist = &selected_brushes;
2381 // take a pointer to a patch
2382 // create tree roots for all the rows and columns of curves in the patch, the patch takes ownership of these new tree roots
2383 // generate lists of pointers to all the trees in all the patches in the map which need to match the LOD of trees owned by this patch
2384 // store all the lists in a list of lists
2385 // recursively generate the rest of every tree in each list in the list
2386 void Patch_CreateLODTrees( patchMesh_t *patch ){
2387 BTreeList_t *pBTList;
2388 int col, row, pos; //, rowcount, colcount;
2389 BTListList_t *pLists;
2391 //Sys_Printf("Patch_CreateMatchedLODTrees: called\n");
2393 BTListList_t *LODLists;
2398 patch->bDirty = false;
2399 patch->LODUpdated = true;
2401 for ( col = 1; col < patch->width; col += 2 )
2403 if ( patch->colDirty[( col - 1 ) / 2] ) {
2406 else{patch->colDirty[( col - 1 ) / 2] = true; }
2408 // create list for rows of current patch
2409 for ( row = 0; row < patch->height; row++ )
2411 pos = ( ( ( col - 1 ) / 2 ) * patch->height ) + row;
2412 patch->rowLOD[pos] = BTree_Delete( patch->rowLOD[pos] );
2413 patch->rowLOD[pos] = BTree_Create( patch->ctrl[col][row] );
2414 pBTList = BTree_AddToList( pBTList, patch->rowLOD[pos], patch->ctrl[col - 1][row], patch->ctrl[col + 1][row] );
2417 //create connection list for first row
2418 pBTList = Patch_FindLODMatches( patch, pBTList, patch->ctrl[col][0], patch->ctrl[col - 1][0], patch->ctrl[col + 1][0] );
2419 //create connection list for last row
2420 pBTList = Patch_FindLODMatches( patch, pBTList, patch->ctrl[col][row - 1], patch->ctrl[col - 1][row - 1], patch->ctrl[col + 1][row - 1] );
2422 LODLists = BTree_AddListToList( LODLists, pBTList );
2427 for ( row = 1; row < patch->height; row += 2 )
2429 if ( patch->rowDirty[( row - 1 ) / 2] ) {
2432 else{patch->rowDirty[( row - 1 ) / 2] = true; }
2434 // create list for cols of current patch
2435 for ( col = 0; col < patch->width; col++ )
2437 pos = ( ( ( row - 1 ) / 2 ) * patch->width ) + col;
2438 patch->colLOD[pos] = BTree_Delete( patch->colLOD[pos] );
2439 patch->colLOD[pos] = BTree_Create( patch->ctrl[col][row] );
2440 pBTList = BTree_AddToList( pBTList, patch->colLOD[pos], patch->ctrl[col][row - 1], patch->ctrl[col][row + 1] );
2443 //create connection list for first col
2444 pBTList = Patch_FindLODMatches( patch, pBTList, patch->ctrl[0][row], patch->ctrl[0][row - 1], patch->ctrl[0][row + 1] );
2445 //create connection list for last col
2446 pBTList = Patch_FindLODMatches( patch, pBTList, patch->ctrl[col - 1][row], patch->ctrl[col - 1][row - 1], patch->ctrl[col - 1][row + 1] );
2448 LODLists = BTree_AddListToList( LODLists, pBTList );
2452 for ( pLists = LODLists; pLists != NULL; pLists = pLists->next )
2453 BTree_ListCurveRecurse( pLists->list );
2454 LODLists = BTree_DeleteListFromList( LODLists );
2457 int Patch_GetCVTangent( vec3_t &v1, vec3_t &p1, vec3_t &p2, vec3_t &p3 ){
2458 if ( VectorCompare( p1, p2 ) ) {
2459 if ( VectorCompare( p1, p3 ) ) {
2462 else{VectorSubtract( p3, p1, v1 ); }
2465 else{VectorSubtract( p2, p1, v1 ); }
2469 void Patch_CVNormal( vec3_t ctrl[3][3], vec3_t &normal ){
2470 vec3_t v1, v2, vTemp1, vTemp2;
2473 a = Patch_GetCVTangent( v1, ctrl[0][0], ctrl[1][0], ctrl[2][0] );
2474 b = Patch_GetCVTangent( v2, ctrl[0][0], ctrl[0][1], ctrl[0][2] );
2476 //Sys_Printf("p1: (%1.1f %1.1f %1.1f) p2: (%1.1f %1.1f %1.1f) p2: (%1.1f %1.1f %1.1f)\n",
2477 // ctrl[0][0][0], ctrl[0][0][1], ctrl[0][0][2], ctrl[0][2][0], ctrl[0][2][1], ctrl[0][2][2], ctrl[2][0][0], ctrl[2][0][1], ctrl[2][0][2]);
2479 v1[0] = v1[1] = v1[2] = v2[0] = v2[1] = v2[2] = 0;
2482 a = Patch_GetCVTangent( v1, ctrl[0][0], ctrl[1][1], ctrl[1][2] );
2485 b = Patch_GetCVTangent( v2, ctrl[0][0], ctrl[1][1], ctrl[2][1] );
2489 a = Patch_GetCVTangent( v1, ctrl[0][0], ctrl[2][1], ctrl[2][2] );
2492 b = Patch_GetCVTangent( v2, ctrl[0][0], ctrl[1][2], ctrl[2][2] );
2495 CrossProduct( v1, v2, normal );
2498 if ( normal[0] == 0.0f && normal[1] == 0.0f && normal[2] == 0.0f ) {
2499 // more degenerate cases
2503 if (VectorCompare(ctrl[0][0], ctrl[2][0])) // endcap left
2505 if (VectorCompare(ctrl[0][2], ctrl[1][2]))
2507 VectorSubtract(ctrl[2][2], ctrl[0][0], v2);
2509 else if (VectorCompare(ctrl[1][2], ctrl[2][2]))
2511 VectorSubtract(ctrl[0][2], ctrl[0][0], v2);
2514 a = Patch_DegenCurve(ctrl[0][2], ctrl[1][2], ctrl[2][2]);
2517 VectorCopy(ctrl[0][2], vCurve[0]);
2518 VectorCopy(ctrl[1][2], vCurve[1]);
2519 VectorCopy(ctrl[2][2], vCurve[2]);
2520 Patch_BezierInterpolate(vCurve, pMid);
2521 VectorSubtract(pMid, ctrl[0][0], v1);
2526 else if (VectorCompare(ctrl[0][0], ctrl[0][2])) // endcap right
2529 if (VectorCompare(ctrl[2][0], ctrl[2][1]))
2531 VectorSubtract(ctrl[2][2], ctrl[0][0], v2);
2533 else if (VectorCompare(ctrl[2][1], ctrl[2][2]))
2535 VectorSubtract(ctrl[2][0], ctrl[0][0], v2);
2539 b = Patch_DegenCurve(ctrl[2][0], ctrl[2][1], ctrl[2][2]);
2542 VectorCopy(ctrl[2][0], vCurve[0]);
2543 VectorCopy(ctrl[2][1], vCurve[1]);
2544 VectorCopy(ctrl[2][2], vCurve[2]);
2545 Patch_BezierInterpolate(vCurve, pMid);
2546 VectorSubtract(pMid, ctrl[0][0], v2);
2551 if ( VectorCompare( ctrl[0][0], ctrl[2][0] ) ) { // bottom degen
2552 Patch_GetCVTangent( v1, ctrl[0][0], ctrl[2][1], ctrl[2][2] );
2554 else if ( VectorCompare( ctrl[0][0], ctrl[0][2] ) ) { // left degen
2555 Patch_GetCVTangent( v2, ctrl[0][0], ctrl[1][2], ctrl[2][2] );
2557 else if ( VectorCompare( ctrl[0][2], ctrl[2][2] ) ) { // top degen
2558 VectorSubtract( ctrl[2][0], ctrl[0][0], v1 );
2560 else if ( VectorCompare( ctrl[2][0], ctrl[2][2] ) ) { // right degen
2561 VectorSubtract( ctrl[0][2], ctrl[0][0], v2 );
2563 else // tangents parallel
2565 VectorCopy( v1, vTemp1 );
2566 VectorCopy( v2, vTemp2 );
2567 VectorNormalize( vTemp1, vTemp1 );
2568 VectorNormalize( vTemp2, vTemp2 );
2569 if ( VectorCompare( vTemp1, vTemp2 ) ) { // parallel same way
2570 VectorSubtract( ctrl[2][0], ctrl[0][0], vTemp1 );
2571 VectorNormalize( vTemp1, vTemp1 );
2572 if ( VectorCompare( vTemp1, vTemp2 ) ) {
2573 VectorSubtract( ctrl[0][2], ctrl[0][0], v2 );
2577 VectorCopy( vTemp1, v1 );
2580 else // parallel opposite way
2582 VectorCopy( ctrl[2][0], vCurve[0] );
2583 VectorCopy( ctrl[1][1], vCurve[1] );
2584 VectorCopy( ctrl[0][2], vCurve[2] );
2585 Patch_BezierInterpolate( vCurve, pMid );
2586 VectorSubtract( pMid, ctrl[0][0], v2 );
2590 CrossProduct( v1, v2, normal );
2594 void Patch_CalcCVNormals( patchMesh_t *patch ){
2595 int row, col, i, j, n;
2599 for ( col = 0; col < patch->width; col += 2 )
2601 for ( row = 0; row < patch->height; row += 2 )
2604 if ( col + 1 != patch->width && row + 1 != patch->height ) {
2605 for ( i = 0; i < 3; i++ )
2606 for ( j = 0; j < 3; j++ )
2607 VectorCopy( patch->ctrl[col + i][row + j].xyz, ctrl[i][j] );
2609 Patch_CVNormal( ctrl, normals[n] );
2610 VectorNormalize( normals[n], normals[n] );
2614 if ( col - 1 >= 0 && row - 1 >= 0 ) {
2615 for ( i = 0; i < 3; i++ )
2616 for ( j = 0; j < 3; j++ )
2617 VectorCopy( patch->ctrl[col - i][row - j].xyz, ctrl[i][j] );
2619 Patch_CVNormal( ctrl, normals[n] );
2620 VectorNormalize( normals[n], normals[n] );
2623 if ( col - 1 >= 0 && row + 1 != patch->height ) {
2624 for ( i = 0; i < 3; i++ )
2625 for ( j = 0; j < 3; j++ )
2626 VectorCopy( patch->ctrl[col - i][row + j].xyz, ctrl[j][i] );
2628 Patch_CVNormal( ctrl, normals[n] );
2629 VectorNormalize( normals[n], normals[n] );
2632 if ( col + 1 != patch->width && row - 1 >= 0 ) {
2633 for ( i = 0; i < 3; i++ )
2634 for ( j = 0; j < 3; j++ )
2635 VectorCopy( patch->ctrl[col + i][row - j].xyz, ctrl[j][i] );
2637 Patch_CVNormal( ctrl, normals[n] );
2638 VectorNormalize( normals[n], normals[n] );
2642 for ( i = 0; i < 3; i++ )
2645 patch->ctrl[col][row].normal[i] = normals[0][i];
2648 patch->ctrl[col][row].normal[i] = ( normals[0][i] + normals[1][i] ) / n;
2650 //if (n == 3) patch->ctrl[col][row].normal[i] = (normals[0][i] + normals[1][i] + normals[2][i]) / n;
2652 patch->ctrl[col][row].normal[i] = ( normals[0][i] + normals[1][i] + normals[2][i] + normals[3][i] ) / n;
2655 VectorNormalize( patch->ctrl[col][row].normal, patch->ctrl[col][row].normal );
2656 //if (!g_PrefsDlg.m_bGLLighting)
2657 // ShadeVertex(patch->ctrl[col][row]);
2663 void BTree_SetNormals( BTNode_t *pBT, vec3_t &normal ){
2664 if ( pBT != NULL ) {
2665 if ( pBT->left != NULL && pBT->right != NULL ) {
2666 VectorCopy( normal, pBT->vMid.normal );
2667 //if (!g_PrefsDlg.m_bGLLighting)
2668 // ShadeVertex(pBT->vMid);
2670 BTree_SetNormals( pBT->left, normal );
2671 BTree_SetNormals( pBT->right, normal );
2676 void NormalFromPoints( vec3_t p1, vec3_t p2, vec3_t p3, vec3_t &normal, bool flip = false ){
2680 VectorSubtract( p2, p3, v1 ); //p3->p2
2681 VectorSubtract( p1, p2, v2 ); //p2->p1
2685 VectorSubtract( p2, p1, v1 ); //p1->p2
2686 VectorSubtract( p3, p2, v2 ); //p2->p3
2688 CrossProduct( v1, v2, normal );
2692 void BTree_GenerateNormals( BTNode_t *pBTMid, BTNode_t *pBTLeft, BTNode_t *pBTRight, bool avg, bool flat, bool nomid, bool noleft, bool noright, /*bool endcap, vec3_t &n1, vec3_t &n2,*/ bool flip ){
2693 if ( pBTMid != NULL ) {
2694 if ( pBTMid->left != NULL && pBTMid->right != NULL ) {
2697 if ( noleft ) { // left curve is degenerate
2698 if ( nomid ) { // mid curve is degenerate
2699 NormalFromPoints( pBTRight->right->info.xyz, pBTRight->vMid.xyz, pBTMid->vMid.xyz, normal, flip );
2700 NormalFromPoints( pBTRight->right->info.xyz, pBTRight->vMid.xyz, pBTMid->vMid.xyz, pBTRight->vMid.normal, flip );
2704 // VectorCopy(n1, normal);
2705 // NormalFromPoints(pBTRight->right->info.xyz, pBTRight->vMid.xyz, pBTMid->vMid.xyz, pBTRight->vMid.normal, flip);
2709 NormalFromPoints( pBTMid->left->info.xyz, pBTLeft->vMid.xyz, pBTMid->vMid.xyz, normal, flip );
2710 NormalFromPoints( pBTRight->right->info.xyz, pBTRight->vMid.xyz, pBTMid->vMid.xyz, pBTRight->vMid.normal, flip );
2713 else if ( noright ) { // right curve is degenerate
2714 if ( nomid ) { // mid curve is degenerate
2715 NormalFromPoints( pBTLeft->left->info.xyz, pBTLeft->vMid.xyz, pBTMid->vMid.xyz, normal, flip );
2716 NormalFromPoints( pBTLeft->left->info.xyz, pBTLeft->vMid.xyz, pBTMid->vMid.xyz, pBTRight->vMid.normal, flip );
2720 // NormalFromPoints(pBTLeft->left->info.xyz, pBTLeft->vMid.xyz, pBTMid->vMid.xyz, normal, flip);
2721 // VectorCopy(n2, pBTRight->vMid.normal);
2725 NormalFromPoints( pBTLeft->left->info.xyz, pBTLeft->vMid.xyz, pBTMid->vMid.xyz, normal, flip );
2726 NormalFromPoints( pBTMid->right->info.xyz, pBTRight->vMid.xyz, pBTMid->vMid.xyz, pBTRight->vMid.normal, flip );
2731 if ( flat ) { // all curves are semi-degenerate (flat) or degenerate
2732 NormalFromPoints( pBTLeft->left->info.xyz, pBTLeft->vMid.xyz, pBTRight->vMid.xyz, normal, flip );
2733 NormalFromPoints( pBTRight->right->info.xyz, pBTRight->vMid.xyz, pBTLeft->vMid.xyz, pBTRight->vMid.normal, flip );
2737 NormalFromPoints( pBTLeft->left->info.xyz, pBTLeft->vMid.xyz, pBTMid->vMid.xyz, normal, flip );
2738 NormalFromPoints( pBTRight->right->info.xyz, pBTRight->vMid.xyz, pBTMid->vMid.xyz, pBTRight->vMid.normal, flip );
2742 VectorNormalize( normal, normal );
2744 for ( int i = 0; i < 3; i++ )
2745 pBTLeft->vMid.normal[i] = ( normal[i] + pBTLeft->vMid.normal[i] ) / 2.0f;
2747 else{VectorCopy( normal, pBTLeft->vMid.normal ); }
2749 VectorNormalize( pBTLeft->vMid.normal, pBTLeft->vMid.normal );
2750 VectorNormalize( pBTRight->vMid.normal, pBTRight->vMid.normal );
2753 BTree_GenerateNormals( pBTMid->left, pBTLeft->left, pBTRight->left, avg, flat, nomid, noleft, noright, /*endcap, n1, n2,*/ flip );
2754 BTree_GenerateNormals( pBTMid->right, pBTLeft->right, pBTRight->right, avg, flat, nomid, noleft, noright, /*endcap, n1, n2,*/ flip );
2760 void Patch_GenerateLODNormals( patchMesh_t *patch ){
2761 int col, row, rowpos, colpos, i;
2762 BTNode_t *tree[2][3];
2764 bool rowAvg, colAvg;
2766 for ( col = 0; col + 2 < patch->width; col += 2 )
2768 for ( row = 0; row + 2 < patch->height; row += 2 )
2770 if ( !patch->colDirty[col / 2] && !patch->rowDirty[row / 2] ) {
2774 rowpos = ( ( col / 2 ) * patch->height ) + row;
2775 colpos = ( ( row / 2 ) * patch->width ) + col;
2780 else{ rowAvg = true; }
2784 else{ colAvg = true; }
2786 for ( i = 0; i < 3; i++ )
2788 tree[0][i] = patch->rowLOD[rowpos + i];
2789 tree[1][i] = patch->colLOD[colpos + i];
2791 degen[0][i] = Patch_DegenCurve( patch->ctrl[col][row + i].xyz, patch->ctrl[col + 1][row + i].xyz, patch->ctrl[col + 2][row + i].xyz );
2792 degen[1][i] = Patch_DegenCurve( patch->ctrl[col + i][row].xyz, patch->ctrl[col + i][row + 1].xyz, patch->ctrl[col + i][row + 2].xyz );
2795 BTree_GenerateNormals( tree[0][1], tree[0][0], tree[0][2], rowAvg, ( degen[1][0] && degen[1][1] && degen[1][2] ), degen[0][1] == 2, degen[0][0] == 2, degen[0][2] == 2, /*degen[1][1], patch->ctrl[col][row].normal, patch->ctrl[col][row+2].normal,*/ false );
2796 BTree_GenerateNormals( tree[1][1], tree[1][0], tree[1][2], colAvg, ( degen[0][0] && degen[0][1] && degen[0][2] ), degen[1][1] == 2, degen[1][0] == 2, degen[1][2] == 2, /*degen[0][1], patch->ctrl[col][row].normal, patch->ctrl[col+2][row].normal,*/ true );
2802 void Patch_ClearLODFlags( patchMesh_t *p ){
2805 for ( i = 0; i < ( p->width - 1 ) / 2; i++ )
2806 p->colDirty[i] = false;
2808 for ( i = 0; i < ( p->height - 1 ) / 2; i++ )
2809 p->rowDirty[i] = false;
2812 // reset the lodDirty flags owned by all patches in the map
2813 // create new LOD trees for all dirty patches, matched with all other patches in the map
2814 void Patch_LODMatchAll(){
2815 brush_t *pb, *brushlist;
2818 // create LOD tree roots and LOD tree lists for all patches that are dirty
2820 brushlist = &active_brushes;
2821 for ( i = 0; i < 2; i++ )
2823 for ( pb = brushlist->next; pb && ( pb != brushlist ); pb = pb->next )
2825 // create lod for selected patches when patches are filtered
2826 if ( pb->bFiltered && ( pb->patchBrush && !pb->pPatch->bSelected ) ) {
2829 if ( !pb->patchBrush ) {
2832 if ( !pb->pPatch->bDirty ) {
2836 Patch_CalcCVNormals( pb->pPatch );
2837 Patch_CreateLODTrees( pb->pPatch );
2839 brushlist = &selected_brushes;
2842 brushlist = &active_brushes;
2843 for ( i = 0; i < 2; i++ )
2845 for ( pb = brushlist->next; pb && ( pb != brushlist ); pb = pb->next )
2847 if ( !pb->patchBrush ) {
2851 if ( pb->pPatch->LODUpdated ) {
2852 Patch_GenerateLODNormals( pb->pPatch );
2855 Patch_ClearLODFlags( pb->pPatch );
2857 brushlist = &selected_brushes;
2862 void Vertex_TransformTexture( drawVert_t *pVert, float fx, float fy, transformtype xform ){
2874 float x = pVert->st[0];
2875 float y = pVert->st[1];
2876 pVert->st[0] = x * fx - y * fy;
2877 pVert->st[1] = y * fx + x * fy;
2881 void BTree_TransformTexture( BTNode_t *pBT, float fx, float fy, transformtype xform ){
2882 if ( pBT != NULL ) { // PreOrder traversal
2883 Vertex_TransformTexture( &pBT->info, fx, fy, xform );
2884 Vertex_TransformTexture( &pBT->vMid, fx, fy, xform );
2885 BTree_TransformTexture( pBT->left, fx, fy, xform );
2886 BTree_TransformTexture( pBT->right, fx, fy, xform );
2890 void Patch_TransformLODTexture( patchMesh_t *p, float fx, float fy, transformtype xform ){
2893 for ( col = 1; col < p->width; col += 2 )
2894 for ( row = 0; row < p->height; row++ )
2895 BTree_TransformTexture( p->rowLOD[( ( ( col - 1 ) / 2 ) * p->height ) + row], fx, fy, xform );
2897 for ( row = 1; row < p->height; row += 2 )
2898 for ( col = 0; col < p->width; col++ )
2899 BTree_TransformTexture( p->colLOD[( ( ( row - 1 ) / 2 ) * p->width ) + col], fx, fy, xform );
2902 void Patch_AddBTreeToDrawListInOrder( list<drawVert_t> *drawList, BTNode_t *pBT ){
2903 if ( pBT != NULL ) { //traverse InOrder
2904 Patch_AddBTreeToDrawListInOrder( drawList, pBT->left );
2905 if ( pBT->left != NULL && pBT->right != NULL ) {
2906 drawList->push_back( pBT->vMid );
2908 Patch_AddBTreeToDrawListInOrder( drawList, pBT->right );
2912 void Patch_InterpolateListFromRowBT( list<drawVert_t> *drawList, BTNode_t *rowBT, BTNode_t *rowBTLeft, drawVert_t *vCurve[], float u, float n, float v ){
2913 if ( rowBT != NULL ) {
2914 Patch_InterpolateListFromRowBT( drawList, rowBT->left, rowBTLeft->left, vCurve, u - n, n * 0.5f, v );
2915 if ( rowBT->left != NULL && rowBT->right != NULL ) {
2917 drawVert_t newVert, vTemp1, vTemp2;
2918 Patch_CurveSplit( vCurve, vTemp1, vTemp2, newVert, u );
2919 for ( int i = 0; i < 3; i++ )
2921 v1[i] = rowBT->vMid.xyz[i] - rowBTLeft->vMid.xyz[i]; // left -> mid
2922 v1[i] = rowBTLeft->vMid.xyz[i] + ( v1[i] * v );
2923 v1[i] = newVert.xyz[i] - v1[i];
2925 VectorSubtract( vTemp1.xyz, newVert.xyz, v2 );
2926 CrossProduct( v1, v2, newVert.normal );
2927 VectorNormalize( newVert.normal, newVert.normal );
2928 //if (!g_PrefsDlg.m_bGLLighting)
2929 // ShadeVertex(newVert);
2930 drawList->push_back( newVert );
2932 Patch_InterpolateListFromRowBT( drawList, rowBT->right, rowBTLeft->right, vCurve, u + n, n * 0.5f, v );
2936 void Patch_TraverseColBTInOrder( list<list<drawVert_t>*>::iterator& iter, BTNode_t *colBTLeft, BTNode_t *colBT, BTNode_t *colBTRight, BTNode_t *rowBT, BTNode_t *rowBTLeft, float v, float n ){
2937 if ( colBT != NULL ) {
2938 //traverse subtree In Order
2939 Patch_TraverseColBTInOrder( iter, colBTLeft->left, colBT->left, colBTRight->left, rowBT, rowBTLeft, v - n, n * 0.5f );
2940 if ( colBT->left != NULL && colBT->right != NULL ) {
2941 drawVert_t *vCurve[3];
2942 vCurve[0] = &colBTLeft->vMid;
2943 vCurve[1] = &colBT->vMid;
2944 vCurve[2] = &colBTRight->vMid;
2945 Patch_InterpolateListFromRowBT( ( *iter ), rowBT, rowBTLeft, vCurve, 0.5f, 0.25f, v );
2947 ( *iter )->push_back( colBTRight->vMid );
2950 Patch_TraverseColBTInOrder( iter, colBTLeft->right, colBT->right, colBTRight->right, rowBT, rowBTLeft, v + n, n * 0.5f );
2955 void Patch_StartDrawLists( list<list<drawVert_t>*> *drawLists, BTNode_t *colBT ){
2956 if ( colBT != NULL ) {
2957 //traverse subtree In Order
2958 Patch_StartDrawLists( drawLists, colBT->left );
2959 if ( colBT->left != NULL && colBT->right != NULL ) {
2960 list<drawVert_t> *newList = new list<drawVert_t>;
2961 drawLists->push_back( newList ); // add empty list to back
2962 drawLists->back()->push_back( colBT->vMid );
2964 Patch_StartDrawLists( drawLists, colBT->right );
2968 typedef list<drawVert_t> drawList_t;
2969 typedef list<list<drawVert_t>*> drawLists_t;
2971 void Patch_CreateDrawLists( patchMesh_t *patch ){
2972 int col, row, colpos, rowpos;
2974 drawLists_t *drawLists = new drawLists_t;
2976 drawLists_t::iterator iter1, iter2;
2978 for ( row = 0; row < patch->height; row += 2 )
2980 colpos = ( row / 2 ) * patch->width;
2981 drawList_t *newList = new drawList_t;
2982 drawLists->push_back( newList ); // add a new empty list to back
2983 drawLists->back()->push_back( patch->ctrl[0][row] ); // fill list at back
2985 if ( row + 1 == patch->height ) {
2988 Patch_StartDrawLists( drawLists, patch->colLOD[colpos] );
2991 iter1 = drawLists->begin();
2992 for ( row = 0; row < patch->height; row += 2 )
2995 for ( col = 0; col + 1 < patch->width; col += 2 )
2998 colpos = ( ( row / 2 ) * patch->width ) + col;
2999 rowpos = ( ( col / 2 ) * patch->height ) + row;
3001 Patch_AddBTreeToDrawListInOrder( ( *iter1 ), patch->rowLOD[rowpos] );
3002 ( *iter1 )->push_back( patch->ctrl[col + 2][row] );
3004 if ( row + 1 == patch->height ) {
3010 Patch_TraverseColBTInOrder( iter1, patch->colLOD[colpos], patch->colLOD[colpos + 1], patch->colLOD[colpos + 2], patch->rowLOD[rowpos + 1], patch->rowLOD[rowpos], 0.5, 0.25 );
3014 patch->drawLists = drawLists;
3018 void Patch_DeleteDrawLists( patchMesh_t *patch ){
3019 drawLists_t *drawLists;
3020 drawLists_t::iterator iter;
3022 if ( patch->drawLists == NULL ) {
3026 drawLists = (drawLists_t *)patch->drawLists;
3028 for ( iter = drawLists->begin(); iter != drawLists->end(); iter++ )
3034 patch->drawLists = NULL;
3038 void Patch_DrawLODPatchMesh( patchMesh_t *patch ){
3039 drawLists_t *drawLists;
3041 drawLists_t::iterator iterLists, iterListsNext;
3042 drawList_t::iterator iterList, iterListNext;
3044 //int nGLState = g_pParentWnd->GetCamera()->Camera()->draw_glstate;
3046 if ( patch->drawLists == NULL ) {
3050 drawLists = (drawLists_t *)patch->drawLists;
3052 iterListsNext = drawLists->begin();
3054 for ( iterLists = drawLists->begin(); iterLists != drawLists->end() && iterListsNext != drawLists->end(); iterLists++, iterListsNext++ )
3056 // traverse two drawlists at once to draw a strip
3057 //if (nGLState & DRAW_GL_LINE)
3058 qglBegin( GL_QUAD_STRIP );
3060 // qglBegin(GL_TRIANGLE_STRIP);
3061 for ( iterList = ( *iterLists )->begin(), iterListNext = ( *iterListsNext )->begin(); iterList != ( *iterLists )->end() && iterListNext != ( *iterListsNext )->end(); iterList++, iterListNext++ )
3063 //if (g_PrefsDlg.m_bGLLighting)
3064 qglNormal3fv( ( *iterList ).normal );
3065 //else if (bShade && !g_PrefsDlg.m_bDisplayLists)
3066 // qglColor3f((*iterList).lightmap[0], (*iterList).lightmap[0], (*iterList).lightmap[0]);
3068 qglTexCoord2fv( ( *iterList ).st );
3069 qglVertex3fv( ( *iterList ).xyz );
3071 //if (g_PrefsDlg.m_bGLLighting)
3072 qglNormal3fv( ( *iterListNext ).normal );
3073 //else if (bShade && !g_PrefsDlg.m_bDisplayLists)
3074 // qglColor3f((*iterListNext).lightmap[0], (*iterListNext).lightmap[0], (*iterListNext).lightmap[0]);
3076 qglTexCoord2fv( ( *iterListNext ).st );
3077 qglVertex3fv( ( *iterListNext ).xyz );
3084 for (iterLists=drawLists->begin(); iterLists != drawLists->end(); iterLists++)
3086 qglBegin (GL_LINES); // draw normals
3087 //qglColor3f(1,1,1);
3088 for (iterList=(*iterLists)->begin(); iterList != (*iterLists)->end(); iterList++)
3090 VectorAdd((*iterList).xyz, (*iterList).normal, vNormal);
3091 qglVertex3fv ((*iterList).xyz);
3092 qglVertex3fv (vNormal);
3097 Patch_DrawNormals(patch);
3104 // fast memory-efficient ray-triangle intersection - MollerTrumbore97
3106 #define EPSILON 0.000001
3107 #define CROSS(dest,v1,v2) {dest[0]=v1[1]*v2[2]-v1[2]*v2[1];dest[1]=v1[2]*v2[0]-v1[0]*v2[2];dest[2]=v1[0]*v2[1]-v1[1]*v2[0];}
3108 #define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
3109 #define SUB(dest,v1,v2) {dest[0]=v1[0]-v2[0];dest[1]=v1[1]-v2[1];dest[2]=v1[2]-v2[2];}
3111 int intersect_triangle(float orig[3], float dir[3],
3112 float vert0[3], float vert1[3], float vert2[3],
3113 double *t, double *u, double *v)
3115 double edge1[3], edge2[3], tvec[3], pvec[3], qvec[3];
3118 // find vectors for two edges sharing vert0
3119 SUB(edge1, vert1, vert0);
3120 SUB(edge2, vert2, vert0);
3122 // begin calculating determinant - also used to calculate U parameter
3123 CROSS(pvec, dir, edge2);
3125 // if determinant is near zero, ray lies in plane of triangle
3126 det = DOT(edge1, pvec);
3128 #ifdef TEST_CULL // define TEST_CULL if culling is desired
3132 // calculate distance from vert0 to ray origin
3133 SUB(tvec, orig, vert0);
3135 // calculate U parameter and test bounds
3136 *u = DOT(tvec, pvec);
3137 if (*u < 0.0 || *u > det)
3140 // prepare to test V parameter
3141 CROSS(qvec, tvec, edge1);
3143 // calculate V parameter and test bounds
3144 *v = DOT(dir, qvec);
3145 if (*v < 0.0 || *u + *v > det)
3148 // calculate t, scale parameters, ray intersects triangle
3149 *t = DOT(edge2, qvec);
3150 inv_det = 1.0 / det;
3154 #else // the non-culling branch
3155 if (det > -EPSILON && det < EPSILON)
3157 inv_det = 1.0 / det;
3159 // calculate distance from vert0 to ray origin
3160 SUB(tvec, orig, vert0);
3162 // calculate U parameter and test bounds
3163 *u = DOT(tvec, pvec) * inv_det;
3164 if (*u < 0.0 || *u > 1.0)
3167 // prepare to test V parameter
3168 CROSS(qvec, tvec, edge1);
3170 // calculate V parameter and test bounds
3171 *v = DOT(dir, qvec) * inv_det;
3172 if (*v < 0.0 || *u + *v > 1.0)
3175 // calculate t, ray intersects triangle
3176 *t = DOT(edge2, qvec) * inv_det;
3182 int Triangle_Ray( float orig[3], float dir[3], bool bCullBack,
3183 float vert0[3], float vert1[3], float vert2[3],
3184 double *t, double *u, double *v ){
3185 float edge1[3], edge2[3], tvec[3], pvec[3], qvec[3];
3188 /* find vectors for two edges sharing vert0 */
3189 VectorSubtract( vert1, vert0, edge1 );
3190 VectorSubtract( vert2, vert0, edge2 );
3192 /* begin calculating determinant - also used to calculate U parameter */
3193 CrossProduct( dir, edge2, pvec );
3195 /* if determinant is near zero, ray lies in plane of triangle */
3196 det = DotProduct( edge1, pvec );
3199 if ( det < 0.000001 ) {
3203 // calculate distance from vert0 to ray origin
3204 VectorSubtract( orig, vert0, tvec );
3206 // calculate U parameter and test bounds
3207 *u = DotProduct( tvec, pvec );
3208 if ( *u < 0.0 || *u > det ) {
3212 // prepare to test V parameter
3213 CrossProduct( tvec, edge1, qvec );
3215 // calculate V parameter and test bounds
3216 *v = DotProduct( dir, qvec );
3217 if ( *v < 0.0 || *u + *v > det ) {
3221 // calculate t, scale parameters, ray intersects triangle
3222 *t = DotProduct( edge2, qvec );
3223 inv_det = 1.0 / det;
3230 /* the non-culling branch */
3231 if ( det > -0.000001 && det < 0.000001 ) {
3234 inv_det = 1.0 / det;
3236 /* calculate distance from vert0 to ray origin */
3237 VectorSubtract( orig, vert0, tvec );
3239 /* calculate U parameter and test bounds */
3240 *u = DotProduct( tvec, pvec ) * inv_det;
3241 if ( *u < 0.0 || *u > 1.0 ) {
3245 /* prepare to test V parameter */
3246 CrossProduct( tvec, edge1, qvec );
3248 /* calculate V parameter and test bounds */
3249 *v = DotProduct( dir, qvec ) * inv_det;
3250 if ( *v < 0.0 || *u + *v > 1.0 ) {
3254 /* calculate t, ray intersects triangle */
3255 *t = DotProduct( edge2, qvec ) * inv_det;
3260 bool Patch_Ray( patchMesh_t *patch, vec3_t origin, vec3_t dir, double *t, double *u, double *v ){
3261 drawLists_t *drawLists;
3263 drawLists_t::iterator iterLists, iterListsNext;
3264 drawList_t::iterator i1, i2, i3, i4;
3266 // vec3_t tris[2][3];
3267 bool bIntersect = false;
3268 float tBest = FLT_MAX;
3270 if ( patch->drawLists == NULL ) {
3274 drawLists = (drawLists_t *)patch->drawLists;
3276 iterListsNext = drawLists->begin();
3278 for ( iterLists = drawLists->begin(); iterLists != drawLists->end() && iterListsNext != drawLists->end(); iterLists++, iterListsNext++ )
3280 // traverse two drawlists at once with two iterators each to triangulate
3281 i1 = i3 = ( *iterLists )->begin();
3282 i2 = i4 = ( *iterListsNext )->begin();
3285 while ( i3 != ( *iterLists )->end() && i4 != ( *iterListsNext )->end() )
3287 if ( Triangle_Ray( origin, dir, false, ( *i1 ).xyz, ( *i2 ).xyz, ( *i3 ).xyz, t, u, v ) ) {
3293 if ( Triangle_Ray( origin, dir, false, ( *i3 ).xyz, ( *i4 ).xyz, ( *i2 ).xyz, t, u, v ) ) {
3316 // spog - curve LOD stuff ends
3323 void DrawPatchMesh( patchMesh_t *pm ){
3324 if ( g_PrefsDlg.m_bDisplayLists ) {
3325 if ( pm->bDirty || pm->nListID <= 0 || pm->LODUpdated ) {
3326 if ( pm->nListID <= 0 ) {
3327 pm->nListID = qglGenLists( 1 );
3329 if ( pm->nListID > 0 ) {
3330 qglNewList( pm->nListID, GL_COMPILE_AND_EXECUTE );
3333 Patch_DeleteDrawLists( pm );
3334 Patch_CreateDrawLists( pm );
3336 Patch_DrawLODPatchMesh( pm );
3338 if ( pm->nListID > 0 ) {
3343 pm->LODUpdated = false;
3347 qglCallList( pm->nListID );
3352 if ( pm->bDirty || pm->LODUpdated ) {
3353 Patch_DeleteDrawLists( pm );
3354 Patch_CreateDrawLists( pm );
3356 pm->LODUpdated = false;
3358 Patch_DrawLODPatchMesh( pm );
3367 void DrawPatchControls( patchMesh_t *pm ){
3369 bool bSelectedPoints[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT];
3371 bool bOverlay = pm->bOverlay;
3374 if ( g_bPatchBendMode ) {
3376 if ( g_bPatchAxisOnRow ) {
3377 qglColor3f( 1, 0, 1 );
3378 if ( !g_PrefsDlg.m_bGlPtWorkaround ) {
3379 qglBegin( GL_POINTS );
3380 for ( i = 0; i < pm->width; i++ )
3382 qglVertex3fv( pm->ctrl[i][g_nPatchAxisIndex].xyz );
3388 qglLineWidth( 2.0 );
3389 qglBegin( GL_LINES );
3390 for ( i = 0; i < pm->width; i++ )
3392 DrawAlternatePoint( pm->ctrl[i][g_nPatchAxisIndex].xyz, 0 );
3395 qglLineWidth( 1.0 );
3398 if ( g_nPatchBendState == BEND_SELECT_EDGE || g_nPatchBendState == BEND_BENDIT || g_nPatchBendState == BEND_SELECT_ORIGIN ) {
3399 if ( !g_PrefsDlg.m_bGlPtWorkaround ) {
3400 qglColor3f( 0, 0, 1 );
3401 qglBegin( GL_POINTS );
3402 if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
3403 qglVertex3fv( g_vBendOrigin );
3407 for ( i = 0; i < pm->width; i++ )
3409 if ( g_bPatchLowerEdge ) {
3410 for ( j = 0; j < g_nPatchAxisIndex; j++ )
3411 qglVertex3fv( pm->ctrl[i][j].xyz );
3415 for ( j = pm->height - 1; j > g_nPatchAxisIndex; j-- )
3416 qglVertex3fv( pm->ctrl[i][j].xyz );
3423 qglColor3f( 0, 0, 1 );
3424 qglLineWidth( 2.0 );
3425 qglBegin( GL_LINES );
3426 if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
3427 DrawAlternatePoint( g_vBendOrigin, 0 );
3431 for ( i = 0; i < pm->width; i++ )
3433 if ( g_bPatchLowerEdge ) {
3434 for ( j = 0; j < g_nPatchAxisIndex; j++ )
3436 DrawAlternatePoint( pm->ctrl[i][j].xyz, 0 );
3441 for ( j = pm->height - 1; j > g_nPatchAxisIndex; j-- )
3443 DrawAlternatePoint( pm->ctrl[i][j].xyz, 0 );
3449 qglLineWidth( 1.0 );
3455 if ( !g_PrefsDlg.m_bGlPtWorkaround ) {
3456 qglColor3f( 1, 0, 1 );
3457 qglBegin( GL_POINTS );
3458 for ( i = 0; i < pm->height; i++ )
3460 qglVertex3fv( pm->ctrl[g_nPatchAxisIndex][i].xyz );
3465 qglColor3f( 1, 0, 1 );
3466 qglLineWidth( 2.0 );
3467 qglBegin( GL_LINES );
3468 for ( i = 0; i < pm->height; i++ )
3470 DrawAlternatePoint( pm->ctrl[g_nPatchAxisIndex][i].xyz, 0 );
3473 qglLineWidth( 1.0 );
3476 if ( g_nPatchBendState == BEND_SELECT_EDGE || g_nPatchBendState == BEND_BENDIT || g_nPatchBendState == BEND_SELECT_ORIGIN ) {
3477 if ( !g_PrefsDlg.m_bGlPtWorkaround ) {
3478 qglColor3f( 0, 0, 1 );
3479 qglBegin( GL_POINTS );
3480 for ( i = 0; i < pm->height; i++ )
3482 if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
3483 qglVertex3fv( pm->ctrl[g_nBendOriginIndex][i].xyz );
3487 if ( g_bPatchLowerEdge ) {
3488 for ( j = 0; j < g_nPatchAxisIndex; j++ )
3489 qglVertex3fv( pm->ctrl[j][i].xyz );
3493 for ( j = pm->width - 1; j > g_nPatchAxisIndex; j-- )
3494 qglVertex3fv( pm->ctrl[j][i].xyz );
3501 qglColor3f( 0, 0, 1 );
3502 qglLineWidth( 2.0 );
3503 qglBegin( GL_LINES );
3504 for ( i = 0; i < pm->height; i++ )
3506 if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
3507 DrawAlternatePoint( pm->ctrl[g_nBendOriginIndex][i].xyz, 0 );
3511 if ( g_bPatchLowerEdge ) {
3512 for ( j = 0; j < g_nPatchAxisIndex; j++ )
3514 DrawAlternatePoint( pm->ctrl[j][i].xyz, 0 );
3519 for ( j = pm->width - 1; j > g_nPatchAxisIndex; j-- )
3521 DrawAlternatePoint( pm->ctrl[j][i].xyz, 0 );
3527 qglLineWidth( 1.0 );
3534 //qglDisable(GL_TEXTURE_2D); // stops point colours being multiplied by texture colour..
3535 //draw CV lattice - could be made optional
3536 //qglDisable( GL_CULL_FACE );
3537 // qglPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
3538 qglEnable( GL_POLYGON_OFFSET_LINE );
3539 if ( g_PrefsDlg.m_bNoStipple == FALSE ) {
3540 qglDisable( GL_LINE_STIPPLE );
3543 qglColor3f( 1.0f, 0.75f, 0.0f );
3544 for ( i = 0 ; i + 1 < pm->width ; i++ )
3546 qglBegin( GL_QUAD_STRIP );
3547 for ( j = 0 ; j < pm->height ; j++ )
3549 qglVertex3fv( pm->ctrl[i][j].xyz );
3550 qglVertex3fv( pm->ctrl[i + 1][j].xyz );
3554 qglDisable( GL_POLYGON_OFFSET_LINE );
3555 //if (g_PrefsDlg.m_bNoStipple == FALSE)
3556 // qglEnable (GL_LINE_STIPPLE);
3558 // draw selection handles
3559 if ( !g_PrefsDlg.m_bGlPtWorkaround ) {
3561 qglBegin( GL_POINTS );
3562 for ( i = 0 ; i < pm->width ; i++ )
3564 for ( j = 0 ; j < pm->height ; j++ )
3566 if ( PointInMoveList( pm->ctrl[i][j].xyz ) != -1 ) {
3567 bSelectedPoints[i][j] = true;
3571 bSelectedPoints[i][j] = false;
3572 if ( i & 0x01 || j & 0x01 ) {
3573 qglColor3f( 1, 0, 1 );
3576 qglColor3f( 0, 1, 0 );
3579 qglVertex3fv( pm->ctrl[i][j].xyz );
3583 qglColor3f( 0, 0, 1 );
3584 for ( i = 0 ; i < pm->width ; i++ )
3586 for ( j = 0 ; j < pm->height ; j++ )
3588 if ( bSelectedPoints[i][j] ) {
3589 qglVertex3fv( pm->ctrl[i][j].xyz );
3597 qglLineWidth( 2.0 );
3598 qglBegin( GL_LINES );
3599 for ( i = 0; i < pm->width; i++ )
3601 for ( j = 0; j < pm->height; j++ )
3603 if ( PointInMoveList( pm->ctrl[i][j].xyz ) != -1 ) {
3604 bSelectedPoints[i][j] = true;
3608 bSelectedPoints[i][j] = false;
3609 if ( i & 0x01 || j & 0x01 ) {
3610 qglColor3f( 1, 0, 1 );
3613 qglColor3f( 0, 1, 0 );
3617 DrawAlternatePoint( pm->ctrl[i][j].xyz, 0 );
3621 qglColor3f( 0, 0, 1 );
3622 for ( i = 0; i < pm->width; i++ )
3624 for ( j = 0; j < pm->height; j++ )
3626 if ( bSelectedPoints[i][j] ) {
3628 DrawAlternatePoint( pm->ctrl[i][j].xyz, 0 );
3633 qglLineWidth( 1.0 );
3637 if ( !g_PrefsDlg.m_bGlPtWorkaround ) {
3639 qglBegin( GL_POINTS );
3640 for ( i = 0 ; i < pm->width ; i++ )
3642 for ( j = 0 ; j < pm->height ; j++ )
3644 if ( i & 0x01 || j & 0x01 ) {
3645 qglColor3f( 1, 0, 1 );
3648 qglColor3f( 0, 1, 0 );
3650 qglVertex3fv( pm->ctrl[i][j].xyz );
3657 qglLineWidth( 2.0 );
3658 qglBegin( GL_LINES );
3659 for ( i = 0 ; i < pm->width ; i++ )
3661 for ( j = 0 ; j < pm->height ; j++ )
3663 if ( i & 0x01 || j & 0x01 ) {
3664 qglColor3f( 1, 0, 1 );
3667 qglColor3f( 0, 1, 0 );
3670 DrawAlternatePoint( pm->ctrl[i][j].xyz, 0 );
3674 qglLineWidth( 1.0 );
3685 void Patch_DrawXY( patchMesh_t *pm ){
3686 qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
3688 if ( pm->bSelected ) {
3689 qglColor3fv( g_qeglobals.d_savedinfo.colors[COLOR_SELBRUSHES] );
3690 if ( g_PrefsDlg.m_bNoStipple == FALSE ) {
3691 qglEnable( GL_LINE_STIPPLE );
3696 DrawPatchMesh( pm );
3698 if ( ( pm->bSelected && ( g_qeglobals.d_select_mode == sel_curvepoint
3699 || g_qeglobals.d_select_mode == sel_area
3700 || g_bPatchBendMode ) )
3702 DrawPatchControls( pm );
3711 void Patch_DrawCam( patchMesh_t *pm ){
3712 qglPushAttrib( GL_ALL_ATTRIB_BITS ); // save the current state
3714 if ( g_bPatchWireFrame ) {
3715 qglDisable( GL_CULL_FACE );
3716 qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
3717 qglDisable( GL_TEXTURE_2D );
3718 if ( g_PrefsDlg.m_bGLLighting ) {
3719 qglDisable( GL_LIGHTING );
3722 DrawPatchMesh( pm );
3724 //if (g_PrefsDlg.m_bGLLighting)
3725 // qglEnable(GL_LIGHTING);
3726 //qglEnable( GL_CULL_FACE );
3730 qglDisable( GL_CULL_FACE );
3731 qglBindTexture( GL_TEXTURE_2D, pm->d_texture->texture_number );
3732 qglPolygonMode( GL_FRONT, GL_FILL );
3733 qglPolygonMode( GL_BACK, GL_LINE );
3735 if ( pm->pShader->getTrans() < 1.0f ) {
3736 qglEnable( GL_BLEND );
3737 qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
3738 qglColor4f( pm->d_texture->color[0], pm->d_texture->color[1], pm->d_texture->color[2], pm->pShader->getTrans() );
3741 DrawPatchMesh( pm ); // both sides
3744 qglPopAttrib(); // restore saved state
3747 void ConvexHullForSection( float section[2][4][7] ) {
3750 void BrushesForSection( float section[2][4][7] ) {
3758 void Patch_BuildPoints( brush_t *b ){
3760 b->patchBrush = false;
3761 for ( f = b->brush_faces ; f ; f = f->next )
3763 if ( f->texdef.flags & SURF_PATCH ) {
3764 b->patchBrush = true;
3765 //vec3_t vMin, vMax;
3766 //Patch_CalcBounds(&patchMeshes[b->nPatchID], vMin, vMax);
3767 //VectorCopy(vMin, b->mins);
3768 //VectorCopy(vMax, b->maxs);
3779 void Patch_Move( patchMesh_t *pm, const vec3_t vMove, bool bRebuild ){
3781 for ( int w = 0; w < pm->width; w++ )
3783 for ( int h = 0; h < pm->height; h++ )
3785 VectorAdd( pm->ctrl[w][h].xyz, vMove, pm->ctrl[w][h].xyz );
3788 // bRebuild is never true
3791 Patch_CalcBounds( pm, vMin, vMax );
3792 //Brush_RebuildBrush(patchMeshes[n].pSymbiot, vMin, vMax);
3794 UpdatePatchInspector();
3803 void Patch_ApplyMatrix( patchMesh_t *p, const vec3_t vOrigin, const vec3_t vMatrix[3], bool bSnap ){
3806 for ( int w = 0; w < p->width; w++ )
3808 for ( int h = 0; h < p->height; h++ )
3810 if ( ( ( g_qeglobals.d_select_mode == sel_curvepoint && g_qeglobals.d_num_move_points != 0 ) || g_bPatchBendMode )
3811 && PointInMoveList( p->ctrl[w][h].xyz ) == -1 ) { // snap selected points only, if selected
3814 VectorSubtract( p->ctrl[w][h].xyz, vOrigin, vTemp );
3815 for ( int j = 0; j < 3; j++ )
3817 p->ctrl[w][h].xyz[j] = DotProduct( vTemp, vMatrix[j] ) + vOrigin[j];
3819 p->ctrl[w][h].xyz[j] = floor( p->ctrl[w][h].xyz[j] + 0.5 );
3825 Patch_CalcBounds( p, vMin, vMax );
3826 Brush_RebuildBrush( p->pSymbiot, vMin, vMax );
3834 void Patch_EditPatch(){
3835 //--patchMesh_t* p = &patchMeshes[n];
3836 g_qeglobals.d_numpoints = 0;
3837 g_qeglobals.d_num_move_points = 0;
3839 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
3841 if ( pb->patchBrush ) {
3842 patchMesh_t* p = pb->pPatch;
3843 for ( int i = 0 ; i < p->width ; i++ )
3845 for ( int j = 0 ; j < p->height ; j++ )
3847 VectorCopy( p->ctrl[i][j].xyz, g_qeglobals.d_points[g_qeglobals.d_numpoints] );
3848 if ( g_qeglobals.d_numpoints < MAX_POINTS - 1 ) {
3849 g_qeglobals.d_numpoints++;
3855 g_qeglobals.d_select_mode = sel_curvepoint;
3856 //--g_nSelectedPatch = n;
3866 //FIXME: need all sorts of asserts throughout a lot of this crap
3867 void Patch_Deselect(){
3868 //--g_nSelectedPatch = -1;
3869 g_qeglobals.d_select_mode = sel_brush;
3871 for ( brush_t *b = selected_brushes.next ; b != &selected_brushes ; b = b->next )
3873 if ( b->patchBrush ) {
3874 b->pPatch->bSelected = false;
3878 //for (int i = 0; i < numPatchMeshes; i++)
3879 // patchMeshes[i].bSelected = false;
3881 if ( g_bPatchBendMode ) {
3884 // if (g_bPatchInsertMode)
3885 // Patch_InsDelToggle();
3894 void Patch_Select( patchMesh_t *p ){
3895 // maintained for point manip.. which i need to fix as this
3896 // is pf error prone
3897 //--g_nSelectedPatch = n;
3898 p->bSelected = true;
3907 void Patch_Deselect( patchMesh_t *p ){
3908 p->bSelected = false;
3917 extern BTNode_t *BTree_Delete( BTNode_t *pBT );
3918 extern BTListList_t *BTree_DeleteListFromList( BTListList_t *pBTListList );
3920 void Patch_Delete( patchMesh_t *p ){
3921 if ( p->pSymbiot ) { // Hydra - added a check to prevent access violations.
3922 p->pSymbiot->pPatch = NULL;
3923 p->pSymbiot->patchBrush = false;
3926 // spog - free dynamically allocated memory used by LODs
3927 int rowcount = ( ( MAX_PATCH_WIDTH - 1 ) / 2 ) * MAX_PATCH_HEIGHT;
3928 int colcount = ( ( MAX_PATCH_HEIGHT - 1 ) / 2 ) * MAX_PATCH_WIDTH;
3930 for ( i = 0; i < rowcount; i++ )
3931 p->rowLOD[i] = BTree_Delete( p->rowLOD[i] );
3932 for ( i = 0; i < colcount; i++ )
3933 p->colLOD[i] = BTree_Delete( p->colLOD[i] );
3935 // delete display list associated with patch
3936 if ( p->nListID != -1 ) {
3937 qglDeleteLists( p->nListID, 1 ); // list#, number of lists
3940 // delete LOD drawLists
3941 Patch_DeleteDrawLists( p );
3948 UpdatePatchInspector();
3957 void Patch_Scale( patchMesh_t *p, const vec3_t vOrigin, const vec3_t vAmt, bool bRebuild ){
3959 for ( int w = 0; w < p->width; w++ )
3961 for ( int h = 0; h < p->height; h++ )
3963 if ( g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList( p->ctrl[w][h].xyz ) == -1 ) {
3966 for ( int i = 0 ; i < 3 ; i++ )
3968 p->ctrl[w][h].xyz[i] -= vOrigin[i];
3969 p->ctrl[w][h].xyz[i] *= vAmt[i];
3970 p->ctrl[w][h].xyz[i] += vOrigin[i];
3976 Patch_CalcBounds( p, vMin, vMax );
3977 Brush_RebuildBrush( p->pSymbiot, vMin, vMax );
3979 UpdatePatchInspector();
3988 void Patch_SetView( int n ){
3989 g_bSameView = ( n == g_nPatchClickedView );
3990 g_nPatchClickedView = n;
3999 // FIXME: need array validation throughout
4000 void Patch_SetTexture( patchMesh_t *p, texdef_t *tex_def, IPluginTexdef* pPlugTexdef ){
4001 // NOTE: I don't know for sure if this happens
4003 p->pShader->DecRef();
4005 p->pShader = QERApp_Shader_ForName( tex_def->GetName() );
4006 p->pShader->IncRef();
4007 p->d_texture = p->pShader->getTexture();
4009 UpdatePatchInspector();
4018 bool Patch_DragScale( patchMesh_t *p, vec3_t vAmt, vec3_t vMove ){
4019 vec3_t vMin, vMax, vScale, vTemp, vMid;
4022 Patch_CalcBounds( p, vMin, vMax );
4024 VectorSubtract( vMax, vMin, vTemp );
4026 // if we are scaling in the same dimension the patch has no depth
4027 for ( i = 0; i < 3; i++ )
4029 if ( vTemp[i] == 0 && vMove[i] != 0 ) {
4030 //Patch_Move(n, vMove, true);
4035 for ( i = 0 ; i < 3 ; i++ )
4036 vMid[i] = ( vMin[i] + ( ( vMax[i] - vMin[i] ) / 2 ) );
4038 for ( i = 0; i < 3; i++ )
4040 if ( vAmt[i] != 0 ) {
4041 vScale[i] = 1.0 + vAmt[i] / vTemp[i];
4049 Patch_Scale( p, vMid, vScale, false );
4051 VectorSubtract( vMax, vMin, vTemp );
4053 Patch_CalcBounds( p, vMin, vMax );
4055 VectorSubtract( vMax, vMin, vMid );
4057 VectorSubtract( vMid, vTemp, vTemp );
4059 VectorScale( vTemp, 0.5, vTemp );
4061 // abs of both should always be equal
4062 if ( !VectorCompare( vMove, vAmt ) ) {
4063 for ( i = 0; i < 3; i++ )
4065 if ( vMove[i] != vAmt[i] ) {
4066 vTemp[i] = -( vTemp[i] );
4071 Patch_Move( p, vTemp );
4080 void Patch_InsertColumn( patchMesh_t *p, bool bAdd ){
4085 if ( p->width + 2 >= MAX_PATCH_WIDTH ) {
4090 // check for selected column points
4091 for ( h = 0; h < p->height; h++ )
4093 for ( w = 1; w < p->width; w += 2 )
4094 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4097 if ( w < p->width ) {
4100 for ( w = 0; w < p->width; w += 2 )
4101 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4104 if ( w < p->width ) {
4109 if ( w >= p->width ) {
4115 else if ( w == 0 ) {
4123 for ( h = 0; h < p->height; h++ )
4125 for ( width = p->width - 1; width > w; width-- )
4126 memcpy( &p->ctrl[width + 2][h],&p->ctrl[width][h], sizeof( drawVert_t ) );
4128 // set two new column points
4129 memcpy( &p->ctrl[w + 2][h],&p->ctrl[w][h], sizeof( drawVert_t ) );
4130 memcpy( &p->ctrl[w + 1][h],&p->ctrl[w - 1][h], sizeof( drawVert_t ) );
4132 for ( i = 0; i < 3; i++ ) // xyz
4134 vTemp[i] = p->ctrl[w][h].xyz[i] - p->ctrl[w - 1][h].xyz[i];
4135 p->ctrl[w + 1][h].xyz[i] = p->ctrl[w + 1][h].xyz[i] + ( vTemp[i] / 2 );
4137 vTemp[i] = p->ctrl[w - 2][h].xyz[i] - p->ctrl[w - 1][h].xyz[i];
4138 p->ctrl[w - 1][h].xyz[i] = p->ctrl[w - 1][h].xyz[i] + ( vTemp[i] / 2 );
4140 vTemp[i] = p->ctrl[w + 1][h].xyz[i] - p->ctrl[w - 1][h].xyz[i];
4141 p->ctrl[w][h].xyz[i] = p->ctrl[w - 1][h].xyz[i] + ( vTemp[i] / 2 );
4143 for ( i = 0; i < 2; i++ ) // st
4145 stTemp[i] = p->ctrl[w][h].st[i] - p->ctrl[w - 1][h].st[i];
4146 p->ctrl[w + 1][h].st[i] = p->ctrl[w + 1][h].st[i] + ( stTemp[i] / 2 );
4148 stTemp[i] = p->ctrl[w - 2][h].st[i] - p->ctrl[w - 1][h].st[i];
4149 p->ctrl[w - 1][h].st[i] = p->ctrl[w - 1][h].st[i] + ( stTemp[i] / 2 );
4151 stTemp[i] = p->ctrl[w + 1][h].st[i] - p->ctrl[w - 1][h].st[i];
4152 p->ctrl[w][h].st[i] = p->ctrl[w - 1][h].st[i] + ( stTemp[i] / 2 );
4157 // deselect all points to keep things neat
4158 if ( g_qeglobals.d_select_mode == sel_curvepoint ) {
4162 UpdatePatchInspector();
4171 void Patch_InsertRow( patchMesh_t *p, bool bAdd ){
4172 int h, w, i, height;
4176 if ( p->height + 2 >= MAX_PATCH_HEIGHT ) {
4181 // check for selected row points
4182 for ( w = 0; w < p->width; w++ )
4184 for ( h = 1; h < p->height; h += 2 )
4185 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4188 if ( h < p->height ) {
4191 for ( h = 0; h < p->height; h += 2 )
4192 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4195 if ( h < p->height ) {
4199 if ( h >= p->height ) {
4205 else if ( h == 0 ) {
4213 for ( w = 0; w < p->width; w++ )
4215 for ( height = p->height - 1; height > h; height-- )
4216 memcpy( &p->ctrl[w][height + 2],&p->ctrl[w][height], sizeof( drawVert_t ) );
4218 // set two new row points
4219 memcpy( &p->ctrl[w][h + 2],&p->ctrl[w][h], sizeof( drawVert_t ) );
4220 memcpy( &p->ctrl[w][h + 1],&p->ctrl[w][h - 1], sizeof( drawVert_t ) );
4222 for ( i = 0; i < 3; i++ ) // xyz
4224 vTemp[i] = p->ctrl[w][h].xyz[i] - p->ctrl[w][h - 1].xyz[i];
4225 p->ctrl[w][h + 1].xyz[i] = p->ctrl[w][h + 1].xyz[i] + ( vTemp[i] / 2 );
4227 vTemp[i] = p->ctrl[w][h - 2].xyz[i] - p->ctrl[w][h - 1].xyz[i];
4228 p->ctrl[w][h - 1].xyz[i] = p->ctrl[w][h - 1].xyz[i] + ( vTemp[i] / 2 );
4230 vTemp[i] = p->ctrl[w][h + 1].xyz[i] - p->ctrl[w][h - 1].xyz[i];
4231 p->ctrl[w][h].xyz[i] = p->ctrl[w][h - 1].xyz[i] + ( vTemp[i] / 2 );
4233 for ( i = 0; i < 2; i++ ) // st
4235 stTemp[i] = p->ctrl[w][h].st[i] - p->ctrl[w][h - 1].st[i];
4236 p->ctrl[w][h + 1].st[i] = p->ctrl[w][h + 1].st[i] + ( stTemp[i] / 2 );
4238 stTemp[i] = p->ctrl[w][h - 2].st[i] - p->ctrl[w][h - 1].st[i];
4239 p->ctrl[w][h - 1].st[i] = p->ctrl[w][h - 1].st[i] + ( stTemp[i] / 2 );
4241 stTemp[i] = p->ctrl[w][h + 1].st[i] - p->ctrl[w][h - 1].st[i];
4242 p->ctrl[w][h].st[i] = p->ctrl[w][h - 1].st[i] + ( stTemp[i] / 2 );
4247 // deselect all points to keep things neat
4248 if ( g_qeglobals.d_select_mode == sel_curvepoint ) {
4252 UpdatePatchInspector();
4260 void Patch_RemoveRow( patchMesh_t *p, bool bFirst ){
4261 int w, h, i, height;
4264 bool bExtrapolate = true;
4266 if ( p->height <= MIN_PATCH_HEIGHT ) {
4271 for ( w = 0; w < p->width; w++ )
4273 for ( h = 0; h < p->height; h += 2 )
4274 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4277 if ( h < p->height ) {
4280 for ( h = 1; h < p->height; h += 2 )
4281 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4284 if ( h < p->height ) {
4289 if ( h >= p->height ) {
4290 bExtrapolate = false;
4296 else if ( h <= 0 ) {
4299 else if ( h > p->height - 3 ) {
4308 for ( w = 0; w < p->width; w++ )
4310 if ( bExtrapolate ) {
4311 for ( i = 0; i < 3; i++ ) // xyz
4313 vTemp[i] = p->ctrl[w][h + 2].xyz[i] - p->ctrl[w][h - 2].xyz[i];
4314 p->ctrl[w][h - 1].xyz[i] = p->ctrl[w][h - 2].xyz[i] + ( vTemp[i] / 2 );
4316 vTemp[i] = p->ctrl[w][h].xyz[i] - p->ctrl[w][h - 1].xyz[i];
4317 p->ctrl[w][h - 1].xyz[i] = p->ctrl[w][h - 1].xyz[i] + ( vTemp[i] * 2 );
4320 for ( i = 0; i < 2; i++ ) // st
4322 stTemp[i] = p->ctrl[w][h + 2].st[i] - p->ctrl[w][h - 2].st[i];
4323 p->ctrl[w][h - 1].st[i] = p->ctrl[w][h - 2].st[i] + ( stTemp[i] / 2 );
4325 stTemp[i] = p->ctrl[w][h].st[i] - p->ctrl[w][h - 1].st[i];
4326 p->ctrl[w][h - 1].st[i] = p->ctrl[w][h - 1].st[i] + ( stTemp[i] * 2 );
4336 for ( height = h; height < p->height; height++ )
4337 memcpy( &p->ctrl[w][height], &p->ctrl[w][height + 2], sizeof( drawVert_t ) );
4339 // deselect all points to keep things neat
4340 if ( g_qeglobals.d_select_mode == sel_curvepoint ) {
4344 UpdatePatchInspector();
4352 void Patch_RemoveColumn( patchMesh_t *p, bool bFirst ){
4356 bool bExtrapolate = true;
4358 if ( p->width <= MIN_PATCH_WIDTH ) {
4363 for ( h = 0; h < p->height; h++ )
4365 for ( w = 0; w < p->width; w += 2 )
4366 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4369 if ( w < p->width ) {
4372 for ( w = 1; w < p->width; w += 2 )
4373 if ( PointInMoveList( p->ctrl[w][h].xyz ) != -1 ) {
4376 if ( w < p->width ) {
4381 if ( w >= p->width ) {
4382 bExtrapolate = false;
4388 else if ( w <= 0 ) {
4391 else if ( w > p->width - 3 ) {
4400 for ( h = 0; h < p->height; h++ )
4402 if ( bExtrapolate ) {
4403 for ( i = 0; i < 3; i++ ) // xyz
4405 vTemp[i] = p->ctrl[w + 2][h].xyz[i] - p->ctrl[w - 2][h].xyz[i];
4406 p->ctrl[w - 1][h].xyz[i] = p->ctrl[w - 2][h].xyz[i] + ( vTemp[i] / 2 );
4408 vTemp[i] = p->ctrl[w][h].xyz[i] - p->ctrl[w - 1][h].xyz[i];
4409 p->ctrl[w - 1][h].xyz[i] = p->ctrl[w - 1][h].xyz[i] + ( vTemp[i] * 2 );
4412 for ( i = 0; i < 2; i++ ) // st
4414 stTemp[i] = p->ctrl[w + 2][h].st[i] - p->ctrl[w - 2][h].st[i];
4415 p->ctrl[w - 1][h].st[i] = p->ctrl[w - 2][h].st[i] + ( stTemp[i] / 2 );
4417 stTemp[i] = p->ctrl[w][h].st[i] - p->ctrl[w - 1][h].st[i];
4418 p->ctrl[w - 1][h].st[i] = p->ctrl[w - 1][h].st[i] + ( stTemp[i] * 2 );
4429 for ( width = w; width < p->width; width++ )
4430 memcpy( &p->ctrl[width][h], &p->ctrl[width + 2][h], sizeof( drawVert_t ) );
4432 // deselect all points to keep things neat
4433 if ( g_qeglobals.d_select_mode == sel_curvepoint ) {
4437 UpdatePatchInspector();
4446 void Patch_AdjustColumns(patchMesh_t *p, int nCols)
4448 vec3_t vTemp, vTemp2;
4451 if (nCols & 0x01 || p->width + nCols < 3 || p->width + nCols > MAX_PATCH_WIDTH)
4454 // add in column adjustment
4457 for (h = 0; h < p->height; h++)
4459 // for each column, we need to evenly disperse p->width number
4460 // of points across the old bounds
4462 // calc total distance to interpolate
4463 VectorSubtract(p->ctrl[p->width - 1 - nCols][h].xyz, p->ctrl[0][h].xyz, vTemp);
4466 for (i = 0; i < 3; i ++)
4468 vTemp2[i] = vTemp[i] / (p->width - 1);
4472 for (w = 0; w < p->width-1; w++)
4474 VectorAdd(p->ctrl[w][h].xyz, vTemp2, p->ctrl[w+1][h].xyz);
4478 for ( w = 0 ; w < p->width ; w++ )
4480 for ( h = 0 ; h < p->height ; h++ )
4482 p->ctrl[w][h].st[0] = 4 * (float)w / (p->width - 1);
4483 p->ctrl[w][h].st[1] = 4 * (float)h / (p->height - 1);
4486 UpdatePatchInspector();
4496 void Patch_AdjustRows(patchMesh_t *p, int nRows)
4498 vec3_t vTemp, vTemp2;
4501 if (nRows & 0x01 || p->height + nRows < 3 || p->height + nRows > MAX_PATCH_HEIGHT)
4504 // add in column adjustment
4507 for (w = 0; w < p->width; w++)
4509 // for each row, we need to evenly disperse p->height number
4510 // of points across the old bounds
4512 // calc total distance to interpolate
4513 VectorSubtract(p->ctrl[w][p->height - 1 - nRows].xyz, p->ctrl[w][0].xyz, vTemp);
4515 //vTemp[0] = vTemp[1] = vTemp[2] = 0;
4516 //for (h = 0; h < p->height - nRows; h ++)
4518 // VectorAdd(vTemp, p->ctrl[w][h], vTemp);
4522 for (i = 0; i < 3; i ++)
4524 vTemp2[i] = vTemp[i] / (p->height - 1);
4528 for (h = 0; h < p->height-1; h++)
4530 VectorAdd(p->ctrl[w][h].xyz, vTemp2, p->ctrl[w][h+1].xyz);
4534 for ( w = 0 ; w < p->width ; w++ )
4536 for ( h = 0 ; h < p->height ; h++ )
4538 p->ctrl[w][h].st[0] = 4 * (float)w / (p->width - 1);
4539 p->ctrl[w][h].st[1] = 4 * (float)h / (p->height - 1);
4542 UpdatePatchInspector();
4552 void Patch_DisperseRows(){
4553 vec3_t vTemp, vTemp2;
4557 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
4559 if ( pb->patchBrush ) {
4560 patchMesh_t *p = pb->pPatch;
4562 for ( w = 0; w < p->width; w++ )
4564 // for each row, we need to evenly disperse p->height number
4565 // of points across the old bounds
4567 // calc total distance to interpolate
4568 VectorSubtract( p->ctrl[w][p->height - 1].xyz, p->ctrl[w][0].xyz, vTemp );
4570 //vTemp[0] = vTemp[1] = vTemp[2] = 0;
4571 //for (h = 0; h < p->height - nRows; h ++)
4573 // VectorAdd(vTemp, p->ctrl[w][h], vTemp);
4577 for ( i = 0; i < 3; i++ )
4579 vTemp2[i] = vTemp[i] / ( p->height - 1 );
4583 for ( h = 0; h < p->height - 1; h++ )
4585 VectorAdd( p->ctrl[w][h].xyz, vTemp2, p->ctrl[w][h + 1].xyz );
4587 Patch_Naturalize( p );
4592 UpdatePatchInspector();
4597 Patch_DisperseIntermediateRows
4601 void Patch_DisperseIntermediateRows(){
4602 vec3_t vTemp, vTemp2;
4606 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
4608 if ( pb->patchBrush ) {
4609 patchMesh_t *p = pb->pPatch;
4611 for ( w = 0; w < p->width; w++ )
4614 for ( h = 0; h < p->height; h += 2 )
4616 // calc distance to interpolate
4617 VectorSubtract( p->ctrl[w][h + 2].xyz, p->ctrl[w][h].xyz, vTemp );
4620 for ( i = 0; i < 3; i++ )
4622 vTemp2[i] = vTemp[i] / 2;
4625 // move control points
4626 VectorAdd( p->ctrl[w][h].xyz, vTemp2, p->ctrl[w][h + 1].xyz );
4631 UpdatePatchInspector();
4636 Patch_DisperseIntermediateColumns
4639 void Patch_DisperseIntermediateColumns(){
4640 vec3_t vTemp, vTemp2;
4644 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
4646 if ( pb->patchBrush ) {
4647 patchMesh_t *p = pb->pPatch;
4649 for ( h = 0; h < p->height; h++ )
4652 for ( w = 0; w < p->width; w += 2 )
4654 // calc distance to interpolate
4655 VectorSubtract( p->ctrl[w + 2][h].xyz, p->ctrl[w][h].xyz, vTemp );
4658 for ( i = 0; i < 3; i++ )
4660 vTemp2[i] = vTemp[i] / 2;
4663 // move control points
4664 VectorAdd( p->ctrl[w][h].xyz, vTemp2, p->ctrl[w + 1][h].xyz );
4669 UpdatePatchInspector();
4676 Patch_AdjustSelected
4679 void Patch_AdjustSelected( bool bInsert, bool bColumn, bool bFlag ){
4680 bool bUpdate = false;
4681 for ( brush_t* pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
4683 if ( pb->patchBrush ) {
4686 Patch_InsertColumn( pb->pPatch, bFlag );
4690 Patch_InsertRow( pb->pPatch, bFlag );
4696 Patch_RemoveColumn( pb->pPatch, bFlag );
4700 Patch_RemoveRow( pb->pPatch, bFlag );
4705 patchMesh_t *p = pb->pPatch;
4706 Patch_CalcBounds( p, vMin, vMax );
4707 Brush_RebuildBrush( p->pSymbiot, vMin, vMax );
4708 pb->pPatch->bDirty = true; // rebuild LOD trees and their normals
4712 Sys_UpdateWindows( W_ALL );
4719 Patch_AdjustSelectedRowCols
4723 void Patch_AdjustSelectedRowCols(int nRows, int nCols)
4725 for (brush_t* pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next)
4729 Patch_InsertColumn(pb->pPatch, false);
4732 Patch_AdjustRows(pb->pPatch, nRows);
4737 Patch_AdjustColumns(pb->pPatch, nCols);
4741 UpdatePatchInspector();
4748 temporary stuff, detect potential problems when saving the texture name
4749 will correct the patch on the fly if problem detected
4753 \todo performance issue with CheckName calls
4754 don't call this too much, only when absolutely necessary
4755 strategies that call here too much are known to be slow
4756 patch 84 to bug 253 adds an additionnal check for textures/
4758 void CheckName( patchMesh_t *p, char *pname ){
4759 if ( strncmp( p->pShader->getName(), "textures/", 9 ) != 0 ) {
4760 p->pShader = QERApp_Shader_ForName( SHADER_NOT_FOUND );
4763 // some manage to get long filename textures (with spaces) in their maps
4764 if ( strchr( p->pShader->getName(), ' ' ) ) {
4766 sprintf( Msg1, "Can't save texture with spaces in name. Rename %s\nNOTE: This message may popup several times .. once for each buggy face detected.", p->pShader->getName() );
4767 Sys_Printf( "%s\n", Msg1 );
4768 gtk_MessageBox( g_pParentWnd->m_pWidget, Msg1, "Error saving map", MB_OK );
4769 strcpy( pname, SHADER_NOT_FOUND );
4770 p->pShader = QERApp_Shader_ForName( SHADER_NOT_FOUND );
4771 p->d_texture = p->pShader->getTexture();
4774 strcpy( pname, p->pShader->getName() + 9 ); // remove "textures/"
4782 void Patch_Write( patchMesh_t *p, MemStream *file ){
4785 MemFile_fprintf( file, " {\n patchDef2\n {\n" );
4787 CheckName( p, pname );
4788 MemFile_fprintf( file, " %s\n", pname );
4789 MemFile_fprintf( file, " ( %i %i %i %i %i ) \n", p->width, p->height, p->contents, p->flags, p->value );
4792 float ctrl[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT][5];
4795 for ( w = 0; w < p->width; w++ )
4797 for ( h = 0; h < p->height; h++ )
4799 ctrl[w][h][0] = p->ctrl[w][h].xyz[0];
4800 ctrl[w][h][1] = p->ctrl[w][h].xyz[1];
4801 ctrl[w][h][2] = p->ctrl[w][h].xyz[2];
4802 ctrl[w][h][3] = p->ctrl[w][h].st[0];
4803 ctrl[w][h][4] = p->ctrl[w][h].st[1];
4807 _Write3DMatrix( file, p->width, p->height, 5, reinterpret_cast<float*>( &ctrl ) );
4809 if ( g_qeglobals.m_bBrushPrimitMode ) {
4811 for ( epair_t *ep = p->epairs ; ep ; ep = ep->next )
4813 MemFile_fprintf( file, "\"%s\" \"%s\"\n", ep->key, ep->value );
4818 MemFile_fprintf( file, " }\n }\n" );
4821 void Patch_Write( patchMesh_t *p, FILE *file ){
4824 fprintf( file, " {\n patchDef2\n {\n" );
4826 CheckName( p, pname );
4827 fprintf( file, " %s\n", pname );
4828 fprintf( file, " ( %i %i %i %i %i ) \n", p->width, p->height, p->contents, p->flags, p->value );
4831 float ctrl[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT][5];
4834 for ( w = 0; w < p->width; w++ )
4836 for ( h = 0; h < p->height; h++ )
4838 ctrl[w][h][0] = p->ctrl[w][h].xyz[0];
4839 ctrl[w][h][1] = p->ctrl[w][h].xyz[1];
4840 ctrl[w][h][2] = p->ctrl[w][h].xyz[2];
4841 ctrl[w][h][3] = p->ctrl[w][h].st[0];
4842 ctrl[w][h][4] = p->ctrl[w][h].st[1];
4846 _Write3DMatrix( file, p->width, p->height, 5, reinterpret_cast<float*>( &ctrl ) );
4848 if ( g_qeglobals.m_bBrushPrimitMode ) {
4850 for ( epair_t *ep = p->epairs ; ep ; ep = ep->next )
4852 fprintf( file, "\"%s\" \"%s\"\n", ep->key, ep->value );
4857 fprintf( file, " }\n }\n" );
4866 void Patch_RotateTexture( patchMesh_t *p, float fAngle ){
4868 float c = cos( fAngle * Q_PI / 180 );
4869 float s = sin( fAngle * Q_PI / 180 );
4871 Patch_TransformLODTexture( p, c, s, ROTATE );
4873 for ( int w = 0; w < p->width; w++ )
4875 for ( int h = 0; h < p->height; h++ )
4877 //if (g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList(p->ctrl[w][h].xyz) == -1)
4880 float x = p->ctrl[w][h].st[0];
4881 float y = p->ctrl[w][h].st[1];
4882 p->ctrl[w][h].st[0] = x * c - y * s;
4883 p->ctrl[w][h].st[1] = y * c + x * s;
4894 void Patch_ScaleTexture( patchMesh_t *p, float fx, float fy, bool bFixup ){
4896 // this hack turns scales into 1.1 or 0.9
4898 fx = ( fx == 0 ) ? 1.0 : ( fx > 0 ) ? 0.9 : 1.10;
4899 fy = ( fy == 0 ) ? 1.0 : ( fy > 0 ) ? 0.9 : 1.10;
4911 for ( int w = 0; w < p->width; w++ )
4913 for ( int h = 0; h < p->height; h++ )
4915 if ( g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList( p->ctrl[w][h].xyz ) == -1 ) {
4919 p->ctrl[w][h].st[0] *= fx;
4920 p->ctrl[w][h].st[1] *= fy;
4923 if ( g_qeglobals.d_select_mode == sel_curvepoint ) {
4925 Patch_LODMatchAll();
4929 Patch_TransformLODTexture( p, fx, fy, SCALE );
4930 p->LODUpdated = true;
4938 shift a texture given a pixel count
4941 void Patch_ShiftTexture( patchMesh_t *p, float fx, float fy ){
4943 pTex = p->pShader->getTexture();
4944 fx = -1 * fx / pTex->width;
4945 fy = fy / pTex->height;
4946 Patch_ShiftTextureST( p, fx, fy );
4950 ====================
4951 Patch_ShiftTextureST
4952 shift a patch texture given an ST increment
4953 ====================
4955 void Patch_ShiftTextureST( patchMesh_t *p, float fx, float fy ){
4957 // NOTE: when called by Patch_ShiftTexture this warning may be bogus
4958 if ( ( ABS( fx ) >= 1 ) || ( ABS( fy ) >= 1 ) ) {
4959 Sys_Printf( "WARNING: increments exceed 1 in Patch_ShiftTextureST\n" );
4962 for ( int w = 0; w < p->width; w++ )
4964 for ( int h = 0; h < p->height; h++ )
4966 if ( g_qeglobals.d_select_mode == sel_curvepoint && PointInMoveList( p->ctrl[w][h].xyz ) == -1 ) {
4970 p->ctrl[w][h].st[0] += fx;
4971 p->ctrl[w][h].st[1] += fy;
4974 if ( g_qeglobals.d_select_mode == sel_curvepoint ) {
4976 Patch_LODMatchAll();
4980 Patch_TransformLODTexture( p, fx, fy, TRANSLATE );
4981 p->LODUpdated = true;
4987 Patch_ToggleInverted
4990 void Patch_ToggleInverted(){
4991 bool bUpdate = false;
4993 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
4995 if ( pb->patchBrush ) {
4997 patchInvert( pb->pPatch );
5002 Sys_UpdateWindows( W_ALL );
5004 UpdatePatchInspector();
5009 Patch_ToggleInverted
5012 void Patch_InvertTexture( bool bY ){
5013 bool bUpdate = false;
5016 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5018 if ( pb->patchBrush ) {
5020 patchMesh_t *p = pb->pPatch;
5023 for ( int i = 0 ; i < p->height ; i++ )
5025 for ( int j = 0; j < p->width / 2; j++ )
5027 memcpy( fTemp, &p->ctrl[p->width - 1 - j][i].st[0], sizeof( float[2] ) );
5028 memcpy( &p->ctrl[p->width - 1 - j][i].st[0], &p->ctrl[j][i].st[0], sizeof( float[2] ) );
5029 memcpy( &p->ctrl[j][i].st[0], fTemp, sizeof( float[2] ) );
5035 for ( int i = 0 ; i < p->width ; i++ )
5037 for ( int j = 0; j < p->height / 2; j++ )
5039 memcpy( fTemp, &p->ctrl[i][p->height - 1 - j].st[0], sizeof( float[2] ) );
5040 memcpy( &p->ctrl[i][p->height - 1 - j].st[0], &p->ctrl[i][j].st[0], sizeof( float[2] ) );
5041 memcpy( &p->ctrl[i][j].st[0], fTemp, sizeof( float[2] ) );
5049 Sys_UpdateWindows( W_ALL );
5051 UpdatePatchInspector();
5061 Saves patch ctrl info (originally to deal with a
5062 cancel in the surface dialog
5064 void Patch_Save( patchMesh_t *p ){
5065 patchSave.width = p->width;
5066 patchSave.height = p->height;
5067 memcpy( patchSave.ctrl, p->ctrl, sizeof( p->ctrl ) );
5076 void Patch_Restore( patchMesh_t *p ){
5077 p->width = patchSave.width;
5078 p->height = patchSave.height;
5079 memcpy( p->ctrl, patchSave.ctrl, sizeof( p->ctrl ) );
5082 void Patch_ResetTexturing( float fx, float fy ){
5083 for ( brush_t* pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5085 if ( pb->patchBrush ) {
5086 patchMesh_t *p = pb->pPatch;
5088 for ( int i = 0 ; i < p->width ; i++ )
5090 for ( int j = 0 ; j < p->height ; j++ )
5092 p->ctrl[i][j].st[0] = fx * (float)i / ( p->width - 1 );
5093 p->ctrl[i][j].st[1] = 1 - fy * (float)j / ( p->height - 1 );
5101 void Patch_FitTexturing(){
5102 Patch_ResetTexturing( 1.0f, 1.0f );
5105 void Patch_SetTextureInfo( texdef_t *pt ){
5106 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5108 if ( pb->patchBrush ) {
5110 Patch_RotateTexture( pb->pPatch, pt->rotate );
5113 if ( pt->shift[0] || pt->shift[1] ) {
5114 Patch_ShiftTexture( pb->pPatch, pt->shift[0], pt->shift[1] );
5117 if ( pt->scale[0] || pt->scale[1] ) {
5118 Patch_ScaleTexture( pb->pPatch, pt->scale[0], pt->scale[1], false );
5121 patchMesh_t *p = pb->pPatch;
5122 p->contents = pt->contents;
5123 p->flags = pt->flags;
5124 p->value = pt->value;
5129 bool OnlyPatchesSelected(){
5130 if ( g_ptrSelectedFaces.GetSize() > 0 || selected_brushes.next == &selected_brushes ) {
5133 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5135 if ( !pb->patchBrush ) {
5142 bool AnyPatchesSelected(){
5143 if ( g_ptrSelectedFaces.GetSize() > 0 || selected_brushes.next == &selected_brushes ) {
5146 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5148 if ( pb->patchBrush ) {
5155 patchMesh_t* SinglePatchSelected(){
5156 if ( selected_brushes.next->patchBrush ) {
5157 return selected_brushes.next->pPatch;
5162 void Patch_BendToggle(){
5163 if ( g_bPatchBendMode ) {
5164 g_bPatchBendMode = false;
5166 g_pParentWnd->UpdatePatchToolbarButtons() ;
5170 brush_t* b = selected_brushes.next;
5172 if ( !QE_SingleBrush( true ) || !b->patchBrush ) {
5173 Sys_Printf( "Patch_BendToggle: you must have a single patch selected\n" );
5177 Patch_Save( b->pPatch );
5178 g_bPatchBendMode = true;
5179 g_nPatchBendState = BEND_SELECT_ROTATION;
5180 g_bPatchAxisOnRow = true;
5181 g_nPatchAxisIndex = 1;
5182 ShowInfoDialog( g_pBendStateMsg[BEND_SELECT_ROTATION] );
5185 void Patch_BendHandleTAB(){
5186 if ( !g_bPatchBendMode ) {
5190 brush_t* b = selected_brushes.next;
5191 if ( !QE_SingleBrush() || !b->patchBrush ) {
5193 Sys_Printf( "No patch to bend!" );
5197 patchMesh_t *p = b->pPatch;
5199 bool bShift = Sys_ShiftDown();
5201 if ( g_nPatchBendState == BEND_SELECT_ROTATION ) {
5202 // only able to deal with odd numbered rows/cols
5203 g_nPatchAxisIndex += ( bShift ) ? -2 : 2;
5204 if ( g_bPatchAxisOnRow ) {
5205 if ( ( bShift ) ? g_nPatchAxisIndex <= 0 : g_nPatchAxisIndex >= p->height ) {
5206 g_bPatchAxisOnRow = false;
5207 g_nPatchAxisIndex = ( bShift ) ? p->width - 1 : 1;
5212 if ( ( bShift ) ? g_nPatchAxisIndex <= 0 : g_nPatchAxisIndex >= p->width ) {
5213 g_bPatchAxisOnRow = true;
5214 g_nPatchAxisIndex = ( bShift ) ? p->height - 1 : 1;
5219 if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
5220 g_nBendOriginIndex += ( bShift ) ? -1 : 1;
5221 if ( g_bPatchAxisOnRow ) {
5223 if ( g_nBendOriginIndex < 0 ) {
5224 g_nBendOriginIndex = p->width - 1;
5229 if ( g_nBendOriginIndex > p->width - 1 ) {
5230 g_nBendOriginIndex = 0;
5233 VectorCopy( p->ctrl[g_nBendOriginIndex][g_nPatchAxisIndex].xyz, g_vBendOrigin );
5238 if ( g_nBendOriginIndex < 0 ) {
5239 g_nBendOriginIndex = p->height - 1;
5244 if ( g_nBendOriginIndex > p->height - 1 ) {
5245 g_nBendOriginIndex = 0;
5248 VectorCopy( p->ctrl[g_nPatchAxisIndex][g_nBendOriginIndex].xyz, g_vBendOrigin );
5252 if ( g_nPatchBendState == BEND_SELECT_EDGE ) {
5253 g_bPatchLowerEdge ^= 1;
5255 Sys_UpdateWindows( W_ALL );
5258 void Patch_BendHandleENTER(){
5259 if ( !g_bPatchBendMode ) {
5263 if ( g_nPatchBendState < BEND_BENDIT ) {
5264 g_nPatchBendState++;
5265 ShowInfoDialog( g_pBendStateMsg[g_nPatchBendState] );
5266 if ( g_nPatchBendState == BEND_SELECT_ORIGIN ) {
5267 g_vBendOrigin[0] = g_vBendOrigin[1] = g_vBendOrigin[2] = 0;
5268 g_nBendOriginIndex = 0;
5269 Patch_BendHandleTAB();
5272 if ( g_nPatchBendState == BEND_SELECT_EDGE ) {
5273 g_bPatchLowerEdge = true;
5276 if ( g_nPatchBendState == BEND_BENDIT ) {
5277 // basically we go into rotation mode, set the axis to the center of the
5285 Sys_UpdateWindows( W_ALL );
5290 void Patch_BendHandleESC(){
5291 if ( !g_bPatchBendMode ) {
5295 brush_t* b = selected_brushes.next;
5296 if ( QE_SingleBrush() && b->patchBrush ) {
5297 Patch_Restore( b->pPatch );
5299 Sys_UpdateWindows( W_ALL );
5302 void Patch_SetBendRotateOrigin( patchMesh_t *p ){
5304 int nType = g_pParentWnd->ActiveXY()->GetViewType();
5305 int nDim3 = ( nType == XY ) ? 2 : ( nType == YZ ) ? 0 : 1;
5307 g_vBendOrigin[nDim3] = 0;
5308 VectorCopy( g_vBendOrigin, g_pParentWnd->ActiveXY()->RotateOrigin() );
5311 int nDim1 = ( g_pParentWnd->ActiveXY()->GetViewType() == YZ ) ? 1 : 0;
5312 int nDim2 = ( g_pParentWnd->ActiveXY()->GetViewType() == XY ) ? 1 : 2;
5314 float fxLo, fyLo, fxHi, fyHi;
5316 fxHi = fyHi = -9999;
5318 if ( g_bPatchAxisOnRow ) {
5319 for ( int i = 0; i < p->width; i++ )
5321 if ( p->ctrl[i][g_nPatchAxisIndex].xyz[nDim1] < fxLo ) {
5322 fxLo = p->ctrl[i][g_nPatchAxisIndex].xyz[nDim1];
5325 if ( p->ctrl[i][g_nPatchAxisIndex].xyz[nDim1] > fxHi ) {
5326 fxHi = p->ctrl[i][g_nPatchAxisIndex].xyz[nDim1];
5329 if ( p->ctrl[i][g_nPatchAxisIndex].xyz[nDim2] < fyLo ) {
5330 fyLo = p->ctrl[i][g_nPatchAxisIndex].xyz[nDim2];
5333 if ( p->ctrl[i][g_nPatchAxisIndex].xyz[nDim2] > fyHi ) {
5334 fyHi = p->ctrl[i][g_nPatchAxisIndex].xyz[nDim2];
5340 for ( int i = 0; i < p->height; i++ )
5342 if ( p->ctrl[g_nPatchAxisIndex][i].xyz[nDim1] < fxLo ) {
5343 fxLo = p->ctrl[g_nPatchAxisIndex][i].xyz[nDim1];
5346 if ( p->ctrl[g_nPatchAxisIndex][i].xyz[nDim1] > fxHi ) {
5347 fxHi = p->ctrl[g_nPatchAxisIndex][i].xyz[nDim1];
5350 if ( p->ctrl[g_nPatchAxisIndex][i].xyz[nDim2] < fyLo ) {
5351 fyLo = p->ctrl[g_nPatchAxisIndex][i].xyz[nDim2];
5354 if ( p->ctrl[g_nPatchAxisIndex][i].xyz[nDim2] > fyHi ) {
5355 fyHi = p->ctrl[g_nPatchAxisIndex][i].xyz[nDim2];
5360 g_pParentWnd->ActiveXY()->RotateOrigin()[0] = g_pParentWnd->ActiveXY()->RotateOrigin()[1] = g_pParentWnd->ActiveXY()->RotateOrigin()[2] = 0.0;
5361 g_pParentWnd->ActiveXY()->RotateOrigin()[nDim1] = ( fxLo + fxHi ) * 0.5;
5362 g_pParentWnd->ActiveXY()->RotateOrigin()[nDim2] = ( fyLo + fyHi ) * 0.5;
5366 // also sets the rotational origin
5367 void Patch_SelectBendAxis(){
5368 brush_t* b = selected_brushes.next;
5369 if ( !QE_SingleBrush() || !b->patchBrush ) {
5370 // should not ever happen
5375 patchMesh_t *p = b->pPatch;
5376 if ( g_bPatchAxisOnRow ) {
5377 SelectRow( p, g_nPatchAxisIndex, false );
5381 SelectColumn( p, g_nPatchAxisIndex, false );
5384 //FIXME: this only needs to be set once...
5385 Patch_SetBendRotateOrigin( p );
5389 void Patch_SelectBendNormal(){
5390 brush_t* b = selected_brushes.next;
5391 if ( !QE_SingleBrush() || !b->patchBrush ) {
5392 // should not ever happen
5397 patchMesh_t *p = b->pPatch;
5399 g_qeglobals.d_num_move_points = 0;
5400 if ( g_bPatchAxisOnRow ) {
5401 if ( g_bPatchLowerEdge ) {
5402 for ( int j = 0; j < g_nPatchAxisIndex; j++ )
5403 SelectRow( p, j, true );
5407 for ( int j = p->height - 1; j > g_nPatchAxisIndex; j-- )
5408 SelectRow( p, j, true );
5413 if ( g_bPatchLowerEdge ) {
5414 for ( int j = 0; j < g_nPatchAxisIndex; j++ )
5415 SelectColumn( p, j, true );
5419 for ( int j = p->width - 1; j > g_nPatchAxisIndex; j-- )
5420 SelectColumn( p, j, true );
5423 Patch_SetBendRotateOrigin( p );
5429 void Patch_InsDelToggle()
5431 if (g_bPatchInsertMode)
5433 g_bPatchInsertMode = false;
5435 g_pParentWnd->UpdatePatchToolbarButtons() ;
5439 brush_t* b = selected_brushes.next;
5441 if (!QE_SingleBrush(true) || !b->patchBrush)
5443 Sys_Printf("Patch_InsDelToggle: you must have a single patch selected\n");
5447 Patch_Save(b->pPatch);
5448 g_bPatchInsertMode = true;
5449 g_nPatchInsertState = INSERT_SELECT_EDGE;
5450 g_bPatchAxisOnRow = true;
5451 g_nPatchAxisIndex = 0;
5452 ShowInfoDialog(g_pInsertStateMsg[INSERT_SELECT_EDGE]);
5456 void Patch_InsDelESC()
5458 if (!g_bPatchInsertMode)
5462 Patch_InsDelToggle();
5463 Sys_UpdateWindows(W_ALL);
5467 void Patch_InsDelHandleENTER()
5471 void Patch_InsDelHandleTAB()
5473 if (!g_bPatchInsertMode)
5475 Patch_InsDelToggle();
5479 brush_t* b = selected_brushes.next;
5480 if (!QE_SingleBrush() || !b->patchBrush)
5483 Sys_Printf("No patch to bend!");
5487 patchMesh_t *p = b->pPatch;
5489 // only able to deal with odd numbered rows/cols
5490 g_nPatchAxisIndex += 2;
5491 if (g_bPatchAxisOnRow)
5493 if (g_nPatchAxisIndex >= p->height-1)
5495 g_bPatchAxisOnRow = false;
5496 g_nPatchAxisIndex = 0;
5501 if (g_nPatchAxisIndex >= p->width-1)
5503 g_bPatchAxisOnRow = true;
5504 g_nPatchAxisIndex = 0;
5507 Sys_UpdateWindows(W_ALL);
5512 void _Write1DMatrix( FILE *f, int x, float *m ) {
5516 for ( i = 0 ; i < x ; i++ ) {
5517 if ( m[i] == (int)m[i] ) {
5518 fprintf( f, "%i ", (int)m[i] );
5521 fprintf( f, "%f ", m[i] );
5527 void _Write2DMatrix( FILE *f, int y, int x, float *m ) {
5531 for ( i = 0 ; i < y ; i++ ) {
5532 _Write1DMatrix( f, x, m + i * x );
5535 fprintf( f, ")\n" );
5539 void _Write3DMatrix( FILE *f, int z, int y, int x, float *m ) {
5542 fprintf( f, "(\n" );
5543 for ( i = 0 ; i < z ; i++ ) {
5544 _Write2DMatrix( f, y, x, m + i * ( x * MAX_PATCH_HEIGHT ) );
5546 fprintf( f, ")\n" );
5549 void _Write1DMatrix( MemStream *f, int x, float *m ) {
5552 MemFile_fprintf( f, "( " );
5553 for ( i = 0 ; i < x ; i++ ) {
5554 if ( m[i] == (int)m[i] ) {
5555 MemFile_fprintf( f, "%i ", (int)m[i] );
5558 MemFile_fprintf( f, "%f ", m[i] );
5561 MemFile_fprintf( f, ")" );
5564 void _Write2DMatrix( MemStream *f, int y, int x, float *m ) {
5567 MemFile_fprintf( f, "( " );
5568 for ( i = 0 ; i < y ; i++ ) {
5569 _Write1DMatrix( f, x, m + i * x );
5570 MemFile_fprintf( f, " " );
5572 MemFile_fprintf( f, ")\n" );
5576 void _Write3DMatrix( MemStream *f, int z, int y, int x, float *m ) {
5579 MemFile_fprintf( f, "(\n" );
5580 for ( i = 0 ; i < z ; i++ ) {
5581 _Write2DMatrix( f, y, x, m + i * ( x * MAX_PATCH_HEIGHT ) );
5583 MemFile_fprintf( f, ")\n" );
5586 // NOTE: why the hell is this called Naturalize?
5587 // we dispatch either to Patch+Naturalize or Patch_CapTexture..
5588 void Patch_NaturalizeSelected( bool bCap ){
5589 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5591 if ( pb->patchBrush ) {
5593 Patch_CapTexture( pb->pPatch ); //, bCycleCap);
5596 Patch_Naturalize( pb->pPatch );
5602 // go through the selected patches and call Patch_CapTexture
5603 // deal with cycling
5604 void Patch_CycleCapSelected(){
5605 // compute the g_vCycleCapNormal according to g_nCycleCapIndex
5606 VectorClear( g_vCycleCapNormal );
5607 g_vCycleCapNormal[g_nCycleCapIndex] = 1.0f; // cf VIEWTYPE defintion: enum VIEWTYPE {YZ, XZ, XY};
5608 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5610 if ( pb->patchBrush ) {
5611 Patch_CapTexture( pb->pPatch, true );
5614 switch ( g_nCycleCapIndex )
5617 g_nCycleCapIndex = XZ;
5620 g_nCycleCapIndex = XY;
5623 g_nCycleCapIndex = YZ;
5628 bool within( vec3_t vTest, vec3_t vTL, vec3_t vBR ){
5629 int nDim1 = ( g_pParentWnd->ActiveXY()->GetViewType() == YZ ) ? 1 : 0;
5630 int nDim2 = ( g_pParentWnd->ActiveXY()->GetViewType() == XY ) ? 1 : 2;
5631 if ( ( vTest[nDim1] > vTL[nDim1] && vTest[nDim1] < vBR[nDim1] ) ||
5632 ( vTest[nDim1] < vTL[nDim1] && vTest[nDim1] > vBR[nDim1] ) ) {
5633 if ( ( vTest[nDim2] > vTL[nDim2] && vTest[nDim2] < vBR[nDim2] ) ||
5634 ( vTest[nDim2] < vTL[nDim2] && vTest[nDim2] > vBR[nDim2] ) ) {
5642 void Patch_SelectAreaPoints( bool bMulti ){
5644 g_qeglobals.d_num_move_points = 0;
5647 if ( g_nPatchClickedView == W_CAMERA ) {
5648 // Clip against a pyramid
5649 // Create our 5 normals (that are pointing to the inside)
5650 camera_t *m_pCamera = g_pParentWnd->GetCamWnd()->Camera();
5652 float r[2], u[2], hh, hw;
5654 vec_t corners[2][2];
5658 VectorCopy( m_pCamera->vpn, norm[0] ); // only points in front of the camera
5660 // get our rectangle
5661 corners[0][0] = MIN( g_qeglobals.d_vAreaTL[0], g_qeglobals.d_vAreaBR[0] );
5662 corners[0][1] = MAX( g_qeglobals.d_vAreaTL[1], g_qeglobals.d_vAreaBR[1] );
5663 corners[1][0] = MAX( g_qeglobals.d_vAreaTL[0], g_qeglobals.d_vAreaBR[0] );
5664 corners[1][1] = MIN( g_qeglobals.d_vAreaTL[1], g_qeglobals.d_vAreaBR[1] );
5666 // calculate our four ray vectors
5667 hh = m_pCamera->height / 2;
5668 hw = m_pCamera->width / 2;
5669 u[0] = (float)( corners[0][1] - hh ) / ( hw );
5670 r[0] = (float)( corners[0][0] - hw ) / ( hw );
5671 u[1] = (float)( corners[1][1] - hh ) / ( hw );
5672 r[1] = (float)( corners[1][0] - hw ) / ( hw );
5674 for ( idx = 0 ; idx < 3; idx++ )
5675 ray[0][idx] = m_pCamera->vpn[idx] + m_pCamera->vright[idx] * r[0] + m_pCamera->vup[idx] * u[0];
5676 for ( idx = 0 ; idx < 3; idx++ )
5677 ray[1][idx] = m_pCamera->vpn[idx] + m_pCamera->vright[idx] * r[1] + m_pCamera->vup[idx] * u[0];
5678 for ( idx = 0 ; idx < 3; idx++ )
5679 ray[2][idx] = m_pCamera->vpn[idx] + m_pCamera->vright[idx] * r[1] + m_pCamera->vup[idx] * u[1];
5680 for ( idx = 0 ; idx < 3; idx++ )
5681 ray[3][idx] = m_pCamera->vpn[idx] + m_pCamera->vright[idx] * r[0] + m_pCamera->vup[idx] * u[1];
5683 // Create our four other directions from these
5684 CrossProduct( ray[0], ray[1], norm[1] ); VectorNormalize( norm[1], norm[1] );
5685 CrossProduct( ray[1], ray[2], norm[2] ); VectorNormalize( norm[2], norm[2] );
5686 CrossProduct( ray[2], ray[3], norm[3] ); VectorNormalize( norm[3], norm[3] );
5687 CrossProduct( ray[3], ray[0], norm[4] ); VectorNormalize( norm[4], norm[4] );
5690 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5692 if ( pb->patchBrush ) {
5693 patchMesh_t *p = pb->pPatch;
5694 for ( int i = 0; i < p->width; i++ )
5696 for ( int j = 0; j < p->height; j++ )
5698 VectorSubtract( m_pCamera->origin, p->ctrl[i][j].xyz, check );
5699 VectorNormalize( check, check );
5700 for ( idx = 0 ; idx < 5; idx++ )
5702 if ( DotProduct( check, norm[idx] ) >= 0 ) {
5706 if ( idx == 5 ) { // all test were good
5707 if ( bMulti && PointInMoveList( p->ctrl[i][j].xyz ) != -1 ) {
5708 RemovePointFromMoveList( p->ctrl[i][j].xyz );
5711 g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = p->ctrl[i][j].xyz;
5721 // Simple 2D clipping
5722 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5724 if ( pb->patchBrush ) {
5725 patchMesh_t *p = pb->pPatch;
5726 for ( int i = 0; i < p->width; i++ )
5728 for ( int j = 0; j < p->height; j++ )
5730 if ( within( p->ctrl[i][j].xyz, g_qeglobals.d_vAreaTL, g_qeglobals.d_vAreaBR ) ) {
5731 if ( bMulti && PointInMoveList( p->ctrl[i][j].xyz ) != -1 ) {
5732 RemovePointFromMoveList( p->ctrl[i][j].xyz );
5735 g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = p->ctrl[i][j].xyz;
5744 g_nPatchClickedView = -1;
5747 // TTimo: return the shader name for a patch
5748 const char* Patch_GetTextureName(){
5749 brush_t* b = selected_brushes.next;
5750 if ( b->patchBrush ) {
5751 patchMesh_t *p = b->pPatch;
5752 return p->pShader->getName();
5757 patchMesh_t* Patch_Duplicate( patchMesh_t *pFrom ){
5758 patchMesh_t* p = MakeNewPatch();
5759 memcpy( p, pFrom, sizeof( patchMesh_t ) );
5761 // spog - initialise patch LOD pointers (again)
5762 Patch_InitialiseLODPointers( p );
5763 p->drawLists = NULL;
5765 p->bSelected = false;
5767 p->bOverlay = false;
5769 AddBrushForPatch( p );
5775 void Patch_Thicken( int nAmount, bool bSeam, qboolean bGroupResult ){
5785 if ( !QE_SingleBrush() ) {
5786 Sys_Printf( "Cannot thicken multiple patches. Please select a single patch.\n" );
5790 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5792 if ( pb->patchBrush ) {
5793 patchMesh_t *p = pb->pPatch;
5794 Patch_MeshNormals( p );
5795 patchMesh_t *pNew = Patch_Duplicate( p );
5796 for ( i = 0; i < p->width; i++ )
5798 for ( j = 0; j < p->height; j++ )
5800 VectorMA( p->ctrl[i][j].xyz, nAmount, p->ctrl[i][j].normal, pNew->ctrl[i][j].xyz );
5804 Patch_Rebuild( pNew );
5805 pNew->type |= PATCH_THICK;
5806 brushes.Add( pNew->pSymbiot );
5810 // FIXME: this should detect if any edges of the patch are closed and act appropriately
5812 if ( !( p->type & PATCH_CYLINDER ) ) {
5813 b = Patch_GenericMesh( 3, p->height, 2, false, true );
5815 pSeam->type |= PATCH_SEAM;
5816 for ( i = 0; i < p->height; i++ )
5818 VectorCopy( p->ctrl[0][i].xyz, pSeam->ctrl[0][i].xyz );
5819 VectorCopy( pNew->ctrl[0][i].xyz, pSeam->ctrl[2][i].xyz );
5820 VectorAdd( pSeam->ctrl[0][i].xyz, pSeam->ctrl[2][i].xyz, pSeam->ctrl[1][i].xyz );
5821 VectorScale( pSeam->ctrl[1][i].xyz, 0.5, pSeam->ctrl[1][i].xyz );
5825 Patch_CalcBounds( pSeam, vMin, vMax );
5826 Brush_RebuildBrush( pSeam->pSymbiot, vMin, vMax );
5827 //--Patch_CapTexture(pSeam);
5828 Patch_Naturalize( pSeam );
5829 patchInvert( pSeam );
5833 b = Patch_GenericMesh( 3, p->height, 2, false, true );
5835 pSeam->type |= PATCH_SEAM;
5836 for ( i = 0; i < p->height; i++ )
5838 VectorCopy( p->ctrl[w][i].xyz, pSeam->ctrl[0][i].xyz );
5839 VectorCopy( pNew->ctrl[w][i].xyz, pSeam->ctrl[2][i].xyz );
5840 VectorAdd( pSeam->ctrl[0][i].xyz, pSeam->ctrl[2][i].xyz, pSeam->ctrl[1][i].xyz );
5841 VectorScale( pSeam->ctrl[1][i].xyz, 0.5, pSeam->ctrl[1][i].xyz );
5843 Patch_CalcBounds( pSeam, vMin, vMax );
5844 Brush_RebuildBrush( pSeam->pSymbiot, vMin, vMax );
5845 //--Patch_CapTexture(pSeam);
5846 Patch_Naturalize( pSeam );
5851 // otherwise we will add one per end
5852 b = Patch_GenericMesh( p->width, 3, 2, false, true );
5854 pSeam->type |= PATCH_SEAM;
5855 for ( i = 0; i < p->width; i++ )
5857 VectorCopy( p->ctrl[i][0].xyz, pSeam->ctrl[i][0].xyz );
5858 VectorCopy( pNew->ctrl[i][0].xyz, pSeam->ctrl[i][2].xyz );
5859 VectorAdd( pSeam->ctrl[i][0].xyz, pSeam->ctrl[i][2].xyz, pSeam->ctrl[i][1].xyz );
5860 VectorScale( pSeam->ctrl[i][1].xyz, 0.5, pSeam->ctrl[i][1].xyz );
5864 Patch_CalcBounds( pSeam, vMin, vMax );
5865 Brush_RebuildBrush( pSeam->pSymbiot, vMin, vMax );
5866 //--Patch_CapTexture(pSeam);
5867 Patch_Naturalize( pSeam );
5868 patchInvert( pSeam );
5872 b = Patch_GenericMesh( p->width, 3, 2, false, true );
5874 pSeam->type |= PATCH_SEAM;
5875 for ( i = 0; i < p->width; i++ )
5877 VectorCopy( p->ctrl[i][h].xyz, pSeam->ctrl[i][0].xyz );
5878 VectorCopy( pNew->ctrl[i][h].xyz, pSeam->ctrl[i][2].xyz );
5879 VectorAdd( pSeam->ctrl[i][0].xyz, pSeam->ctrl[i][2].xyz, pSeam->ctrl[i][1].xyz );
5880 VectorScale( pSeam->ctrl[i][1].xyz, 0.5, pSeam->ctrl[i][1].xyz );
5882 Patch_CalcBounds( pSeam, vMin, vMax );
5883 Brush_RebuildBrush( pSeam->pSymbiot, vMin, vMax );
5884 //--Patch_CapTexture(pSeam);
5885 Patch_Naturalize( pSeam );
5889 patchInvert( pNew );
5893 for ( i = 0; i < brushes.GetSize(); i++ )
5895 Select_Brush( reinterpret_cast<brush_t*>( brushes.GetAt( i ) ) );
5898 if ( bGroupResult ) {
5899 entity_t *e = Entity_Alloc();
5900 SetKeyValue( e, "classname", "func_group" );
5901 SetKeyValue( e, "type", "patchThick" );
5902 Select_GroupEntity( e );
5903 Entity_AddToList( e, &entities );
5906 UpdatePatchInspector();
5911 lets get another list together as far as necessities..
5913 *snapping stuff to the grid (i will only snap movements by the mouse to the grid.. snapping the rotational bend stuff will fubar everything)
5915 capping bevels/endcaps
5919 texture fix for caps
5929 void Patch_SetOverlays(){
5930 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5932 if ( pb->patchBrush ) {
5933 pb->pPatch->bOverlay = true;
5940 void Patch_ClearOverlays(){
5942 for ( pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5944 if ( pb->patchBrush ) {
5945 pb->pPatch->bOverlay = false;
5949 for ( pb = active_brushes.next ; pb != &active_brushes ; pb = pb->next )
5951 if ( pb->patchBrush ) {
5952 pb->pPatch->bOverlay = false;
5958 // FIXME: spog - er, someone forgot to finish their patch point freezing feature?
5959 // freezes selected vertices
5960 void Patch_Freeze(){
5962 for ( pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5964 if ( pb->patchBrush ) {
5965 pb->pPatch->bOverlay = false;
5969 for ( pb = active_brushes.next ; pb != &active_brushes ; pb = pb->next )
5971 if ( pb->patchBrush ) {
5972 pb->pPatch->bOverlay = false;
5978 void Patch_UnFreeze( bool bAll ){
5981 void Patch_Transpose(){
5984 for ( brush_t *pb = selected_brushes.next ; pb != &selected_brushes ; pb = pb->next )
5986 if ( pb->patchBrush ) {
5987 patchMesh_t *p = pb->pPatch;
5989 if ( p->width > p->height ) {
5990 for ( i = 0 ; i < p->height ; i++ )
5992 for ( j = i + 1 ; j < p->width ; j++ )
5994 if ( j < p->height ) {
5996 memcpy( &dv,&p->ctrl[j][i],sizeof( drawVert_t ) );
5997 memcpy( &p->ctrl[j][i],&p->ctrl[i][j], sizeof( drawVert_t ) );
5998 memcpy( &p->ctrl[i][j],&dv, sizeof( drawVert_t ) );
6003 memcpy( &p->ctrl[i][j],&p->ctrl[j][i], sizeof( drawVert_t ) );
6010 for ( i = 0 ; i < p->width ; i++ )
6012 for ( j = i + 1 ; j < p->height ; j++ )
6014 if ( j < p->width ) {
6016 memcpy( &dv,&p->ctrl[i][j], sizeof( drawVert_t ) );
6017 memcpy( &p->ctrl[i][j],&p->ctrl[j][i], sizeof( drawVert_t ) );
6018 memcpy( &p->ctrl[j][i],&dv, sizeof( drawVert_t ) );
6023 memcpy( &p->ctrl[j][i],&p->ctrl[i][j], sizeof( drawVert_t ) );
6030 p->width = p->height;
6040 void Patch_SnapToGrid( patchMesh_t *p ){
6043 // if patch points selected, snap only selected points
6044 if ( g_qeglobals.d_select_mode == sel_curvepoint && g_qeglobals.d_num_move_points != 0 ) {
6045 for ( i = 0; i < g_qeglobals.d_num_move_points; i++ )
6046 for ( j = 0; j < 3; j++ )
6047 g_qeglobals.d_move_points[i][j] = floor( g_qeglobals.d_move_points[i][j] / g_qeglobals.d_gridsize + 0.5 ) * g_qeglobals.d_gridsize;
6050 // else snap all patch points
6052 for ( i = 0; i < p->width; i++ )
6053 for ( j = 0; j < p->height; j++ )
6054 for ( k = 0; k < 3; k++ )
6055 p->ctrl[i][j].xyz[k] = floor( p->ctrl[i][j].xyz[k] / g_qeglobals.d_gridsize + 0.5 ) * g_qeglobals.d_gridsize;
6059 Patch_CalcBounds( p, vMin, vMax );
6060 Brush_RebuildBrush( p->pSymbiot, vMin, vMax );
6064 void Patch_FindReplaceTexture( brush_t *pb, const char *pFind, const char *pReplace, bool bForce ){
6065 if ( pb->patchBrush ) {
6066 patchMesh_t *p = pb->pPatch;
6067 if ( bForce || strcmpi( p->pShader->getName(), pFind ) == 0 ) {
6068 p->pShader->DecRef();
6069 p->pShader = QERApp_Shader_ForName( pReplace );
6070 p->d_texture = p->pShader->getTexture();
6075 /* uncomment if necessary, currently not used
6076 void Patch_FromTriangle(vec5_t vx, vec5_t vy, vec5_t vz)
6078 patchMesh_t* p = MakeNewPatch();
6079 p->pShader = g_qeglobals.d_texturewin.pShader;
6080 p->d_texture = g_qeglobals.d_texturewin.pShader->getTexture();
6083 p->type = PATCH_TRIANGLE;
6089 // 1 0 goes to mid of x and z
6090 // 1 1 goes to mid of x y and z
6091 // 1 2 goes to mid of x and y
6094 // 2 1 goes to mid of y and z
6102 for (j = 0; j < 3; j++)
6104 _Vector5Add(vx, vz, vMidXZ);
6105 _Vector5Scale(vMidXZ, 0.5, vMidXZ);
6106 //vMidXZ[j] = vx[j] + abs((vx[j] - vz[j]) * 0.5);
6109 for (j = 0; j < 3; j++)
6111 _Vector5Add(vx, vy, vMidXY);
6112 _Vector5Scale(vMidXY, 0.5, vMidXY);
6113 //vMidXY[j] = vx[j] + abs((vx[j] - vy[j]) * 0.5);
6116 for (j = 0; j < 3; j++)
6118 _Vector5Add(vy, vz, vMidYZ);
6119 _Vector5Scale(vMidYZ, 0.5, vMidYZ);
6120 //vMidYZ[j] = vy[j] + abs((vy[j] - vz[j]) * 0.5);
6123 _Vector53Copy(vx, p->ctrl[0][0].xyz);
6124 _Vector53Copy(vx, p->ctrl[0][1].xyz);
6125 _Vector53Copy(vx, p->ctrl[0][2].xyz);
6126 p->ctrl[0][0].st[0] = vx[3];
6127 p->ctrl[0][0].st[1] = vx[4];
6128 p->ctrl[0][1].st[0] = vx[3];
6129 p->ctrl[0][1].st[1] = vx[4];
6130 p->ctrl[0][2].st[0] = vx[3];
6131 p->ctrl[0][2].st[1] = vx[4];
6133 _Vector53Copy(vMidXY, p->ctrl[1][0].xyz);
6134 _Vector53Copy(vx, p->ctrl[1][1].xyz);
6135 _Vector53Copy(vMidXZ, p->ctrl[1][2].xyz);
6136 p->ctrl[1][0].st[0] = vMidXY[3];
6137 p->ctrl[1][0].st[1] = vMidXY[4];
6138 p->ctrl[1][1].st[0] = vx[3];
6139 p->ctrl[1][1].st[1] = vx[4];
6140 p->ctrl[1][2].st[0] = vMidXZ[3];
6141 p->ctrl[1][2].st[1] = vMidXZ[4];
6143 _Vector53Copy(vy, p->ctrl[2][0].xyz);
6144 _Vector53Copy(vMidYZ, p->ctrl[2][1].xyz);
6145 _Vector53Copy(vz, p->ctrl[2][2].xyz);
6146 p->ctrl[2][0].st[0] = vy[3];
6147 p->ctrl[2][0].st[1] = vy[4];
6148 p->ctrl[2][1].st[0] = vMidYZ[3];
6149 p->ctrl[2][1].st[1] = vMidYZ[4];
6150 p->ctrl[2][2].st[0] = vz[3];
6151 p->ctrl[2][2].st[1] = vz[4];
6154 //Patch_Naturalize(p);
6157 AddBrushForPatch(p);
6162 #ifdef ENABLE_GROUPS
6166 sets an epair for the given patch
6169 void Patch_SetEpair( patchMesh_t *p, const char *pKey, const char *pValue ){
6170 if ( g_qeglobals.m_bBrushPrimitMode ) {
6171 SetKeyValue( p->epairs, pKey, pValue );
6180 const char* Patch_GetKeyValue( patchMesh_t *p, const char *pKey ){
6181 if ( g_qeglobals.m_bBrushPrimitMode ) {
6182 return ValueForKey( p->epairs, pKey );
6189 //Real nitpicky, but could you make CTRL-S save the current map with the current name? (ie: File/Save)
6192 When reading in textures, please check for the presence of a file called "textures.link" or something, which contains one line such as;
6194 g:\quake3\baseq3\textures\common
6196 So that, when I'm reading in, lets say, my \eerie directory, it goes through and adds my textures to the palette, along with everything in common.
6198 Don't forget to add "Finer texture alignment" to the list. I'd like to be able to move in 0.1 increments using the Shift-Arrow Keys.
6200 No. Sometimes textures are drawn the wrong way on patches. We'd like the ability to flip a texture. Like the way X/Y scale -1 used to worked.
6202 1) Easier way of deleting rows, columns
6203 2) Fine tuning of textures on patches (X/Y shifts other than with the surface dialog)
6204 2) Patch matrix transposition
6206 1) Actually, bump texture flipping on patches to the top of the list of things to do.
6207 2) When you select a patch, and hit S, it should read in the selected patch texture. Should not work if you multiselect patches and hit S
6208 3) Brandon has a wierd anomoly. He fine-tunes a patch with caps. It looks fine when the patch is selected, but as soon as he escapes out, it reverts to it's pre-tuned state. When he selects the patch again, it looks tuned
6211 *1) Flipping textures on patches
6212 *2) When you select a patch, and hit S, it should read in the selected patch texture. Should not work if you multiselect patches and hit S
6213 3) Easier way of deleting rows columns
6215 5) Patch matrix transposition
6216 6) Inverted cylinder capping
6220 Have a new feature request. "Compute Bounding Box" for mapobjects (md3 files). This would be used for misc_mapobject (essentially, drop in 3DS Max models into our maps)
6222 Ok, Feature Request. Load and draw MD3's in the Camera view with proper bounding boxes. This should be off misc_model
6224 Feature Addition: View/Hide Hint Brushes -- This should be a specific case.