#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 #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; junlock(); 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(); jvisualEngine->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; jvblock <= (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& 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; jresample(sizeCurve, ARRAYSIZE(sizeCurve), 0, (float)FInt::RenderMaxParticleSize); for( int j=0; j