forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			620 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			620 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | //
 | ||
|  | // Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
 | ||
|  | //
 | ||
|  | // This software is provided 'as-is', without any express or implied
 | ||
|  | // warranty.  In no event will the authors be held liable for any damages
 | ||
|  | // arising from the use of this software.
 | ||
|  | // Permission is granted to anyone to use this software for any purpose,
 | ||
|  | // including commercial applications, and to alter it and redistribute it
 | ||
|  | // freely, subject to the following restrictions:
 | ||
|  | // 1. The origin of this software must not be misrepresented; you must not
 | ||
|  | //    claim that you wrote the original software. If you use this software
 | ||
|  | //    in a product, an acknowledgment in the product documentation would be
 | ||
|  | //    appreciated but is not required.
 | ||
|  | // 2. Altered source versions must be plainly marked as such, and must not be
 | ||
|  | //    misrepresented as being the original software.
 | ||
|  | // 3. This notice may not be removed or altered from any source distribution.
 | ||
|  | //
 | ||
|  | 
 | ||
|  | #include "DetourObstacleAvoidance.h"
 | ||
|  | #include "DetourCommon.h"
 | ||
|  | #include "DetourMath.h"
 | ||
|  | #include "DetourAlloc.h"
 | ||
|  | #include "DetourAssert.h"
 | ||
|  | #include <string.h>
 | ||
|  | #include <float.h>
 | ||
|  | #include <new>
 | ||
|  | 
 | ||
|  | static const float DT_PI = 3.14159265f; | ||
|  | 
 | ||
|  | static int sweepCircleCircle(const float* c0, const float r0, const float* v, | ||
|  | 							 const float* c1, const float r1, | ||
|  | 							 float& tmin, float& tmax) | ||
|  | { | ||
|  | 	static const float EPS = 0.0001f; | ||
|  | 	float s[3]; | ||
|  | 	dtVsub(s,c1,c0); | ||
|  | 	float r = r0+r1; | ||
|  | 	float c = dtVdot2D(s,s) - r*r; | ||
|  | 	float a = dtVdot2D(v,v); | ||
|  | 	if (a < EPS) return 0;	// not moving
 | ||
|  | 	 | ||
|  | 	// Overlap, calc time to exit.
 | ||
|  | 	float b = dtVdot2D(v,s); | ||
|  | 	float d = b*b - a*c; | ||
|  | 	if (d < 0.0f) return 0; // no intersection.
 | ||
|  | 	a = 1.0f / a; | ||
|  | 	const float rd = dtMathSqrtf(d); | ||
|  | 	tmin = (b - rd) * a; | ||
|  | 	tmax = (b + rd) * a; | ||
|  | 	return 1; | ||
|  | } | ||
|  | 
 | ||
