/* =========================================================================== Copyright (C) 2015 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // gravpath.cpp: Gravity path - Used for underwater currents and wells. #include "g_local.h" #include "entity.h" #include "gravpath.h" #include "../corepp/container.h" #include "navigate.h" #include "misc.h" #include "player.h" #include "g_phys.h" #include "debuglines.h" GravPathManager gravPathManager; CLASS_DECLARATION(Class, GravPathManager, NULL) { {NULL, NULL} }; GravPathManager::~GravPathManager() { Reset(); } void GravPathManager::Reset(void) { while (pathList.NumObjects() > 0) { delete (GravPath *)pathList.ObjectAt(1); } pathList.FreeObjectList(); } void GravPathManager::AddPath(GravPath *p) { pathList.AddObject(p); } void GravPathManager::RemovePath(GravPath *p) { pathList.RemoveObject(p); } void GravPathManager::DrawGravPaths(void) { int i; int num = pathList.NumObjects(); for (i = 1; i <= num; i++) { GravPath *p = (GravPath *)pathList.ObjectAt(i); p->DrawPath(1, 0, 0); } } Vector GravPathManager::CalculateGravityPull(Entity& ent, Vector pos, qboolean *force, float *max_speed) { int i, num; GravPath *p; GravPathNode *node; Vector point; Vector newpoint; Vector dir; float bestdist = 99999; float dist; float speed; float radius; Vector velocity; int bestpath = 0; int entity_contents, grav_contents; num = pathList.NumObjects(); if (!num) { return vec_zero; } entity_contents = gi.pointcontents(ent.origin, 0); for (i = 1; i <= num; i++) { p = (GravPath *)pathList.ObjectAt(i); if (!p) { continue; } // Check to see if path is active node = p->GetNode(1); if (!node || !node->active) { continue; } // Check to see if the contents are the same grav_contents = gi.pointcontents(node->origin, 0); // If grav node is in water, make sure ent is too. if ((grav_contents & CONTENTS_WATER) && !(entity_contents & CONTENTS_WATER)) { continue; } // Test to see if we are in this path's bounding box if ((pos.x < p->maxs.x) && (pos.y < p->maxs.y) && (pos.z < p->maxs.z) && (pos.x > p->mins.x) && (pos.y > p->mins.y) && (pos.z > p->mins.z)) { point = p->ClosestPointOnPath(pos, ent, &dist, &speed, &radius); // If the closest distance on the path is greater than the radius, then // do not consider this path. if (dist > radius) { continue; } else if (dist < bestdist) { bestpath = i; bestdist = dist; } } } if (!bestpath) { return vec_zero; } p = (GravPath *)pathList.ObjectAt(bestpath); if (!p) { return velocity; } *force = p->force; dist = p->DistanceAlongPath(pos, &speed); newpoint = p->PointAtDistance(pos, dist + speed, ent.isSubclassOf(Player), max_speed); dir = newpoint - pos; dir.normalize(); velocity = dir * speed; //velocity *= .75; return velocity; } /*****************************************************************************/ /*QUAKED info_grav_pathnode (0 0 .5) (-16 -16 0) (16 16 32) HEADNODE FORCE PULL_UPWARDS "radius" Radius of the effect of the pull (Default is 256) "speed" Speed of the pull (Use negative for a repulsion) (Default is 100) Set HEADNODE to signify the head of the path. Set FORCE if you want un-fightable gravity ( i.e. can't go backwards ) Set PULL_UPWARDS if you want the gravnodes to pull you upwards also ******************************************************************************/ #define PULL_UPWARDS (1 << 2) Event EV_GravPath_Create ( "gravpath_create", EV_DEFAULT, NULL, NULL, "Create the grav path.", EV_NORMAL ); Event EV_GravPath_Activate ( "activate", EV_DEFAULT, NULL, NULL, "Activate the grav path.", EV_NORMAL ); Event EV_GravPath_Deactivate ( "deactivate", EV_DEFAULT, NULL, NULL, "Deactivate the grav path.", EV_NORMAL ); Event EV_GravPath_SetSpeed ( "speed", EV_DEFAULT, "f", "speed", "Set the speed of the grav path.", EV_NORMAL ); Event EV_GravPath_SetMaxSpeed ( "maxspeed", EV_DEFAULT, "f", "maxspeed", "Set the max speed of the grav path.", EV_NORMAL ); Event EV_GravPath_SetRadius ( "radius", EV_DEFAULT, "f", "radius", "Set the radius of the grav path.", EV_NORMAL ); CLASS_DECLARATION(Entity, GravPathNode, "info_grav_pathnode") { {&EV_GravPath_Create, &GravPathNode::CreatePath }, {&EV_GravPath_Activate, &GravPathNode::Activate }, {&EV_GravPath_Deactivate, &GravPathNode::Deactivate }, {&EV_GravPath_SetSpeed, &GravPathNode::SetSpeed }, {&EV_GravPath_SetMaxSpeed, &GravPathNode::SetMaxSpeed}, {&EV_GravPath_SetRadius, &GravPathNode::SetRadius }, {NULL, NULL } }; GravPathNode::GravPathNode() { if (LoadingSavegame) { // all data will be setup by the archive function return; } setMoveType(MOVETYPE_NONE); setSolidType(SOLID_NOT); hideModel(); speed = 100.0f; max_speed = 200.0f; radius = 256.0f; headnode = spawnflags & 1; active = true; // This is the head of a new path, post an event to create the path if (headnode) { PostEvent(EV_GravPath_Create, 0); } } void GravPathNode::SetSpeed(Event *ev) { speed = ev->GetFloat(1); } void GravPathNode::SetMaxSpeed(Event *ev) { max_speed = ev->GetFloat(1); } void GravPathNode::SetRadius(Event *ev) { radius = ev->GetFloat(1); } float GravPathNode::Speed(void) { if (active) { return speed; } else { return 0; } } float GravPathNode::MaxSpeed(void) { return max_speed; } void GravPathNode::Activate(Event *ev) { GravPathNode *node; Entity *ent; const char *target; active = true; node = this; // Go through the entire path and activate it target = node->Target(); while (target[0]) { ent = (Entity *)G_FindTarget(NULL, target); if (ent) { node = (GravPathNode *)ent; assert(node); node->active = true; } else { gi.Error(ERR_DROP, "GravPathNode::CreatePath: target %s not found\n", target); } target = node->Target(); } } void GravPathNode::Deactivate(Event *ev) { GravPathNode *node; Entity *ent; const char *target; active = false; node = this; // Go through the entire path and activate it target = node->Target(); while (target[0]) { ent = (Entity *)G_FindTarget(NULL, target); if (ent) { node = (GravPathNode *)ent; assert(node); node->active = false; } else { gi.Error(ERR_DROP, "GravPathNode::CreatePath: target %s not found\n", target); } target = node->Target(); } } void GravPathNode::CreatePath(Event *ev) { const char *target; GravPath *path = new GravPath; GravPathNode *node; Entity *ent; ClearBounds(path->mins, path->maxs); // This node is the head of a path, create a new path in the path manager. // and add it in, then add all of it's children in the path. node = this; path->AddNode(node); path->force = spawnflags & 2; // Make the path from the targetlist. target = node->Target(); while (target[0]) { ent = (Entity *)G_FindTarget(NULL, target); if (ent) { node = (GravPathNode *)ent; assert(node); path->AddNode(node); } else { gi.Error(ERR_DROP, "GravPathNode::CreatePath: target %s not found\n", target); } target = node->Target(); } // Set the origin. path->origin = path->mins + path->maxs; path->origin *= 0.5f; } CLASS_DECLARATION(Listener, GravPath, NULL) { {NULL, NULL} }; GravPath::GravPath() { pathlength = 0; from = NULL; to = NULL; nextnode = 1; if (!LoadingSavegame) { gravPathManager.AddPath(this); } } GravPath::~GravPath() { pathlength = 0; from = NULL; to = NULL; nextnode = 1; gravPathManager.RemovePath(this); } void GravPath::Clear(void) { nextnode = 1; pathlength = 0; from = NULL; to = NULL; pathlist.FreeObjectList(); } void GravPath::Reset(void) { nextnode = 1; } GravPathNode *GravPath::Start(void) { return from; } GravPathNode *GravPath::End(void) { return to; } void GravPath::AddNode(GravPathNode *node) { int num; Vector r, addp; if (!from) { from = node; } to = node; pathlist.AddObject(GravPathNodePtr(node)); num = NumNodes(); if (num > 1) { pathlength += (node->origin - GetNode(num)->origin).length(); } r.setXYZ(node->Radius(), node->Radius(), node->Radius()); addp = node->origin + r; AddPointToBounds(addp, mins, maxs); addp = node->origin - r; AddPointToBounds(addp, mins, maxs); } GravPathNode *GravPath::GetNode(int num) { return pathlist.ObjectAt(num); } GravPathNode *GravPath::NextNode(void) { if (nextnode <= NumNodes()) { return pathlist.ObjectAt(nextnode++); } return NULL; } Vector GravPath::ClosestPointOnPath(Vector pos, Entity& ent, float *ret_dist, float *speed, float *radius) { GravPathNode *s; GravPathNode *e; int num; int i; float bestdist; Vector bestpoint; float dist; float segmentlength; Vector delta; Vector p1; Vector p2; Vector p3; float t; trace_t trace; num = NumNodes(); s = GetNode(1); trace = G_Trace(pos, ent.mins, ent.maxs, s->origin, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 1"); bestpoint = s->origin; delta = bestpoint - pos; bestdist = delta.length(); *speed = s->Speed(); *radius = s->Radius(); for (i = 2; i <= num; i++) { e = GetNode(i); // check if we're closest to the endpoint delta = e->origin - pos; dist = delta.length(); if (dist < bestdist) { trace = G_Trace( pos, ent.mins, ent.maxs, e->origin, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 2" ); bestdist = dist; bestpoint = e->origin; *speed = e->Speed(); *radius = e->Radius(); } // check if we're closest to the segment p1 = e->origin - s->origin; segmentlength = p1.length(); p1 *= 1 / segmentlength; p2 = pos - s->origin; t = p1 * p2; if ((t > 0) && (t < segmentlength)) { p3 = (p1 * t) + s->origin; delta = p3 - pos; dist = delta.length(); if (dist < bestdist) { trace = G_Trace( pos, ent.mins, ent.maxs, p3, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 3" ); bestdist = dist; bestpoint = p3; *speed = (e->Speed() * t) + (s->Speed() * (1.0f - t)); *radius = (e->Radius() * t) + (s->Radius() * (1.0f - t)); } } s = e; } *ret_dist = bestdist; return bestpoint; } float GravPath::DistanceAlongPath(Vector pos, float *speed) { GravPathNode *s; GravPathNode *e; int num; int i; float bestdist; float dist; float segmentlength; Vector delta; Vector segment; Vector p1; Vector p2; Vector p3; float t; float pathdist; float bestdistalongpath; float oosl; pathdist = 0; num = NumNodes(); s = GetNode(1); delta = s->origin - pos; bestdist = delta.length(); bestdistalongpath = 0; *speed = s->Speed(); for (i = 2; i <= num; i++) { e = GetNode(i); segment = e->origin - s->origin; segmentlength = segment.length(); // check if we're closest to the endpoint delta = e->origin - pos; dist = delta.length(); if (dist < bestdist) { bestdist = dist; bestdistalongpath = pathdist + segmentlength; *speed = e->Speed(); } // check if we're closest to the segment oosl = (1 / segmentlength); p1 = segment * oosl; p1.normalize(); p2 = pos - s->origin; t = p1 * p2; if ((t > 0) && (t < segmentlength)) { p3 = (p1 * t) + s->origin; delta = p3 - pos; dist = delta.length(); if (dist < bestdist) { bestdist = dist; bestdistalongpath = pathdist + t; t *= oosl; *speed = (e->Speed() * t) + (s->Speed() * (1.0f - t)); } } s = e; pathdist += segmentlength; } return bestdistalongpath; } Vector GravPath::PointAtDistance(Vector pos, float dist, qboolean is_player, float *max_speed) { GravPathNode *s; GravPathNode *e; int num; int i; Vector delta; Vector p1; float t; float pathdist; float segmentlength; num = NumNodes(); s = GetNode(1); pathdist = 0; for (i = 2; i <= num; i++) { e = GetNode(i); delta = e->origin - s->origin; segmentlength = delta.length(); if ((pathdist + segmentlength) > dist) { t = dist - pathdist; p1 = delta * (t / segmentlength); // return p1 + s->origin; if (e->spawnflags & PULL_UPWARDS && is_player) { p1.z = p1.length() / 2; } *max_speed = e->MaxSpeed(); return p1 + pos; } s = e; pathdist += segmentlength; } *max_speed = s->MaxSpeed(); // cap it off at start or end of path return s->origin; } void GravPath::DrawPath(float r, float g, float b) { Vector s; Vector e; Vector offset; GravPathNode *node; int num; int i; num = NumNodes(); node = GetNode(1); s = node->origin; G_DebugBBox(s, Vector(8, 8, 8), Vector(-8, -8, -8), 0, 1, 0, 1); offset = Vector(r, g, b) * 4 + Vector(0, 0, 0); offset = Vector(0, 0, 0); for (i = 2; i <= num; i++) { node = GetNode(i); e = node->origin; G_DebugLine(s + offset, e + offset, r, g, b, 1); G_DebugBBox(e, Vector(8, 8, 8), Vector(-8, -8, -8), 0, 1, 0, 1); s = e; } G_DebugBBox(origin, mins - origin, maxs - origin, 1, 0, 0, 1); } int GravPath::NumNodes(void) { return pathlist.NumObjects(); } float GravPath::Length(void) { return pathlength; }