Blender V2.61 - r43446
|
00001 /* 00002 * Simulation for obstacle avoidance behavior 00003 * 00004 * ***** BEGIN GPL LICENSE BLOCK ***** 00005 * 00006 * This program is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU General Public License 00008 * as published by the Free Software Foundation; either version 2 00009 * of the License, or (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License 00017 * along with this program; if not, write to the Free Software Foundation, 00018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 * 00020 * Contributor(s): none yet. 00021 * 00022 * ***** END GPL LICENSE BLOCK ***** 00023 */ 00024 00025 #include "KX_ObstacleSimulation.h" 00026 #include "KX_NavMeshObject.h" 00027 #include "KX_PythonInit.h" 00028 #include "DNA_object_types.h" 00029 #include "BLI_math.h" 00030 00031 namespace 00032 { 00033 inline float perp(const MT_Vector2& a, const MT_Vector2& b) { return a.x()*b.y() - a.y()*b.x(); } 00034 00035 inline float sqr(float x) { return x*x; } 00036 inline float lerp(float a, float b, float t) { return a + (b-a)*t; } 00037 inline float clamp(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } 00038 00039 inline float vdistsqr(const float* a, const float* b) { return sqr(b[0]-a[0]) + sqr(b[1]-a[1]); } 00040 inline float vdist(const float* a, const float* b) { return sqrtf(vdistsqr(a,b)); } 00041 inline void vcpy(float* a, const float* b) { a[0]=b[0]; a[1]=b[1]; } 00042 inline float vdot(const float* a, const float* b) { return a[0]*b[0] + a[1]*b[1]; } 00043 inline float vperp(const float* a, const float* b) { return a[0]*b[1] - a[1]*b[0]; } 00044 inline void vsub(float* v, const float* a, const float* b) { v[0] = a[0]-b[0]; v[1] = a[1]-b[1]; } 00045 inline void vadd(float* v, const float* a, const float* b) { v[0] = a[0]+b[0]; v[1] = a[1]+b[1]; } 00046 inline void vscale(float* v, const float* a, const float s) { v[0] = a[0]*s; v[1] = a[1]*s; } 00047 inline void vset(float* v, float x, float y) { v[0]=x; v[1]=y; } 00048 inline float vlensqr(const float* v) { return vdot(v,v); } 00049 inline float vlen(const float* v) { return sqrtf(vlensqr(v)); } 00050 inline void vlerp(float* v, const float* a, const float* b, float t) { v[0] = lerp(a[0], b[0], t); v[1] = lerp(a[1], b[1], t); } 00051 inline void vmad(float* v, const float* a, const float* b, float s) { v[0] = a[0] + b[0]*s; v[1] = a[1] + b[1]*s; } 00052 inline void vnorm(float* v) 00053 { 00054 float d = vlen(v); 00055 if (d > 0.0001f) 00056 { 00057 d = 1.0f/d; 00058 v[0] *= d; 00059 v[1] *= d; 00060 } 00061 } 00062 } 00063 inline float triarea(const float* a, const float* b, const float* c) 00064 { 00065 return (b[0]*a[1] - a[0]*b[1]) + (c[0]*b[1] - b[0]*c[1]) + (a[0]*c[1] - c[0]*a[1]); 00066 } 00067 00068 static void closestPtPtSeg(const float* pt, 00069 const float* sp, const float* sq, 00070 float& t) 00071 { 00072 float dir[2],diff[3]; 00073 vsub(dir,sq,sp); 00074 vsub(diff,pt,sp); 00075 t = vdot(diff,dir); 00076 if (t <= 0.0f) { t = 0; return; } 00077 float d = vdot(dir,dir); 00078 if (t >= d) { t = 1; return; } 00079 t /= d; 00080 } 00081 00082 static float distPtSegSqr(const float* pt, const float* sp, const float* sq) 00083 { 00084 float t; 00085 closestPtPtSeg(pt, sp,sq, t); 00086 float np[2]; 00087 vlerp(np, sp,sq, t); 00088 return vdistsqr(pt,np); 00089 } 00090 00091 static int sweepCircleCircle(const MT_Vector3& pos0, const MT_Scalar r0, const MT_Vector2& v, 00092 const MT_Vector3& pos1, const MT_Scalar r1, 00093 float& tmin, float& tmax) 00094 { 00095 static const float EPS = 0.0001f; 00096 MT_Vector2 c0(pos0.x(), pos0.y()); 00097 MT_Vector2 c1(pos1.x(), pos1.y()); 00098 MT_Vector2 s = c1 - c0; 00099 MT_Scalar r = r0+r1; 00100 float c = s.length2() - r*r; 00101 float a = v.length2(); 00102 if (a < EPS) return 0; // not moving 00103 00104 // Overlap, calc time to exit. 00105 float b = MT_dot(v,s); 00106 float d = b*b - a*c; 00107 if (d < 0.0f) return 0; // no intersection. 00108 tmin = (b - sqrtf(d)) / a; 00109 tmax = (b + sqrtf(d)) / a; 00110 return 1; 00111 } 00112 00113 static int sweepCircleSegment(const MT_Vector3& pos0, const MT_Scalar r0, const MT_Vector2& v, 00114 const MT_Vector3& pa, const MT_Vector3& pb, const MT_Scalar sr, 00115 float& tmin, float &tmax) 00116 { 00117 // equation parameters 00118 MT_Vector2 c0(pos0.x(), pos0.y()); 00119 MT_Vector2 sa(pa.x(), pa.y()); 00120 MT_Vector2 sb(pb.x(), pb.y()); 00121 MT_Vector2 L = sb-sa; 00122 MT_Vector2 H = c0-sa; 00123 MT_Scalar radius = r0+sr; 00124 float l2 = L.length2(); 00125 float r2 = radius * radius; 00126 float dl = perp(v, L); 00127 float hl = perp(H, L); 00128 float a = dl * dl; 00129 float b = 2.0f * hl * dl; 00130 float c = hl * hl - (r2 * l2); 00131 float d = (b*b) - (4.0f * a * c); 00132 00133 // infinite line missed by infinite ray. 00134 if (d < 0.0f) 00135 return 0; 00136 00137 d = sqrtf(d); 00138 tmin = (-b - d) / (2.0f * a); 00139 tmax = (-b + d) / (2.0f * a); 00140 00141 // line missed by ray range. 00142 /* if (tmax < 0.0f || tmin > 1.0f) 00143 return 0;*/ 00144 00145 // find what part of the ray was collided. 00146 MT_Vector2 Pedge; 00147 Pedge = c0+v*tmin; 00148 H = Pedge - sa; 00149 float e0 = MT_dot(H, L) / l2; 00150 Pedge = c0 + v*tmax; 00151 H = Pedge - sa; 00152 float e1 = MT_dot(H, L) / l2; 00153 00154 if (e0 < 0.0f || e1 < 0.0f) 00155 { 00156 float ctmin, ctmax; 00157 if (sweepCircleCircle(pos0, r0, v, pa, sr, ctmin, ctmax)) 00158 { 00159 if (e0 < 0.0f && ctmin > tmin) 00160 tmin = ctmin; 00161 if (e1 < 0.0f && ctmax < tmax) 00162 tmax = ctmax; 00163 } 00164 else 00165 { 00166 return 0; 00167 } 00168 } 00169 00170 if (e0 > 1.0f || e1 > 1.0f) 00171 { 00172 float ctmin, ctmax; 00173 if (sweepCircleCircle(pos0, r0, v, pb, sr, ctmin, ctmax)) 00174 { 00175 if (e0 > 1.0f && ctmin > tmin) 00176 tmin = ctmin; 00177 if (e1 > 1.0f && ctmax < tmax) 00178 tmax = ctmax; 00179 } 00180 else 00181 { 00182 return 0; 00183 } 00184 } 00185 00186 return 1; 00187 } 00188 00189 static bool inBetweenAngle(float a, float amin, float amax, float& t) 00190 { 00191 if (amax < amin) amax += (float)M_PI*2; 00192 if (a < amin-(float)M_PI) a += (float)M_PI*2; 00193 if (a > amin+(float)M_PI) a -= (float)M_PI*2; 00194 if (a >= amin && a < amax) 00195 { 00196 t = (a-amin) / (amax-amin); 00197 return true; 00198 } 00199 return false; 00200 } 00201 00202 static float interpolateToi(float a, const float* dir, const float* toi, const int ntoi) 00203 { 00204 for (int i = 0; i < ntoi; ++i) 00205 { 00206 int next = (i+1) % ntoi; 00207 float t; 00208 if (inBetweenAngle(a, dir[i], dir[next], t)) 00209 { 00210 return lerp(toi[i], toi[next], t); 00211 } 00212 } 00213 return 0; 00214 } 00215 00216 KX_ObstacleSimulation::KX_ObstacleSimulation(MT_Scalar levelHeight, bool enableVisualization) 00217 : m_levelHeight(levelHeight) 00218 , m_enableVisualization(enableVisualization) 00219 { 00220 00221 } 00222 00223 KX_ObstacleSimulation::~KX_ObstacleSimulation() 00224 { 00225 for (size_t i=0; i<m_obstacles.size(); i++) 00226 { 00227 KX_Obstacle* obs = m_obstacles[i]; 00228 delete obs; 00229 } 00230 m_obstacles.clear(); 00231 } 00232 KX_Obstacle* KX_ObstacleSimulation::CreateObstacle(KX_GameObject* gameobj) 00233 { 00234 KX_Obstacle* obstacle = new KX_Obstacle(); 00235 obstacle->m_gameObj = gameobj; 00236 00237 vset(obstacle->vel, 0,0); 00238 vset(obstacle->pvel, 0,0); 00239 vset(obstacle->dvel, 0,0); 00240 vset(obstacle->nvel, 0,0); 00241 for (int i = 0; i < VEL_HIST_SIZE; ++i) 00242 vset(&obstacle->hvel[i*2], 0,0); 00243 obstacle->hhead = 0; 00244 00245 gameobj->RegisterObstacle(this); 00246 m_obstacles.push_back(obstacle); 00247 return obstacle; 00248 } 00249 00250 void KX_ObstacleSimulation::AddObstacleForObj(KX_GameObject* gameobj) 00251 { 00252 KX_Obstacle* obstacle = CreateObstacle(gameobj); 00253 struct Object* blenderobject = gameobj->GetBlenderObject(); 00254 obstacle->m_type = KX_OBSTACLE_OBJ; 00255 obstacle->m_shape = KX_OBSTACLE_CIRCLE; 00256 obstacle->m_rad = blenderobject->obstacleRad; 00257 } 00258 00259 void KX_ObstacleSimulation::AddObstaclesForNavMesh(KX_NavMeshObject* navmeshobj) 00260 { 00261 dtStatNavMesh* navmesh = navmeshobj->GetNavMesh(); 00262 if (navmesh) 00263 { 00264 int npoly = navmesh->getPolyCount(); 00265 for (int pi=0; pi<npoly; pi++) 00266 { 00267 const dtStatPoly* poly = navmesh->getPoly(pi); 00268 00269 for (int i = 0, j = (int)poly->nv-1; i < (int)poly->nv; j = i++) 00270 { 00271 if (poly->n[j]) continue; 00272 const float* vj = navmesh->getVertex(poly->v[j]); 00273 const float* vi = navmesh->getVertex(poly->v[i]); 00274 00275 KX_Obstacle* obstacle = CreateObstacle(navmeshobj); 00276 obstacle->m_type = KX_OBSTACLE_NAV_MESH; 00277 obstacle->m_shape = KX_OBSTACLE_SEGMENT; 00278 obstacle->m_pos = MT_Point3(vj[0], vj[2], vj[1]); 00279 obstacle->m_pos2 = MT_Point3(vi[0], vi[2], vi[1]); 00280 obstacle->m_rad = 0; 00281 } 00282 } 00283 } 00284 } 00285 00286 void KX_ObstacleSimulation::DestroyObstacleForObj(KX_GameObject* gameobj) 00287 { 00288 for (size_t i=0; i<m_obstacles.size(); ) 00289 { 00290 if (m_obstacles[i]->m_gameObj == gameobj) 00291 { 00292 KX_Obstacle* obstacle = m_obstacles[i]; 00293 obstacle->m_gameObj->UnregisterObstacle(); 00294 m_obstacles[i] = m_obstacles.back(); 00295 m_obstacles.pop_back(); 00296 delete obstacle; 00297 } 00298 else 00299 i++; 00300 } 00301 } 00302 00303 void KX_ObstacleSimulation::UpdateObstacles() 00304 { 00305 for (size_t i=0; i<m_obstacles.size(); i++) 00306 { 00307 if (m_obstacles[i]->m_type==KX_OBSTACLE_NAV_MESH || m_obstacles[i]->m_shape==KX_OBSTACLE_SEGMENT) 00308 continue; 00309 00310 KX_Obstacle* obs = m_obstacles[i]; 00311 obs->m_pos = obs->m_gameObj->NodeGetWorldPosition(); 00312 obs->vel[0] = obs->m_gameObj->GetLinearVelocity().x(); 00313 obs->vel[1] = obs->m_gameObj->GetLinearVelocity().y(); 00314 00315 // Update velocity history and calculate perceived (average) velocity. 00316 vcpy(&obs->hvel[obs->hhead*2], obs->vel); 00317 obs->hhead = (obs->hhead+1) % VEL_HIST_SIZE; 00318 vset(obs->pvel,0,0); 00319 for (int j = 0; j < VEL_HIST_SIZE; ++j) 00320 vadd(obs->pvel, obs->pvel, &obs->hvel[j*2]); 00321 vscale(obs->pvel, obs->pvel, 1.0f/VEL_HIST_SIZE); 00322 } 00323 } 00324 00325 KX_Obstacle* KX_ObstacleSimulation::GetObstacle(KX_GameObject* gameobj) 00326 { 00327 for (size_t i=0; i<m_obstacles.size(); i++) 00328 { 00329 if (m_obstacles[i]->m_gameObj == gameobj) 00330 return m_obstacles[i]; 00331 } 00332 00333 return NULL; 00334 } 00335 00336 void KX_ObstacleSimulation::AdjustObstacleVelocity(KX_Obstacle* activeObst, KX_NavMeshObject* activeNavMeshObj, 00337 MT_Vector3& velocity, MT_Scalar maxDeltaSpeed,MT_Scalar maxDeltaAngle) 00338 { 00339 } 00340 00341 void KX_ObstacleSimulation::DrawObstacles() 00342 { 00343 if (!m_enableVisualization) 00344 return; 00345 static const MT_Vector3 bluecolor(0,0,1); 00346 static const MT_Vector3 normal(0.,0.,1.); 00347 static const int SECTORS_NUM = 32; 00348 for (size_t i=0; i<m_obstacles.size(); i++) 00349 { 00350 if (m_obstacles[i]->m_shape==KX_OBSTACLE_SEGMENT) 00351 { 00352 MT_Point3 p1 = m_obstacles[i]->m_pos; 00353 MT_Point3 p2 = m_obstacles[i]->m_pos2; 00354 //apply world transform 00355 if (m_obstacles[i]->m_type == KX_OBSTACLE_NAV_MESH) 00356 { 00357 KX_NavMeshObject* navmeshobj = static_cast<KX_NavMeshObject*>(m_obstacles[i]->m_gameObj); 00358 p1 = navmeshobj->TransformToWorldCoords(p1); 00359 p2 = navmeshobj->TransformToWorldCoords(p2); 00360 } 00361 00362 KX_RasterizerDrawDebugLine(p1, p2, bluecolor); 00363 } 00364 else if (m_obstacles[i]->m_shape==KX_OBSTACLE_CIRCLE) 00365 { 00366 KX_RasterizerDrawDebugCircle(m_obstacles[i]->m_pos, m_obstacles[i]->m_rad, bluecolor, 00367 normal, SECTORS_NUM); 00368 } 00369 } 00370 } 00371 00372 static MT_Point3 nearestPointToObstacle(MT_Point3& pos ,KX_Obstacle* obstacle) 00373 { 00374 switch (obstacle->m_shape) 00375 { 00376 case KX_OBSTACLE_SEGMENT : 00377 { 00378 MT_Vector3 ab = obstacle->m_pos2 - obstacle->m_pos; 00379 if (!ab.fuzzyZero()) 00380 { 00381 MT_Vector3 abdir = ab.normalized(); 00382 MT_Vector3 v = pos - obstacle->m_pos; 00383 MT_Scalar proj = abdir.dot(v); 00384 CLAMP(proj, 0, ab.length()); 00385 MT_Point3 res = obstacle->m_pos + abdir*proj; 00386 return res; 00387 } 00388 } 00389 case KX_OBSTACLE_CIRCLE : 00390 default: 00391 return obstacle->m_pos; 00392 } 00393 } 00394 00395 static bool filterObstacle(KX_Obstacle* activeObst, KX_NavMeshObject* activeNavMeshObj, KX_Obstacle* otherObst, 00396 float levelHeight) 00397 { 00398 //filter obstacles by type 00399 if ( (otherObst == activeObst) || 00400 (otherObst->m_type==KX_OBSTACLE_NAV_MESH && otherObst->m_gameObj!=activeNavMeshObj) ) 00401 return false; 00402 00403 //filter obstacles by position 00404 MT_Point3 p = nearestPointToObstacle(activeObst->m_pos, otherObst); 00405 if ( fabs(activeObst->m_pos.z() - p.z()) > levelHeight) 00406 return false; 00407 00408 return true; 00409 } 00410 00412 KX_ObstacleSimulationTOI::KX_ObstacleSimulationTOI(MT_Scalar levelHeight, bool enableVisualization) 00413 : KX_ObstacleSimulation(levelHeight, enableVisualization), 00414 m_maxSamples(32), 00415 m_minToi(0.0f), 00416 m_maxToi(0.0f), 00417 m_velWeight(1.0f), 00418 m_curVelWeight(1.0f), 00419 m_toiWeight(1.0f), 00420 m_collisionWeight(1.0f) 00421 { 00422 } 00423 00424 00425 void KX_ObstacleSimulationTOI::AdjustObstacleVelocity(KX_Obstacle* activeObst, KX_NavMeshObject* activeNavMeshObj, 00426 MT_Vector3& velocity, MT_Scalar maxDeltaSpeed, MT_Scalar maxDeltaAngle) 00427 { 00428 int nobs = m_obstacles.size(); 00429 int obstidx = std::find(m_obstacles.begin(), m_obstacles.end(), activeObst) - m_obstacles.begin(); 00430 if (obstidx == nobs) 00431 return; 00432 00433 vset(activeObst->dvel, velocity.x(), velocity.y()); 00434 00435 //apply RVO 00436 sampleRVO(activeObst, activeNavMeshObj, maxDeltaAngle); 00437 00438 // Fake dynamic constraint. 00439 float dv[2]; 00440 float vel[2]; 00441 vsub(dv, activeObst->nvel, activeObst->vel); 00442 float ds = vlen(dv); 00443 if (ds > maxDeltaSpeed || ds<-maxDeltaSpeed) 00444 vscale(dv, dv, fabs(maxDeltaSpeed/ds)); 00445 vadd(vel, activeObst->vel, dv); 00446 00447 velocity.x() = vel[0]; 00448 velocity.y() = vel[1]; 00449 } 00450 00452 static const int AVOID_MAX_STEPS = 128; 00453 struct TOICircle 00454 { 00455 TOICircle() : n(0), minToi(0), maxToi(1) {} 00456 float toi[AVOID_MAX_STEPS]; // Time of impact (seconds) 00457 float toie[AVOID_MAX_STEPS]; // Time of exit (seconds) 00458 float dir[AVOID_MAX_STEPS]; // Direction (radians) 00459 int n; // Number of samples 00460 float minToi, maxToi; // Min/max TOI (seconds) 00461 }; 00462 00463 KX_ObstacleSimulationTOI_rays::KX_ObstacleSimulationTOI_rays(MT_Scalar levelHeight, bool enableVisualization): 00464 KX_ObstacleSimulationTOI(levelHeight, enableVisualization) 00465 { 00466 m_maxSamples = 32; 00467 m_minToi = 0.5f; 00468 m_maxToi = 1.2f; 00469 m_velWeight = 4.0f; 00470 m_toiWeight = 1.0f; 00471 m_collisionWeight = 100.0f; 00472 } 00473 00474 00475 void KX_ObstacleSimulationTOI_rays::sampleRVO(KX_Obstacle* activeObst, KX_NavMeshObject* activeNavMeshObj, 00476 const float maxDeltaAngle) 00477 { 00478 MT_Vector2 vel(activeObst->dvel[0], activeObst->dvel[1]); 00479 float vmax = (float) vel.length(); 00480 float odir = (float) atan2(vel.y(), vel.x()); 00481 00482 MT_Vector2 ddir = vel; 00483 ddir.normalize(); 00484 00485 float bestScore = FLT_MAX; 00486 float bestDir = odir; 00487 float bestToi = 0; 00488 00489 TOICircle tc; 00490 tc.n = m_maxSamples; 00491 tc.minToi = m_minToi; 00492 tc.maxToi = m_maxToi; 00493 00494 const int iforw = m_maxSamples/2; 00495 const float aoff = (float)iforw / (float)m_maxSamples; 00496 00497 size_t nobs = m_obstacles.size(); 00498 for (int iter = 0; iter < m_maxSamples; ++iter) 00499 { 00500 // Calculate sample velocity 00501 const float ndir = ((float)iter/(float)m_maxSamples) - aoff; 00502 const float dir = odir+ndir*M_PI*2; 00503 MT_Vector2 svel; 00504 svel.x() = cosf(dir) * vmax; 00505 svel.y() = sinf(dir) * vmax; 00506 00507 // Find min time of impact and exit amongst all obstacles. 00508 float tmin = m_maxToi; 00509 float tmine = 0; 00510 for (int i = 0; i < nobs; ++i) 00511 { 00512 KX_Obstacle* ob = m_obstacles[i]; 00513 bool res = filterObstacle(activeObst, activeNavMeshObj, ob, m_levelHeight); 00514 if (!res) 00515 continue; 00516 00517 float htmin,htmax; 00518 00519 if (ob->m_shape == KX_OBSTACLE_CIRCLE) 00520 { 00521 MT_Vector2 vab; 00522 if (vlen(ob->vel) < 0.01f*0.01f) 00523 { 00524 // Stationary, use VO 00525 vab = svel; 00526 } 00527 else 00528 { 00529 // Moving, use RVO 00530 vab = 2*svel - vel - ob->vel; 00531 } 00532 00533 if (!sweepCircleCircle(activeObst->m_pos, activeObst->m_rad, 00534 vab, ob->m_pos, ob->m_rad, htmin, htmax)) 00535 continue; 00536 } 00537 else if (ob->m_shape == KX_OBSTACLE_SEGMENT) 00538 { 00539 MT_Point3 p1 = ob->m_pos; 00540 MT_Point3 p2 = ob->m_pos2; 00541 //apply world transform 00542 if (ob->m_type == KX_OBSTACLE_NAV_MESH) 00543 { 00544 KX_NavMeshObject* navmeshobj = static_cast<KX_NavMeshObject*>(ob->m_gameObj); 00545 p1 = navmeshobj->TransformToWorldCoords(p1); 00546 p2 = navmeshobj->TransformToWorldCoords(p2); 00547 } 00548 if (!sweepCircleSegment(activeObst->m_pos, activeObst->m_rad, svel, 00549 p1, p2, ob->m_rad, htmin, htmax)) 00550 continue; 00551 } 00552 00553 if (htmin > 0.0f) 00554 { 00555 // The closest obstacle is somewhere ahead of us, keep track of nearest obstacle. 00556 if (htmin < tmin) 00557 tmin = htmin; 00558 } 00559 else if (htmax > 0.0f) 00560 { 00561 // The agent overlaps the obstacle, keep track of first safe exit. 00562 if (htmax > tmine) 00563 tmine = htmax; 00564 } 00565 } 00566 00567 // Calculate sample penalties and final score. 00568 const float apen = m_velWeight * fabsf(ndir); 00569 const float tpen = m_toiWeight * (1.0f/(0.0001f+tmin/m_maxToi)); 00570 const float cpen = m_collisionWeight * (tmine/m_minToi)*(tmine/m_minToi); 00571 const float score = apen + tpen + cpen; 00572 00573 // Update best score. 00574 if (score < bestScore) 00575 { 00576 bestDir = dir; 00577 bestToi = tmin; 00578 bestScore = score; 00579 } 00580 00581 tc.dir[iter] = dir; 00582 tc.toi[iter] = tmin; 00583 tc.toie[iter] = tmine; 00584 } 00585 00586 if (vlen(activeObst->vel) > 0.1) 00587 { 00588 // Constrain max turn rate. 00589 float cura = atan2(activeObst->vel[1],activeObst->vel[0]); 00590 float da = bestDir - cura; 00591 if (da < -M_PI) da += (float)M_PI*2; 00592 if (da > M_PI) da -= (float)M_PI*2; 00593 if (da < -maxDeltaAngle) 00594 { 00595 bestDir = cura - maxDeltaAngle; 00596 bestToi = min(bestToi, interpolateToi(bestDir, tc.dir, tc.toi, tc.n)); 00597 } 00598 else if (da > maxDeltaAngle) 00599 { 00600 bestDir = cura + maxDeltaAngle; 00601 bestToi = min(bestToi, interpolateToi(bestDir, tc.dir, tc.toi, tc.n)); 00602 } 00603 } 00604 00605 // Adjust speed when time of impact is less than min TOI. 00606 if (bestToi < m_minToi) 00607 vmax *= bestToi/m_minToi; 00608 00609 // New steering velocity. 00610 activeObst->nvel[0] = cosf(bestDir) * vmax; 00611 activeObst->nvel[1] = sinf(bestDir) * vmax; 00612 } 00613 00615 00616 static void processSamples(KX_Obstacle* activeObst, KX_NavMeshObject* activeNavMeshObj, 00617 KX_Obstacles& obstacles, float levelHeight, const float vmax, 00618 const float* spos, const float cs, const int nspos, float* res, 00619 float maxToi, float velWeight, float curVelWeight, float sideWeight, 00620 float toiWeight) 00621 { 00622 vset(res, 0,0); 00623 00624 const float ivmax = 1.0f / vmax; 00625 00626 float adir[2] /*, adist */; 00627 vcpy(adir, activeObst->pvel); 00628 if (vlen(adir) > 0.01f) 00629 vnorm(adir); 00630 else 00631 vset(adir,0,0); 00632 float activeObstPos[2]; 00633 vset(activeObstPos, activeObst->m_pos.x(), activeObst->m_pos.y()); 00634 /* adist = vdot(adir, activeObstPos); */ 00635 00636 float minPenalty = FLT_MAX; 00637 00638 for (int n = 0; n < nspos; ++n) 00639 { 00640 float vcand[2]; 00641 vcpy(vcand, &spos[n*2]); 00642 00643 // Find min time of impact and exit amongst all obstacles. 00644 float tmin = maxToi; 00645 float side = 0; 00646 int nside = 0; 00647 00648 for (int i = 0; i < obstacles.size(); ++i) 00649 { 00650 KX_Obstacle* ob = obstacles[i]; 00651 bool res = filterObstacle(activeObst, activeNavMeshObj, ob, levelHeight); 00652 if (!res) 00653 continue; 00654 float htmin, htmax; 00655 00656 if (ob->m_shape==KX_OBSTACLE_CIRCLE) 00657 { 00658 float vab[2]; 00659 00660 // Moving, use RVO 00661 vscale(vab, vcand, 2); 00662 vsub(vab, vab, activeObst->vel); 00663 vsub(vab, vab, ob->vel); 00664 00665 // Side 00666 // NOTE: dp, and dv are constant over the whole calculation, 00667 // they can be precomputed per object. 00668 const float* pa = activeObstPos; 00669 float pb[2]; 00670 vset(pb, ob->m_pos.x(), ob->m_pos.y()); 00671 00672 const float orig[2] = {0,0}; 00673 float dp[2],dv[2],np[2]; 00674 vsub(dp,pb,pa); 00675 vnorm(dp); 00676 vsub(dv,ob->dvel, activeObst->dvel); 00677 00678 const float a = triarea(orig, dp,dv); 00679 if (a < 0.01f) 00680 { 00681 np[0] = -dp[1]; 00682 np[1] = dp[0]; 00683 } 00684 else 00685 { 00686 np[0] = dp[1]; 00687 np[1] = -dp[0]; 00688 } 00689 00690 side += clamp(min(vdot(dp,vab)*2,vdot(np,vab)*2), 0.0f, 1.0f); 00691 nside++; 00692 00693 if (!sweepCircleCircle(activeObst->m_pos, activeObst->m_rad, vab, ob->m_pos, ob->m_rad, 00694 htmin, htmax)) 00695 continue; 00696 00697 // Handle overlapping obstacles. 00698 if (htmin < 0.0f && htmax > 0.0f) 00699 { 00700 // Avoid more when overlapped. 00701 htmin = -htmin * 0.5f; 00702 } 00703 } 00704 else if (ob->m_shape == KX_OBSTACLE_SEGMENT) 00705 { 00706 MT_Point3 p1 = ob->m_pos; 00707 MT_Point3 p2 = ob->m_pos2; 00708 //apply world transform 00709 if (ob->m_type == KX_OBSTACLE_NAV_MESH) 00710 { 00711 KX_NavMeshObject* navmeshobj = static_cast<KX_NavMeshObject*>(ob->m_gameObj); 00712 p1 = navmeshobj->TransformToWorldCoords(p1); 00713 p2 = navmeshobj->TransformToWorldCoords(p2); 00714 } 00715 float p[2], q[2]; 00716 vset(p, p1.x(), p1.y()); 00717 vset(q, p2.x(), p2.y()); 00718 00719 // NOTE: the segments are assumed to come from a navmesh which is shrunken by 00720 // the agent radius, hence the use of really small radius. 00721 // This can be handle more efficiently by using seg-seg test instead. 00722 // If the whole segment is to be treated as obstacle, use agent->rad instead of 0.01f! 00723 const float r = 0.01f; // agent->rad 00724 if (distPtSegSqr(activeObstPos, p, q) < sqr(r+ob->m_rad)) 00725 { 00726 float sdir[2], snorm[2]; 00727 vsub(sdir, q, p); 00728 snorm[0] = sdir[1]; 00729 snorm[1] = -sdir[0]; 00730 // If the velocity is pointing towards the segment, no collision. 00731 if (vdot(snorm, vcand) < 0.0f) 00732 continue; 00733 // Else immediate collision. 00734 htmin = 0.0f; 00735 htmax = 10.0f; 00736 } 00737 else 00738 { 00739 if (!sweepCircleSegment(activeObstPos, r, vcand, p, q, ob->m_rad, htmin, htmax)) 00740 continue; 00741 } 00742 00743 // Avoid less when facing walls. 00744 htmin *= 2.0f; 00745 } 00746 00747 if (htmin >= 0.0f) 00748 { 00749 // The closest obstacle is somewhere ahead of us, keep track of nearest obstacle. 00750 if (htmin < tmin) 00751 tmin = htmin; 00752 } 00753 } 00754 00755 // Normalize side bias, to prevent it dominating too much. 00756 if (nside) 00757 side /= nside; 00758 00759 const float vpen = velWeight * (vdist(vcand, activeObst->dvel) * ivmax); 00760 const float vcpen = curVelWeight * (vdist(vcand, activeObst->vel) * ivmax); 00761 const float spen = sideWeight * side; 00762 const float tpen = toiWeight * (1.0f/(0.1f+tmin/maxToi)); 00763 00764 const float penalty = vpen + vcpen + spen + tpen; 00765 00766 if (penalty < minPenalty) 00767 { 00768 minPenalty = penalty; 00769 vcpy(res, vcand); 00770 } 00771 } 00772 } 00773 00774 void KX_ObstacleSimulationTOI_cells::sampleRVO(KX_Obstacle* activeObst, KX_NavMeshObject* activeNavMeshObj, 00775 const float maxDeltaAngle) 00776 { 00777 vset(activeObst->nvel, 0.f, 0.f); 00778 float vmax = vlen(activeObst->dvel); 00779 00780 float* spos = new float[2*m_maxSamples]; 00781 int nspos = 0; 00782 00783 if (!m_adaptive) 00784 { 00785 const float cvx = activeObst->dvel[0]*m_bias; 00786 const float cvy = activeObst->dvel[1]*m_bias; 00787 float vmax = vlen(activeObst->dvel); 00788 const float vrange = vmax*(1-m_bias); 00789 const float cs = 1.0f / (float)m_sampleRadius*vrange; 00790 00791 for (int y = -m_sampleRadius; y <= m_sampleRadius; ++y) 00792 { 00793 for (int x = -m_sampleRadius; x <= m_sampleRadius; ++x) 00794 { 00795 if (nspos < m_maxSamples) 00796 { 00797 const float vx = cvx + (float)(x+0.5f)*cs; 00798 const float vy = cvy + (float)(y+0.5f)*cs; 00799 if (vx*vx+vy*vy > sqr(vmax+cs/2)) continue; 00800 spos[nspos*2+0] = vx; 00801 spos[nspos*2+1] = vy; 00802 nspos++; 00803 } 00804 } 00805 } 00806 processSamples(activeObst, activeNavMeshObj, m_obstacles, m_levelHeight, vmax, spos, cs/2, 00807 nspos, activeObst->nvel, m_maxToi, m_velWeight, m_curVelWeight, m_collisionWeight, m_toiWeight); 00808 } 00809 else 00810 { 00811 int rad; 00812 float res[2]; 00813 float cs; 00814 // First sample location. 00815 rad = 4; 00816 res[0] = activeObst->dvel[0]*m_bias; 00817 res[1] = activeObst->dvel[1]*m_bias; 00818 cs = vmax*(2-m_bias*2) / (float)(rad-1); 00819 00820 for (int k = 0; k < 5; ++k) 00821 { 00822 const float half = (rad-1)*cs*0.5f; 00823 00824 nspos = 0; 00825 for (int y = 0; y < rad; ++y) 00826 { 00827 for (int x = 0; x < rad; ++x) 00828 { 00829 const float vx = res[0] + x*cs - half; 00830 const float vy = res[1] + y*cs - half; 00831 if (vx*vx+vy*vy > sqr(vmax+cs/2)) continue; 00832 spos[nspos*2+0] = vx; 00833 spos[nspos*2+1] = vy; 00834 nspos++; 00835 } 00836 } 00837 00838 processSamples(activeObst, activeNavMeshObj, m_obstacles, m_levelHeight, vmax, spos, cs/2, 00839 nspos, res, m_maxToi, m_velWeight, m_curVelWeight, m_collisionWeight, m_toiWeight); 00840 00841 cs *= 0.5f; 00842 } 00843 vcpy(activeObst->nvel, res); 00844 } 00845 } 00846 00847 KX_ObstacleSimulationTOI_cells::KX_ObstacleSimulationTOI_cells(MT_Scalar levelHeight, bool enableVisualization) 00848 : KX_ObstacleSimulationTOI(levelHeight, enableVisualization) 00849 , m_bias(0.4f) 00850 , m_adaptive(true) 00851 , m_sampleRadius(15) 00852 { 00853 m_maxSamples = (m_sampleRadius*2+1)*(m_sampleRadius*2+1) + 100; 00854 m_maxToi = 1.5f; 00855 m_velWeight = 2.0f; 00856 m_curVelWeight = 0.75f; 00857 m_toiWeight = 2.5f; 00858 m_collisionWeight = 0.75f; //side_weight 00859 }