/* ================ CL_DrawLine ================ */ static void CL_DrawLine( const vec3_t start, const vec3_t end, int pcolor, float life, float gap ) { particle_t *p; float len, curdist; vec3_t diff; int i; // Determine distance; VectorSubtract( end, start, diff ); len = VectorNormalizeLength( diff ); curdist = 0; while( curdist <= len ) { p = CL_AllocParticle( NULL ); if( !p ) return; for( i = 0; i < 3; i++ ) p->org[i] = start[i] + curdist * diff[i]; p->color = pcolor; p->type = pt_static; p->die += life; curdist += gap; } }
/* =============== CL_TracerEffect =============== */ void CL_TracerEffect( const vec3_t start, const vec3_t end ) { particle_t *p; byte *color; vec3_t dir; float life, dist; p = CL_AllocParticle( CL_BulletTracerDraw ); if( !p ) return; // get out shot direction and length VectorSubtract( end, start, dir ); VectorCopy( dir, p->vel ); dist = VectorNormalizeLength( dir ); // don't make small tracers if( dist <= traceroffset->value ) return; p->ramp = Com_RandomFloat( 200.0f, 256.0f ) * tracerlength->value; color = gTracerColors[4]; life = ( dist + p->ramp ) / ( max( 1.0f, tracerspeed->value )); p->color = CL_LookupColor( color[0], color[1], color[2] ); VectorCopy( start, p->org ); p->type = pt_tracer; p->die += life; }
/* ===================== PlaneFromPoints Returns false if the triangle is degenrate. The normal will point out of the clock for clockwise ordered points ===================== */ bool PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { vec3_t d1, d2; VectorSubtract( b, a, d1 ); VectorSubtract( c, a, d2 ); CrossProduct( d2, d1, plane ); if( VectorNormalizeLength( plane ) == 0.0f ) return false; plane[3] = DotProduct( a, plane ); return true; }
/* =============== CL_RocketTrail =============== */ void CL_RocketTrail( vec3_t start, vec3_t end, int type ) { vec3_t vec; float len; particle_t *p; int j, dec; static int tracercount; VectorSubtract( end, start, vec ); len = VectorNormalizeLength( vec ); if( type < 128 ) { dec = 3; } else { dec = 1; type -= 128; } while( len > 0 ) { len -= dec; p = CL_AllocParticle( NULL ); if( !p ) return; p->die += 2.0f; switch( type ) { case 0: // rocket trail p->ramp = Com_RandomLong( 0, 4 ); p->color = ramp3[(int)p->ramp]; p->type = pt_fire; for( j = 0; j < 3; j++ ) p->org[j] = start[j] + ((rand() % 6 ) - 3 ); break; case 1: // smoke smoke p->ramp = Com_RandomLong( 2, 6 ); p->color = ramp3[(int)p->ramp]; p->type = pt_fire; for( j = 0; j < 3; j++ ) p->org[j] = start[j] + ((rand() % 6 ) - 3 ); break; case 2: // blood p->type = pt_grav; p->color = Com_RandomLong( 67, 71 ); for( j = 0; j < 3; j++ ) p->org[j] = start[j] + ((rand() % 6 ) - 3 ); break; case 3: case 5: // tracer p->die += 0.5f; p->type = pt_static; if( type == 3 ) p->color = 52 + (( tracercount & 4 )<<1 ); else p->color = 230 + (( tracercount & 4 )<<1 ); tracercount++; VectorCopy( start, p->org ); if( tracercount & 1 ) { p->vel[0] = 30 * vec[1]; p->vel[1] = 30 * -vec[0]; } else { p->vel[0] = 30 * -vec[1]; p->vel[1] = 30 * vec[0]; } break; case 4: // slight blood p->type = pt_grav; p->color = Com_RandomLong( 67, 71 ); for( j = 0; j < 3; j++ ) p->org[j] = start[j] + Com_RandomFloat( -3.0f, 3.0f ); len -= 3; break; case 6: // voor trail p->color = Com_RandomLong( 152, 156 ); p->type = pt_static; p->die += 0.3f; for( j = 0; j < 3; j++ ) p->org[j] = start[j] + Com_RandomFloat( -16.0f, 16.0f ); break; } VectorAdd( start, vec, start ); } }
static void CL_BulletTracerDraw( particle_t *p, float frametime ) { vec3_t lineDir, viewDir, cross; vec3_t vecEnd, vecStart, vecDir; float sDistance, eDistance, totalDist; float dDistance, dTotal, fOffset; int alpha = (int)(traceralpha->value * 255); float width = 3.0f, life, frac, length; vec3_t tmp; // calculate distance VectorCopy( p->vel, vecDir ); totalDist = VectorNormalizeLength( vecDir ); length = p->ramp; // ramp used as length // calculate fraction life = ( totalDist + length ) / ( max( 1.0f, tracerspeed->value )); frac = life - ( p->die - cl.time ) + frametime; // calculate our distance along our path sDistance = tracerspeed->value * frac; eDistance = sDistance - length; // clip to start sDistance = max( 0.0f, sDistance ); eDistance = max( 0.0f, eDistance ); if(( sDistance == 0.0f ) && ( eDistance == 0.0f )) return; // clip it if( totalDist != 0.0f ) { sDistance = min( sDistance, totalDist ); eDistance = min( eDistance, totalDist ); } // get our delta to calculate the tc offset dDistance = fabs( sDistance - eDistance ); dTotal = ( length != 0.0f ) ? length : 0.01f; fOffset = ( dDistance / dTotal ); // find our points along our path VectorMA( p->org, sDistance, vecDir, vecEnd ); VectorMA( p->org, eDistance, vecDir, vecStart ); // setup our info for drawing the line VectorSubtract( vecEnd, vecStart, lineDir ); VectorSubtract( vecEnd, RI.vieworg, viewDir ); CrossProduct( lineDir, viewDir, cross ); VectorNormalize( cross ); GL_SetRenderMode( kRenderTransTexture ); GL_Bind( GL_TEXTURE0, cls.particleImage ); pglBegin( GL_QUADS ); pglColor4ub( clgame.palette[p->color][0], clgame.palette[p->color][1], clgame.palette[p->color][2], alpha ); VectorMA( vecStart, -width, cross, tmp ); pglTexCoord2f( 1.0f, 0.0f ); pglVertex3fv( tmp ); VectorMA( vecStart, width, cross, tmp ); pglTexCoord2f( 0.0f, 0.0f ); pglVertex3fv( tmp ); VectorMA( vecEnd, width, cross, tmp ); pglTexCoord2f( 0.0f, fOffset ); pglVertex3fv( tmp ); VectorMA( vecEnd, -width, cross, tmp ); pglTexCoord2f( 1.0f, fOffset ); pglVertex3fv( tmp ); pglEnd(); }
float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent) #endif { float maxfrac, maxrealfrac; int n; entity_render_t *ent; float tracemins[3], tracemaxs[3]; trace_t trace; float tempnormal[3], starttransformed[3], endtransformed[3]; #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND vec3_t end; vec_t len = 0; if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) { // TRICK: make the trace 1 qu longer! VectorSubtract(pEnd, start, end); len = VectorNormalizeLength(end); VectorMA(pEnd, collision_endposnudge.value, end, end); } else VectorCopy(pEnd, end); #endif memset (&trace, 0 , sizeof(trace_t)); trace.fraction = 1; trace.realfraction = 1; VectorCopy (end, trace.endpos); if (hitent) *hitent = 0; if (cl.worldmodel && cl.worldmodel->TraceLine) cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, start, end, SUPERCONTENTS_SOLID); if (normal) VectorCopy(trace.plane.normal, normal); maxfrac = trace.fraction; maxrealfrac = trace.realfraction; tracemins[0] = min(start[0], end[0]); tracemaxs[0] = max(start[0], end[0]); tracemins[1] = min(start[1], end[1]); tracemaxs[1] = max(start[1], end[1]); tracemins[2] = min(start[2], end[2]); tracemaxs[2] = max(start[2], end[2]); // look for embedded bmodels for (n = 0;n < cl.num_entities;n++) { if (!cl.entities_active[n]) continue; ent = &cl.entities[n].render; if (!BoxesOverlap(ent->mins, ent->maxs, tracemins, tracemaxs)) continue; if (!ent->model || !ent->model->TraceLine) continue; if ((ent->flags & RENDER_EXTERIORMODEL) && !chase_active.integer) continue; // if transparent and not selectable, skip entity if (!(cl.entities[n].state_current.effects & EF_SELECTABLE) && (ent->alpha < 1 || (ent->effects & (EF_ADDITIVE | EF_NODEPTHTEST)))) continue; if (ent == ignoreent) continue; Matrix4x4_Transform(&ent->inversematrix, start, starttransformed); Matrix4x4_Transform(&ent->inversematrix, end, endtransformed); Collision_ClipTrace_Box(&trace, ent->model->normalmins, ent->model->normalmaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID, SUPERCONTENTS_SOLID, 0, NULL); #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) Collision_ShortenTrace(&trace, len / (len + collision_endposnudge.value), pEnd); #endif if (maxrealfrac < trace.realfraction) continue; ent->model->TraceLine(ent->model, ent->frameblend, ent->skeleton, &trace, starttransformed, endtransformed, SUPERCONTENTS_SOLID); if (maxrealfrac > trace.realfraction) { if (hitent) *hitent = n; maxfrac = trace.fraction; maxrealfrac = trace.realfraction; if (normal) { VectorCopy(trace.plane.normal, tempnormal); Matrix4x4_Transform3x3(&ent->matrix, tempnormal, normal); } } } maxfrac = bound(0, maxfrac, 1); maxrealfrac = bound(0, maxrealfrac, 1); //if (maxfrac < 0 || maxfrac > 1) Con_Printf("fraction out of bounds %f %s:%d\n", maxfrac, __FILE__, __LINE__); if (impact) VectorLerp(start, maxfrac, end, impact); return maxfrac; }
trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities) #endif { vec3_t hullmins, hullmaxs; int i, bodysupercontents; int passedictprog; qboolean pointtrace; prvm_edict_t *traceowner, *touch; trace_t trace; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // size of the moving object vec3_t clipmins, clipmaxs; // size when clipping against monsters vec3_t clipmins2, clipmaxs2; // start and end origin of move vec3_t clipstart, clipend; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND vec3_t end; vec_t len = 0; if (VectorCompare(mins, maxs)) { vec3_t shiftstart, shiftend; VectorAdd(start, mins, shiftstart); VectorAdd(pEnd, mins, shiftend); if (VectorCompare(start, pEnd)) trace = CL_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); else trace = CL_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities, false); VectorSubtract(trace.endpos, mins, trace.endpos); return trace; } if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) { // TRICK: make the trace 1 qu longer! VectorSubtract(pEnd, start, end); len = VectorNormalizeLength(end); VectorMA(pEnd, collision_endposnudge.value, end, end); } else VectorCopy(pEnd, end); #else if (VectorCompare(mins, maxs)) { vec3_t shiftstart, shiftend; VectorAdd(start, mins, shiftstart); VectorAdd(end, mins, shiftend); if (VectorCompare(start, end)) trace = CL_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); else trace = CL_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); VectorSubtract(trace.endpos, mins, trace.endpos); return trace; } #endif if (hitnetworkentity) *hitnetworkentity = 0; VectorCopy(start, clipstart); VectorCopy(end, clipend); VectorCopy(mins, clipmins); VectorCopy(maxs, clipmaxs); VectorCopy(mins, clipmins2); VectorCopy(maxs, clipmaxs2); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); #endif // clip to world Collision_ClipToWorld(&cliptrace, cl.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask); cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog ? prog->edicts : NULL; if (type == MOVE_WORLDONLY) goto finished; if (type == MOVE_MISSILE) { // LordHavoc: modified this, was = -15, now -= 15 for (i = 0;i < 3;i++) { clipmins2[i] -= 15; clipmaxs2[i] += 15; } } // get adjusted box for bmodel collisions if the world is q1bsp or hlbsp if (cl.worldmodel && cl.worldmodel->brush.RoundUpToHullSize) cl.worldmodel->brush.RoundUpToHullSize(cl.worldmodel, clipmins, clipmaxs, hullmins, hullmaxs); else { VectorCopy(clipmins, hullmins); VectorCopy(clipmaxs, hullmaxs); } // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1; clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1; } // debug override to test against everything if (sv_debugmove.integer) { clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; } // if the passedict is world, make it NULL (to avoid two checks each time) // this checks prog because this function is often called without a CSQC // VM context if (prog == NULL || passedict == prog->edicts) passedict = NULL; // precalculate prog value for passedict for comparisons passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0; // figure out whether this is a point trace for comparisons pointtrace = VectorCompare(clipmins, clipmaxs); // precalculate passedict's owner edict pointer for comparisons traceowner = passedict ? PRVM_PROG_TO_EDICT(passedict->fields.client->owner) : NULL; // collide against network entities if (hitnetworkbrushmodels) { for (i = 0;i < cl.num_brushmodel_entities;i++) { entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) continue; Collision_ClipToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, mins, maxs, end, hitsupercontentsmask); if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) *hitnetworkentity = cl.brushmodel_entities[i]; Collision_CombineTraces(&cliptrace, &trace, NULL, true); } } // collide against player entities if (hitnetworkplayers) { vec3_t origin, entmins, entmaxs; matrix4x4_t entmatrix, entinversematrix; if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) { // don't hit network players, if we are a nonsolid player if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616) goto skipnetworkplayers; } for (i = 1;i <= cl.maxclients;i++) { entity_render_t *ent = &cl.entities[i].render; // don't hit ourselves if (i == cl.playerentity) continue; // don't hit players that don't exist if (!cl.scores[i-1].name[0]) continue; if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC) { // don't hit spectators or nonsolid players if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616) continue; } Matrix4x4_OriginFromMatrix(&ent->matrix, origin); VectorAdd(origin, cl.playerstandmins, entmins); VectorAdd(origin, cl.playerstandmaxs, entmaxs); if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs)) continue; Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]); Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]); Collision_ClipToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, mins, maxs, end, hitsupercontentsmask); if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) *hitnetworkentity = i; Collision_CombineTraces(&cliptrace, &trace, NULL, false); } skipnetworkplayers: ; } // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below // note: if prog is NULL then there won't be any linked entities numtouchedicts = 0; if (hitcsqcentities && prog != NULL) { numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (touch->fields.client->solid < SOLID_BBOX) continue; if (type == MOVE_NOMONSTERS && touch->fields.client->solid != SOLID_BSP) continue; if (passedict) { // don't clip against self if (passedict == touch) continue; // don't clip owned entities against owner if (traceowner == touch) continue; // don't clip owner against owned entities if (passedictprog == touch->fields.client->owner) continue; // don't clip points against points (they can't collide) if (pointtrace && VectorCompare(touch->fields.client->mins, touch->fields.client->maxs) && (type != MOVE_MISSILE || !((int)touch->fields.client->flags & FL_MONSTER))) continue; } bodysupercontents = touch->fields.client->solid == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; // might interact, so do an exact clip model = NULL; if ((int) touch->fields.client->solid == SOLID_BSP || type == MOVE_HITMODEL) model = CL_GetModelFromEdict(touch); if (model) Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.client->origin[0], touch->fields.client->origin[1], touch->fields.client->origin[2], touch->fields.client->angles[0], touch->fields.client->angles[1], touch->fields.client->angles[2], 1); else Matrix4x4_CreateTranslate(&matrix, touch->fields.client->origin[0], touch->fields.client->origin[1], touch->fields.client->origin[2]); Matrix4x4_Invert_Simple(&imatrix, &matrix); if ((int)touch->fields.client->flags & FL_MONSTER) Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touch->fields.client->mins, touch->fields.client->maxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask); else Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touch->fields.client->mins, touch->fields.client->maxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask); if (cliptrace.realfraction > trace.realfraction && hitnetworkentity) *hitnetworkentity = 0; Collision_CombineTraces(&cliptrace, &trace, (void *)touch, touch->fields.client->solid == SOLID_BSP); } finished: #ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0) Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd); #endif return cliptrace; }
/* ================= AddBrushBevels adds any additional planes necessary to allow the brush being built to be expanded against axial bounding boxes 2003-01-20: added mr.Elusive fixes ================= */ void AddBrushBevels( void ) { int axis, dir; int i, j, k, l, order = 0; side_t sidetemp; side_t *s, *s2; winding_t *w, *w2; vec3_t normal; float dist; vec3_t vec, vec2; float d, minBack; // add the axial planes for( axis = 0; axis < 3; axis++ ) { for( dir = -1; dir <= 1; dir += 2, order++ ) { // see if the plane is allready present for( i = 0, s = buildBrush->sides; i < buildBrush->numsides; i++, s++ ) { if( mapplanes[s->planenum].normal[axis] == dir ) break; } if( i == buildBrush->numsides ) { // add a new side if( buildBrush->numsides == MAX_BUILD_SIDES ) Sys_Break( "Entity %i, Brush %i MAX_BUILD_SIDES\n", buildBrush->entityNum, buildBrush->brushNum ); Mem_Set( s, 0, sizeof( *s )); buildBrush->numsides++; VectorClear (normal); normal[axis] = dir; if( dir == 1 ) { // adding bevel plane snapping for fewer bsp planes if( bevelSnap > 0 ) dist = floor( buildBrush->maxs[axis] / bevelSnap ) * bevelSnap; else dist = buildBrush->maxs[axis]; } else { // adding bevel plane snapping for fewer bsp planes if( bevelSnap > 0 ) dist = -ceil( buildBrush->mins[axis] / bevelSnap ) * bevelSnap; else dist = -buildBrush->mins[axis]; } s->planenum = FindFloatPlane( normal, dist, 0, NULL ); s->contentFlags = buildBrush->sides[0].contentFlags; s->bevel = true; c_boxbevels++; } // if the plane is not in it canonical order, swap it if( i != order ) { sidetemp = buildBrush->sides[order]; buildBrush->sides[order] = buildBrush->sides[i]; buildBrush->sides[i] = sidetemp; } } } // add the edge bevels if( buildBrush->numsides == 6 ) return; // pure axial // test the non-axial plane edges for( i = 6; i < buildBrush->numsides; i++ ) { s = buildBrush->sides + i; w = s->winding; if( !w ) continue; for( j = 0; j < w->numpoints; j++ ) { k = (j+1)%w->numpoints; VectorSubtract( w->p[j], w->p[k], vec ); if( VectorNormalizeLength( vec ) < 0.5f ) continue; SnapNormal( vec ); for( k = 0; k < 3; k++ ) { if( vec[k] == -1.0f || vec[k] == 1.0f || (vec[k] == 0.0f && vec[(k+1)%3] == 0.0f)) break; // axial } if( k != 3 ) continue; // only test non-axial edges // try the six possible slanted axials from this edge for( axis = 0; axis < 3; axis++ ) { for( dir = -1; dir <= 1; dir += 2 ) { // construct a plane VectorClear( vec2 ); vec2[axis] = dir; CrossProduct( vec, vec2, normal ); if( VectorNormalizeLength( normal ) < 0.5f ) continue; dist = DotProduct( w->p[j], normal ); // if all the points on all the sides are // behind this plane, it is a proper edge bevel for( k = 0; k < buildBrush->numsides; k++ ) { // if this plane has allready been used, skip it if( PlaneEqual( &mapplanes[buildBrush->sides[k].planenum], normal, dist )) break; w2 = buildBrush->sides[k].winding; if( !w2 ) continue; minBack = 0.0f; for( l = 0; l < w2->numpoints; l++ ) { d = DotProduct( w2->p[l], normal ) - dist; if( d > 0.1f ) break; // point in front if( d < minBack ) minBack = d; } // if some point was at the front if( l != w2->numpoints ) break; // if no points at the back then the winding is on the bevel plane if( minBack > -0.1f ) break; } if( k != buildBrush->numsides ) continue; // wasn't part of the outer hull // add this plane if( buildBrush->numsides == MAX_BUILD_SIDES ) Sys_Break( "Entity %i, Brush %i MAX_BUILD_SIDES\n", buildBrush->entityNum, buildBrush->brushNum ); s2 = &buildBrush->sides[buildBrush->numsides]; buildBrush->numsides++; Mem_Set( s2, 0, sizeof( *s2 ) ); s2->planenum = FindFloatPlane( normal, dist, 1, &w->p[j] ); s2->contentFlags = buildBrush->sides[0].contentFlags; s2->bevel = true; c_edgebevels++; } } } } }
/* ================== CM_AddFacetBevels ================== */ static void CM_AddFacetBevels( cfacet_t *facet ) { int i, j, k, l; int axis, dir, order, flipped; float plane[4], d, newplane[4]; vec3_t mins, maxs, vec, vec2; cwinding_t *w, *w2; Vector4Copy( planes[facet->surfacePlane].plane, plane ); w = CM_BaseWindingForPlane( plane, plane[3] ); for( j = 0; j < facet->numBorders && w; j++ ) { if( facet->borderPlanes[j] == facet->surfacePlane ) continue; Vector4Copy( planes[facet->borderPlanes[j]].plane, plane ); if( !facet->borderInward[j] ) { VectorNegate( plane, plane ); plane[3] = -plane[3]; } CM_ChopWindingInPlace( &w, plane, plane[3], ON_EPSILON ); } if( !w ) return; CM_WindingBounds( w, mins, maxs ); // // add the axial planes // order = 0; for( axis = 0; axis < 3; axis++ ) { for( dir = -1; dir <= 1; dir += 2, order++ ) { VectorClear( plane ); plane[axis] = dir; if( dir == 1 ) plane[3] = maxs[axis]; else plane[3] = -mins[axis]; // if it's the surface plane if( CM_PlaneEqual( &planes[facet->surfacePlane], plane, &flipped )) continue; // see if the plane is allready present for( i = 0; i < facet->numBorders; i++ ) { if( CM_PlaneEqual( &planes[facet->borderPlanes[i]], plane, &flipped )) break; } if( i == facet->numBorders ) { if( facet->numBorders > MAX_FACET_BEVELS ) MsgDev( D_ERROR, "CM_AddFacetBevels: too many bevels\n" ); facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); facet->borderNoAdjust[facet->numBorders] = 0; facet->borderInward[facet->numBorders] = flipped; facet->numBorders++; } } } // // add the edge bevels // // test the non-axial plane edges for( j = 0; j < w->numpoints; j++ ) { k = (j + 1) % w->numpoints; VectorSubtract( w->p[j], w->p[k], vec ); // if it's a degenerate edge if( VectorNormalizeLength( vec ) < 0.5f ) continue; CM_SnapVector( vec ); for( k = 0; k < 3; k++ ) { if( vec[k] == -1 || vec[k] == 1 ) break; // axial } if( k < 3 ) continue; // only test non-axial edges // try the six possible slanted axials from this edge for( axis = 0; axis < 3; axis++ ) { for( dir = -1; dir <= 1; dir += 2 ) { // construct a plane VectorClear( vec2 ); vec2[axis] = dir; CrossProduct( vec, vec2, plane ); if( VectorNormalizeLength( plane ) < 0.5f ) continue; plane[3] = DotProduct( w->p[j], plane ); // if all the points of the facet winding are // behind this plane, it is a proper edge bevel for( l = 0; l < w->numpoints; l++ ) { d = DotProduct( w->p[l], plane ) - plane[3]; if( d > ON_EPSILON ) break; // point in front } if( l < w->numpoints ) continue; // if it's the surface plane if( CM_PlaneEqual( &planes[facet->surfacePlane], plane, &flipped )) continue; // see if the plane is allready present for( i = 0; i < facet->numBorders; i++ ) { if( CM_PlaneEqual( &planes[facet->borderPlanes[i]], plane, &flipped )) break; } if( i == facet->numBorders ) { if( facet->numBorders > MAX_FACET_BEVELS ) MsgDev( D_ERROR, "CM_AddFacetBevels: too many bevels\n" ); facet->borderPlanes[facet->numBorders] = CM_FindPlane2( plane, &flipped ); for( k = 0; k < facet->numBorders; k++ ) { if( facet->borderPlanes[facet->numBorders] == facet->borderPlanes[k] ) MsgDev( D_WARN, "CM_AddFacetBevels: bevel plane already used\n" ); } facet->borderNoAdjust[facet->numBorders] = 0; facet->borderInward[facet->numBorders] = flipped; w2 = CM_CopyWinding( w ); Vector4Copy( planes[facet->borderPlanes[facet->numBorders]].plane, newplane ); if( !facet->borderInward[facet->numBorders] ) { VectorNegate( newplane, newplane ); newplane[3] = -newplane[3]; } CM_ChopWindingInPlace( &w2, newplane, newplane[3], ON_EPSILON ); if( !w2 ) { cm.numInvalidBevels++; continue; } else CM_FreeWinding( w2 ); facet->numBorders++; // already got a bevel } } } } CM_FreeWinding( w ); // add opposite plane facet->borderPlanes[facet->numBorders] = facet->surfacePlane; facet->borderNoAdjust[facet->numBorders] = 0; facet->borderInward[facet->numBorders] = true; facet->numBorders++; }