2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 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.
14 Doom 3 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.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 #include "Game_local.h"
35 CLASS_DECLARATION( idEntity, idBrittleFracture )
36 EVENT( EV_Activate, idBrittleFracture::Event_Activate )
37 EVENT( EV_Touch, idBrittleFracture::Event_Touch )
40 const int SHARD_ALIVE_TIME = 5000;
41 const int SHARD_FADE_START = 2000;
43 static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_";
47 idBrittleFracture::idBrittleFracture
50 idBrittleFracture::idBrittleFracture( void ) {
55 maxShatterRadius = 0.0f;
56 minShatterRadius = 0.0f;
57 linearVelocityScale = 0.0f;
58 angularVelocityScale = 0.0f;
66 disableFracture = false;
68 lastRenderEntityUpdate = -1;
71 fl.networkSync = true;
74 isXraySurface = false;
80 idBrittleFracture::~idBrittleFracture
83 idBrittleFracture::~idBrittleFracture( void ) {
86 for ( i = 0; i < shards.Num(); i++ ) {
87 shards[i]->decals.DeleteContents( true );
91 // make sure the render entity is freed before the model is freed
93 renderModelManager->FreeModel( renderEntity.hModel );
98 idBrittleFracture::Save
101 void idBrittleFracture::Save( idSaveGame *savefile ) const {
104 savefile->WriteInt( health );
105 entityFlags_s flags = fl;
106 LittleBitField( &flags, sizeof( flags ) );
107 savefile->Write( &flags, sizeof( flags ) );
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 );
125 savefile->WriteBounds( bounds );
126 savefile->WriteBool( disableFracture );
128 savefile->WriteInt( lastRenderEntityUpdate );
129 savefile->WriteBool( changed );
131 savefile->WriteStaticObject( physicsObj );
133 savefile->WriteInt( shards.Num() );
134 for ( i = 0; i < shards.Num(); i++ ) {
135 savefile->WriteWinding( shards[i]->winding );
137 savefile->WriteInt( shards[i]->decals.Num() );
138 for ( j = 0; j < shards[i]->decals.Num(); j++ ) {
139 savefile->WriteWinding( *shards[i]->decals[j] );
142 savefile->WriteInt( shards[i]->neighbours.Num() );
143 for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) {
144 int index = shards.FindIndex(shards[i]->neighbours[j]);
146 savefile->WriteInt( index );
149 savefile->WriteInt( shards[i]->edgeHasNeighbour.Num() );
150 for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) {
151 savefile->WriteBool( shards[i]->edgeHasNeighbour[j] );
154 savefile->WriteInt( shards[i]->droppedTime );
155 savefile->WriteInt( shards[i]->islandNum );
156 savefile->WriteBool( shards[i]->atEdge );
157 savefile->WriteStaticObject( shards[i]->physicsObj );
161 savefile->WriteBool( isXraySurface );
167 idBrittleFracture::Restore
170 void idBrittleFracture::Restore( idRestoreGame *savefile ) {
173 renderEntity.hModel = renderModelManager->AllocModel();
174 renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName );
175 renderEntity.callback = idBrittleFracture::ModelCallback;
176 renderEntity.noShadow = true;
177 renderEntity.noSelfShadow = true;
178 renderEntity.noDynamicInteractions = false;
180 savefile->ReadInt( health );
181 savefile->Read( &fl, sizeof( fl ) );
182 LittleBitField( &fl, sizeof( fl ) );
185 savefile->ReadMaterial( material );
186 savefile->ReadMaterial( decalMaterial );
187 savefile->ReadFloat( decalSize );
188 savefile->ReadFloat( maxShardArea );
189 savefile->ReadFloat( maxShatterRadius );
190 savefile->ReadFloat( minShatterRadius );
191 savefile->ReadFloat( linearVelocityScale );
192 savefile->ReadFloat( angularVelocityScale );
193 savefile->ReadFloat( shardMass );
194 savefile->ReadFloat( density );
195 savefile->ReadFloat( friction );
196 savefile->ReadFloat( bouncyness );
197 savefile->ReadString( fxFracture );
200 savefile->ReadBounds(bounds);
201 savefile->ReadBool( disableFracture );
203 savefile->ReadInt( lastRenderEntityUpdate );
204 savefile->ReadBool( changed );
206 savefile->ReadStaticObject( physicsObj );
207 RestorePhysics( &physicsObj );
209 savefile->ReadInt( num );
210 shards.SetNum( num );
211 for ( i = 0; i < num; i++ ) {
212 shards[i] = new shard_t;
215 for ( i = 0; i < num; i++ ) {
216 savefile->ReadWinding( shards[i]->winding );
218 savefile->ReadInt( j );
219 shards[i]->decals.SetNum( j );
220 for ( j = 0; j < shards[i]->decals.Num(); j++ ) {
221 shards[i]->decals[j] = new idFixedWinding;
222 savefile->ReadWinding( *shards[i]->decals[j] );
225 savefile->ReadInt( j );
226 shards[i]->neighbours.SetNum( j );
227 for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) {
229 savefile->ReadInt( index );
231 shards[i]->neighbours[j] = shards[index];
234 savefile->ReadInt( j );
235 shards[i]->edgeHasNeighbour.SetNum( j );
236 for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) {
237 savefile->ReadBool( shards[i]->edgeHasNeighbour[j] );
240 savefile->ReadInt( shards[i]->droppedTime );
241 savefile->ReadInt( shards[i]->islandNum );
242 savefile->ReadBool( shards[i]->atEdge );
243 savefile->ReadStaticObject( shards[i]->physicsObj );
244 if ( shards[i]->droppedTime < 0 ) {
245 shards[i]->clipModel = physicsObj.GetClipModel( i );
247 shards[i]->clipModel = shards[i]->physicsObj.GetClipModel();
252 savefile->ReadBool( isXraySurface );
258 idBrittleFracture::Spawn
261 void idBrittleFracture::Spawn( void ) {
263 // get shard properties
264 decalMaterial = declManager->FindMaterial( spawnArgs.GetString( "mtr_decal" ) );
265 decalSize = spawnArgs.GetFloat( "decalSize", "40" );
266 maxShardArea = spawnArgs.GetFloat( "maxShardArea", "200" );
267 maxShardArea = idMath::ClampFloat( 100, 10000, maxShardArea );
268 maxShatterRadius = spawnArgs.GetFloat( "maxShatterRadius", "40" );
269 minShatterRadius = spawnArgs.GetFloat( "minShatterRadius", "10" );
270 linearVelocityScale = spawnArgs.GetFloat( "linearVelocityScale", "0.1" );
271 angularVelocityScale = spawnArgs.GetFloat( "angularVelocityScale", "40" );
272 fxFracture = spawnArgs.GetString( "fx" );
274 // get rigid body properties
275 shardMass = spawnArgs.GetFloat( "shardMass", "20" );
276 shardMass = idMath::ClampFloat( 0.001f, 1000.0f, shardMass );
277 spawnArgs.GetFloat( "density", "0.1", density );
278 density = idMath::ClampFloat( 0.001f, 1000.0f, density );
279 spawnArgs.GetFloat( "friction", "0.4", friction );
280 friction = idMath::ClampFloat( 0.0f, 1.0f, friction );
281 spawnArgs.GetFloat( "bouncyness", "0.01", bouncyness );
282 bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness );
284 disableFracture = spawnArgs.GetBool( "disableFracture", "0" );
285 health = spawnArgs.GetInt( "health", "40" );
286 fl.takedamage = true;
288 // FIXME: set "bleed" so idProjectile calls AddDamageEffect
289 spawnArgs.SetBool( "bleed", 1 );
292 // check for xray surface
294 const idRenderModel *model = renderEntity.hModel;
296 isXraySurface = false;
298 for ( int i = 0; i < model->NumSurfaces(); i++ ) {
299 const modelSurface_t *surf = model->Surface( i );
301 if ( idStr( surf->shader->GetName() ) == "textures/smf/window_scratch" ) {
302 isXraySurface = true;
309 CreateFractures( renderEntity.hModel );
313 renderEntity.hModel = renderModelManager->AllocModel();
314 renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName );
315 renderEntity.callback = idBrittleFracture::ModelCallback;
316 renderEntity.noShadow = true;
317 renderEntity.noSelfShadow = true;
318 renderEntity.noDynamicInteractions = false;
323 idBrittleFracture::AddShard
326 void idBrittleFracture::AddShard( idClipModel *clipModel, idFixedWinding &w ) {
327 shard_t *shard = new shard_t;
328 shard->clipModel = clipModel;
329 shard->droppedTime = -1;
331 shard->decals.Clear();
332 shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false );
333 shard->neighbours.Clear();
334 shard->atEdge = false;
335 shards.Append( shard );
340 idBrittleFracture::RemoveShard
343 void idBrittleFracture::RemoveShard( int index ) {
346 delete shards[index];
347 shards.RemoveIndex( index );
348 physicsObj.RemoveIndex( index );
350 for ( i = index; i < shards.Num(); i++ ) {
351 shards[i]->clipModel->SetId( i );
357 idBrittleFracture::UpdateRenderEntity
360 bool idBrittleFracture::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const {
361 int i, j, k, n, msec, numTris, numDecalTris;
364 srfTriangles_t *tris, *decalTris;
365 modelSurface_t surface;
370 // this may be triggered by a model trace or other non-view related source,
371 // to which we should look like an empty model
376 // don't regenerate it if it is current
377 if ( lastRenderEntityUpdate == gameLocal.time || !changed ) {
381 lastRenderEntityUpdate = gameLocal.time;
386 for ( i = 0; i < shards.Num(); i++ ) {
387 n = shards[i]->winding.GetNumPoints();
391 for ( k = 0; k < shards[i]->decals.Num(); k++ ) {
392 n = shards[i]->decals[k]->GetNumPoints();
394 numDecalTris += n - 2;
399 // FIXME: re-use model surfaces
400 renderEntity->hModel->InitEmpty( brittleFracture_SnapshotName );
402 // allocate triangle surfaces for the fractures and decals
403 tris = renderEntity->hModel->AllocSurfaceTriangles( numTris * 3, material->ShouldCreateBackSides() ? numTris * 6 : numTris * 3 );
404 decalTris = renderEntity->hModel->AllocSurfaceTriangles( numDecalTris * 3, decalMaterial->ShouldCreateBackSides() ? numDecalTris * 6 : numDecalTris * 3 );
406 for ( i = 0; i < shards.Num(); i++ ) {
407 const idVec3 &origin = shards[i]->clipModel->GetOrigin();
408 const idMat3 &axis = shards[i]->clipModel->GetAxis();
411 if ( shards[i]->droppedTime >= 0 ) {
412 msec = gameLocal.time - shards[i]->droppedTime - SHARD_FADE_START;
414 fade = 1.0f - (float) msec / ( SHARD_ALIVE_TIME - SHARD_FADE_START );
417 packedColor = PackColor( idVec4( renderEntity->shaderParms[ SHADERPARM_RED ] * fade,
418 renderEntity->shaderParms[ SHADERPARM_GREEN ] * fade,
419 renderEntity->shaderParms[ SHADERPARM_BLUE ] * fade,
422 const idWinding &winding = shards[i]->winding;
424 winding.GetPlane( plane );
425 tangents = ( plane.Normal() * axis ).ToMat3();
427 for ( j = 2; j < winding.GetNumPoints(); j++ ) {
429 v = &tris->verts[tris->numVerts++];
431 v->xyz = origin + winding[0].ToVec3() * axis;
432 v->st[0] = winding[0].s;
433 v->st[1] = winding[0].t;
434 v->normal = tangents[0];
435 v->tangents[0] = tangents[1];
436 v->tangents[1] = tangents[2];
437 v->SetColor( packedColor );
439 v = &tris->verts[tris->numVerts++];
441 v->xyz = origin + winding[j-1].ToVec3() * axis;
442 v->st[0] = winding[j-1].s;
443 v->st[1] = winding[j-1].t;
444 v->normal = tangents[0];
445 v->tangents[0] = tangents[1];
446 v->tangents[1] = tangents[2];
447 v->SetColor( packedColor );
449 v = &tris->verts[tris->numVerts++];
451 v->xyz = origin + winding[j].ToVec3() * axis;
452 v->st[0] = winding[j].s;
453 v->st[1] = winding[j].t;
454 v->normal = tangents[0];
455 v->tangents[0] = tangents[1];
456 v->tangents[1] = tangents[2];
457 v->SetColor( packedColor );
459 tris->indexes[tris->numIndexes++] = tris->numVerts - 3;
460 tris->indexes[tris->numIndexes++] = tris->numVerts - 2;
461 tris->indexes[tris->numIndexes++] = tris->numVerts - 1;
463 if ( material->ShouldCreateBackSides() ) {
465 tris->indexes[tris->numIndexes++] = tris->numVerts - 2;
466 tris->indexes[tris->numIndexes++] = tris->numVerts - 3;
467 tris->indexes[tris->numIndexes++] = tris->numVerts - 1;
471 for ( k = 0; k < shards[i]->decals.Num(); k++ ) {
472 const idWinding &decalWinding = *shards[i]->decals[k];
474 for ( j = 2; j < decalWinding.GetNumPoints(); j++ ) {
476 v = &decalTris->verts[decalTris->numVerts++];
478 v->xyz = origin + decalWinding[0].ToVec3() * axis;
479 v->st[0] = decalWinding[0].s;
480 v->st[1] = decalWinding[0].t;
481 v->normal = tangents[0];
482 v->tangents[0] = tangents[1];
483 v->tangents[1] = tangents[2];
484 v->SetColor( packedColor );
486 v = &decalTris->verts[decalTris->numVerts++];
488 v->xyz = origin + decalWinding[j-1].ToVec3() * axis;
489 v->st[0] = decalWinding[j-1].s;
490 v->st[1] = decalWinding[j-1].t;
491 v->normal = tangents[0];
492 v->tangents[0] = tangents[1];
493 v->tangents[1] = tangents[2];
494 v->SetColor( packedColor );
496 v = &decalTris->verts[decalTris->numVerts++];
498 v->xyz = origin + decalWinding[j].ToVec3() * axis;
499 v->st[0] = decalWinding[j].s;
500 v->st[1] = decalWinding[j].t;
501 v->normal = tangents[0];
502 v->tangents[0] = tangents[1];
503 v->tangents[1] = tangents[2];
504 v->SetColor( packedColor );
506 decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3;
507 decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2;
508 decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1;
510 if ( decalMaterial->ShouldCreateBackSides() ) {
512 decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2;
513 decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3;
514 decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1;
520 tris->tangentsCalculated = true;
521 decalTris->tangentsCalculated = true;
523 SIMDProcessor->MinMax( tris->bounds[0], tris->bounds[1], tris->verts, tris->numVerts );
524 SIMDProcessor->MinMax( decalTris->bounds[0], decalTris->bounds[1], decalTris->verts, decalTris->numVerts );
526 memset( &surface, 0, sizeof( surface ) );
527 surface.shader = material;
529 surface.geometry = tris;
530 renderEntity->hModel->AddSurface( surface );
532 memset( &surface, 0, sizeof( surface ) );
533 surface.shader = decalMaterial;
535 surface.geometry = decalTris;
536 renderEntity->hModel->AddSurface( surface );
543 idBrittleFracture::ModelCallback
546 bool idBrittleFracture::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
547 const idBrittleFracture *ent;
549 ent = static_cast<idBrittleFracture *>(gameLocal.entities[ renderEntity->entityNum ]);
551 gameLocal.Error( "idBrittleFracture::ModelCallback: callback with NULL game entity" );
554 return ent->UpdateRenderEntity( renderEntity, renderView );
559 idBrittleFracture::Present
562 void idBrittleFracture::Present() {
564 // don't present to the renderer if the entity hasn't changed
565 if ( !( thinkFlags & TH_UPDATEVISUALS ) ) {
568 BecomeInactive( TH_UPDATEVISUALS );
570 renderEntity.bounds = bounds;
571 renderEntity.origin.Zero();
572 renderEntity.axis.Identity();
574 // force an update because the bounds/origin/axis may stay the same while the model changes
575 renderEntity.forceUpdate = true;
577 // add to refresh list
578 if ( modelDefHandle == -1 ) {
579 modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
581 gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity );
589 idBrittleFracture::Think
592 void idBrittleFracture::Think( void ) {
593 int i, startTime, endTime, droppedTime;
595 bool atRest = true, fading = false;
597 // remove overdue shards
598 for ( i = 0; i < shards.Num(); i++ ) {
599 droppedTime = shards[i]->droppedTime;
600 if ( droppedTime != -1 ) {
601 if ( gameLocal.time - droppedTime > SHARD_ALIVE_TIME ) {
609 // remove the entity when nothing is visible
610 if ( !shards.Num() ) {
611 PostEventMS( &EV_Remove, 0 );
615 if ( thinkFlags & TH_PHYSICS ) {
617 startTime = gameLocal.previousTime;
618 endTime = gameLocal.time;
620 // run physics on shards
621 for ( i = 0; i < shards.Num(); i++ ) {
624 if ( shard->droppedTime == -1 ) {
628 shard->physicsObj.Evaluate( endTime - startTime, endTime );
630 if ( !shard->physicsObj.IsAtRest() ) {
636 BecomeInactive( TH_PHYSICS );
638 BecomeActive( TH_PHYSICS );
642 if ( !atRest || bounds.IsCleared() ) {
644 for ( i = 0; i < shards.Num(); i++ ) {
645 bounds.AddBounds( shards[i]->clipModel->GetAbsBounds() );
650 BecomeActive( TH_UPDATEVISUALS | TH_THINK );
652 BecomeInactive( TH_THINK );
661 idBrittleFracture::ApplyImpulse
664 void idBrittleFracture::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
666 if ( id < 0 || id >= shards.Num() ) {
670 if ( shards[id]->droppedTime != -1 ) {
671 shards[id]->physicsObj.ApplyImpulse( 0, point, impulse );
672 } else if ( health <= 0 && !disableFracture ) {
673 Shatter( point, impulse, gameLocal.time );
679 idBrittleFracture::AddForce
682 void idBrittleFracture::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) {
684 if ( id < 0 || id >= shards.Num() ) {
688 if ( shards[id]->droppedTime != -1 ) {
689 shards[id]->physicsObj.AddForce( 0, point, force );
690 } else if ( health <= 0 && !disableFracture ) {
691 Shatter( point, force, gameLocal.time );
697 idBrittleFracture::ProjectDecal
700 void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) {
701 int i, j, bits, clipBits;
703 idVec2 st[MAX_POINTS_ON_WINDING];
705 idMat3 axis, axistemp;
706 idPlane textureAxis[2];
708 if ( gameLocal.isServer ) {
710 byte msgBuf[MAX_EVENT_PARAM_SIZE];
712 msg.Init( msgBuf, sizeof( msgBuf ) );
714 msg.WriteFloat( point[0] );
715 msg.WriteFloat( point[1] );
716 msg.WriteFloat( point[2] );
717 msg.WriteFloat( dir[0] );
718 msg.WriteFloat( dir[1] );
719 msg.WriteFloat( dir[2] );
720 ServerSendEvent( EVENT_PROJECT_DECAL, &msg, true, -1 );
723 if ( time >= gameLocal.time ) {
724 // try to get the sound from the damage def
725 const idDeclEntityDef *damageDef = NULL;
726 const idSoundShader *sndShader = NULL;
727 if ( damageDefName ) {
728 damageDef = gameLocal.FindEntityDef( damageDefName, false );
730 sndShader = declManager->FindSound( damageDef->dict.GetString( "snd_shatter", "" ) );
735 StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL );
737 StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL );
741 a = gameLocal.random.RandomFloat() * idMath::TWO_PI;
747 axis[2].NormalVectors( axistemp[0], axistemp[1] );
748 axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s;
749 axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c;
751 textureAxis[0] = axis[0] * ( 1.0f / decalSize );
752 textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f;
754 textureAxis[1] = axis[1] * ( 1.0f / decalSize );
755 textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f;
757 for ( i = 0; i < shards.Num(); i++ ) {
758 idFixedWinding &winding = shards[i]->winding;
759 origin = shards[i]->clipModel->GetOrigin();
760 axis = shards[i]->clipModel->GetAxis();
764 for ( j = 0; j < winding.GetNumPoints(); j++ ) {
765 idVec3 p = origin + winding[j].ToVec3() * axis;
767 st[j].x = d0 = textureAxis[0].Distance( p );
768 st[j].y = d1 = textureAxis[1].Distance( p );
770 bits = FLOATSIGNBITSET( d0 );
772 bits |= FLOATSIGNBITSET( d1 ) << 2;
774 bits |= FLOATSIGNBITSET( d0 ) << 1;
775 bits |= FLOATSIGNBITSET( d1 ) << 3;
784 idFixedWinding *decal = new idFixedWinding;
785 shards[i]->decals.Append( decal );
787 decal->SetNumPoints( winding.GetNumPoints() );
788 for ( j = 0; j < winding.GetNumPoints(); j++ ) {
789 (*decal)[j].ToVec3() = winding[j].ToVec3();
790 (*decal)[j].s = st[j].x;
791 (*decal)[j].t = st[j].y;
795 BecomeActive( TH_UPDATEVISUALS );
800 idBrittleFracture::DropShard
803 void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) {
804 int i, j, clipModelId;
810 // don't display decals on dropped shards
811 shard->decals.DeleteContents( true );
813 // remove neighbour pointers of neighbours pointing to this shard
814 for ( i = 0; i < shard->neighbours.Num(); i++ ) {
815 neighbour = shard->neighbours[i];
816 for ( j = 0; j < neighbour->neighbours.Num(); j++ ) {
817 if ( neighbour->neighbours[j] == shard ) {
818 neighbour->neighbours.RemoveIndex( j );
824 // remove neighbour pointers
825 shard->neighbours.Clear();
827 // remove the clip model from the static physics object
828 clipModelId = shard->clipModel->GetId();
829 physicsObj.SetClipModel( NULL, 1.0f, clipModelId, false );
831 origin = shard->clipModel->GetOrigin();
832 axis = shard->clipModel->GetAxis();
834 // set the dropped time for fading
835 shard->droppedTime = time;
837 dir2 = origin - point;
838 dist = dir2.Normalize();
839 f = dist > maxShatterRadius ? 1.0f : idMath::Sqrt( dist - minShatterRadius ) * ( 1.0f / idMath::Sqrt( maxShatterRadius - minShatterRadius ) );
842 shard->physicsObj.SetSelf( this );
843 shard->physicsObj.SetClipModel( shard->clipModel, density );
844 shard->physicsObj.SetMass( shardMass );
845 shard->physicsObj.SetOrigin( origin );
846 shard->physicsObj.SetAxis( axis );
847 shard->physicsObj.SetBouncyness( bouncyness );
848 shard->physicsObj.SetFriction( 0.6f, 0.6f, friction );
849 shard->physicsObj.SetGravity( gameLocal.GetGravity() );
850 shard->physicsObj.SetContents( CONTENTS_RENDERMODEL );
851 shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP );
852 shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir );
853 shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) );
855 shard->clipModel->SetId( clipModelId );
857 BecomeActive( TH_PHYSICS );
862 idBrittleFracture::Shatter
865 void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, const int time ) {
871 if ( gameLocal.isServer ) {
873 byte msgBuf[MAX_EVENT_PARAM_SIZE];
875 msg.Init( msgBuf, sizeof( msgBuf ) );
877 msg.WriteFloat( point[0] );
878 msg.WriteFloat( point[1] );
879 msg.WriteFloat( point[2] );
880 msg.WriteFloat( impulse[0] );
881 msg.WriteFloat( impulse[1] );
882 msg.WriteFloat( impulse[2] );
883 ServerSendEvent( EVENT_SHATTER, &msg, true, -1 );
886 if ( time > ( gameLocal.time - SHARD_ALIVE_TIME ) ) {
887 StartSound( "snd_shatter", SND_CHANNEL_ANY, 0, false, NULL );
894 if ( fxFracture.Length() ) {
895 idEntityFx::StartFx( fxFracture, &point, &GetPhysics()->GetAxis(), this, true );
901 for ( i = 0; i < shards.Num(); i++ ) {
904 if ( shard->droppedTime != -1 ) {
908 if ( ( shard->clipModel->GetOrigin() - point ).LengthSqr() > Square( maxShatterRadius ) ) {
912 DropShard( shard, point, dir, m, time );
915 DropFloatingIslands( point, impulse, time );
920 idBrittleFracture::DropFloatingIslands
923 void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) {
924 int i, j, numIslands;
925 int queueStart, queueEnd;
926 shard_t *curShard, *nextShard, **queue;
934 queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) );
935 for ( i = 0; i < shards.Num(); i++ ) {
936 shards[i]->islandNum = 0;
939 for ( i = 0; i < shards.Num(); i++ ) {
941 if ( shards[i]->droppedTime != -1 ) {
945 if ( shards[i]->islandNum ) {
951 queue[0] = shards[i];
952 shards[i]->islandNum = numIslands+1;
955 if ( shards[i]->atEdge ) {
959 for ( curShard = queue[queueStart]; queueStart < queueEnd; curShard = queue[++queueStart] ) {
961 for ( j = 0; j < curShard->neighbours.Num(); j++ ) {
963 nextShard = curShard->neighbours[j];
965 if ( nextShard->droppedTime != -1 ) {
969 if ( nextShard->islandNum ) {
973 queue[queueEnd++] = nextShard;
974 nextShard->islandNum = numIslands+1;
976 if ( nextShard->atEdge ) {
983 // if the island is not connected to the world at any edges
984 if ( !touchesEdge ) {
985 for ( j = 0; j < queueEnd; j++ ) {
986 DropShard( queue[j], point, dir, 0.0f, time );
994 idBrittleFracture::Break
997 void idBrittleFracture::Break( void ) {
998 fl.takedamage = false;
999 physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER );
1004 idBrittleFracture::IsBroken
1007 bool idBrittleFracture::IsBroken( void ) const {
1008 return ( fl.takedamage == false );
1013 idBrittleFracture::Killed
1016 void idBrittleFracture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
1017 if ( !disableFracture ) {
1018 ActivateTargets( this );
1025 idBrittleFracture::AddDamageEffect
1028 void idBrittleFracture::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ) {
1029 if ( !disableFracture ) {
1030 ProjectDecal( collision.c.point, collision.c.normal, gameLocal.time, damageDefName );
1036 idBrittleFracture::Fracture_r
1039 void idBrittleFracture::Fracture_r( idFixedWinding &w ) {
1040 int i, j, bestPlane;
1041 float a, c, s, dist, bestDist;
1043 idPlane windingPlane, splitPlanes[2];
1044 idMat3 axis, axistemp;
1045 idFixedWinding back;
1047 idClipModel *clipModel;
1050 origin = w.GetCenter();
1051 w.GetPlane( windingPlane );
1053 if ( w.GetArea() < maxShardArea ) {
1057 // randomly create a split plane
1058 axis[2] = windingPlane.Normal();
1060 if ( isXraySurface ) {
1061 a = idMath::TWO_PI / 2.f;
1064 a = gameLocal.random.RandomFloat() * idMath::TWO_PI;
1067 a = gameLocal.random.RandomFloat() * idMath::TWO_PI;
1071 axis[2].NormalVectors( axistemp[0], axistemp[1] );
1072 axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s;
1073 axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c;
1075 // get the best split plane
1078 for ( i = 0; i < 2; i++ ) {
1079 splitPlanes[i].SetNormal( axis[i] );
1080 splitPlanes[i].FitThroughPoint( origin );
1081 for ( j = 0; j < w.GetNumPoints(); j++ ) {
1082 dist = splitPlanes[i].Distance( w[j].ToVec3() );
1083 if ( dist > bestDist ) {
1090 // split the winding
1091 if ( !w.Split( &back, splitPlanes[bestPlane] ) ) {
1095 // recursively create shards for the back winding
1099 // translate the winding to it's center
1100 origin = w.GetCenter();
1101 for ( j = 0; j < w.GetNumPoints(); j++ ) {
1102 w[j].ToVec3() -= origin;
1104 w.RemoveEqualPoints();
1106 trm.SetupPolygon( w );
1107 trm.Shrink( CM_CLIP_EPSILON );
1108 clipModel = new idClipModel( trm );
1110 physicsObj.SetClipModel( clipModel, 1.0f, shards.Num() );
1111 physicsObj.SetOrigin( GetPhysics()->GetOrigin() + origin, shards.Num() );
1112 physicsObj.SetAxis( GetPhysics()->GetAxis(), shards.Num() );
1114 AddShard( clipModel, w );
1119 idBrittleFracture::CreateFractures
1122 void idBrittleFracture::CreateFractures( const idRenderModel *renderModel ) {
1124 const modelSurface_t *surf;
1125 const idDrawVert *v;
1128 if ( !renderModel ) {
1132 physicsObj.SetSelf( this );
1133 physicsObj.SetOrigin( GetPhysics()->GetOrigin(), 0 );
1134 physicsObj.SetAxis( GetPhysics()->GetAxis(), 0 );
1137 if ( isXraySurface ) {
1138 for ( i = 0; i < 1 /*renderModel->NumSurfaces()*/; i++ ) {
1139 surf = renderModel->Surface( i );
1140 material = surf->shader;
1145 v = &surf->geometry->verts[k];
1146 w.AddPoint( v->xyz );
1151 v = &surf->geometry->verts[k];
1152 w.AddPoint( v->xyz );
1157 v = &surf->geometry->verts[k];
1158 w.AddPoint( v->xyz );
1163 v = &surf->geometry->verts[k];
1164 w.AddPoint( v->xyz );
1173 for ( i = 0; i < 1 /*renderModel->NumSurfaces()*/; i++ ) {
1174 surf = renderModel->Surface( i );
1175 material = surf->shader;
1177 for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) {
1179 for ( k = 0; k < 3; k++ ) {
1180 v = &surf->geometry->verts[ surf->geometry->indexes[ j + 2 - k ] ];
1181 w.AddPoint( v->xyz );
1190 for ( i = 0; i < 1 /*renderModel->NumSurfaces()*/; i++ ) {
1191 surf = renderModel->Surface( i );
1192 material = surf->shader;
1194 for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) {
1196 for ( k = 0; k < 3; k++ ) {
1197 v = &surf->geometry->verts[ surf->geometry->indexes[ j + 2 - k ] ];
1198 w.AddPoint( v->xyz );
1207 physicsObj.SetContents( material->GetContentFlags() );
1208 SetPhysics( &physicsObj );
1213 idBrittleFracture::FindNeighbours
1216 void idBrittleFracture::FindNeighbours( void ) {
1222 for ( i = 0; i < shards.Num(); i++ ) {
1224 shard_t *shard1 = shards[i];
1225 const idWinding &w1 = shard1->winding;
1226 const idVec3 &origin1 = shard1->clipModel->GetOrigin();
1227 const idMat3 &axis1 = shard1->clipModel->GetAxis();
1229 for ( k = 0; k < w1.GetNumPoints(); k++ ) {
1231 p1 = origin1 + w1[k].ToVec3() * axis1;
1232 p2 = origin1 + w1[(k+1)%w1.GetNumPoints()].ToVec3() * axis1;
1235 axis = dir.ToMat3();
1237 plane[0].SetNormal( dir );
1238 plane[0].FitThroughPoint( p1 );
1239 plane[1].SetNormal( -dir );
1240 plane[1].FitThroughPoint( p2 );
1241 plane[2].SetNormal( axis[1] );
1242 plane[2].FitThroughPoint( p1 );
1243 plane[3].SetNormal( axis[2] );
1244 plane[3].FitThroughPoint( p1 );
1246 for ( j = 0; j < shards.Num(); j++ ) {
1252 shard_t *shard2 = shards[j];
1254 for ( l = 0; l < shard1->neighbours.Num(); l++ ) {
1255 if ( shard1->neighbours[l] == shard2 ) {
1259 if ( l < shard1->neighbours.Num() ) {
1263 const idWinding &w2 = shard2->winding;
1264 const idVec3 &origin2 = shard2->clipModel->GetOrigin();
1265 const idMat3 &axis2 = shard2->clipModel->GetAxis();
1267 for ( l = w2.GetNumPoints()-1; l >= 0; l-- ) {
1268 p1 = origin2 + w2[l].ToVec3() * axis2;
1269 p2 = origin2 + w2[(l-1+w2.GetNumPoints())%w2.GetNumPoints()].ToVec3() * axis2;
1270 if ( plane[0].Side( p2, 0.1f ) == SIDE_FRONT && plane[1].Side( p1, 0.1f ) == SIDE_FRONT ) {
1271 if ( plane[2].Side( p1, 0.1f ) == SIDE_ON && plane[3].Side( p1, 0.1f ) == SIDE_ON ) {
1272 if ( plane[2].Side( p2, 0.1f ) == SIDE_ON && plane[3].Side( p2, 0.1f ) == SIDE_ON ) {
1273 shard1->neighbours.Append( shard2 );
1274 shard1->edgeHasNeighbour[k] = true;
1275 shard2->neighbours.Append( shard1 );
1276 shard2->edgeHasNeighbour[(l-1+w2.GetNumPoints())%w2.GetNumPoints()] = true;
1285 for ( k = 0; k < w1.GetNumPoints(); k++ ) {
1286 if ( !shard1->edgeHasNeighbour[k] ) {
1290 if ( k < w1.GetNumPoints() ) {
1291 shard1->atEdge = true;
1293 shard1->atEdge = false;
1300 idBrittleFracture::Event_Activate
1303 void idBrittleFracture::Event_Activate( idEntity *activator ) {
1304 disableFracture = false;
1305 if ( health <= 0 ) {
1312 idBrittleFracture::Event_Touch
1315 void idBrittleFracture::Event_Touch( idEntity *other, trace_t *trace ) {
1316 idVec3 point, impulse;
1318 if ( !IsBroken() ) {
1322 if ( trace->c.id < 0 || trace->c.id >= shards.Num() ) {
1326 point = shards[trace->c.id]->clipModel->GetOrigin();
1327 impulse = other->GetPhysics()->GetLinearVelocity() * other->GetPhysics()->GetMass();
1329 Shatter( point, impulse, gameLocal.time );
1334 idBrittleFracture::ClientPredictionThink
1337 void idBrittleFracture::ClientPredictionThink( void ) {
1338 // only think forward because the state is not synced through snapshots
1339 if ( !gameLocal.isNewFrame ) {
1348 idBrittleFracture::ClientReceiveEvent
1351 bool idBrittleFracture::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
1355 case EVENT_PROJECT_DECAL: {
1356 point[0] = msg.ReadFloat();
1357 point[1] = msg.ReadFloat();
1358 point[2] = msg.ReadFloat();
1359 dir[0] = msg.ReadFloat();
1360 dir[1] = msg.ReadFloat();
1361 dir[2] = msg.ReadFloat();
1362 ProjectDecal( point, dir, time, NULL );
1365 case EVENT_SHATTER: {
1366 point[0] = msg.ReadFloat();
1367 point[1] = msg.ReadFloat();
1368 point[2] = msg.ReadFloat();
1369 dir[0] = msg.ReadFloat();
1370 dir[1] = msg.ReadFloat();
1371 dir[2] = msg.ReadFloat();
1372 Shatter( point, dir, time );
1376 return idEntity::ClientReceiveEvent( event, time, msg );