Files
2025-09-18 17:55:52 -04:00

348 lines
8.7 KiB
C++

#include "stdafx.h"
#include "CustomEmitter.h"
#include "V8DataModel/PartInstance.h"
#include "v8datamodel/CustomParticleEmitter.h"
#include "SceneUpdater.h"
#include "Util.h"
#include "Emitter.h"
#include "TextureManager.h"
#include "VisualEngine.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace RBX
{
namespace Graphics
{
CustomEmitter::CustomEmitter(VisualEngine* visualEngine)
: Super(visualEngine, CullMode_SpatialHash)
, dirtyFlags(0)
, enabled(false)
, localBox(Vector3::zero(), Vector3::zero())
, requestedEmitCount(0)
{
}
CustomEmitter::~CustomEmitter()
{
unbind();
// notify scene updater about destruction so that the pointer to CustomEmitter is no longer stored
getVisualEngine()->getSceneUpdater()->notifyDestroyed(this);
}
void CustomEmitter::onSleepingChangedEx(bool sleeping)
{
if (sleeping)
{
getVisualEngine()->getSceneUpdater()->notifySleeping(this);
}
else
{
getVisualEngine()->getSceneUpdater()->notifyAwake(this);
}
}
void CustomEmitter::updateCoordinateFrame(bool recalcLocalBounds)
{
CoordinateFrame frame = part ? part->calcRenderingCoordinateFrame() : CoordinateFrame();
if ( (dirtyFlags & kDirty_Transform) == 0 && transform.fuzzyEq(frame))
{
return; // Nothing to update
}
transform = frame;
if (emitter)
{
emitter->transform() = transform;
localBox = emitter->computeBBox();
updateWorldBounds(localBox.toWorldSpace(transform));
if (part)
emitter->setVelocity(part->getVelocity());
else
emitter->setVelocity(Velocity());
}
}
void CustomEmitter::onCombinedSignalEx(Instance::CombinedSignalType type, const Instance::ICombinedSignalData* data)
{
switch (type)
{
case Instance::PROPERTY_CHANGED:
onPropertyChangedEx(boost::polymorphic_downcast<const Instance::PropertyChangedSignalData*>(data)->propertyDescriptor);
break;
case Instance::ANCESTRY_CHANGED:
onAncestorChangedEx();
break;
default:
break;
}
}
void CustomEmitter::onAncestorChangedEx()
{
shared_ptr<Instance> effectCopy = effect;
// Remove me from the scene if I am being removed from the Workspace
if (!isInWorkspace(effectCopy.get()))
{
// will cause a delete on next updateEntity()
zombify();
}
else
{
unbind();
RBX::PartInstance* parent = RBX::Instance::fastDynamicCast<RBX::PartInstance>(effectCopy->getParent());
shared_ptr<RBX::PartInstance> part = shared_from(parent);
bind(part, effectCopy);
}
}
void CustomEmitter::bind(const shared_ptr<RBX::PartInstance>& part, const shared_ptr<RBX::Instance>& instance)
{
RBXASSERT(!this->part && !this->effect);
this->part = part;
this->effect = shared_from(instance->fastDynamicCast<RBX::CustomParticleEmitter>());
//
if (part)
{
connections.push_back(part->onDemandWrite()->sleepingChangedSignal.connect(boost::bind(&CustomEmitter::onSleepingChangedEx, this, _1)));
// we just connected, so sync up the state.
onSleepingChangedEx(part->getSleeping());
}
if (effect)
{
connections.push_back(this->effect->onEmitRequested.connect(boost::bind(&CustomEmitter::requestEmit, this, _1)));
connections.push_back(instance->combinedSignal.connect(boost::bind(&CustomEmitter::onCombinedSignalEx, this, _1, _2)));
if(part)
{
connections.push_back( part->propertyChangedSignal.connect( boost::bind(&CustomEmitter::onParentSize, this, _1)) );
}
}
dirtyFlags = kDirty_All;
invalidateEntity();
}
void CustomEmitter::unbind()
{
Super::unbind();
part.reset();
effect.reset();
}
void CustomEmitter::requestEmit(int particleCount)
{
requestedEmitCount += particleCount;
dirtyFlags |= kDirty_Fast;
invalidateEntity();
}
void CustomEmitter::invalidateEntity()
{
if ((dirtyFlags & kDirty_Invalidate) == 0)
{
dirtyFlags |= kDirty_Invalidate; // prevent repeated submissions
getVisualEngine()->getSceneUpdater()->queueInvalidatePart(this);
}
}
void CustomEmitter::updateEntity(bool assetsUpdated)
{
if (connections.empty()) // zombified.
{
getVisualEngine()->getSceneUpdater()->destroyAttachment(this);
return;
}
applyAllSettings();
updateCoordinateFrame(true);
dirtyFlags = kDirty_None;
}
void CustomEmitter::updateRenderQueue(RenderQueue& queue, const RenderCamera& camera, RenderQueue::Pass pass)
{
if (!enabled && emitter && !emitter->pcount())
emitter.reset();
if (emitter)
{
emitter->draw(queue);
}
// Render bounding box
if (getVisualEngine()->getSettings()->getDebugShowBoundingBoxes())
debugRenderBoundingBox();
}
static inline Vector2 v2(const NumberRange& r) { return Vector2(r.min, r.max); }
void CustomEmitter::applyAllSettings()
{
if (!effect) return;
if( !emitter )
{
emitter.reset( new Emitter(getVisualEngine(), true, effect->getFullName() + ".Texture") );
dirtyFlags = kDirty_All;
}
static const Vector2 zz(0,0);
static const Vector3 zzz(0,0,0);
static const Vector4 zzzz(0,0,0,0);
static const Vector2 cc(1,1);
if (dirtyFlags & kDirty_Fast)
{
Vector3 boxsize(1,1,1);
if( part )
{
boxsize = 0.5f * part->getRenderSize();
}
enabled = effect->getEnabled();
emitter->setBlendRatio(1.0f - effect->getLightEmission());
emitter->setEmissionRate( effect->getRate() * effect->getEnabled() );
emitter->setSpeed( v2(effect->getSpeed()) );
emitter->setSpread( cc * effect->getSpread() / 180 * M_PI );
emitter->setRotation( v2(effect->getRotation()) / 180 * M_PI );
emitter->setSpin( v2(effect->getRotSpeed()) / 180 * M_PI );
emitter->setZOffset(effect->getZOffset());
emitter->setLife( v2(effect->getLifetime()) );
emitter->setGlobalForce(effect->getAccel());
emitter->setEmitterShape(0, Box(-boxsize, boxsize) );
emitter->setDampening(effect->getDampening());
emitter->setLockedToLocalSpace(effect->getLockedToLocalSpace());
emitter->setVelocityInheritance(effect->getVelocityInheritance());
localBox = emitter->computeBBox();
updateWorldBounds(localBox.toWorldSpace(transform));
//calculate the spherical direction
Vector2 sphericalDirection;
switch(effect->getEmissionDirection())
{
case RBX::NORM_Y:
sphericalDirection = Vector2(M_PI / 2, M_PI / 2);
break;
case RBX::NORM_Y_NEG:
sphericalDirection = Vector2(-M_PI / 2, M_PI / 2);
break;
case RBX::NORM_X:
sphericalDirection = Vector2(0, M_PI / 2);
break;
case RBX::NORM_X_NEG:
sphericalDirection = Vector2(0, -M_PI / 2);
break;
case RBX::NORM_Z_NEG:
sphericalDirection = Vector2(0, M_PI);
break;
case RBX::NORM_Z:
sphericalDirection = Vector2(0, 0);
break;
default:
break;
}
emitter->setSphericalDirection(sphericalDirection);
//emit!
if (requestedEmitCount > 0)
{
updateCoordinateFrame(true);
emitter->emit(requestedEmitCount);
requestedEmitCount = 0;
}
}
if (dirtyFlags & kDirty_Curves)
{
emitter->setColorCurve( &effect->getColor() );
emitter->setAlphaCurve( &effect->getTransparency() );
emitter->setSizeCurve( &effect->getSize() );
localBox = emitter->computeBBox();
updateWorldBounds(localBox.toWorldSpace(transform));
}
if (dirtyFlags & kDirty_Slow)
{
Emitter::Appearance a = {};
a.alphaStripTexture = "";
a.colorStripTexture = "";
a.mainTexture = effect->getTexture().c_str();
a.colorStripBaseline = -1;
a.shader = Emitter::Shader_Custom;
a.blendCode = Emitter::Blend_PremultipliedAlpha;
emitter->setAppearance( a, effect->getFullName() + ".Texture" );
}
}
void CustomEmitter::onPropertyChangedEx(const RBX::Reflection::PropertyDescriptor* p)
{
if (p == &CustomParticleEmitter::prop_texture)
{
dirtyFlags |= kDirty_Slow;
invalidateEntity();
return;
}
if (p == &CustomParticleEmitter::prop_color)
{
dirtyFlags |= kDirty_Curves;
invalidateEntity();
return;
}
if (p == &CustomParticleEmitter::prop_transp)
{
dirtyFlags |= kDirty_Curves;
invalidateEntity();
return;
}
if (p == &CustomParticleEmitter::prop_size)
{
dirtyFlags |= kDirty_Curves;
invalidateEntity();
return;
}
dirtyFlags |= kDirty_Fast;
invalidateEntity();
}
void CustomEmitter::onParentSize(pd p)
{
if (p == &PartInstance::prop_Size)
{
dirtyFlags |= kDirty_Fast;
invalidateEntity();
}
}
}
}