diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e076529c9..2fad79c63 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1162,7 +1162,6 @@ set (PCH_SOURCES g_shared/a_flashfader.cpp g_shared/a_lightning.cpp g_shared/a_morph.cpp - g_shared/a_movingcamera.cpp g_shared/a_quake.cpp g_shared/a_randomspawner.cpp g_shared/a_skies.cpp diff --git a/src/g_shared/a_movingcamera.cpp b/src/g_shared/a_movingcamera.cpp deleted file mode 100644 index f32550f2d..000000000 --- a/src/g_shared/a_movingcamera.cpp +++ /dev/null @@ -1,650 +0,0 @@ -/* -** a_movingcamera.cpp -** Cameras that move and related neat stuff -** -**--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "actor.h" -#include "info.h" -#include "p_local.h" -#include "p_lnspec.h" -#include "doomstat.h" -#include "serializer.h" -#include "g_levellocals.h" - -/* -== InterpolationPoint: node along a camera's path -== -== args[0] = pitch -== args[1] = time (in octics) to get here from previous node -== args[2] = time (in octics) to stay here before moving to next node -== args[3] = low byte of next node's tid -== args[4] = high byte of next node's tid -*/ - -class AInterpolationPoint : public AActor -{ - DECLARE_CLASS (AInterpolationPoint, AActor) - HAS_OBJECT_POINTERS -public: - void BeginPlay (); - void HandleSpawnFlags (); - void Tick () {} // Nodes do no thinking - AInterpolationPoint *ScanForLoop (); - void FormChain (); - - - void Serialize(FSerializer &arc); - - TObjPtr Next; -}; - -IMPLEMENT_CLASS(AInterpolationPoint, false, true) - -IMPLEMENT_POINTERS_START(AInterpolationPoint) - IMPLEMENT_POINTER(Next) -IMPLEMENT_POINTERS_END - -void AInterpolationPoint::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("next", Next); -} - -void AInterpolationPoint::BeginPlay () -{ - Super::BeginPlay (); - Next = NULL; -} - -void AInterpolationPoint::HandleSpawnFlags () -{ - // Spawn flags mean nothing to an interpolation point -} - -void AInterpolationPoint::FormChain () -{ - if (flags & MF_AMBUSH) - return; - - flags |= MF_AMBUSH; - - TActorIterator iterator (args[3] + 256 * args[4]); - Next = iterator.Next (); - - if (Next == this) // Don't link to self - Next = iterator.Next (); - - if (Next == NULL && (args[3] | args[4])) - Printf ("Can't find target for camera node %d\n", tid); - - Angles.Pitch = (double)clamp((signed char)args[0], -89, 89); - - if (Next != NULL) - Next->FormChain (); -} - -// Return the node (if any) where a path loops, relative to this one. -AInterpolationPoint *AInterpolationPoint::ScanForLoop () -{ - AInterpolationPoint *node = this; - while (node->Next && node->Next != this && node->special1 == 0) - { - node->special1 = 1; - node = node->Next; - } - return node->Next == this ? node : NULL; -} - -/* -== InterpolationSpecial: Holds a special to execute when a -== PathFollower reaches an InterpolationPoint of the same TID. -*/ - -class AInterpolationSpecial : public AActor -{ - DECLARE_CLASS (AInterpolationSpecial, AActor) -public: - void Tick () {} // Does absolutely nothing itself -}; - -IMPLEMENT_CLASS(AInterpolationSpecial, false, false) - -/* -== PathFollower: something that follows a camera path -== Base class for some moving cameras -== -== args[0] = low byte of first node in path's tid -== args[1] = high byte of first node's tid -== args[2] = bit 0 = follow a linear path (rather than curved) -== bit 1 = adjust angle -== bit 2 = adjust pitch -== bit 3 = aim in direction of motion -== -== Also uses: -== target = first node in path -== lastenemy = node prior to first node (if looped) -*/ - -class APathFollower : public AActor -{ - DECLARE_CLASS (APathFollower, AActor) - HAS_OBJECT_POINTERS -public: - void BeginPlay (); - void PostBeginPlay (); - void Tick (); - void Activate (AActor *activator); - void Deactivate (AActor *activator); -protected: - double Splerp (double p1, double p2, double p3, double p4); - double Lerp (double p1, double p2); - virtual bool Interpolate (); - virtual void NewNode (); - - - void Serialize(FSerializer &arc); - - bool bActive, bJustStepped; - TObjPtr PrevNode, CurrNode; - float Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode->Next - int HoldTime; -}; - -IMPLEMENT_CLASS(APathFollower, false, true) - -IMPLEMENT_POINTERS_START(APathFollower) - IMPLEMENT_POINTER(PrevNode) - IMPLEMENT_POINTER(CurrNode) -IMPLEMENT_POINTERS_END - -void APathFollower::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("active", bActive) - ("juststepped", bJustStepped) - ("prevnode", PrevNode) - ("currnode", CurrNode) - ("time", Time) - ("holdtime", HoldTime); -} - -// Interpolate between p2 and p3 along a Catmull-Rom spline -// http://research.microsoft.com/~hollasch/cgindex/curves/catmull-rom.html -double APathFollower::Splerp (double p1, double p2, double p3, double p4) -{ - double t = Time; - double res = 2*p2; - res += (p3 - p1) * Time; - t *= Time; - res += (2*p1 - 5*p2 + 4*p3 - p4) * t; - t *= Time; - res += (3*p2 - 3*p3 + p4 - p1) * t; - return 0.5f * res; -} - -// Linearly interpolate between p1 and p2 -double APathFollower::Lerp (double p1, double p2) -{ - return p1 + Time * (p2 - p1); -} - -void APathFollower::BeginPlay () -{ - Super::BeginPlay (); - PrevNode = CurrNode = NULL; - bActive = false; -} - -void APathFollower::PostBeginPlay () -{ - // Find first node of path - TActorIterator iterator (args[0] + 256 * args[1]); - AInterpolationPoint *node = iterator.Next (); - AInterpolationPoint *prevnode; - - target = node; - - if (node == NULL) - { - Printf ("PathFollower %d: Can't find interpolation pt %d\n", - tid, args[0] + 256 * args[1]); - return; - } - - // Verify the path has enough nodes - node->FormChain (); - if (args[2] & 1) - { // linear path; need 2 nodes - if (node->Next == NULL) - { - Printf ("PathFollower %d: Path needs at least 2 nodes\n", tid); - return; - } - lastenemy = NULL; - } - else - { // spline path; need 4 nodes - if (node->Next == NULL || - node->Next->Next == NULL || - node->Next->Next->Next == NULL) - { - Printf ("PathFollower %d: Path needs at least 4 nodes\n", tid); - return; - } - // If the first node is in a loop, we can start there. - // Otherwise, we need to start at the second node in the path. - prevnode = node->ScanForLoop (); - if (prevnode == NULL || prevnode->Next != node) - { - lastenemy = target; - target = node->Next; - } - else - { - lastenemy = prevnode; - } - } -} - -void APathFollower::Deactivate (AActor *activator) -{ - bActive = false; -} - -void APathFollower::Activate (AActor *activator) -{ - if (!bActive) - { - CurrNode = barrier_cast(target); - PrevNode = barrier_cast(lastenemy); - - if (CurrNode != NULL) - { - NewNode (); - SetOrigin (CurrNode->Pos(), false); - Time = 0.f; - HoldTime = 0; - bJustStepped = true; - bActive = true; - } - } -} - -void APathFollower::Tick () -{ - if (!bActive) - return; - - if (bJustStepped) - { - bJustStepped = false; - if (CurrNode->args[2]) - { - HoldTime = level.time + CurrNode->args[2] * TICRATE / 8; - SetXYZ(CurrNode->Pos()); - } - } - - if (HoldTime > level.time) - return; - - // Splines must have a previous node. - if (PrevNode == NULL && !(args[2] & 1)) - { - bActive = false; - return; - } - - // All paths must have a current node. - if (CurrNode->Next == NULL) - { - bActive = false; - return; - } - - if (Interpolate ()) - { - Time += float(8.f / ((double)CurrNode->args[1] * (double)TICRATE)); - if (Time > 1.f) - { - Time -= 1.f; - bJustStepped = true; - PrevNode = CurrNode; - CurrNode = CurrNode->Next; - if (CurrNode != NULL) - NewNode (); - if (CurrNode == NULL || CurrNode->Next == NULL) - CallDeactivate (this); - if ((args[2] & 1) == 0 && CurrNode->Next->Next == NULL) - CallDeactivate (this); - } - } -} - -void APathFollower::NewNode () -{ - TActorIterator iterator (CurrNode->tid); - AInterpolationSpecial *spec; - - while ( (spec = iterator.Next ()) ) - { - P_ExecuteSpecial(spec->special, NULL, NULL, false, spec->args[0], - spec->args[1], spec->args[2], spec->args[3], spec->args[4]); - } -} - -bool APathFollower::Interpolate () -{ - DVector3 dpos(0, 0, 0); - FLinkContext ctx; - - if ((args[2] & 8) && Time > 0.f) - { - dpos = Pos(); - } - - if (CurrNode->Next==NULL) return false; - - UnlinkFromWorld (&ctx); - DVector3 newpos; - if (args[2] & 1) - { // linear - newpos.X = Lerp(CurrNode->X(), CurrNode->Next->X()); - newpos.Y = Lerp(CurrNode->Y(), CurrNode->Next->Y()); - newpos.Z = Lerp(CurrNode->Z(), CurrNode->Next->Z()); - } - else - { // spline - if (CurrNode->Next->Next==NULL) return false; - - newpos.X = Splerp(PrevNode->X(), CurrNode->X(), CurrNode->Next->X(), CurrNode->Next->Next->X()); - newpos.Y = Splerp(PrevNode->Y(), CurrNode->Y(), CurrNode->Next->Y(), CurrNode->Next->Next->Y()); - newpos.Z = Splerp(PrevNode->Z(), CurrNode->Z(), CurrNode->Next->Z(), CurrNode->Next->Next->Z()); - } - SetXYZ(newpos); - LinkToWorld (&ctx); - - if (args[2] & 6) - { - if (args[2] & 8) - { - if (args[2] & 1) - { // linear - dpos.X = CurrNode->Next->X() - CurrNode->X(); - dpos.Y = CurrNode->Next->Y() - CurrNode->Y(); - dpos.Z = CurrNode->Next->Z() - CurrNode->Z(); - } - else if (Time > 0.f) - { // spline - dpos = newpos - dpos; - } - else - { - int realarg = args[2]; - args[2] &= ~(2|4|8); - Time += 0.1f; - dpos = newpos; - Interpolate (); - Time -= 0.1f; - args[2] = realarg; - dpos = newpos - dpos; - newpos -= dpos; - SetXYZ(newpos); - } - if (args[2] & 2) - { // adjust yaw - Angles.Yaw = dpos.Angle(); - } - if (args[2] & 4) - { // adjust pitch; use floats for precision - double dist = dpos.XY().Length(); - Angles.Pitch = dist != 0.f ? VecToAngle(dist, -dpos.Z) : 0.; - } - } - else - { - if (args[2] & 2) - { // interpolate angle - DAngle angle1 = CurrNode->Angles.Yaw.Normalized180(); - DAngle angle2 = angle1 + deltaangle(angle1, CurrNode->Next->Angles.Yaw); - Angles.Yaw = Lerp(angle1.Degrees, angle2.Degrees); - } - if (args[2] & 1) - { // linear - if (args[2] & 4) - { // interpolate pitch - Angles.Pitch = Lerp(CurrNode->Angles.Pitch.Degrees, CurrNode->Next->Angles.Pitch.Degrees); - } - } - else - { // spline - if (args[2] & 4) - { // interpolate pitch - Angles.Pitch = Splerp(PrevNode->Angles.Pitch.Degrees, CurrNode->Angles.Pitch.Degrees, - CurrNode->Next->Angles.Pitch.Degrees, CurrNode->Next->Next->Angles.Pitch.Degrees); - } - } - } - } - - return true; -} - -/* -== ActorMover: Moves any actor along a camera path -== -== Same as PathFollower, except -== args[2], bit 7: make nonsolid -== args[3] = tid of thing to move -== -== also uses: -== tracer = thing to move -*/ - -class AActorMover : public APathFollower -{ - DECLARE_CLASS (AActorMover, APathFollower) -public: - void BeginPlay(); - void PostBeginPlay (); - void Activate (AActor *activator); - void Deactivate (AActor *activator); -protected: - bool Interpolate (); -}; - -IMPLEMENT_CLASS(AActorMover, false, false) - -void AActorMover::BeginPlay() -{ - ChangeStatNum(STAT_ACTORMOVER); -} - -void AActorMover::PostBeginPlay () -{ - Super::PostBeginPlay (); - - TActorIterator iterator (args[3]); - tracer = iterator.Next (); - - if (tracer == NULL) - { - Printf ("ActorMover %d: Can't find target %d\n", tid, args[3]); - } - else - { - special1 = tracer->flags; - special2 = tracer->flags2; - } -} - -bool AActorMover::Interpolate () -{ - if (tracer == NULL) - return true; - - if (Super::Interpolate ()) - { - double savedz = tracer->Z(); - tracer->SetZ(Z()); - if (!P_TryMove (tracer, Pos(), true)) - { - tracer->SetZ(savedz); - return false; - } - - if (args[2] & 2) - tracer->Angles.Yaw = Angles.Yaw; - if (args[2] & 4) - tracer->Angles.Pitch = Angles.Pitch; - - return true; - } - return false; -} - -void AActorMover::Activate (AActor *activator) -{ - if (tracer == NULL || bActive) - return; - - Super::Activate (activator); - special1 = tracer->flags; - special2 = tracer->flags2; - tracer->flags |= MF_NOGRAVITY; - if (args[2] & 128) - { - FLinkContext ctx; - tracer->UnlinkFromWorld (&ctx); - tracer->flags |= MF_NOBLOCKMAP; - tracer->flags &= ~MF_SOLID; - tracer->LinkToWorld (&ctx); - } - if (tracer->flags3 & MF3_ISMONSTER) - { - tracer->flags2 |= MF2_INVULNERABLE | MF2_DORMANT; - } - // Don't let the renderer interpolate between the actor's - // old position and its new position. - Interpolate (); - tracer->ClearInterpolation(); -} - -void AActorMover::Deactivate (AActor *activator) -{ - if (bActive) - { - Super::Deactivate (activator); - if (tracer != NULL) - { - FLinkContext ctx; - tracer->UnlinkFromWorld (&ctx); - tracer->flags = ActorFlags::FromInt (special1); - tracer->LinkToWorld (&ctx); - tracer->flags2 = ActorFlags2::FromInt (special2); - } - } -} - -/* -== MovingCamera: Moves any actor along a camera path -== -== Same as PathFollower, except -== args[3] = tid of thing to look at (0 if none) -== -== Also uses: -== tracer = thing to look at -*/ - -class AMovingCamera : public APathFollower -{ - DECLARE_CLASS (AMovingCamera, APathFollower) - HAS_OBJECT_POINTERS -public: - void PostBeginPlay (); - - - void Serialize(FSerializer &arc); -protected: - bool Interpolate (); - - TObjPtr Activator; -}; - -IMPLEMENT_CLASS(AMovingCamera, false, true) - -IMPLEMENT_POINTERS_START(AMovingCamera) - IMPLEMENT_POINTER(Activator) -IMPLEMENT_POINTERS_END - -void AMovingCamera::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("activator", Activator); -} - -void AMovingCamera::PostBeginPlay () -{ - Super::PostBeginPlay (); - - Activator = NULL; - if (args[3] != 0) - { - TActorIterator iterator (args[3]); - tracer = iterator.Next (); - if (tracer == NULL) - { - Printf ("MovingCamera %d: Can't find thing %d\n", tid, args[3]); - } - } -} - -bool AMovingCamera::Interpolate () -{ - if (tracer == NULL) - return Super::Interpolate (); - - if (Super::Interpolate ()) - { - Angles.Yaw = AngleTo(tracer, true); - - if (args[2] & 4) - { // Also aim camera's pitch; - DVector3 diff = Pos() - tracer->PosPlusZ(tracer->Height / 2); - double dist = diff.XY().Length(); - Angles.Pitch = dist != 0.f ? VecToAngle(dist, diff.Z) : 0.; - } - - return true; - } - return false; -} diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index e6b2aebea..ea2434b25 100644 --- a/src/p_maputl.cpp +++ b/src/p_maputl.cpp @@ -396,7 +396,8 @@ bool AActor::FixMapthingPos() DEFINE_ACTION_FUNCTION(AActor, UnlinkFromWorld) { PARAM_SELF_PROLOGUE(AActor); - self->UnlinkFromWorld(nullptr); // fixme + PARAM_POINTER_DEF(ctx, FLinkContext); + self->UnlinkFromWorld(ctx); // fixme return 0; } @@ -530,7 +531,8 @@ void AActor::LinkToWorld(FLinkContext *ctx, bool spawningmapthing, sector_t *sec DEFINE_ACTION_FUNCTION(AActor, LinkToWorld) { PARAM_SELF_PROLOGUE(AActor); - self->LinkToWorld(nullptr); // fixme + PARAM_POINTER_DEF(ctx, FLinkContext); + self->LinkToWorld(ctx); return 0; } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 128455a39..22eae11a1 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -7754,6 +7754,13 @@ DEFINE_ACTION_FUNCTION(AActor, RotateVector) ACTION_RETURN_VEC2(DVector2(x, y).Rotated(angle)); } +DEFINE_ACTION_FUNCTION(AActor, Normalize180) +{ + PARAM_PROLOGUE; + PARAM_ANGLE(angle); + ACTION_RETURN_FLOAT(angle.Normalized180().Degrees); +} + DEFINE_ACTION_FUNCTION(AActor, DistanceBySpeed) { PARAM_SELF_PROLOGUE(AActor); diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 8133b1ad2..587fcf6de 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -28,6 +28,12 @@ struct FCheckPosition native void ClearLastRipped(); } +struct LinkContext +{ + voidptr sector_list; // really msecnode but that's not exported yet. + voidptr render_list; +} + class Actor : Thinker native { @@ -282,6 +288,7 @@ class Actor : Thinker native native static double absangle(double ang1, double ang2); native static Vector2 AngleToVector(double angle, double length = 1); native static Vector2 RotateVector(Vector2 vec, double angle); + native static double Normalize180(double ang); bool IsPointerEqual(int ptr_select1, int ptr_select2) @@ -433,8 +440,8 @@ class Actor : Thinker native native state FindState(statelabel st, bool exact = false); bool SetStateLabel(statelabel st, bool nofunction = false) { return SetState(FindState(st), nofunction); } native action state ResolveState(statelabel st); // this one, unlike FindState, is context aware. - native void LinkToWorld(); - native void UnlinkFromWorld(); + native void LinkToWorld(LinkContext ctx = null); + native void UnlinkFromWorld(out LinkContext ctx = null); native bool CanSeek(Actor target); native double AngleTo(Actor target, bool absolute = false); native void AddZ(double zadd, bool moving = true); diff --git a/wadsrc/static/zscript/shared/movingcamera.txt b/wadsrc/static/zscript/shared/movingcamera.txt index c5b8534db..0fc50f3e2 100644 --- a/wadsrc/static/zscript/shared/movingcamera.txt +++ b/wadsrc/static/zscript/shared/movingcamera.txt @@ -1,5 +1,51 @@ -class InterpolationPoint : Actor native +/* +** a_movingcamera.cpp +** Cameras that move and related neat stuff +** +**--------------------------------------------------------------------------- +** Copyright 1998-2016 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, self list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, self list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from self software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +/* +== InterpolationPoint: node along a camera's path +== +== args[0] = pitch +== args[1] = time (in octics) to get here from previous node +== args[2] = time (in octics) to stay here before moving to next node +== args[3] = low byte of next node's tid +== args[4] = high byte of next node's tid +*/ +class InterpolationPoint : Actor { + + InterpolationPoint Next; + default { +NOBLOCKMAP @@ -7,9 +53,61 @@ class InterpolationPoint : Actor native +DONTSPLASH RenderStyle "None"; } + + override void BeginPlay () + { + Super.BeginPlay (); + Next = null; + } + + void FormChain () + { + let me = self; + + while (me != null) + { + if (me.bAmbush) return; + + me.bAmbush = true; + + let iterator = ActorIterator.Create(me.args[3] + 256 * me.args[4], "InterpolationPoint"); + me.Next = InterpolationPoint(iterator.Next ()); + + if (me.Next == me) // Don't link to self + me.Next = InterpolationPoint(iterator.Next ()); + + int pt = (me.args[0] << 24) >> 24; // this is for truncating the value to a byte, presumably because some old WAD needs it... + me.Pitch = clamp(pt, -89, 89); + + if (me.Next == null && (me.args[3] | me.args[4])) + { + A_Log("Can't find target for camera node " .. me.tid); + } + + me = me.Next; + } + } + + // Return the node (if any) where a path loops, relative to self one. + InterpolationPoint ScanForLoop () + { + InterpolationPoint node = self; + while (node.Next && node.Next != self && node.special1 == 0) + { + node.special1 = 1; + node = node.Next; + } + return node.Next == self ? node : null; + } + } -class InterpolationSpecial : Actor native +/* +== InterpolationSpecial: Holds a special to execute when a +== PathFollower reaches an InterpolationPoint of the same TID. +*/ + +class InterpolationSpecial : Actor { default { @@ -18,9 +116,28 @@ class InterpolationSpecial : Actor native +NOGRAVITY +DONTSPLASH } + + override void Tick () {} // Does absolutely nothing itself + } -class PathFollower : Actor native +/* +== PathFollower: something that follows a camera path +== Base class for some moving cameras +== +== args[0] = low byte of first node in path's tid +== args[1] = high byte of first node's tid +== args[2] = bit 0 = follow a linear path (rather than curved) +== bit 1 = adjust angle +== bit 2 = adjust pitch +== bit 3 = aim in direction of motion +== +== Also uses: +== target = first node in path +== lastenemy = node prior to first node (if looped) +*/ + +class PathFollower : Actor { default { @@ -29,18 +146,436 @@ class PathFollower : Actor native +NOGRAVITY +DONTSPLASH } + + bool bActive, bJustStepped; + InterpolationPoint PrevNode, CurrNode; + double Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode.Next + int HoldTime; + + // Interpolate between p2 and p3 along a Catmull-Rom spline + // http://research.microsoft.com/~hollasch/cgindex/curves/catmull-rom.html + double Splerp (double p1, double p2, double p3, double p4) + { + double t = Time; + double res = 2*p2; + res += (p3 - p1) * Time; + t *= Time; + res += (2*p1 - 5*p2 + 4*p3 - p4) * t; + t *= Time; + res += (3*p2 - 3*p3 + p4 - p1) * t; + return 0.5 * res; + } + + // Linearly interpolate between p1 and p2 + double Lerp (double p1, double p2) + { + return p1 + Time * (p2 - p1); + } + + override void BeginPlay () + { + Super.BeginPlay (); + PrevNode = CurrNode = null; + bActive = false; + } + + override void PostBeginPlay () + { + // Find first node of path + let iterator = ActorIterator.Create(args[0] + 256 * args[1], "InterpolationPoint"); + let node = InterpolationPoint(iterator.Next ()); + InterpolationPoint prevnode; + + target = node; + + if (node == null) + { + A_Log ("PathFollower " .. tid .. ": Can't find interpolation pt " .. args[0] + 256 * args[1] .. "%d\n"); + return; + } + + // Verify the path has enough nodes + node.FormChain (); + if (args[2] & 1) + { // linear path; need 2 nodes + if (node.Next == null) + { + A_Log ("PathFollower " .. tid .. ": Path needs at least 2 nodes\n"); + return; + } + lastenemy = null; + } + else + { // spline path; need 4 nodes + if (node.Next == null || + node.Next.Next == null || + node.Next.Next.Next == null) + { + A_Log ("PathFollower " .. tid .. ": Path needs at least 4 nodes\n"); + return; + } + // If the first node is in a loop, we can start there. + // Otherwise, we need to start at the second node in the path. + prevnode = node.ScanForLoop (); + if (prevnode == null || prevnode.Next != node) + { + lastenemy = target; + target = node.Next; + } + else + { + lastenemy = prevnode; + } + } + } + + override void Deactivate (Actor activator) + { + bActive = false; + } + + override void Activate (Actor activator) + { + if (!bActive) + { + CurrNode = InterpolationPoint(target); + PrevNode = InterpolationPoint(lastenemy); + + if (CurrNode != null) + { + NewNode (); + SetOrigin (CurrNode.Pos, false); + Time = 0.; + HoldTime = 0; + bJustStepped = true; + bActive = true; + } + } + } + + override void Tick () + { + if (!bActive) + return; + + if (bJustStepped) + { + bJustStepped = false; + if (CurrNode.args[2]) + { + HoldTime = level.time + CurrNode.args[2] * TICRATE / 8; + SetXYZ(CurrNode.Pos); + } + } + + if (HoldTime > level.time) + return; + + // Splines must have a previous node. + if (PrevNode == null && !(args[2] & 1)) + { + bActive = false; + return; + } + + // All paths must have a current node. + if (CurrNode.Next == null) + { + bActive = false; + return; + } + + if (Interpolate ()) + { + Time += (8. / (CurrNode.args[1] * TICRATE)); + if (Time > 1.) + { + Time -= 1.; + bJustStepped = true; + PrevNode = CurrNode; + CurrNode = CurrNode.Next; + if (CurrNode != null) + NewNode (); + if (CurrNode == null || CurrNode.Next == null) + Deactivate (self); + if ((args[2] & 1) == 0 && CurrNode.Next.Next == null) + Deactivate (self); + } + } + } + + void NewNode () + { + let iterator = ActorIterator.Create(CurrNode.tid, "InterpolationSpecial"); + InterpolationSpecial spec; + + while ( (spec = InterpolationSpecial(iterator.Next ())) ) + { + A_CallSpecial(spec.special, spec.args[0], spec.args[1], spec.args[2], spec.args[3], spec.args[4]); + } + } + + virtual bool Interpolate () + { + Vector3 dpos = (0, 0, 0); + LinkContext ctx; + + if ((args[2] & 8) && Time > 0) + { + dpos = Pos; + } + + if (CurrNode.Next==null) return false; + + UnlinkFromWorld (ctx); + Vector3 newpos; + if (args[2] & 1) + { // linear + newpos.X = Lerp(CurrNode.pos.X, CurrNode.Next.pos.X); + newpos.Y = Lerp(CurrNode.pos.Y, CurrNode.Next.pos.Y); + newpos.Z = Lerp(CurrNode.pos.Z, CurrNode.Next.pos.Z); + } + else + { // spline + if (CurrNode.Next.Next==null) return false; + + newpos.X = Splerp(PrevNode.pos.X, CurrNode.pos.X, CurrNode.Next.pos.X, CurrNode.Next.Next.pos.X); + newpos.Y = Splerp(PrevNode.pos.Y, CurrNode.pos.Y, CurrNode.Next.pos.Y, CurrNode.Next.Next.pos.Y); + newpos.Z = Splerp(PrevNode.pos.Z, CurrNode.pos.Z, CurrNode.Next.pos.Z, CurrNode.Next.Next.pos.Z); + } + SetXYZ(newpos); + LinkToWorld (ctx); + + if (args[2] & 6) + { + if (args[2] & 8) + { + if (args[2] & 1) + { // linear + dpos.X = CurrNode.Next.pos.X - CurrNode.pos.X; + dpos.Y = CurrNode.Next.pos.Y - CurrNode.pos.Y; + dpos.Z = CurrNode.Next.pos.Z - CurrNode.pos.Z; + } + else if (Time > 0) + { // spline + dpos = newpos - dpos; + } + else + { + int realarg = args[2]; + args[2] &= ~(2|4|8); + Time += 0.1; + dpos = newpos; + Interpolate (); + Time -= 0.1; + args[2] = realarg; + dpos = newpos - dpos; + newpos -= dpos; + SetXYZ(newpos); + } + if (args[2] & 2) + { // adjust yaw + Angle = VectorAngle(dpos.X, dpos.Y); + } + if (args[2] & 4) + { // adjust pitch; + double dist = dpos.XY.Length(); + Pitch = dist != 0 ? VectorAngle(dist, -dpos.Z) : 0.; + } + } + else + { + if (args[2] & 2) + { // interpolate angle + double angle1 = Normalize180(CurrNode.angle); + double angle2 = angle1 + deltaangle(angle1, CurrNode.Next.angle); + angle = Lerp(angle1, angle2); + } + if (args[2] & 1) + { // linear + if (args[2] & 4) + { // interpolate pitch + Pitch = Lerp(CurrNode.Pitch, CurrNode.Next.Pitch); + } + } + else + { // spline + if (args[2] & 4) + { // interpolate pitch + Pitch = Splerp(PrevNode.Pitch, CurrNode.Pitch, + CurrNode.Next.Pitch, CurrNode.Next.Next.Pitch); + } + } + } + } + + return true; + } + } -class ActorMover : PathFollower native +/* +== ActorMover: Moves any actor along a camera path +== +== Same as PathFollower, except +== args[2], bit 7: make nonsolid +== args[3] = tid of thing to move +== +== also uses: +== tracer = thing to move +*/ + +class ActorMover : PathFollower { + override void BeginPlay() + { + ChangeStatNum(STAT_ACTORMOVER); + } + + override void PostBeginPlay () + { + Super.PostBeginPlay (); + + let iterator = ActorIterator.Create(args[3]); + tracer = iterator.Next (); + + if (tracer == null) + { + A_Log("ActorMover " .. tid .. ": Can't find target " .. args[3] .. "\n"); + } + else + { + special1 = tracer.bNoGravity + (tracer.bNoBlockmap<<1) + (tracer.bSolid<<2) + (tracer.bInvulnerable<<4) + (tracer.bDormant<<8); + } + } + + override bool Interpolate () + { + if (tracer == null) + return true; + + if (Super.Interpolate ()) + { + double savedz = tracer.pos.Z; + tracer.SetZ(pos.Z); + if (!tracer.TryMove (Pos.XY, true)) + { + tracer.SetZ(savedz); + return false; + } + + if (args[2] & 2) + tracer.angle = angle; + if (args[2] & 4) + tracer.Pitch = Pitch; + + return true; + } + return false; + } + + override void Activate (Actor activator) + { + if (tracer == null || bActive) + return; + + Super.Activate (activator); + let tracer = self.tracer; + special1 = tracer.bNoGravity + (tracer.bNoBlockmap<<1) + (tracer.bSolid<<2) + (tracer.bInvulnerable<<4) + (tracer.bDormant<<8); + bNoGravity = true; + if (args[2] & 128) + { + LinkContext ctx; + tracer.UnlinkFromWorld (ctx); + bNoBlockmap = true; + bSolid = false; + tracer.LinkToWorld (ctx); + } + if (tracer.bIsMonster) + { + bInvulnerable = true; + bDormant = true; + } + // Don't let the renderer interpolate between the actor's + // old position and its new position. + Interpolate (); + tracer.ClearInterpolation(); + } + + override void Deactivate (Actor activator) + { + if (bActive) + { + Super.Deactivate (activator); + let tracer = self.tracer; + if (tracer != null) + { + LinkContext ctx; + tracer.UnlinkFromWorld (ctx); + tracer.bNoGravity = (special1 & 1); + tracer.bNoBlockmap = !!(special1 & 2); + tracer.bSolid = !!(special1 & 4); + tracer.bInvulnerable = !!(special1 & 8); + tracer.bDormant = !!(special1 & 16); + tracer.LinkToWorld (ctx); + } + } + } } -class MovingCamera : PathFollower native +/* +== MovingCamera: Moves any actor along a camera path +== +== Same as PathFollower, except +== args[3] = tid of thing to look at (0 if none) +== +== Also uses: +== tracer = thing to look at +*/ + +class MovingCamera : PathFollower { + Actor activator; default { CameraHeight 0; } + + override void PostBeginPlay () + { + Super.PostBeginPlay (); + + Activator = null; + if (args[3] != 0) + { + let iterator = ActorIterator.Create(args[3]); + tracer = iterator.Next (); + if (tracer == null) + { + A_Log("MovingCamera " .. tid .. ": Can't find thing " .. args[3] .. "\n"); + } + } + } + + override bool Interpolate () + { + if (tracer == null) + return Super.Interpolate (); + + if (Super.Interpolate ()) + { + angle = AngleTo(tracer, true); + + if (args[2] & 4) + { // Also aim camera's pitch; + Vector3 diff = Pos - tracer.Pos - (0, 0, tracer.Height / 2); + double dist = diff.XY.Length(); + Pitch = dist != 0 ? VectorAngle(dist, diff.Z) : 0.; + } + + return true; + } + return false; + } + } - -