]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/d3xp/physics/Physics_Monster.cpp
hello world
[icculus/iodoom3.git] / neo / d3xp / physics / Physics_Monster.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
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.
13
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.
18
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/>.
21
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.
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 CLASS_DECLARATION( idPhysics_Actor, idPhysics_Monster )
35 END_CLASS
36
37 const float OVERCLIP = 1.001f;
38
39 /*
40 =====================
41 idPhysics_Monster::CheckGround
42 =====================
43 */
44 void idPhysics_Monster::CheckGround( monsterPState_t &state ) {
45         trace_t groundTrace;
46         idVec3 down;
47
48         if ( gravityNormal == vec3_zero ) {
49                 state.onGround = false;
50                 groundEntityPtr = NULL;
51                 return;
52         }
53
54         down = state.origin + gravityNormal * CONTACT_EPSILON;
55         gameLocal.clip.Translation( groundTrace, state.origin, down, clipModel, clipModel->GetAxis(), clipMask, self );
56
57         if ( groundTrace.fraction == 1.0f ) {
58                 state.onGround = false;
59                 groundEntityPtr = NULL;
60                 return;
61         }
62
63         groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ];
64
65         if ( ( groundTrace.c.normal * -gravityNormal ) < minFloorCosine ) {
66                 state.onGround = false;
67                 return;
68         }
69
70         state.onGround = true;
71
72         // let the entity know about the collision
73         self->Collide( groundTrace, state.velocity );
74
75         // apply impact to a non world floor entity
76         if ( groundTrace.c.entityNum != ENTITYNUM_WORLD && groundEntityPtr.GetEntity() ) {
77                 impactInfo_t info;
78                 groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info );
79                 if ( info.invMass != 0.0f ) {
80                         groundEntityPtr.GetEntity()->ApplyImpulse( self, 0, groundTrace.c.point, state.velocity  / ( info.invMass * 10.0f ) );
81                 }
82         }
83 }
84
85 /*
86 =====================
87 idPhysics_Monster::SlideMove
88 =====================
89 */
90 monsterMoveResult_t idPhysics_Monster::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) {
91         int i;
92         trace_t tr;
93         idVec3 move;
94
95         blockingEntity = NULL;
96         move = delta;
97         for( i = 0; i < 3; i++ ) {
98                 gameLocal.clip.Translation( tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self );
99
100                 start = tr.endpos;
101
102                 if ( tr.fraction == 1.0f ) {
103                         if ( i > 0 ) {
104                                 return MM_SLIDING;
105                         }
106                         return MM_OK;
107                 }
108
109                 if ( tr.c.entityNum != ENTITYNUM_NONE ) {
110                         blockingEntity = gameLocal.entities[ tr.c.entityNum ];
111                 } 
112                 
113                 // clip the movement delta and velocity
114                 move.ProjectOntoPlane( tr.c.normal, OVERCLIP );
115                 velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP );
116         }
117
118         return MM_BLOCKED;
119 }
120
121 /*
122 =====================
123 idPhysics_Monster::StepMove
124
125   move start into the delta direction
126   the velocity is clipped conform any collisions
127 =====================
128 */
129 monsterMoveResult_t idPhysics_Monster::StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) {
130         trace_t tr;
131         idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel;
132         monsterMoveResult_t result1, result2;
133         float   stepdist;
134         float   nostepdist;
135
136         if ( delta == vec3_origin ) {
137                 return MM_OK;
138         }
139
140         // try to move without stepping up
141         noStepPos = start;
142         noStepVel = velocity;
143         result1 = SlideMove( noStepPos, noStepVel, delta );
144         if ( result1 == MM_OK ) {
145                 velocity = noStepVel;
146                 if ( gravityNormal == vec3_zero ) {
147                         start = noStepPos;
148                         return MM_OK;
149                 }
150
151                 // try to step down so that we walk down slopes and stairs at a normal rate
152                 down = noStepPos + gravityNormal * maxStepHeight;
153                 gameLocal.clip.Translation( tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self );
154                 if ( tr.fraction < 1.0f ) {
155                         start = tr.endpos;
156                         return MM_STEPPED;
157                 } else {
158                         start = noStepPos;
159                         return MM_OK;
160                 }
161         }
162
163         if ( blockingEntity && blockingEntity->IsType( idActor::Type ) ) {
164                 // try to step down in case walking into an actor while going down steps
165                 down = noStepPos + gravityNormal * maxStepHeight;
166                 gameLocal.clip.Translation( tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self );
167                 start = tr.endpos;
168                 velocity = noStepVel;
169                 return MM_BLOCKED;
170         }
171
172         if ( gravityNormal == vec3_zero ) {
173                 return result1;
174         }
175
176         // try to step up
177         up = start - gravityNormal * maxStepHeight;
178         gameLocal.clip.Translation( tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self );
179         if ( tr.fraction == 0.0f ) {
180                 start = noStepPos;
181                 velocity = noStepVel;
182                 return result1;
183         }
184
185         // try to move at the stepped up position
186         stepPos = tr.endpos;
187         stepVel = velocity;
188         result2 = SlideMove( stepPos, stepVel, delta );
189         if ( result2 == MM_BLOCKED ) {
190                 start = noStepPos;
191                 velocity = noStepVel;
192                 return result1;
193         }
194
195         // step down again
196         down = stepPos + gravityNormal * maxStepHeight;
197         gameLocal.clip.Translation( tr, stepPos, down, clipModel, clipModel->GetAxis(), clipMask, self );
198         stepPos = tr.endpos;
199
200         // if the move is further without stepping up, or the slope is too steap, don't step up
201         nostepdist = ( noStepPos - start ).LengthSqr();
202         stepdist = ( stepPos - start ).LengthSqr();
203         if ( ( nostepdist >= stepdist ) || ( ( tr.c.normal * -gravityNormal ) < minFloorCosine ) ) {
204                 start = noStepPos;
205                 velocity = noStepVel;
206                 return MM_SLIDING;
207         }
208
209         start = stepPos;
210         velocity = stepVel;
211
212         return MM_STEPPED;
213 }
214
215 /*
216 ================
217 idPhysics_Monster::Activate
218 ================
219 */
220 void idPhysics_Monster::Activate( void ) {
221         current.atRest = -1;
222         self->BecomeActive( TH_PHYSICS );
223 }
224
225 /*
226 ================
227 idPhysics_Monster::Rest
228 ================
229 */
230 void idPhysics_Monster::Rest( void ) {
231         current.atRest = gameLocal.time;
232         current.velocity.Zero();
233         self->BecomeInactive( TH_PHYSICS );
234 }
235
236 /*
237 ================
238 idPhysics_Monster::PutToRest
239 ================
240 */
241 void idPhysics_Monster::PutToRest( void ) {
242         Rest();
243 }
244
245 /*
246 ================
247 idPhysics_Monster::idPhysics_Monster
248 ================
249 */
250 idPhysics_Monster::idPhysics_Monster( void ) {
251
252         memset( &current, 0, sizeof( current ) );
253         current.atRest = -1;
254         saved = current;
255         
256         delta.Zero();
257         maxStepHeight = 18.0f;
258         minFloorCosine = 0.7f;
259         moveResult = MM_OK;
260         forceDeltaMove = false;
261         fly = false;
262         useVelocityMove = false;
263         noImpact = false;
264         blockingEntity = NULL;
265 }
266
267 /*
268 ================
269 idPhysics_Monster_SavePState
270 ================
271 */
272 void idPhysics_Monster_SavePState( idSaveGame *savefile, const monsterPState_t &state ) {
273         savefile->WriteVec3( state.origin );
274         savefile->WriteVec3( state.velocity );
275         savefile->WriteVec3( state.localOrigin );
276         savefile->WriteVec3( state.pushVelocity );
277         savefile->WriteBool( state.onGround );
278         savefile->WriteInt( state.atRest );
279 }
280
281 /*
282 ================
283 idPhysics_Monster_RestorePState
284 ================
285 */
286 void idPhysics_Monster_RestorePState( idRestoreGame *savefile, monsterPState_t &state ) {
287         savefile->ReadVec3( state.origin );
288         savefile->ReadVec3( state.velocity );
289         savefile->ReadVec3( state.localOrigin );
290         savefile->ReadVec3( state.pushVelocity );
291         savefile->ReadBool( state.onGround );
292         savefile->ReadInt( state.atRest );
293 }
294
295 /*
296 ================
297 idPhysics_Monster::Save
298 ================
299 */
300 void idPhysics_Monster::Save( idSaveGame *savefile ) const {
301
302         idPhysics_Monster_SavePState( savefile, current );
303         idPhysics_Monster_SavePState( savefile, saved );
304
305         savefile->WriteFloat( maxStepHeight );
306         savefile->WriteFloat( minFloorCosine );
307         savefile->WriteVec3( delta );
308
309         savefile->WriteBool( forceDeltaMove );
310         savefile->WriteBool( fly );
311         savefile->WriteBool( useVelocityMove );
312         savefile->WriteBool( noImpact );
313         
314         savefile->WriteInt( (int)moveResult );
315         savefile->WriteObject( blockingEntity );
316 }
317
318 /*
319 ================
320 idPhysics_Monster::Restore
321 ================
322 */
323 void idPhysics_Monster::Restore( idRestoreGame *savefile ) {
324
325         idPhysics_Monster_RestorePState( savefile, current );
326         idPhysics_Monster_RestorePState( savefile, saved );
327
328         savefile->ReadFloat( maxStepHeight );
329         savefile->ReadFloat( minFloorCosine );
330         savefile->ReadVec3( delta );
331
332         savefile->ReadBool( forceDeltaMove );
333         savefile->ReadBool( fly );
334         savefile->ReadBool( useVelocityMove );
335         savefile->ReadBool( noImpact );
336
337         savefile->ReadInt( (int &)moveResult );
338         savefile->ReadObject( reinterpret_cast<idClass *&>( blockingEntity ) );
339 }
340
341 /*
342 ================
343 idPhysics_Monster::SetDelta
344 ================
345 */
346 void idPhysics_Monster::SetDelta( const idVec3 &d ) {
347         delta = d;
348         if ( delta != vec3_origin ) {
349                 Activate();
350         }
351 }
352
353 /*
354 ================
355 idPhysics_Monster::SetMaxStepHeight
356 ================
357 */
358 void idPhysics_Monster::SetMaxStepHeight( const float newMaxStepHeight ) {
359         maxStepHeight = newMaxStepHeight;
360 }
361
362 /*
363 ================
364 idPhysics_Monster::GetMaxStepHeight
365 ================
366 */
367 float idPhysics_Monster::GetMaxStepHeight( void ) const {
368         return maxStepHeight;
369 }
370
371 /*
372 ================
373 idPhysics_Monster::OnGround
374 ================
375 */
376 bool idPhysics_Monster::OnGround( void ) const {
377         return current.onGround;
378 }
379
380 /*
381 ================
382 idPhysics_Monster::GetSlideMoveEntity
383 ================
384 */
385 idEntity *idPhysics_Monster::GetSlideMoveEntity( void ) const {
386         return blockingEntity;
387 }
388
389 /*
390 ================
391 idPhysics_Monster::GetMoveResult
392 ================
393 */
394 monsterMoveResult_t idPhysics_Monster::GetMoveResult( void ) const {
395         return moveResult;
396 }
397
398 /*
399 ================
400 idPhysics_Monster::ForceDeltaMove
401 ================
402 */
403 void idPhysics_Monster::ForceDeltaMove( bool force ) {
404         forceDeltaMove = force;
405 }
406
407 /*
408 ================
409 idPhysics_Monster::UseFlyMove
410 ================
411 */
412 void idPhysics_Monster::UseFlyMove( bool force ) {
413         fly = force;
414 }
415
416 /*
417 ================
418 idPhysics_Monster::UseVelocityMove
419 ================
420 */
421 void idPhysics_Monster::UseVelocityMove( bool force ) {
422         useVelocityMove = force;
423 }
424
425 /*
426 ================
427 idPhysics_Monster::EnableImpact
428 ================
429 */
430 void idPhysics_Monster::EnableImpact( void ) {
431         noImpact = false;
432 }
433
434 /*
435 ================
436 idPhysics_Monster::DisableImpact
437 ================
438 */
439 void idPhysics_Monster::DisableImpact( void ) {
440         noImpact = true;
441 }
442
443 /*
444 ================
445 idPhysics_Monster::Evaluate
446 ================
447 */
448 bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) {
449         idVec3 masterOrigin, oldOrigin;
450         idMat3 masterAxis;
451         float timeStep;
452
453         timeStep = MS2SEC( timeStepMSec );
454
455         moveResult = MM_OK;
456         blockingEntity = NULL;
457         oldOrigin = current.origin;
458
459         // if bound to a master
460         if ( masterEntity ) {
461                 self->GetMasterPosition( masterOrigin, masterAxis );
462                 current.origin = masterOrigin + current.localOrigin * masterAxis;
463                 clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
464                 current.velocity = ( current.origin - oldOrigin ) / timeStep;
465                 masterDeltaYaw = masterYaw;
466                 masterYaw = masterAxis[0].ToYaw();
467                 masterDeltaYaw = masterYaw - masterDeltaYaw;
468                 return true;
469         }
470
471         // if the monster is at rest
472         if ( current.atRest >= 0 ) {
473                 return false;
474         }
475
476         ActivateContactEntities();
477
478         // move the monster velocity into the frame of a pusher
479         current.velocity -= current.pushVelocity;
480
481         clipModel->Unlink();
482
483         // check if on the ground
484         idPhysics_Monster::CheckGround( current );
485
486         // if not on the ground or moving upwards
487         float upspeed;
488         if ( gravityNormal != vec3_zero ) {
489                 upspeed = -( current.velocity * gravityNormal );
490         } else {
491                 upspeed = current.velocity.z;
492         }
493         if ( fly || ( !forceDeltaMove && ( !current.onGround || upspeed > 1.0f ) ) ) {
494                 if ( upspeed < 0.0f ) {
495                         moveResult = MM_FALLING;
496                 }
497                 else {
498                         current.onGround = false;
499                         moveResult = MM_OK;
500                 }
501                 delta = current.velocity * timeStep;
502                 if ( delta != vec3_origin ) {
503                         moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta );
504             delta.Zero();
505                 }
506
507                 if ( !fly ) {
508                         current.velocity += gravityVector * timeStep;
509                 }
510         } else {
511                 if ( useVelocityMove ) {
512                         delta = current.velocity * timeStep;
513                 } else {
514                         current.velocity = delta / timeStep;
515                 }
516
517                 current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal;
518
519                 if ( delta == vec3_origin ) {
520                         Rest();
521                 } else {
522                         // try moving into the desired direction
523                         moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta );
524                         delta.Zero();
525                 }
526         }
527
528         clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
529
530         // get all the ground contacts
531         EvaluateContacts();
532
533         // move the monster velocity back into the world frame
534         current.velocity += current.pushVelocity;
535         current.pushVelocity.Zero();
536
537         if ( IsOutsideWorld() ) {
538                 gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) );
539                 Rest();
540         }
541
542         return ( current.origin != oldOrigin );
543 }
544
545 /*
546 ================
547 idPhysics_Monster::UpdateTime
548 ================
549 */
550 void idPhysics_Monster::UpdateTime( int endTimeMSec ) {
551 }
552
553 /*
554 ================
555 idPhysics_Monster::GetTime
556 ================
557 */
558 int idPhysics_Monster::GetTime( void ) const {
559         return gameLocal.time;
560 }
561
562 /*
563 ================
564 idPhysics_Monster::GetImpactInfo
565 ================
566 */
567 void idPhysics_Monster::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const {
568         info->invMass = invMass;
569         info->invInertiaTensor.Zero();
570         info->position.Zero();
571         info->velocity = current.velocity;
572 }
573
574 /*
575 ================
576 idPhysics_Monster::ApplyImpulse
577 ================
578 */
579 void idPhysics_Monster::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
580         if ( noImpact ) {
581                 return;
582         }
583         current.velocity += impulse * invMass;
584         Activate();
585 }
586
587 /*
588 ================
589 idPhysics_Monster::IsAtRest
590 ================
591 */
592 bool idPhysics_Monster::IsAtRest( void ) const {
593         return current.atRest >= 0;
594 }
595
596 /*
597 ================
598 idPhysics_Monster::GetRestStartTime
599 ================
600 */
601 int idPhysics_Monster::GetRestStartTime( void ) const {
602         return current.atRest;
603 }
604
605 /*
606 ================
607 idPhysics_Monster::SaveState
608 ================
609 */
610 void idPhysics_Monster::SaveState( void ) {
611         saved = current;
612 }
613
614 /*
615 ================
616 idPhysics_Monster::RestoreState
617 ================
618 */
619 void idPhysics_Monster::RestoreState( void ) {
620         current = saved;
621
622         clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
623
624         EvaluateContacts();
625 }
626
627 /*
628 ================
629 idPhysics_Player::SetOrigin
630 ================
631 */
632 void idPhysics_Monster::SetOrigin( const idVec3 &newOrigin, int id ) {
633         idVec3 masterOrigin;
634         idMat3 masterAxis;
635
636         current.localOrigin = newOrigin;
637         if ( masterEntity ) {
638                 self->GetMasterPosition( masterOrigin, masterAxis );
639                 current.origin = masterOrigin + newOrigin * masterAxis;
640         }
641         else {
642                 current.origin = newOrigin;
643         }
644         clipModel->Link( gameLocal.clip, self, 0, newOrigin, clipModel->GetAxis() );
645         Activate();
646 }
647
648 /*
649 ================
650 idPhysics_Player::SetAxis
651 ================
652 */
653 void idPhysics_Monster::SetAxis( const idMat3 &newAxis, int id ) {
654         clipModel->Link( gameLocal.clip, self, 0, clipModel->GetOrigin(), newAxis );
655         Activate();
656 }
657
658 /*
659 ================
660 idPhysics_Monster::Translate
661 ================
662 */
663 void idPhysics_Monster::Translate( const idVec3 &translation, int id ) {
664
665         current.localOrigin += translation;
666         current.origin += translation;
667         clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
668         Activate();
669 }
670
671 /*
672 ================
673 idPhysics_Monster::Rotate
674 ================
675 */
676 void idPhysics_Monster::Rotate( const idRotation &rotation, int id ) {
677         idVec3 masterOrigin;
678         idMat3 masterAxis;
679
680         current.origin *= rotation;
681         if ( masterEntity ) {
682                 self->GetMasterPosition( masterOrigin, masterAxis );
683                 current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
684         }
685         else {
686                 current.localOrigin = current.origin;
687         }
688         clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() );
689         Activate();
690 }
691
692 /*
693 ================
694 idPhysics_Monster::SetLinearVelocity
695 ================
696 */
697 void idPhysics_Monster::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
698         current.velocity = newLinearVelocity;
699         Activate();
700 }
701
702 /*
703 ================
704 idPhysics_Monster::GetLinearVelocity
705 ================
706 */
707 const idVec3 &idPhysics_Monster::GetLinearVelocity( int id ) const {
708         return current.velocity;
709 }
710
711 /*
712 ================
713 idPhysics_Monster::SetPushed
714 ================
715 */
716 void idPhysics_Monster::SetPushed( int deltaTime ) {
717         // velocity with which the monster is pushed
718         current.pushVelocity += ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC );
719 }
720
721 /*
722 ================
723 idPhysics_Monster::GetPushedLinearVelocity
724 ================
725 */
726 const idVec3 &idPhysics_Monster::GetPushedLinearVelocity( const int id ) const {
727         return current.pushVelocity;
728 }
729
730 /*
731 ================
732 idPhysics_Monster::SetMaster
733
734   the binding is never orientated
735 ================
736 */
737 void idPhysics_Monster::SetMaster( idEntity *master, const bool orientated ) {
738         idVec3 masterOrigin;
739         idMat3 masterAxis;
740
741         if ( master ) {
742                 if ( !masterEntity ) {
743                         // transform from world space to master space
744                         self->GetMasterPosition( masterOrigin, masterAxis );
745                         current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
746                         masterEntity = master;
747                         masterYaw = masterAxis[0].ToYaw();
748                 }
749                 ClearContacts();
750         }
751         else {
752                 if ( masterEntity ) {
753                         masterEntity = NULL;
754                         Activate();
755                 }
756         }
757 }
758
759 const float     MONSTER_VELOCITY_MAX                    = 4000;
760 const int       MONSTER_VELOCITY_TOTAL_BITS             = 16;
761 const int       MONSTER_VELOCITY_EXPONENT_BITS  = idMath::BitsForInteger( idMath::BitsForFloat( MONSTER_VELOCITY_MAX ) ) + 1;
762 const int       MONSTER_VELOCITY_MANTISSA_BITS  = MONSTER_VELOCITY_TOTAL_BITS - 1 - MONSTER_VELOCITY_EXPONENT_BITS;
763
764 /*
765 ================
766 idPhysics_Monster::WriteToSnapshot
767 ================
768 */
769 void idPhysics_Monster::WriteToSnapshot( idBitMsgDelta &msg ) const {
770         msg.WriteFloat( current.origin[0] );
771         msg.WriteFloat( current.origin[1] );
772         msg.WriteFloat( current.origin[2] );
773         msg.WriteFloat( current.velocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
774         msg.WriteFloat( current.velocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
775         msg.WriteFloat( current.velocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
776         msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] );
777         msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] );
778         msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] );
779         msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
780         msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
781         msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
782         msg.WriteLong( current.atRest );
783         msg.WriteBits( current.onGround, 1 );
784 }
785
786 /*
787 ================
788 idPhysics_Monster::ReadFromSnapshot
789 ================
790 */
791 void idPhysics_Monster::ReadFromSnapshot( const idBitMsgDelta &msg ) {
792         current.origin[0] = msg.ReadFloat();
793         current.origin[1] = msg.ReadFloat();
794         current.origin[2] = msg.ReadFloat();
795         current.velocity[0] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
796         current.velocity[1] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
797         current.velocity[2] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
798         current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] );
799         current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] );
800         current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] );
801         current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
802         current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
803         current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS );
804         current.atRest = msg.ReadLong();
805         current.onGround = msg.ReadBits( 1 ) != 0;
806 }