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