Home | History | Annotate | Download | only in BroadphaseCollision
      1 /*
      2 Bullet Continuous Collision Detection and Physics Library
      3 Copyright (c) 2003-2006 Erwin Coumans  http://continuousphysics.com/Bullet/
      4 
      5 This software is provided 'as-is', without any express or implied warranty.
      6 In no event will the authors be held liable for any damages arising from the use of this software.
      7 Permission is granted to anyone to use this software for any purpose,
      8 including commercial applications, and to alter it and redistribute it freely,
      9 subject to the following restrictions:
     10 
     11 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.
     12 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
     13 3. This notice may not be removed or altered from any source distribution.
     14 */
     15 
     16 #include "btMultiSapBroadphase.h"
     17 
     18 #include "btSimpleBroadphase.h"
     19 #include "LinearMath/btAabbUtil2.h"
     20 #include "btQuantizedBvh.h"
     21 
     22 ///	btSapBroadphaseArray	m_sapBroadphases;
     23 
     24 ///	btOverlappingPairCache*	m_overlappingPairs;
     25 extern int gOverlappingPairs;
     26 
     27 /*
     28 class btMultiSapSortedOverlappingPairCache : public btSortedOverlappingPairCache
     29 {
     30 public:
     31 
     32 	virtual btBroadphasePair*	addOverlappingPair(btBroadphaseProxy* proxy0,btBroadphaseProxy* proxy1)
     33 	{
     34 		return btSortedOverlappingPairCache::addOverlappingPair((btBroadphaseProxy*)proxy0->m_multiSapParentProxy,(btBroadphaseProxy*)proxy1->m_multiSapParentProxy);
     35 	}
     36 };
     37 
     38 */
     39 
     40 btMultiSapBroadphase::btMultiSapBroadphase(int /*maxProxies*/,btOverlappingPairCache* pairCache)
     41 :m_overlappingPairs(pairCache),
     42 m_optimizedAabbTree(0),
     43 m_ownsPairCache(false),
     44 m_invalidPair(0)
     45 {
     46 	if (!m_overlappingPairs)
     47 	{
     48 		m_ownsPairCache = true;
     49 		void* mem = btAlignedAlloc(sizeof(btSortedOverlappingPairCache),16);
     50 		m_overlappingPairs = new (mem)btSortedOverlappingPairCache();
     51 	}
     52 
     53 	struct btMultiSapOverlapFilterCallback : public btOverlapFilterCallback
     54 	{
     55 		virtual ~btMultiSapOverlapFilterCallback()
     56 		{}
     57 		// return true when pairs need collision
     58 		virtual bool	needBroadphaseCollision(btBroadphaseProxy* childProxy0,btBroadphaseProxy* childProxy1) const
     59 		{
     60 			btBroadphaseProxy* multiProxy0 = (btBroadphaseProxy*)childProxy0->m_multiSapParentProxy;
     61 			btBroadphaseProxy* multiProxy1 = (btBroadphaseProxy*)childProxy1->m_multiSapParentProxy;
     62 
     63 			bool collides = (multiProxy0->m_collisionFilterGroup & multiProxy1->m_collisionFilterMask) != 0;
     64 			collides = collides && (multiProxy1->m_collisionFilterGroup & multiProxy0->m_collisionFilterMask);
     65 
     66 			return collides;
     67 		}
     68 	};
     69 
     70 	void* mem = btAlignedAlloc(sizeof(btMultiSapOverlapFilterCallback),16);
     71 	m_filterCallback = new (mem)btMultiSapOverlapFilterCallback();
     72 
     73 	m_overlappingPairs->setOverlapFilterCallback(m_filterCallback);
     74 //	mem = btAlignedAlloc(sizeof(btSimpleBroadphase),16);
     75 //	m_simpleBroadphase = new (mem) btSimpleBroadphase(maxProxies,m_overlappingPairs);
     76 }
     77 
     78 btMultiSapBroadphase::~btMultiSapBroadphase()
     79 {
     80 	if (m_ownsPairCache)
     81 	{
     82 		m_overlappingPairs->~btOverlappingPairCache();
     83 		btAlignedFree(m_overlappingPairs);
     84 	}
     85 }
     86 
     87 
     88 void	btMultiSapBroadphase::buildTree(const btVector3& bvhAabbMin,const btVector3& bvhAabbMax)
     89 {
     90 	m_optimizedAabbTree = new btQuantizedBvh();
     91 	m_optimizedAabbTree->setQuantizationValues(bvhAabbMin,bvhAabbMax);
     92 	QuantizedNodeArray&	nodes = m_optimizedAabbTree->getLeafNodeArray();
     93 	for (int i=0;i<m_sapBroadphases.size();i++)
     94 	{
     95 		btQuantizedBvhNode node;
     96 		btVector3 aabbMin,aabbMax;
     97 		m_sapBroadphases[i]->getBroadphaseAabb(aabbMin,aabbMax);
     98 		m_optimizedAabbTree->quantize(&node.m_quantizedAabbMin[0],aabbMin,0);
     99 		m_optimizedAabbTree->quantize(&node.m_quantizedAabbMax[0],aabbMax,1);
    100 		int partId = 0;
    101 		node.m_escapeIndexOrTriangleIndex = (partId<<(31-MAX_NUM_PARTS_IN_BITS)) | i;
    102 		nodes.push_back(node);
    103 	}
    104 	m_optimizedAabbTree->buildInternal();
    105 }
    106 
    107 btBroadphaseProxy*	btMultiSapBroadphase::createProxy(  const btVector3& aabbMin,  const btVector3& aabbMax,int shapeType,void* userPtr, short int collisionFilterGroup,short int collisionFilterMask, btDispatcher* dispatcher,void* /*ignoreMe*/)
    108 {
    109 	//void* ignoreMe -> we could think of recursive multi-sap, if someone is interested
    110 
    111 	void* mem = btAlignedAlloc(sizeof(btMultiSapProxy),16);
    112 	btMultiSapProxy* proxy = new (mem)btMultiSapProxy(aabbMin,  aabbMax,shapeType,userPtr, collisionFilterGroup,collisionFilterMask);
    113 	m_multiSapProxies.push_back(proxy);
    114 
    115 	///this should deal with inserting/removal into child broadphases
    116 	setAabb(proxy,aabbMin,aabbMax,dispatcher);
    117 	return proxy;
    118 }
    119 
    120 void	btMultiSapBroadphase::destroyProxy(btBroadphaseProxy* /*proxy*/,btDispatcher* /*dispatcher*/)
    121 {
    122 	///not yet
    123 	btAssert(0);
    124 
    125 }
    126 
    127 
    128 void	btMultiSapBroadphase::addToChildBroadphase(btMultiSapProxy* parentMultiSapProxy, btBroadphaseProxy* childProxy, btBroadphaseInterface*	childBroadphase)
    129 {
    130 	void* mem = btAlignedAlloc(sizeof(btBridgeProxy),16);
    131 	btBridgeProxy* bridgeProxyRef = new(mem) btBridgeProxy;
    132 	bridgeProxyRef->m_childProxy = childProxy;
    133 	bridgeProxyRef->m_childBroadphase = childBroadphase;
    134 	parentMultiSapProxy->m_bridgeProxies.push_back(bridgeProxyRef);
    135 }
    136 
    137 
    138 bool boxIsContainedWithinBox(const btVector3& amin,const btVector3& amax,const btVector3& bmin,const btVector3& bmax);
    139 bool boxIsContainedWithinBox(const btVector3& amin,const btVector3& amax,const btVector3& bmin,const btVector3& bmax)
    140 {
    141 return
    142 amin.getX() >= bmin.getX() && amax.getX() <= bmax.getX() &&
    143 amin.getY() >= bmin.getY() && amax.getY() <= bmax.getY() &&
    144 amin.getZ() >= bmin.getZ() && amax.getZ() <= bmax.getZ();
    145 }
    146 
    147 
    148 
    149 
    150 
    151 
    152 void	btMultiSapBroadphase::getAabb(btBroadphaseProxy* proxy,btVector3& aabbMin, btVector3& aabbMax ) const
    153 {
    154 	btMultiSapProxy* multiProxy = static_cast<btMultiSapProxy*>(proxy);
    155 	aabbMin = multiProxy->m_aabbMin;
    156 	aabbMax = multiProxy->m_aabbMax;
    157 }
    158 
    159 void	btMultiSapBroadphase::rayTest(const btVector3& rayFrom,const btVector3& rayTo, btBroadphaseRayCallback& rayCallback, const btVector3& aabbMin,const btVector3& aabbMax)
    160 {
    161 	for (int i=0;i<m_multiSapProxies.size();i++)
    162 	{
    163 		rayCallback.process(m_multiSapProxies[i]);
    164 	}
    165 }
    166 
    167 
    168 //#include <stdio.h>
    169 
    170 void	btMultiSapBroadphase::setAabb(btBroadphaseProxy* proxy,const btVector3& aabbMin,const btVector3& aabbMax, btDispatcher* dispatcher)
    171 {
    172 	btMultiSapProxy* multiProxy = static_cast<btMultiSapProxy*>(proxy);
    173 	multiProxy->m_aabbMin = aabbMin;
    174 	multiProxy->m_aabbMax = aabbMax;
    175 
    176 
    177 //	bool fullyContained = false;
    178 //	bool alreadyInSimple = false;
    179 
    180 
    181 
    182 
    183 	struct MyNodeOverlapCallback : public btNodeOverlapCallback
    184 	{
    185 		btMultiSapBroadphase*	m_multiSap;
    186 		btMultiSapProxy*		m_multiProxy;
    187 		btDispatcher*			m_dispatcher;
    188 
    189 		MyNodeOverlapCallback(btMultiSapBroadphase* multiSap,btMultiSapProxy* multiProxy,btDispatcher* dispatcher)
    190 			:m_multiSap(multiSap),
    191 			m_multiProxy(multiProxy),
    192 			m_dispatcher(dispatcher)
    193 		{
    194 
    195 		}
    196 
    197 		virtual void processNode(int /*nodeSubPart*/, int broadphaseIndex)
    198 		{
    199 			btBroadphaseInterface* childBroadphase = m_multiSap->getBroadphaseArray()[broadphaseIndex];
    200 
    201 			int containingBroadphaseIndex = -1;
    202 			//already found?
    203 			for (int i=0;i<m_multiProxy->m_bridgeProxies.size();i++)
    204 			{
    205 
    206 				if (m_multiProxy->m_bridgeProxies[i]->m_childBroadphase == childBroadphase)
    207 				{
    208 					containingBroadphaseIndex = i;
    209 					break;
    210 				}
    211 			}
    212 			if (containingBroadphaseIndex<0)
    213 			{
    214 				//add it
    215 				btBroadphaseProxy* childProxy = childBroadphase->createProxy(m_multiProxy->m_aabbMin,m_multiProxy->m_aabbMax,m_multiProxy->m_shapeType,m_multiProxy->m_clientObject,m_multiProxy->m_collisionFilterGroup,m_multiProxy->m_collisionFilterMask, m_dispatcher,m_multiProxy);
    216 				m_multiSap->addToChildBroadphase(m_multiProxy,childProxy,childBroadphase);
    217 
    218 			}
    219 		}
    220 	};
    221 
    222 	MyNodeOverlapCallback	myNodeCallback(this,multiProxy,dispatcher);
    223 
    224 
    225 
    226 
    227 	if (m_optimizedAabbTree)
    228 		m_optimizedAabbTree->reportAabbOverlappingNodex(&myNodeCallback,aabbMin,aabbMax);
    229 
    230 	int i;
    231 
    232 	for ( i=0;i<multiProxy->m_bridgeProxies.size();i++)
    233 	{
    234 		btVector3 worldAabbMin,worldAabbMax;
    235 		multiProxy->m_bridgeProxies[i]->m_childBroadphase->getBroadphaseAabb(worldAabbMin,worldAabbMax);
    236 		bool overlapsBroadphase = TestAabbAgainstAabb2(worldAabbMin,worldAabbMax,multiProxy->m_aabbMin,multiProxy->m_aabbMax);
    237 		if (!overlapsBroadphase)
    238 		{
    239 			//remove it now
    240 			btBridgeProxy* bridgeProxy = multiProxy->m_bridgeProxies[i];
    241 
    242 			btBroadphaseProxy* childProxy = bridgeProxy->m_childProxy;
    243 			bridgeProxy->m_childBroadphase->destroyProxy(childProxy,dispatcher);
    244 
    245 			multiProxy->m_bridgeProxies.swap( i,multiProxy->m_bridgeProxies.size()-1);
    246 			multiProxy->m_bridgeProxies.pop_back();
    247 
    248 		}
    249 	}
    250 
    251 
    252 	/*
    253 
    254 	if (1)
    255 	{
    256 
    257 		//find broadphase that contain this multiProxy
    258 		int numChildBroadphases = getBroadphaseArray().size();
    259 		for (int i=0;i<numChildBroadphases;i++)
    260 		{
    261 			btBroadphaseInterface* childBroadphase = getBroadphaseArray()[i];
    262 			btVector3 worldAabbMin,worldAabbMax;
    263 			childBroadphase->getBroadphaseAabb(worldAabbMin,worldAabbMax);
    264 			bool overlapsBroadphase = TestAabbAgainstAabb2(worldAabbMin,worldAabbMax,multiProxy->m_aabbMin,multiProxy->m_aabbMax);
    265 
    266 		//	fullyContained = fullyContained || boxIsContainedWithinBox(worldAabbMin,worldAabbMax,multiProxy->m_aabbMin,multiProxy->m_aabbMax);
    267 			int containingBroadphaseIndex = -1;
    268 
    269 			//if already contains this
    270 
    271 			for (int i=0;i<multiProxy->m_bridgeProxies.size();i++)
    272 			{
    273 				if (multiProxy->m_bridgeProxies[i]->m_childBroadphase == childBroadphase)
    274 				{
    275 					containingBroadphaseIndex = i;
    276 				}
    277 				alreadyInSimple = alreadyInSimple || (multiProxy->m_bridgeProxies[i]->m_childBroadphase == m_simpleBroadphase);
    278 			}
    279 
    280 			if (overlapsBroadphase)
    281 			{
    282 				if (containingBroadphaseIndex<0)
    283 				{
    284 					btBroadphaseProxy* childProxy = childBroadphase->createProxy(aabbMin,aabbMax,multiProxy->m_shapeType,multiProxy->m_clientObject,multiProxy->m_collisionFilterGroup,multiProxy->m_collisionFilterMask, dispatcher);
    285 					childProxy->m_multiSapParentProxy = multiProxy;
    286 					addToChildBroadphase(multiProxy,childProxy,childBroadphase);
    287 				}
    288 			} else
    289 			{
    290 				if (containingBroadphaseIndex>=0)
    291 				{
    292 					//remove
    293 					btBridgeProxy* bridgeProxy = multiProxy->m_bridgeProxies[containingBroadphaseIndex];
    294 
    295 					btBroadphaseProxy* childProxy = bridgeProxy->m_childProxy;
    296 					bridgeProxy->m_childBroadphase->destroyProxy(childProxy,dispatcher);
    297 
    298 					multiProxy->m_bridgeProxies.swap( containingBroadphaseIndex,multiProxy->m_bridgeProxies.size()-1);
    299 					multiProxy->m_bridgeProxies.pop_back();
    300 				}
    301 			}
    302 		}
    303 
    304 
    305 		///If we are in no other child broadphase, stick the proxy in the global 'simple' broadphase (brute force)
    306 		///hopefully we don't end up with many entries here (can assert/provide feedback on stats)
    307 		if (0)//!multiProxy->m_bridgeProxies.size())
    308 		{
    309 			///we don't pass the userPtr but our multisap proxy. We need to patch this, before processing an actual collision
    310 			///this is needed to be able to calculate the aabb overlap
    311 			btBroadphaseProxy* childProxy = m_simpleBroadphase->createProxy(aabbMin,aabbMax,multiProxy->m_shapeType,multiProxy->m_clientObject,multiProxy->m_collisionFilterGroup,multiProxy->m_collisionFilterMask, dispatcher);
    312 			childProxy->m_multiSapParentProxy = multiProxy;
    313 			addToChildBroadphase(multiProxy,childProxy,m_simpleBroadphase);
    314 		}
    315 	}
    316 
    317 	if (!multiProxy->m_bridgeProxies.size())
    318 	{
    319 		///we don't pass the userPtr but our multisap proxy. We need to patch this, before processing an actual collision
    320 		///this is needed to be able to calculate the aabb overlap
    321 		btBroadphaseProxy* childProxy = m_simpleBroadphase->createProxy(aabbMin,aabbMax,multiProxy->m_shapeType,multiProxy->m_clientObject,multiProxy->m_collisionFilterGroup,multiProxy->m_collisionFilterMask, dispatcher);
    322 		childProxy->m_multiSapParentProxy = multiProxy;
    323 		addToChildBroadphase(multiProxy,childProxy,m_simpleBroadphase);
    324 	}
    325 */
    326 
    327 
    328 	//update
    329 	for ( i=0;i<multiProxy->m_bridgeProxies.size();i++)
    330 	{
    331 		btBridgeProxy* bridgeProxyRef = multiProxy->m_bridgeProxies[i];
    332 		bridgeProxyRef->m_childBroadphase->setAabb(bridgeProxyRef->m_childProxy,aabbMin,aabbMax,dispatcher);
    333 	}
    334 
    335 }
    336 bool stopUpdating=false;
    337 
    338 
    339 
    340 class btMultiSapBroadphasePairSortPredicate
    341 {
    342 	public:
    343 
    344 		bool operator() ( const btBroadphasePair& a1, const btBroadphasePair& b1 ) const
    345 		{
    346 				btMultiSapBroadphase::btMultiSapProxy* aProxy0 = a1.m_pProxy0 ? (btMultiSapBroadphase::btMultiSapProxy*)a1.m_pProxy0->m_multiSapParentProxy : 0;
    347 				btMultiSapBroadphase::btMultiSapProxy* aProxy1 = a1.m_pProxy1 ? (btMultiSapBroadphase::btMultiSapProxy*)a1.m_pProxy1->m_multiSapParentProxy : 0;
    348 				btMultiSapBroadphase::btMultiSapProxy* bProxy0 = b1.m_pProxy0 ? (btMultiSapBroadphase::btMultiSapProxy*)b1.m_pProxy0->m_multiSapParentProxy : 0;
    349 				btMultiSapBroadphase::btMultiSapProxy* bProxy1 = b1.m_pProxy1 ? (btMultiSapBroadphase::btMultiSapProxy*)b1.m_pProxy1->m_multiSapParentProxy : 0;
    350 
    351 				 return aProxy0 > bProxy0 ||
    352 					(aProxy0 == bProxy0 && aProxy1 > bProxy1) ||
    353 					(aProxy0 == bProxy0 && aProxy1 == bProxy1 && a1.m_algorithm > b1.m_algorithm);
    354 		}
    355 };
    356 
    357 
    358         ///calculateOverlappingPairs is optional: incremental algorithms (sweep and prune) might do it during the set aabb
    359 void    btMultiSapBroadphase::calculateOverlappingPairs(btDispatcher* dispatcher)
    360 {
    361 
    362 //	m_simpleBroadphase->calculateOverlappingPairs(dispatcher);
    363 
    364 	if (!stopUpdating && getOverlappingPairCache()->hasDeferredRemoval())
    365 	{
    366 
    367 		btBroadphasePairArray&	overlappingPairArray = getOverlappingPairCache()->getOverlappingPairArray();
    368 
    369 	//	quicksort(overlappingPairArray,0,overlappingPairArray.size());
    370 
    371 		overlappingPairArray.quickSort(btMultiSapBroadphasePairSortPredicate());
    372 
    373 		//perform a sort, to find duplicates and to sort 'invalid' pairs to the end
    374 	//	overlappingPairArray.heapSort(btMultiSapBroadphasePairSortPredicate());
    375 
    376 		overlappingPairArray.resize(overlappingPairArray.size() - m_invalidPair);
    377 		m_invalidPair = 0;
    378 
    379 
    380 		int i;
    381 
    382 		btBroadphasePair previousPair;
    383 		previousPair.m_pProxy0 = 0;
    384 		previousPair.m_pProxy1 = 0;
    385 		previousPair.m_algorithm = 0;
    386 
    387 
    388 		for (i=0;i<overlappingPairArray.size();i++)
    389 		{
    390 
    391 			btBroadphasePair& pair = overlappingPairArray[i];
    392 
    393 			btMultiSapProxy* aProxy0 = pair.m_pProxy0 ? (btMultiSapProxy*)pair.m_pProxy0->m_multiSapParentProxy : 0;
    394 			btMultiSapProxy* aProxy1 = pair.m_pProxy1 ? (btMultiSapProxy*)pair.m_pProxy1->m_multiSapParentProxy : 0;
    395 			btMultiSapProxy* bProxy0 = previousPair.m_pProxy0 ? (btMultiSapProxy*)previousPair.m_pProxy0->m_multiSapParentProxy : 0;
    396 			btMultiSapProxy* bProxy1 = previousPair.m_pProxy1 ? (btMultiSapProxy*)previousPair.m_pProxy1->m_multiSapParentProxy : 0;
    397 
    398 			bool isDuplicate = (aProxy0 == bProxy0) && (aProxy1 == bProxy1);
    399 
    400 			previousPair = pair;
    401 
    402 			bool needsRemoval = false;
    403 
    404 			if (!isDuplicate)
    405 			{
    406 				bool hasOverlap = testAabbOverlap(pair.m_pProxy0,pair.m_pProxy1);
    407 
    408 				if (hasOverlap)
    409 				{
    410 					needsRemoval = false;//callback->processOverlap(pair);
    411 				} else
    412 				{
    413 					needsRemoval = true;
    414 				}
    415 			} else
    416 			{
    417 				//remove duplicate
    418 				needsRemoval = true;
    419 				//should have no algorithm
    420 				btAssert(!pair.m_algorithm);
    421 			}
    422 
    423 			if (needsRemoval)
    424 			{
    425 				getOverlappingPairCache()->cleanOverlappingPair(pair,dispatcher);
    426 
    427 		//		m_overlappingPairArray.swap(i,m_overlappingPairArray.size()-1);
    428 		//		m_overlappingPairArray.pop_back();
    429 				pair.m_pProxy0 = 0;
    430 				pair.m_pProxy1 = 0;
    431 				m_invalidPair++;
    432 				gOverlappingPairs--;
    433 			}
    434 
    435 		}
    436 
    437 	///if you don't like to skip the invalid pairs in the array, execute following code:
    438 	#define CLEAN_INVALID_PAIRS 1
    439 	#ifdef CLEAN_INVALID_PAIRS
    440 
    441 		//perform a sort, to sort 'invalid' pairs to the end
    442 		//overlappingPairArray.heapSort(btMultiSapBroadphasePairSortPredicate());
    443 		overlappingPairArray.quickSort(btMultiSapBroadphasePairSortPredicate());
    444 
    445 		overlappingPairArray.resize(overlappingPairArray.size() - m_invalidPair);
    446 		m_invalidPair = 0;
    447 	#endif//CLEAN_INVALID_PAIRS
    448 
    449 		//printf("overlappingPairArray.size()=%d\n",overlappingPairArray.size());
    450 	}
    451 
    452 
    453 }
    454 
    455 
    456 bool	btMultiSapBroadphase::testAabbOverlap(btBroadphaseProxy* childProxy0,btBroadphaseProxy* childProxy1)
    457 {
    458 	btMultiSapProxy* multiSapProxy0 = (btMultiSapProxy*)childProxy0->m_multiSapParentProxy;
    459 		btMultiSapProxy* multiSapProxy1 = (btMultiSapProxy*)childProxy1->m_multiSapParentProxy;
    460 
    461 		return	TestAabbAgainstAabb2(multiSapProxy0->m_aabbMin,multiSapProxy0->m_aabbMax,
    462 			multiSapProxy1->m_aabbMin,multiSapProxy1->m_aabbMax);
    463 
    464 }
    465 
    466 
    467 void	btMultiSapBroadphase::printStats()
    468 {
    469 /*	printf("---------------------------------\n");
    470 
    471 		printf("btMultiSapBroadphase.h\n");
    472 		printf("numHandles = %d\n",m_multiSapProxies.size());
    473 			//find broadphase that contain this multiProxy
    474 		int numChildBroadphases = getBroadphaseArray().size();
    475 		for (int i=0;i<numChildBroadphases;i++)
    476 		{
    477 
    478 			btBroadphaseInterface* childBroadphase = getBroadphaseArray()[i];
    479 			childBroadphase->printStats();
    480 
    481 		}
    482 		*/
    483 
    484 }
    485 
    486 void btMultiSapBroadphase::resetPool(btDispatcher* dispatcher)
    487 {
    488 	// not yet
    489 }
    490