mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
797 lines
24 KiB
C++
797 lines
24 KiB
C++
#include "stdafx.h"
|
|
#include "Emitter.h"
|
|
|
|
#include "GfxCore/Device.h"
|
|
#include "util/G3DCore.h"
|
|
#include "VisualEngine.h"
|
|
#include "ShaderManager.h"
|
|
#include "RenderQueue.h"
|
|
#include "RenderCamera.h"
|
|
#include "TextureManager.h"
|
|
#include "GfxBase/FrameRateManager.h"
|
|
|
|
#include "GfxCore/Geometry.h"
|
|
#include "Material.h"
|
|
#include "EmitterShared.h"
|
|
#include "RenderNode.h"
|
|
|
|
#include <boost/cstdint.hpp>
|
|
|
|
#include "v8datamodel/NumberSequence.h"
|
|
#include "v8datamodel/ColorSequence.h"
|
|
|
|
#undef min
|
|
#undef max
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
FASTFLAG(GlowEnabled)
|
|
FASTINTVARIABLE(RenderMaxParticleSize, 200);
|
|
|
|
DYNAMIC_FASTFLAG(EnableParticleDrag)
|
|
|
|
static int gEmitterCount = 0;
|
|
|
|
using G3D::clamp;
|
|
|
|
namespace RBX{ namespace Graphics{
|
|
|
|
typedef boost::int16_t int16;
|
|
typedef boost::uint8_t uint8;
|
|
typedef boost::uint32_t uint32;
|
|
typedef boost::uint16_t index_t;
|
|
|
|
|
|
static const float kThrottleDist = 200.0f;
|
|
static const float kCutoffDist = 1000.0f;
|
|
static const float kMinThrottle = 0.1f;
|
|
static const float kCutoffAlpha = 10/255.0f;
|
|
static const float kAlphaBoost = 0.6f;
|
|
static const float kMaxLife = 20;
|
|
|
|
#if defined(RBX_PLATFORM_IOS) || defined(__ANDROID__)
|
|
static const int kMaxParticles = 14000/4;
|
|
static const float kMaxThrottle = 0.7f;
|
|
static const float kMaxEmissionRate = 100;
|
|
#else
|
|
static const int kMaxParticles = 64000/4;
|
|
static const float kMaxThrottle = 1.0f;
|
|
static const float kMaxEmissionRate = 400;
|
|
#endif
|
|
|
|
|
|
static const float kLongFrameSimStep = 0.016f; // ~60fps
|
|
static const int kMaxParticlesPerEmitter = 60000;
|
|
|
|
char dummy[ sizeof(EmitterShared().shaders) / sizeof(EmitterShared().shaders[0]) == Emitter::Shader__Count ];
|
|
|
|
struct ParticleVertex
|
|
{
|
|
float x,y,z; //
|
|
int16 scaleRotLifeq[4]; // .x = sx, .y = sy, .z = angle, .w = normalized 0..1 lifetime
|
|
int16 disp[2]; // constant 0,0 0,1 1,0 1,1 for corner displacement from the center
|
|
int16 cline[2]; // color line ( .y is reserved )
|
|
uint8 color[4]; // r,g,b,a
|
|
};
|
|
|
|
static const VertexLayout::Element kVertexDecl[] =
|
|
{
|
|
VertexLayout::Element(0, offsetof(ParticleVertex, x), VertexLayout::Format_Float3, VertexLayout::Semantic_Position, 0),
|
|
VertexLayout::Element(0, offsetof(ParticleVertex, scaleRotLifeq), VertexLayout::Format_Short4, VertexLayout::Semantic_Texture, 0),
|
|
VertexLayout::Element(0, offsetof(ParticleVertex, disp), VertexLayout::Format_Short2, VertexLayout::Semantic_Texture, 1),
|
|
VertexLayout::Element(0, offsetof(ParticleVertex, cline), VertexLayout::Format_Short2, VertexLayout::Semantic_Texture, 2),
|
|
VertexLayout::Element(0, offsetof(ParticleVertex, color), VertexLayout::Format_UByte4, VertexLayout::Semantic_Texture, 3),
|
|
};
|
|
|
|
/*
|
|
|
|
This is all we need to know about particles:
|
|
|
|
0 1
|
|
*---------------*
|
|
| / |
|
|
| / |
|
|
| / |
|
|
| / |
|
|
| / |
|
|
| / |
|
|
| / |
|
|
*---------------*
|
|
2 3
|
|
|
|
*/
|
|
struct Emitter::Particle
|
|
{
|
|
float zpos; // sort key
|
|
Vector3 pos;
|
|
Vector3 vel;
|
|
float rot;
|
|
float spin;
|
|
float life;
|
|
float lifeSpan;
|
|
float sx;
|
|
float sy;
|
|
uint32 cline;
|
|
};
|
|
|
|
void EmitterShared::init(VisualEngine* ve)
|
|
{
|
|
if (ibuf) return; // already there
|
|
|
|
visualEngine = ve;
|
|
colorBGR = ve->getDevice()->getCaps().colorOrderBGR;
|
|
Device* dev = visualEngine->getDevice();
|
|
|
|
int numParticles = kMaxParticles;
|
|
int numIndices = kMaxParticles * 6;
|
|
|
|
vbuf = dev->createVertexBuffer(sizeof(ParticleVertex), kMaxParticles * 4, VertexBuffer::Usage_Dynamic);
|
|
|
|
ibuf = dev->createIndexBuffer(2, numIndices, IndexBuffer::Usage_Static);
|
|
index_t* ptr = (index_t*) ibuf->lock();
|
|
for (int j=0; j<numParticles; j++)
|
|
{
|
|
ptr[6*j + 0] = 4*j + 0;
|
|
ptr[6*j + 1] = 4*j + 1;
|
|
ptr[6*j + 2] = 4*j + 2;
|
|
ptr[6*j + 3] = 4*j + 2;
|
|
ptr[6*j + 4] = 4*j + 1;
|
|
ptr[6*j + 5] = 4*j + 3;
|
|
}
|
|
ibuf->unlock();
|
|
|
|
if (!vlayout)
|
|
{
|
|
vlayout = dev->createVertexLayout(std::vector< VertexLayout::Element >(kVertexDecl, kVertexDecl + sizeof(kVertexDecl)/sizeof(kVertexDecl[0])));
|
|
}
|
|
|
|
shaders[0] = ve->getShaderManager()->getProgram("ParticleVS","ParticleAddFS");
|
|
shaders[1] = ve->getShaderManager()->getProgram("ParticleVS","ParticleModulateFS");
|
|
shaders[2] = ve->getShaderManager()->getProgram("ParticleVS","ParticleCrazyFS");
|
|
shaders[3] = ve->getShaderManager()->getProgram("ParticleVS","ParticleCrazySparklesFS");
|
|
shaders[4] = ve->getShaderManager()->getProgram("ParticleCustomVS","ParticleCustomFS");
|
|
|
|
vblock = vbptr = vbend = 0;
|
|
}
|
|
|
|
void* EmitterShared::lock(int* retIndex, int vcnt)
|
|
{
|
|
*retIndex = 0xbaadf00d;
|
|
if (!vblock)
|
|
{
|
|
vblock = vbptr = vbuf->lock(VertexBuffer::Lock_Discard);
|
|
vbend = (char*)vbptr + vbuf->getElementCount() * vbuf->getElementSize();
|
|
}
|
|
|
|
int bsize = vcnt * vbuf->getElementSize();
|
|
|
|
if ((char*)vbptr + bsize > vbend) return 0; // won't fit
|
|
|
|
void* ret = vbptr;
|
|
*retIndex = ((char*)vbptr - (char*)vblock) / vbuf->getElementSize();
|
|
vbptr = (char*)vbptr + bsize;
|
|
return ret;
|
|
}
|
|
|
|
void EmitterShared::flush()
|
|
{
|
|
if (!vbuf || !vblock) return;
|
|
vbuf->unlock();
|
|
vblock = vbptr = vbend = 0;
|
|
}
|
|
|
|
Emitter::Emitter(VisualEngine* ve, bool enableCurves_, const std::string& context)
|
|
: enableCurves(enableCurves_)
|
|
{
|
|
|
|
Device* dev = ve->getDevice();
|
|
RBXASSERT(dev);
|
|
|
|
sharedState = ve->getEmitterSharedState();
|
|
sharedState->init(ve);
|
|
|
|
geom = dev->createGeometry(sharedState->vlayout, sharedState->vbuf, sharedState->ibuf, 0);
|
|
batch.reset(new GeometryBatch(geom, Geometry::Primitive_Triangles, 0, 0, 0, 0));
|
|
|
|
plist.reserve(60);
|
|
emissionCounter = 0;
|
|
|
|
Vector2 zz(0,0);
|
|
Vector3 zzz(0,0,0);
|
|
|
|
life = Vector2(5,5);
|
|
emitterShape = 0;
|
|
emitterBox = Box(Vector3(-0.5f,-0.5f,-0.5f), Vector3(0.5f,0.5f,0.5f));
|
|
emissionRate = 10;
|
|
|
|
|
|
speed = Vector2(0,0);
|
|
spread = zz;
|
|
globalForce = localForce = zzz;
|
|
dampening = 0;
|
|
rotation = zz;
|
|
spin = zz;
|
|
sizeX = Vector2(1,1);
|
|
sizeY = Vector2(1,1);
|
|
growth = zz;
|
|
maxSize = 0;
|
|
velocity = Velocity();
|
|
velocityInheritance = 0;
|
|
lockedToLocalSpace = false;
|
|
sphericalDirection = Vector2(M_PI / 2, M_PI / 2);
|
|
|
|
Appearance def = {};
|
|
def.blendCode = Blend_AlphaBlend;
|
|
def.shader = Shader_Modulate;
|
|
def.mainTexture = "rbxasset://textures/particles/sparkles_main.dds";
|
|
def.colorStripTexture = "rbxasset://textures/particles/sparkles_color.dds";
|
|
def.alphaStripTexture = "rbxasset://textures/particles/common_alpha.dds";
|
|
def.colorStripBaseline = -1;
|
|
|
|
modulateColor = Vector4(1,1,1,1);
|
|
zOffset = 0;
|
|
inheritMotion = 0;
|
|
brightenOnThrottle = 0;
|
|
blendRatio = 0.5f; // doesn't do anything unless configured to use a Crazy shader
|
|
|
|
setAppearance(def, context);
|
|
|
|
gEmitterCount++;
|
|
}
|
|
|
|
Emitter::~Emitter()
|
|
{
|
|
gEmitterCount--;
|
|
}
|
|
|
|
static inline float lerp( float a, float b, float s ) { return a + (b-a)*s; }
|
|
|
|
static inline float sampleCurve( const Vector2* ptr, float t, float tr)
|
|
{
|
|
static const int kNumIntervals = Emitter::kNumCachePoints - 1;
|
|
|
|
float ut = kNumIntervals * t; // un-normalized time, e.g.: 15.33 means that we're at the 15th interval, 33% towards the next one
|
|
|
|
int i(ut); // current interval index
|
|
float r = ut - i; // ratio towards the next point
|
|
|
|
float min = lerp( ptr[i].x, ptr[i+1].x, r );
|
|
float max = lerp( ptr[i].y, ptr[i+1].y, r );
|
|
return lerp( min, max, tr );
|
|
}
|
|
|
|
static inline void sampleCurve( Vector3* val, const Vector3* a, const Vector3* b, float t, float tr )
|
|
{
|
|
static const int kNumIntervals = Emitter::kNumCachePoints - 1;
|
|
|
|
float ut = kNumIntervals * t; // un-normalized time, e.g.: 15.33 means that we're at the 15th interval, 33% towards the next one
|
|
|
|
int i(ut); // current interval index
|
|
float r = ut - i; // ratio towards the next point
|
|
|
|
val->x = lerp( lerp( a[i].x, a[i+1].x, r ), lerp( b[i].x, b[i+1].x, r), tr );
|
|
val->y = lerp( lerp( a[i].y, a[i+1].y, r ), lerp( b[i].y, b[i+1].y, r), tr );
|
|
val->z = lerp( lerp( a[i].z, a[i+1].z, r ), lerp( b[i].z, b[i+1].z, r), tr );
|
|
}
|
|
|
|
struct ZSortPr
|
|
{
|
|
bool operator() (const Emitter::Particle& a, const Emitter::Particle& b) const
|
|
{
|
|
return a.zpos > b.zpos;
|
|
}
|
|
};
|
|
|
|
void Emitter::simulateParticle(Particle& p, float dt, Vector3 wsAccel, CoordinateFrame disp, float weight)
|
|
{
|
|
p.life = std::max(p.life - dt, 0.0f);
|
|
|
|
Vector3 accel = wsAccel;
|
|
if (lockedToLocalSpace)
|
|
{
|
|
accel = cframe.vectorToWorldSpace(globalForce) + cframe.vectorToWorldSpace(localForce);
|
|
p.vel = cframe.vectorToWorldSpace(prevCframe.vectorToObjectSpace(p.vel));
|
|
p.pos = cframe.pointToWorldSpace(prevCframe.pointToObjectSpace(p.pos));
|
|
}
|
|
|
|
p.pos += p.vel * dt + accel * (0.5f * dt*dt);
|
|
p.vel += accel * dt;
|
|
|
|
if (DFFlag::EnableParticleDrag)
|
|
{
|
|
p.vel *= powf(2.f, -dampening * dt);
|
|
}
|
|
else
|
|
{
|
|
p.vel = p.vel.lerp(Vector3(0,0,0), dampening*dt);
|
|
}
|
|
|
|
p.sx += growth.x * dt;
|
|
p.sy += growth.y * dt;
|
|
}
|
|
|
|
void Emitter::sim(float dt)
|
|
{
|
|
const RenderCamera& cam = sharedState->visualEngine->getCamera();
|
|
|
|
FrameRateManager* frm = sharedState->visualEngine->getFrameRateManager();
|
|
|
|
float ptf = (float)frm->GetParticleThrottleFactor();
|
|
float emissionRateMul = G3D::clamp( ptf, kMinThrottle, kMaxThrottle );
|
|
float camDist = (cam.getPosition() - cframe.translation).length();
|
|
float distFactor = G3D::clamp(1 - (camDist - kThrottleDist) / (kCutoffDist - kThrottleDist), 0, 1);
|
|
|
|
emissionCounter -= emit(emissionCounter);
|
|
emissionCounter += dt * emissionRate * emissionRateMul * distFactor;
|
|
|
|
if ( !plist.empty() )
|
|
{
|
|
// Motion inheritance: figure out how far the emitter has moved, then apply the displacement to each particle
|
|
// (subject to inheritMotion property)
|
|
CoordinateFrame disp = cframe * prevCframe.inverse();
|
|
|
|
Vector3 wsAccel = cframe.vectorToWorldSpace(localForce) + globalForce;
|
|
float weight = inheritMotion;
|
|
|
|
for (unsigned j=0, e = plist.size(); j<e;)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if (plist[j].life < 0.001f || plist[j].sx < 0 || plist[j].sy < 0)
|
|
{
|
|
e = kill(j);
|
|
continue;
|
|
}
|
|
|
|
Particle& p = plist[j];
|
|
simulateParticle(p, dt, wsAccel, disp, weight);
|
|
|
|
plist[j].zpos = (plist[j].pos - cam.getPosition()).dot(cam.getDirection());
|
|
|
|
++j;
|
|
}
|
|
}
|
|
|
|
prevCframe = cframe;
|
|
}
|
|
|
|
void Emitter::draw(RenderQueue& rq)
|
|
{
|
|
if (!teq) return;
|
|
|
|
float dt = sharedState->visualEngine->getFrameRateManager()->GetFrameTimeStats().getLatest() / 1000.f;
|
|
bool longFrame = !!sharedState->visualEngine->getSettings()->getEagerBulkExecution(); // are we having a long frame?
|
|
FrameRateManager* frm = sharedState->visualEngine->getFrameRateManager();
|
|
float ptf = (float)frm->GetParticleThrottleFactor();
|
|
float emissionRateMul = G3D::clamp( ptf, kMinThrottle, kMaxThrottle );
|
|
const RenderCamera& cam = sharedState->visualEngine->getCamera();
|
|
|
|
if (!longFrame)
|
|
{
|
|
dt = std::min( dt, 0.066f );
|
|
sim(dt);
|
|
}
|
|
else
|
|
{
|
|
for (float st = dt; st > 0; st -= kLongFrameSimStep)
|
|
{
|
|
sim(kLongFrameSimStep);
|
|
}
|
|
}
|
|
|
|
if (plist.empty()) return;
|
|
|
|
const int visiblePCount = plist.size();
|
|
int startIndex;
|
|
ParticleVertex* ptr = (ParticleVertex*) sharedState->lock(&startIndex, 4*visiblePCount);
|
|
if (!ptr) return; // won't fit
|
|
|
|
if (!plist.empty())
|
|
{
|
|
Emitter::Particle* p = &plist[0];
|
|
std::sort(p, p+plist.size(), ZSortPr());
|
|
}
|
|
|
|
for (unsigned j=0, e = visiblePCount; j<e; ++j)
|
|
{
|
|
Particle& p = plist[j];
|
|
|
|
ParticleVertex v;
|
|
v.x = p.pos.x;
|
|
v.y = p.pos.y;
|
|
v.z = p.pos.z;
|
|
v.cline[0] = p.cline;
|
|
v.cline[1] = 0;
|
|
|
|
float sx = p.sx;
|
|
float sy = p.sy;
|
|
Vector3 color;
|
|
|
|
if (enableCurves)
|
|
{
|
|
float nl = 1 - p.life/p.lifeSpan; // normalized life
|
|
float tr = p.cline/32767.0f; // trajectory
|
|
|
|
sx = sy = sampleCurve( sizeCurve, nl, tr );
|
|
sampleCurve( &color, colorMin, colorMax, nl, tr );
|
|
|
|
// transparency
|
|
v.color[3] = 255.0f * sampleCurve( alphaCurve, nl, tr );
|
|
|
|
// color
|
|
v.color[0] = 255.0f * color.x;
|
|
v.color[1] = 255.0f * color.y;
|
|
v.color[2] = 255.0f * color.z;
|
|
}
|
|
|
|
|
|
v.scaleRotLifeq[0] = short((sx - 127) * 256 + 0.5f );
|
|
v.scaleRotLifeq[1] = short((sy - 127) * 256 + 0.5f );
|
|
v.scaleRotLifeq[2] = short((p.rot + p.spin * p.life) * (32767.f / (2 * 3.1415926f)) + 0.5f);
|
|
v.scaleRotLifeq[3] = short( (p.life / p.lifeSpan) * 32767.f + 0.5f);
|
|
|
|
v.disp[0] = 0; v.disp[1] = 0;
|
|
*ptr++ = v;
|
|
|
|
v.disp[0] = 1; v.disp[1] = 0;
|
|
*ptr++ = v;
|
|
|
|
v.disp[0] = 0; v.disp[1] = 1;
|
|
*ptr++ = v;
|
|
|
|
v.disp[0] = 1; v.disp[1] = 1;
|
|
*ptr++ = v;
|
|
}
|
|
|
|
RBXASSERT((char*)sharedState->vblock <= (char*)sharedState->vbptr && (char*)sharedState->vbptr <= (char*)sharedState->vbend);
|
|
|
|
teq->setConstant("throttleFactor", Vector4(kCutoffAlpha * (1-ptf), kAlphaBoost * (1-ptf), 0, blendRatio));
|
|
teq->setConstant("modulateColor", lerp( modulateColor, modulateColor/emissionRateMul, brightenOnThrottle) );
|
|
teq->setConstant("zOffset", Vector4(zOffset,0,0,0));
|
|
|
|
*batch = GeometryBatch(geom, Geometry::Primitive_Triangles, 6*startIndex/4, 6*visiblePCount, startIndex, startIndex + 4*visiblePCount);
|
|
|
|
RenderOperation rop;
|
|
rop.renderable = 0;
|
|
rop.distanceKey = RenderEntity::computeViewDepth(cam, cframe.translation, -0.1f - zOffset);
|
|
rop.geometry = this->batch.get();
|
|
rop.technique = teq.get();
|
|
|
|
rq.getGroup(rq.Id_Transparent).push(rop);
|
|
|
|
}
|
|
|
|
static Vector3 f3rand(G3D::Random& rnd, Vector3 ext)
|
|
{
|
|
ext.x = rnd.uniform(-ext.x, ext.x);
|
|
ext.y = rnd.uniform(-ext.y, ext.y);
|
|
ext.z = rnd.uniform(-ext.z, ext.z);
|
|
return ext;
|
|
}
|
|
/*
|
|
uint32 color(Vector4 v, bool d3d)
|
|
{
|
|
v.x += 0.5f/255;
|
|
v.y += 0.5f/255;
|
|
v.z += 0.5f/255;
|
|
v.w += 0.5f/255;
|
|
v *= 255;
|
|
|
|
v = v.clamp(0, 255.5f);
|
|
|
|
if (d3d)
|
|
{
|
|
return (uint32(v.w) << 24) | (uint32(v.x) << 16) | (uint32(v.y) << 8 ) | (uint32(v.z));
|
|
}
|
|
else
|
|
{
|
|
return (uint32(v.w) << 24) | (uint32(v.z) << 16) | (uint32(v.y) << 8 ) | (uint32(v.x));
|
|
}
|
|
}
|
|
*/
|
|
|
|
static Vector3 vrand(G3D::Random& rnd, Vector2 speed, Vector2 spread, Vector2 sphericalDirection)
|
|
{
|
|
float deltaTheta = rnd.uniform(-spread.x, spread.x);
|
|
float theta = sphericalDirection.x + deltaTheta;
|
|
|
|
float deltaPhi = rnd.uniform(-spread.y, spread.y);
|
|
float phi = sphericalDirection.y + deltaPhi;
|
|
|
|
float radius = rnd.uniform(speed.x, speed.y);
|
|
|
|
Vector3 ret;
|
|
|
|
ret.x = radius * sinf(phi) * cosf(theta);
|
|
ret.y = radius * sinf(phi) * sinf(theta);
|
|
ret.z = radius * cosf(phi);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static Vector4 v4rand(G3D::Random& rnd)
|
|
{
|
|
return Vector4(rnd.uniform(-1,1), rnd.uniform(-1,1), rnd.uniform(-1,1), rnd.uniform(-1,1));
|
|
}
|
|
|
|
|
|
int Emitter::emit(int n)
|
|
{
|
|
if (!teq) return 0;
|
|
if (plist.empty())
|
|
prevCframe = cframe;
|
|
|
|
n = std::min(n, int(kMaxParticlesPerEmitter - plist.size()));
|
|
|
|
if (n <= 0) return 0;
|
|
|
|
Vector3 ext = emitterBox.extent() * 0.5f; // because G3D::Box::extent() is not 'extent' as the rest of the world knows it.
|
|
|
|
G3D::Random& rnd = sharedState->rnd;
|
|
|
|
uint32 csSizeY = colorStripTex.getTexture() ? colorStripTex.getTexture()->getHeight() : 256;
|
|
uint32 fixedCLine = (uint32) (32766.99f * appearance.colorStripBaseline / csSizeY);
|
|
|
|
for (int j=0; j<n; ++j)
|
|
{
|
|
Particle p;
|
|
|
|
Vector3 particlePosition = f3rand(rnd, ext);
|
|
if (lockedToLocalSpace)
|
|
p.pos = prevCframe.pointToWorldSpace(particlePosition);
|
|
else
|
|
p.pos = cframe.pointToWorldSpace(particlePosition);
|
|
|
|
Vector3 particleVelocity = vrand(rnd, speed, spread, sphericalDirection);
|
|
if (lockedToLocalSpace)
|
|
{
|
|
p.vel = prevCframe.vectorToWorldSpace(particleVelocity);
|
|
}
|
|
else
|
|
{
|
|
p.vel = cframe.vectorToWorldSpace(particleVelocity);
|
|
|
|
if (velocityInheritance != 0)
|
|
{
|
|
p.vel += velocity.linearVelocityAtOffset(p.pos - cframe.translation) * velocityInheritance;
|
|
}
|
|
}
|
|
|
|
p.life = p.lifeSpan = rnd.uniform(life.x, life.y);
|
|
p.sx = rnd.uniform(sizeX.x, sizeX.y );
|
|
p.sy = rnd.uniform(sizeY.x, sizeY.y );
|
|
p.rot = rnd.uniform(rotation.x, rotation.y);
|
|
p.spin = rnd.uniform(spin.x, spin.y);
|
|
|
|
if (appearance.colorStripBaseline < 0 )
|
|
p.cline = rnd.uniform(0,32766.99f);
|
|
else
|
|
p.cline = fixedCLine;
|
|
|
|
plist.push_back(p);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int Emitter::kill(int n)
|
|
{
|
|
std::vector< Emitter::Particle >& vec = plist;
|
|
|
|
int last = vec.size();
|
|
if (!last)
|
|
return 0;
|
|
|
|
last--;
|
|
vec[n] = vec[last];
|
|
vec.resize(last);
|
|
return last;
|
|
}
|
|
|
|
static TextureRef gettex( VisualEngine* ve, const std::string& name, const std::string& context)
|
|
{
|
|
return ve->getTextureManager()->load( ContentId(name), TextureManager::Fallback_BlackTransparent, context);
|
|
}
|
|
|
|
static BlendState createBlendState(Emitter::BlendMode blendMode)
|
|
{
|
|
// to understand values of separate alpha blend, check the setupTechnique function in MaterialGenerator.cpp
|
|
if (FFlag::GlowEnabled)
|
|
{
|
|
switch (blendMode)
|
|
{
|
|
case Emitter::Blend_None:
|
|
return BlendState(BlendState::Factor_One, BlendState::Factor_Zero, BlendState::Factor_One, BlendState::Factor_One);
|
|
break;
|
|
case Emitter::Blend_Additive:
|
|
return BlendState(BlendState::Factor_One, BlendState::Factor_One, BlendState::Factor_InvDstAlpha, BlendState::Factor_One);
|
|
break;
|
|
case Emitter::Blend_Multiplicative:
|
|
return BlendState(BlendState::Factor_DstColor, BlendState::Factor_Zero, BlendState::Factor_InvDstAlpha, BlendState::Factor_One);
|
|
break;
|
|
case Emitter::Blend_AlphaBlend:
|
|
return BlendState(BlendState::Factor_SrcAlpha, BlendState::Factor_InvSrcAlpha, BlendState::Factor_InvDstAlpha, BlendState::Factor_One);
|
|
break;
|
|
case Emitter::Blend_PremultipliedAlpha:
|
|
return BlendState(BlendState::Factor_One, BlendState::Factor_InvSrcAlpha, BlendState::Factor_InvDstAlpha, BlendState::Factor_One);
|
|
break;
|
|
case Emitter::Blend_AlphaOne:
|
|
return BlendState(BlendState::Factor_SrcAlpha, BlendState::Factor_One, BlendState::Factor_InvDstAlpha, BlendState::Factor_One);
|
|
break;
|
|
default:
|
|
RBXASSERT(false); // did you add new mode?
|
|
return BlendState(BlendState::Mode_None);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (blendMode)
|
|
{
|
|
case Emitter::Blend_None:
|
|
return BlendState(BlendState::Factor_One, BlendState::Factor_Zero);
|
|
break;
|
|
case Emitter::Blend_Additive:
|
|
return BlendState(BlendState::Factor_One, BlendState::Factor_One);
|
|
break;
|
|
case Emitter::Blend_Multiplicative:
|
|
return BlendState(BlendState::Factor_DstColor, BlendState::Factor_Zero);
|
|
break;
|
|
case Emitter::Blend_AlphaBlend:
|
|
return BlendState(BlendState::Factor_SrcAlpha, BlendState::Factor_InvSrcAlpha);
|
|
break;
|
|
case Emitter::Blend_PremultipliedAlpha:
|
|
return BlendState(BlendState::Factor_One, BlendState::Factor_InvSrcAlpha);
|
|
break;
|
|
case Emitter::Blend_AlphaOne:
|
|
return BlendState(BlendState::Factor_SrcAlpha, BlendState::Factor_One);
|
|
break;
|
|
default:
|
|
RBXASSERT(false); // did you add new mode?
|
|
return BlendState(BlendState::Mode_None);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void Emitter::setAppearance(const Appearance& a, const std::string& context)
|
|
{
|
|
appearance = a;
|
|
teq.reset();
|
|
|
|
if( !sharedState->shaders[a.shader] ) return;
|
|
teq.reset( new Technique(sharedState->shaders[a.shader], 0 ) );
|
|
|
|
if( !teq ) return;
|
|
|
|
RasterizerState rs(RasterizerState::Cull_None, 0);
|
|
DepthState ds(DepthState::Function_LessEqual, false, DepthState::Stencil_None);
|
|
|
|
teq->setRasterizerState(rs);
|
|
teq->setDepthState(ds);
|
|
teq->setBlendState(createBlendState(a.blendCode));
|
|
|
|
TextureRef black = sharedState->visualEngine->getTextureManager()->getFallbackTexture(TextureManager::Fallback_Black);
|
|
|
|
TextureRef particleTexture = gettex( sharedState->visualEngine, a.mainTexture, context);
|
|
TextureRef colorStripTexture = !a.colorStripTexture.empty() ? gettex( sharedState->visualEngine, a.colorStripTexture, context ) : black;
|
|
TextureRef alphaStripTexture = !a.alphaStripTexture.empty() ? gettex( sharedState->visualEngine, a.alphaStripTexture, context ) : black;
|
|
|
|
teq->setTexture( 0, particleTexture, SamplerState(SamplerState::Filter_Linear) );
|
|
if (!enableCurves)
|
|
{
|
|
teq->setTexture( 1, colorStripTexture, SamplerState(SamplerState::Filter_Linear,SamplerState::Address_Clamp) );
|
|
teq->setTexture( 2, alphaStripTexture, SamplerState(SamplerState::Filter_Linear,SamplerState::Address_Clamp) );
|
|
}
|
|
|
|
this->colorStripTex = colorStripTexture;
|
|
}
|
|
|
|
Extents Emitter::computeBBox()
|
|
{
|
|
// estimates the bounding box around all these particles
|
|
// does take dampening effect into account for now
|
|
|
|
bool preComputeLockedToLocalSpace = lockedToLocalSpace;
|
|
lockedToLocalSpace = false;
|
|
|
|
Extents boundingBox = Extents(Vector3(-1, -1, -1), Vector3(1, 1, 1));
|
|
Vector3 ext = emitterBox.extent() * 0.5f;
|
|
|
|
Vector3 wsAccel = globalForce;
|
|
if (preComputeLockedToLocalSpace)
|
|
wsAccel = cframe.vectorToWorldSpace(wsAccel);
|
|
|
|
for (int direction = 0; direction < 6; direction++)
|
|
{
|
|
Particle p;
|
|
p.life = p.lifeSpan = life.y;
|
|
|
|
switch(direction)
|
|
{
|
|
case 0:
|
|
p.vel = cframe.vectorToWorldSpace(Vector3(1, 0, 0) * speed.y);
|
|
p.pos = cframe.pointToWorldSpace(Vector3(ext.x, 0, 0));
|
|
break;
|
|
case 1:
|
|
p.vel = cframe.vectorToWorldSpace(Vector3(-1, 0, 0) * speed.y);
|
|
p.pos = cframe.pointToWorldSpace(Vector3(-ext.x, 0, 0));
|
|
break;
|
|
case 2:
|
|
p.vel = cframe.vectorToWorldSpace(Vector3(0, 1, 0) * speed.y);
|
|
p.pos = cframe.pointToWorldSpace(Vector3(0, ext.y, 0));
|
|
break;
|
|
case 3:
|
|
p.vel = cframe.vectorToWorldSpace(Vector3(0, -1, 0) * speed.y);
|
|
p.pos = cframe.pointToWorldSpace(Vector3(0, -ext.y, 0));
|
|
break;
|
|
case 4:
|
|
p.vel = cframe.vectorToWorldSpace(Vector3(0, 0, 1) * speed.y);
|
|
p.pos = cframe.pointToWorldSpace(Vector3(0, 0, ext.z));
|
|
break;
|
|
case 5:
|
|
p.vel = cframe.vectorToWorldSpace(Vector3(0, 0, -1) * speed.y);
|
|
p.pos = cframe.pointToWorldSpace(Vector3(0, 0, -ext.z));
|
|
break;
|
|
}
|
|
|
|
if (velocityInheritance != 0)
|
|
p.vel += velocity.linearVelocityAtOffset(p.pos - cframe.translation) * velocityInheritance;
|
|
|
|
while (p.life > 0)
|
|
{
|
|
simulateParticle(p, 0.05, wsAccel, CoordinateFrame(), inheritMotion);
|
|
boundingBox.expandToContain(cframe.pointToObjectSpace(p.pos));
|
|
}
|
|
}
|
|
|
|
lockedToLocalSpace = preComputeLockedToLocalSpace;
|
|
return boundingBox;
|
|
}
|
|
|
|
int Emitter::pcount() const
|
|
{
|
|
return plist.size();
|
|
}
|
|
|
|
void Emitter::setEmissionRate(float v)
|
|
{
|
|
this->emissionRate = clamp(v, 0, kMaxEmissionRate);
|
|
}
|
|
|
|
void Emitter::setLife(Vector2 v)
|
|
{
|
|
v = Vector2(std::min(v.x, v.y), std::max(v.x, v.y));
|
|
v.x = clamp(v.x, 0, kMaxLife);
|
|
v.y = clamp(v.y, 0, kMaxLife);
|
|
this->life = v;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// new stuff:
|
|
|
|
void Emitter::setColorCurve(const ColorSequence* v)
|
|
{
|
|
v->resample(colorMin, colorMax, ARRAYSIZE(colorMin));
|
|
}
|
|
|
|
void Emitter::setAlphaCurve(const NumberSequence* v )
|
|
{
|
|
v->resample(alphaCurve, ARRAYSIZE(alphaCurve), 0, 1);
|
|
for( int j=0; j<kNumCachePoints; ++j)
|
|
{
|
|
alphaCurve[j] = G3D::Vector2(1,1) - alphaCurve[j].yx(); // flip
|
|
}
|
|
}
|
|
|
|
void Emitter::setSizeCurve(const NumberSequence* v )
|
|
{
|
|
maxSize = 0;
|
|
v->resample(sizeCurve, ARRAYSIZE(sizeCurve), 0, (float)FInt::RenderMaxParticleSize);
|
|
for( int j=0; j<kNumCachePoints; ++j)
|
|
{
|
|
maxSize = std::max( maxSize, sizeCurve[j].y );
|
|
}
|
|
}
|
|
|
|
}}
|