1 /*
2 ** a_movingcamera.cpp
3 ** Cameras that move and related neat stuff
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 #include "actor.h"
36 #include "info.h"
37 #include "p_local.h"
38 #include "p_lnspec.h"
39 #include "doomstat.h"
40 #include "farchive.h"
41
42 /*
43 == InterpolationPoint: node along a camera's path
44 ==
45 == args[0] = pitch
46 == args[1] = time (in octics) to get here from previous node
47 == args[2] = time (in octics) to stay here before moving to next node
48 == args[3] = low byte of next node's tid
49 == args[4] = high byte of next node's tid
50 */
51
52 class AInterpolationPoint : public AActor
53 {
54 DECLARE_CLASS (AInterpolationPoint, AActor)
55 HAS_OBJECT_POINTERS
56 public:
57 void BeginPlay ();
58 void HandleSpawnFlags ();
Tick()59 void Tick () {} // Nodes do no thinking
60 AInterpolationPoint *ScanForLoop ();
61 void FormChain ();
62
63 void Serialize (FArchive &arc);
64
65 TObjPtr<AInterpolationPoint> Next;
66 };
67
68 IMPLEMENT_POINTY_CLASS (AInterpolationPoint)
DECLARE_POINTER(Next)69 DECLARE_POINTER (Next)
70 END_POINTERS
71
72 void AInterpolationPoint::Serialize (FArchive &arc)
73 {
74 Super::Serialize (arc);
75 arc << Next;
76 }
77
BeginPlay()78 void AInterpolationPoint::BeginPlay ()
79 {
80 Super::BeginPlay ();
81 Next = NULL;
82 }
83
HandleSpawnFlags()84 void AInterpolationPoint::HandleSpawnFlags ()
85 {
86 // Spawn flags mean nothing to an interpolation point
87 }
88
FormChain()89 void AInterpolationPoint::FormChain ()
90 {
91 if (flags & MF_AMBUSH)
92 return;
93
94 flags |= MF_AMBUSH;
95
96 TActorIterator<AInterpolationPoint> iterator (args[3] + 256 * args[4]);
97 Next = iterator.Next ();
98
99 if (Next == this) // Don't link to self
100 Next = iterator.Next ();
101
102 if (Next == NULL && (args[3] | args[4]))
103 Printf ("Can't find target for camera node %d\n", tid);
104
105 pitch = (signed int)((char)args[0]) * ANGLE_1;
106 if (pitch <= -ANGLE_90)
107 pitch = -ANGLE_90 + ANGLE_1;
108 else if (pitch >= ANGLE_90)
109 pitch = ANGLE_90 - ANGLE_1;
110
111 if (Next != NULL)
112 Next->FormChain ();
113 }
114
115 // Return the node (if any) where a path loops, relative to this one.
ScanForLoop()116 AInterpolationPoint *AInterpolationPoint::ScanForLoop ()
117 {
118 AInterpolationPoint *node = this;
119 while (node->Next && node->Next != this && node->special1 == 0)
120 {
121 node->special1 = 1;
122 node = node->Next;
123 }
124 return node->Next == this ? node : NULL;
125 }
126
127 /*
128 == InterpolationSpecial: Holds a special to execute when a
129 == PathFollower reaches an InterpolationPoint of the same TID.
130 */
131
132 class AInterpolationSpecial : public AActor
133 {
134 DECLARE_CLASS (AInterpolationSpecial, AActor)
135 public:
Tick()136 void Tick () {} // Does absolutely nothing itself
137 };
138
139 IMPLEMENT_CLASS (AInterpolationSpecial)
140
141 /*
142 == PathFollower: something that follows a camera path
143 == Base class for some moving cameras
144 ==
145 == args[0] = low byte of first node in path's tid
146 == args[1] = high byte of first node's tid
147 == args[2] = bit 0 = follow a linear path (rather than curved)
148 == bit 1 = adjust angle
149 == bit 2 = adjust pitch
150 == bit 3 = aim in direction of motion
151 ==
152 == Also uses:
153 == target = first node in path
154 == lastenemy = node prior to first node (if looped)
155 */
156
157 class APathFollower : public AActor
158 {
159 DECLARE_CLASS (APathFollower, AActor)
160 HAS_OBJECT_POINTERS
161 public:
162 void BeginPlay ();
163 void PostBeginPlay ();
164 void Tick ();
165 void Activate (AActor *activator);
166 void Deactivate (AActor *activator);
167 protected:
168 float Splerp (float p1, float p2, float p3, float p4);
169 float Lerp (float p1, float p2);
170 virtual bool Interpolate ();
171 virtual void NewNode ();
172
173 void Serialize (FArchive &arc);
174
175 bool bActive, bJustStepped;
176 TObjPtr<AInterpolationPoint> PrevNode, CurrNode;
177 float Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode->Next
178 int HoldTime;
179 };
180
181 IMPLEMENT_POINTY_CLASS (APathFollower)
DECLARE_POINTER(PrevNode)182 DECLARE_POINTER (PrevNode)
183 DECLARE_POINTER (CurrNode)
184 END_POINTERS
185
186 void APathFollower::Serialize (FArchive &arc)
187 {
188 Super::Serialize (arc);
189 arc << bActive << bJustStepped << PrevNode << CurrNode << Time << HoldTime;
190 }
191
192 // Interpolate between p2 and p3 along a Catmull-Rom spline
193 // http://research.microsoft.com/~hollasch/cgindex/curves/catmull-rom.html
Splerp(float p1,float p2,float p3,float p4)194 float APathFollower::Splerp (float p1, float p2, float p3, float p4)
195 {
196 float t = Time;
197 float res = 2*p2;
198 res += (p3 - p1) * Time;
199 t *= Time;
200 res += (2*p1 - 5*p2 + 4*p3 - p4) * t;
201 t *= Time;
202 res += (3*p2 - 3*p3 + p4 - p1) * t;
203 return 0.5f * res;
204 }
205
206 // Linearly interpolate between p1 and p2
Lerp(float p1,float p2)207 float APathFollower::Lerp (float p1, float p2)
208 {
209 return p1 + Time * (p2 - p1);
210 }
211
BeginPlay()212 void APathFollower::BeginPlay ()
213 {
214 Super::BeginPlay ();
215 PrevNode = CurrNode = NULL;
216 bActive = false;
217 }
218
PostBeginPlay()219 void APathFollower::PostBeginPlay ()
220 {
221 // Find first node of path
222 TActorIterator<AInterpolationPoint> iterator (args[0] + 256 * args[1]);
223 AInterpolationPoint *node = iterator.Next ();
224 AInterpolationPoint *prevnode;
225
226 target = node;
227
228 if (node == NULL)
229 {
230 Printf ("PathFollower %d: Can't find interpolation pt %d\n",
231 tid, args[0] + 256 * args[1]);
232 return;
233 }
234
235 // Verify the path has enough nodes
236 node->FormChain ();
237 if (args[2] & 1)
238 { // linear path; need 2 nodes
239 if (node->Next == NULL)
240 {
241 Printf ("PathFollower %d: Path needs at least 2 nodes\n", tid);
242 return;
243 }
244 lastenemy = NULL;
245 }
246 else
247 { // spline path; need 4 nodes
248 if (node->Next == NULL ||
249 node->Next->Next == NULL ||
250 node->Next->Next->Next == NULL)
251 {
252 Printf ("PathFollower %d: Path needs at least 4 nodes\n", tid);
253 return;
254 }
255 // If the first node is in a loop, we can start there.
256 // Otherwise, we need to start at the second node in the path.
257 prevnode = node->ScanForLoop ();
258 if (prevnode == NULL || prevnode->Next != node)
259 {
260 lastenemy = target;
261 target = node->Next;
262 }
263 else
264 {
265 lastenemy = prevnode;
266 }
267 }
268 }
269
Deactivate(AActor * activator)270 void APathFollower::Deactivate (AActor *activator)
271 {
272 bActive = false;
273 }
274
Activate(AActor * activator)275 void APathFollower::Activate (AActor *activator)
276 {
277 if (!bActive)
278 {
279 CurrNode = barrier_cast<AInterpolationPoint *>(target);
280 PrevNode = barrier_cast<AInterpolationPoint *>(lastenemy);
281
282 if (CurrNode != NULL)
283 {
284 NewNode ();
285 SetOrigin (CurrNode->Pos(), false);
286 Time = 0.f;
287 HoldTime = 0;
288 bJustStepped = true;
289 bActive = true;
290 }
291 }
292 }
293
Tick()294 void APathFollower::Tick ()
295 {
296 if (!bActive)
297 return;
298
299 if (bJustStepped)
300 {
301 bJustStepped = false;
302 if (CurrNode->args[2])
303 {
304 HoldTime = level.time + CurrNode->args[2] * TICRATE / 8;
305 SetXYZ(CurrNode->X(), CurrNode->Y(), CurrNode->Z());
306 }
307 }
308
309 if (HoldTime > level.time)
310 return;
311
312 // Splines must have a previous node.
313 if (PrevNode == NULL && !(args[2] & 1))
314 {
315 bActive = false;
316 return;
317 }
318
319 // All paths must have a current node.
320 if (CurrNode->Next == NULL)
321 {
322 bActive = false;
323 return;
324 }
325
326 if (Interpolate ())
327 {
328 Time += 8.f / ((float)CurrNode->args[1] * (float)TICRATE);
329 if (Time > 1.f)
330 {
331 Time -= 1.f;
332 bJustStepped = true;
333 PrevNode = CurrNode;
334 CurrNode = CurrNode->Next;
335 if (CurrNode != NULL)
336 NewNode ();
337 if (CurrNode == NULL || CurrNode->Next == NULL)
338 Deactivate (this);
339 if ((args[2] & 1) == 0 && CurrNode->Next->Next == NULL)
340 Deactivate (this);
341 }
342 }
343 }
344
NewNode()345 void APathFollower::NewNode ()
346 {
347 TActorIterator<AInterpolationSpecial> iterator (CurrNode->tid);
348 AInterpolationSpecial *spec;
349
350 while ( (spec = iterator.Next ()) )
351 {
352 P_ExecuteSpecial(spec->special, NULL, NULL, false, spec->args[0],
353 spec->args[1], spec->args[2], spec->args[3], spec->args[4]);
354 }
355 }
356
Interpolate()357 bool APathFollower::Interpolate ()
358 {
359 fixed_t dx = 0, dy = 0, dz = 0;
360
361 if ((args[2] & 8) && Time > 0.f)
362 {
363 dx = X();
364 dy = Y();
365 dz = Z();
366 }
367
368 if (CurrNode->Next==NULL) return false;
369
370 UnlinkFromWorld ();
371 fixed_t x, y, z;
372 if (args[2] & 1)
373 { // linear
374 x = FLOAT2FIXED(Lerp (FIXED2FLOAT(CurrNode->X()), FIXED2FLOAT(CurrNode->Next->X())));
375 y = FLOAT2FIXED(Lerp (FIXED2FLOAT(CurrNode->Y()), FIXED2FLOAT(CurrNode->Next->Y())));
376 z = FLOAT2FIXED(Lerp (FIXED2FLOAT(CurrNode->Z()), FIXED2FLOAT(CurrNode->Next->Z())));
377 }
378 else
379 { // spline
380 if (CurrNode->Next->Next==NULL) return false;
381
382 x = FLOAT2FIXED(Splerp (FIXED2FLOAT(PrevNode->X()), FIXED2FLOAT(CurrNode->X()),
383 FIXED2FLOAT(CurrNode->Next->X()), FIXED2FLOAT(CurrNode->Next->Next->X())));
384 y = FLOAT2FIXED(Splerp (FIXED2FLOAT(PrevNode->Y()), FIXED2FLOAT(CurrNode->Y()),
385 FIXED2FLOAT(CurrNode->Next->Y()), FIXED2FLOAT(CurrNode->Next->Next->Y())));
386 z = FLOAT2FIXED(Splerp (FIXED2FLOAT(PrevNode->Z()), FIXED2FLOAT(CurrNode->Z()),
387 FIXED2FLOAT(CurrNode->Next->Z()), FIXED2FLOAT(CurrNode->Next->Next->Z())));
388 }
389 SetXYZ(x, y, z);
390 LinkToWorld ();
391
392 if (args[2] & 6)
393 {
394 if (args[2] & 8)
395 {
396 if (args[2] & 1)
397 { // linear
398 dx = CurrNode->Next->X() - CurrNode->X();
399 dy = CurrNode->Next->Y() - CurrNode->Y();
400 dz = CurrNode->Next->Z() - CurrNode->Z();
401 }
402 else if (Time > 0.f)
403 { // spline
404 dx = x - dx;
405 dy = y - dy;
406 dz = z - dz;
407 }
408 else
409 {
410 int realarg = args[2];
411 args[2] &= ~(2|4|8);
412 Time += 0.1f;
413 dx = x;
414 dy = y;
415 dz = z;
416 Interpolate ();
417 Time -= 0.1f;
418 args[2] = realarg;
419 dx = x - dx;
420 dy = y - dy;
421 dz = z - dz;
422 x -= dx;
423 y -= dy;
424 z -= dz;
425 SetXYZ(x, y, z);
426 }
427 if (args[2] & 2)
428 { // adjust yaw
429 angle = R_PointToAngle2 (0, 0, dx, dy);
430 }
431 if (args[2] & 4)
432 { // adjust pitch; use floats for precision
433 float fdx = FIXED2FLOAT(dx);
434 float fdy = FIXED2FLOAT(dy);
435 float fdz = FIXED2FLOAT(-dz);
436 float dist = (float)sqrt (fdx*fdx + fdy*fdy);
437 float ang = dist != 0.f ? (float)atan2 (fdz, dist) : 0;
438 pitch = (angle_t)(ang * 2147483648.f / PI);
439 }
440 }
441 else
442 {
443 if (args[2] & 2)
444 { // interpolate angle
445 float angle1 = (float)CurrNode->angle;
446 float angle2 = (float)CurrNode->Next->angle;
447 if (angle2 - angle1 <= -2147483648.f)
448 {
449 float lerped = Lerp (angle1, angle2 + 4294967296.f);
450 if (lerped >= 4294967296.f)
451 {
452 angle = (angle_t)(lerped - 4294967296.f);
453 }
454 else
455 {
456 angle = (angle_t)lerped;
457 }
458 }
459 else if (angle2 - angle1 >= 2147483648.f)
460 {
461 float lerped = Lerp (angle1, angle2 - 4294967296.f);
462 if (lerped < 0.f)
463 {
464 angle = (angle_t)(lerped + 4294967296.f);
465 }
466 else
467 {
468 angle = (angle_t)lerped;
469 }
470 }
471 else
472 {
473 angle = (angle_t)Lerp (angle1, angle2);
474 }
475 }
476 if (args[2] & 1)
477 { // linear
478 if (args[2] & 4)
479 { // interpolate pitch
480 pitch = FLOAT2FIXED(Lerp (FIXED2FLOAT(CurrNode->pitch), FIXED2FLOAT(CurrNode->Next->pitch)));
481 }
482 }
483 else
484 { // spline
485 if (args[2] & 4)
486 { // interpolate pitch
487 pitch = FLOAT2FIXED(Splerp (FIXED2FLOAT(PrevNode->pitch), FIXED2FLOAT(CurrNode->pitch),
488 FIXED2FLOAT(CurrNode->Next->pitch), FIXED2FLOAT(CurrNode->Next->Next->pitch)));
489 }
490 }
491 }
492 }
493
494 return true;
495 }
496
497 /*
498 == ActorMover: Moves any actor along a camera path
499 ==
500 == Same as PathFollower, except
501 == args[2], bit 7: make nonsolid
502 == args[3] = tid of thing to move
503 ==
504 == also uses:
505 == tracer = thing to move
506 */
507
508 class AActorMover : public APathFollower
509 {
510 DECLARE_CLASS (AActorMover, APathFollower)
511 public:
512 void BeginPlay();
513 void PostBeginPlay ();
514 void Activate (AActor *activator);
515 void Deactivate (AActor *activator);
516 protected:
517 bool Interpolate ();
518 };
519
IMPLEMENT_CLASS(AActorMover)520 IMPLEMENT_CLASS (AActorMover)
521
522 void AActorMover::BeginPlay()
523 {
524 ChangeStatNum(STAT_ACTORMOVER);
525 }
526
PostBeginPlay()527 void AActorMover::PostBeginPlay ()
528 {
529 Super::PostBeginPlay ();
530
531 TActorIterator<AActor> iterator (args[3]);
532 tracer = iterator.Next ();
533
534 if (tracer == NULL)
535 {
536 Printf ("ActorMover %d: Can't find target %d\n", tid, args[3]);
537 }
538 else
539 {
540 special1 = tracer->flags;
541 special2 = tracer->flags2;
542 }
543 }
544
Interpolate()545 bool AActorMover::Interpolate ()
546 {
547 if (tracer == NULL)
548 return true;
549
550 if (Super::Interpolate ())
551 {
552 fixed_t savedz = tracer->Z();
553 tracer->SetZ(Z());
554 if (!P_TryMove (tracer, X(), Y(), true))
555 {
556 tracer->SetZ(savedz);
557 return false;
558 }
559
560 if (args[2] & 2)
561 tracer->angle = angle;
562 if (args[2] & 4)
563 tracer->pitch = pitch;
564
565 return true;
566 }
567 return false;
568 }
569
Activate(AActor * activator)570 void AActorMover::Activate (AActor *activator)
571 {
572 if (tracer == NULL || bActive)
573 return;
574
575 Super::Activate (activator);
576 special1 = tracer->flags;
577 special2 = tracer->flags2;
578 tracer->flags |= MF_NOGRAVITY;
579 if (args[2] & 128)
580 {
581 tracer->UnlinkFromWorld ();
582 tracer->flags |= MF_NOBLOCKMAP;
583 tracer->flags &= ~MF_SOLID;
584 tracer->LinkToWorld ();
585 }
586 if (tracer->flags3 & MF3_ISMONSTER)
587 {
588 tracer->flags2 |= MF2_INVULNERABLE | MF2_DORMANT;
589 }
590 // Don't let the renderer interpolate between the actor's
591 // old position and its new position.
592 Interpolate ();
593 tracer->PrevX = tracer->X();
594 tracer->PrevY = tracer->Y();
595 tracer->PrevZ = tracer->Z();
596 tracer->PrevAngle = tracer->angle;
597 }
598
Deactivate(AActor * activator)599 void AActorMover::Deactivate (AActor *activator)
600 {
601 if (bActive)
602 {
603 Super::Deactivate (activator);
604 if (tracer != NULL)
605 {
606 tracer->UnlinkFromWorld ();
607 tracer->flags = ActorFlags::FromInt (special1);
608 tracer->LinkToWorld ();
609 tracer->flags2 = ActorFlags2::FromInt (special2);
610 }
611 }
612 }
613
614 /*
615 == MovingCamera: Moves any actor along a camera path
616 ==
617 == Same as PathFollower, except
618 == args[3] = tid of thing to look at (0 if none)
619 ==
620 == Also uses:
621 == tracer = thing to look at
622 */
623
624 class AMovingCamera : public APathFollower
625 {
626 DECLARE_CLASS (AMovingCamera, APathFollower)
627 HAS_OBJECT_POINTERS
628 public:
629 void PostBeginPlay ();
630
631 void Serialize (FArchive &arc);
632 protected:
633 bool Interpolate ();
634
635 TObjPtr<AActor> Activator;
636 };
637
638 IMPLEMENT_POINTY_CLASS (AMovingCamera)
DECLARE_POINTER(Activator)639 DECLARE_POINTER (Activator)
640 END_POINTERS
641
642 void AMovingCamera::Serialize (FArchive &arc)
643 {
644 Super::Serialize (arc);
645 arc << Activator;
646 }
647
PostBeginPlay()648 void AMovingCamera::PostBeginPlay ()
649 {
650 Super::PostBeginPlay ();
651
652 Activator = NULL;
653 if (args[3] != 0)
654 {
655 TActorIterator<AActor> iterator (args[3]);
656 tracer = iterator.Next ();
657 if (tracer == NULL)
658 {
659 Printf ("MovingCamera %d: Can't find thing %d\n", tid, args[3]);
660 }
661 }
662 }
663
Interpolate()664 bool AMovingCamera::Interpolate ()
665 {
666 if (tracer == NULL)
667 return Super::Interpolate ();
668
669 if (Super::Interpolate ())
670 {
671 angle = AngleTo(tracer, true);
672
673 if (args[2] & 4)
674 { // Also aim camera's pitch; use floats for precision
675 double dx = FIXED2DBL(X() - tracer->X());
676 double dy = FIXED2DBL(Y() - tracer->Y());
677 double dz = FIXED2DBL(Z() - tracer->Z() - tracer->height/2);
678 double dist = sqrt (dx*dx + dy*dy);
679 double ang = dist != 0.f ? atan2 (dz, dist) : 0;
680 pitch = (angle_t)(ang * 2147483648.f / PI);
681 }
682
683 return true;
684 }
685 return false;
686 }
687