mirror of
https://github.com/pissang/claygl.git
synced 2026-02-01 17:27:08 +00:00
wip(type): add type to particles
This commit is contained in:
parent
9b93debbf5
commit
42a6df0852
@ -6,7 +6,7 @@ import Vector4 from './Vector4';
|
||||
* Random or constant 1d, 2d, 3d vector generator
|
||||
*/
|
||||
|
||||
interface Value<T> {
|
||||
export interface Value<T> {
|
||||
get(out?: T): T;
|
||||
}
|
||||
|
||||
|
||||
@ -1,68 +1,64 @@
|
||||
// @ts-nocheck
|
||||
import Base from '../core/Base';
|
||||
import Vector3 from '../math/Vector3';
|
||||
import Particle from './Particle';
|
||||
import Value from '../math/Value';
|
||||
import * as Value from '../math/Value';
|
||||
|
||||
/**
|
||||
* @constructor clay.particle.Emitter
|
||||
* @extends clay.core.Base
|
||||
*/
|
||||
const Emitter = Base.extend(
|
||||
/** @lends clay.particle.Emitter# */ {
|
||||
/**
|
||||
* Maximum number of particles created by this emitter
|
||||
* @type {number}
|
||||
*/
|
||||
max: 1000,
|
||||
/**
|
||||
* Number of particles created by this emitter each shot
|
||||
* @type {number}
|
||||
*/
|
||||
amount: 20,
|
||||
interface ParticleEmitterOpts {
|
||||
/**
|
||||
* Maximum number of particles created by this emitter
|
||||
*/
|
||||
max: number;
|
||||
/**
|
||||
* Number of particles created by this emitter each shot
|
||||
*/
|
||||
amount: number;
|
||||
|
||||
// Init status for each particle
|
||||
/**
|
||||
* Particle life generator
|
||||
* @type {?clay.Value.<number>}
|
||||
*/
|
||||
life: null,
|
||||
/**
|
||||
* Particle position generator
|
||||
* @type {?clay.Value.<clay.Vector3>}
|
||||
*/
|
||||
position: null,
|
||||
/**
|
||||
* Particle rotation generator
|
||||
* @type {?clay.Value.<clay.Vector3>}
|
||||
*/
|
||||
rotation: null,
|
||||
/**
|
||||
* Particle velocity generator
|
||||
* @type {?clay.Value.<clay.Vector3>}
|
||||
*/
|
||||
velocity: null,
|
||||
/**
|
||||
* Particle angular velocity generator
|
||||
* @type {?clay.Value.<clay.Vector3>}
|
||||
*/
|
||||
angularVelocity: null,
|
||||
/**
|
||||
* Particle sprite size generator
|
||||
* @type {?clay.Value.<number>}
|
||||
*/
|
||||
spriteSize: null,
|
||||
/**
|
||||
* Particle weight generator
|
||||
* @type {?clay.Value.<number>}
|
||||
*/
|
||||
weight: null,
|
||||
// Init status for each particle
|
||||
/**
|
||||
* Particle life generator
|
||||
*/
|
||||
life?: Value.Value<number>;
|
||||
/**
|
||||
* Particle position generator
|
||||
*/
|
||||
position?: Value.Value<Vector3>;
|
||||
/**
|
||||
* Particle rotation generator
|
||||
*/
|
||||
rotation?: Value.Value<Vector3>;
|
||||
/**
|
||||
* Particle velocity generator
|
||||
*/
|
||||
velocity?: Value.Value<Vector3>;
|
||||
/**
|
||||
* Particle angular velocity generator
|
||||
*/
|
||||
angularVelocity?: Value.Value<Vector3>;
|
||||
/**
|
||||
* Particle sprite size generator
|
||||
*/
|
||||
spriteSize?: Value.Value<number>;
|
||||
/**
|
||||
* Particle weight generator
|
||||
*/
|
||||
weight?: Value.Value<number>;
|
||||
}
|
||||
|
||||
_particlePool: null
|
||||
},
|
||||
function () {
|
||||
this._particlePool = [];
|
||||
interface ParticleEmitter extends ParticleEmitterOpts {}
|
||||
class ParticleEmitter {
|
||||
/** @lends clay.particle.Emitter# */
|
||||
/**
|
||||
* Maximum number of particles created by this emitter
|
||||
* @type {number}
|
||||
*/
|
||||
max = 1000;
|
||||
/*
|
||||
* Number of particles created by this emitter each shot
|
||||
* @type {number}
|
||||
*/
|
||||
amount = 20;
|
||||
|
||||
_particlePool: Particle[] = [];
|
||||
constructor() {
|
||||
// TODO Reduce heap memory
|
||||
for (let i = 0; i < this.max; i++) {
|
||||
const particle = new Particle();
|
||||
@ -76,84 +72,74 @@ const Emitter = Base.extend(
|
||||
particle.angularVelocity = new Vector3();
|
||||
}
|
||||
}
|
||||
},
|
||||
/** @lends clay.particle.Emitter.prototype */
|
||||
{
|
||||
/**
|
||||
* Emitter number of particles and push them to a given particle list. Emmit number is defined by amount property
|
||||
* @param {Array.<clay.particle.Particle>} out
|
||||
*/
|
||||
emit: function (out) {
|
||||
const amount = Math.min(this._particlePool.length, this.amount);
|
||||
}
|
||||
/**
|
||||
* Emitter number of particles and push them to a given particle list. Emmit number is defined by amount property
|
||||
*/
|
||||
emit(out: Particle[]) {
|
||||
const amount = Math.min(this._particlePool.length, this.amount);
|
||||
|
||||
let particle;
|
||||
for (let i = 0; i < amount; i++) {
|
||||
particle = this._particlePool.pop();
|
||||
// Initialize particle status
|
||||
if (this.position) {
|
||||
this.position.get(particle.position);
|
||||
}
|
||||
if (this.rotation) {
|
||||
this.rotation.get(particle.rotation);
|
||||
}
|
||||
if (this.velocity) {
|
||||
this.velocity.get(particle.velocity);
|
||||
}
|
||||
if (this.angularVelocity) {
|
||||
this.angularVelocity.get(particle.angularVelocity);
|
||||
}
|
||||
if (this.life) {
|
||||
particle.life = this.life.get();
|
||||
}
|
||||
if (this.spriteSize) {
|
||||
particle.spriteSize = this.spriteSize.get();
|
||||
}
|
||||
if (this.weight) {
|
||||
particle.weight = this.weight.get();
|
||||
}
|
||||
particle.age = 0;
|
||||
|
||||
out.push(particle);
|
||||
let particle;
|
||||
for (let i = 0; i < amount; i++) {
|
||||
particle = this._particlePool.pop()!;
|
||||
// Initialize particle status
|
||||
if (this.position) {
|
||||
this.position.get(particle.position);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Kill a dead particle and put it back in the pool
|
||||
* @param {clay.particle.Particle} particle
|
||||
*/
|
||||
kill: function (particle) {
|
||||
this._particlePool.push(particle);
|
||||
if (this.rotation) {
|
||||
this.rotation.get(particle.rotation);
|
||||
}
|
||||
if (this.velocity) {
|
||||
this.velocity.get(particle.velocity);
|
||||
}
|
||||
if (this.angularVelocity) {
|
||||
this.angularVelocity.get(particle.angularVelocity);
|
||||
}
|
||||
if (this.life) {
|
||||
particle.life = this.life.get();
|
||||
}
|
||||
if (this.spriteSize) {
|
||||
particle.spriteSize = this.spriteSize.get();
|
||||
}
|
||||
if (this.weight) {
|
||||
particle.weight = this.weight.get();
|
||||
}
|
||||
particle.age = 0;
|
||||
|
||||
out.push(particle);
|
||||
}
|
||||
}
|
||||
);
|
||||
/**
|
||||
* Kill a dead particle and put it back in the pool
|
||||
*/
|
||||
kill(particle: Particle) {
|
||||
this._particlePool.push(particle);
|
||||
}
|
||||
/**
|
||||
* Create a constant 1d value generator. Alias for {@link clay.Value.constant}
|
||||
* @function clay.particle.Emitter.constant
|
||||
*/
|
||||
static constant = Value.constant;
|
||||
|
||||
/**
|
||||
* Create a constant 1d value generator. Alias for {@link clay.Value.constant}
|
||||
* @function clay.particle.Emitter.constant
|
||||
*/
|
||||
Emitter.constant = Value.constant;
|
||||
/**
|
||||
* Create a constant vector value(2d or 3d) generator. Alias for {@link clay.Value.vector}
|
||||
*/
|
||||
static vector = Value.vector;
|
||||
|
||||
/**
|
||||
* Create a constant vector value(2d or 3d) generator. Alias for {@link clay.Value.vector}
|
||||
* @function clay.particle.Emitter.vector
|
||||
*/
|
||||
Emitter.vector = Value.vector;
|
||||
/**
|
||||
* Create a random 1d value generator. Alias for {@link clay.Value.random1D}
|
||||
*/
|
||||
static random1D = Value.random1D;
|
||||
|
||||
/**
|
||||
* Create a random 1d value generator. Alias for {@link clay.Value.random1D}
|
||||
* @function clay.particle.Emitter.random1D
|
||||
*/
|
||||
Emitter.random1D = Value.random1D;
|
||||
/**
|
||||
* Create a random 2d value generator. Alias for {@link clay.Value.random2D}
|
||||
*/
|
||||
static random2D = Value.random2D;
|
||||
|
||||
/**
|
||||
* Create a random 2d value generator. Alias for {@link clay.Value.random2D}
|
||||
* @function clay.particle.Emitter.random2D
|
||||
*/
|
||||
Emitter.random2D = Value.random2D;
|
||||
/**
|
||||
* Create a random 3d value generator. Alias for {@link clay.Value.random3D}
|
||||
*/
|
||||
static random3D = Value.random3D;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a random 3d value generator. Alias for {@link clay.Value.random3D}
|
||||
* @function clay.particle.Emitter.random3D
|
||||
*/
|
||||
Emitter.random3D = Value.random3D;
|
||||
|
||||
export default Emitter;
|
||||
export default ParticleEmitter;
|
||||
|
||||
@ -1,22 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import Base from '../core/Base';
|
||||
/**
|
||||
* @constructor clay.particle.Field
|
||||
* @extends clay.core.Base
|
||||
*/
|
||||
const Field = Base.extend(
|
||||
{},
|
||||
{
|
||||
/**
|
||||
* Apply a field to the particle and update the particle velocity
|
||||
* @param {clay.Vector3} velocity
|
||||
* @param {clay.Vector3} position
|
||||
* @param {number} weight
|
||||
* @param {number} deltaTime
|
||||
* @memberOf clay.particle.Field.prototype
|
||||
*/
|
||||
applyTo: function (velocity, position, weight, deltaTime) {}
|
||||
}
|
||||
);
|
||||
import Vector3 from '../math/Vector3';
|
||||
|
||||
export default Field;
|
||||
export default interface ParticleField {
|
||||
/**
|
||||
* Apply a field to the particle and update the particle velocity
|
||||
*/
|
||||
applyTo(
|
||||
velocity: Vector3 | undefined,
|
||||
position: Vector3,
|
||||
weight: number | undefined,
|
||||
deltaTime: number
|
||||
): void;
|
||||
}
|
||||
|
||||
@ -1,25 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import Field from './Field';
|
||||
import ParticleField from './Field';
|
||||
import Vector3 from '../math/Vector3';
|
||||
import vec3 from '../glmatrix/vec3';
|
||||
import * as vec3 from '../glmatrix/vec3';
|
||||
|
||||
/**
|
||||
* @constructor clay.particle.ForceField
|
||||
* @extends clay.particle.Field
|
||||
*/
|
||||
const ForceField = Field.extend(
|
||||
function () {
|
||||
return {
|
||||
force: new Vector3()
|
||||
};
|
||||
},
|
||||
{
|
||||
applyTo: function (velocity, position, weight, deltaTime) {
|
||||
if (weight > 0) {
|
||||
vec3.scaleAndAdd(velocity.array, velocity.array, this.force.array, deltaTime / weight);
|
||||
}
|
||||
export class ForceParticleField implements ParticleField {
|
||||
force: Vector3;
|
||||
constructor(force: Vector3) {
|
||||
this.force = force;
|
||||
}
|
||||
applyTo(velocity: Vector3, position: Vector3, weight: number, deltaTime: number) {
|
||||
if (weight > 0) {
|
||||
vec3.scaleAndAdd(velocity.array, velocity.array, this.force.array, deltaTime / weight);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default ForceField;
|
||||
export default ForceParticleField;
|
||||
|
||||
@ -1,75 +1,45 @@
|
||||
// @ts-nocheck
|
||||
import Vector3 from '../math/Vector3';
|
||||
import vec3 from '../glmatrix/vec3';
|
||||
import * as vec3 from '../glmatrix/vec3';
|
||||
import ParticleEmitter from './Emitter';
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @alias clay.particle.Particle
|
||||
*/
|
||||
const Particle = function () {
|
||||
/**
|
||||
* @type {clay.Vector3}
|
||||
*/
|
||||
this.position = new Vector3();
|
||||
class Particle {
|
||||
position = new Vector3();
|
||||
|
||||
/**
|
||||
* Use euler angle to represent particle rotation
|
||||
* @type {clay.Vector3}
|
||||
*/
|
||||
this.rotation = new Vector3();
|
||||
rotation = new Vector3();
|
||||
|
||||
velocity?: Vector3;
|
||||
|
||||
angularVelocity?: Vector3;
|
||||
|
||||
life = 1;
|
||||
|
||||
age = 0;
|
||||
|
||||
spriteSize: = 1;
|
||||
|
||||
weight = 1;
|
||||
|
||||
emitter?: ParticleEmitter;
|
||||
|
||||
/**
|
||||
* @type {?clay.Vector3}
|
||||
* Update particle position
|
||||
*/
|
||||
this.velocity = null;
|
||||
|
||||
/**
|
||||
* @type {?clay.Vector3}
|
||||
*/
|
||||
this.angularVelocity = null;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.life = 1;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.age = 0;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.spriteSize = 1;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.weight = 1;
|
||||
|
||||
/**
|
||||
* @type {clay.particle.Emitter}
|
||||
*/
|
||||
this.emitter = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update particle position
|
||||
* @param {number} deltaTime
|
||||
*/
|
||||
Particle.prototype.update = function (deltaTime) {
|
||||
if (this.velocity) {
|
||||
vec3.scaleAndAdd(this.position.array, this.position.array, this.velocity.array, deltaTime);
|
||||
update(deltaTime: number) {
|
||||
if (this.velocity) {
|
||||
vec3.scaleAndAdd(this.position.array, this.position.array, this.velocity.array, deltaTime);
|
||||
}
|
||||
if (this.angularVelocity) {
|
||||
vec3.scaleAndAdd(
|
||||
this.rotation.array,
|
||||
this.rotation.array,
|
||||
this.angularVelocity.array,
|
||||
deltaTime
|
||||
);
|
||||
}
|
||||
}
|
||||
if (this.angularVelocity) {
|
||||
vec3.scaleAndAdd(
|
||||
this.rotation.array,
|
||||
this.rotation.array,
|
||||
this.angularVelocity.array,
|
||||
deltaTime
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default Particle;
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import Renderable from '../Renderable';
|
||||
import Renderable, { RenderableOpts } from '../Renderable';
|
||||
|
||||
import Geometry from '../Geometry';
|
||||
import Material from '../Material';
|
||||
import Shader from '../Shader';
|
||||
|
||||
import particleEssl from './particle.glsl.js';
|
||||
import ParticleEmitter from './Emitter';
|
||||
import ParticleField from './Field';
|
||||
import Particle from './Particle';
|
||||
import { optional } from '../core/util';
|
||||
import Renderer from '../Renderer';
|
||||
Shader.import(particleEssl);
|
||||
|
||||
const particleShader = new Shader(
|
||||
@ -13,10 +17,21 @@ const particleShader = new Shader(
|
||||
Shader.source('clay.particle.fragment')
|
||||
);
|
||||
|
||||
interface ParticleRenderableOpts extends RenderableOpts {
|
||||
loop: boolean;
|
||||
oneshot: boolean;
|
||||
/**
|
||||
* Duration of particle system in milliseconds
|
||||
*/
|
||||
duration: number;
|
||||
|
||||
// UV Animation
|
||||
spriteAnimationTileX: number;
|
||||
spriteAnimationTileY: number;
|
||||
spriteAnimationRepeat: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @constructor clay.particle.ParticleRenderable
|
||||
* @extends clay.Renderable
|
||||
*
|
||||
* @example
|
||||
* const particleRenderable = new clay.particle.ParticleRenderable({
|
||||
* spriteAnimationTileX: 4,
|
||||
@ -45,50 +60,31 @@ const particleShader = new Shader(
|
||||
* renderer.render(scene, camera);
|
||||
* });
|
||||
*/
|
||||
const ParticleRenderable = Renderable.extend(
|
||||
/** @lends clay.particle.ParticleRenderable# */ {
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
loop: true,
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
oneshot: false,
|
||||
/**
|
||||
* Duration of particle system in milliseconds
|
||||
* @type {number}
|
||||
*/
|
||||
duration: 1,
|
||||
interface ParticleRenderable extends ParticleRenderableOpts {}
|
||||
class ParticleRenderable extends Renderable {
|
||||
mode = Renderable.POINTS;
|
||||
|
||||
// UV Animation
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
spriteAnimationTileX: 1,
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
spriteAnimationTileY: 1,
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
spriteAnimationRepeat: 0,
|
||||
ignorePicking = true;
|
||||
|
||||
mode: Renderable.POINTS,
|
||||
culling = false;
|
||||
frustumCulling = false;
|
||||
castShadow = false;
|
||||
receiveShadow = false;
|
||||
|
||||
ignorePicking: true,
|
||||
_elapsedTime = 0;
|
||||
_emitting = true;
|
||||
private _emitters: ParticleEmitter[] = [];
|
||||
private _fields: ParticleField[] = [];
|
||||
private _particles: Particle[] = [];
|
||||
|
||||
_elapsedTime: 0,
|
||||
|
||||
_emitting: true
|
||||
},
|
||||
function () {
|
||||
constructor(opts: Partial<ParticleRenderableOpts>) {
|
||||
super(opts);
|
||||
Object.assign(this, opts);
|
||||
this.geometry = new Geometry({
|
||||
dynamic: true
|
||||
});
|
||||
|
||||
if (!this.material) {
|
||||
if (!opts.material) {
|
||||
this.material = new Material({
|
||||
shader: particleShader,
|
||||
transparent: true,
|
||||
@ -98,210 +94,205 @@ const ParticleRenderable = Renderable.extend(
|
||||
this.material.enableTexture('sprite');
|
||||
}
|
||||
|
||||
this._particles = [];
|
||||
this._fields = [];
|
||||
this._emitters = [];
|
||||
},
|
||||
/** @lends clay.particle.ParticleRenderable.prototype */
|
||||
{
|
||||
culling: false,
|
||||
|
||||
frustumCulling: false,
|
||||
|
||||
castShadow: false,
|
||||
receiveShadow: false,
|
||||
|
||||
/**
|
||||
* Add emitter
|
||||
* @param {clay.particle.Emitter} emitter
|
||||
*/
|
||||
addEmitter: function (emitter) {
|
||||
this._emitters.push(emitter);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove emitter
|
||||
* @param {clay.particle.Emitter} emitter
|
||||
*/
|
||||
removeEmitter: function (emitter) {
|
||||
this._emitters.splice(this._emitters.indexOf(emitter), 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add field
|
||||
* @param {clay.particle.Field} field
|
||||
*/
|
||||
addField: function (field) {
|
||||
this._fields.push(field);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove field
|
||||
* @param {clay.particle.Field} field
|
||||
*/
|
||||
removeField: function (field) {
|
||||
this._fields.splice(this._fields.indexOf(field), 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the particle system.
|
||||
*/
|
||||
reset: function () {
|
||||
// Put all the particles back
|
||||
for (let i = 0; i < this._particles.length; i++) {
|
||||
const p = this._particles[i];
|
||||
p.emitter.kill(p);
|
||||
}
|
||||
this._particles.length = 0;
|
||||
this._elapsedTime = 0;
|
||||
this._emitting = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {number} deltaTime
|
||||
*/
|
||||
updateParticles: function (deltaTime) {
|
||||
// MS => Seconds
|
||||
deltaTime /= 1000;
|
||||
this._elapsedTime += deltaTime;
|
||||
|
||||
const particles = this._particles;
|
||||
|
||||
if (this._emitting) {
|
||||
for (let i = 0; i < this._emitters.length; i++) {
|
||||
this._emitters[i].emit(particles);
|
||||
}
|
||||
if (this.oneshot) {
|
||||
this._emitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Aging
|
||||
let len = particles.length;
|
||||
for (let i = 0; i < len; ) {
|
||||
const p = particles[i];
|
||||
p.age += deltaTime;
|
||||
if (p.age >= p.life) {
|
||||
p.emitter.kill(p);
|
||||
particles[i] = particles[len - 1];
|
||||
particles.pop();
|
||||
len--;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
// Update
|
||||
const p = particles[i];
|
||||
if (this._fields.length > 0) {
|
||||
for (let j = 0; j < this._fields.length; j++) {
|
||||
this._fields[j].applyTo(p.velocity, p.position, p.weight, deltaTime);
|
||||
}
|
||||
}
|
||||
p.update(deltaTime);
|
||||
}
|
||||
|
||||
this._updateVertices();
|
||||
},
|
||||
|
||||
_updateVertices: function () {
|
||||
const geometry = this.geometry;
|
||||
// If has uv animation
|
||||
const animTileX = this.spriteAnimationTileX;
|
||||
const animTileY = this.spriteAnimationTileY;
|
||||
const animRepeat = this.spriteAnimationRepeat;
|
||||
const nUvAnimFrame = animTileY * animTileX * animRepeat;
|
||||
const hasUvAnimation = nUvAnimFrame > 1;
|
||||
let positions = geometry.attributes.position.value;
|
||||
// Put particle status in normal
|
||||
let normals = geometry.attributes.normal.value;
|
||||
let uvs = geometry.attributes.texcoord0.value;
|
||||
let uvs2 = geometry.attributes.texcoord1.value;
|
||||
|
||||
const len = this._particles.length;
|
||||
if (!positions || positions.length !== len * 3) {
|
||||
// TODO Optimize
|
||||
positions = geometry.attributes.position.value = new Float32Array(len * 3);
|
||||
normals = geometry.attributes.normal.value = new Float32Array(len * 3);
|
||||
if (hasUvAnimation) {
|
||||
uvs = geometry.attributes.texcoord0.value = new Float32Array(len * 2);
|
||||
uvs2 = geometry.attributes.texcoord1.value = new Float32Array(len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
const invAnimTileX = 1 / animTileX;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const particle = this._particles[i];
|
||||
const offset = i * 3;
|
||||
for (let j = 0; j < 3; j++) {
|
||||
positions[offset + j] = particle.position.array[j];
|
||||
normals[offset] = particle.age / particle.life;
|
||||
// normals[offset + 1] = particle.rotation;
|
||||
normals[offset + 1] = 0;
|
||||
normals[offset + 2] = particle.spriteSize;
|
||||
}
|
||||
const offset2 = i * 2;
|
||||
if (hasUvAnimation) {
|
||||
// TODO
|
||||
const p = particle.age / particle.life;
|
||||
const stage = Math.round(p * (nUvAnimFrame - 1)) * animRepeat;
|
||||
const v = Math.floor(stage * invAnimTileX);
|
||||
const u = stage - v * animTileX;
|
||||
uvs[offset2] = u / animTileX;
|
||||
uvs[offset2 + 1] = 1 - v / animTileY;
|
||||
uvs2[offset2] = (u + 1) / animTileX;
|
||||
uvs2[offset2 + 1] = 1 - (v + 1) / animTileY;
|
||||
}
|
||||
}
|
||||
|
||||
geometry.dirty();
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isFinished: function () {
|
||||
return this._elapsedTime > this.duration && !this.loop;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {clay.Renderer} renderer
|
||||
*/
|
||||
dispose: function (renderer) {
|
||||
// Put all the particles back
|
||||
for (let i = 0; i < this._particles.length; i++) {
|
||||
const p = this._particles[i];
|
||||
p.emitter.kill(p);
|
||||
}
|
||||
this.geometry.dispose(renderer);
|
||||
// TODO Dispose texture ?
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {clay.particle.ParticleRenderable}
|
||||
*/
|
||||
clone: function () {
|
||||
const particleRenderable = new ParticleRenderable({
|
||||
material: this.material
|
||||
});
|
||||
particleRenderable.loop = this.loop;
|
||||
particleRenderable.duration = this.duration;
|
||||
particleRenderable.oneshot = this.oneshot;
|
||||
particleRenderable.spriteAnimationRepeat = this.spriteAnimationRepeat;
|
||||
particleRenderable.spriteAnimationTileY = this.spriteAnimationTileY;
|
||||
particleRenderable.spriteAnimationTileX = this.spriteAnimationTileX;
|
||||
|
||||
particleRenderable.position.copy(this.position);
|
||||
particleRenderable.rotation.copy(this.rotation);
|
||||
particleRenderable.scale.copy(this.scale);
|
||||
|
||||
for (let i = 0; i < this._children.length; i++) {
|
||||
particleRenderable.add(this._children[i].clone());
|
||||
}
|
||||
return particleRenderable;
|
||||
}
|
||||
opts = opts || {};
|
||||
this.loop = optional(opts.loop, true);
|
||||
this.oneshot = optional(opts.oneshot, false);
|
||||
this.duration = optional(opts.duration, 1);
|
||||
this.spriteAnimationTileX = optional(opts.spriteAnimationTileX, 1);
|
||||
this.spriteAnimationTileY = optional(opts.spriteAnimationTileY, 1);
|
||||
this.spriteAnimationRepeat = optional(opts.spriteAnimationRepeat, 0);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Add emitter
|
||||
* @param {clay.particle.Emitter} emitter
|
||||
*/
|
||||
addEmitter(emitter: ParticleEmitter) {
|
||||
this._emitters.push(emitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove emitter
|
||||
* @param {clay.particle.Emitter} emitter
|
||||
*/
|
||||
removeEmitter(emitter: ParticleEmitter) {
|
||||
this._emitters.splice(this._emitters.indexOf(emitter), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add field
|
||||
* @param {clay.particle.Field} field
|
||||
*/
|
||||
addField(field: ParticleField) {
|
||||
this._fields.push(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove field
|
||||
* @param {clay.particle.Field} field
|
||||
*/
|
||||
removeField(field: ParticleField) {
|
||||
this._fields.splice(this._fields.indexOf(field), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the particle system.
|
||||
*/
|
||||
reset() {
|
||||
// Put all the particles back
|
||||
for (let i = 0; i < this._particles.length; i++) {
|
||||
const p = this._particles[i];
|
||||
p.emitter!.kill(p);
|
||||
}
|
||||
this._particles.length = 0;
|
||||
this._elapsedTime = 0;
|
||||
this._emitting = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} deltaTime
|
||||
*/
|
||||
updateParticles(deltaTime: number) {
|
||||
// MS => Seconds
|
||||
deltaTime /= 1000;
|
||||
this._elapsedTime += deltaTime;
|
||||
|
||||
const particles = this._particles;
|
||||
|
||||
if (this._emitting) {
|
||||
for (let i = 0; i < this._emitters.length; i++) {
|
||||
this._emitters[i].emit(particles);
|
||||
}
|
||||
if (this.oneshot) {
|
||||
this._emitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Aging
|
||||
let len = particles.length;
|
||||
for (let i = 0; i < len; ) {
|
||||
const p = particles[i];
|
||||
p.age += deltaTime;
|
||||
if (p.age >= p.life) {
|
||||
p.emitter!.kill(p);
|
||||
particles[i] = particles[len - 1];
|
||||
particles.pop();
|
||||
len--;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
// Update
|
||||
const p = particles[i];
|
||||
if (this._fields.length > 0) {
|
||||
for (let j = 0; j < this._fields.length; j++) {
|
||||
this._fields[j].applyTo(p.velocity, p.position, p.weight, deltaTime);
|
||||
}
|
||||
}
|
||||
p.update(deltaTime);
|
||||
}
|
||||
|
||||
this._updateVertices();
|
||||
}
|
||||
|
||||
_updateVertices() {
|
||||
const geometry = this.geometry;
|
||||
// If has uv animation
|
||||
const animTileX = this.spriteAnimationTileX;
|
||||
const animTileY = this.spriteAnimationTileY;
|
||||
const animRepeat = this.spriteAnimationRepeat;
|
||||
const nUvAnimFrame = animTileY * animTileX * animRepeat;
|
||||
const hasUvAnimation = nUvAnimFrame > 1;
|
||||
let positions = geometry.attributes.position.value;
|
||||
// Put particle status in normal
|
||||
let normals = geometry.attributes.normal.value!;
|
||||
let uvs = geometry.attributes.texcoord0.value!;
|
||||
let uvs2 = geometry.attributes.texcoord1.value!;
|
||||
|
||||
const len = this._particles.length;
|
||||
if (!positions || positions.length !== len * 3) {
|
||||
// TODO Optimize
|
||||
positions = geometry.attributes.position.value = new Float32Array(len * 3);
|
||||
normals = geometry.attributes.normal.value = new Float32Array(len * 3);
|
||||
if (hasUvAnimation) {
|
||||
uvs = geometry.attributes.texcoord0.value = new Float32Array(len * 2);
|
||||
uvs2 = geometry.attributes.texcoord1.value = new Float32Array(len * 2);
|
||||
}
|
||||
}
|
||||
|
||||
const invAnimTileX = 1 / animTileX;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const particle = this._particles[i];
|
||||
const offset = i * 3;
|
||||
for (let j = 0; j < 3; j++) {
|
||||
positions[offset + j] = particle.position.array[j];
|
||||
normals[offset] = particle.age / particle.life;
|
||||
// normals[offset + 1] = particle.rotation;
|
||||
normals[offset + 1] = 0;
|
||||
normals[offset + 2] = particle.spriteSize;
|
||||
}
|
||||
const offset2 = i * 2;
|
||||
if (hasUvAnimation) {
|
||||
// TODO
|
||||
const p = particle.age / particle.life;
|
||||
const stage = Math.round(p * (nUvAnimFrame - 1)) * animRepeat;
|
||||
const v = Math.floor(stage * invAnimTileX);
|
||||
const u = stage - v * animTileX;
|
||||
uvs[offset2] = u / animTileX;
|
||||
uvs[offset2 + 1] = 1 - v / animTileY;
|
||||
uvs2[offset2] = (u + 1) / animTileX;
|
||||
uvs2[offset2 + 1] = 1 - (v + 1) / animTileY;
|
||||
}
|
||||
}
|
||||
|
||||
geometry.dirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
isFinished() {
|
||||
return this._elapsedTime > this.duration && !this.loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {clay.Renderer} renderer
|
||||
*/
|
||||
dispose(renderer: Renderer) {
|
||||
// Put all the particles back
|
||||
for (let i = 0; i < this._particles.length; i++) {
|
||||
const p = this._particles[i];
|
||||
p.emitter!.kill(p);
|
||||
}
|
||||
this.geometry.dispose(renderer);
|
||||
// TODO Dispose texture ?
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {clay.particle.ParticleRenderable}
|
||||
*/
|
||||
clone() {
|
||||
const particleRenderable = new ParticleRenderable({
|
||||
material: this.material
|
||||
});
|
||||
particleRenderable.loop = this.loop;
|
||||
particleRenderable.duration = this.duration;
|
||||
particleRenderable.oneshot = this.oneshot;
|
||||
particleRenderable.spriteAnimationRepeat = this.spriteAnimationRepeat;
|
||||
particleRenderable.spriteAnimationTileY = this.spriteAnimationTileY;
|
||||
particleRenderable.spriteAnimationTileX = this.spriteAnimationTileX;
|
||||
|
||||
particleRenderable.position.copy(this.position);
|
||||
particleRenderable.rotation.copy(this.rotation);
|
||||
particleRenderable.scale.copy(this.scale);
|
||||
|
||||
for (let i = 0; i < this._children.length; i++) {
|
||||
particleRenderable.add(this._children[i].clone());
|
||||
}
|
||||
return particleRenderable;
|
||||
}
|
||||
}
|
||||
|
||||
export default ParticleRenderable;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
import calcAmbientSHLightEssl from './calcAmbientSHLight.glsl.js';
|
||||
|
||||
const uniformVec3Prefix = 'uniform vec3 ';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user