DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

BrittleFracture.cpp (37476B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition 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 Doom 3 BFG Edition 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 Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition 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 #include "../idlib/precompiled.h"
     30 #pragma hdrstop
     31 
     32 #include "Game_local.h"
     33 
     34 
     35 CLASS_DECLARATION( idEntity, idBrittleFracture )
     36 	EVENT( EV_Activate, idBrittleFracture::Event_Activate )
     37 	EVENT( EV_Touch, idBrittleFracture::Event_Touch )
     38 END_CLASS
     39 
     40 const int SHARD_ALIVE_TIME	= 5000;
     41 const int SHARD_FADE_START	= 2000;
     42 
     43 static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_";
     44 
     45 /*
     46 ================
     47 idBrittleFracture::idBrittleFracture
     48 ================
     49 */
     50 idBrittleFracture::idBrittleFracture() {
     51 	material = NULL;
     52 	decalMaterial = NULL;
     53 	decalSize = 0.0f;
     54 	maxShardArea = 0.0f;
     55 	maxShatterRadius = 0.0f;
     56 	minShatterRadius = 0.0f;
     57 	linearVelocityScale = 0.0f;
     58 	angularVelocityScale = 0.0f;
     59 	shardMass = 0.0f;
     60 	density = 0.0f;
     61 	friction = 0.0f;
     62 	bouncyness = 0.0f;
     63 	fxFracture.Clear();
     64 
     65 	bounds.Clear();
     66 	disableFracture = false;
     67 
     68 	lastRenderEntityUpdate = -1;
     69 	changed = false;
     70 
     71 	fl.networkSync = true;
     72 
     73 	isXraySurface = false;
     74 }
     75 
     76 /*
     77 ================
     78 idBrittleFracture::~idBrittleFracture
     79 ================
     80 */
     81 idBrittleFracture::~idBrittleFracture() {
     82 	int i;
     83 
     84 	for ( i = 0; i < shards.Num(); i++ ) {
     85 		shards[i]->decals.DeleteContents( true );
     86 		delete shards[i];
     87 	}
     88 
     89 	// make sure the render entity is freed before the model is freed
     90 	FreeModelDef();
     91 	renderModelManager->FreeModel( renderEntity.hModel );
     92 
     93 	// Free our events list memory
     94 	storedEvents.Clear();
     95 }
     96 
     97 /*
     98 ================
     99 idBrittleFracture::Save
    100 ================
    101 */
    102 void idBrittleFracture::Save( idSaveGame *savefile ) const {
    103 
    104 	savefile->WriteInt( health );
    105 	entityFlags_s flags = fl;
    106 	LittleBitField( &flags, sizeof( flags ) );
    107 	savefile->Write( &flags, sizeof( flags ) );
    108 	
    109 	// setttings
    110 	savefile->WriteMaterial( material );
    111 	savefile->WriteMaterial( decalMaterial );
    112 	savefile->WriteFloat( decalSize );
    113 	savefile->WriteFloat( maxShardArea );
    114 	savefile->WriteFloat( maxShatterRadius );
    115 	savefile->WriteFloat( minShatterRadius );
    116 	savefile->WriteFloat( linearVelocityScale );
    117 	savefile->WriteFloat( angularVelocityScale );
    118 	savefile->WriteFloat( shardMass );
    119 	savefile->WriteFloat( density );
    120 	savefile->WriteFloat( friction );
    121 	savefile->WriteFloat( bouncyness );
    122 	savefile->WriteString( fxFracture );
    123 
    124 	// state
    125 	savefile->WriteBounds( bounds );
    126 	savefile->WriteBool( disableFracture );
    127 
    128 	savefile->WriteInt( lastRenderEntityUpdate );
    129 	savefile->WriteBool( changed );
    130 
    131 	savefile->WriteModel( defaultRenderModel );
    132 
    133 	// So we can re-break the object on load if needed
    134 	savefile->WriteInt( storedEvents.Num() );
    135 	for ( int i = 0; i < storedEvents.Num(); ++i ) {
    136 		savefile->WriteInt( storedEvents[i].eventType );
    137 		savefile->WriteVec3( storedEvents[i].point );
    138 		savefile->WriteVec3( storedEvents[i].vector );
    139 	}
    140 	savefile->WriteBool( isXraySurface );
    141 }
    142 
    143 /*
    144 ================
    145 idBrittleFracture::Restore
    146 ================
    147 */
    148 void idBrittleFracture::Restore( idRestoreGame *savefile ) {
    149 
    150 	savefile->ReadInt( health );
    151 	savefile->Read( &fl, sizeof( fl ) );
    152 	LittleBitField( &fl, sizeof( fl ) );
    153 
    154 	// setttings
    155 	savefile->ReadMaterial( material );
    156 	savefile->ReadMaterial( decalMaterial );
    157 	savefile->ReadFloat( decalSize );
    158 	savefile->ReadFloat( maxShardArea );
    159 	savefile->ReadFloat( maxShatterRadius );
    160 	savefile->ReadFloat( minShatterRadius );
    161 	savefile->ReadFloat( linearVelocityScale );
    162 	savefile->ReadFloat( angularVelocityScale );
    163 	savefile->ReadFloat( shardMass );
    164 	savefile->ReadFloat( density );
    165 	savefile->ReadFloat( friction );
    166 	savefile->ReadFloat( bouncyness );
    167 	savefile->ReadString( fxFracture );
    168 
    169 	// state
    170 	savefile->ReadBounds(bounds);
    171 	savefile->ReadBool( disableFracture );
    172 
    173 	savefile->ReadInt( lastRenderEntityUpdate );
    174 	savefile->ReadBool( changed );
    175 
    176 	savefile->ReadModel( defaultRenderModel );
    177 
    178 	// Reset all brittle Fractures so we can re-break them if necessary
    179 	fl.takedamage = true;
    180 	CreateFractures( defaultRenderModel );
    181 	FindNeighbours();
    182 
    183 	int numEvents = 0;
    184 	bool resolveBreaks = false;
    185 	savefile->ReadInt( numEvents );
    186 	for( int i = 0; i < numEvents; i++ ) {
    187 		fractureEvent_s restoredEvent;
    188 
    189 		savefile->ReadInt( restoredEvent.eventType );
    190 		savefile->ReadVec3( restoredEvent.point );
    191 		savefile->ReadVec3( restoredEvent.vector );
    192 
    193 		if ( restoredEvent.eventType == EVENT_PROJECT_DECAL ) {
    194 			ProjectDecal( restoredEvent.point, restoredEvent.vector, gameLocal.time, NULL );
    195 		} else {
    196 			Shatter( restoredEvent.point, restoredEvent.vector, gameLocal.time );
    197 		}
    198 		resolveBreaks = true;
    199 	}
    200 
    201 	// remove any dropped shards
    202 	for ( int i = 0; resolveBreaks && i < shards.Num(); i++ ) {
    203 		if ( shards[i]->droppedTime!= -1 ) {
    204 			RemoveShard( i );
    205 			i--;
    206 		}
    207 	}
    208 
    209 	renderEntity.hModel = renderModelManager->AllocModel();
    210 	renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName );
    211 	renderEntity.callback = idBrittleFracture::ModelCallback;
    212 	renderEntity.noShadow = true;
    213 	renderEntity.noSelfShadow = true;
    214 	renderEntity.noDynamicInteractions = false;
    215 
    216 	savefile->ReadBool( isXraySurface );
    217 }
    218 
    219 /*
    220 ================
    221 idBrittleFracture::Spawn
    222 ================
    223 */
    224 void idBrittleFracture::Spawn() {
    225 
    226 	// get shard properties
    227 	decalMaterial = declManager->FindMaterial( spawnArgs.GetString( "mtr_decal" ) );
    228 	decalSize = spawnArgs.GetFloat( "decalSize", "40" );
    229 	maxShardArea = spawnArgs.GetFloat( "maxShardArea", "200" ) * 2.0f ;
    230 	maxShardArea = idMath::ClampFloat( 100, 10000, maxShardArea );
    231 	maxShatterRadius = spawnArgs.GetFloat( "maxShatterRadius", "40" );
    232 	minShatterRadius = spawnArgs.GetFloat( "minShatterRadius", "10" );
    233 	linearVelocityScale = spawnArgs.GetFloat( "linearVelocityScale", "0.1" );
    234 	angularVelocityScale = spawnArgs.GetFloat( "angularVelocityScale", "40" );
    235 	fxFracture = spawnArgs.GetString( "fx" );
    236 
    237 	// make sure that max is greater than min  ( otherwise negative number square root happens )
    238 	if( maxShatterRadius < minShatterRadius ) {
    239 		idLib::Warning( "BrittleFracture, minShatterRadius(%2f) is greater than maxShatterRadius(%2f). Unknown results will ensue.", minShatterRadius, maxShatterRadius );
    240 	}
    241 
    242 	// get rigid body properties
    243 	shardMass = spawnArgs.GetFloat( "shardMass", "20" );
    244 	shardMass = idMath::ClampFloat( 0.001f, 1000.0f, shardMass );
    245 	spawnArgs.GetFloat( "density", "0.1", density );
    246 	density = idMath::ClampFloat( 0.001f, 1000.0f, density );
    247 	spawnArgs.GetFloat( "friction", "0.4", friction );
    248 	friction = idMath::ClampFloat( 0.0f, 1.0f, friction );
    249 	spawnArgs.GetFloat( "bouncyness", "0.01", bouncyness );
    250 	bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness );
    251 
    252 	disableFracture = spawnArgs.GetBool( "disableFracture", "0" );
    253 	health = spawnArgs.GetInt( "health", "40" );
    254 	fl.takedamage = true;
    255 
    256 	// FIXME: set "bleed" so idProjectile calls AddDamageEffect
    257 	spawnArgs.SetBool( "bleed", 1 );
    258 
    259 	// check for xray surface
    260 	if ( renderEntity.hModel != NULL ) {
    261 		const idRenderModel *model = renderEntity.hModel;
    262 
    263 		isXraySurface = false;
    264 
    265 		for ( int i = 0; i < model->NumSurfaces(); i++ ) {
    266 			const modelSurface_t *surf = model->Surface( i );
    267 
    268 			if ( idStr( surf->shader->GetName() ) == "textures/smf/window_scratch" ) {
    269 				isXraySurface = true;
    270 				break;
    271 			}
    272 		}
    273 	}
    274 
    275 	CreateFractures( renderEntity.hModel );
    276 
    277 	FindNeighbours();
    278 
    279 	defaultRenderModel = renderEntity.hModel;
    280 	renderEntity.hModel = renderModelManager->AllocModel();
    281 	renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName );
    282 	renderEntity.callback = idBrittleFracture::ModelCallback;
    283 	renderEntity.noShadow = true;
    284 	renderEntity.noSelfShadow = true;
    285 	renderEntity.noDynamicInteractions = false;
    286 }
    287 
    288 /*
    289 ================
    290 idBrittleFracture::AddShard
    291 ================
    292 */
    293 void idBrittleFracture::AddShard( idClipModel *clipModel, idFixedWinding &w ) {
    294 	shard_t *shard = new (TAG_PARTICLE) shard_t;
    295 	shard->clipModel = clipModel;
    296 	shard->droppedTime = -1;
    297 	shard->winding = w;
    298 	shard->decals.Clear();
    299 	shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false );
    300 	shard->neighbours.Clear();
    301 	shard->atEdge = false;
    302 	shards.Append( shard );
    303 }
    304 
    305 /*
    306 ================
    307 idBrittleFracture::RemoveShard
    308 ================
    309 */
    310 void idBrittleFracture::RemoveShard( int index ) {
    311 	int i;
    312 
    313 	delete shards[index];
    314 	shards.RemoveIndex( index );
    315 	physicsObj.RemoveIndex( index );
    316 
    317 	for ( i = index; i < shards.Num(); i++ ) {
    318 		shards[i]->clipModel->SetId( i );
    319 	}
    320 }
    321 
    322 /*
    323 ================
    324 idBrittleFracture::UpdateRenderEntity
    325 ================
    326 */
    327 bool idBrittleFracture::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const {
    328 	int i, j, k, n, msec, numTris, numDecalTris;
    329 	float fade;
    330 	dword packedColor;
    331 	srfTriangles_t *tris, *decalTris;
    332 	modelSurface_t surface;
    333 	idDrawVert *v;
    334 	idPlane plane;
    335 	idMat3 tangents;
    336 
    337 	// this may be triggered by a model trace or other non-view related source,
    338 	// to which we should look like an empty model
    339 	if ( !renderView ) {
    340 		return false;
    341 	}
    342 
    343 	// don't regenerate it if it is current
    344 	if ( lastRenderEntityUpdate == gameLocal.time || !changed ) {
    345 		return false;
    346 	}
    347 
    348 	lastRenderEntityUpdate = gameLocal.time;
    349 	changed = false;
    350 
    351 	numTris = 0;
    352 	numDecalTris = 0;
    353 	for ( i = 0; i < shards.Num(); i++ ) {
    354 		n = shards[i]->winding.GetNumPoints();
    355 		if ( n > 2 ) {
    356 			numTris += n - 2;
    357 		}
    358 		for ( k = 0; k < shards[i]->decals.Num(); k++ ) {
    359 			n = shards[i]->decals[k]->GetNumPoints();
    360 			if ( n > 2 ) {
    361 				numDecalTris += n - 2;
    362 			}
    363 		}
    364 	}
    365 
    366 	// FIXME: re-use model surfaces
    367 	renderEntity->hModel->InitEmpty( brittleFracture_SnapshotName );
    368 
    369 	// allocate triangle surfaces for the fractures and decals
    370 	tris = renderEntity->hModel->AllocSurfaceTriangles( numTris * 3, material->ShouldCreateBackSides() ? numTris * 6 : numTris * 3 );
    371 	decalTris = renderEntity->hModel->AllocSurfaceTriangles( numDecalTris * 3, decalMaterial->ShouldCreateBackSides() ? numDecalTris * 6 : numDecalTris * 3 );
    372 
    373 	for ( i = 0; i < shards.Num(); i++ ) {
    374 		const idVec3 &origin = shards[i]->clipModel->GetOrigin();
    375 		const idMat3 &axis = shards[i]->clipModel->GetAxis();
    376 
    377 		fade = 1.0f;
    378 		if ( shards[i]->droppedTime >= 0 ) {
    379 			msec = gameLocal.time - shards[i]->droppedTime - SHARD_FADE_START;
    380 			if ( msec > 0 ) {
    381 				fade = 1.0f - (float) msec / ( SHARD_ALIVE_TIME - SHARD_FADE_START );
    382 			}
    383 		}
    384 
    385 		packedColor = PackColor( idVec4( renderEntity->shaderParms[ SHADERPARM_RED ] * fade,
    386 										renderEntity->shaderParms[ SHADERPARM_GREEN ] * fade,
    387                                         renderEntity->shaderParms[ SHADERPARM_BLUE ] * fade,
    388 										fade ) );
    389 
    390 		const idWinding &winding = shards[i]->winding;
    391 
    392 		winding.GetPlane( plane );
    393 		tangents = ( plane.Normal() * axis ).ToMat3();
    394 
    395 		for ( j = 2; j < winding.GetNumPoints(); j++ ) {
    396 
    397 			v = &tris->verts[tris->numVerts++];
    398 			v->Clear();
    399 			v->xyz = origin + winding[0].ToVec3() * axis;
    400 			v->SetTexCoord( winding[0].s, winding[0].t );
    401 			v->SetNormal( tangents[0] );
    402 			v->SetTangent( tangents[1] );
    403 			v->SetBiTangent( tangents[2] );
    404 			v->SetColor( packedColor );
    405 
    406 			v = &tris->verts[tris->numVerts++];
    407 			v->Clear();
    408 			v->xyz = origin + winding[j-1].ToVec3() * axis;
    409 			v->SetTexCoord( winding[j-1].s, winding[j-1].t );
    410 			v->SetNormal( tangents[0] );
    411 			v->SetTangent( tangents[1] );
    412 			v->SetBiTangent( tangents[2] );
    413 			v->SetColor( packedColor );
    414 
    415 			v = &tris->verts[tris->numVerts++];
    416 			v->Clear();
    417 			v->xyz = origin + winding[j].ToVec3() * axis;
    418 			v->SetTexCoord( winding[j].s, winding[j].t );
    419 			v->SetNormal( tangents[0] );
    420 			v->SetTangent( tangents[1] );
    421 			v->SetBiTangent( tangents[2] );
    422 			v->SetColor( packedColor );
    423 
    424 			tris->indexes[tris->numIndexes++] = tris->numVerts - 3;
    425 			tris->indexes[tris->numIndexes++] = tris->numVerts - 2;
    426 			tris->indexes[tris->numIndexes++] = tris->numVerts - 1;
    427 
    428 			if ( material->ShouldCreateBackSides() ) {
    429 
    430 				tris->indexes[tris->numIndexes++] = tris->numVerts - 2;
    431 				tris->indexes[tris->numIndexes++] = tris->numVerts - 3;
    432 				tris->indexes[tris->numIndexes++] = tris->numVerts - 1;
    433 			}
    434 		}
    435 
    436 		for ( k = 0; k < shards[i]->decals.Num(); k++ ) {
    437 			const idWinding &decalWinding = *shards[i]->decals[k];
    438 
    439 			for ( j = 2; j < decalWinding.GetNumPoints(); j++ ) {
    440 
    441 				v = &decalTris->verts[decalTris->numVerts++];
    442 				v->Clear();
    443 				v->xyz = origin + decalWinding[0].ToVec3() * axis;
    444 				v->SetTexCoord( decalWinding[0].s, decalWinding[0].t );
    445 				v->SetNormal( tangents[0] );
    446 				v->SetTangent( tangents[1] );
    447 				v->SetBiTangent( tangents[2] );
    448 				v->SetColor( packedColor );
    449 
    450 				v = &decalTris->verts[decalTris->numVerts++];
    451 				v->Clear();
    452 				v->xyz = origin + decalWinding[j-1].ToVec3() * axis;
    453 				v->SetTexCoord( decalWinding[j-1].s, decalWinding[j-1].t );
    454 				v->SetNormal( tangents[0] );
    455 				v->SetTangent( tangents[1] );
    456 				v->SetBiTangent( tangents[2] );
    457 				v->SetColor( packedColor );
    458 
    459 				v = &decalTris->verts[decalTris->numVerts++];
    460 				v->Clear();
    461 				v->xyz = origin + decalWinding[j].ToVec3() * axis;
    462 				v->SetTexCoord( decalWinding[j].s, decalWinding[j].t );
    463 				v->SetNormal( tangents[0] );
    464 				v->SetTangent( tangents[1] );
    465 				v->SetBiTangent( tangents[2] );
    466 				v->SetColor( packedColor );
    467 
    468 				decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3;
    469 				decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2;
    470 				decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1;
    471 
    472 				if ( decalMaterial->ShouldCreateBackSides() ) {
    473 
    474 					decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2;
    475 					decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3;
    476 					decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1;
    477 				}
    478 			}
    479 		}
    480 	}
    481 
    482 	tris->tangentsCalculated = true;
    483 	decalTris->tangentsCalculated = true;
    484 
    485 	SIMDProcessor->MinMax( tris->bounds[0], tris->bounds[1], tris->verts, tris->numVerts );
    486 	SIMDProcessor->MinMax( decalTris->bounds[0], decalTris->bounds[1], decalTris->verts, decalTris->numVerts );
    487 
    488 	memset( &surface, 0, sizeof( surface ) );
    489 	surface.shader = material;
    490 	surface.id = 0;
    491 	surface.geometry = tris;
    492 	renderEntity->hModel->AddSurface( surface );
    493 
    494 	memset( &surface, 0, sizeof( surface ) );
    495 	surface.shader = decalMaterial;
    496 	surface.id = 1;
    497 	surface.geometry = decalTris;
    498 	renderEntity->hModel->AddSurface( surface );
    499 
    500 	return true;
    501 }
    502 
    503 /*
    504 ================
    505 idBrittleFracture::ModelCallback
    506 ================
    507 */
    508 bool idBrittleFracture::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
    509 	const idBrittleFracture *ent;
    510 
    511 	ent = static_cast<idBrittleFracture *>(gameLocal.entities[ renderEntity->entityNum ]);
    512 	if ( ent == NULL ) {
    513 		gameLocal.Error( "idBrittleFracture::ModelCallback: callback with NULL game entity" );
    514 		return false;
    515 	}
    516 
    517 	return ent->UpdateRenderEntity( renderEntity, renderView );
    518 }
    519 
    520 /*
    521 ================
    522 idBrittleFracture::Present
    523 ================
    524 */
    525 void idBrittleFracture::Present() {
    526 
    527 	// don't present to the renderer if the entity hasn't changed
    528 	if ( !( thinkFlags & TH_UPDATEVISUALS ) ) {
    529 		return;
    530 	}
    531 	BecomeInactive( TH_UPDATEVISUALS );
    532 
    533 	renderEntity.bounds = bounds;
    534 	renderEntity.origin.Zero();
    535 	renderEntity.axis.Identity();
    536 
    537 	// force an update because the bounds/origin/axis may stay the same while the model changes
    538 	renderEntity.forceUpdate = true;
    539 
    540 	// add to refresh list
    541 	if ( modelDefHandle == -1 ) {
    542 		modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
    543 	} else {
    544 		gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity );
    545 	}
    546 
    547 	changed = true;
    548 }
    549 
    550 /*
    551 ================
    552 idBrittleFracture::Think
    553 ================
    554 */
    555 void idBrittleFracture::Think() {
    556 	int i, startTime, endTime, droppedTime;
    557 	shard_t *shard;
    558 	bool atRest = true, fading = false;
    559 
    560 	// remove overdue shards
    561 	for ( i = 0; i < shards.Num(); i++ ) {
    562 		droppedTime = shards[i]->droppedTime;
    563 		if ( droppedTime != -1 ) {
    564 			if ( gameLocal.time - droppedTime > SHARD_ALIVE_TIME ) {
    565 				RemoveShard( i );
    566 				i--;
    567 			}
    568 			fading = true;
    569 		}
    570 	}
    571 
    572 	// remove the entity when nothing is visible
    573 	if ( !shards.Num() ) {
    574 		PostEventMS( &EV_Remove, 0 );
    575 		return;
    576 	}
    577 
    578 	if ( thinkFlags & TH_PHYSICS ) {
    579 
    580 		startTime = gameLocal.previousTime;
    581 		endTime = gameLocal.time;
    582 
    583 		// run physics on shards
    584 		for ( i = 0; i < shards.Num(); i++ ) {
    585 			shard = shards[i];
    586 
    587 			if ( shard->droppedTime == -1 ) {
    588 				continue;
    589 			}
    590 
    591 			shard->physicsObj.Evaluate( endTime - startTime, endTime );
    592 
    593 			if ( !shard->physicsObj.IsAtRest() ) {
    594 				atRest = false;
    595 			}
    596 		}
    597 
    598 		if ( atRest ) {
    599 			BecomeInactive( TH_PHYSICS );
    600 		} else {
    601 			BecomeActive( TH_PHYSICS );
    602 		}
    603 	}
    604 
    605 	if ( !atRest || bounds.IsCleared() ) {
    606 		bounds.Clear();
    607 		for ( i = 0; i < shards.Num(); i++ ) {
    608 			bounds.AddBounds( shards[i]->clipModel->GetAbsBounds() );
    609 		}
    610 	}
    611 
    612 	if ( fading ) {
    613 		BecomeActive( TH_UPDATEVISUALS | TH_THINK );
    614 	} else {
    615 		BecomeInactive( TH_THINK );
    616 	}
    617 
    618 	RunPhysics();
    619 	Present();
    620 }
    621 
    622 /*
    623 ================
    624 idBrittleFracture::ApplyImpulse
    625 ================
    626 */
    627 void idBrittleFracture::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
    628 
    629 	if ( id < 0 || id >= shards.Num() ) {
    630 		return;
    631 	}
    632 
    633 	if ( shards[id]->droppedTime != -1 ) {
    634 		shards[id]->physicsObj.ApplyImpulse( 0, point, impulse );
    635 	} else if ( health <= 0 && !disableFracture ) {
    636 		Shatter( point, impulse, gameLocal.time );
    637 	}
    638 }
    639 
    640 /*
    641 ================
    642 idBrittleFracture::AddForce
    643 ================
    644 */
    645 void idBrittleFracture::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) {
    646 
    647 	if ( id < 0 || id >= shards.Num() ) {
    648 		return;
    649 	}
    650 
    651 	if ( shards[id]->droppedTime != -1 ) {
    652 		shards[id]->physicsObj.AddForce( 0, point, force );
    653 	} else if ( health <= 0 && !disableFracture ) {
    654 		Shatter( point, force, gameLocal.time );
    655 	}
    656 }
    657 
    658 /*
    659 ================
    660 idBrittleFracture::ProjectDecal
    661 ================
    662 */
    663 void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) {
    664 	int i, j, bits, clipBits;
    665 	float a, c, s;
    666 	idVec2 st[MAX_POINTS_ON_WINDING];
    667 	idVec3 origin;
    668 	idMat3 axis, axistemp;
    669 	idPlane textureAxis[2];
    670 
    671 	if ( common->IsServer() ) {
    672 		idBitMsg	msg;
    673 		byte		msgBuf[MAX_EVENT_PARAM_SIZE];
    674 
    675 		msg.InitWrite( msgBuf, sizeof( msgBuf ) );
    676 		msg.BeginWriting();
    677 		msg.WriteFloat( point[0] );
    678 		msg.WriteFloat( point[1] );
    679 		msg.WriteFloat( point[2] );
    680 		msg.WriteFloat( dir[0] );
    681 		msg.WriteFloat( dir[1] );
    682 		msg.WriteFloat( dir[2] );
    683 		ServerSendEvent( EVENT_PROJECT_DECAL, &msg, true );
    684 	}
    685 
    686 	// store the event so we can rebuilt the fracture after loading a save
    687 	fractureEvent_s fractureEvent;
    688 	fractureEvent.eventType = EVENT_PROJECT_DECAL;
    689 	fractureEvent.point = point;
    690 	fractureEvent.vector = dir;
    691 	storedEvents.Append( fractureEvent );
    692 
    693 	if ( time >= gameLocal.time ) {
    694 		// try to get the sound from the damage def
    695 		const idDeclEntityDef *damageDef = NULL;
    696 		const idSoundShader *sndShader = NULL;
    697 		if ( damageDefName ) {
    698 			damageDef = gameLocal.FindEntityDef( damageDefName, false );
    699 			if ( damageDef ) {
    700 				const char * sndName = damageDef->dict.GetString( "snd_shatter", "" );
    701 				if ( sndName[0] != 0 ) {
    702 					sndShader = declManager->FindSound( sndName );
    703 				}
    704 			}
    705 		}
    706 
    707 		if ( sndShader ) {
    708 			StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL );
    709 		} else {
    710 			StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL );
    711 		}
    712 	}
    713 
    714 	a = gameLocal.random.RandomFloat() * idMath::TWO_PI;
    715 	c = cos( a );
    716 	s = -sin( a );
    717 
    718 	axis[2] = -dir;
    719 	axis[2].Normalize();
    720 	axis[2].NormalVectors( axistemp[0], axistemp[1] );
    721 	axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s;
    722 	axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c;
    723 
    724 	textureAxis[0] = axis[0] * ( 1.0f / decalSize );
    725 	textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f;
    726 
    727 	textureAxis[1] = axis[1] * ( 1.0f / decalSize );
    728 	textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f;
    729 
    730 	for ( i = 0; i < shards.Num(); i++ ) {
    731 		idFixedWinding &winding = shards[i]->winding;
    732 		origin = shards[i]->clipModel->GetOrigin();
    733 		axis = shards[i]->clipModel->GetAxis();
    734 		float d0, d1;
    735 
    736 		clipBits = -1;
    737 		for ( j = 0; j < winding.GetNumPoints(); j++ ) {
    738 			idVec3 p = origin + winding[j].ToVec3() * axis;
    739 
    740 			st[j].x = d0 = textureAxis[0].Distance( p );
    741 			st[j].y = d1 = textureAxis[1].Distance( p );
    742 
    743 			bits = IEEE_FLT_SIGNBITSET( d0 );
    744 			d0 = 1.0f - d0;
    745 			bits |= IEEE_FLT_SIGNBITSET( d1 ) << 2;
    746 			d1 = 1.0f - d1;
    747 			bits |= IEEE_FLT_SIGNBITSET( d0 ) << 1;
    748 			bits |= IEEE_FLT_SIGNBITSET( d1 ) << 3;
    749 
    750 			clipBits &= bits;
    751 		}
    752 
    753 		if ( clipBits ) {
    754 			continue;
    755 		}
    756 
    757 		idFixedWinding *decal = new (TAG_PARTICLE) idFixedWinding;
    758 		shards[i]->decals.Append( decal );
    759 
    760 		decal->SetNumPoints( winding.GetNumPoints() );
    761 		for ( j = 0; j < winding.GetNumPoints(); j++ ) {
    762 			(*decal)[j].ToVec3() = winding[j].ToVec3();
    763 			(*decal)[j].s = st[j].x;
    764 			(*decal)[j].t = st[j].y;
    765 		}
    766 	}
    767 
    768 	BecomeActive( TH_UPDATEVISUALS );
    769 }
    770 
    771 /*
    772 ================
    773 idBrittleFracture::DropShard
    774 ================
    775 */
    776 void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) {
    777 	int i, j, clipModelId;
    778 	float dist, f;
    779 	idVec3 dir2, origin;
    780 	idMat3 axis;
    781 	shard_t *neighbour;
    782 
    783 	// don't display decals on dropped shards
    784 	shard->decals.DeleteContents( true );
    785 
    786 	// remove neighbour pointers of neighbours pointing to this shard
    787 	for ( i = 0; i < shard->neighbours.Num(); i++ ) {
    788 		neighbour = shard->neighbours[i];
    789 		for ( j = 0; j < neighbour->neighbours.Num(); j++ ) {
    790 			if ( neighbour->neighbours[j] == shard ) {
    791 				neighbour->neighbours.RemoveIndex( j );
    792 				break;
    793 			}
    794 		}
    795 	}
    796 
    797 	// remove neighbour pointers
    798 	shard->neighbours.Clear();
    799 
    800 	// remove the clip model from the static physics object
    801 	clipModelId = shard->clipModel->GetId();
    802 	physicsObj.SetClipModel( NULL, 1.0f, clipModelId, false );
    803 
    804 	origin = shard->clipModel->GetOrigin();
    805 	axis = shard->clipModel->GetAxis();
    806 
    807 	// set the dropped time for fading
    808 	shard->droppedTime = time;
    809 
    810 	dir2 = origin - point;
    811 	dist = dir2.Normalize();
    812 	f = dist > maxShatterRadius ? 1.0f : idMath::Sqrt( idMath::Fabs( dist - minShatterRadius ) ) * ( 1.0f / idMath::Sqrt(  idMath::Fabs( maxShatterRadius - minShatterRadius ) ) );
    813 
    814 	// setup the physics
    815 	shard->physicsObj.SetSelf( this );
    816 	shard->physicsObj.SetClipModel( shard->clipModel, density );
    817 	shard->physicsObj.SetMass( shardMass );
    818 	shard->physicsObj.SetOrigin( origin );
    819 	shard->physicsObj.SetAxis( axis );
    820 	shard->physicsObj.SetBouncyness( bouncyness );
    821 	shard->physicsObj.SetFriction( 0.6f, 0.6f, friction );
    822 	shard->physicsObj.SetGravity( gameLocal.GetGravity() );
    823 	shard->physicsObj.SetContents( CONTENTS_RENDERMODEL );
    824 	shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP );
    825 	shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir );
    826 	shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) );
    827 
    828 	shard->clipModel->SetId( clipModelId );
    829 
    830 	BecomeActive( TH_PHYSICS );
    831 }
    832 
    833 /*
    834 ================
    835 idBrittleFracture::Shatter
    836 ================
    837 */
    838 void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, const int time ) {
    839 	int i;
    840 	idVec3 dir;
    841 	shard_t *shard;
    842 	float m;
    843 
    844 	if ( common->IsServer() ) {
    845 		idBitMsg	msg;
    846 		byte		msgBuf[MAX_EVENT_PARAM_SIZE];
    847 
    848 		msg.InitWrite( msgBuf, sizeof( msgBuf ) );
    849 		msg.BeginWriting();
    850 		msg.WriteFloat( point[0] );
    851 		msg.WriteFloat( point[1] );
    852 		msg.WriteFloat( point[2] );
    853 		msg.WriteFloat( impulse[0] );
    854 		msg.WriteFloat( impulse[1] );
    855 		msg.WriteFloat( impulse[2] );
    856 		ServerSendEvent( EVENT_SHATTER, &msg, true );
    857 	}
    858 
    859 	// Store off the event so we can rebuilt the object if we reload a savegame
    860 	fractureEvent_s fractureEvent;
    861 	fractureEvent.eventType = EVENT_SHATTER;
    862 	fractureEvent.point = point;
    863 	fractureEvent.vector = impulse;
    864 	storedEvents.Append( fractureEvent );
    865 
    866 	if ( time > ( gameLocal.time - SHARD_ALIVE_TIME ) ) {
    867 		StartSound( "snd_shatter", SND_CHANNEL_ANY, 0, false, NULL );
    868 	}
    869 
    870 	if ( !IsBroken() ) {
    871 		Break();
    872 	}
    873 
    874 	if ( fxFracture.Length() ) {
    875 		idEntityFx::StartFx( fxFracture, &point, &GetPhysics()->GetAxis(), this, true );
    876 	}
    877 
    878 	dir = impulse;
    879 	m = dir.Normalize();
    880 
    881 	for ( i = 0; i < shards.Num(); i++ ) {
    882 		shard = shards[i];
    883 
    884 		if ( shard->droppedTime != -1 ) {
    885 			continue;
    886 		}
    887 
    888 		if ( ( shard->clipModel->GetOrigin() - point ).LengthSqr() > Square( maxShatterRadius ) ) {
    889 			continue;
    890 		}
    891 
    892 		DropShard( shard, point, dir, m, time );
    893 	}
    894 
    895 	DropFloatingIslands( point, impulse, time );
    896 }
    897 
    898 /*
    899 ================
    900 idBrittleFracture::DropFloatingIslands
    901 ================
    902 */
    903 void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) {
    904 	int i, j, numIslands;
    905 	int queueStart, queueEnd;
    906 	shard_t *curShard, *nextShard, **queue;
    907 	bool touchesEdge;
    908 	idVec3 dir;
    909 
    910 	dir = impulse;
    911 	dir.Normalize();
    912 
    913 	numIslands = 0;
    914 	queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) );
    915 	for ( i = 0; i < shards.Num(); i++ ) {
    916 		shards[i]->islandNum = 0;
    917 	}
    918 
    919 	for ( i = 0; i < shards.Num(); i++ ) {
    920 
    921 		if ( shards[i]->droppedTime != -1 ) {
    922 			continue;
    923 		}
    924 
    925 		if ( shards[i]->islandNum ) {
    926 			continue;
    927 		}
    928 
    929         queueStart = 0;
    930 		queueEnd = 1;
    931 		queue[0] = shards[i];
    932 		shards[i]->islandNum = numIslands+1;
    933 		touchesEdge = false;
    934 
    935 		if ( shards[i]->atEdge ) {
    936 			touchesEdge = true;
    937 		}
    938 
    939 		for ( curShard = queue[queueStart]; queueStart < queueEnd; curShard = queue[++queueStart] ) {
    940 
    941 			for ( j = 0; j < curShard->neighbours.Num(); j++ ) {
    942 
    943 				nextShard = curShard->neighbours[j];
    944 
    945 				if ( nextShard->droppedTime != -1 ) {
    946 					continue;
    947 				}
    948 
    949 				if ( nextShard->islandNum ) {
    950 					continue;
    951 				}
    952 
    953 				queue[queueEnd++] = nextShard;
    954 				nextShard->islandNum = numIslands+1;
    955 
    956 				if ( nextShard->atEdge ) {
    957 					touchesEdge = true;
    958 				}
    959 			}
    960 		}
    961 		numIslands++;
    962 
    963 		// if the island is not connected to the world at any edges
    964 		if ( !touchesEdge ) {
    965 			for ( j = 0; j < queueEnd; j++ ) {
    966 				DropShard( queue[j], point, dir, 0.0f, time );
    967 			}
    968 		}
    969 	}
    970 }
    971 
    972 /*
    973 ================
    974 idBrittleFracture::Break
    975 ================
    976 */
    977 void idBrittleFracture::Break() {
    978 	fl.takedamage = false;
    979 	physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER );
    980 }
    981 
    982 /*
    983 ================
    984 idBrittleFracture::IsBroken
    985 ================
    986 */
    987 bool idBrittleFracture::IsBroken() const {
    988 	return ( fl.takedamage == false );
    989 }
    990 
    991 /*
    992 ================
    993 idBrittleFracture::Killed
    994 ================
    995 */
    996 void idBrittleFracture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
    997 	if ( !disableFracture ) {
    998 		ActivateTargets( this );
    999 		Break();
   1000 	}
   1001 }
   1002 
   1003 /*
   1004 ================
   1005 idBrittleFracture::AddDamageEffect
   1006 ================
   1007 */
   1008 void idBrittleFracture::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) {
   1009 	if ( !disableFracture ) {
   1010 		ProjectDecal( collision.c.point, collision.c.normal, gameLocal.time, damageDefName );
   1011 	}
   1012 }
   1013 
   1014 static float fractureSplitTable[] = { 1365.123f, 5.324f, 1125.34f, 50.34f, 555.252f, 100.12f, 230.53f, 10000.87f, 10000.87f  };
   1015 
   1016 /*
   1017 ================
   1018 idBrittleFracture::Fracture_r
   1019 ================
   1020 */
   1021 void idBrittleFracture::Fracture_r( idFixedWinding &w, idRandom2 & random ) {
   1022 	int i, j, bestPlane;
   1023 	float a, c, s, dist, bestDist;
   1024 	idVec3 origin;
   1025 	idPlane windingPlane, splitPlanes[2];
   1026 	idMat3 axis, axistemp;
   1027 	idFixedWinding back;
   1028 	idTraceModel trm;
   1029 	idClipModel *clipModel;
   1030 
   1031 
   1032 	while( 1 ) {
   1033 		origin = w.GetCenter();
   1034 		w.GetPlane( windingPlane );
   1035 
   1036 		if ( w.GetArea() < maxShardArea ) {
   1037 			break;
   1038 		}
   1039 
   1040 		// randomly create a split plane
   1041 		axis[2] = windingPlane.Normal();
   1042 		if ( isXraySurface ) {
   1043 			a = idMath::TWO_PI / 2.f;
   1044 		}
   1045 		else {
   1046 			a = random.RandomFloat() * idMath::TWO_PI;
   1047 		}
   1048 		c = cos( a );
   1049 		s = -sin( a );
   1050 		axis[2].NormalVectors( axistemp[0], axistemp[1] );
   1051 		axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s;
   1052 		axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c;
   1053 
   1054 		// get the best split plane
   1055 		bestDist = 0.0f;
   1056 		bestPlane = 0;
   1057 		for ( i = 0; i < 2; i++ ) {
   1058 			splitPlanes[i].SetNormal( axis[i] );
   1059 			splitPlanes[i].FitThroughPoint( origin );
   1060 			for ( j = 0; j < w.GetNumPoints(); j++ ) {
   1061 				dist = splitPlanes[i].Distance( w[j].ToVec3() );
   1062 				if ( dist > bestDist ) {
   1063 					bestDist = dist;
   1064 					bestPlane = i;
   1065 				}
   1066 			}
   1067 		}
   1068 
   1069 		// split the winding
   1070 		if ( !w.Split( &back, splitPlanes[bestPlane] ) ) {
   1071 			break;
   1072 		}
   1073 
   1074 		// recursively create shards for the back winding
   1075 		Fracture_r( back, random );
   1076 	}
   1077 
   1078 	// translate the winding to it's center
   1079 	origin = w.GetCenter();
   1080 	for ( j = 0; j < w.GetNumPoints(); j++ ) {
   1081 		w[j].ToVec3() -= origin;
   1082 	}
   1083 	w.RemoveEqualPoints();
   1084 
   1085 	trm.SetupPolygon( w );
   1086 	trm.Shrink( CM_CLIP_EPSILON );
   1087 	clipModel = new (TAG_PHYSICS) idClipModel( trm, false );
   1088 
   1089 	physicsObj.SetClipModel( clipModel, 1.0f, shards.Num() );
   1090 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() + origin, shards.Num() );
   1091 	physicsObj.SetAxis( GetPhysics()->GetAxis(), shards.Num() );
   1092 
   1093 	AddShard( clipModel, w );
   1094 }
   1095 
   1096 /*
   1097 ================
   1098 CompareVec5
   1099 ================
   1100 */
   1101 bool CompareVec5( const idVec5 & v0, const idVec5 & v1 ) {
   1102 	float dx = v0.x - v1.x;
   1103 	float dy = v0.y - v1.y;
   1104 	float dz = v0.z - v1.z;
   1105 	float ds = v0.s - v1.s;
   1106 	float dt = v0.t - v1.t;
   1107 	float d = ( dx * dx ) + ( dy * dy ) + ( dz * dz ) + ( ds * ds ) + ( dt + dt );
   1108 	return ( d == 0.0f );
   1109 }
   1110 
   1111 /*
   1112 ================
   1113 idBrittleFracture::CreateFractures
   1114 ================
   1115 */
   1116 void idBrittleFracture::CreateFractures( const idRenderModel *renderModel ) {
   1117 	if ( !renderModel || renderModel->NumSurfaces() < 1 ) {
   1118 		return;
   1119 	}
   1120 
   1121 	physicsObj.SetSelf( this );
   1122 	physicsObj.SetOrigin( GetPhysics()->GetOrigin(), 0 );
   1123 	physicsObj.SetAxis( GetPhysics()->GetAxis(), 0 );
   1124 
   1125 	const modelSurface_t * surf = renderModel->Surface( 0 );
   1126 	material = surf->shader;
   1127 
   1128 	idMat3 physAxis;
   1129 	physAxis = physicsObj.GetAxis();
   1130 	if ( isXraySurface ) {
   1131 		idFixedWinding w;
   1132 
   1133 		for ( int i = 0; i < 4; i++ ) {
   1134 			const idDrawVert * v = &surf->geometry->verts[i];
   1135 			w.AddPoint( idVec5( v->xyz, v->GetTexCoord() ) );
   1136 		}
   1137 
   1138 		idRandom2 random( entityNumber );
   1139 		Fracture_r( w , random );
   1140 
   1141 	} else
   1142 	{
   1143 		const idDrawVert * verts = surf->geometry->verts;
   1144 		triIndex_t * indexes = surf->geometry->indexes;
   1145 
   1146 		for ( int j = 0; j < surf->geometry->numIndexes; j += 3 ) {
   1147 			int i0 = indexes[ j + 0 ];
   1148 			int i1 = indexes[ j + 1 ];
   1149 			int i2 = indexes[ j + 2 ];
   1150 			idFixedWinding w;
   1151 			w.AddPoint( idVec5( verts[i2].xyz, verts[i2].GetTexCoord() ) );
   1152 			w.AddPoint( idVec5( verts[i1].xyz, verts[i1].GetTexCoord() ) );
   1153 			w.AddPoint( idVec5( verts[i0].xyz, verts[i0].GetTexCoord() ) );
   1154 			idPlane p1;
   1155 			w.GetPlane( p1 );
   1156 			for ( int k = j + 3; k < surf->geometry->numIndexes && ( w.GetNumPoints() + 1 < MAX_POINTS_ON_WINDING ); k += 3 ) {
   1157 				int i3 = indexes[ k + 0 ];
   1158 				int i4 = indexes[ k + 1 ];
   1159 				int i5 = indexes[ k + 2 ];
   1160 				idFixedWinding w2;
   1161 				w2.AddPoint( idVec5( verts[i5].xyz, verts[i5].GetTexCoord() ) );
   1162 				w2.AddPoint( idVec5( verts[i4].xyz, verts[i4].GetTexCoord() ) );
   1163 				w2.AddPoint( idVec5( verts[i3].xyz, verts[i3].GetTexCoord() ) );
   1164 				idPlane p2;
   1165 				w2.GetPlane( p2 );
   1166 				if ( p1 != p2 ) {
   1167 					break;
   1168 				}
   1169 				bool found = false;
   1170 				for ( int w1i = 0; w1i < w.GetNumPoints(); w1i++ ) {
   1171 					for ( int w2i = 0; w2i < w2.GetNumPoints(); w2i++ ) {
   1172 						if ( CompareVec5( w[w1i], w2[w2i] ) && CompareVec5( w[(w1i+1)%w.GetNumPoints()], w2[(w2i+2)%w2.GetNumPoints()] ) ) {
   1173 							w.InsertPoint( w2[(w2i+1)%w2.GetNumPoints()], (w1i+1)%w.GetNumPoints() );
   1174 							j = k;
   1175 							found = true;
   1176 							break;
   1177 						}
   1178 					}
   1179 					if ( found ) {
   1180 						break;
   1181 					}
   1182 				}
   1183 				if ( !found ) {
   1184 					break;
   1185 				}
   1186 			}
   1187 
   1188 			idRandom2 random( entityNumber );
   1189 			Fracture_r( w, random );
   1190 		}
   1191 	}
   1192 
   1193 
   1194 	physicsObj.SetContents( material->GetContentFlags() );
   1195 	SetPhysics( &physicsObj );
   1196 }
   1197 
   1198 /*
   1199 ================
   1200 idBrittleFracture::FindNeighbours
   1201 ================
   1202 */
   1203 void idBrittleFracture::FindNeighbours() {
   1204 	int i, j, k, l;
   1205 	idVec3 p1, p2, dir;
   1206 	idMat3 axis;
   1207 	idPlane plane[4];
   1208 
   1209 	for ( i = 0; i < shards.Num(); i++ ) {
   1210 
   1211 		shard_t *shard1 = shards[i];
   1212 		const idWinding &w1 = shard1->winding;
   1213 		const idVec3 &origin1 = shard1->clipModel->GetOrigin();
   1214 		const idMat3 &axis1 = shard1->clipModel->GetAxis();
   1215 
   1216 		for ( k = 0; k < w1.GetNumPoints(); k++ ) {
   1217 
   1218 			p1 = origin1 + w1[k].ToVec3() * axis1;
   1219 			p2 = origin1 + w1[(k+1)%w1.GetNumPoints()].ToVec3() * axis1;
   1220 			dir = p2 - p1;
   1221 			dir.Normalize();
   1222 			axis = dir.ToMat3();
   1223 
   1224 			plane[0].SetNormal( dir );
   1225 			plane[0].FitThroughPoint( p1 );
   1226 			plane[1].SetNormal( -dir );
   1227 			plane[1].FitThroughPoint( p2 );
   1228 			plane[2].SetNormal( axis[1] );
   1229 			plane[2].FitThroughPoint( p1 );
   1230 			plane[3].SetNormal( axis[2] );
   1231 			plane[3].FitThroughPoint( p1 );
   1232 
   1233 			for ( j = 0; j < shards.Num(); j++ ) {
   1234 
   1235 				if ( i == j ) {
   1236 					continue;
   1237 				}
   1238 
   1239 				shard_t *shard2 = shards[j];
   1240 
   1241 				for ( l = 0; l < shard1->neighbours.Num(); l++ ) {
   1242 					if ( shard1->neighbours[l] == shard2 ) {
   1243 						break;
   1244 					}
   1245 				}
   1246 				if ( l < shard1->neighbours.Num() ) {
   1247 					continue;
   1248 				}
   1249 
   1250 				const idWinding &w2 = shard2->winding;
   1251 				const idVec3 &origin2 = shard2->clipModel->GetOrigin();
   1252 				const idMat3 &axis2 = shard2->clipModel->GetAxis();
   1253 
   1254 				for ( l = w2.GetNumPoints()-1; l >= 0; l-- ) {
   1255 					p1 = origin2 + w2[l].ToVec3() * axis2;
   1256 					p2 = origin2 + w2[(l-1+w2.GetNumPoints())%w2.GetNumPoints()].ToVec3() * axis2;
   1257 					if ( plane[0].Side( p2, 0.1f ) == SIDE_FRONT && plane[1].Side( p1, 0.1f ) == SIDE_FRONT ) {
   1258 						if ( plane[2].Side( p1, 0.1f ) == SIDE_ON && plane[3].Side( p1, 0.1f ) == SIDE_ON ) {
   1259 							if ( plane[2].Side( p2, 0.1f ) == SIDE_ON && plane[3].Side( p2, 0.1f ) == SIDE_ON ) {
   1260 								shard1->neighbours.Append( shard2 );
   1261 								shard1->edgeHasNeighbour[k] = true;
   1262 								shard2->neighbours.Append( shard1 );
   1263 								shard2->edgeHasNeighbour[(l-1+w2.GetNumPoints())%w2.GetNumPoints()] = true;
   1264 								break;
   1265 							}
   1266 						}
   1267 					}
   1268 				}
   1269 			}
   1270 		}
   1271 
   1272 		for ( k = 0; k < w1.GetNumPoints(); k++ ) {
   1273 			if ( !shard1->edgeHasNeighbour[k] ) {
   1274 				break;
   1275 			}
   1276 		}
   1277 		if ( k < w1.GetNumPoints() ) {
   1278 			shard1->atEdge = true;
   1279 		} else {
   1280 			shard1->atEdge = false;
   1281 		}
   1282 	}
   1283 }
   1284 
   1285 /*
   1286 ================
   1287 idBrittleFracture::Event_Activate
   1288 ================
   1289 */
   1290 void idBrittleFracture::Event_Activate( idEntity *activator ) {
   1291 	disableFracture = false;
   1292 	if ( health <= 0 ) {
   1293 		Break();
   1294 	}
   1295 }
   1296 
   1297 /*
   1298 ================
   1299 idBrittleFracture::Event_Touch
   1300 ================
   1301 */
   1302 void idBrittleFracture::Event_Touch( idEntity *other, trace_t *trace ) {
   1303 	idVec3 point, impulse;
   1304 
   1305 	// Let the server handle this, clients dont' predict it
   1306 	if ( common->IsClient() ) {
   1307 		return;
   1308 	}
   1309 
   1310 	if ( !IsBroken() ) {
   1311 		return;
   1312 	}
   1313 
   1314 	if ( trace->c.id < 0 || trace->c.id >= shards.Num() ) {
   1315 		return;
   1316 	}
   1317 
   1318 	point = shards[trace->c.id]->clipModel->GetOrigin();
   1319 	impulse = other->GetPhysics()->GetLinearVelocity() * other->GetPhysics()->GetMass();
   1320 
   1321 	Shatter( point, impulse, gameLocal.time );
   1322 }
   1323 
   1324 /*
   1325 ================
   1326 idBrittleFracture::ClientThink
   1327 ================
   1328 */
   1329 void idBrittleFracture::ClientThink( const int curTime, const float fraction, const bool predict ) {
   1330 
   1331 	// only think forward because the state is not synced through snapshots
   1332 	if ( !gameLocal.isNewFrame ) {
   1333 		return;
   1334 	}
   1335 
   1336 	Think();
   1337 }
   1338 
   1339 /*
   1340 ================
   1341 idBrittleFracture::ClientPredictionThink
   1342 ================
   1343 */
   1344 void idBrittleFracture::ClientPredictionThink() {
   1345 	// only think forward because the state is not synced through snapshots
   1346 	if ( !gameLocal.isNewFrame ) {
   1347 		return;
   1348 	}
   1349 
   1350 	Think();
   1351 }
   1352 
   1353 /*
   1354 ================
   1355 idBrittleFracture::ClientReceiveEvent
   1356 ================
   1357 */
   1358 bool idBrittleFracture::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
   1359 	idVec3 point, dir;
   1360 
   1361 	switch( event ) {
   1362 		case EVENT_PROJECT_DECAL: {
   1363 			point[0] = msg.ReadFloat();
   1364 			point[1] = msg.ReadFloat();
   1365 			point[2] = msg.ReadFloat();
   1366 			dir[0] = msg.ReadFloat();
   1367 			dir[1] = msg.ReadFloat();
   1368 			dir[2] = msg.ReadFloat();
   1369 			ProjectDecal( point, dir, time, NULL );
   1370 			return true;
   1371 		}
   1372 		case EVENT_SHATTER: {
   1373 			point[0] = msg.ReadFloat();
   1374 			point[1] = msg.ReadFloat();
   1375 			point[2] = msg.ReadFloat();
   1376 			dir[0] = msg.ReadFloat();
   1377 			dir[1] = msg.ReadFloat();
   1378 			dir[2] = msg.ReadFloat();
   1379 			Shatter( point, dir, time );
   1380 			return true;
   1381 		}
   1382 		default: {
   1383 			return idEntity::ClientReceiveEvent( event, time, msg );
   1384 		}
   1385 	}
   1386 }