//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "sim/sceneObject.h" #include "sceneGraph/sceneGraph.h" #include "console/consoleTypes.h" #include "collision/extrudedPolyList.h" #include "collision/earlyOutPolyList.h" #include "platform/profiler.h" #include "platform/profiler.h" #include "interior/interior.h" #include "interior/interiorInstance.h" #include "terrain/terrData.h" #include "dgl/gBitmap.h" #include "dgl/dgl.h" #include "sim/netConnection.h" #include "lightingSystem/sgLightObject.h" IMPLEMENT_CONOBJECT(SceneObject); const U32 Container::csmNumBins = 16; const F32 Container::csmBinSize = 64; const F32 Container::csmTotalBinSize = Container::csmBinSize * Container::csmNumBins; U32 Container::smCurrSeqKey = 1; const U32 Container::csmRefPoolBlockSize = 4096; // Statics used by buildPolyList methods AbstractPolyList* sPolyList; SphereF sBoundingSphere; Box3F sBoundingBox; // Statics used by collide methods ExtrudedPolyList sExtrudedPolyList; Polyhedron sBoxPolyhedron; //-------------------------------------------------------------------------- //-------------------------------------- Console callbacks // ConsoleMethod( SceneObject, getTransform, const char*, 2, 2, "Get transform of object.") { char *returnBuffer = Con::getReturnBuffer(256); const MatrixF& mat = object->getTransform(); Point3F pos; mat.getColumn(3,&pos); AngAxisF aa(mat); dSprintf(returnBuffer,256,"%g %g %g %g %g %g %g", pos.x,pos.y,pos.z,aa.axis.x,aa.axis.y,aa.axis.z,aa.angle); return returnBuffer; } ConsoleMethod( SceneObject, getPosition, const char*, 2, 2, "Get position of object.") { char *returnBuffer = Con::getReturnBuffer(256); const MatrixF& mat = object->getTransform(); Point3F pos; mat.getColumn(3,&pos); dSprintf(returnBuffer,256,"%g %g %g",pos.x,pos.y,pos.z); return returnBuffer; } ConsoleMethod( SceneObject, getForwardVector, const char*, 2, 2, "Returns a vector indicating the direction this object is facing.") { char *returnBuffer = Con::getReturnBuffer(256); const MatrixF& mat = object->getTransform(); Point3F dir; mat.getColumn(1,&dir); dSprintf(returnBuffer,256,"%g %g %g",dir.x,dir.y,dir.z); return returnBuffer; } ConsoleMethod( SceneObject, setTransform, void, 3, 3, "(Transform T)") { Point3F pos; const MatrixF& tmat = object->getTransform(); tmat.getColumn(3,&pos); AngAxisF aa(tmat); dSscanf(argv[2],"%g %g %g %g %g %g %g", &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); MatrixF mat; aa.setMatrix(&mat); mat.setColumn(3,pos); object->setTransform(mat); } ConsoleMethod( SceneObject, getScale, const char*, 2, 2, "Get scaling as a Point3F.") { char *returnBuffer = Con::getReturnBuffer(256); const VectorF & scale = object->getScale(); dSprintf(returnBuffer, 256, "%g %g %g", scale.x, scale.y, scale.z); return(returnBuffer); } ConsoleMethod( SceneObject, setScale, void, 3, 3, "(Point3F scale)") { VectorF scale(0.f,0.f,0.f); dSscanf(argv[2], "%g %g %g", &scale.x, &scale.y, &scale.z); object->setScale(scale); } ConsoleMethod( SceneObject, getWorldBox, const char*, 2, 2, "Returns six fields, two Point3Fs, containing the min and max points of the worldbox.") { char *returnBuffer = Con::getReturnBuffer(256); const Box3F& box = object->getWorldBox(); dSprintf(returnBuffer,256,"%g %g %g %g %g %g", box.min.x, box.min.y, box.min.z, box.max.x, box.max.y, box.max.z); return returnBuffer; } ConsoleMethod( SceneObject, getWorldBoxCenter, const char*, 2, 2, "Returns the center of the world bounding box.") { char *returnBuffer = Con::getReturnBuffer(256); const Box3F& box = object->getWorldBox(); Point3F center; box.getCenter(¢er); dSprintf(returnBuffer,256,"%g %g %g", center.x, center.y, center.z); return returnBuffer; } ConsoleMethod( SceneObject, getObjectBox, const char *, 2, 2, "Returns the bounding box relative to the object's origin.") { char *returnBuffer = Con::getReturnBuffer(256); const Box3F& box = object->getObjBox(); dSprintf(returnBuffer,256,"%g %g %g %g %g %g", box.min.x, box.min.y, box.min.z, box.max.x, box.max.y, box.max.z); return returnBuffer; } ConsoleFunctionGroupBegin( Containers, "Functions for ray casting and spatial queries.\n\n" "@note These only work server-side."); ConsoleFunction(containerBoxEmpty, bool, 4, 6, "(bitset mask, Point3F center, float xRadius, float yRadius, float zRadius)" "See if any objects of given types are present in box of given extent.\n\n" "@note Extent parameter is last since only one radius is often needed. If one radius is provided, " "the yRadius and zRadius are assumed to be the same.\n" "@param mask Indicates the type of objects we are checking against.\n" "@param center Center of box.\n" "@param xRadius See above.\n" "@param yRadius See above.\n" "@param zRadius See above.") { Point3F center; Point3F extent; U32 mask = dAtoi(argv[1]); dSscanf(argv[2], "%g %g %g", ¢er.x, ¢er.y, ¢er.z); extent.x = dAtof(argv[3]); extent.y = argc > 4 ? dAtof(argv[4]) : extent.x; extent.z = argc > 5 ? dAtof(argv[5]) : extent.x; Box3F B(center - extent, center + extent, true); EarlyOutPolyList polyList; polyList.mPlaneList.clear(); polyList.mNormal.set(0,0,0); polyList.mPlaneList.setSize(6); polyList.mPlaneList[0].set(B.min, VectorF(-1,0,0)); polyList.mPlaneList[1].set(B.max, VectorF(0,1,0)); polyList.mPlaneList[2].set(B.max, VectorF(1,0,0)); polyList.mPlaneList[3].set(B.min, VectorF(0,-1,0)); polyList.mPlaneList[4].set(B.min, VectorF(0,0,-1)); polyList.mPlaneList[5].set(B.max, VectorF(0,0,1)); return ! gServerContainer.buildPolyList(B, mask, &polyList); } ConsoleFunction( initContainerRadiusSearch, void, 4, 4, "(Point3F pos, float radius, bitset mask)" "Start a search for items within radius of pos, filtering by bitset mask.") { F32 x, y, z; dSscanf(argv[1], "%g %g %g", &x, &y, &z); F32 r = dAtof(argv[2]); U32 mask = dAtoi(argv[3]); gServerContainer.initRadiusSearch(Point3F(x, y, z), r, mask); } ConsoleFunction( containerSearchNext, S32, 1, 1, "Get next item from a search started with initContainerRadiusSearch.") { return gServerContainer.containerSearchNext(); } ConsoleFunction( containerSearchCurrDist, F32, 1, 1, "Get distance of the center of the current item from the center of the current initContainerRadiusSearch.") { return gServerContainer.containerSearchCurrDist(); } ConsoleFunction( containerSearchCurrRadiusDist, F32, 1, 1, "Get the distance of the closest point of the current item from the center of the current initContainerRadiusSearch.") { return gServerContainer.containerSearchCurrRadiusDist(); } ConsoleFunction( containerRayCast, const char*, 4, 5, "( Point3F start, Point3F end, bitset mask, SceneObject exempt=NULL )" "Cast a ray from start to end, checking for collision against items matching mask.\n\n" "If exempt is specified, then it is temporarily excluded from collision checks (For " "instance, you might want to exclude the player if said player was firing a weapon.)\n" "@returns A string containing either null, if nothing was struck, or these fields:\n" " - The ID of the object that was struck.\n" " - The x, y, z position that it was struck.\n" " - The x, y, z of the normal of the face that was struck.") { char *returnBuffer = Con::getReturnBuffer(256); Point3F start, end; dSscanf(argv[1], "%g %g %g", &start.x, &start.y, &start.z); dSscanf(argv[2], "%g %g %g", &end.x, &end.y, &end.z); U32 mask = dAtoi(argv[3]); SceneObject* pExempt = NULL; if (argc > 4) { U32 exemptId = dAtoi(argv[4]); Sim::findObject(exemptId, pExempt); } if (pExempt) pExempt->disableCollision(); RayInfo rinfo; S32 ret = 0; if (gServerContainer.castRay(start, end, mask, &rinfo) == true) ret = rinfo.object->getId(); if (pExempt) pExempt->enableCollision(); // add the hit position and normal? if(ret) { dSprintf(returnBuffer, 256, "%d %g %g %g %g %g %g", ret, rinfo.point.x, rinfo.point.y, rinfo.point.z, rinfo.normal.x, rinfo.normal.y, rinfo.normal.z); } else { returnBuffer[0] = '0'; returnBuffer[1] = '\0'; } return(returnBuffer); } ConsoleFunctionGroupEnd( Containers ); // Utility method for bin insertion void getBinRange(const F32 min, const F32 max, U32& minBin, U32& maxBin) { AssertFatal(max >= min, "Error, bad range! in getBinRange"); if ((max - min) >= Container::csmTotalBinSize) { F32 minCoord = mFmod(min, Container::csmTotalBinSize); if (minCoord < 0.0f) { minCoord += Container::csmTotalBinSize; // This is truly lame, but it can happen. There must be a better way to // deal with this. if (minCoord == Container::csmTotalBinSize) minCoord = Container::csmTotalBinSize - 0.01; } AssertFatal(minCoord >= 0.0 && minCoord < Container::csmTotalBinSize, "Bad minCoord"); minBin = U32(minCoord / Container::csmBinSize); AssertFatal(minBin < Container::csmNumBins, avar("Error, bad clipping! (%g, %d)", minCoord, minBin)); maxBin = minBin + (Container::csmNumBins - 1); return; } else { F32 minCoord = mFmod(min, Container::csmTotalBinSize); if (minCoord < 0.0f) { minCoord += Container::csmTotalBinSize; // This is truly lame, but it can happen. There must be a better way to // deal with this. if (minCoord == Container::csmTotalBinSize) minCoord = Container::csmTotalBinSize - 0.01; } AssertFatal(minCoord >= 0.0 && minCoord < Container::csmTotalBinSize, "Bad minCoord"); F32 maxCoord = mFmod(max, Container::csmTotalBinSize); if (maxCoord < 0.0f) { maxCoord += Container::csmTotalBinSize; // This is truly lame, but it can happen. There must be a better way to // deal with this. if (maxCoord == Container::csmTotalBinSize) maxCoord = Container::csmTotalBinSize - 0.01; } AssertFatal(maxCoord >= 0.0 && maxCoord < Container::csmTotalBinSize, "Bad maxCoord"); minBin = U32(minCoord / Container::csmBinSize); maxBin = U32(maxCoord / Container::csmBinSize); AssertFatal(minBin < Container::csmNumBins, avar("Error, bad clipping(min)! (%g, %d)", maxCoord, minBin)); AssertFatal(minBin < Container::csmNumBins, avar("Error, bad clipping(max)! (%g, %d)", maxCoord, maxBin)); // MSVC6 seems to be generating some bad floating point code around // here when full optimizations are on. The min != max test should // not be needed, but it clears up the VC issue. if (min != max && minCoord >= maxCoord) maxBin += Container::csmNumBins; AssertFatal(maxBin >= minBin, "Error, min should always be less than max!"); } } //-------------------------------------------------------------------------- //-------------------------------------- SceneObject implementation // LightInfo SceneObject::LightingInfo::smAmbientLight; SceneObject::SceneObject() { overrideOptions = true; receiveLMLighting = true; receiveSunLight = true; useAdaptiveSelfIllumination = false; useCustomAmbientLighting = false; customAmbientForSelfIllumination = false; customAmbientLighting = ColorF(0.0f, 0.0f, 0.0f); lightGroupName = NULL; useLightingOcclusion = true; moveSnapshotId = 0; mContainer = 0; mTypeMask = DefaultObjectType; mCollisionCount = 0; mGlobalBounds = false; mObjScale.set(1,1,1); mObjToWorld.identity(); mWorldToObj.identity(); mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); mWorldBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); mWorldSphere = SphereF(Point3F(0, 0, 0), 0); mRenderObjToWorld.identity(); mRenderWorldToObj.identity(); mRenderWorldBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); mRenderWorldSphere = SphereF(Point3F(0, 0, 0), 0); mContainerSeqKey = 0; mBinRefHead = NULL; mSceneManager = NULL; mZoneRangeStart = 0xFFFFFFFF; mNumCurrZones = 0; mZoneRefHead = NULL; mLastState = NULL; mLastStateKey = 0; mBinMinX = 0xFFFFFFFF; mBinMaxX = 0xFFFFFFFF; mBinMinY = 0xFFFFFFFF; mBinMaxY = 0xFFFFFFFF; } SceneObject::~SceneObject() { AssertFatal(mZoneRangeStart == 0xFFFFFFFF && mSceneManager == NULL, "Error, SceneObject not properly removed from sceneGraph"); AssertFatal(mZoneRefHead == NULL && mBinRefHead == NULL, "Error, still linked in reference lists!"); unlink(); } //---------------------------------------------------------------------------- const char* SceneObject::scriptThis() { return Con::getIntArg(getId()); } //-------------------------------------------------------------------------- void SceneObject::buildConvex(const Box3F&, Convex*) { return; } bool SceneObject::buildPolyList(AbstractPolyList*, const Box3F&, const SphereF&) { return false; } BSPNode *SceneObject::buildCollisionBSP(BSPTree*, const Box3F&, const SphereF&) { return NULL; } bool SceneObject::castRay(const Point3F&, const Point3F&, RayInfo*) { return false; } bool SceneObject::collideBox(const Point3F &start, const Point3F &end, RayInfo *info) { const F32 * pStart = (const F32*)start; const F32 * pEnd = (const F32*)end; const F32 * pMin = (const F32*)mObjBox.min; const F32 * pMax = (const F32*)mObjBox.max; F32 maxStartTime = -1; F32 minEndTime = 1; F32 startTime; F32 endTime; // used for getting normal U32 hitIndex; U32 side; // walk the axis for(U32 i = 0; i < 3; i++) { // if(pStart[i] < pEnd[i]) { if(pEnd[i] < pMin[i] || pStart[i] > pMax[i]) return(false); F32 dist = pEnd[i] - pStart[i]; startTime = (pStart[i] < pMin[i]) ? (pMin[i] - pStart[i]) / dist : -1; endTime = (pEnd[i] > pMax[i]) ? (pMax[i] - pStart[i]) / dist : 1; side = 1; } else { if(pStart[i] < pMin[i] || pEnd[i] > pMax[i]) return(false); F32 dist = pStart[i] - pEnd[i]; startTime = (pStart[i] > pMax[i]) ? (pStart[i] - pMax[i]) / dist : -1; endTime = (pEnd[i] < pMin[i]) ? (pStart[i] - pMin[i]) / dist : 1; side = 0; } // if(startTime > maxStartTime) { maxStartTime = startTime; hitIndex = i * 2 + side; } if(endTime < minEndTime) minEndTime = endTime; if(minEndTime < maxStartTime) return(false); } // fail if inside if(maxStartTime < 0.f) return(false); // static Point3F boxNormals[] = { Point3F( 1, 0, 0), Point3F(-1, 0, 0), Point3F( 0, 1, 0), Point3F( 0,-1, 0), Point3F( 0, 0, 1), Point3F( 0, 0,-1), }; // info->t = maxStartTime; info->object = this; mObjToWorld.mulV(boxNormals[hitIndex], &info->normal); info->material = 0; return(true); } void SceneObject::disableCollision() { mCollisionCount++; AssertFatal(mCollisionCount < 50, "Wow, that's too much"); } bool SceneObject::isDisplacable() const { return false; } Point3F SceneObject::getMomentum() const { AssertFatal(false, "(SceneObject::getMomentum): Should never be called, this is just a default"); return Point3F(0, 0, 0); } void SceneObject::setMomentum(const Point3F&) { AssertFatal(false, "(SceneObject::setMomentum): Should never be called, this is just a default"); } F32 SceneObject::getMass() const { AssertFatal(false, "(SceneObject::getMass): Should never be called, this is just a default"); return 1.0; } bool SceneObject::displaceObject(const Point3F&) { AssertFatal(false, "(SceneObject::displaceObject): Should never be called, this is just a default"); return false; } void SceneObject::enableCollision() { if (mCollisionCount) --mCollisionCount; } bool SceneObject::onAdd() { if (Parent::onAdd() == false) return false; mWorldToObj = mObjToWorld; mWorldToObj.affineInverse(); resetWorldBox(); setRenderTransform(mObjToWorld); return true; } void SceneObject::addToScene() { if(isClientObject()) { gClientContainer.addObject(this); gClientSceneGraph->addObjectToScene(this); } else { gServerContainer.addObject(this); gServerSceneGraph->addObjectToScene(this); } } void SceneObject::onRemove() { Parent::onRemove(); } void SceneObject::inspectPostApply() { if(isServerObject()) { setTransform(getTransform()); setScale(getScale()); } } void SceneObject::removeFromScene() { if (mSceneManager != NULL) mSceneManager->removeObjectFromScene(this); if (getContainer()) getContainer()->removeObject(this); } void SceneObject::setTransform(const MatrixF& mat) { mObjToWorld = mWorldToObj = mat; mWorldToObj.affineInverse(); resetWorldBox(); if (mSceneManager != NULL && mNumCurrZones != 0) { mSceneManager->zoneRemove(this); mSceneManager->zoneInsert(this); if (getContainer()) getContainer()->checkBins(this); } if(isClientObject()) { mLightingInfo.mDirty = true; moveSnapshotId++; } setRenderTransform(mat); } void SceneObject::setScale(const VectorF & scale) { mObjScale = scale; setTransform(MatrixF(mObjToWorld)); // Make sure that any subclasses of me get a chance to react to the // scale being changed. onScaleChanged(); setMaskBits( ScaleMask ); } void SceneObject::resetWorldBox() { AssertFatal(mObjBox.isValidBox(), "Bad object box!"); mWorldBox = mObjBox; mWorldBox.min.convolve(mObjScale); mWorldBox.max.convolve(mObjScale); mObjToWorld.mul(mWorldBox); AssertFatal(mWorldBox.isValidBox(), "Bad world box!"); // Create mWorldSphere from mWorldBox mWorldBox.getCenter(&mWorldSphere.center); mWorldSphere.radius = (mWorldBox.max - mWorldSphere.center).len(); } void SceneObject::setRenderTransform(const MatrixF& mat) { mRenderObjToWorld = mRenderWorldToObj = mat; mRenderWorldToObj.affineInverse(); AssertFatal(mObjBox.isValidBox(), "Bad object box!"); resetRenderWorldBox(); } void SceneObject::resetRenderWorldBox() { AssertFatal(mObjBox.isValidBox(), "Bad object box!"); mRenderWorldBox = mObjBox; mRenderWorldBox.min.convolve(mObjScale); mRenderWorldBox.max.convolve(mObjScale); mRenderObjToWorld.mul(mRenderWorldBox); // #if defined(__linux__) || defined(__OpenBSD__) // if( !mRenderWorldBox.isValidBox() ) { // // reset // mRenderWorldBox.min.set( 0.0f, 0.0f, 0.0f ); // mRenderWorldBox.max.set( 0.0f, 0.0f, 0.0f ); // } // #else AssertFatal(mRenderWorldBox.isValidBox(), "Bad world box!"); //#endif // Create mRenderWorldSphere from mRenderWorldBox mRenderWorldBox.getCenter(&mRenderWorldSphere.center); mRenderWorldSphere.radius = (mRenderWorldBox.max - mRenderWorldSphere.center).len(); } void SceneObject::initPersistFields() { Parent::initPersistFields(); addGroup("Transform"); // MM: Added group header. addField("position", TypeMatrixPosition, Offset(mObjToWorld, SceneObject)); addField("rotation", TypeMatrixRotation, Offset(mObjToWorld, SceneObject)); addField("scale", TypePoint3F, Offset(mObjScale, SceneObject)); endGroup("Transform"); // MM: Added group footer. } bool SceneObject::onSceneAdd(SceneGraph* pGraph) { mSceneManager = pGraph; mSceneManager->zoneInsert(this); return true; } void SceneObject::onSceneRemove() { mSceneManager->zoneRemove(this); mSceneManager = NULL; } void SceneObject::onScaleChanged() { // Override this function where you need to specially handle something // when the size of your object has been changed. } bool SceneObject::prepRenderImage(SceneState*, const U32, const U32, const bool) { return false; } void SceneObject::renderObject(SceneState*, SceneRenderImage*) { // } bool SceneObject::scopeObject(const Point3F& /*rootPosition*/, const F32 /*rootDistance*/, bool* /*zoneScopeState*/) { AssertFatal(false, "Error, this should never be called on a bare (non-zonemanaging) object. All zone managers must override this function"); return false; } //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- // A quick note about these three functions. They should only be called // on zoneManagers, but since we don't want to force every non-zoneManager // to implement them, they assert out instead of being pure virtual. // bool SceneObject::getOverlappingZones(SceneObject*, U32*, U32* numZones) { AssertISV(false, "Pure virtual (essentially) function called. Should never execute this"); *numZones = 0; return false; } U32 SceneObject::getPointZone(const Point3F&) { AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); return 0; } void SceneObject::transformModelview(const U32, const MatrixF&, MatrixF*) { AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); } void SceneObject::transformPosition(const U32, Point3F&) { AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); } bool SceneObject::computeNewFrustum(const U32, const F64*, const F64, const F64, const RectI&, F64*, RectI&, const bool) { AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); return false; } void SceneObject::openPortal(const U32 /*portalIndex*/, SceneState* /*pCurrState*/, SceneState* /*pParentState*/) { AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); } void SceneObject::closePortal(const U32 /*portalIndex*/, SceneState* /*pCurrState*/, SceneState* /*pParentState*/) { AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); } void SceneObject::getWSPortalPlane(const U32 /*portalIndex*/, PlaneF*) { AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); } //---------------------------------------------------------------------------- //-------------------------------------- Container implementation // Container::Link::Link() { next = prev = this; } void Container::Link::unlink() { next->prev = prev; prev->next = next; next = prev = this; } void Container::Link::linkAfter(Container::Link* ptr) { next = ptr->next; next->prev = this; prev = ptr; prev->next = this; } //---------------------------------------------------------------------------- Container gServerContainer; Container gClientContainer; Container::Container() { mEnd.next = mEnd.prev = &mStart; mStart.next = mStart.prev = &mEnd; if (!sBoxPolyhedron.edgeList.size()) { Box3F box; box.min.set(-1,-1,-1); box.max.set(+1,+1,+1); MatrixF imat(1); sBoxPolyhedron.buildBox(imat,box); } mBinArray = new SceneObjectRef[csmNumBins * csmNumBins]; for (U32 i = 0; i < csmNumBins; i++) { U32 base = i * csmNumBins; for (U32 j = 0; j < csmNumBins; j++) { mBinArray[base + j].object = NULL; mBinArray[base + j].nextInBin = NULL; mBinArray[base + j].prevInBin = NULL; mBinArray[base + j].nextInObj = NULL; } } mOverflowBin.object = NULL; mOverflowBin.nextInBin = NULL; mOverflowBin.prevInBin = NULL; mOverflowBin.nextInObj = NULL; VECTOR_SET_ASSOCIATION(mRefPoolBlocks); VECTOR_SET_ASSOCIATION(mSearchList); mFreeRefPool = NULL; addRefPoolBlock(); cleanupSearchVectors(); } Container::~Container() { for (U32 i = 0; i < mRefPoolBlocks.size(); i++) { SceneObjectRef* pool = mRefPoolBlocks[i]; for (U32 j = 0; j < csmRefPoolBlockSize; j++) { // Depressingly, this can give weird results if its pointing at bad memory... if(pool[j].object != NULL) Con::warnf("Error, a %s (%x) isn't properly out of the bins!", pool[j].object->getClassName(), pool[j].object); // If you're getting this it means that an object created didn't // remove itself from its container before we destroyed the // container. Typically you get this behavior from particle // emitters, as they try to hang around until all their particles // die. In general it's benign, though if you get it for things // that aren't particle emitters it can be a bad sign! } delete [] pool; } mFreeRefPool = NULL; cleanupSearchVectors(); } bool Container::addObject(SceneObject* obj) { AssertFatal(obj->mContainer == NULL, "Adding already added object."); obj->mContainer = this; obj->linkAfter(&mStart); insertIntoBins(obj); return true; } bool Container::removeObject(SceneObject* obj) { AssertFatal(obj->mContainer == this, "Trying to remove from wrong container."); removeFromBins(obj); obj->mContainer = 0; obj->unlink(); return true; } void Container::addRefPoolBlock() { mRefPoolBlocks.push_back(new SceneObjectRef[csmRefPoolBlockSize]); for (U32 i = 0; i < csmRefPoolBlockSize-1; i++) { mRefPoolBlocks.last()[i].object = NULL; mRefPoolBlocks.last()[i].prevInBin = NULL; mRefPoolBlocks.last()[i].nextInBin = NULL; mRefPoolBlocks.last()[i].nextInObj = &(mRefPoolBlocks.last()[i+1]); } mRefPoolBlocks.last()[csmRefPoolBlockSize-1].object = NULL; mRefPoolBlocks.last()[csmRefPoolBlockSize-1].prevInBin = NULL; mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInBin = NULL; mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInObj = mFreeRefPool; mFreeRefPool = &(mRefPoolBlocks.last()[0]); } void Container::insertIntoBins(SceneObject* obj) { AssertFatal(obj != NULL, "No object?"); AssertFatal(obj->mBinRefHead == NULL, "Error, already have a bin chain!"); // The first thing we do is find which bins are covered in x and y... const Box3F* pWBox = &obj->getWorldBox(); U32 minX, maxX, minY, maxY; getBinRange(pWBox->min.x, pWBox->max.x, minX, maxX); getBinRange(pWBox->min.y, pWBox->max.y, minY, maxY); // Store the current regions for later queries obj->mBinMinX = minX; obj->mBinMaxX = maxX; obj->mBinMinY = minY; obj->mBinMaxY = maxY; // For huge objects, dump them into the overflow bin. Otherwise, everything // goes into the grid... if ((maxX - minX + 1) < csmNumBins || (maxY - minY + 1) < csmNumBins && !obj->isGlobalBounds()) { SceneObjectRef** pCurrInsert = &obj->mBinRefHead; for (U32 i = minY; i <= maxY; i++) { U32 insertY = i % csmNumBins; U32 base = insertY * csmNumBins; for (U32 j = minX; j <= maxX; j++) { U32 insertX = j % csmNumBins; SceneObjectRef* ref = allocateObjectRef(); ref->object = obj; ref->nextInBin = mBinArray[base + insertX].nextInBin; ref->prevInBin = &mBinArray[base + insertX]; ref->nextInObj = NULL; if (mBinArray[base + insertX].nextInBin) mBinArray[base + insertX].nextInBin->prevInBin = ref; mBinArray[base + insertX].nextInBin = ref; *pCurrInsert = ref; pCurrInsert = &ref->nextInObj; } } } else { SceneObjectRef* ref = allocateObjectRef(); ref->object = obj; ref->nextInBin = mOverflowBin.nextInBin; ref->prevInBin = &mOverflowBin; ref->nextInObj = NULL; if (mOverflowBin.nextInBin) mOverflowBin.nextInBin->prevInBin = ref; mOverflowBin.nextInBin = ref; obj->mBinRefHead = ref; } } void Container::insertIntoBins(SceneObject* obj, U32 minX, U32 maxX, U32 minY, U32 maxY) { AssertFatal(obj != NULL, "No object?"); AssertFatal(obj->mBinRefHead == NULL, "Error, already have a bin chain!"); // Store the current regions for later queries obj->mBinMinX = minX; obj->mBinMaxX = maxX; obj->mBinMinY = minY; obj->mBinMaxY = maxY; // For huge objects, dump them into the overflow bin. Otherwise, everything // goes into the grid... // if ((maxX - minX + 1) < csmNumBins || (maxY - minY + 1) < csmNumBins && !obj->isGlobalBounds()) { SceneObjectRef** pCurrInsert = &obj->mBinRefHead; for (U32 i = minY; i <= maxY; i++) { U32 insertY = i % csmNumBins; U32 base = insertY * csmNumBins; for (U32 j = minX; j <= maxX; j++) { U32 insertX = j % csmNumBins; SceneObjectRef* ref = allocateObjectRef(); ref->object = obj; ref->nextInBin = mBinArray[base + insertX].nextInBin; ref->prevInBin = &mBinArray[base + insertX]; ref->nextInObj = NULL; if (mBinArray[base + insertX].nextInBin) mBinArray[base + insertX].nextInBin->prevInBin = ref; mBinArray[base + insertX].nextInBin = ref; *pCurrInsert = ref; pCurrInsert = &ref->nextInObj; } } } else { SceneObjectRef* ref = allocateObjectRef(); ref->object = obj; ref->nextInBin = mOverflowBin.nextInBin; ref->prevInBin = &mOverflowBin; ref->nextInObj = NULL; if (mOverflowBin.nextInBin) mOverflowBin.nextInBin->prevInBin = ref; mOverflowBin.nextInBin = ref; obj->mBinRefHead = ref; } } void Container::removeFromBins(SceneObject* obj) { AssertFatal(obj != NULL, "No object?"); SceneObjectRef* chain = obj->mBinRefHead; obj->mBinRefHead = NULL; while (chain) { SceneObjectRef* trash = chain; chain = chain->nextInObj; AssertFatal(trash->prevInBin != NULL, "Error, must have a previous entry in the bin!"); if (trash->nextInBin) trash->nextInBin->prevInBin = trash->prevInBin; trash->prevInBin->nextInBin = trash->nextInBin; freeObjectRef(trash); } } void Container::checkBins(SceneObject* obj) { AssertFatal(obj != NULL, "No object?"); if (obj->mBinRefHead == NULL) { insertIntoBins(obj); return; } // Otherwise, the object is already in the bins. Let's see if it has strayed out of // the bins that it's currently in... const Box3F* pWBox = &obj->getWorldBox(); U32 minX, maxX, minY, maxY; getBinRange(pWBox->min.x, pWBox->max.x, minX, maxX); getBinRange(pWBox->min.y, pWBox->max.y, minY, maxY); if (obj->mBinMinX != minX || obj->mBinMaxX != maxX || obj->mBinMinY != minY || obj->mBinMaxY != maxY) { // We have to rebin the object removeFromBins(obj); insertIntoBins(obj, minX, maxX, minY, maxY); } } void Container::findObjects(const Box3F& box, U32 mask, FindCallback callback, void *key) { U32 minX, maxX, minY, maxY; getBinRange(box.min.x, box.max.x, minX, maxX); getBinRange(box.min.y, box.max.y, minY, maxY); smCurrSeqKey++; for (U32 i = minY; i <= maxY; i++) { U32 insertY = i % csmNumBins; U32 base = insertY * csmNumBins; for (U32 j = minX; j <= maxX; j++) { U32 insertX = j % csmNumBins; SceneObjectRef* chain = mBinArray[base + insertX].nextInBin; while (chain) { if (chain->object->getContainerSeqKey() != smCurrSeqKey) { chain->object->setContainerSeqKey(smCurrSeqKey); if ((chain->object->getType() & mask) != 0 && chain->object->isCollisionEnabled()) { if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) { (*callback)(chain->object,key); } } } chain = chain->nextInBin; } } } SceneObjectRef* chain = mOverflowBin.nextInBin; while (chain) { if (chain->object->getContainerSeqKey() != smCurrSeqKey) { chain->object->setContainerSeqKey(smCurrSeqKey); if ((chain->object->getType() & mask) != 0 && chain->object->isCollisionEnabled()) { if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) { (*callback)(chain->object,key); } } } chain = chain->nextInBin; } } void Container::polyhedronFindObjects(const Polyhedron& polyhedron, U32 mask, FindCallback callback, void *key) { U32 i; Box3F box; box.min.set(1e9, 1e9, 1e9); box.max.set(-1e9, -1e9, -1e9); for (i = 0; i < polyhedron.pointList.size(); i++) { box.min.setMin(polyhedron.pointList[i]); box.max.setMax(polyhedron.pointList[i]); } U32 minX, maxX, minY, maxY; getBinRange(box.min.x, box.max.x, minX, maxX); getBinRange(box.min.y, box.max.y, minY, maxY); smCurrSeqKey++; for (i = minY; i <= maxY; i++) { U32 insertY = i % csmNumBins; U32 base = insertY * csmNumBins; for (U32 j = minX; j <= maxX; j++) { U32 insertX = j % csmNumBins; SceneObjectRef* chain = mBinArray[base + insertX].nextInBin; while (chain) { if (chain->object->getContainerSeqKey() != smCurrSeqKey) { chain->object->setContainerSeqKey(smCurrSeqKey); if ((chain->object->getType() & mask) != 0 && chain->object->isCollisionEnabled()) { if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) { (*callback)(chain->object,key); } } } chain = chain->nextInBin; } } } SceneObjectRef* chain = mOverflowBin.nextInBin; while (chain) { if (chain->object->getContainerSeqKey() != smCurrSeqKey) { chain->object->setContainerSeqKey(smCurrSeqKey); if ((chain->object->getType() & mask) != 0 && chain->object->isCollisionEnabled()) { if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) { (*callback)(chain->object,key); } } } chain = chain->nextInBin; } } //---------------------------------------------------------------------------- // DMMNOTE: There are still some optimizations to be done here. In particular: // - After checking the overflow bin, we can potentially shorten the line // that we rasterize against the grid if there is a collision with say, // the terrain. // - The optimal grid size isn't necessarily what we have set here. possibly // a resolution of 16 meters would give better results // - The line rasterizer is pretty lame. Unfortunately we can't use a // simple bres. here, since we need to check every grid element that the line // passes through, which bres does _not_ do for us. Possibly there's a // rasterizer for anti-aliased lines that will serve better than what // we have below. // bool Container::castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info) { PROFILE_START(ContainerCastRay); F32 currentT = 2.0; smCurrSeqKey++; SceneObjectRef* chain = mOverflowBin.nextInBin; static RayInfo ri; ri.material = 0; while (chain) { SceneObject* ptr = chain->object; if (ptr->getContainerSeqKey() != smCurrSeqKey) { ptr->setContainerSeqKey(smCurrSeqKey); // In the overflow bin, the world box is always going to intersect the line, // so we can omit that test... if ((ptr->getType() & mask) != 0 && ptr->isCollisionEnabled() == true) { Point3F xformedStart, xformedEnd; ptr->mWorldToObj.mulP(start, &xformedStart); ptr->mWorldToObj.mulP(end, &xformedEnd); xformedStart.convolveInverse(ptr->mObjScale); xformedEnd.convolveInverse(ptr->mObjScale); if (ptr->castRay(xformedStart, xformedEnd, &ri)) { if(ri.t < currentT) { *info = ri; info->point.interpolate(start, end, info->t); currentT = ri.t; } } } } chain = chain->nextInBin; } // These are just for rasterizing the line against the grid. We want the x coord // of the start to be <= the x coord of the end Point3F normalStart, normalEnd; if (start.x <= end.x) { normalStart = start; normalEnd = end; } else { normalStart = end; normalEnd = start; } // Ok, let's scan the grids. The simplest way to do this will be to scan across in // x, finding the y range for each affected bin... U32 minX, maxX; U32 minY, maxY; //if (normalStart.x == normalEnd.x) // Con::printf("X start = %g, end = %g", normalStart.x, normalEnd.x); getBinRange(normalStart.x, normalEnd.x, minX, maxX); getBinRange(getMin(normalStart.y, normalEnd.y), getMax(normalStart.y, normalEnd.y), minY, maxY); //if (normalStart.x == normalEnd.x && minX != maxX) // Con::printf("X min = %d, max = %d", minX, maxX); //if (normalStart.y == normalEnd.y && minY != maxY) // Con::printf("Y min = %d, max = %d", minY, maxY); // We'll optimize the case that the line is contained in one bin row or column, which // will be quite a few lines. No sense doing more work than we have to... // if ((mFabs(normalStart.x - normalEnd.x) < csmTotalBinSize && minX == maxX) || (mFabs(normalStart.y - normalEnd.y) < csmTotalBinSize && minY == maxY)) { U32 count; U32 incX, incY; if (minX == maxX) { count = maxY - minY + 1; incX = 0; incY = 1; } else { count = maxX - minX + 1; incX = 1; incY = 0; } U32 x = minX; U32 y = minY; for (U32 i = 0; i < count; i++) { U32 checkX = x % csmNumBins; U32 checkY = y % csmNumBins; SceneObjectRef* chain = mBinArray[(checkY * csmNumBins) + checkX].nextInBin; while (chain) { SceneObject* ptr = chain->object; if (ptr->getContainerSeqKey() != smCurrSeqKey) { ptr->setContainerSeqKey(smCurrSeqKey); if ((ptr->getType() & mask) != 0 && ptr->isCollisionEnabled() == true) { if (ptr->getWorldBox().collideLine(start, end) || chain->object->isGlobalBounds()) { Point3F xformedStart, xformedEnd; ptr->mWorldToObj.mulP(start, &xformedStart); ptr->mWorldToObj.mulP(end, &xformedEnd); xformedStart.convolveInverse(ptr->mObjScale); xformedEnd.convolveInverse(ptr->mObjScale); RayInfo ri; if (ptr->castRay(xformedStart, xformedEnd, &ri)) { if(ri.t < currentT) { *info = ri; info->point.interpolate(start, end, info->t); currentT = ri.t; } } } } } chain = chain->nextInBin; } x += incX; y += incY; } } else { // Oh well, let's earn our keep. We know that after the above conditional, we're // going to cross at least one boundary, so that simplifies our job... F32 currStartX = normalStart.x; AssertFatal(currStartX != normalEnd.x, "This is going to cause problems in Container::castRay"); while (currStartX != normalEnd.x) { F32 currEndX = getMin(currStartX + csmTotalBinSize, normalEnd.x); F32 currStartT = (currStartX - normalStart.x) / (normalEnd.x - normalStart.x); F32 currEndT = (currEndX - normalStart.x) / (normalEnd.x - normalStart.x); F32 y1 = normalStart.y + (normalEnd.y - normalStart.y) * currStartT; F32 y2 = normalStart.y + (normalEnd.y - normalStart.y) * currEndT; U32 subMinX, subMaxX; getBinRange(currStartX, currEndX, subMinX, subMaxX); F32 subStartX = currStartX; F32 subEndX = currStartX; if (currStartX < 0.0f) subEndX -= mFmod(subEndX, csmBinSize); else subEndX += (csmBinSize - mFmod(subEndX, csmBinSize)); for (U32 currXBin = subMinX; currXBin <= subMaxX; currXBin++) { U32 checkX = currXBin % csmNumBins; F32 subStartT = (subStartX - currStartX) / (currEndX - currStartX); F32 subEndT = getMin(F32((subEndX - currStartX) / (currEndX - currStartX)), 1.f); F32 subY1 = y1 + (y2 - y1) * subStartT; F32 subY2 = y1 + (y2 - y1) * subEndT; U32 newMinY, newMaxY; getBinRange(getMin(subY1, subY2), getMax(subY1, subY2), newMinY, newMaxY); for (U32 i = newMinY; i <= newMaxY; i++) { U32 checkY = i % csmNumBins; SceneObjectRef* chain = mBinArray[(checkY * csmNumBins) + checkX].nextInBin; while (chain) { SceneObject* ptr = chain->object; if (ptr->getContainerSeqKey() != smCurrSeqKey) { ptr->setContainerSeqKey(smCurrSeqKey); if ((ptr->getType() & mask) != 0 && ptr->isCollisionEnabled() == true) { if (ptr->getWorldBox().collideLine(start, end)) { Point3F xformedStart, xformedEnd; ptr->mWorldToObj.mulP(start, &xformedStart); ptr->mWorldToObj.mulP(end, &xformedEnd); xformedStart.convolveInverse(ptr->mObjScale); xformedEnd.convolveInverse(ptr->mObjScale); if (ptr->castRay(xformedStart, xformedEnd, &ri)) { if(ri.t < currentT) { *info = ri; info->point.interpolate(start, end, info->t); currentT = ri.t; } } } } } chain = chain->nextInBin; } } subStartX = subEndX; subEndX = getMin(subEndX + csmBinSize, currEndX); } currStartX = currEndX; } } // Bump the normal into worldspace if appropriate. if(currentT != 2) { PlaneF fakePlane; fakePlane.x = info->normal.x; fakePlane.y = info->normal.y; fakePlane.z = info->normal.z; fakePlane.d = 0; PlaneF result; mTransformPlane(info->object->getTransform(), info->object->getScale(), fakePlane, &result); info->normal = result; PROFILE_END(); return true; } else { // Do nothing and exit... PROFILE_END(); return false; } } // collide with the objects projected object box bool Container::collideBox(const Point3F &start, const Point3F &end, U32 mask, RayInfo * info) { F32 currentT = 2; for (Link* itr = mStart.next; itr != &mEnd; itr = itr->next) { SceneObject* ptr = static_cast(itr); if (ptr->getType() & mask && !ptr->mCollisionCount) { Point3F xformedStart, xformedEnd; ptr->mWorldToObj.mulP(start, &xformedStart); ptr->mWorldToObj.mulP(end, &xformedEnd); xformedStart.convolveInverse(ptr->mObjScale); xformedEnd.convolveInverse(ptr->mObjScale); RayInfo ri; if(ptr->collideBox(xformedStart, xformedEnd, &ri)) { if(ri.t < currentT) { *info = ri; info->point.interpolate(start, end, info->t); currentT = ri.t; } } } } return currentT != 2; } //---------------------------------------------------------------------------- static void buildCallback(SceneObject* object,void *key) { Container::CallbackInfo* info = reinterpret_cast(key); object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); } //---------------------------------------------------------------------------- bool Container::buildPolyList(const Box3F& box, U32 mask, AbstractPolyList* polyList,FindCallback callback,void *key) { CallbackInfo info; info.boundingBox = box; info.polyList = polyList; info.key = key; // Build bounding sphere info.boundingSphere.center = (info.boundingBox.min + info.boundingBox.max) * 0.5; VectorF bv = box.max - info.boundingSphere.center; info.boundingSphere.radius = bv.len(); sPolyList = polyList; findObjects(box,mask,callback? callback: buildCallback,&info); return !polyList->isEmpty(); } //---------------------------------------------------------------------------- bool Container::buildCollisionList(const Box3F& box, const Point3F& start, const Point3F& end, const VectorF& velocity, U32 mask, CollisionList* collisionList, FindCallback callback, void * key, const Box3F* queryExpansion) { VectorF vector = end - start; if (mFabs(vector.x) + mFabs(vector.y) + mFabs(vector.z) == 0) return false; CallbackInfo info; info.key = key; // Polylist bounding box & sphere info.boundingBox.min = info.boundingBox.max = start; info.boundingBox.min.setMin(end); info.boundingBox.max.setMax(end); info.boundingBox.min += box.min; info.boundingBox.max += box.max; info.boundingSphere.center = (info.boundingBox.min + info.boundingBox.max) * 0.5; VectorF bv = info.boundingBox.max - info.boundingSphere.center; info.boundingSphere.radius = bv.len(); // Box polyhedron edges & planes normals are always the same, // just need to fill in the vertices and plane.d Point3F* point = &sBoxPolyhedron.pointList[0]; point[0].x = point[1].x = point[4].x = point[5].x = box.min.x + start.x; point[0].y = point[3].y = point[4].y = point[7].y = box.min.y + start.y; point[2].x = point[3].x = point[6].x = point[7].x = box.max.x + start.x; point[1].y = point[2].y = point[5].y = point[6].y = box.max.y + start.y; point[0].z = point[1].z = point[2].z = point[3].z = box.min.z + start.z; point[4].z = point[5].z = point[6].z = point[7].z = box.max.z + start.z; PlaneF* plane = &sBoxPolyhedron.planeList[0]; plane[0].d = point[0].x; plane[3].d = point[0].y; plane[4].d = point[0].z; plane[1].d = -point[6].y; plane[2].d = -point[6].x; plane[5].d = -point[6].z; // Extruded sExtrudedPolyList.extrude(sBoxPolyhedron,vector); sExtrudedPolyList.setVelocity(velocity); sExtrudedPolyList.setCollisionList(collisionList); if (velocity.isZero()) { sExtrudedPolyList.clearInterestNormal(); } else { Point3F normVec = velocity; normVec.normalize(); sExtrudedPolyList.setInterestNormal(normVec); } info.polyList = &sExtrudedPolyList; // Optional expansion of the query box Box3F queryBox = info.boundingBox; if (queryExpansion) { queryBox.min += queryExpansion->min; queryBox.max += queryExpansion->max; } // Query main database findObjects(queryBox,mask,callback? callback: buildCallback,&info); sExtrudedPolyList.adjustCollisionTime(); return collisionList->count != 0; } //---------------------------------------------------------------------------- bool Container::buildCollisionList(const Polyhedron& polyhedron, const Point3F& start, const Point3F& end, const VectorF& velocity, U32 mask, CollisionList* collisionList, FindCallback callback, void *key) { VectorF vector = end - start; if (mFabs(vector.x) + mFabs(vector.y) + mFabs(vector.z) == 0) return false; CallbackInfo info; info.key = key; // Polylist bounding box & sphere Point3F min(1e10, 1e10, 1e10); Point3F max(-1e10, -1e10, -1e10); for (U32 i = 0; i < polyhedron.pointList.size(); i++) { min.setMin(polyhedron.pointList[i]); max.setMax(polyhedron.pointList[i]); } info.boundingBox.min = info.boundingBox.max = Point3F(0, 0, 0); info.boundingBox.min.setMin(vector); info.boundingBox.max.setMax(vector); info.boundingBox.min += min; info.boundingBox.max += max; info.boundingSphere.center = (info.boundingBox.min + info.boundingBox.max) * 0.5; VectorF bv = info.boundingBox.max - info.boundingSphere.center; info.boundingSphere.radius = bv.len(); // Extruded sExtrudedPolyList.extrude(polyhedron, vector); sExtrudedPolyList.setVelocity(velocity); if (velocity.isZero()) { sExtrudedPolyList.clearInterestNormal(); } else { Point3F normVec = velocity; normVec.normalize(); sExtrudedPolyList.setInterestNormal(normVec); } sExtrudedPolyList.setCollisionList(collisionList); info.polyList = &sExtrudedPolyList; Box3F queryBox = info.boundingBox; // Query main database findObjects(queryBox, mask, callback ? callback : buildCallback, &info); sExtrudedPolyList.adjustCollisionTime(); return collisionList->count != 0; } void Container::cleanupSearchVectors() { for (U32 i = 0; i < mSearchList.size(); i++) delete mSearchList[i]; mSearchList.clear(); mCurrSearchPos = -1; } static Point3F sgSortReferencePoint; int QSORT_CALLBACK cmpSearchPointers(const void* inP1, const void* inP2) { SimObjectPtr** p1 = (SimObjectPtr**)inP1; SimObjectPtr** p2 = (SimObjectPtr**)inP2; Point3F temp; F32 d1, d2; if (bool(**p1)) { (**p1)->getWorldBox().getCenter(&temp); d1 = (temp - sgSortReferencePoint).len(); } else { d1 = 0; } if (bool(**p2)) { (**p2)->getWorldBox().getCenter(&temp); d2 = (temp - sgSortReferencePoint).len(); } else { d2 = 0; } if (d1 > d2) return 1; else if (d1 < d2) return -1; else return 0; } void Container::initRadiusSearch(const Point3F& searchPoint, const F32 searchRadius, const U32 searchMask) { AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); cleanupSearchVectors(); mSearchReferencePoint = searchPoint; Box3F queryBox(searchPoint, searchPoint); queryBox.min -= Point3F(searchRadius, searchRadius, searchRadius); queryBox.max += Point3F(searchRadius, searchRadius, searchRadius); SimpleQueryList queryList; findObjects(queryBox, searchMask, SimpleQueryList::insertionCallback, &queryList); F32 radiusSquared = searchRadius * searchRadius; const F32* pPoint = &searchPoint.x; for (U32 i = 0; i < queryList.mList.size(); i++) { const F32* bMins; const F32* bMaxs; bMins = &queryList.mList[i]->getWorldBox().min.x; bMaxs = &queryList.mList[i]->getWorldBox().max.x; F32 sum = 0; for (U32 j = 0; j < 3; j++) { if (pPoint[j] < bMins[j]) sum += (pPoint[j] - bMins[j])*(pPoint[j] - bMins[j]); else if (pPoint[j] > bMaxs[j]) sum += (pPoint[j] - bMaxs[j])*(pPoint[j] - bMaxs[j]); } if (sum < radiusSquared || queryList.mList[i]->isGlobalBounds()) { mSearchList.push_back(new SimObjectPtr); *(mSearchList.last()) = queryList.mList[i]; } } if (mSearchList.size() != 0) { sgSortReferencePoint = mSearchReferencePoint; dQsort(mSearchList.address(), mSearchList.size(), sizeof(SimObjectPtr*), cmpSearchPointers); } } U32 Container::containerSearchNext() { AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); if (mCurrSearchPos >= mSearchList.size()) return 0; mCurrSearchPos++; while (mCurrSearchPos < mSearchList.size() && bool(*mSearchList[mCurrSearchPos]) == false) mCurrSearchPos++; if (mCurrSearchPos == mSearchList.size()) return 0; return (*mSearchList[mCurrSearchPos])->getId(); } F32 Container::containerSearchCurrDist() { AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); AssertFatal(mCurrSearchPos != -1, "Error, must call containerSearchNext before containerSearchCurrDist"); if (mCurrSearchPos == -1 || mCurrSearchPos >= mSearchList.size() || bool(*mSearchList[mCurrSearchPos]) == false) return 0.0; Point3F pos; (*mSearchList[mCurrSearchPos])->getWorldBox().getCenter(&pos); return (pos - mSearchReferencePoint).len(); } F32 Container::containerSearchCurrRadiusDist() { AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); AssertFatal(mCurrSearchPos != -1, "Error, must call containerSearchNext before containerSearchCurrDist"); if (mCurrSearchPos == -1 || mCurrSearchPos >= mSearchList.size() || bool(*mSearchList[mCurrSearchPos]) == false) return 0.0; Point3F pos; (*mSearchList[mCurrSearchPos])->getWorldBox().getCenter(&pos); F32 dist = (pos - mSearchReferencePoint).len(); F32 min = (*mSearchList[mCurrSearchPos])->getWorldBox().len_x(); if ((*mSearchList[mCurrSearchPos])->getWorldBox().len_y() < min) min = (*mSearchList[mCurrSearchPos])->getWorldBox().len_y(); if ((*mSearchList[mCurrSearchPos])->getWorldBox().len_z() < min) min = (*mSearchList[mCurrSearchPos])->getWorldBox().len_z(); dist -= min; if (dist < 0) dist = 0; return dist; } //---------------------------------------------------------------------------- void SimpleQueryList::insertionCallback(SceneObject* obj, void *key) { SimpleQueryList* pList = (SimpleQueryList*)key; pList->insertObject(obj); } Point3F SceneObject::getPosition() const { Point3F pos; mObjToWorld.getColumn(3, &pos); return pos; } Point3F SceneObject::getRenderPosition() const { Point3F pos; mRenderObjToWorld.getColumn(3, &pos); return pos; } void SceneObject::setPosition(const Point3F &pos) { MatrixF xform = mObjToWorld; xform.setColumn(3, pos); setTransform(xform); } //-------------------------------------------------------------------------- SceneObject::LightingInfo::LightingInfo() { mUseInfo = false; mDirty = false; mHasLastColor = false; // set the colors to half white for invalid surfaces and all... mDefaultColor.set(0.5f, 0.5f, 0.5f); //mAlarmColor.set(0.5f, 0.5f, 0.5f); mLastColor.set(0.5f, 0.5f, 0.5f); mLastTime = 0; } //-------------------------------------------------------------------------- // Lighting update: not used directly by sceneobject... // - if an interior, which contains this object, moves then this value will be incorrect bool SceneObject::getLightingAmbientColor(ColorF * col) { AssertFatal(col!=NULL, "SceneObject::getLightingAmbientColor: invalid color ptr"); const F32 cRayLength = 100.f; // down/up const F32 cTerrainRayLength = 10.f; // height above terrain for no ambient const F32 cColorStep = 0.2f; // amount to add per 100ms PROFILE_START(GetLightingAmbientColor); // query a new value? if(mLightingInfo.mDirty) { mLightingInfo.mDirty = false; Point3F pos; getRenderWorldBox().getCenter(&pos); // check if shadowed: disableCollision(); mLightingInfo.mUseInfo = false; //mLightingInfo.mInterior = 0; // Ambient light is determined by the surface we are standing on. RayInfo collision; if (gClientContainer.castRay(pos, Point3F(pos.x, pos.y, pos.z - cRayLength), InteriorObjectType | TerrainObjectType, &collision)) { // Standing on an interior object if (InteriorInstance * instance = dynamic_cast(collision.object)) { if (collision.face != -1) { // determine the color of this point... Interior * interior = instance->getDetailLevel(0); AssertFatal(interior!=NULL, "SceneObject::getLightingAmbientColor: invalid interior"); AssertFatal(collision.face < interior->getSurfaceCount(), "SceneObject::getLightingAmbientColor: invalid surface"); const Interior::Surface & surface = interior->getSurface(collision.face); // get the render order U32 currIndex = 0; U32 last = 2; static U32 renderIndices[512]; while(last < surface.windingCount) { // first renderIndices[currIndex++] = last - 2; renderIndices[currIndex++] = last - 1; renderIndices[currIndex++] = last - 0; last++; if(last == surface.windingCount) break; // second renderIndices[currIndex++] = last - 1; renderIndices[currIndex++] = last - 2; renderIndices[currIndex++] = last - 0; last++; } // get the winding indices, the src indices and the points static Point3F points[512]; static U32 srcIndices[512]; for(U32 i = 0; i < currIndex; i++) { srcIndices[i] = surface.windingStart + renderIndices[i]; renderIndices[i] = interior->getWinding(srcIndices[i]); points[i] = interior->getPoint(renderIndices[i]); } // project the collision point into object space instance->getWorldTransform().mulP(collision.point); collision.point.convolveInverse(instance->getScale()); PlaneF plane = interior->getFlippedPlane(surface.planeIndex); // walk the tri's to find the one under collision point S32 insideOffset = -1; for(U32 offset = 0; offset < currIndex; offset += 3) { bool inside = true; for(U32 j = 0; (j < 3) && inside; j++) { U32 k = (j+1) % 3; VectorF vec1 = points[offset + k] - points[offset + j]; VectorF vec2 = collision.point - points[offset + j]; Point3F cross; mCross(vec2, vec1, &cross); if(mDot(plane, cross) < 0.f) inside = false; } if(inside) { insideOffset = offset; break; } } if(insideOffset != -1) { // box map the surface: F32 max = 0.f; S32 index = -1; F32 * pNormal = (F32*)plane; // n, s, t: static U32 BoxIndices[][3] = { { 0, 1, 2 }, { 1, 0, 2 }, { 2, 0, 1 }, }; for(U32 i = 0; i < 3; i++) { if(mFabs(pNormal[i]) >= mFabs(max)) { max = pNormal[i]; index = i; } } AssertFatal(index >= 0, "Failed to get best normal"); ColorF color(0.f, 0.f, 0.f, 1.f); // grab the points Point3F pnts[3]; for(U32 j = 0; j < 3; j++) pnts[j] = points[insideOffset + j]; // project the collision point F32 * pColPnt = const_cast((const F32*)collision.point); pColPnt[BoxIndices[index][0]] = 0.f; // calculate both colors (default/alarm): for(U32 i = 0; i < 1; i++) { Vector * vertexColors = 0; ColorF * setColor = 0; // default/alarm pass: if(i == 0) { vertexColors = instance->getVertexColorsNormal(0); setColor = &mLightingInfo.mDefaultColor; } /*else if(interior->hasAlarmState()) { // only need the interior handle if it has an alarm state //mLightingInfo.mInterior = instance; vertexColors = instance->getVertexColorsAlarm(0); setColor = &mLightingInfo.mAlarmColor; } else { mLightingInfo.mAlarmColor = mLightingInfo.mDefaultColor; break; }*/ setColor->set(0.f, 0.f, 0.f); // grab the source colors ColorF srcColors[3]; if(!vertexColors->size()) { for(U32 j = 0; j < 3; j++) srcColors[j].set(1,1,1); } else { for(U32 j = 0; j < 3; j++) srcColors[j] = (*vertexColors)[srcIndices[insideOffset + j]]; } // handle each component F32 * pDestColor = const_cast(((const F32*)*setColor)); for(U32 j = 0; j < 3; j++) { // project onto a color plane for(U32 k = 0; k < 3; k++) { F32 * pPnt = const_cast((const F32*)pnts[k]); F32 * pSrcCol = const_cast((const F32*)srcColors[k]); pPnt[BoxIndices[index][0]] = pSrcCol[j]; } // create a plane from the projected points PlaneF colPlane(pnts[0], pnts[1], pnts[2]); // determine the color value... (distance to plane is 0) F32 * pColPlane = const_cast((const F32 *)colPlane); U32 * pBoxIndices = BoxIndices[index]; F32 val = (pColPlane[pBoxIndices[1]] * pColPnt[pBoxIndices[1]] + pColPlane[pBoxIndices[2]] * pColPnt[pBoxIndices[2]] + colPlane.d) / (-pColPlane[pBoxIndices[0]]); pDestColor[j] = mClampF(val, 0.f, 1.f); } } } // use info if collided //mLightingInfo.mLightSource = LightingInfo::Interior; mLightingInfo.mUseInfo = true; } } else { // Must be a terrain object TerrainBlock * terrain = dynamic_cast(collision.object); AssertFatal(terrain != 0,"Internal error: incorrect object type in ambient lighting"); // get the position of the point on the terrain const MatrixF & mat = terrain->getTransform(); Point3F origin; mat.getColumn(3, &origin); F32 lmapSize = F32(terrain->getSquareSize()) * (F32(TerrainBlock::BlockSize) / F32(TerrainBlock::LightmapSize)); U32 lmapMask = TerrainBlock::LightmapSize - 1; // grab the lightmap colors... const F32 scale = 1.0f / 31.0f; GBitmap *lightmap = terrain->lightMap; AssertFatal(lightmap!=NULL, "SceneObject::getAmbientLightingColor: no lightmap on terrain"); AssertFatal(lightmap->getFormat() == GBitmap::RGB5551, "SceneObject::getAmbientLightingColor: terrain lightmap is not 5551"); Vector posinfolist; posinfolist.increment(4); for(U32 l=0; l<4; l++) { posinfo &info = posinfolist[l]; Point2F objspace = Point2F((collision.point.x - origin.x), (collision.point.y - origin.y)); if(l & 0x1) objspace.x += lmapSize; if(l & 0x2) objspace.y += lmapSize; info.lexelspace.x = (S32)mFloor((objspace.x) / lmapSize); info.lexelspace.y = (S32)mFloor((objspace.y) / lmapSize); info.lexelspace.x &= lmapMask; info.lexelspace.y &= lmapMask; ColorI templexel; AssertFatal((lightmap->getWidth() > info.lexelspace.x) && (lightmap->getHeight() > info.lexelspace.y), "SceneObject::getAmbientLightingColor: invalid lightmap coords"); if(!lightmap->getColorBGRA(info.lexelspace.x, info.lexelspace.y, templexel)) AssertFatal((false), "Error retrieving terrain lighting data."); // convert the color... info.lexel.red = F32(templexel.red) * scale; info.lexel.green = F32(templexel.green) * scale; info.lexel.blue = F32(templexel.blue) * scale; info.lexel.clamp(); } posinfo &postl = posinfolist[0]; posinfo &postr = posinfolist[1]; posinfo &posbl = posinfolist[2]; posinfo &posbr = posinfolist[3]; Point2F pos; pos.x = (collision.point.x - origin.x) / lmapSize; pos.y = (collision.point.y - origin.y) / lmapSize; pos.x = pos.x - mFloor(pos.x); pos.y = pos.y - mFloor(pos.y); ColorF cola, colb, colc; cola.interpolate(postl.lexel, postr.lexel, pos.x); colb.interpolate(posbl.lexel, posbr.lexel, pos.x); colc.interpolate(cola, colb, pos.y); colc.clamp(); mLightingInfo.mDefaultColor = colc; mLightingInfo.mUseInfo = true; } } enableCollision(); } PROFILE_END(); // has a value? if(mLightingInfo.mUseInfo) { // currently in an interior which has an alarm state? ColorF color; /*if(bool(mLightingInfo.mInterior)) { InteriorInstance * interior = static_cast(static_cast(mLightingInfo.mInterior)); if(interior->inAlarmState()) color = mLightingInfo.mAlarmColor; else color = mLightingInfo.mDefaultColor; } else*/ color = mLightingInfo.mDefaultColor; if(!receiveLMLighting) color = ColorF(0.0f, 0.0f, 0.0f); S32 time = Platform::getVirtualMilliseconds(); // has a previous color? if(mLightingInfo.mHasLastColor) { // do each componant F32 * pColor = const_cast((const F32 *)color); F32 * pLastColor = const_cast((const F32 *)mLightingInfo.mLastColor); // cColorStep is amount added per 100ms F32 step = (F32(time - mLightingInfo.mLastTime) / 100.f) * cColorStep; for(U32 i = 0; i < 3; i++) { if(pColor[i] > pLastColor[i]) pColor[i] = mClampF(pLastColor[i] + step, 0.f, pColor[i]); else if(pColor[i] < pLastColor[i]) pColor[i] = mClampF(pLastColor[i] - step, pColor[i], 1.f); } } mLightingInfo.mHasLastColor = true; mLightingInfo.mLastColor = color; mLightingInfo.mLastTime = time; *col = color; return(true); } else { mLightingInfo.mHasLastColor = false; *col = mLightingInfo.mLastColor; return(true); } return(false); } Point3F SceneObject::getVelocity() const { return Point3F(0, 0, 0); } void SceneObject::setVelocity(const Point3F &) { // derived objects should track velocity if they want... } void SceneObject::findLights(const char *name, NetConnection *con) { SimObject *object = Sim::findObject(name); if(!object) return; SimGroup *group = dynamic_cast(object); if(!group) { sgLightObject *light = dynamic_cast(object); if(!light) return; // its a light object... S32 id = con->getGhostIndex(light); if(id > -1) lightIds.push_back(id); } else { // its a group object so get the contained light objects... for(SimObject **obj = group->begin(); obj != group->end(); obj++) { sgLightObject *light = dynamic_cast(*obj); if(!light) continue; S32 id = con->getGhostIndex(light); if(id > -1) lightIds.push_back(id); } } } /// converts lightGroupName into a list of sgUniversalStaticLight objects. void SceneObject::findLightGroup(NetConnection *con) { AssertFatal((isServerObject()), "Client object called 'findLightGroup'."); lightIds.clear(); if(lightGroupName && (dStrlen(lightGroupName) > 0)) { char *lightname = new char[dStrlen(lightGroupName) + 1]; dStrcpy(lightname, lightGroupName); char *currentname = lightname; char *delimiter = NULL; while((delimiter = dStrchr(currentname, ';')) != NULL) { delimiter[0] = 0; findLights(currentname, con); currentname = &delimiter[1]; } findLights(currentname, con); delete[] lightname; } }