1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
8
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW MP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 /*
30 * name: g_utils.c
31 *
32 * desc: misc utility functions for game module
33 *
34 */
35
36 #include "g_local.h"
37
38 typedef struct {
39 char oldShader[MAX_QPATH];
40 char newShader[MAX_QPATH];
41 float timeOffset;
42 } shaderRemap_t;
43
44 #define MAX_SHADER_REMAPS 128
45
46 int remapCount = 0;
47 shaderRemap_t remappedShaders[MAX_SHADER_REMAPS];
48
AddRemap(const char * oldShader,const char * newShader,float timeOffset)49 void AddRemap( const char *oldShader, const char *newShader, float timeOffset ) {
50 int i;
51
52 for ( i = 0; i < remapCount; i++ ) {
53 if ( Q_stricmp( oldShader, remappedShaders[i].oldShader ) == 0 ) {
54 // found it, just update this one
55 strcpy( remappedShaders[i].newShader,newShader );
56 remappedShaders[i].timeOffset = timeOffset;
57 return;
58 }
59 }
60 if ( remapCount < MAX_SHADER_REMAPS ) {
61 strcpy( remappedShaders[remapCount].newShader,newShader );
62 strcpy( remappedShaders[remapCount].oldShader,oldShader );
63 remappedShaders[remapCount].timeOffset = timeOffset;
64 remapCount++;
65 }
66 }
67
BuildShaderStateConfig(void)68 const char *BuildShaderStateConfig(void) {
69 static char buff[MAX_STRING_CHARS * 4];
70 char out[( MAX_QPATH * 2 ) + 5];
71 int i;
72
73 memset( buff, 0, MAX_STRING_CHARS );
74 for ( i = 0; i < remapCount; i++ ) {
75 Com_sprintf( out, ( MAX_QPATH * 2 ) + 5, "%s=%s:%5.2f@", remappedShaders[i].oldShader, remappedShaders[i].newShader, remappedShaders[i].timeOffset );
76 Q_strcat( buff, sizeof( buff ), out );
77 }
78 return buff;
79 }
80
81 /*
82 =========================================================================
83
84 model / sound configstring indexes
85
86 =========================================================================
87 */
88
89 /*
90 ================
91 G_FindConfigstringIndex
92
93 ================
94 */
G_FindConfigstringIndex(const char * name,int start,int max,qboolean create)95 int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) {
96 int i;
97 char s[MAX_STRING_CHARS];
98
99 if ( !name || !name[0] ) {
100 return 0;
101 }
102
103 for ( i = 1 ; i < max ; i++ ) {
104 trap_GetConfigstring( start + i, s, sizeof( s ) );
105 if ( !s[0] ) {
106 break;
107 }
108 if ( !strcmp( s, name ) ) {
109 return i;
110 }
111 }
112
113 if ( !create ) {
114 return 0;
115 }
116
117 if ( i == max ) {
118 G_Error( "G_FindConfigstringIndex: overflow" );
119 }
120
121 trap_SetConfigstring( start + i, name );
122
123 return i;
124 }
125
126
G_ModelIndex(char * name)127 int G_ModelIndex( char *name ) {
128 return G_FindConfigstringIndex( name, CS_MODELS, MAX_MODELS, qtrue );
129 }
130
G_SoundIndex(const char * name)131 int G_SoundIndex( const char *name ) {
132 return G_FindConfigstringIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue );
133 }
134
135 //=====================================================================
136
137
138 /*
139 ================
140 G_TeamCommand
141
142 Broadcasts a command to only a specific team
143 ================
144 */
G_TeamCommand(team_t team,char * cmd)145 void G_TeamCommand( team_t team, char *cmd ) {
146 int i;
147
148 for ( i = 0 ; i < level.maxclients ; i++ ) {
149 if ( level.clients[i].pers.connected == CON_CONNECTED ) {
150 if ( level.clients[i].sess.sessionTeam == team ) {
151 trap_SendServerCommand( i, va( "%s", cmd ) );
152 }
153 }
154 }
155 }
156
157
158 /*
159 =============
160 G_Find
161
162 Searches all active entities for the next one that holds
163 the matching string at fieldofs (use the FOFS() macro) in the structure.
164
165 Searches beginning at the entity after from, or the beginning if NULL
166 NULL will be returned if the end of the list is reached.
167
168 =============
169 */
G_Find(gentity_t * from,int fieldofs,const char * match)170 gentity_t *G_Find( gentity_t *from, int fieldofs, const char *match ) {
171 char *s;
172
173 if ( !from ) {
174 from = g_entities;
175 } else {
176 from++;
177 }
178
179 for ( ; from < &g_entities[level.num_entities] ; from++ )
180 {
181 if ( !from->inuse ) {
182 continue;
183 }
184 s = *( char ** )( (byte *)from + fieldofs );
185 if ( !s ) {
186 continue;
187 }
188 if ( !Q_stricmp( s, match ) ) {
189 return from;
190 }
191 }
192
193 return NULL;
194 }
195
196
197 /*
198 =============
199 G_PickTarget
200
201 Selects a random entity from among the targets
202 =============
203 */
204 #define MAXCHOICES 32
205
G_PickTarget(char * targetname)206 gentity_t *G_PickTarget( char *targetname ) {
207 gentity_t *ent = NULL;
208 int num_choices = 0;
209 gentity_t *choice[MAXCHOICES];
210
211 if ( !targetname ) {
212 //G_Printf("G_PickTarget called with NULL targetname\n");
213 return NULL;
214 }
215
216 while ( 1 )
217 {
218 ent = G_Find( ent, FOFS( targetname ), targetname );
219 if ( !ent ) {
220 break;
221 }
222 choice[num_choices++] = ent;
223 if ( num_choices == MAXCHOICES ) {
224 break;
225 }
226 }
227
228 if ( !num_choices ) {
229 G_Printf( "G_PickTarget: target %s not found\n", targetname );
230 return NULL;
231 }
232
233 return choice[rand() % num_choices];
234 }
235
236
237 /*
238 ==============================
239 G_UseTargets
240
241 "activator" should be set to the entity that initiated the firing.
242
243 Search for (string)targetname in all entities that
244 match (string)self.target and call their .use function
245
246 ==============================
247 */
G_UseTargets(gentity_t * ent,gentity_t * activator)248 void G_UseTargets( gentity_t *ent, gentity_t *activator ) {
249 gentity_t *t;
250
251 if ( !ent ) {
252 return;
253 }
254
255 if ( ent->targetShaderName && ent->targetShaderNewName ) {
256 float f = level.time * 0.001;
257 AddRemap( ent->targetShaderName, ent->targetShaderNewName, f );
258 trap_SetConfigstring( CS_SHADERSTATE, BuildShaderStateConfig() );
259 }
260
261 if ( !ent->target ) {
262 return;
263 }
264
265 t = NULL;
266 while ( ( t = G_Find( t, FOFS( targetname ), ent->target ) ) != NULL ) {
267 if ( t == ent ) {
268 G_Printf( "WARNING: Entity used itself.\n" );
269 } else {
270 if ( t->use ) {
271 //G_Printf ("ent->classname %s ent->targetname %s t->targetname %s t->s.number %d\n", ent->classname, ent->targetname, t->targetname, t->s.number);
272
273 t->flags |= ( ent->flags & FL_KICKACTIVATE ); // (SA) If 'ent' was kicked to activate, pass this along to it's targets.
274 // It may become handy to put a "KICKABLE" flag in ents so that it knows whether to pass this along or not
275 // Right now, the only situation where it would be weird would be an invisible_user that is a 'button' near
276 // a rotating door that it triggers. Kick the switch and the door next to it flies open.
277
278 t->flags |= ( ent->flags & FL_SOFTACTIVATE ); // (SA) likewise for soft activation
279
280 if ( activator &&
281 ( ( Q_stricmp( t->classname, "func_door" ) == 0 ) ||
282 ( Q_stricmp( t->classname, "func_door_rotating" ) == 0 )
283 )
284 ) {
285 // check door usage rules before allowing any entity to trigger a door open
286 G_TryDoor( t, ent, activator ); // (door,other,activator)
287 } else {
288 t->use( t, ent, activator );
289 }
290 }
291 }
292 if ( !ent->inuse ) {
293 G_Printf( "entity was removed while using targets\n" );
294 return;
295 }
296 }
297 }
298
299
300 /*
301 =============
302 TempVector
303
304 This is just a convenience function
305 for making temporary vectors for function calls
306 =============
307 */
308 /*
309 float *tv( float x, float y, float z ) {
310 static int index;
311 static vec3_t vecs[8];
312 float *v;
313
314 // use an array so that multiple tempvectors won't collide
315 // for a while
316 v = vecs[index];
317 index = (index + 1)&7;
318
319 v[0] = x;
320 v[1] = y;
321 v[2] = z;
322
323 return v;
324 }
325 */
326
327 /*
328 =============
329 VectorToString
330
331 This is just a convenience function
332 for printing vectors
333 =============
334 */
vtos(const vec3_t v)335 char *vtos( const vec3_t v ) {
336 static int index;
337 static char str[8][32];
338 char *s;
339
340 // use an array so that multiple vtos won't collide
341 s = str[index];
342 index = ( index + 1 ) & 7;
343
344 Com_sprintf( s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2] );
345
346 return s;
347 }
vtosf(const vec3_t v)348 char *vtosf( const vec3_t v ) {
349 static int index;
350 static char str[8][64];
351 char *s;
352
353 // use an array so that multiple vtos won't collide
354 s = str[index];
355 index = ( index + 1 ) & 7;
356
357 Com_sprintf( s, 64, "(%f %f %f)", v[0], v[1], v[2] );
358
359 return s;
360 }
361
362
363 /*
364 ===============
365 G_SetMovedir
366
367 The editor only specifies a single value for angles (yaw),
368 but we have special constants to generate an up or down direction.
369 Angles will be cleared, because it is being used to represent a direction
370 instead of an orientation.
371 ===============
372 */
G_SetMovedir(vec3_t angles,vec3_t movedir)373 void G_SetMovedir( vec3_t angles, vec3_t movedir ) {
374 static vec3_t VEC_UP = {0, -1, 0};
375 static vec3_t MOVEDIR_UP = {0, 0, 1};
376 static vec3_t VEC_DOWN = {0, -2, 0};
377 static vec3_t MOVEDIR_DOWN = {0, 0, -1};
378
379 if ( VectorCompare( angles, VEC_UP ) ) {
380 VectorCopy( MOVEDIR_UP, movedir );
381 } else if ( VectorCompare( angles, VEC_DOWN ) ) {
382 VectorCopy( MOVEDIR_DOWN, movedir );
383 } else {
384 AngleVectors( angles, movedir, NULL, NULL );
385 }
386 VectorClear( angles );
387 }
388
389
390
G_InitGentity(gentity_t * e)391 void G_InitGentity( gentity_t *e ) {
392 e->inuse = qtrue;
393 e->classname = "noclass";
394 e->s.number = e - g_entities;
395 e->r.ownerNum = ENTITYNUM_NONE;
396 e->headshotDamageScale = 1.0; // RF, default value
397
398 // RF, init scripting
399 e->scriptStatus.scriptEventIndex = -1;
400 }
401
402 /*
403 =================
404 G_Spawn
405
406 Either finds a free entity, or allocates a new one.
407
408 The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will
409 never be used by anything else.
410
411 Try to avoid reusing an entity that was recently freed, because it
412 can cause the client to think the entity morphed into something else
413 instead of being removed and recreated, which can cause interpolated
414 angles and bad trails.
415 =================
416 */
G_Spawn(void)417 gentity_t *G_Spawn( void ) {
418 int i, force;
419 gentity_t *e;
420
421 e = NULL; // shut up warning
422 for ( force = 0 ; force < 2 ; force++ ) {
423 // if we go through all entities and can't find one to free,
424 // override the normal minimum times before use
425 e = &g_entities[MAX_CLIENTS];
426 for ( i = MAX_CLIENTS ; i < level.num_entities ; i++, e++ ) {
427 if ( e->inuse ) {
428 continue;
429 }
430
431 // the first couple seconds of server time can involve a lot of
432 // freeing and allocating, so relax the replacement policy
433 if ( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 ) {
434 continue;
435 }
436
437 // reuse this slot
438 G_InitGentity( e );
439 return e;
440 }
441 if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) {
442 break;
443 }
444 }
445 if ( level.num_entities == ENTITYNUM_MAX_NORMAL ) {
446 for ( i = 0; i < MAX_GENTITIES; i++ ) {
447 G_Printf( "%4i: %s\n", i, g_entities[i].classname );
448 }
449 G_Error( "G_Spawn: no free entities" );
450 }
451
452 // open up a new slot
453 level.num_entities++;
454
455 // let the server system know that there are more entities
456 trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),
457 &level.clients[0].ps, sizeof( level.clients[0] ) );
458
459 G_InitGentity( e );
460 return e;
461 }
462
463 /*
464 =================
465 G_EntitiesFree
466 =================
467 */
G_EntitiesFree(void)468 qboolean G_EntitiesFree( void ) {
469 int i;
470 gentity_t *e;
471
472 if ( level.num_entities < ENTITYNUM_MAX_NORMAL ) {
473 // can open a new slot if needed
474 return qtrue;
475 }
476
477 e = &g_entities[MAX_CLIENTS];
478 for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++ ) {
479 if ( e->inuse ) {
480 continue;
481 }
482 // slot available
483 return qtrue;
484 }
485 return qfalse;
486 }
487
488
489 /*
490 =================
491 G_FreeEntity
492
493 Marks the entity as free
494 =================
495 */
G_FreeEntity(gentity_t * ed)496 void G_FreeEntity( gentity_t *ed ) {
497 trap_UnlinkEntity( ed ); // unlink from world
498
499 if ( ed->neverFree ) {
500 return;
501 }
502
503 memset( ed, 0, sizeof( *ed ) );
504 ed->classname = "freed";
505 ed->freetime = level.time;
506 ed->inuse = qfalse;
507 }
508
509 /*
510 =================
511 G_TempEntity
512
513 Spawns an event entity that will be auto-removed
514 The origin will be snapped to save net bandwidth, so care
515 must be taken if the origin is right on a surface (snap towards start vector first)
516 =================
517 */
G_TempEntity(vec3_t origin,int event)518 gentity_t *G_TempEntity( vec3_t origin, int event ) {
519 gentity_t *e;
520 vec3_t snapped;
521
522 e = G_Spawn();
523 e->s.eType = ET_EVENTS + event;
524
525 e->classname = "tempEntity";
526 e->eventTime = level.time;
527 e->r.eventTime = level.time;
528 e->freeAfterEvent = qtrue;
529
530 VectorCopy( origin, snapped );
531 SnapVector( snapped ); // save network bandwidth
532 G_SetOrigin( e, snapped );
533
534 // find cluster for PVS
535 trap_LinkEntity( e );
536
537 return e;
538 }
539
540
541
542 /*
543 ==============================================================================
544
545 Kill box
546
547 ==============================================================================
548 */
549
550 /*
551 =================
552 G_KillBox
553
554 Kills all entities that would touch the proposed new positioning
555 of ent. Ent should be unlinked before calling this!
556 =================
557 */
G_KillBox(gentity_t * ent)558 void G_KillBox( gentity_t *ent ) {
559 int i, num;
560 int touch[MAX_GENTITIES];
561 gentity_t *hit;
562 vec3_t mins, maxs;
563
564 VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
565 VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
566 num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
567
568 for ( i = 0 ; i < num ; i++ ) {
569 hit = &g_entities[touch[i]];
570 if ( !hit->client ) {
571 continue;
572 }
573 if ( !hit->r.linked ) { // RF, inactive AI shouldn't be gibbed
574 continue;
575 }
576
577 // nail it
578 G_Damage( hit, ent, ent, NULL, NULL,
579 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
580 }
581
582 }
583
584 //==============================================================================
585
586 /*
587 ===============
588 G_AddPredictableEvent
589
590 Use for non-pmove events that would also be predicted on the
591 client side: jumppads and item pickups
592 Adds an event+parm and twiddles the event counter
593 ===============
594 */
G_AddPredictableEvent(gentity_t * ent,int event,int eventParm)595 void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) {
596 if ( !ent->client ) {
597 return;
598 }
599 BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps );
600 }
601
602
603 /*
604 ===============
605 G_AddEvent
606
607 Adds an event+parm and twiddles the event counter
608 ===============
609 */
G_AddEvent(gentity_t * ent,int event,int eventParm)610 void G_AddEvent( gentity_t *ent, int event, int eventParm ) {
611 // int bits;
612
613 if ( !event ) {
614 G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number );
615 return;
616 }
617
618 // Ridah, use the sequential event list
619 if ( ent->client ) {
620 // NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now
621 ent->client->ps.events[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = event;
622 ent->client->ps.eventParms[ent->client->ps.eventSequence & ( MAX_EVENTS - 1 )] = eventParm;
623 ent->client->ps.eventSequence++;
624 // -NERVE - SMF
625
626 // NERVE - SMF - commented out
627 // bits = ent->client->ps.externalEvent & EV_EVENT_BITS;
628 // bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
629 // ent->client->ps.externalEvent = event | bits;
630 // ent->client->ps.externalEventParm = eventParm;
631 // ent->client->ps.externalEventTime = level.time;
632 // -NERVE - SMF
633 } else {
634 // NERVE - SMF - commented in - externalEvents not being handled properly in Wolf right now
635 ent->s.events[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = event;
636 ent->s.eventParms[ent->s.eventSequence & ( MAX_EVENTS - 1 )] = eventParm;
637 ent->s.eventSequence++;
638 // -NERVE - SMF
639
640 // NERVE - SMF - commented out
641 // bits = ent->s.event & EV_EVENT_BITS;
642 // bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS;
643 // ent->s.event = event | bits;
644 // ent->s.eventParm = eventParm;
645 // -NERVE - SMF
646 }
647 ent->eventTime = level.time;
648 ent->r.eventTime = level.time;
649 }
650
651
652 /*
653 =============
654 G_Sound
655
656 Ridah, removed channel parm, since it wasn't used, and could cause confusion
657 =============
658 */
G_Sound(gentity_t * ent,int soundIndex)659 void G_Sound( gentity_t *ent, int soundIndex ) {
660 gentity_t *te;
661
662 te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND );
663 te->s.eventParm = soundIndex;
664 }
665
666 /*
667 =============
668 G_AnimScriptSound
669 =============
670 */
G_AnimScriptSound(int soundIndex,vec3_t org,int client)671 void G_AnimScriptSound( int soundIndex, vec3_t org, int client ) {
672 gentity_t *e;
673 e = &g_entities[client];
674 G_AddEvent( e, EV_GENERAL_SOUND, soundIndex );
675 AICast_RecordScriptSound( client );
676 }
677
678 //==============================================================================
679
680
681 /*
682 ================
683 G_SetOrigin
684
685 Sets the pos trajectory for a fixed position
686 ================
687 */
G_SetOrigin(gentity_t * ent,vec3_t origin)688 void G_SetOrigin( gentity_t *ent, vec3_t origin ) {
689 VectorCopy( origin, ent->s.pos.trBase );
690 ent->s.pos.trType = TR_STATIONARY;
691 ent->s.pos.trTime = 0;
692 ent->s.pos.trDuration = 0;
693 VectorClear( ent->s.pos.trDelta );
694
695 VectorCopy( origin, ent->r.currentOrigin );
696 }
697
698
699 /*
700 ==============
701 G_SetOrigin
702 ==============
703 */
G_SetAngle(gentity_t * ent,vec3_t angle)704 void G_SetAngle( gentity_t *ent, vec3_t angle ) {
705
706 VectorCopy( angle, ent->s.apos.trBase );
707 ent->s.apos.trType = TR_STATIONARY;
708 ent->s.apos.trTime = 0;
709 ent->s.apos.trDuration = 0;
710 VectorClear( ent->s.apos.trDelta );
711
712 VectorCopy( angle, ent->r.currentAngles );
713
714 // VectorCopy (ent->s.angles, ent->s.apos.trDelta );
715
716 }
717
718 /*
719 ====================
720 infront
721 ====================
722 */
723
infront(gentity_t * self,gentity_t * other)724 qboolean infront( gentity_t *self, gentity_t *other ) {
725 vec3_t vec;
726 float dot;
727 vec3_t forward;
728
729 AngleVectors( self->s.angles, forward, NULL, NULL );
730 VectorSubtract( other->r.currentOrigin, self->r.currentOrigin, vec );
731 VectorNormalize( vec );
732 dot = DotProduct( vec, forward );
733 // G_Printf( "other %5.2f\n", dot);
734 if ( dot > 0.0 ) {
735 return qtrue;
736 }
737 return qfalse;
738 }
739
740 //RF, tag connections
741 /*
742 ==================
743 G_ProcessTagConnect
744 ==================
745 */
G_ProcessTagConnect(gentity_t * ent)746 void G_ProcessTagConnect( gentity_t *ent ) {
747 if ( !ent->tagName ) {
748 G_Error( "G_ProcessTagConnect: NULL ent->tagName\n" );
749 }
750 if ( !ent->tagParent ) {
751 G_Error( "G_ProcessTagConnect: NULL ent->tagParent\n" );
752 }
753 G_FindConfigstringIndex( va( "%i %i %s", ent->s.number, ent->tagParent->s.number, ent->tagName ), CS_TAGCONNECTS, MAX_TAGCONNECTS, qtrue );
754 ent->s.eFlags |= EF_TAGCONNECT;
755
756 // clear out the angles so it always starts out facing the tag direction
757 VectorClear( ent->s.angles );
758 VectorCopy( ent->s.angles, ent->s.apos.trBase );
759 ent->s.apos.trTime = level.time;
760 ent->s.apos.trDuration = 0;
761 ent->s.apos.trType = TR_STATIONARY;
762 VectorClear( ent->s.apos.trDelta );
763 VectorClear( ent->r.currentAngles );
764 }
765
766 /*
767 ================
768 DebugLine
769
770 debug polygons only work when running a local game
771 with r_debugSurface set to 2
772 ================
773 */
DebugLine(vec3_t start,vec3_t end,int color)774 int DebugLine( vec3_t start, vec3_t end, int color ) {
775 vec3_t points[4], dir, cross, up = {0, 0, 1};
776 float dot;
777
778 VectorCopy( start, points[0] );
779 VectorCopy( start, points[1] );
780 //points[1][2] -= 2;
781 VectorCopy( end, points[2] );
782 //points[2][2] -= 2;
783 VectorCopy( end, points[3] );
784
785
786 VectorSubtract( end, start, dir );
787 VectorNormalize( dir );
788 dot = DotProduct( dir, up );
789 if ( dot > 0.99 || dot < -0.99 ) {
790 VectorSet( cross, 1, 0, 0 );
791 } else { CrossProduct( dir, up, cross );}
792
793 VectorNormalize( cross );
794
795 VectorMA( points[0], 2, cross, points[0] );
796 VectorMA( points[1], -2, cross, points[1] );
797 VectorMA( points[2], -2, cross, points[2] );
798 VectorMA( points[3], 2, cross, points[3] );
799
800 return trap_DebugPolygonCreate( color, 4, points );
801 }
802