123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- /*
- ** Copyright (C) 1996, 1997 Microsoft Corporation. All Rights Reserved.
- **
- ** File: KDnode.cpp
- **
- ** Author:
- **
- ** Description:
- **
- ** History:
- */
- #include "pch.h"
- //Allocate space for the node's hitTest and endpoint arrays
- //(as a single allocation)
- void KDnode::allocHitTestsEndpoints(int n)
- {
- if (m_maxHitTests < n)
- {
- //Not enough space ... realloc
- delete [] m_ppHitTests;
- void** v = new void* [n * (1 + 2 * c_nAxes)];
- assert (v);
- m_maxHitTests = n;
- m_ppHitTests = (HitTest**)v;
- m_ppEndpoints[0] = (Endpoint**)(v + n);
- for (int i = 1; (i < c_nAxes); i++)
- m_ppEndpoints[i] = m_ppEndpoints[i - 1] + (n << 1);
- }
- }
- KDnode::KDnode(KDnode* parent)
- :
- m_parent(parent),
- m_pivotAxis(-1),
- m_pivotValue(0.0f),
- m_nHitTests(0),
- m_maxHitTests(0),
- m_ppHitTests(NULL)
- {
- m_ppEndpoints[0] = NULL;
- m_children[0] = m_children[1] = NULL;
- }
- KDnode::~KDnode(void)
- {
- delete m_children[1];
- delete m_children[0];
- delete [] m_ppHitTests;
- }
- void KDnode::reset(bool highF)
- {
- assert (m_parent);
- //Set asside enough space for the same number of hitTests as the parent
- //(pessimistic, but fast)
- allocHitTestsEndpoints(m_parent->m_nHitTests);
- int pivotAxis = m_parent->m_pivotAxis;
- assert (pivotAxis >= 0);
- assert (pivotAxis < c_nAxes);
- float pivotValue = m_parent->m_pivotValue;
- //Extract and mark the subset of the parent's hitTests that belong to this node
- {
- m_nHitTests = 0;
- for (HitTest** ppHitTest = &m_parent->m_ppHitTests[m_parent->m_nHitTests - 1];
- (ppHitTest >= m_parent->m_ppHitTests);
- ppHitTest--)
- {
- HitTest* pHitTest = *ppHitTest;
- assert (pHitTest);
- if (highF)
- {
- //Creating the high child ... anything extending above the pivot value is accepted
- if (pHitTest->m_boundingBox.axes[pivotAxis].values[1] > pivotValue)
- {
- pHitTest->m_activeF = true;
- m_ppHitTests[m_nHitTests++] = pHitTest;
- }
- else
- pHitTest->m_activeF = false;
- }
- else
- {
- //Creating the low child ... anything extending below the pivot value is accepted
- if (pHitTest->m_boundingBox.axes[pivotAxis].values[0] < pivotValue)
- {
- pHitTest->m_activeF = true;
- m_ppHitTests[m_nHitTests++] = pHitTest;
- }
- else
- pHitTest->m_activeF = false;
- }
- }
- assert (m_nHitTests != 0);
- assert (m_nHitTests != m_parent->m_nHitTests);
- }
- if (m_nHitTests >= c_minNodeSize)
- {
- //Generate the sorted endpoint lists for this node
- //use the fact that the parent's endpoint lists are sorted
- //and that the endpoint lists for this node will be a subset
- //(consisting of only the endpoints for the "active" hitTests)
- for (int axis = 0; (axis < c_nAxes); axis++)
- {
- //Work backwards to so the conditionals test vs. a non-equation.
- int thisID = (m_nHitTests << 1) - 1;
- //Note: the condition tests thisID so that the loop will stop as soon as
- //the endpoint list is filled (the remaining endpoints in the parent's
- //endpoint list must not be active).
- for (int parentID = (m_parent->m_nHitTests << 1) - 1;
- (thisID >= 0);
- parentID--)
- {
- assert (parentID >= 0);
- HitTest* pHitTest = m_parent->m_ppEndpoints[axis][parentID]->pHitTest;
- if (pHitTest->m_activeF)
- m_ppEndpoints[axis][thisID--] = m_parent->m_ppEndpoints[axis][parentID];
- }
- //There's no need to sort the endpoint list (since is was sorted in the parent).
- }
- pivot();
- }
- else
- m_pivotAxis = -1;
- }
- static bool findPivot(int nHitTests,
- Endpoint* const * ppEndpoints,
- float* pValue,
- int* pCost)
- {
- assert (nHitTests >= c_minNodeSize);
- assert (ppEndpoints);
- assert (pValue);
- assert (pCost);
- bool optimalF = false;
- int nEndpoints = nHitTests << 1;
- //costBias is used to guarantee that the pivot is worthwhile. Must be
- //in the range [nHitTests, nHitTests * 2].
- int costBias = (5 * nHitTests) >> 2;
- //This is used to generate the minCost (below) and test for the when it's time to quit
- int bestBaseCost = costBias - (nHitTests << 1);
- //Theoretical lowest cost for this node ... account for the fact that it is impossible to
- //perfectly balance a node with an odd number of hitTests
- int minCost = bestBaseCost + (nHitTests & 0x01); //fast mod 2
- //The endpoints start with a low and end with a high, meaning we can skip the first endpoint
- //in the loop (hence the pre-increment) and only test for exit on the high endpoints.
- assert (!ppEndpoints[0]->highF);
- assert (ppEndpoints[(nHitTests << 1) - 1]->highF);
- int nLow = 0; //Number of intervals completely below of the current endpoint
- int nHigh = nHitTests - 1; //Number of intervales completely above the current endpoint
- //Only pivots with a cost less than zero will be considered by pivot, so set the best cost to
- //zero so the only the positives costs pivots will be ignored.
- float bestValue;
- int bestCost = 0;
- while (true)
- {
- assert (nLow < nHitTests);
- const Endpoint* pEndpoint = *(++ppEndpoints);
- if (pEndpoint->highF)
- {
- //crossed a high endpoint, meaning there is one less
- //interval completely below the current point
- nLow++;
- //Calculate the unbalanced and totals here since this will give a lower
- //overall cost than calculating it before nLow is incremented.
- int unbalanced = nLow - nHigh;
- int baseCost = costBias - ((nLow + nHigh) << 1);
- //thisCost == base + |unbalanced| ... just do it fast
- int thisCost = (unbalanced >= 0)
- ? (baseCost + unbalanced)
- : (baseCost - unbalanced);
- if (thisCost < bestCost)
- {
- bestCost = thisCost;
- bestValue = pEndpoint->value;
- if (bestCost == minCost)
- {
- optimalF = true;
- break;
- }
- }
- else if ((bestCost <= bestBaseCost + unbalanced) ||
- (nLow == nHitTests))
- {
- //Since unbalanced only increases, we can give up when the best cost
- //is <= the best cost we can hope to achieve
- //we can also give up if we've reached the end of the endpoint list
- break;
- }
- }
- else
- {
- //No need to calculate costs ... either the previous endpoint was a right
- //endpoint (and the cost wouldn't change since nHigh would be decremented
- //after caclulating the cost) or it was a left endpoint (in which case the
- //cost could only be higher than it was before).
- //Crossed a low endpoint, meaning there is one less
- //interval completely above the current point
- nHigh--;
- }
- }
- *pCost = bestCost;
- *pValue = bestValue;
- return optimalF;
- }
- void KDnode::pivot(void)
- {
- //Select the best pivot axis and value for this node
- //based on the endpoint data
- assert (m_nHitTests >= c_minNodeSize);
- //Rotate the axes to encourage (in the event of a tie for best cost)
- //that children pivot on different axes than their parents.
- int startAxis = m_parent ? ((m_parent->m_pivotAxis + 1) % c_nAxes) : 0;
- int bestAxis = startAxis;
- float bestValue;
- int bestCost;
- bool optimalF = findPivot(m_nHitTests,
- m_ppEndpoints[bestAxis],
- &bestValue,
- &bestCost);
- for (int i = 1; ((!optimalF) && (i < c_nAxes)); i++)
- {
- int axis = (i + startAxis) % c_nAxes;
- float value;
- int cost;
-
- optimalF = findPivot(m_nHitTests,
- m_ppEndpoints[axis],
- &value, &cost);
- if (cost < bestCost)
- {
- bestAxis = axis;
- bestValue = value;
- bestCost = cost;
- }
- }
- if (bestCost < 0)
- {
- //Found a pivot value that did some good ... repivot on the new pivot point
- m_pivotAxis = bestAxis;
- m_pivotValue = bestValue;
- if (!m_children[0])
- {
- assert (!m_children[1]);
- //This node has no children ... create some
- m_children[0] = new KDnode(this);
- m_children[1] = new KDnode(this);
- }
- assert (m_children[0] && m_children[1]);
- //Reset both children based on the new pivot axis/value.
- m_children[0]->reset(false);
- m_children[1]->reset(true);
- }
- else
- m_pivotAxis = -1;
- }
- void KDnode::test(HitTest* pHitTest1,
- CollisionQueue* pQueue) const
- {
- if (m_pivotAxis >= 0)
- {
- //Pass the hit tests along to one or both children
- const Interval& interval = pHitTest1->m_boundingBox.axes[m_pivotAxis];
- if (interval.values[0] < m_pivotValue)
- m_children[0]->test(pHitTest1, pQueue);
- if (interval.values[1] > m_pivotValue)
- m_children[1]->test(pHitTest1, pQueue);
- }
- else
- {
- //this is a terminal node: test bb against each contained hitTest's bounding box
- for (HitTest** ppHitTest = &m_ppHitTests[m_nHitTests - 1];
- (ppHitTest >= m_ppHitTests);
- ppHitTest--)
- {
- HitTest* pHitTest2 = *ppHitTest;
- if ((pHitTest1->m_id < pHitTest2->m_id) &&
- (pHitTest2->m_lastTest != pHitTest1) &&
- (pHitTest1->m_noHit != pHitTest2) &&
- (pHitTest2->m_noHit != pHitTest1))
- {
- pHitTest2->m_lastTest = pHitTest1;
- pHitTest1->Collide(pHitTest2, pQueue);
- }
- }
- }
- }
|