diff options
| author | git perforce import user <a@b> | 2016-10-25 12:29:14 -0600 |
|---|---|---|
| committer | Sheikh Dawood Abdul Ajees <Sheikh Dawood Abdul Ajees> | 2016-10-25 18:56:37 -0500 |
| commit | 3dfe2108cfab31ba3ee5527e217d0d8e99a51162 (patch) | |
| tree | fa6485c169e50d7415a651bf838f5bcd0fd3bfbd /APEX_1.4/module/emitter/src/GroundEmitterActorImpl.cpp | |
| download | physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.tar.xz physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.zip | |
Initial commit:
PhysX 3.4.0 Update @ 21294896
APEX 1.4.0 Update @ 21275617
[CL 21300167]
Diffstat (limited to 'APEX_1.4/module/emitter/src/GroundEmitterActorImpl.cpp')
| -rw-r--r-- | APEX_1.4/module/emitter/src/GroundEmitterActorImpl.cpp | 1166 |
1 files changed, 1166 insertions, 0 deletions
diff --git a/APEX_1.4/module/emitter/src/GroundEmitterActorImpl.cpp b/APEX_1.4/module/emitter/src/GroundEmitterActorImpl.cpp new file mode 100644 index 00000000..1bd098d6 --- /dev/null +++ b/APEX_1.4/module/emitter/src/GroundEmitterActorImpl.cpp @@ -0,0 +1,1166 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + + +#include "Apex.h" +#include "GroundEmitterAsset.h" +#include "SceneIntl.h" +#include "RenderDebugInterface.h" +#include "InstancedObjectSimulationIntl.h" +#include "GroundEmitterAssetImpl.h" +#include "GroundEmitterActorImpl.h" +#include "EmitterScene.h" +#include "ApexUsingNamespace.h" +#include "ApexRand.h" + +#include "ScopedPhysXLock.h" +#include "PxRigidDynamic.h" + +namespace nvidia +{ +namespace emitter +{ + +using namespace physx; + +void InjectorData::InjectTask::run() +{ + mActor->injectParticles(*mData); +} + +// this function will 'a'+'b' and wrap it around the max value back to 0 if necessary +// not safe when 'a' + 'b' > 2*max, or something like that +static inline int32_t INCREMENT_CELL(int32_t& a, int32_t b, int32_t max) +{ + if (a + b > max) + { + return ((a + b) - max - 1); + } + else + { + return a + b; + } +} + +#pragma warning(disable: 4355) + +GroundEmitterActorImpl::GroundEmitterActorImpl(const GroundEmitterActorDesc& desc, GroundEmitterAssetImpl& asset, ResourceList& list, EmitterScene& scene) + : mAsset(&asset) + , mScene(&scene) + , mLocalUpDirection(PxVec3(0.0f, 1.0f, 0.0f)) + , mGridCellSize(0.0f) + , mTotalElapsedTimeMs(0) + , mShouldUseGroupsMask(false) + , mTickTask(*this) +{ + mRand.setSeed(scene.mApexScene->getSeed()); + + GroundEmitterActorDesc defaults; + + /* Read default values from descriptor or authored asset */ + if (desc.raycastCollisionGroups == defaults.raycastCollisionGroups) + { + setRaycastCollisionGroups(mAsset->mRaycastCollisionGroups); + } + else + { + setRaycastCollisionGroups(desc.raycastCollisionGroups); + } + if (desc.radius == defaults.radius) + { + setRadius(mAsset->getRadius()); + } + else + { + setRadius(desc.radius); + } + if (desc.maxRaycastsPerFrame == 0) + { + setMaxRaycastsPerFrame(mAsset->getMaxRaycastsPerFrame()); + } + else + { + setMaxRaycastsPerFrame(desc.maxRaycastsPerFrame); + } + if (desc.raycastHeight == 0.0f) + { + setRaycastHeight(mAsset->getRaycastHeight()); + } + else + { + setRaycastHeight(desc.raycastHeight); + } + if (desc.density == 0.0f) + { + mDensity = mAsset->getDensity(); + } + else + { + mDensity = desc.density; + } + if (desc.attachActor) + { + setAttachActor(desc.attachActor); + setAttachRelativePosition(desc.attachRelativePosition); + } + else + { + mAttachActor = NULL; + } + if (desc.spawnHeight >= 0.0f) + { + setSpawnHeight(desc.spawnHeight); + } + else + { + setSpawnHeight(mAsset->getSpawnHeight()); + } + + mOldLocalPlayerPosition = PxVec3(0.0f); + mPose = PxTransform(PxIdentity); + setRotation(desc.rotation); + setPosition(desc.initialPosition); + + mMaterialCallback = desc.materialCallback; + + mRaycastCollisionGroupsMask = PxFilterData(0, 0, 0, 0); + + mRefreshFullCircle = true; + mSimulationSteps = 0; + + for (uint32_t i = 0 ; i < mAsset->mMaterialFactoryMaps->size() ; i++) + { + MaterialFactoryMappingDesc factoryDesc; + factoryDesc.instancedObjectEffectsAssetName = (*mAsset->mMaterialFactoryMaps)[i].iofxAssetName->name(); + factoryDesc.instancedObjectSimulationTypeName = (*mAsset->mMaterialFactoryMaps)[i].iosAssetName->className(); + factoryDesc.instancedObjectSimulationAssetName = (*mAsset->mMaterialFactoryMaps)[i].iosAssetName->name(); + factoryDesc.physicalMaterialName = (*mAsset->mMaterialFactoryMaps)[i].physMatName; + factoryDesc.weight = (*mAsset->mMaterialFactoryMaps)[i].weight; + factoryDesc.maxSlopeAngle = (*mAsset->mMaterialFactoryMaps)[i].maxSlopeAngle; + EmitterLodParamDesc lodParamDesc; + GroundEmitterAssetImpl::copyLodDesc(lodParamDesc, (*mAsset->mMaterialFactoryMaps)[i].lodParamDesc); + if (!addMeshForGroundMaterial(factoryDesc, lodParamDesc)) + { + return; + } + } + + list.add(*this); // Add self to asset's list of actors + addSelfToContext(*scene.mApexScene->getApexContext()); // Add self to ApexScene + addSelfToContext(scene); // Add self to EmitterScene's list of actors + + mValid = true; +} + +GroundEmitterActorImpl::~GroundEmitterActorImpl() +{ +} + +void GroundEmitterActorImpl::submitTasks() +{ + float dt; + bool stepPhysX = mScene->mApexScene->physXElapsedTime(dt); + + PxTaskManager* tm = mScene->mApexScene->getTaskManager(); + + for (uint32_t i = 0; i < mInjectorList.size(); i++) + { + tm->submitUnnamedTask(mInjectorList[i]->mTask); + } + + if (stepPhysX) + { + tm->submitUnnamedTask(mTickTask); + } +} + +void GroundEmitterActorImpl::setTaskDependencies() +{ + float dt; + bool stepPhysX = mScene->mApexScene->physXElapsedTime(dt); + + PxTaskManager* tm = mScene->mApexScene->getTaskManager(); + + for (uint32_t i = 0; i < mInjectorList.size(); i++) + { + PxTask* injectTask = &mInjectorList[i]->mTask; + + injectTask->finishBefore(mInjectorList[i]->mInjector->getCompletionTaskID()); + + if (stepPhysX) + { + mTickTask.startAfter(injectTask->getTaskID()); + } + } + + if (stepPhysX) + { + mTickTask.finishBefore(tm->getNamedTask(AST_PHYSX_SIMULATE)); + } +} + + +Asset* GroundEmitterActorImpl::getOwner() const +{ + READ_ZONE(); + return (Asset*) mAsset; +} +GroundEmitterAsset* GroundEmitterActorImpl::getEmitterAsset() const +{ + READ_ZONE(); + return mAsset; +} + +void GroundEmitterActorImpl::setRaycastCollisionGroupsMask(physx::PxFilterData* m) +{ + WRITE_ZONE(); + if (!m) + { + mShouldUseGroupsMask = false; + } + else + { + mRaycastCollisionGroupsMask = *m; + mShouldUseGroupsMask = true; + } +} + + +void GroundEmitterActorImpl::setPreferredRenderVolume(nvidia::apex::RenderVolume* vol) +{ + WRITE_ZONE(); + for (uint32_t i = 0 ; i < mInjectorList.size() ; i++) + { + InjectorData* data = mInjectorList[i]; + if (data->mInjector) + { + data->mInjector->setPreferredRenderVolume(vol); + } + } +} + +void GroundEmitterActorImpl::setPhysXScene(physx::PxScene* s) +{ + mPxScene = s; +} + +void GroundEmitterActorImpl::getLodRange(float& min, float& max, bool& intOnly) const +{ + PX_UNUSED(min); + PX_UNUSED(max); + PX_UNUSED(intOnly); + APEX_INVALID_OPERATION("not implemented"); +} + + +float GroundEmitterActorImpl::getActiveLod() const +{ + APEX_INVALID_OPERATION("GroundEmitterActor does not support this operation"); + return -1.0f; +} + + +void GroundEmitterActorImpl::forceLod(float lod) +{ + PX_UNUSED(lod); + APEX_INVALID_OPERATION("not implemented"); +} + + +void GroundEmitterActorImpl::release() +{ + if (mInRelease) + { + return; + } + mInRelease = true; + mAsset->releaseActor(*this); +} + + +void GroundEmitterActorImpl::destroy() +{ + /* Order is important here, pay attention */ + + // Remove ourselves from all contexts, so they don't get stuck trying to release us + ApexActor::destroy(); + + for (uint32_t i = 0; i < mInjectorList.size(); i++) + { + // Release our actor first + if (mInjectorList[i]->mInjector) + { + mInjectorList[i]->mInjector->release(); + } + + delete mInjectorList[i]; + } + mInjectorList.clear(); + + delete this; +} + + +void GroundEmitterActorImpl::removeActorAtIndex(uint32_t index) +{ + // One of our injectors has been released + for (uint32_t i = 0; i < mInjectorList.size(); i++) + { + if (mInjectorList[i]->mInjector == mActorArray[ index ]) + { + mInjectorList[i]->mInjector = NULL; + } + } + + ApexContext::removeActorAtIndex(index); + release(); +} + +const PxMat44 GroundEmitterActorImpl::getPose() const +{ + READ_ZONE(); + PxMat44 mat44 = PxMat44(mPose); + mat44.setPosition(mWorldPlayerPosition); + return mat44; +} + +void GroundEmitterActorImpl::setPose(const PxMat44& pos) +{ + WRITE_ZONE(); + PxMat44 rotation = pos; + rotation.setPosition(PxVec3(0.0f)); + + mWorldPlayerPosition = pos.getPosition(); + + setRotation(PxTransform(rotation)); +} + + +void GroundEmitterActorImpl::setRotation(const PxTransform& rotation) +{ + PX_ASSERT(mPose.p == PxVec3(0.0f)); + mPose.q = rotation.q; + mInversePose = mPose.getInverse(); + setPosition(mWorldPlayerPosition); +} + +void GroundEmitterActorImpl::setPosition(const PxVec3& worldPlayerPosition) +{ + // put the world player position in the ground emitter's space + mLocalPlayerPosition = mInversePose.transform(worldPlayerPosition); + + mHorizonPlane = PxPlane(mLocalPlayerPosition, mLocalUpDirection); + PxVec3 dir = mLocalPlayerPosition - mHorizonPlane.project(mLocalPlayerPosition); + mStepsize = dir.normalize(); + + mWorldPlayerPosition = worldPlayerPosition; + + // keep track of when we move 1/2 a grid cell in distance + float dist = (mOldLocalPlayerPosition - mLocalPlayerPosition).magnitudeSquared(); + if (dist > (mGridCellSize * mGridCellSize * 0.25)) + { + mOldLocalPlayerPosition = mLocalPlayerPosition; + } +} + + +void GroundEmitterActorImpl::setRadius(float r) +{ + WRITE_ZONE(); + mRadius = r; + mRadius2 = mRadius * mRadius; + mMaxStepSize = 2 * mRadius; + mCircleArea = PxPi * mRadius2; +} + +#ifdef WITHOUT_DEBUG_VISUALIZE +void GroundEmitterActorImpl::visualize(RenderDebugInterface&) +{ +} +#else +void GroundEmitterActorImpl::visualize(RenderDebugInterface& renderDebug) +{ + if (!mScene->mEmitterDebugRenderParams->groundEmitterParameters.VISUALIZE_GROUND_EMITTER_ACTOR) + { + return; + } + if ( !mEnableDebugVisualization ) return; + + using RENDER_DEBUG::DebugColors; + + RENDER_DEBUG_IFACE(&renderDebug)->pushRenderState(); + + RENDER_DEBUG_IFACE(&renderDebug)->setCurrentColor(RENDER_DEBUG_IFACE(&renderDebug)->getDebugColor(DebugColors::Green), RENDER_DEBUG_IFACE(&renderDebug)->getDebugColor(DebugColors::Yellow)); + + if (mScene->mEmitterDebugRenderParams->groundEmitterParameters.VISUALIZE_GROUND_EMITTER_SPHERE) + { + RENDER_DEBUG_IFACE(&renderDebug)->debugSphere(mWorldPlayerPosition, mRadius); + } + + + // Determine bounds of grid cells around the player + PxVec3 pos = mHorizonPlane.project(mLocalPlayerPosition); + PxVec3 max = mHorizonPlane.project(mLocalPlayerPosition + PxVec3(mRadius)); + PxVec3 min = mHorizonPlane.project(mLocalPlayerPosition - PxVec3(mRadius)); + + if (mGridCellSize == 0.0f) + { + RENDER_DEBUG_IFACE(&renderDebug)->popRenderState(); + return; + } + + if (mScene->mEmitterDebugRenderParams->groundEmitterParameters.VISUALIZE_GROUND_EMITTER_RAYCAST) + { + + if (mVisualizeRaycastsList.size() > 0) + { + PxVec3 rayOffset = mRaycastHeight * mLocalUpDirection; + + //NOTE: this array should be processed whenever it has stuff in it, even if vis has been disabled, otherwise it may never get cleared! + for (physx::Array<RaycastVisInfo>::Iterator i = mVisualizeRaycastsList.begin(); i != mVisualizeRaycastsList.end(); i++) + { + //was it deleted less than a second ago? If yes, draw it, otherwise remove it from the array. + uint32_t timeAdded = (*i).timeSubmittedMs; + if (timeAdded + 1000 < mTotalElapsedTimeMs) + { + mVisualizeRaycastsList.replaceWithLast(static_cast<uint32_t>(i - mVisualizeRaycastsList.begin())); + i--; + } + else + { + PxVec3 worldStart, worldStop, localStop; + worldStart = mPose.transform((*i).rayStart + rayOffset); + localStop = (*i).rayStart; + localStop.y = 0.0f; + worldStop = mPose.transform(localStop); + RENDER_DEBUG_IFACE(&renderDebug)->debugLine(worldStart, worldStop); + } + } + } + + // put the raycast direction on top of the grid (2 around the center) +#define GE_DEBUG_RAY_THICKNESS (0.05f) + + RENDER_DEBUG_IFACE(&renderDebug)->setCurrentArrowSize(GE_DEBUG_RAY_THICKNESS * 4); + + PxVec3 localPlayerZeroedPosition(mLocalPlayerPosition.x, 0.0f, mLocalPlayerPosition.z); + PxVec3 worldRayStart, worldRayStop; + PxVec3 localRayStart(localPlayerZeroedPosition + PxVec3(mGridCellSize, mRaycastHeight + mLocalPlayerPosition.y, mGridCellSize)); + PxVec3 localRayStop(localPlayerZeroedPosition + PxVec3(mGridCellSize, 0.0f, mGridCellSize)); + + worldRayStart = mPose.transform(localRayStart); + worldRayStop = mPose.transform(localRayStop); + RENDER_DEBUG_IFACE(&renderDebug)->debugThickRay(worldRayStart, worldRayStop, GE_DEBUG_RAY_THICKNESS); + + localRayStart = PxVec3(localPlayerZeroedPosition + PxVec3(-mGridCellSize, mRaycastHeight + mLocalPlayerPosition.y, -mGridCellSize)); + localRayStop = PxVec3(localPlayerZeroedPosition + PxVec3(-mGridCellSize, 0.0f, -mGridCellSize)); + + worldRayStart = mPose.transform(localRayStart); + worldRayStop = mPose.transform(localRayStop); + RENDER_DEBUG_IFACE(&renderDebug)->debugThickRay(worldRayStart, worldRayStop, GE_DEBUG_RAY_THICKNESS); + } + + if (mScene->mEmitterDebugRenderParams->groundEmitterParameters.VISUALIZE_GROUND_EMITTER_GRID) + { + // draw a grid on the ground plane representing the emitter grid and one at mRaycastHeight + playerHeight + // draw two grids, one at 0 + RENDER_DEBUG_IFACE(&renderDebug)->setCurrentColor(RENDER_DEBUG_IFACE(&renderDebug)->getDebugColor(DebugColors::Yellow), RENDER_DEBUG_IFACE(&renderDebug)->getDebugColor(DebugColors::Yellow)); + for (uint32_t i = 0; i < 2; i++) + { + float gridY = i * (mRaycastHeight + mLocalPlayerPosition.y); + for (float x = min.x; x <= max.x; x += mGridCellSize) + { + // draw "vertical lines" (on a piece of paper) + PxVec3 p0(x, gridY, min.z), p1(x, gridY, max.z), worldP0, worldP1; + + worldP0 = mPose.transform(p0); + worldP1 = mPose.transform(p1); + + RENDER_DEBUG_IFACE(&renderDebug)->debugLine(worldP0, worldP1); + } + for (float z = min.z; z <= max.z; z += mGridCellSize) + { + // draw "horizontal lines" (on a piece of paper) + PxVec3 p0(min.x, gridY, z), p1(max.x, gridY, z), worldP0, worldP1; + + worldP0 = mPose.transform(p0); + worldP1 = mPose.transform(p1); + + RENDER_DEBUG_IFACE(&renderDebug)->debugLine(worldP0, worldP1); + } + } + + // draw a big box around the grids + PxVec3 bmin(min.x, 0.0f, min.z), + bmax(max.x, mRaycastHeight + mLocalPlayerPosition.y, max.z), + bWorldMin, + bWorldMax; + + bWorldMin = mPose.transform(bmin); + bWorldMax = mPose.transform(bmax); + + RENDER_DEBUG_IFACE(&renderDebug)->setCurrentColor(RENDER_DEBUG_IFACE(&renderDebug)->getDebugColor(DebugColors::Yellow)); + RENDER_DEBUG_IFACE(&renderDebug)->debugBound(PxBounds3(bWorldMin, bWorldMax)); + } + + if (mScene->mEmitterDebugRenderParams->groundEmitterParameters.VISUALIZE_GROUND_EMITTER_ACTOR_NAME) + { + PxVec3 bLocalMax(max.x, mRaycastHeight, max.z); + PxVec3 bWorldMax; + bWorldMax = mPose.transform(bLocalMax); + + RENDER_DEBUG_IFACE(&renderDebug)->pushRenderState(); + RENDER_DEBUG_IFACE(&renderDebug)->setCurrentTextScale(2.0f); + + PxVec3 textLocation = bWorldMax; + RENDER_DEBUG_IFACE(&renderDebug)->debugText(textLocation, " %s %s", this->getOwner()->getObjTypeName(), this->getOwner()->getName()); + + RENDER_DEBUG_IFACE(&renderDebug)->popRenderState(); + } + + if (mScene->mEmitterDebugRenderParams->groundEmitterParameters.VISUALIZE_GROUND_EMITTER_ACTOR_POSE) + { + PxMat44 groundEmitterAxes = getRotation(); + groundEmitterAxes.setPosition(getPosition()); + RENDER_DEBUG_IFACE(&renderDebug)->debugAxes(groundEmitterAxes, 1); + } + + RENDER_DEBUG_IFACE(&renderDebug)->popRenderState(); +} +#endif + + +/** + * Add an IOFX/IOS pair to a material. + */ +bool GroundEmitterActorImpl::addMeshForGroundMaterial( + const MaterialFactoryMappingDesc& desc, + const EmitterLodParamDesc& lodDesc) +{ + ApexSDKIntl* sdk = mAsset->mModule->mSdk; + ResourceProviderIntl* nrp = sdk->getInternalResourceProvider(); + + /* Resolve the actual MaterialIndex from the provided name */ + ResID pmns = sdk->getPhysicalMaterialNameSpace(); + ResID matresid = nrp->createResource(pmns, desc.physicalMaterialName); + PxMaterialTableIndex groundMaterialIndex = (PxMaterialTableIndex)(size_t) nrp->getResource(matresid); + + uint32_t injectorIndex; + for (injectorIndex = 0; injectorIndex < mInjectorList.size(); injectorIndex++) + { + if (mInjectorList[injectorIndex]->iofxAssetName == desc.instancedObjectEffectsAssetName && + mInjectorList[injectorIndex]->iosAssetName == desc.instancedObjectSimulationAssetName) + { + break; + } + } + + if (injectorIndex >= mInjectorList.size()) + { + InjectorData* data = PX_NEW(InjectorData)(); + data->initTask(*this, *data); + + const char* iofxAssetName = desc.instancedObjectEffectsAssetName; + IofxAsset* iofxAsset = static_cast<IofxAsset*>(mAsset->mIofxAssetTracker.getAssetFromName(iofxAssetName)); + IosAsset* iosAsset = mAsset->mIosAssetTracker.getIosAssetFromName( + desc.instancedObjectSimulationTypeName, + desc.instancedObjectSimulationAssetName); + + if (!iosAsset || !iofxAsset) + { + delete data; + return false; + } + + Actor* nxActor = iosAsset->createIosActor(*mScene->mApexScene, iofxAsset); + InstancedObjectSimulationIntl* ios = NULL; + if (nxActor) + { + ApexActor* aa = GetInternalApexSDK()->getApexActor(nxActor); + if (aa) + { + ios = DYNAMIC_CAST(InstancedObjectSimulationIntl*)(aa); + } + } + if (!ios) + { + APEX_DEBUG_INFO("IOS asset retrieval failure: %s", desc.instancedObjectSimulationAssetName); + delete data; + return false; + } + + /* Keep list of unique ios pointers */ + uint32_t i; + for (i = 0 ; i < mIosList.size() ; i++) + { + if (mIosList[i] == ios) + { + break; + } + } + if (i == mIosList.size()) + { + mIosList.pushBack(ios); + } + + data->mInjector = ios->allocateInjector(iofxAsset); + if (!data->mInjector) + { + delete data; + return false; + } + + data->mInjector->addSelfToContext(*this); + data->mObjectRadius = ios->getObjectRadius(); + if (!data->mInjector) + { + APEX_DEBUG_INFO("IOS injector allocation failure"); + delete data; + return false; + } + + data->mInjector->setLODWeights(lodDesc.maxDistance, lodDesc.distanceWeight, lodDesc.speedWeight, + lodDesc.lifeWeight, lodDesc.separationWeight, lodDesc.bias); + + mInjectorList.pushBack(data); + } + + mPerMaterialData.use(groundMaterialIndex); + MaterialData& data = mPerMaterialData.direct(groundMaterialIndex); + + data.injectorIndices.pushBack(injectorIndex); + float weight = desc.weight; + if (data.accumWeights.size() > 0) + { + weight += data.accumWeights.back(); + } + data.accumWeights.pushBack(weight); + // Store the sine of the complimentary angle for comparison + float angleCompSin = PxSin((90.0f - desc.maxSlopeAngle) * PxPi / 180.0f); + data.maxSlopeAngles.pushBack(angleCompSin); + + return true; +} + +/** + * Submit up to mMaxNumRaycastsPerFrame raycasts to the PhysX engine. These will be + * processed asynchronously and we'll get the results in fetchResults(). + */ +void GroundEmitterActorImpl::submitRaycasts() +{ + PX_PROFILE_ZONE("GroundParticlesEmitterRaycasts", GetInternalApexSDK()->getContextId()); + + /* Avoid raycasts if we have material callback and spawn height */ + if (mMaterialCallback && mSpawnHeight > 0.0f && mToRaycast.size()) + { + uint32_t nbHits = mToRaycast.size(); + mMaterialRequestArray.resize(nbHits); + MaterialLookupCallback::MaterialRequest* matRequest = &mMaterialRequestArray[0]; + for (uint32_t i = 0 ; i < nbHits ; i++) + { + PxVec3 position; + if (mToRaycast.popFront(position)) + { + matRequest[i].samplePosition = mPose.transform(position); + } + } + + mMaterialCallback->requestMaterialLookups(nbHits, matRequest); + + nvidia::Mutex::ScopedLock scopeLock(mInjectorDataLock); + + for (uint32_t i = 0 ; i < nbHits ; i++) + { + uint16_t matIndex = (uint16_t) matRequest[i].outMaterialID; + + if (!mPerMaterialData.isValid(matIndex)) + { + continue; + } + if (!mPerMaterialData.isUsed(matIndex)) + { + continue; + } + + MaterialData& data = mPerMaterialData.direct(matIndex); + float maxSlopeAngle; + uint32_t particleFactoryIndex = data.chooseIOFX(maxSlopeAngle, mRand); + InjectorData& injectorData = *mInjectorList[ particleFactoryIndex ]; + + PX_ASSERT(mLocalUpDirection == PxVec3(0, 1, 0)); + + IosNewObject particle; + + // the spawn height must be added to the "up axis", so transform pos back to local, + // offset, then back to world + PxVec3 localPosition, worldPosition; + localPosition = mInversePose.transform(matRequest[i].samplePosition); + localPosition += PxVec3(0.0f, mSpawnHeight, 0.0f); + particle.initialPosition = mPose.transform(localPosition); + + particle.lodBenefit = 0; + particle.iofxActorID = IofxActorIDIntl(0); + particle.userData = 0; + + particle.initialVelocity = mRand.getScaled(getVelocityLow(), getVelocityHigh()); + particle.lifetime = mRand.getScaled(getLifetimeLow(), getLifetimeHigh()); + injectorData.particles.pushBack(particle); + } + } + else + { + physx::PxFilterData* groupsMask = (mShouldUseGroupsMask) ? &mRaycastCollisionGroupsMask : NULL; + PxVec3 rayOffset = mRaycastHeight * mLocalUpDirection; + uint32_t numRaycastsDone = 0; + + PxVec3 orig; + PxVec3 dir; + PxRaycastHit hit; + PxVec3 worldUpDirection; + worldUpDirection = mPose.transform(mLocalUpDirection); + dir = -worldUpDirection; + + PxVec3 newPos; + static const int DefaultRaycastHitNum = 256; + int raycastHitNum = DefaultRaycastHitNum; + int nbHits; + PxRaycastHit hitsStatck[DefaultRaycastHitNum]; + PxRaycastHit* hits = hitsStatck; + PxQueryFilterData filterData(PxQueryFlag::eSTATIC); + if (groupsMask) + filterData.data = *groupsMask; + while (mToRaycast.popFront(newPos)) + { + PxVec3 rotatedPosition; + rotatedPosition = mPose.transform(newPos + rayOffset); + orig = rotatedPosition; + + for(;;) + { + PxRaycastBuffer rcBuffer(hits, DefaultRaycastHitNum); + mPxScene->raycast(orig, dir, PX_MAX_F32, rcBuffer, + PxHitFlag::ePOSITION|PxHitFlag::eNORMAL|PxHitFlag::eDISTANCE|PxHitFlag::eUV, + filterData); + nbHits = (int32_t)rcBuffer.getNbAnyHits(); + if (nbHits != -1) + break; + + if (hitsStatck != hits) + { + PX_FREE(hits); + } + raycastHitNum <<= 1; // *2 + hits = (PxRaycastHit*)PX_ALLOC(sizeof(PxRaycastHit)*raycastHitNum, PX_DEBUG_EXP("GroundEmitterActor_PxRaycastHit")); + } + + if (!onRaycastQuery((uint32_t)nbHits, hits)) + break; + + if (++numRaycastsDone >= mMaxNumRaycastsPerFrame) + { + break; + } + } + if (hits != hitsStatck) + { + PX_FREE(hits); + } + } +} + + +/** + * Called during ApexScene::fetchResults() to get raycast results + * from the PhysX engine. + */ +void GroundEmitterActorImpl::fetchResults() +{ +} + + +/** + * Inject all queued particles/objects into their IOS + */ +void GroundEmitterActorImpl::injectParticles(InjectorData& data) +{ + PX_PROFILE_ZONE("GroundParticlesEmitterInjection", GetInternalApexSDK()->getContextId()); + + nvidia::Mutex::ScopedLock scopeLock(mInjectorDataLock); + + if (data.particles.size() > 0) + { + data.mInjector->createObjects(data.particles.size(), &data.particles[0]); + data.particles.clear(); + } +} + + +/** + * Iterate over all the objects spawned by this emitter actor (by iterating over its IOFX + * instances) and determine where new objects need to be spawned. We do this by defining a + * 2D grid and determining the object density in each cell. If the cell is below the appropriate + * density, and the cell is in the appropriate refresh area, we spawn new objects to bring that + * cell up to our density requirement. + */ +void GroundEmitterActorImpl::refreshCircle(bool edgeOnly) +{ + PX_PROFILE_ZONE("GroundParticlesEmitterRefreshCircle", GetInternalApexSDK()->getContextId()); + + // Voluntarily throttle myself if any injector already + // has a backlog of particles. + for (uint32_t i = 0 ; i < mInjectorList.size() ; i++) + { + if (mInjectorList[ i ]->mInjector->isBacklogged()) + { + return; + } + } + + // Simulation steps required before a grid can be re-refreshed. There's a trade-off here + // between constant refresh for areas that have a high slope and cannot support particles + // and between players out-running the refresh radius. This number must be > 2 in order to + // protect against multi-frame particle emission latencies. +#define MIN_CELL_REFRESH_STEPS 12 + + // Hack, for now, to simplify the math so we can debug functionality + // DoD and SimpleVegetation are both Y up. + PX_ASSERT(mLocalUpDirection == PxVec3(0, 1, 0)); + + mCurrentDensity = mAsset->getDensity() * mAsset->mModule->getGroundDensityScale(); + + // Calculate grid size based on mCurrentDensity and mRadius + float gridSize = mRadius / 4; + while (mCurrentDensity * gridSize * gridSize < 20) + { + gridSize *= 1.5; + } + + while (mCurrentDensity * gridSize * gridSize > 40) + { + gridSize *= 0.5; + } + + // clear the mCellLastRefreshSteps list and the grid/cell loop counters if the grid size changes + if (gridSize != mGridCellSize) + { + for (uint32_t i = 0; i < mCellLastRefreshSteps.size(); i++) + { + mCellLastRefreshSteps[i] = 0; + } + mNextGridCell.x = 0; + mNextGridCell.y = 0; + } + + // clear the mCellLastRefreshSteps list if the player moves more than 1/2 a grid cell in distance + if (mOldLocalPlayerPosition == mLocalPlayerPosition && mStepsize > 0.0f) + { + for (uint32_t i = 0; i < mCellLastRefreshSteps.size(); i++) + { + mCellLastRefreshSteps[i] = 0; + } + } + + // persist grid size + mGridCellSize = gridSize; + + // Determine bounds of grid cells around the player + float heightFudge = mSpawnHeight / 5; // this should be authorable... + PxVec3 pos = mHorizonPlane.project(mLocalPlayerPosition); + PxVec3 max = mHorizonPlane.project(mLocalPlayerPosition + PxVec3(mRadius)); + PxVec3 min = mHorizonPlane.project(mLocalPlayerPosition - PxVec3(mRadius)); + int32_t xMin, xMax, yMin, yMax; + + xMin = (int32_t) PxFloor(min.x / gridSize); + xMax = (int32_t) PxCeil(max.x / gridSize); + + yMin = (int32_t) PxFloor(min.z / gridSize); + yMax = (int32_t) PxCeil(max.z / gridSize); + + // Allocate grid of uint32_t counters based on grid size and mRadius + uint32_t gridDim = (uint32_t)PxMax(yMax - yMin, xMax - xMin); + uint32_t totalGrids = gridDim * gridDim; + uint32_t* grids = (uint32_t*) mAsset->mModule->mSdk->getTempMemory(totalGrids * sizeof(uint32_t)); + mCellLastRefreshSteps.resize(totalGrids, (uint32_t) - MIN_CELL_REFRESH_STEPS); + memset(grids, 0, totalGrids * sizeof(uint32_t)); + + // This loop should be in a CUDA kernel + for (uint32_t i = 0; i < mIosList.size(); i++) + { + uint32_t count, stride; + InstancedObjectSimulationIntl* ios = mIosList[i]; + const PxVec3* positions = ios->getRecentPositions(count, stride); + const char* ptr = reinterpret_cast<const char*>(positions); + const PxVec3* worldPositionPtr; + PxVec3 localPosition; + + for (uint32_t j = 0 ; j < count ; j++) + { + worldPositionPtr = reinterpret_cast<const PxVec3*>(ptr); + localPosition = mInversePose.transform(*worldPositionPtr); + ptr += stride; + PxVec3 proj = mHorizonPlane.project(localPosition); + int32_t cx = (int32_t) PxFloor(proj.x / gridSize) - xMin; + if (cx < 0 || cx >= (int32_t) gridDim) + { + continue; + } + + int32_t cy = (int32_t) PxFloor(proj.z / gridSize) - yMin; + if (cy < 0 || cy >= (int32_t) gridDim) + { + continue; + } + + float dist = localPosition.y - mLocalPlayerPosition.y; + if ((mSpawnHeight > 0.0f) && + ((dist < (mSpawnHeight - heightFudge)) || (dist > (mSpawnHeight + heightFudge)))) + { + continue; + } + + grids[ cy * gridDim + cx ]++; + } + } + + { + PX_PROFILE_ZONE("GroundParticlesEmitterGridInspect", GetInternalApexSDK()->getContextId()); + // Iterate over grid. For each under density threshold, generate + // new particles to bring it up to spec. + uint32_t neededOccupancy = (uint32_t) PxCeil(mCurrentDensity * gridSize * gridSize * 0.10f); + + bool stopScanningGridCells = false; + + for (int32_t x = 0 ; x < (int32_t) gridDim ; x++) + { + if (stopScanningGridCells) + { + break; + } + + int32_t cellx = INCREMENT_CELL(mNextGridCell.x, x, (int32_t)(gridDim - 1)); + + float fx = (cellx + xMin) * gridSize; + for (int32_t y = 0 ; y < (int32_t) gridDim ; y++) + { + int32_t celly = INCREMENT_CELL(mNextGridCell.y, y, (int32_t)(gridDim - 1)); + float fy = (celly + yMin) * gridSize; + + if (edgeOnly) + { + // Ignore grids that do not include the radius. This is a horseshoe calculation + // that tests whether the grid center is more than gridSize from the radius. + float cx = (fx + gridSize * 0.5f) - pos.x; + float cy = (fy + gridSize * 0.5f) - pos.z; + float distsq = cx * cx + cy * cy; + if (fabs(distsq - mRadius2) > gridSize * gridSize) + { + continue; + } + } + + uint32_t gridID = (uint32_t) celly * gridDim + cellx; + uint32_t gridOccupancy = grids[ gridID ]; + if (gridOccupancy >= neededOccupancy) + { + continue; + } + + // Do not refresh a grid more often than once every half second + if (mSimulationSteps - mCellLastRefreshSteps[ gridID ] < MIN_CELL_REFRESH_STEPS) + { + continue; + } + + // Refresh this grid + uint32_t numRaycasts = (uint32_t) PxCeil(mCurrentDensity * gridSize * gridSize) - gridOccupancy; + + // If this cell pushes us over the max raycast count, take what we can from the cell and run + // it again next frame. This does not apply to the first frame (edgeOnly) + if (!mRefreshFullCircle && ((mToRaycast.size() + numRaycasts) > getMaxRaycastsPerFrame())) + { + if (mToRaycast.size() > getMaxRaycastsPerFrame()) + { + numRaycasts = 0; + } + else + { + numRaycasts = getMaxRaycastsPerFrame() - mToRaycast.size(); + } + } + + // compute the positions of the new raycasts + bool visualizeRaycasts = mScene->mEmitterDebugRenderParams->groundEmitterParameters.VISUALIZE_GROUND_EMITTER_RAYCAST && + mScene->mDebugRenderParams->Enable; + // safety valve, in case no one is actually rendering + if (mVisualizeRaycastsList.size() > 16 * 1024) + { + visualizeRaycasts = false; + } + + for (uint32_t j = 0; j < numRaycasts; j++) + { + float tmpx = mRand.getScaled(0, gridSize); + float tmpy = mRand.getScaled(0, gridSize); + mToRaycast.pushBack(PxVec3(fx + tmpx, mLocalPlayerPosition.y, fy + tmpy)); + + if (visualizeRaycasts) + { + RaycastVisInfo& raycastInfo = mVisualizeRaycastsList.insert(); + raycastInfo.rayStart = PxVec3(fx + tmpx, mLocalPlayerPosition.y, fy + tmpy); + raycastInfo.timeSubmittedMs = mTotalElapsedTimeMs; + } + } + + // break out if the raycast buffer will grow beyond the max raycasts per frame + if (!mRefreshFullCircle && (mToRaycast.size() >= getMaxRaycastsPerFrame())) + { + // save the next cell in the grid to continue scanning + if (cellx == (int32_t)gridDim - 1 && celly == (int32_t)gridDim - 1) + { + mNextGridCell.x = mNextGridCell.y = 0; + } + else if (cellx == (int32_t)gridDim - 1) + { + mNextGridCell.x = INCREMENT_CELL(cellx, 1, (int32_t)(gridDim - 1)); + } + else + { + mNextGridCell.x = cellx; + } + + mNextGridCell.y = INCREMENT_CELL(celly, 1, (int32_t)(gridDim - 1)); + + stopScanningGridCells = true; + break; + } + + mCellLastRefreshSteps[ gridID ] = mSimulationSteps; + } + } + } + + mAsset->mModule->mSdk->releaseTempMemory(grids); +} + +void GroundEmitterActorImpl::tick() +{ + float dt = mScene->mApexScene->getElapsedTime(); + + mTotalElapsedTimeMs = mTotalElapsedTimeMs + (uint32_t)(1000.0f * dt); + PX_PROFILE_ZONE("GroundParticlesEmitterTick", GetInternalApexSDK()->getContextId()); + + mSimulationSteps++; + + //TODO: make more localize locks + SCOPED_PHYSX_LOCK_WRITE(mScene->mApexScene); + + if (mAttachActor) + { + PxTransform t = mAttachActor->is<physx::PxRigidDynamic>()->getGlobalPose(); + setPosition(t.p + mAttachRelativePosition); + } + + // generate new raycast positions based on refresh requirements + refreshCircle((mStepsize < mMaxStepSize) && (mSpawnHeight == 0.0f) && !mRefreshFullCircle); + mRefreshFullCircle = false; + + if (mToRaycast.size()) + { + submitRaycasts(); + } +} + + +/** + * Raycast callback which is triggered by calling mQueryObject->finish() + */ +bool GroundEmitterActorImpl::onRaycastQuery(uint32_t nbHits, const PxRaycastHit* hits) +{ + PX_UNUSED(userData); + + PX_PROFILE_ZONE("GroundParticlesEmitterOnRaycastQuery", GetInternalApexSDK()->getContextId()); + + if (!nbHits) + { + return true; + } + + MaterialLookupCallback::MaterialRequest* matRequest = NULL; + if (mMaterialCallback) + { + mMaterialRequestArray.resize(nbHits); + matRequest = &mMaterialRequestArray[0]; + for (uint32_t i = 0 ; i < nbHits ; i++) + { + matRequest[i].samplePosition = hits[i].position; + } + mMaterialCallback->requestMaterialLookups(nbHits, matRequest); + } + + nvidia::Mutex::ScopedLock scopeLock(mInjectorDataLock); + + PxVec3 worldUpDirection; + worldUpDirection = getPose().rotate(mLocalUpDirection); + + for (uint32_t i = 0; i < nbHits; i++) + { + const PxRaycastHit& hit = hits[i]; + + // TODO 3.0 apan, check matRequest!! + //this->mScene->mPhysXScene->getPhysics().getNbMaterials +// PxMaterial* materail = hit.shape->getMaterialFromInternalFaceIndex(hit.faceIndex); + uint16_t matIndex = matRequest ? (uint16_t) matRequest[i].outMaterialID : (uint16_t) 0;//materail; // apan, fixme, hard code to 0 + if (mPerMaterialData.isValid(matIndex) && + mPerMaterialData.isUsed(matIndex)) + { + PX_ASSERT(PxAbs(1.0f - hit.normal.magnitude()) < 0.001f); // assert normal is normalized + + MaterialData& data = mPerMaterialData.direct(matIndex); + float maxSlopeAngle = 0.0f; + uint32_t particleFactoryIndex = data.chooseIOFX(maxSlopeAngle, mRand); + + float upNormal = hit.normal.dot(worldUpDirection); + if (upNormal < maxSlopeAngle) + { + continue; + } + + InjectorData& injectorData = *mInjectorList[particleFactoryIndex]; + IosNewObject particle; + + if (mSpawnHeight <= 0.0f) + { + particle.initialPosition = hit.position; + particle.initialPosition += injectorData.mObjectRadius * hit.normal; + } + else + { + PX_ASSERT(mLocalUpDirection == PxVec3(0, 1, 0)); + particle.initialPosition.x = hit.position.x; + particle.initialPosition.z = hit.position.z; + particle.initialPosition.y = mLocalPlayerPosition.y + mSpawnHeight; + } + + particle.initialVelocity = mRand.getScaled(getVelocityLow(), getVelocityHigh()); + particle.lifetime = mRand.getScaled(getLifetimeLow(), getLifetimeHigh()); + + particle.lodBenefit = 0; + particle.iofxActorID = IofxActorIDIntl(0); + particle.userData = 0; + + injectorData.particles.pushBack(particle); + } + } + + return true; +} + +} +} // namespace nvidia::apex |