/* * Add steering behaviors * * * ***** BEGIN GPL LICENSE BLOCK ***** * * This program 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. * * This program 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. * All rights reserved. * * The Original Code is: all of this file. * * Contributor(s): none yet. * * ***** END GPL LICENSE BLOCK ***** */ #include "BLI_math.h" #include "KX_SteeringActuator.h" #include "KX_GameObject.h" #include "KX_NavMeshObject.h" #include "KX_ObstacleSimulation.h" #include "KX_PythonInit.h" #include "KX_PyMath.h" #include "Recast.h" /* ------------------------------------------------------------------------- */ /* Native functions */ /* ------------------------------------------------------------------------- */ KX_SteeringActuator::KX_SteeringActuator(SCA_IObject *gameobj, int mode, KX_GameObject *target, KX_GameObject *navmesh, float distance, float velocity, float acceleration, float turnspeed, bool isSelfTerminated, int pathUpdatePeriod, KX_ObstacleSimulation* simulation, short facingmode, bool normalup, bool enableVisualization, bool lockzvel) : SCA_IActuator(gameobj, KX_ACT_STEERING), m_target(target), m_mode(mode), m_distance(distance), m_velocity(velocity), m_acceleration(acceleration), m_turnspeed(turnspeed), m_simulation(simulation), m_updateTime(0), m_obstacle(NULL), m_isActive(false), m_isSelfTerminated(isSelfTerminated), m_enableVisualization(enableVisualization), m_facingMode(facingmode), m_normalUp(normalup), m_pathLen(0), m_pathUpdatePeriod(pathUpdatePeriod), m_lockzvel(lockzvel), m_wayPointIdx(-1), m_steerVec(MT_Vector3(0, 0, 0)) { m_navmesh = static_cast(navmesh); if (m_navmesh) m_navmesh->RegisterActuator(this); if (m_target) m_target->RegisterActuator(this); if (m_simulation) m_obstacle = m_simulation->GetObstacle((KX_GameObject*)gameobj); KX_GameObject* parent = ((KX_GameObject*)gameobj)->GetParent(); if (m_facingMode>0 && parent) { m_parentlocalmat = parent->GetSGNode()->GetLocalOrientation(); } else m_parentlocalmat.setIdentity(); } KX_SteeringActuator::~KX_SteeringActuator() { if (m_navmesh) m_navmesh->UnregisterActuator(this); if (m_target) m_target->UnregisterActuator(this); } CValue* KX_SteeringActuator::GetReplica() { KX_SteeringActuator* replica = new KX_SteeringActuator(*this); // replication just copy the m_base pointer => common random generator replica->ProcessReplica(); return replica; } void KX_SteeringActuator::ProcessReplica() { if (m_target) m_target->RegisterActuator(this); if (m_navmesh) m_navmesh->RegisterActuator(this); SCA_IActuator::ProcessReplica(); } void KX_SteeringActuator::ReParent(SCA_IObject* parent) { SCA_IActuator::ReParent(parent); if (m_simulation) m_obstacle = m_simulation->GetObstacle((KX_GameObject*)m_gameobj); } bool KX_SteeringActuator::UnlinkObject(SCA_IObject* clientobj) { if (clientobj == m_target) { m_target = NULL; return true; } else if (clientobj == m_navmesh) { m_navmesh = NULL; return true; } return false; } void KX_SteeringActuator::Relink(CTR_Map *obj_map) { void **h_obj = (*obj_map)[m_target]; if (h_obj) { if (m_target) m_target->UnregisterActuator(this); m_target = (KX_GameObject*)(*h_obj); m_target->RegisterActuator(this); } h_obj = (*obj_map)[m_navmesh]; if (h_obj) { if (m_navmesh) m_navmesh->UnregisterActuator(this); m_navmesh = (KX_NavMeshObject*)(*h_obj); m_navmesh->RegisterActuator(this); } } bool KX_SteeringActuator::Update(double curtime, bool frame) { if (frame) { double delta = curtime - m_updateTime; m_updateTime = curtime; if (m_posevent && !m_isActive) { delta = 0.0; m_pathUpdateTime = -1.0; m_updateTime = curtime; m_isActive = true; } bool bNegativeEvent = IsNegativeEvent(); if (bNegativeEvent) m_isActive = false; RemoveAllEvents(); if (!delta) return true; if (bNegativeEvent || !m_target) return false; // do nothing on negative events KX_GameObject *obj = (KX_GameObject*) GetParent(); const MT_Point3& mypos = obj->NodeGetWorldPosition(); const MT_Point3& targpos = m_target->NodeGetWorldPosition(); MT_Vector3 vectotarg = targpos - mypos; MT_Vector3 vectotarg2d = vectotarg; vectotarg2d.z() = 0.0f; m_steerVec = MT_Vector3(0.0f, 0.0f, 0.0f); bool apply_steerforce = false; bool terminate = true; switch (m_mode) { case KX_STEERING_SEEK: if (vectotarg2d.length2()>m_distance*m_distance) { terminate = false; m_steerVec = vectotarg; m_steerVec.normalize(); apply_steerforce = true; } break; case KX_STEERING_FLEE: if (vectotarg2d.length2()m_distance*m_distance) { terminate = false; static const MT_Scalar WAYPOINT_RADIUS(0.25f); if (m_pathUpdateTime<0 || (m_pathUpdatePeriod>=0 && curtime - m_pathUpdateTime>((double)m_pathUpdatePeriod/1000.0))) { m_pathUpdateTime = curtime; m_pathLen = m_navmesh->FindPath(mypos, targpos, m_path, MAX_PATH_LENGTH); m_wayPointIdx = m_pathLen > 1 ? 1 : -1; } if (m_wayPointIdx>0) { MT_Vector3 waypoint(&m_path[3*m_wayPointIdx]); if ((waypoint-mypos).length2()=m_pathLen) { m_wayPointIdx = -1; terminate = true; } else waypoint.setValue(&m_path[3*m_wayPointIdx]); } m_steerVec = waypoint - mypos; apply_steerforce = true; if (m_enableVisualization) { //debug draw static const MT_Vector3 PATH_COLOR(1.0f,0.0f,0.0f); m_navmesh->DrawPath(m_path, m_pathLen, PATH_COLOR); } } } break; } if (apply_steerforce) { bool isdyna = obj->IsDynamic(); if (isdyna) m_steerVec.z() = 0; if (!m_steerVec.fuzzyZero()) m_steerVec.normalize(); MT_Vector3 newvel = m_velocity * m_steerVec; //adjust velocity to avoid obstacles if (m_simulation && m_obstacle /*&& !newvel.fuzzyZero()*/) { if (m_enableVisualization) KX_RasterizerDrawDebugLine(mypos, mypos + newvel, MT_Vector3(1.0f, 0.0f, 0.0f)); m_simulation->AdjustObstacleVelocity(m_obstacle, m_mode!=KX_STEERING_PATHFOLLOWING ? m_navmesh : NULL, newvel, m_acceleration*(float)delta, m_turnspeed/(180.0f*(float)(M_PI*delta))); if (m_enableVisualization) KX_RasterizerDrawDebugLine(mypos, mypos + newvel, MT_Vector3(0.0f, 1.0f, 0.0f)); } HandleActorFace(newvel); if (isdyna) { //temporary solution: set 2D steering velocity directly to obj //correct way is to apply physical force MT_Vector3 curvel = obj->GetLinearVelocity(); if (m_lockzvel) newvel.z() = 0.0f; else newvel.z() = curvel.z(); obj->setLinearVelocity(newvel, false); } else { MT_Vector3 movement = delta*newvel; obj->ApplyMovement(movement, false); } } else { if (m_simulation && m_obstacle) { m_obstacle->dvel[0] = 0.f; m_obstacle->dvel[1] = 0.f; } } if (terminate && m_isSelfTerminated) return false; } return true; } const MT_Vector3& KX_SteeringActuator::GetSteeringVec() { static MT_Vector3 ZERO_VECTOR(0, 0, 0); if (m_isActive) return m_steerVec; else return ZERO_VECTOR; } inline float vdot2(const float* a, const float* b) { return a[0]*b[0] + a[2]*b[2]; } static bool barDistSqPointToTri(const float* p, const float* a, const float* b, const float* c) { float v0[3], v1[3], v2[3]; rcVsub(v0, c,a); rcVsub(v1, b,a); rcVsub(v2, p,a); const float dot00 = vdot2(v0, v0); const float dot01 = vdot2(v0, v1); const float dot02 = vdot2(v0, v2); const float dot11 = vdot2(v1, v1); const float dot12 = vdot2(v1, v2); // Compute barycentric coordinates float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); float u = (dot11 * dot02 - dot01 * dot12) * invDenom; float v = (dot00 * dot12 - dot01 * dot02) * invDenom; float ud = u<0.f ? -u : (u>1.f ? u-1.f : 0.f); float vd = v<0.f ? -v : (v>1.f ? v-1.f : 0.f); return ud * ud + vd * vd; } inline void flipAxes(float* vec) { std::swap(vec[1],vec[2]); } static bool getNavmeshNormal(dtStatNavMesh* navmesh, const MT_Vector3& pos, MT_Vector3& normal) { static const float polyPickExt[3] = {2, 4, 2}; float spos[3]; pos.getValue(spos); flipAxes(spos); dtStatPolyRef sPolyRef = navmesh->findNearestPoly(spos, polyPickExt); if (sPolyRef == 0) return false; const dtStatPoly* p = navmesh->getPoly(sPolyRef-1); const dtStatPolyDetail* pd = navmesh->getPolyDetail(sPolyRef-1); float distMin = FLT_MAX; int idxMin = -1; for (int i = 0; i < pd->ntris; ++i) { const unsigned char* t = navmesh->getDetailTri(pd->tbase+i); const float* v[3]; for (int j = 0; j < 3; ++j) { if (t[j] < p->nv) v[j] = navmesh->getVertex(p->v[t[j]]); else v[j] = navmesh->getDetailVertex(pd->vbase+(t[j]-p->nv)); } float dist = barDistSqPointToTri(spos, v[0], v[1], v[2]); if (dist=0) { const unsigned char* t = navmesh->getDetailTri(pd->tbase+idxMin); const float* v[3]; for (int j = 0; j < 3; ++j) { if (t[j] < p->nv) v[j] = navmesh->getVertex(p->v[t[j]]); else v[j] = navmesh->getDetailVertex(pd->vbase+(t[j]-p->nv)); } MT_Vector3 tri[3]; for (size_t j=0; j<3; j++) tri[j].setValue(v[j][0],v[j][2],v[j][1]); MT_Vector3 a,b; a = tri[1]-tri[0]; b = tri[2]-tri[0]; normal = b.cross(a).safe_normalized(); return true; } return false; } void KX_SteeringActuator::HandleActorFace(MT_Vector3& velocity) { if (m_facingMode==0 && (!m_navmesh || !m_normalUp)) return; KX_GameObject* curobj = (KX_GameObject*) GetParent(); MT_Vector3 dir = m_facingMode==0 ? curobj->NodeGetLocalOrientation().getColumn(1) : velocity; if (dir.fuzzyZero()) return; dir.normalize(); MT_Vector3 up(0,0,1); MT_Vector3 left; MT_Matrix3x3 mat; if (m_navmesh && m_normalUp) { dtStatNavMesh* navmesh = m_navmesh->GetNavMesh(); MT_Vector3 normal; MT_Vector3 trpos = m_navmesh->TransformToLocalCoords(curobj->NodeGetWorldPosition()); if (getNavmeshNormal(navmesh, trpos, normal)) { left = (dir.cross(up)).safe_normalized(); dir = (-left.cross(normal)).safe_normalized(); up = normal; } } switch (m_facingMode) { case 1: // TRACK X { left = dir.safe_normalized(); dir = -(left.cross(up)).safe_normalized(); break; }; case 2: // TRACK Y { left = (dir.cross(up)).safe_normalized(); break; } case 3: // track Z { left = up.safe_normalized(); up = dir.safe_normalized(); dir = left; left = (dir.cross(up)).safe_normalized(); break; } case 4: // TRACK -X { left = -dir.safe_normalized(); dir = -(left.cross(up)).safe_normalized(); break; }; case 5: // TRACK -Y { left = (-dir.cross(up)).safe_normalized(); dir = -dir; break; } case 6: // track -Z { left = up.safe_normalized(); up = -dir.safe_normalized(); dir = left; left = (dir.cross(up)).safe_normalized(); break; } } mat.setValue ( left[0], dir[0],up[0], left[1], dir[1],up[1], left[2], dir[2],up[2] ); KX_GameObject* parentObject = curobj->GetParent(); if (parentObject) { MT_Point3 localpos; localpos = curobj->GetSGNode()->GetLocalPosition(); MT_Matrix3x3 parentmatinv; parentmatinv = parentObject->NodeGetWorldOrientation ().inverse (); mat = parentmatinv * mat; mat = m_parentlocalmat * mat; curobj->NodeSetLocalOrientation(mat); curobj->NodeSetLocalPosition(localpos); } else { curobj->NodeSetLocalOrientation(mat); } } #ifdef WITH_PYTHON /* ------------------------------------------------------------------------- */ /* Python functions */ /* ------------------------------------------------------------------------- */ /* Integration hooks ------------------------------------------------------- */ PyTypeObject KX_SteeringActuator::Type = { PyVarObject_HEAD_INIT(NULL, 0) "KX_SteeringActuator", sizeof(PyObjectPlus_Proxy), 0, py_base_dealloc, 0, 0, 0, 0, py_base_repr, 0,0,0,0,0,0,0,0,0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 0,0,0,0,0,0,0, Methods, 0, 0, &SCA_IActuator::Type, 0,0,0,0,0,0, py_base_new }; PyMethodDef KX_SteeringActuator::Methods[] = { {NULL,NULL} //Sentinel }; PyAttributeDef KX_SteeringActuator::Attributes[] = { KX_PYATTRIBUTE_INT_RW("behavior", KX_STEERING_NODEF+1, KX_STEERING_MAX-1, true, KX_SteeringActuator, m_mode), KX_PYATTRIBUTE_RW_FUNCTION("target", KX_SteeringActuator, pyattr_get_target, pyattr_set_target), KX_PYATTRIBUTE_RW_FUNCTION("navmesh", KX_SteeringActuator, pyattr_get_navmesh, pyattr_set_navmesh), KX_PYATTRIBUTE_FLOAT_RW("distance", 0.0f, 1000.0f, KX_SteeringActuator, m_distance), KX_PYATTRIBUTE_FLOAT_RW("velocity", 0.0f, 1000.0f, KX_SteeringActuator, m_velocity), KX_PYATTRIBUTE_FLOAT_RW("acceleration", 0.0f, 1000.0f, KX_SteeringActuator, m_acceleration), KX_PYATTRIBUTE_FLOAT_RW("turnspeed", 0.0f, 720.0f, KX_SteeringActuator, m_turnspeed), KX_PYATTRIBUTE_BOOL_RW("selfterminated", KX_SteeringActuator, m_isSelfTerminated), KX_PYATTRIBUTE_BOOL_RW("enableVisualization", KX_SteeringActuator, m_enableVisualization), KX_PYATTRIBUTE_RO_FUNCTION("steeringVec", KX_SteeringActuator, pyattr_get_steeringVec), KX_PYATTRIBUTE_SHORT_RW("facingMode", 0, 6, true, KX_SteeringActuator, m_facingMode), KX_PYATTRIBUTE_INT_RW("pathUpdatePeriod", -1, 100000, true, KX_SteeringActuator, m_pathUpdatePeriod), KX_PYATTRIBUTE_BOOL_RW("lockZVelocity", KX_SteeringActuator, m_lockzvel), { NULL } //Sentinel }; PyObject *KX_SteeringActuator::pyattr_get_target(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef) { KX_SteeringActuator* actuator = static_cast(self); if (!actuator->m_target) Py_RETURN_NONE; else return actuator->m_target->GetProxy(); } int KX_SteeringActuator::pyattr_set_target(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value) { KX_SteeringActuator* actuator = static_cast(self); KX_GameObject *gameobj; if (!ConvertPythonToGameObject(actuator->GetLogicManager(), value, &gameobj, true, "actuator.object = value: KX_SteeringActuator")) return PY_SET_ATTR_FAIL; // ConvertPythonToGameObject sets the error if (actuator->m_target != NULL) actuator->m_target->UnregisterActuator(actuator); actuator->m_target = (KX_GameObject*) gameobj; if (actuator->m_target) actuator->m_target->RegisterActuator(actuator); return PY_SET_ATTR_SUCCESS; } PyObject *KX_SteeringActuator::pyattr_get_navmesh(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef) { KX_SteeringActuator* actuator = static_cast(self); if (!actuator->m_navmesh) Py_RETURN_NONE; else return actuator->m_navmesh->GetProxy(); } int KX_SteeringActuator::pyattr_set_navmesh(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value) { KX_SteeringActuator* actuator = static_cast(self); KX_GameObject *gameobj; if (!ConvertPythonToGameObject(actuator->GetLogicManager(), value, &gameobj, true, "actuator.object = value: KX_SteeringActuator")) return PY_SET_ATTR_FAIL; // ConvertPythonToGameObject sets the error if (dynamic_cast(gameobj) == NULL) { PyErr_Format(PyExc_TypeError, "KX_NavMeshObject is expected"); return PY_SET_ATTR_FAIL; } if (actuator->m_navmesh != NULL) actuator->m_navmesh->UnregisterActuator(actuator); actuator->m_navmesh = static_cast(gameobj); if (actuator->m_navmesh) actuator->m_navmesh->RegisterActuator(actuator); return PY_SET_ATTR_SUCCESS; } PyObject *KX_SteeringActuator::pyattr_get_steeringVec(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef) { KX_SteeringActuator* actuator = static_cast(self); const MT_Vector3& steeringVec = actuator->GetSteeringVec(); return PyObjectFrom(steeringVec); } #endif // WITH_PYTHON /* eof */