|  | static int isectRaySeg(const float* ap, const float* u, | ||
|  | 					   const float* bp, const float* bq, | ||
|  | 					   float& t) | ||
|  | { | ||
|  | 	float v[3], w[3]; | ||
|  | 	dtVsub(v,bq,bp); | ||
|  | 	dtVsub(w,ap,bp); | ||
|  | 	float d = dtVperp2D(u,v); | ||
|  | 	if (dtMathFabsf(d) < 1e-6f) return 0; | ||
|  | 	d = 1.0f/d; | ||
|  | 	t = dtVperp2D(v,w) * d; | ||
|  | 	if (t < 0 || t > 1) return 0; | ||
|  | 	float s = dtVperp2D(u,w) * d; | ||
|  | 	if (s < 0 || s > 1) return 0; | ||
|  | 	return 1; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData() | ||
|  | { | ||
|  | 	void* mem = dtAlloc(sizeof(dtObstacleAvoidanceDebugData), DT_ALLOC_PERM); | ||
|  | 	if (!mem) return 0; | ||
|  | 	return new(mem) dtObstacleAvoidanceDebugData; | ||
|  | } | ||
|  | 
 | ||
|  | void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr) | ||
|  | { | ||
|  | 	if (!ptr) return; | ||
|  | 	ptr->~dtObstacleAvoidanceDebugData(); | ||
|  | 	dtFree(ptr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | dtObstacleAvoidanceDebugData::dtObstacleAvoidanceDebugData() : | ||
|  | 	m_nsamples(0), | ||
|  | 	m_maxSamples(0), | ||
|  | 	m_vel(0), | ||
|  | 	m_ssize(0), | ||
|  | 	m_pen(0), | ||
|  | 	m_vpen(0), | ||
|  | 	m_vcpen(0), | ||
|  | 	m_spen(0), | ||
|  | 	m_tpen(0) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | dtObstacleAvoidanceDebugData::~dtObstacleAvoidanceDebugData() | ||
|  | { | ||
|  | 	dtFree(m_vel); | ||
|  | 	dtFree(m_ssize); | ||
|  | 	dtFree(m_pen); | ||
|  | 	dtFree(m_vpen); | ||
|  | 	dtFree(m_vcpen); | ||
|  | 	dtFree(m_spen); | ||
|  | 	dtFree(m_tpen); | ||
|  | } | ||
|  | 		 | ||
|  | bool dtObstacleAvoidanceDebugData::init(const int maxSamples) | ||
|  | { | ||
|  | 	dtAssert(maxSamples); | ||
|  | 	m_maxSamples = maxSamples; | ||
|  | 
 | ||
|  | 	m_vel = (float*)dtAlloc(sizeof(float)*3*m_maxSamples, DT_ALLOC_PERM); | ||
|  | 	if (!m_vel) | ||
|  | 		return false; | ||
|  | 	m_pen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM); | ||
|  | 	if (!m_pen) | ||
|  | 		return false; | ||
|  | 	m_ssize = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM); | ||
|  | 	if (!m_ssize) | ||
|  | 		return false; | ||
|  | 	m_vpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM); | ||
|  | 	if (!m_vpen) | ||
|  | 		return false; | ||
|  | 	m_vcpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM); | ||
|  | 	if (!m_vcpen) | ||
|  | 		return false; | ||
|  | 	m_spen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM); | ||
|  | 	if (!m_spen) | ||
|  | 		return false; | ||
|  | 	m_tpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM); | ||
|  | 	if (!m_tpen) | ||
|  | 		return false; | ||
|  | 	 | ||
|  | 	return true; | ||
|  | } | ||
|  | 
 | ||
|  | void dtObstacleAvoidanceDebugData::reset() | ||
|  | { | ||
|  | 	m_nsamples = 0; | ||
|  | } | ||
|  | 
 | ||
|  | void dtObstacleAvoidanceDebugData::addSample(const float* vel, const float ssize, const float pen, | ||
|  | 											 const float vpen, const float vcpen, const float spen, const float tpen) | ||
|  | { | ||
|  | 	if (m_nsamples >= m_maxSamples) | ||
|  | 		return; | ||
|  | 	dtAssert(m_vel); | ||
|  | 	dtAssert(m_ssize); | ||
|  | 	dtAssert(m_pen); | ||
|  | 	dtAssert(m_vpen); | ||
|  | 	dtAssert(m_vcpen); | ||
|  | 	dtAssert(m_spen); | ||
|  | 	dtAssert(m_tpen); | ||
|  | 	dtVcopy(&m_vel[m_nsamples*3], vel); | ||
|  | 	m_ssize[m_nsamples] = ssize; | ||
|  | 	m_pen[m_nsamples] = pen; | ||
|  | 	m_vpen[m_nsamples] = vpen; | ||
|  | 	m_vcpen[m_nsamples] = vcpen; | ||
|  | 	m_spen[m_nsamples] = spen; | ||
|  | 	m_tpen[m_nsamples] = tpen; | ||
|  | 	m_nsamples++; | ||
|  | } | ||
|  | 
 | ||
|  | static void normalizeArray(float* arr, const int n) | ||
|  | { | ||
|  | 	// Normalize penaly range.
 | ||
|  | 	float minPen = FLT_MAX; | ||
|  | 	float maxPen = -FLT_MAX; | ||
|  | 	for (int i = 0; i < n; ++i) | ||
|  | 	{ | ||
|  | 		minPen = dtMin(minPen, arr[i]); | ||
|  | 		maxPen = dtMax(maxPen, arr[i]); | ||
|  | 	} | ||
|  | 	const float penRange = maxPen-minPen; | ||
|  | 	const float s = penRange > 0.001f ? (1.0f / penRange) : 1; | ||
|  | 	for (int i = 0; i < n; ++i) | ||
|  | 		arr[i] = dtClamp((arr[i]-minPen)*s, 0.0f, 1.0f); | ||
|  | } | ||
|  | 
 | ||
