public void Build(IList <ISpaceObject> objs) { if (null == m_Objects || m_Objects.Length < objs.Count) { m_ObjectNum = 0; m_Objects = new KdTreeObject[objs.Count * 2]; foreach (ISpaceObject obj in objs) { m_Objects[m_ObjectNum++] = new KdTreeObject(obj); } } else { m_ObjectNum = 0; foreach (ISpaceObject obj in objs) { if (null == m_Objects[m_ObjectNum]) { m_Objects[m_ObjectNum] = new KdTreeObject(obj); } else { m_Objects[m_ObjectNum].CopyFrom(obj); } ++m_ObjectNum; } } if (m_ObjectNum > 0) { if (null == m_KdTree || m_KdTree.Length < 3 * m_ObjectNum) { m_KdTree = new KdTreeNode[3 * m_ObjectNum]; for (int i = 0; i < m_KdTree.Length; ++i) { m_KdTree[i] = new KdTreeNode(); } } m_MaxNodeNum = 2 * m_ObjectNum; BuildImpl(); } }
/* Search for the best new velocity. */ public Vector3 ComputeNewVelocity(ISpaceObject obj, Vector3 prefDir, float timeStep, KdObjectTree kdTree, KdObstacleTree kdObstacleTree, float maxSpeed, float neighborDist, bool isUsingAvoidanceVelocity) { ComputeNeighbors(obj, kdTree, kdObstacleTree, maxSpeed, neighborDist); ClearOrcaLine(); Vector3 position = obj.GetPosition(); float radius = (float)obj.GetRadius(); Vector3 velocity = obj.GetVelocity(); Vector3 newVelocity = new Vector3(); float invTimeHorizonObst = 1.0f / m_TimeHorizonObst; /* Create obstacle ORCA lines. */ for (int i = 0; i < m_ObstacleNeighbors.Count; ++i) { Obstacle obstacle1 = m_ObstacleNeighbors[i].Value; Obstacle obstacle2 = obstacle1.m_NextObstacle; Vector3 relativePosition1 = obstacle1.m_Point - position; Vector3 relativePosition2 = obstacle2.m_Point - position; /* * Check if velocity obstacle of obstacle is already taken care of by * previously constructed obstacle ORCA lines. */ bool alreadyCovered = false; for (int j = 0; j < m_OrcaLines.Count; ++j) { if (VectorMath.det(invTimeHorizonObst * relativePosition1 - m_OrcaLines[j].point, m_OrcaLines[j].direction) - invTimeHorizonObst * radius >= -VectorMath.EPSILON && VectorMath.det(invTimeHorizonObst * relativePosition2 - m_OrcaLines[j].point, m_OrcaLines[j].direction) - invTimeHorizonObst * radius >= -VectorMath.EPSILON) { alreadyCovered = true; break; } } if (alreadyCovered) { continue; } /* Not yet covered. Check for collisions. */ float distSq1 = VectorMath.absSq(relativePosition1); float distSq2 = VectorMath.absSq(relativePosition2); float radiusSq = VectorMath.sqr(radius); Vector3 obstacleVector = obstacle2.m_Point - obstacle1.m_Point; float s = -VectorMath.dot(relativePosition1, obstacleVector) / VectorMath.absSq(obstacleVector); float distSqLine = VectorMath.absSq(-relativePosition1 - s * obstacleVector); OrcaLine line; if (s < 0 && distSq1 <= radiusSq) { /* Collision with left vertex. Ignore if non-convex. */ if (obstacle1.m_IsConvex) { line.point = new Vector3(); line.direction = VectorMath.normalize(new Vector3(-relativePosition1.z, 0, relativePosition1.x)); AddOrcaLine(line); } continue; } else if (s > 1 && distSq2 <= radiusSq) { /* Collision with right vertex. Ignore if non-convex * or if it will be taken care of by neighoring obstace */ if (obstacle2.m_IsConvex && VectorMath.det(relativePosition2, obstacle2.m_UnitDir) >= 0) { line.point = new Vector3(); line.direction = VectorMath.normalize(new Vector3(-relativePosition2.z, 0, relativePosition2.x)); AddOrcaLine(line); } continue; } else if (s >= 0 && s < 1 && distSqLine <= radiusSq) { /* Collision with obstacle segment. */ line.point = new Vector3(); line.direction = -obstacle1.m_UnitDir; AddOrcaLine(line); continue; } /* * No collision. * Compute legs. When obliquely viewed, both legs can come from a single * vertex. Legs extend cut-off line when nonconvex vertex. */ Vector3 leftLegDirection, rightLegDirection; if (s < 0 && distSqLine <= radiusSq) { /* * Obstacle viewed obliquely so that left vertex * defines velocity obstacle. */ if (!obstacle1.m_IsConvex) { /* Ignore obstacle. */ continue; } obstacle2 = obstacle1; float leg1 = VectorMath.sqrt(distSq1 - radiusSq); leftLegDirection = new Vector3(relativePosition1.x * leg1 - relativePosition1.z * radius, 0, relativePosition1.x * radius + relativePosition1.z * leg1) / distSq1; rightLegDirection = new Vector3(relativePosition1.x * leg1 + relativePosition1.z * radius, 0, -relativePosition1.x * radius + relativePosition1.z * leg1) / distSq1; } else if (s > 1 && distSqLine <= radiusSq) { /* * Obstacle viewed obliquely so that * right vertex defines velocity obstacle. */ if (!obstacle2.m_IsConvex) { /* Ignore obstacle. */ continue; } obstacle1 = obstacle2; float leg2 = VectorMath.sqrt(distSq2 - radiusSq); leftLegDirection = new Vector3(relativePosition2.x * leg2 - relativePosition2.z * radius, 0, relativePosition2.x * radius + relativePosition2.z * leg2) / distSq2; rightLegDirection = new Vector3(relativePosition2.x * leg2 + relativePosition2.z * radius, 0, -relativePosition2.x * radius + relativePosition2.z * leg2) / distSq2; } else { /* Usual situation. */ if (obstacle1.m_IsConvex) { float leg1 = VectorMath.sqrt(distSq1 - radiusSq); leftLegDirection = new Vector3(relativePosition1.x * leg1 - relativePosition1.z * radius, 0, relativePosition1.x * radius + relativePosition1.z * leg1) / distSq1; } else { /* Left vertex non-convex; left leg extends cut-off line. */ leftLegDirection = -obstacle1.m_UnitDir; } if (obstacle2.m_IsConvex) { float leg2 = VectorMath.sqrt(distSq2 - radiusSq); rightLegDirection = new Vector3(relativePosition2.x * leg2 + relativePosition2.z * radius, 0, -relativePosition2.x * radius + relativePosition2.z * leg2) / distSq2; } else { /* Right vertex non-convex; right leg extends cut-off line. */ rightLegDirection = obstacle1.m_UnitDir; } } /* * Legs can never point into neighboring edge when convex vertex, * take cutoff-line of neighboring edge instead. If velocity projected on * "foreign" leg, no constraint is added. */ Obstacle leftNeighbor = obstacle1.m_PrevObstacle; bool isLeftLegForeign = false; bool isRightLegForeign = false; if (obstacle1.m_IsConvex && VectorMath.det(leftLegDirection, -leftNeighbor.m_UnitDir) >= 0.0f) { /* Left leg points into obstacle. */ leftLegDirection = -leftNeighbor.m_UnitDir; isLeftLegForeign = true; } if (obstacle2.m_IsConvex && VectorMath.det(rightLegDirection, obstacle2.m_UnitDir) <= 0.0f) { /* Right leg points into obstacle. */ rightLegDirection = obstacle2.m_UnitDir; isRightLegForeign = true; } /* Compute cut-off centers. */ Vector3 leftCutoff = invTimeHorizonObst * (obstacle1.m_Point - position); Vector3 rightCutoff = invTimeHorizonObst * (obstacle2.m_Point - position); Vector3 cutoffVec = rightCutoff - leftCutoff; /* Project current velocity on velocity obstacle. */ /* Check if current velocity is projected on cutoff circles. */ float t = (obstacle1 == obstacle2 ? 0.5f : VectorMath.dot((velocity - leftCutoff), cutoffVec) / VectorMath.absSq(cutoffVec)); float tLeft = VectorMath.dot((velocity - leftCutoff), leftLegDirection); float tRight = VectorMath.dot((velocity - rightCutoff), rightLegDirection); if ((t < 0.0f && tLeft < 0.0f) || (obstacle1 == obstacle2 && tLeft < 0.0f && tRight < 0.0f)) { /* Project on left cut-off circle. */ Vector3 unitW = VectorMath.normalize(velocity - leftCutoff); line.direction = new Vector3(unitW.z, 0, -unitW.x); line.point = leftCutoff + radius * invTimeHorizonObst * unitW; AddOrcaLine(line); continue; } else if (t > 1.0f && tRight < 0.0f) { /* Project on right cut-off circle. */ Vector3 unitW = VectorMath.normalize(velocity - rightCutoff); line.direction = new Vector3(unitW.z, 0, -unitW.x); line.point = rightCutoff + radius * invTimeHorizonObst * unitW; AddOrcaLine(line); continue; } /* * Project on left leg, right leg, or cut-off line, whichever is closest * to velocity. */ float distSqCutoff = ((t <0.0f || t> 1.0f || obstacle1 == obstacle2) ? float.PositiveInfinity : VectorMath.absSq(velocity - (leftCutoff + t * cutoffVec))); float distSqLeft = ((tLeft < 0.0f) ? float.PositiveInfinity : VectorMath.absSq(velocity - (leftCutoff + tLeft * leftLegDirection))); float distSqRight = ((tRight < 0.0f) ? float.PositiveInfinity : VectorMath.absSq(velocity - (rightCutoff + tRight * rightLegDirection))); if (distSqCutoff <= distSqLeft && distSqCutoff <= distSqRight) { /* Project on cut-off line. */ line.direction = -obstacle1.m_UnitDir; line.point = leftCutoff + radius * invTimeHorizonObst * new Vector3(-line.direction.z, 0, line.direction.x); AddOrcaLine(line); continue; } else if (distSqLeft <= distSqRight) { /* Project on left leg. */ if (isLeftLegForeign) { continue; } line.direction = leftLegDirection; line.point = leftCutoff + radius * invTimeHorizonObst * new Vector3(-line.direction.z, 0, line.direction.x); AddOrcaLine(line); continue; } else { /* Project on right leg. */ if (isRightLegForeign) { continue; } line.direction = -rightLegDirection; line.point = rightCutoff + radius * invTimeHorizonObst * new Vector3(-line.direction.z, 0, line.direction.x); AddOrcaLine(line); continue; } } int numObstLines = m_OrcaLines.Count; float invTimeHorizon = 1.0f / m_TimeHorizon; /* Create obj ORCA lines. */ for (int i = 0; i < m_ObjNeighbors.Count; ++i) { KdTreeObject other = m_ObjNeighbors[i].Value; if (obj == other.SpaceObject) { continue; } Vector3 relativePosition = other.Position - position; Vector3 relativeVelocity = velocity - other.Velocity; float distSq = VectorMath.absSq(relativePosition); float combinedRadius = radius + other.Radius; float combinedRadiusSq = VectorMath.sqr(combinedRadius); OrcaLine line; Vector3 u; if (distSq > combinedRadiusSq) { /* No collision. */ Vector3 w = relativeVelocity - invTimeHorizon * relativePosition; /* Vector from cutoff center to relative velocity. */ float wLengthSq = VectorMath.absSq(w); float dotProduct1 = VectorMath.dot(w, relativePosition); if (dotProduct1 < 0.0f && VectorMath.sqr(dotProduct1) > combinedRadiusSq * wLengthSq) { /* Project on cut-off circle. */ float wLength = VectorMath.sqrt(wLengthSq); Vector3 unitW = w / wLength; line.direction = new Vector3(unitW.z, 0, -unitW.x); u = (combinedRadius * invTimeHorizon - wLength) * unitW; } else { /* Project on legs. */ float leg = VectorMath.sqrt(distSq - combinedRadiusSq); if (VectorMath.det(relativePosition, w) > 0.0f) { /* Project on left leg. */ line.direction = new Vector3(relativePosition.x * leg - relativePosition.z * combinedRadius, 0, relativePosition.x * combinedRadius + relativePosition.z * leg) / distSq; } else { /* Project on right leg. */ line.direction = -new Vector3(relativePosition.x * leg + relativePosition.z * combinedRadius, 0, -relativePosition.x * combinedRadius + relativePosition.z * leg) / distSq; } float dotProduct2 = VectorMath.dot(relativeVelocity, line.direction); u = dotProduct2 * line.direction - relativeVelocity; } } else { /* Collision. Project on cut-off circle of time timeStep. */ float invTimeStep = 1.0f / timeStep; /* Vector from cutoff center to relative velocity. */ Vector3 w = relativeVelocity - invTimeStep * relativePosition; float wLength = VectorMath.abs(w); if (wLength < VectorMath.EPSILON) { //LogSystem.Error("RvoAlgorithm error: Objects already overlap ! {0}({1}):{2}-{3}({4}):{5}", obj.GetID(), obj.GetObjType(), obj.GetPosition().ToString(), other.SpaceObject.GetID(), other.SpaceObject.GetObjType(), other.Position.ToString()); continue; } Vector3 unitW = w / wLength; line.direction = new Vector3(unitW.z, 0, -unitW.x); u = (combinedRadius * invTimeStep - wLength) * unitW; } line.point = velocity + 0.5f * u; AddOrcaLine(line); } Vector3 avoidanceVelocity; if (isUsingAvoidanceVelocity) { avoidanceVelocity = obj.GetVelocity(); } else { avoidanceVelocity = new Vector3(); } int lineFail = LinearProgram2(m_OrcaLines, maxSpeed, prefDir, avoidanceVelocity, false, ref newVelocity); if (lineFail < m_OrcaLines.Count) { LinearProgram3(m_OrcaLines, numObstLines, lineFail, maxSpeed, avoidanceVelocity, ref newVelocity); } return(newVelocity); }
private void QueryImpl(Vector3 pos, float range, float rangeSq, MyFunc <float, KdTreeObject, bool> visitor) { m_QueryStack.Push(0); while (m_QueryStack.Count > 0) { int node = m_QueryStack.Pop(); int begin = m_KdTree[node].m_Begin; int end = m_KdTree[node].m_End; int left = m_KdTree[node].m_Left; int right = m_KdTree[node].m_Right; if (end > begin) { for (int i = begin; i < end; ++i) { KdTreeObject obj = m_Objects[i]; if (Geometry.RectangleOverlapRectangle(pos.x - range, pos.z - range, pos.x + range, pos.z + range, obj.MinX, obj.MinZ, obj.MaxX, obj.MaxZ)) { float distSq = Geometry.DistanceSquare(pos, obj.Position); if (!visitor(distSq, obj)) { m_QueryStack.Clear(); return; } } } } float minX = m_KdTree[node].m_MinX; float minZ = m_KdTree[node].m_MinZ; float maxX = m_KdTree[node].m_MaxX; float maxZ = m_KdTree[node].m_MaxZ; bool isVertical = (maxX - minX > maxZ - minZ); float splitValue = (isVertical ? 0.5f * (maxX + minX) : 0.5f * (maxZ + minZ)); if ((isVertical ? pos.x + range : pos.z + range) < splitValue) { if (left > 0) { m_QueryStack.Push(left); } } else if ((isVertical ? pos.x - range : pos.z - range) < splitValue) { if (left > 0) { m_QueryStack.Push(left); } if (right > 0) { m_QueryStack.Push(right); } } else { if (right > 0) { m_QueryStack.Push(right); } } } }
private void BuildImpl() { int nextUnusedNode = 1; m_BuildStack.Push(0); m_BuildStack.Push(m_ObjectNum); m_BuildStack.Push(0); while (m_BuildStack.Count >= 3) { int begin = m_BuildStack.Pop(); int end = m_BuildStack.Pop(); int node = m_BuildStack.Pop(); KdTreeObject obj0 = m_Objects[begin]; float minX = obj0.MinX; float maxX = obj0.MaxX; float minZ = obj0.MinZ; float maxZ = obj0.MaxZ; for (int i = begin + 1; i < end; ++i) { KdTreeObject obj = m_Objects[i]; float newMaxX = obj.MaxX; float newMinX = obj.MinX; float newMaxZ = obj.MaxZ; float newMinZ = obj.MinZ; if (minX > newMinX) { minX = newMinX; } if (maxX < newMaxX) { maxX = newMaxX; } if (minZ > newMinZ) { minZ = newMinZ; } if (maxZ < newMaxZ) { maxZ = newMaxZ; } } m_KdTree[node].m_MinX = minX; m_KdTree[node].m_MaxX = maxX; m_KdTree[node].m_MinZ = minZ; m_KdTree[node].m_MaxZ = maxZ; if (end - begin > c_MaxLeafSize) { m_KdTree[node].m_Left = nextUnusedNode; ++nextUnusedNode; m_KdTree[node].m_Right = nextUnusedNode; ++nextUnusedNode; bool isVertical = (maxX - minX > maxZ - minZ); float splitValue = (isVertical ? 0.5f * (maxX + minX) : 0.5f * (maxZ + minZ)); int begin0 = begin; int left = begin; int right = end; bool canSplit = false; while (left < right) { while (left < right) { KdTreeObject obj = m_Objects[left]; if ((isVertical ? obj.MaxX : obj.MaxZ) < splitValue) { ++left; canSplit = true; } else if ((isVertical ? obj.MinX : obj.MinZ) < splitValue) { obj.Indexed = true; break; } else { break; } } while (left < right) { KdTreeObject obj = m_Objects[right - 1]; if ((isVertical ? obj.MinX : obj.MinZ) >= splitValue) { --right; } else if ((isVertical ? obj.MaxX : obj.MaxZ) >= splitValue) { obj.Indexed = true; break; } else { break; } } if (left < right) { if (m_Objects[left].Indexed || m_Objects[right - 1].Indexed) { if (m_Objects[left].Indexed) { KdTreeObject tmp = m_Objects[begin]; m_Objects[begin] = m_Objects[left]; m_Objects[left] = tmp; ++begin; ++left; canSplit = true; } if (left < right && m_Objects[right - 1].Indexed) { KdTreeObject tmp = m_Objects[begin]; m_Objects[begin] = m_Objects[right - 1]; m_Objects[right - 1] = tmp; ++begin; if (begin >= left) { ++left; canSplit = true; } } } else { KdTreeObject tmp = m_Objects[left]; m_Objects[left] = m_Objects[right - 1]; m_Objects[right - 1] = tmp; ++left; --right; canSplit = true; } } } if (canSplit) { m_KdTree[node].m_Begin = begin0; m_KdTree[node].m_End = begin; if (left > begin) { m_BuildStack.Push(m_KdTree[node].m_Left); m_BuildStack.Push(left); m_BuildStack.Push(begin); } if (end > left) { m_BuildStack.Push(m_KdTree[node].m_Right); m_BuildStack.Push(end); m_BuildStack.Push(left); } } else { m_KdTree[node].m_Begin = begin0; m_KdTree[node].m_End = begin0; m_KdTree[node].m_Left = 0; m_KdTree[node].m_Right = 0; nextUnusedNode -= 2; } } else { m_KdTree[node].m_Begin = begin; m_KdTree[node].m_End = end; m_KdTree[node].m_Left = 0; m_KdTree[node].m_Right = 0; } } }