diff --git a/CHANGELOG.md b/CHANGELOG.md index a3bbce4..2c6b1b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## version 0.10.14 +- Add support for length scale for StretchedBillboard renderer +- Fix EmitSubParticleSystem behavior's bug and support unity subEmitter module +- Add sub emitter example + ## version 0.10.13 - Add support for more emitter shapes (circle, hemisphere) and shapes example diff --git a/examples/AcidBoiling.json b/examples/AcidBoiling.json new file mode 100644 index 0000000..0c3523b --- /dev/null +++ b/examples/AcidBoiling.json @@ -0,0 +1,576 @@ +{ + "metadata": { + "version": 4.6, + "type": "Object", + "generator": "Object3D.toJSON" + }, + "geometries": [ + { + "uuid": "f1725269-a9a7-440e-a21c-0876cc9fa59e", + "type": "PlaneGeometry", + "width": 1, + "height": 1, + "widthSegments": 1, + "heightSegments": 1 + } + ], + "materials": [ + { + "uuid": "6c0211a2-9a5b-41ef-b757-9cf1645f4aac", + "type": "MeshBasicMaterial", + "color": 16777215, + "map": "6122521b-0fba-4f5a-bd24-653f335f85ff", + "reflectivity": 1, + "refractionRatio": 0.98, + "transparent": true, + "depthFunc": 3, + "depthTest": true, + "depthWrite": false, + "colorWrite": true, + "stencilWrite": false, + "stencilWriteMask": 255, + "stencilFunc": 519, + "stencilRef": 0, + "stencilFuncMask": 255, + "stencilFail": 7680, + "stencilZFail": 7680, + "stencilZPass": 7680 + }, { + "uuid": "e3f6c30d-958c-4100-a055-2cfddd13cc13", + "type": "MeshBasicMaterial", + "color": 16777215, + "map": "6122521b-0fba-4f5a-bd24-653f335f85ff", + "reflectivity": 1, + "refractionRatio": 0.98, + "transparent": true, + "depthFunc": 3, + "depthTest": true, + "depthWrite": false, + "colorWrite": true, + "stencilWrite": false, + "stencilWriteMask": 255, + "stencilFunc": 519, + "stencilRef": 0, + "stencilFuncMask": 255, + "stencilFail": 7680, + "stencilZFail": 7680, + "stencilZPass": 7680 + }, { + "uuid": "4ef12ffe-c5a0-4753-b25e-c3f482810a66", + "type": "MeshBasicMaterial", + "color": 16777215, + "map": "16319922-3e46-42a0-90fb-542a5d337e24", + "reflectivity": 1, + "refractionRatio": 0.98, + "blending": 2, + "transparent": true, + "depthFunc": 3, + "depthTest": true, + "depthWrite": false, + "colorWrite": true, + "stencilWrite": false, + "stencilWriteMask": 255, + "stencilFunc": 519, + "stencilRef": 0, + "stencilFuncMask": 255, + "stencilFail": 7680, + "stencilZFail": 7680, + "stencilZPass": 7680 + } + ], + "textures": [ + { + "uuid": "6122521b-0fba-4f5a-bd24-653f335f85ff", + "name": "", + "image": "a77e0093-7673-4ed7-a046-e41485463180", + "mapping": 300, + "channel": 0, + "repeat": [1, 1], + "offset": [0, 0], + "center": [0, 0], + "rotation": 0, + "wrap": [1001, 1001], + "format": 1023, + "internalFormat": null, + "type": 1009, + "colorSpace": "", + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "generateMipmaps": true, + "premultiplyAlpha": false, + "unpackAlignment": 4 + }, { + "uuid": "16319922-3e46-42a0-90fb-542a5d337e24", + "name": "", + "image": "184b6b82-a056-4af6-8429-bc965d2f69cf", + "mapping": 300, + "channel": 0, + "repeat": [1, 1], + "offset": [0, 0], + "center": [0, 0], + "rotation": 0, + "wrap": [1001, 1001], + "format": 1023, + "internalFormat": null, + "type": 1009, + "colorSpace": "", + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "generateMipmaps": true, + "premultiplyAlpha": false, + "unpackAlignment": 4 + } + ], + "images": [ + { + "uuid": "a77e0093-7673-4ed7-a046-e41485463180", + "url": "./textures/circle.png" + }, { + "uuid": "184b6b82-a056-4af6-8429-bc965d2f69cf", + "url": "./textures/bubble_half_thick.png" + } + ], + "object": { + "uuid": "3f4abd90-548d-445e-9f04-5ebed3a67e53", + "type": "Group", + "name": "AcidBoiling", + "layers": 1, + "matrix": [ + 1, 0, 0, 0, 0, -5.3212480199960055e-8, -1.0000000532124802, 0, 0, 1.0000000532124802, -5.3212480199960055e-8, 0, + 0, 0, 0, 1 + ], + "up": [0, 1, 0], + "children": [ + { + "uuid": "f01fc129-a495-4ca8-8647-78620737b849", + "type": "Group", + "name": "SubEmitter", + "layers": 1, + "matrix": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + "up": [0, 1, 0], + "children": [ + { + "uuid": "e3770769-e787-4474-b28f-2ce43a7bc10d", + "type": "ParticleEmitter", + "name": "SubEmitterEmitter", + "layers": 1, + "matrix": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + "ps": { + "version": "2.0", + "autoDestroy": false, + "looping": true, + "prewarm": false, + "duration": 5, + "shape": { + "type": "sphere", + "radius": 0.2, + "arc": 6.283185307179586, + "thickness": 1 + }, + "startLife": { + "type": "IntervalValue", + "a": 0.1, + "b": 0.25 + }, + "startSpeed": { + "type": "IntervalValue", + "a": 1, + "b": 3 + }, + "startRotation": { + "type": "ConstantValue", + "value": 0 + }, + "startSize": { + "type": "IntervalValue", + "a": 0.04, + "b": 0.05 + }, + "startColor": { + "type": "ConstantColor", + "color": { + "r": 0.44705883, + "g": 1, + "b": 0, + "a": 1 + } + }, + "emissionOverTime": { + "type": "ConstantValue", + "value": 0 + }, + "emissionOverDistance": { + "type": "ConstantValue", + "value": 0 + }, + "emissionBursts": [ + { + "time": 0, + "count": { + "type": "IntervalValue", + "a": 2, + "b": 3 + }, + "probability": 1, + "interval": 0.01, + "cycle": 1 + } + ], + "onlyUsedByOther": true, + "instancingGeometry": "f1725269-a9a7-440e-a21c-0876cc9fa59e", + "renderOrder": 0, + "renderMode": 1, + "rendererEmitterSettings": { + "speedFactor": 0, + "lengthFactor": 2 + }, + "material": "6c0211a2-9a5b-41ef-b757-9cf1645f4aac", + "layers": 1, + "startTileIndex": { + "type": "ConstantValue", + "value": 0 + }, + "uTileCount": 1, + "vTileCount": 1, + "behaviors": [ + { + "type": "ForceOverLife", + "x": { + "type": "ConstantValue", + "value": 0 + }, + "y": { + "type": "ConstantValue", + "value": -8.82 + }, + "z": { + "type": "ConstantValue", + "value": 0 + } + }, { + "type": "SizeOverLife", + "size": { + "type": "PiecewiseBezier", + "functions": [ + { + "function": { + "p0": 1, + "p1": 1, + "p2": 0, + "p3": 0 + }, + "start": 0 + } + ] + } + } + ], + "worldSpace": true + } + } + ] + }, { + "uuid": "3b2aa3f7-a29a-44ab-8eee-163baefb5f47", + "type": "Group", + "name": "JumpingParticles", + "layers": 1, + "matrix": [ + 1, 0, 0, 0, 0, 0.9999999999999716, -2.3841855e-7, 0, 0, 2.3841855e-7, 0.9999999999999716, 0, 0, 0, 0, 1 + ], + "up": [0, 1, 0], + "children": [ + { + "uuid": "61de2746-fbf7-4485-af85-323458fcd49d", + "type": "ParticleEmitter", + "name": "JumpingParticlesEmitter", + "layers": 1, + "matrix": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + "ps": { + "version": "2.0", + "autoDestroy": false, + "looping": true, + "prewarm": false, + "duration": 1, + "shape": { + "type": "cone", + "radius": 1, + "arc": 6.283185307179586, + "thickness": 1, + "angle": 0.08726646259971647 + }, + "startLife": { + "type": "IntervalValue", + "a": 0.3, + "b": 0.6 + }, + "startSpeed": { + "type": "IntervalValue", + "a": 4, + "b": 8 + }, + "startRotation": { + "type": "ConstantValue", + "value": 0 + }, + "startSize": { + "type": "ConstantValue", + "value": 0.04 + }, + "startColor": { + "type": "ConstantColor", + "color": { + "r": 1, + "g": 1, + "b": 1, + "a": 1 + } + }, + "emissionOverTime": { + "type": "ConstantValue", + "value": 40 + }, + "emissionOverDistance": { + "type": "ConstantValue", + "value": 0 + }, + "emissionBursts": [], + "onlyUsedByOther": false, + "instancingGeometry": "f1725269-a9a7-440e-a21c-0876cc9fa59e", + "renderOrder": 0, + "renderMode": 0, + "rendererEmitterSettings": {}, + "material": "e3f6c30d-958c-4100-a055-2cfddd13cc13", + "layers": 1, + "startTileIndex": { + "type": "ConstantValue", + "value": 0 + }, + "uTileCount": 1, + "vTileCount": 1, + "behaviors": [ + { + "type": "ForceOverLife", + "x": { + "type": "ConstantValue", + "value": 0 + }, + "y": { + "type": "ConstantValue", + "value": -16.66 + }, + "z": { + "type": "ConstantValue", + "value": 0 + } + }, { + "type": "SizeOverLife", + "size": { + "type": "PiecewiseBezier", + "functions": [ + { + "function": { + "p0": 1, + "p1": 1, + "p2": 1, + "p3": 1 + }, + "start": 0 + }, { + "function": { + "p0": 1, + "p1": 0.6666666649644146, + "p2": 0.3333333350355853, + "p3": 0 + }, + "start": 0.47603834 + } + ] + } + }, { + "type": "RotationOverLife", + "angularVelocity": { + "type": "IntervalValue", + "a": 7.8539815, + "b": 18.849556 + }, + "dynamic": true + }, { + "type": "ColorOverLife", + "color": { + "type": "Gradient", + "color": { + "type": "CLinearFunction", + "subType": "Color", + "keys": [ + { + "value": { + "r": 1, + "g": 1, + "b": 1 + }, + "pos": 0.21809719996948196 + }, { + "value": { + "r": 0, + "g": 1, + "b": 0.17254902 + }, + "pos": 0.835187304493782 + } + ] + }, + "alpha": { + "type": "CLinearFunction", + "subType": "Number", + "keys": [ + { + "value": 0, + "pos": 0 + }, { + "value": 1, + "pos": 0.2235294117647059 + }, { + "value": 1, + "pos": 0.8348668650339514 + }, { + "value": 0, + "pos": 1 + } + ] + } + } + }, { + "type": "SpeedOverLife", + "speed": { + "type": "ConstantValue", + "value": 1 + } + }, { + "type": "LimitSpeedOverLife", + "speed": { + "type": "ConstantValue", + "value": 0.6 + }, + "dampen": 0.2 + } + ], + "worldSpace": true + } + } + ] + }, { + "uuid": "5cfbf971-dfa8-45f8-8667-d86adab808ff", + "type": "ParticleEmitter", + "name": "AcidBoilingEmitter", + "layers": 1, + "matrix": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + "ps": { + "version": "2.0", + "autoDestroy": false, + "looping": true, + "prewarm": false, + "duration": 5, + "shape": { + "type": "circle", + "radius": 1.5, + "arc": 6.283185307179586, + "thickness": 1 + }, + "startLife": { + "type": "IntervalValue", + "a": 0.3, + "b": 0.5 + }, + "startSpeed": { + "type": "ConstantValue", + "value": 0 + }, + "startRotation": { + "type": "ConstantValue", + "value": 0 + }, + "startSize": { + "type": "IntervalValue", + "a": 0.4, + "b": 0.6 + }, + "startColor": { + "type": "ConstantColor", + "color": { + "r": 0, + "g": 1, + "b": 0.007843138, + "a": 1 + } + }, + "emissionOverTime": { + "type": "ConstantValue", + "value": 20 + }, + "emissionOverDistance": { + "type": "ConstantValue", + "value": 0 + }, + "emissionBursts": [], + "onlyUsedByOther": false, + "instancingGeometry": "f1725269-a9a7-440e-a21c-0876cc9fa59e", + "renderOrder": 0, + "renderMode": 5, + "rendererEmitterSettings": {}, + "material": "4ef12ffe-c5a0-4753-b25e-c3f482810a66", + "layers": 1, + "startTileIndex": { + "type": "ConstantValue", + "value": 0 + }, + "uTileCount": 1, + "vTileCount": 1, + "behaviors": [ + { + "type": "ForceOverLife", + "x": { + "type": "ConstantValue", + "value": 0 + }, + "y": { + "type": "ConstantValue", + "value": 0 + }, + "z": { + "type": "ConstantValue", + "value": 0 + } + }, { + "type": "SizeOverLife", + "size": { + "type": "PiecewiseBezier", + "functions": [ + { + "function": { + "p0": 0, + "p1": 0, + "p2": 0.33333333333333337, + "p3": 1 + }, + "start": 0 + } + ] + } + }, { + "type": "EmitSubParticleSystem", + "subParticleSystem": "e3770769-e787-4474-b28f-2ce43a7bc10d", + "useVelocityAsBasis": false, + "mode": 0, + "emitProbability": 1 + } + ], + "worldSpace": true + } + } + ] + } +} diff --git a/examples/index.html b/examples/index.html index 7622507..8181cf8 100644 --- a/examples/index.html +++ b/examples/index.html @@ -89,6 +89,7 @@ import {CustomPluginDemo} from "./customPluginDemo.js"; import {SequencerDemo} from "./sequencerDemo.js"; import {MeshMaterialDemo} from "./meshMaterialDemo.js"; + import {SubEmitterDemo} from "./subEmitterDemo.js"; import {AlphaTestDemo} from "./alphaTestDemo.js"; import {BillboardDemo} from "./billboardDemo.js"; import {NodeBasedVFXDemo} from "./nodeBasedVFXDemo.js"; @@ -155,7 +156,7 @@ let scene; let demo; - let demos = [MuzzleFlashDemo, ExplosionDemo, EmitterShapeDemo, TrailDemo, SequencerDemo, MeshMaterialDemo, TurbulenceDemo, AlphaTestDemo, CustomPluginDemo, BillboardDemo, NodeBasedVFXDemo]; + let demos = [MuzzleFlashDemo, ExplosionDemo, EmitterShapeDemo, TrailDemo, SequencerDemo, MeshMaterialDemo, SubEmitterDemo, TurbulenceDemo, AlphaTestDemo, CustomPluginDemo, BillboardDemo, NodeBasedVFXDemo]; let demoIndex = 0; function init() { @@ -174,7 +175,7 @@ clock = new Clock(); camera = new PerspectiveCamera(60, width / height, 1, 1000); - camera.position.set(0, 16, 8); + camera.position.set(0, 10, 10); controls = new OrbitControls(camera, renderer.domElement); //controls.target = new Vector3(0, 0, 10); diff --git a/examples/subEmitterDemo.js b/examples/subEmitterDemo.js new file mode 100644 index 0000000..d3dad1f --- /dev/null +++ b/examples/subEmitterDemo.js @@ -0,0 +1,28 @@ +import {BatchedParticleRenderer, QuarksLoader} from 'three.quarks'; +import {Demo} from './demo.js'; + +export class SubEmitterDemo extends Demo { + name = 'Sub Emitter'; + //refreshTime = 0; + initScene() { + super.initScene(); + + this.batchRenderer = new BatchedParticleRenderer(); + this.scene.add(this.batchRenderer); + + new QuarksLoader().load('AcidBoiling.json', (obj) => { + obj.traverse((child) => { + if (child.type === 'ParticleEmitter') { + this.batchRenderer.addSystem(child.system); + } + }); + if (obj.type === 'ParticleEmitter') { + this.batchRenderer.addSystem(obj.system); + } + this.scene.add(obj); + this.groups.push(obj); + }); + + return this.scene; + } +} diff --git a/examples/textures/bubble_half_thick.png b/examples/textures/bubble_half_thick.png new file mode 100644 index 0000000..fef3abd Binary files /dev/null and b/examples/textures/bubble_half_thick.png differ diff --git a/examples/textures/circle.png b/examples/textures/circle.png new file mode 100644 index 0000000..db571f5 Binary files /dev/null and b/examples/textures/circle.png differ diff --git a/package.json b/package.json index 8c80f1d..3b830f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "three.quarks", - "version": "0.10.13", + "version": "0.10.14", "description": "A General-Purpose Particle System for three.js", "type": "module", "types": "./dist/types/index.d.ts", diff --git a/src/BatchedRenderer.ts b/src/BatchedRenderer.ts index a7734f3..efe7c12 100644 --- a/src/BatchedRenderer.ts +++ b/src/BatchedRenderer.ts @@ -1,9 +1,10 @@ import {VFXBatch, RenderMode, StoredBatchSettings} from './VFXBatch'; -import {BufferGeometry, Layers, Material, Object3D} from 'three'; +import { BufferGeometry, Layers, Material, Object3D, Vector3 } from "three"; import {SpriteBatch} from './SpriteBatch'; import {TrailBatch} from './TrailBatch'; import {ParticleEmitter} from './ParticleEmitter'; import {IParticle, Particle} from './Particle'; +import { FunctionValueGenerator, ValueGenerator } from "./functions"; export interface VFXBatchSettings { // 5 component x,y,z,u,v @@ -18,8 +19,38 @@ export interface VFXBatchSettings { export interface SerializationOptions { useUrlForImage?: boolean; } -export interface IParticleSystem { + + +export type RendererEmitterSettings = TrailSettings | MeshSettings | BillBoardSettings | StretchedBillBoardSettings; + +export interface StretchedBillBoardSettings { + /** + * how stretched the particle is in the direction of the camera based on the speed of the particle. + * @type {number} + */ speedFactor: number; + /** + * how stretched the particle is in the direction of the camera based on the size of the particle. + * @type {number} + */ + lengthFactor: number; +} + +export interface BillBoardSettings {} + +export interface TrailSettings { + startLength: ValueGenerator | FunctionValueGenerator; + followLocalOrigin: boolean; +} + +export interface MeshSettings { + rotationAxis?: Vector3; + startRotationX: ValueGenerator | FunctionValueGenerator; + startRotationY: ValueGenerator | FunctionValueGenerator; + startRotationZ: ValueGenerator | FunctionValueGenerator; +} + +export interface IParticleSystem { worldSpace: boolean; particleNum: number; duration: number; @@ -28,6 +59,7 @@ export interface IParticleSystem { emitter: ParticleEmitter; _renderer?: BatchedRenderer; instancingGeometry: BufferGeometry; + rendererEmitterSettings: RendererEmitterSettings; getRendererSettings(): VFXBatchSettings; diff --git a/src/ParticleSystem.ts b/src/ParticleSystem.ts index 3a80f5b..e300989 100644 --- a/src/ParticleSystem.ts +++ b/src/ParticleSystem.ts @@ -1,8 +1,21 @@ -import {FunctionValueGenerator, ValueGenerator, ValueGeneratorFromJSON} from './functions'; +import { + AxisAngleGenerator, + ColorGenerator, + ColorGeneratorFromJSON, + ConstantColor, + ConstantValue, + FunctionColorGenerator, + FunctionJSON, + FunctionValueGenerator, + GeneratorFromJSON, + MemorizedFunctionColorGenerator, + ValueGenerator, + ValueGeneratorFromJSON, +} from './functions'; import {Behavior, BehaviorFromJSON} from './behaviors'; import {Particle, SpriteParticle, TrailParticle} from './Particle'; import {MetaData, ParticleEmitter} from './ParticleEmitter'; -import {EmitterFromJSON, EmitterShape, ShapeJSON} from './shape'; +import {EmitterFromJSON, EmitterShape, ShapeJSON, SphereEmitter} from './shape'; import { AdditiveBlending, BaseEvent, @@ -21,21 +34,18 @@ import { Vector3, Vector4, } from 'three'; -import {SphereEmitter} from './shape'; -import { - AxisAngleGenerator, - ColorGenerator, - ColorGeneratorFromJSON, - ConstantColor, - ConstantValue, - FunctionColorGenerator, - FunctionJSON, - GeneratorFromJSON, - MemorizedFunctionColorGenerator, -} from './functions'; import {RenderMode} from './VFXBatch'; -import {BatchedRenderer, SerializationOptions, VFXBatchSettings} from './BatchedRenderer'; +import { + BatchedRenderer, + IParticleSystem, + RendererEmitterSettings, + SerializationOptions, + StretchedBillBoardSettings, + TrailSettings, + VFXBatchSettings, +} from './BatchedRenderer'; import {RotationGenerator} from './functions/RotationGenerator'; + export interface BurstParameters { time: number; count: ValueGenerator | FunctionValueGenerator; @@ -82,7 +92,7 @@ export interface ParticleSystemParameters { instancingGeometry?: BufferGeometry; renderMode?: RenderMode; - rendererEmitterSettings?: TrailSettings | MeshSettings | BillBoardSettings; + rendererEmitterSettings?: RendererEmitterSettings; speedFactor?: number; material: Material; layers?: Layers; @@ -113,10 +123,7 @@ export interface ParticleSystemJSONParameters { emissionBursts?: Array; onlyUsedByOther: boolean; - rendererEmitterSettings: { - startLength?: FunctionJSON; - followLocalOrigin?: boolean; - }; + rendererEmitterSettings: RendererEmitterSettings; instancingGeometry?: any; renderMode: number; @@ -141,20 +148,6 @@ export interface JsonMetaData { geometries: {[uuid: string]: BufferGeometry}; } -export interface BillBoardSettings {} - -export interface TrailSettings { - startLength: ValueGenerator | FunctionValueGenerator; - followLocalOrigin: boolean; -} - -export interface MeshSettings { - rotationAxis?: Vector3; - startRotationX: ValueGenerator | FunctionValueGenerator; - startRotationY: ValueGenerator | FunctionValueGenerator; - startRotationZ: ValueGenerator | FunctionValueGenerator; -} - const DEFAULT_GEOMETRY = new PlaneGeometry(1, 1, 1, 1); export interface EmissionState { @@ -169,7 +162,7 @@ export interface EmissionState { * * @class */ -export class ParticleSystem { +export class ParticleSystem implements IParticleSystem { /** * Determines whether the ParticleSystem should be automatically disposed when it finishes emitting particles. * @@ -243,9 +236,9 @@ export class ParticleSystem { /** * The renderer emitter settings for the ParticleSystem. * - * @type {TrailSettings | MeshSettings | BillBoardSettings} + * @type {TrailSettings | MeshSettings | BillBoardSettings | StretchedBillBoardSettings} */ - rendererEmitterSettings: TrailSettings | MeshSettings | BillBoardSettings; + rendererEmitterSettings: RendererEmitterSettings; /** * The value generator or function value generator for the emission rate of particles over time. @@ -282,14 +275,6 @@ export class ParticleSystem { */ worldSpace: boolean; - /** - * In render mode StretchedBillBoard - * how stretched the particle is in the direction of the camera based on the speed of the particle. - * - * @type {number} - */ - speedFactor: number; - /** * The number of particles in the ParticleSystem. * @@ -446,10 +431,15 @@ export class ParticleSystem { }; this.startRotation = new AxisAngleGenerator(new Vector3(0, 1, 0), new ConstantValue(0)); break; + case RenderMode.StretchedBillBoard: + this.rendererEmitterSettings = {speedFactor: 0, lengthFactor: 2}; + if (this.rendererSettings.renderMode === RenderMode.Mesh) { + this.startRotation = new ConstantValue(0); + } + break; case RenderMode.BillBoard: case RenderMode.VerticalBillBoard: case RenderMode.HorizontalBillBoard: - case RenderMode.StretchedBillBoard: this.rendererEmitterSettings = {}; if (this.rendererSettings.renderMode === RenderMode.Mesh) { this.startRotation = new ConstantValue(0); @@ -499,8 +489,15 @@ export class ParticleSystem { this.emitterShape = parameters.shape ?? new SphereEmitter(); this.behaviors = parameters.behaviors ?? new Array(); this.worldSpace = parameters.worldSpace ?? false; - this.speedFactor = parameters.speedFactor ?? 0; this.rendererEmitterSettings = parameters.rendererEmitterSettings ?? {}; + if (parameters.renderMode === RenderMode.StretchedBillBoard) { + const stretchedBillboardSettings = this.rendererEmitterSettings as StretchedBillBoardSettings; + if (parameters.speedFactor !== undefined) { + stretchedBillboardSettings.speedFactor = parameters.speedFactor; + } + stretchedBillboardSettings.speedFactor = stretchedBillboardSettings.speedFactor ?? 0; + stretchedBillboardSettings.lengthFactor = stretchedBillboardSettings.speedFactor ?? 0; + } this.rendererSettings = { instancingGeometry: parameters.instancingGeometry ?? DEFAULT_GEOMETRY, @@ -660,9 +657,9 @@ export class ParticleSystem { private update(delta: number) { /*if (this.firstTimeUpdate) { - this.renderer.addSystem(this); - this.firstTimeUpdate = false; - }*/ + this.renderer.addSystem(this); + this.firstTimeUpdate = false; + }*/ if (this.paused) return; @@ -724,7 +721,8 @@ export class ParticleSystem { this.particles[i].position.applyMatrix4(this.emitter.matrixWorld); } } else { - this.particles[i].position.addScaledVector(this.particles[i].velocity, delta); + const speedModifier = (this.particles[i] as any).speedModifier ?? 1; + this.particles[i].position.addScaledVector(this.particles[i].velocity, delta * speedModifier); } this.particles[i].age += delta; } @@ -838,6 +836,11 @@ export class ParticleSystem { } else if (this.renderMode === RenderMode.Mesh) { rendererSettingsJSON = {}; /*;*/ + } else if (this.renderMode === RenderMode.StretchedBillBoard) { + rendererSettingsJSON = { + speedFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).speedFactor, + lengthFactor: (this.rendererEmitterSettings as StretchedBillBoardSettings).lengthFactor, + }; } else { rendererSettingsJSON = {}; } @@ -873,7 +876,7 @@ export class ParticleSystem { renderOrder: this.renderOrder, renderMode: this.renderMode, rendererEmitterSettings: rendererSettingsJSON, - speedFactor: this.renderMode === RenderMode.StretchedBillBoard ? this.speedFactor : 0, + //speedFactor: this.renderMode === RenderMode.StretchedBillBoard ? this.speedFactor : 0, //texture: this.texture.uuid, material: this.rendererSettings.material.uuid, layers: this.layers.mask, @@ -899,15 +902,21 @@ export class ParticleSystem { const shape = EmitterFromJSON(json.shape, meta); let rendererEmitterSettings; if (json.renderMode === RenderMode.Trail) { + let trailSettings = json.rendererEmitterSettings as TrailSettings; rendererEmitterSettings = { startLength: - json.rendererEmitterSettings.startLength != undefined - ? ValueGeneratorFromJSON(json.rendererEmitterSettings.startLength) + trailSettings.startLength != undefined + ? ValueGeneratorFromJSON(trailSettings.startLength) : new ConstantValue(30), - followLocalOrigin: json.rendererEmitterSettings.followLocalOrigin, + followLocalOrigin: trailSettings.followLocalOrigin, }; } else if (json.renderMode === RenderMode.Mesh) { rendererEmitterSettings = {}; + } else if (json.renderMode === RenderMode.StretchedBillBoard) { + rendererEmitterSettings = json.rendererEmitterSettings; + if (json.speedFactor != undefined) { + (rendererEmitterSettings as StretchedBillBoardSettings).speedFactor = json.speedFactor; + } } else { rendererEmitterSettings = {}; } @@ -947,7 +956,6 @@ export class ParticleSystem { renderMode: json.renderMode, rendererEmitterSettings: rendererEmitterSettings, renderOrder: json.renderOrder, - speedFactor: json.speedFactor, layers: layers, material: json.material ? meta.materials[json.material] @@ -1039,7 +1047,6 @@ export class ParticleSystem { renderMode: this.renderMode, renderOrder: this.renderOrder, rendererEmitterSettings: rendererEmitterSettings, - speedFactor: this.speedFactor, material: this.rendererSettings.material, startTileIndex: this.startTileIndex, uTileCount: this.uTileCount, diff --git a/src/SpriteBatch.ts b/src/SpriteBatch.ts index fe7a3c0..9824da4 100644 --- a/src/SpriteBatch.ts +++ b/src/SpriteBatch.ts @@ -23,7 +23,7 @@ import local_particle_physics_vert from './shaders/local_particle_physics_vert.g import stretched_bb_particle_vert from './shaders/stretched_bb_particle_vert.glsl'; import {VFXBatch, RenderMode} from './VFXBatch'; import {getMaterialUVChannelName} from './util/ThreeUtil'; -import {VFXBatchSettings} from './BatchedRenderer'; +import {StretchedBillBoardSettings, VFXBatchSettings} from './BatchedRenderer'; const UP = new Vector3(0, 0, 1); @@ -74,7 +74,7 @@ export class SpriteBatch extends VFXBatch { this.uvTileBuffer.setUsage(DynamicDrawUsage); this.geometry.setAttribute('uvTile', this.uvTileBuffer); if (this.settings.renderMode === RenderMode.StretchedBillBoard) { - this.velocityBuffer = new InstancedBufferAttribute(new Float32Array(this.maxParticles * 3), 3); + this.velocityBuffer = new InstancedBufferAttribute(new Float32Array(this.maxParticles * 4), 4); this.velocityBuffer.setUsage(DynamicDrawUsage); this.geometry.setAttribute('velocity', this.velocityBuffer); } @@ -172,7 +172,10 @@ export class SpriteBatch extends VFXBatch { uniforms['tileCount'] = new Uniform(new Vector2(uTileCount, vTileCount)); } - if ((this.settings.material as any).defines && (this.settings.material as any).defines['USE_COLOR_AS_ALPHA'] !== undefined) { + if ( + (this.settings.material as any).defines && + (this.settings.material as any).defines['USE_COLOR_AS_ALPHA'] !== undefined + ) { defines['USE_COLOR_AS_ALPHA'] = ''; } @@ -338,7 +341,9 @@ export class SpriteBatch extends VFXBatch { this.uvTileBuffer.setX(index, particle.uvTile); if (this.settings.renderMode === RenderMode.StretchedBillBoard && this.velocityBuffer) { - const speedFactor = system.speedFactor; + let speedFactor = (system.rendererEmitterSettings as StretchedBillBoardSettings).speedFactor; + if (speedFactor === 0) speedFactor = 0.001; // TODO: use an another buffer + const lengthFactor = (system.rendererEmitterSettings as StretchedBillBoardSettings).lengthFactor; let vec; if (system.worldSpace) { vec = particle.velocity; @@ -351,7 +356,13 @@ export class SpriteBatch extends VFXBatch { vec.copy(particle.velocity).applyMatrix3(this.rotationMat_); } } - this.velocityBuffer.setXYZ(index, vec.x * speedFactor, vec.y * speedFactor, vec.z * speedFactor); + this.velocityBuffer.setXYZW( + index, + vec.x * speedFactor, + vec.y * speedFactor, + vec.z * speedFactor, + lengthFactor + ); } } }); @@ -371,7 +382,7 @@ export class SpriteBatch extends VFXBatch { this.uvTileBuffer.needsUpdate = true; if (this.settings.renderMode === RenderMode.StretchedBillBoard && this.velocityBuffer) { - this.velocityBuffer.updateRange.count = index * 3; + this.velocityBuffer.updateRange.count = index * 4; this.velocityBuffer.needsUpdate = true; } diff --git a/src/behaviors/Behavior.ts b/src/behaviors/Behavior.ts index 858620d..3a8bc88 100644 --- a/src/behaviors/Behavior.ts +++ b/src/behaviors/Behavior.ts @@ -184,6 +184,8 @@ export const BehaviorTypes: {[key: string]: BehaviorPlugin} = { ['particleSystem', 'self'], ['useVelocityAsBasis', 'boolean'], ['subParticleSystem', 'particleSystem'], + ['mode', 'number'], + ['emitProbability', 'number'], ], loadJSON: EmitSubParticleSystem.fromJSON, }, diff --git a/src/behaviors/EmitSubParticleSystem.ts b/src/behaviors/EmitSubParticleSystem.ts index f1c29f8..70acd51 100644 --- a/src/behaviors/EmitSubParticleSystem.ts +++ b/src/behaviors/EmitSubParticleSystem.ts @@ -7,6 +7,16 @@ import {ParticleEmitter} from '../ParticleEmitter'; const VECTOR_ONE = new Vector3(1, 1, 1); const VECTOR_Z = new Vector3(0, 0, 1); +export enum SubParticleEmitMode { + Death, + Birth, + Frame, +} + +interface SubEmissionState extends EmissionState { + matrix: Matrix4; +} + export class EmitSubParticleSystem implements Behavior { type = 'EmitSubParticleSystem'; @@ -15,10 +25,14 @@ export class EmitSubParticleSystem implements Behavior { private v_ = new Vector3(); private v2_ = new Vector3(); + private subEmissions = new Array(); + constructor( private particleSystem: ParticleSystem, public useVelocityAsBasis: boolean, - public subParticleSystem?: ParticleEmitter + public subParticleSystem: ParticleEmitter | undefined, + public mode: SubParticleEmitMode = SubParticleEmitMode.Frame, + public emitProbability: number = 1 ) { if (this.subParticleSystem && this.subParticleSystem.system) { (this.subParticleSystem.system as ParticleSystem).onlyUsedByOther = true; @@ -26,18 +40,31 @@ export class EmitSubParticleSystem implements Behavior { } initialize(particle: Particle): void { - particle.emissionState = { + /*particle.emissionState = { burstIndex: 0, burstWaveIndex: 0, time: 0, waitEmiting: 0, matrix: new Matrix4(), - } as EmissionState; + } as EmissionState;*/ } update(particle: Particle, delta: number): void { - if (!this.subParticleSystem || !particle.emissionState) return; - const m = (particle.emissionState as any).matrix; + if (this.mode === SubParticleEmitMode.Frame) { + this.emit(particle, delta); + } else if (this.mode === SubParticleEmitMode.Birth && particle.age === 0) { + this.emit(particle, delta); + } else if (this.mode === SubParticleEmitMode.Death && particle.age + delta >= particle.life) { + this.emit(particle, delta); + } + } + + private emit(particle: Particle, delta: number) { + if (!this.subParticleSystem) return; + if (Math.random() > this.emitProbability) { + return; + } + const m = new Matrix4(); let rotation; if (particle.rotation === undefined || this.useVelocityAsBasis) { if ( @@ -99,25 +126,62 @@ export class EmitSubParticleSystem implements Behavior { if (!this.particleSystem.worldSpace) { m.multiplyMatrices(this.particleSystem.emitter.matrixWorld, m); } - (this.subParticleSystem.system as ParticleSystem).emit(delta, particle.emissionState, m); + this.subEmissions.push({ + burstIndex: 0, + burstWaveIndex: 0, + time: 0, + waitEmiting: 0, + matrix: m, + }); + //(this.subParticleSystem.system as ParticleSystem).emit(delta, particle.emissionState, m); } - frameUpdate(delta: number): void {} + frameUpdate(delta: number): void { + if (!this.subParticleSystem) return; + console.log(this.subEmissions.length, this.subParticleSystem.system.particleNum); + for (let i = 0; i < this.subEmissions.length; i++) { + if (this.subEmissions[i].time >= (this.subParticleSystem!.system as ParticleSystem).duration) { + this.subEmissions[i] = this.subEmissions[this.subEmissions.length - 1]; + this.subEmissions.length = this.subEmissions.length - 1; + i--; + } else { + (this.subParticleSystem.system as ParticleSystem).emit( + delta, + this.subEmissions[i], + this.subEmissions[i].matrix + ); + } + } + } toJSON(): any { return { type: this.type, subParticleSystem: this.subParticleSystem ? this.subParticleSystem.uuid : '', useVelocityAsBasis: this.useVelocityAsBasis, + mode: this.mode, + emitProbability: this.emitProbability, }; } static fromJSON(json: any, particleSystem: ParticleSystem): Behavior { - return new EmitSubParticleSystem(particleSystem, json.useVelocityAsBasis, json.subParticleSystem); + return new EmitSubParticleSystem( + particleSystem, + json.useVelocityAsBasis, + json.subParticleSystem, + json.mode, + json.emitProbability + ); } clone(): Behavior { - return new EmitSubParticleSystem(this.particleSystem, this.useVelocityAsBasis, this.subParticleSystem); + return new EmitSubParticleSystem( + this.particleSystem, + this.useVelocityAsBasis, + this.subParticleSystem, + this.mode, + this.emitProbability + ); } reset(): void {} } diff --git a/src/behaviors/SpeedOverLife.ts b/src/behaviors/SpeedOverLife.ts index 8531c9f..503e7a6 100644 --- a/src/behaviors/SpeedOverLife.ts +++ b/src/behaviors/SpeedOverLife.ts @@ -12,7 +12,7 @@ export class SpeedOverLife implements Behavior { } update(particle: Particle): void { - particle.velocity.normalize().multiplyScalar(particle.startSpeed * this.speed.genValue(particle.age / particle.life)); + (particle as any).speedModifier = this.speed.genValue(particle.age / particle.life); } toJSON(): any { return { diff --git a/src/nodes/NodeVFX.ts b/src/nodes/NodeVFX.ts index c727fe8..56d0bd6 100644 --- a/src/nodes/NodeVFX.ts +++ b/src/nodes/NodeVFX.ts @@ -18,7 +18,7 @@ import {BatchedRenderer, IParticleSystem, SerializationOptions, VFXBatchSettings import {NodeGraph} from './NodeGraph'; import {Interpreter} from './Interpreter'; import {ExecutionContext} from './NodeType'; -import {BillBoardSettings, MeshSettings, TrailSettings} from '../ParticleSystem'; +import {BillBoardSettings, MeshSettings, TrailSettings} from '../BatchedRenderer'; const UP = new Vector3(0, 0, 1); const tempQ = new Quaternion(); diff --git a/src/shaders/stretched_bb_particle_vert.glsl.ts b/src/shaders/stretched_bb_particle_vert.glsl.ts index 6b9a708..5e6623f 100644 --- a/src/shaders/stretched_bb_particle_vert.glsl.ts +++ b/src/shaders/stretched_bb_particle_vert.glsl.ts @@ -10,7 +10,7 @@ export default /* glsl */ ` attribute vec3 offset; attribute float rotation; attribute float size; -attribute vec3 velocity; +attribute vec4 velocity; attribute float uvTile; #ifdef UV_TILE @@ -24,10 +24,13 @@ void main() { ${uv_vertex_tile} vec4 mvPosition = modelViewMatrix * vec4( offset, 1.0 ); - vec3 viewVelocity = normalMatrix * velocity; + vec3 viewVelocity = normalMatrix * velocity.xyz; vec3 scaledPos = vec3(position.xy * size, position.z); - mvPosition.xyz += scaledPos + dot(scaledPos, viewVelocity) * viewVelocity / length(viewVelocity) / size * speedFactor; + float vlength = length(viewVelocity); + float lengthFactor = velocity.w; + vec3 projVelocity = dot(scaledPos, viewVelocity) * viewVelocity / vlength; + mvPosition.xyz += scaledPos + projVelocity * (speedFactor / size + lengthFactor / vlength); vColor = color;