|  | void dtObstacleAvoidanceDebugData::normalizeSamples() | ||
|  | { | ||
|  | 	normalizeArray(m_pen, m_nsamples); | ||
|  | 	normalizeArray(m_vpen, m_nsamples); | ||
|  | 	normalizeArray(m_vcpen, m_nsamples); | ||
|  | 	normalizeArray(m_spen, m_nsamples); | ||
|  | 	normalizeArray(m_tpen, m_nsamples); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery() | ||
|  | { | ||
|  | 	void* mem = dtAlloc(sizeof(dtObstacleAvoidanceQuery), DT_ALLOC_PERM); | ||
|  | 	if (!mem) return 0; | ||
|  | 	return new(mem) dtObstacleAvoidanceQuery; | ||
|  | } | ||
|  | 
 | ||
|  | void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr) | ||
|  | { | ||
|  | 	if (!ptr) return; | ||
|  | 	ptr->~dtObstacleAvoidanceQuery(); | ||
|  | 	dtFree(ptr); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | dtObstacleAvoidanceQuery::dtObstacleAvoidanceQuery() : | ||
|  | 	m_invHorizTime(0), | ||
|  | 	m_vmax(0), | ||
|  | 	m_invVmax(0), | ||
|  | 	m_maxCircles(0), | ||
|  | 	m_circles(0), | ||
|  | 	m_ncircles(0), | ||
|  | 	m_maxSegments(0), | ||
|  | 	m_segments(0), | ||
|  | 	m_nsegments(0) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | dtObstacleAvoidanceQuery::~dtObstacleAvoidanceQuery() | ||
|  | { | ||
|  | 	dtFree(m_circles); | ||
|  | 	dtFree(m_segments); | ||
|  | } | ||
|  | 
 | ||
|  | bool dtObstacleAvoidanceQuery::init(const int maxCircles, const int maxSegments) | ||
|  | { | ||
|  | 	m_maxCircles = maxCircles; | ||
|  | 	m_ncircles = 0; | ||
|  | 	m_circles = (dtObstacleCircle*)dtAlloc(sizeof(dtObstacleCircle)*m_maxCircles, DT_ALLOC_PERM); | ||
|  | 	if (!m_circles) | ||
|  | 		return false; | ||
|  | 	memset(m_circles, 0, sizeof(dtObstacleCircle)*m_maxCircles); | ||
|  | 
 | ||
|  | 	m_maxSegments = maxSegments; | ||
|  | 	m_nsegments = 0; | ||
|  | 	m_segments = (dtObstacleSegment*)dtAlloc(sizeof(dtObstacleSegment)*m_maxSegments, DT_ALLOC_PERM); | ||
|  | 	if (!m_segments) | ||
|  | 		return false; | ||
|  | 	memset(m_segments, 0, sizeof(dtObstacleSegment)*m_maxSegments); | ||
|  | 	 | ||
|  | 	return true; | ||
|  | } | ||
|  | 
 | ||
|  | void dtObstacleAvoidanceQuery::reset() | ||
|  | { | ||
|  | 	m_ncircles = 0; | ||
|  | 	m_nsegments = 0; | ||
|  | } | ||
|  | 
 | ||
|  | void dtObstacleAvoidanceQuery::addCircle(const float* pos, const float rad, | ||
|  | 										 const float* vel, const float* dvel) | ||
|  | { | ||
|  | 	if (m_ncircles >= m_maxCircles) | ||
|  | 		return; | ||
|  | 		 | ||
|  | 	dtObstacleCircle* cir = &m_circles[m_ncircles++]; | ||
|  | 	dtVcopy(cir->p, pos); | ||
|  | 	cir->rad = rad; | ||
|  | 	dtVcopy(cir->vel, vel); | ||
|  | 	dtVcopy(cir->dvel, dvel); | ||
|  | } | ||
|  | 
 | ||
|  | void dtObstacleAvoidanceQuery::addSegment(const float* p, const float* q) | ||
|  | { | ||
|  | 	if (m_nsegments >= m_maxSegments) | ||
|  | 		return; | ||
|  | 	 | ||
|  | 	dtObstacleSegment* seg = &m_segments[m_nsegments++]; | ||
|  | 	dtVcopy(seg->p, p); | ||
|  | 	dtVcopy(seg->q, q); | ||
|  | } | ||
|  | 
 | ||
|  | void dtObstacleAvoidanceQuery::prepare(const float* pos, const float* dvel) | ||
|  | { | ||
|  | 	// Prepare obstacles
 | ||
|  | 	for (int i = 0; i < m_ncircles; ++i) | ||
|  | 	{ | ||
|  | 		dtObstacleCircle* cir = &m_circles[i]; | ||
|  | 		 | ||
|  | 		// Side
 | ||
|  | 		const float* pa = pos; | ||
|  | 		const float* pb = cir->p; | ||
|  | 		 | ||
|  | 		const float orig[3] = {0,0,0}; | ||
|  | 		float dv[3]; | ||
|  | 		dtVsub(cir->dp,pb,pa); | ||
|  | 		dtVnormalize(cir->dp); | ||
|  | 		dtVsub(dv, cir->dvel, dvel); | ||
|  | 		 | ||
|  | 		const float a = dtTriArea2D(orig, cir->dp,dv); | ||
|  | 		if (a < 0.01f) | ||
|  | 		{ | ||
|  | 			cir->np[0] = -cir->dp[2]; | ||
|  | 			cir->np[2] = cir->dp[0]; | ||
|  | 		} | ||
|  | 		else | ||
|  | 		{ | ||
|  | 			cir->np[0] = cir->dp[2]; | ||
|  | 			cir->np[2] = -cir->dp[0]; | ||
|  | 		} | ||
|  | 	}	 | ||
|  | 
 | ||
|  | 	for (int i = 0; i < m_nsegments; ++i) | ||
|  | 	{ | ||
|  | 		dtObstacleSegment* seg = &m_segments[i]; | ||
|  | 		 | ||
|  | 		// Precalc if the agent is really close to the segment.
 | ||
|  | 		const float r = 0.01f; | ||
|  | 		float t; | ||
|  | 		seg->touch = dtDistancePtSegSqr2D(pos, seg->p, seg->q, t) < dtSqr(r); | ||
|  | 	}	 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Calculate the collision penalty for a given velocity vector
 | ||
|  |  *  | ||
|  |  * @param vcand sampled velocity | ||
|  |  * @param dvel desired velocity | ||
|  |  * @param minPenalty threshold penalty for early out | ||
|  |  */ | ||
|  | float dtObstacleAvoidanceQuery::processSample(const float* vcand, const float cs, | ||
|  | 											  const float* pos, const float rad, | ||
|  | 											  const float* vel, const float* dvel, | ||
|  | 											  const float minPenalty, | ||
|  | 											  dtObstacleAvoidanceDebugData* debug) | ||
|  | { | ||
|  | 	// penalty for straying away from the desired and current velocities
 | ||
|  | 	const float vpen = m_params.weightDesVel * (dtVdist2D(vcand, dvel) * m_invVmax); | ||
|  | 	const float vcpen = m_params.weightCurVel * (dtVdist2D(vcand, vel) * m_invVmax); | ||
|  | 
 | ||
|  | 	// find the threshold hit time to bail out based on the early out penalty
 | ||
|  | 	// (see how the penalty is calculated below to understnad)
 | ||
|  | 	float minPen = minPenalty - vpen - vcpen; | ||
|  | 	float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime; | ||
|  | 	if (tThresold - m_params.horizTime > -FLT_EPSILON) | ||
|  | 		return minPenalty; // already too much
 | ||
|  | 
 | ||
|  | 	// Find min time of impact and exit amongst all obstacles.
 | ||
|  | 	float tmin = m_params.horizTime; | ||
|  | 	float side = 0; | ||
|  | 	int nside = 0; | ||
|  | 	 | ||
|  | 	for (int i = 0; i < m_ncircles; ++i) | ||
|  | 	{ | ||
|  | 		const dtObstacleCircle* cir = &m_circles[i]; | ||
|  | 			 | ||
|  | 		// RVO
 | ||
|  | 		float vab[3]; | ||
|  | 		dtVscale(vab, vcand, 2); | ||
|  | 		dtVsub(vab, vab, vel); | ||
|  | 		dtVsub(vab, vab, cir->vel); | ||
|  | 		 | ||
|  | 		// Side
 | ||
|  | 		side += dtClamp(dtMin(dtVdot2D(cir->dp,vab)*0.5f+0.5f, dtVdot2D(cir->np,vab)*2), 0.0f, 1.0f); | ||
|  | 		nside++; | ||
|  | 		 | ||
|  | 		float htmin = 0, htmax = 0; | ||
|  | 		if (!sweepCircleCircle(pos,rad, vab, cir->p,cir->rad, htmin, htmax)) | ||
|  | 			continue; | ||
|  | 		 | ||
|  | 		// Handle overlapping obstacles.
 | ||
|  | 		if (htmin < 0.0f && htmax > 0.0f) | ||
|  | 		{ | ||
|  | 			// Avoid more when overlapped.
 | ||
|  | 			htmin = -htmin * 0.5f; | ||
|  | 		} | ||
|  | 		 | ||
|  | 		if (htmin >= 0.0f) | ||
|  | 		{ | ||
|  | 			// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
 | ||
|  | 			if (htmin < tmin) | ||
|  | 			{ | ||
|  | 				tmin = htmin; | ||
|  | 				if (tmin < tThresold) | ||
|  | 					return minPenalty; | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for (int i = 0; i < m_nsegments; ++i) | ||
|  | 	{ | ||
|  | 		const dtObstacleSegment* seg = &m_segments[i]; | ||
|  | 		float htmin = 0; | ||
|  | 		 | ||
|  | 		if (seg->touch) | ||
|  | 		{ | ||
|  | 			// Special case when the agent is very close to the segment.
 | ||
|  | 			float sdir[3], snorm[3]; | ||
|  | 			dtVsub(sdir, seg->q, seg->p); | ||
|  | 			snorm[0] = -sdir[2]; | ||
|  | 			snorm[2] = sdir[0]; | ||
|  | 			// If the velocity is pointing towards the segment, no collision.
 | ||
|  | 			if (dtVdot2D(snorm, vcand) < 0.0f) | ||
|  | 				continue; | ||
|  | 			// Else immediate collision.
 | ||
|  | 			htmin = 0.0f; | ||
|  | 		} | ||
|  | 		else | ||
|  | 		{ | ||
|  | 			if (!isectRaySeg(pos, vcand, seg->p, seg->q, htmin)) | ||
|  | 				continue; | ||
|  | 		} | ||
|  | 		 | ||
|  | 		// Avoid less when facing walls.
 | ||
|  | 		htmin *= 2.0f; | ||
|  | 		 | ||
|  | 		// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
 | ||
|  | 		if (htmin < tmin) | ||
|  | 		{ | ||
|  | 			tmin = htmin; | ||
|  | 			if (tmin < tThresold) | ||
|  | 				return minPenalty; | ||
|  | 		} | ||
|  | 	} | ||
|  | 	 | ||
|  | 	// Normalize side bias, to prevent it dominating too much.
 | ||
|  | 	if (nside) | ||
|  | 		side /= nside; | ||
|  | 	 | ||
|  | 	const float spen = m_params.weightSide * side; | ||
|  | 	const float tpen = m_params.weightToi * (1.0f/(0.1f+tmin*m_invHorizTime)); | ||
|  | 	 | ||
|  | 	const float penalty = vpen + vcpen + spen + tpen; | ||
|  | 	 | ||
|  | 	// Store different penalties for debug viewing
 | ||
|  | 	if (debug) | ||
|  | 		debug->addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen); | ||
|  | 	 | ||
|  | 	return penalty; | ||
|  | } | ||
|  | 
 | ||
|  | int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float rad, const float vmax, | ||
|  | 												 const float* vel, const float* dvel, float* nvel, | ||
|  | 												 const dtObstacleAvoidanceParams* params, | ||
|  | 												 dtObstacleAvoidanceDebugData* debug) | ||
|  | { | ||
|  | 	prepare(pos, dvel); | ||
|  | 	 | ||
|  | 	memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams)); | ||
|  | 	m_invHorizTime = 1.0f / m_params.horizTime; | ||
|  | 	m_vmax = vmax; | ||
|  | 	m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX; | ||
|  | 	 | ||
|  | 	dtVset(nvel, 0,0,0); | ||
|  | 	 | ||
|  | 	if (debug) | ||
|  | 		debug->reset(); | ||
|  | 
 | ||
|  | 	const float cvx = dvel[0] * m_params.velBias; | ||
|  | 	const float cvz = dvel[2] * m_params.velBias; | ||
|  | 	const float cs = vmax * 2 * (1 - m_params.velBias) / (float)(m_params.gridSize-1); | ||
|  | 	const float half = (m_params.gridSize-1)*cs*0.5f; | ||
|  | 		 | ||
|  | 	float minPenalty = FLT_MAX; | ||
|  | 	int ns = 0; | ||
|  | 		 | ||
|  | 	for (int y = 0; y < m_params.gridSize; ++y) | ||
|  | 	{ | ||
|  | 		for (int x = 0; x < m_params.gridSize; ++x) | ||
|  | 		{ | ||
|  | 			float vcand[3]; | ||
|  | 			vcand[0] = cvx + x*cs - half; | ||
|  | 			vcand[1] = 0; | ||
|  | 			vcand[2] = cvz + y*cs - half; | ||
|  | 			 | ||
|  | 			if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+cs/2)) continue; | ||
|  | 			 | ||
|  | 			const float penalty = processSample(vcand, cs, pos,rad,vel,dvel, minPenalty, debug); | ||
|  | 			ns++; | ||
|  | 			if (penalty < minPenalty) | ||
|  | 			{ | ||
|  | 				minPenalty = penalty; | ||
|  | 				dtVcopy(nvel, vcand); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 	 | ||
|  | 	return ns; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | // vector normalization that ignores the y-component.
 | ||
|  | inline void dtNormalize2D(float* v) | ||
|  | { | ||
|  | 	float d = dtMathSqrtf(v[0] * v[0] + v[2] * v[2]); | ||
|  | 	if (d==0) | ||
|  | 		return; | ||
|  | 	d = 1.0f / d; | ||
|  | 	v[0] *= d; | ||
|  | 	v[2] *= d; | ||
|  | } | ||
|  | 
 | ||
|  | // vector normalization that ignores the y-component.
 | ||
|  | inline void dtRorate2D(float* dest, const float* v, float ang) | ||
|  | { | ||
|  | 	float c = cosf(ang); | ||
|  | 	float s = sinf(ang); | ||
|  | 	dest[0] = v[0]*c - v[2]*s; | ||
|  | 	dest[2] = v[0]*s + v[2]*c; | ||
|  | 	dest[1] = v[1]; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const float rad, const float vmax, | ||
|  | 													 const float* vel, const float* dvel, float* nvel, | ||
|  | 													 const dtObstacleAvoidanceParams* params, | ||
|  | 													 dtObstacleAvoidanceDebugData* debug) | ||
|  | { | ||
|  | 	prepare(pos, dvel); | ||
|  | 	 | ||
|  | 	memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams)); | ||
|  | 	m_invHorizTime = 1.0f / m_params.horizTime; | ||
|  | 	m_vmax = vmax; | ||
|  | 	m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX; | ||
|  | 	 | ||
|  | 	dtVset(nvel, 0,0,0); | ||
|  | 	 | ||
|  | 	if (debug) | ||
|  | 		debug->reset(); | ||
|  | 
 | ||
|  | 	// Build sampling pattern aligned to desired velocity.
 | ||
|  | 	float pat[(DT_MAX_PATTERN_DIVS*DT_MAX_PATTERN_RINGS+1)*2]; | ||
|  | 	int npat = 0; | ||
|  | 
 | ||
|  | 	const int ndivs = (int)m_params.adaptiveDivs; | ||
|  | 	const int nrings= (int)m_params.adaptiveRings; | ||
|  | 	const int depth = (int)m_params.adaptiveDepth; | ||
|  | 	 | ||
|  | 	const int nd = dtClamp(ndivs, 1, DT_MAX_PATTERN_DIVS); | ||
|  | 	const int nr = dtClamp(nrings, 1, DT_MAX_PATTERN_RINGS); | ||
|  | 	const float da = (1.0f/nd) * DT_PI*2; | ||
|  | 	const float ca = cosf(da); | ||
|  | 	const float sa = sinf(da); | ||
|  | 
 | ||
|  | 	// desired direction
 | ||
|  | 	float ddir[6]; | ||
|  | 	dtVcopy(ddir, dvel); | ||
|  | 	dtNormalize2D(ddir); | ||
|  | 	dtRorate2D (ddir+3, ddir, da*0.5f); // rotated by da/2
 | ||
|  | 
 | ||
|  | 	// Always add sample at zero
 | ||
|  | 	pat[npat*2+0] = 0; | ||
|  | 	pat[npat*2+1] = 0; | ||
|  | 	npat++; | ||
|  | 	 | ||
|  | 	for (int j = 0; j < nr; ++j) | ||
|  | 	{ | ||
|  | 		const float r = (float)(nr-j)/(float)nr; | ||
|  | 		pat[npat*2+0] = ddir[(j%2)*3] * r; | ||
|  | 		pat[npat*2+1] = ddir[(j%2)*3+2] * r; | ||
|  | 		float* last1 = pat + npat*2; | ||
|  | 		float* last2 = last1; | ||
|  | 		npat++; | ||
|  | 
 | ||
|  | 		for (int i = 1; i < nd-1; i+=2) | ||
|  | 		{ | ||
|  | 			// get next point on the "right" (rotate CW)
 | ||
|  | 			pat[npat*2+0] = last1[0]*ca + last1[1]*sa; | ||
|  | 			pat[npat*2+1] = -last1[0]*sa + last1[1]*ca; | ||
|  | 			// get next point on the "left" (rotate CCW)
 | ||
|  | 			pat[npat*2+2] = last2[0]*ca - last2[1]*sa; | ||
|  | 			pat[npat*2+3] = last2[0]*sa + last2[1]*ca; | ||
|  | 
 | ||
|  | 			last1 = pat + npat*2; | ||
|  | 			last2 = last1 + 2; | ||
|  | 			npat += 2; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if ((nd&1) == 0) | ||
|  | 		{ | ||
|  | 			pat[npat*2+2] = last2[0]*ca - last2[1]*sa; | ||
|  | 			pat[npat*2+3] = last2[0]*sa + last2[1]*ca; | ||
|  | 			npat++; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  | 	// Start sampling.
 | ||
|  | 	float cr = vmax * (1.0f - m_params.velBias); | ||
|  | 	float res[3]; | ||
|  | 	dtVset(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias); | ||
|  | 	int ns = 0; | ||
|  | 
 | ||
|  | 	for (int k = 0; k < depth; ++k) | ||
|  | 	{ | ||
|  | 		float minPenalty = FLT_MAX; | ||
|  | 		float bvel[3]; | ||
|  | 		dtVset(bvel, 0,0,0); | ||
|  | 		 | ||
|  | 		for (int i = 0; i < npat; ++i) | ||
|  | 		{ | ||
|  | 			float vcand[3]; | ||
|  | 			vcand[0] = res[0] + pat[i*2+0]*cr; | ||
|  | 			vcand[1] = 0; | ||
|  | 			vcand[2] = res[2] + pat[i*2+1]*cr; | ||
|  | 			 | ||
|  | 			if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+0.001f)) continue; | ||
|  | 			 | ||
|  | 			const float penalty = processSample(vcand,cr/10, pos,rad,vel,dvel, minPenalty, debug); | ||
|  | 			ns++; | ||
|  | 			if (penalty < minPenalty) | ||
|  | 			{ | ||
|  | 				minPenalty = penalty; | ||
|  | 				dtVcopy(bvel, vcand); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		dtVcopy(res, bvel); | ||
|  | 
 | ||
|  | 		cr *= 0.5f; | ||
|  | 	}	 | ||
|  | 	 | ||
|  | 	dtVcopy(nvel, res); | ||
|  | 	 | ||
|  | 	return ns; | ||
|  | } |