Skip to content

Commit

Permalink
Merge pull request Hubs-Foundation#5926 from mozilla/bitecs-particle-…
Browse files Browse the repository at this point in the history
…emitter

Bitecs particle emitter
  • Loading branch information
keianhzo authored Feb 7, 2023
2 parents c8bd5da + 72b0488 commit acfc970
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,6 @@ export const VideoTextureTarget = defineComponent({
});
export const SimpleWater = defineComponent();
export const Mirror = defineComponent();
export const ParticleEmitterTag = defineComponent({
src: Types.ui32
});
55 changes: 55 additions & 0 deletions src/bit-systems/particle-emitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { defineQuery, enterQuery, exitQuery } from "bitecs";
import { ParticleEmitter } from "lib-hubs/packages/three-particle-emitter/lib/esm/index";
import { Texture } from "three";

import { HubsWorld } from "../app";
import { ParticleEmitterTag } from "../bit-components";
import { JobRunner } from "../utils/coroutine-utils";
import { proxiedUrlFor } from "../utils/media-url-utils";
import { resolveUrl, textureLoader } from "../utils/media-utils";

function* setTexture(world: HubsWorld, eid: number) {
let src = APP.sid2str.get(ParticleEmitterTag.src[eid]);

const result: URL = yield resolveUrl(src);
let canonicalUrl = result.origin;

// handle protocol relative urls
if (canonicalUrl.startsWith("//")) {
canonicalUrl = location.protocol + canonicalUrl;
}

// todo: we don't need to proxy for many things if the canonical URL has permissive CORS headers
src = proxiedUrlFor(canonicalUrl);

const texture: Texture = yield textureLoader.loadAsync(src);

const particleEmitter: ParticleEmitter = world.eid2obj.get(eid)! as ParticleEmitter;
particleEmitter.material.uniforms.map.value = texture;
particleEmitter.visible = true;

particleEmitter.updateParticles();
}

const particleEmitterQuery = defineQuery([ParticleEmitterTag]);
const particleEmitterEnterQuery = enterQuery(particleEmitterQuery);
const particleEmitterExitQuery = exitQuery(particleEmitterQuery);

const jobs = new JobRunner();
export function particleEmitterSystem(world: HubsWorld) {
particleEmitterEnterQuery(world).forEach(eid => {
jobs.add(eid, () => setTexture(world, eid));
});
particleEmitterExitQuery(world).forEach(function (eid) {
// Object3D resources release happens in remove-object3D-system
jobs.stop(eid);
});
particleEmitterQuery(world).forEach(eid => {
jobs.tick();

const particleEmitter: ParticleEmitter = world.eid2obj.get(eid)! as ParticleEmitter;
if (particleEmitter.visible) {
particleEmitter.update(world.time.delta / 1000);
}
});
}
5 changes: 5 additions & 0 deletions src/components/particle-emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { textureLoader } from "../utils/media-utils";
import { resolveUrl } from "../utils/media-utils";
import { proxiedUrlFor } from "../utils/media-url-utils";
import defaultSrcImage from "../assets/images/warning_icon.png";
import { disposeNode } from "../utils/three-utils";

const defaultSrcUrl = new URL(defaultSrcImage, window.location.href).href;

Expand Down Expand Up @@ -69,6 +70,10 @@ AFRAME.registerComponent("particle-emitter", {
this.updateParticles = true;
},

remove() {
disposeNode(this.particleEmitter);
},

update(prevData) {
const data = this.data;
const particleEmitter = this.particleEmitter;
Expand Down
88 changes: 88 additions & 0 deletions src/inflators/particle-emitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { addComponent } from "bitecs";
import { addObject3DComponent } from "../utils/jsx-entity";
import { ParticleEmitterTag } from "../bit-components";
import { HubsWorld } from "../app";
import defaultSrcImage from "../assets/images/warning_icon.png";
import { ParticleEmitter } from "lib-hubs/packages/three-particle-emitter/lib/esm/index";

const defaultSrcUrl = new URL(defaultSrcImage, window.location.href).href;

export type ParticleEmitterParams = {
src: string;
startColor: string;
middleColor: string;
endColor: string;
startOpacity: number;
middleOpacity: number;
endOpacity: number;
colorCurve: string;
sizeCurve: string;
startSize: number;
endSize: number;
sizeRandomness: number;
ageRandomness: number;
lifetime: number;
lifetimeRandomness: number;
particleCount: number;
startVelocity: [number, number];
endVelocity: [number, number, number];
velocityCurve: string;
angularVelocity: number;
};

const DEFAULTS = {
src: defaultSrcImage,
startColor: "#ffffff",
middleColor: "#ffffff",
endColor: "#ffffff",
startOpacity: 1,
middleOpacity: 1,
endOpacity: 1,
colorCurve: "linear",
sizeCurve: "linear",
startSize: 0.25,
endSize: 0.25,
sizeRandomness: 0,
ageRandomness: 10,
lifetime: 5,
lifetimeRandomness: 5,
particleCount: 100,
startVelocity: [0, 0, 0.5],
endVelocity: [0, 0, 0],
velocityCurve: "linear",
angularVelocity: 0
};

export function inflateParticleEmitter(world: HubsWorld, eid: number, params: ParticleEmitterParams) {
params = Object.assign({}, DEFAULTS, params);

const particleEmitter = new ParticleEmitter(null);
particleEmitter.visible = false;

addObject3DComponent(world, eid, particleEmitter);
addComponent(world, ParticleEmitterTag, eid);

ParticleEmitterTag.src[eid] = APP.getSid(params.src || defaultSrcUrl);

particleEmitter.startColor.set(params.startColor);
particleEmitter.middleColor.set(params.middleColor);
particleEmitter.endColor.set(params.endColor);
particleEmitter.startOpacity = params.startOpacity;
particleEmitter.middleOpacity = params.middleOpacity;
particleEmitter.endOpacity = params.endOpacity;
particleEmitter.colorCurve = params.colorCurve;
particleEmitter.sizeCurve = params.sizeCurve;
particleEmitter.startSize = params.startSize;
particleEmitter.endSize = params.endSize;
particleEmitter.sizeRandomness = params.sizeRandomness;
particleEmitter.ageRandomness = params.ageRandomness;
particleEmitter.lifetime = params.lifetime;
particleEmitter.lifetimeRandomness = params.lifetimeRandomness;
particleEmitter.particleCount = params.particleCount;
particleEmitter.startVelocity.fromArray(params.startVelocity);
particleEmitter.endVelocity.fromArray(params.endVelocity);
particleEmitter.velocityCurve = params.velocityCurve;
particleEmitter.angularVelocity = params.angularVelocity;

return eid;
}
5 changes: 3 additions & 2 deletions src/systems/hubs-systems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import { videoTextureSystem } from "../bit-systems/video-texture";
import { uvScrollSystem } from "../bit-systems/uv-scroll";
import { simpleWaterSystem } from "../bit-systems/simple-water";
import { pdfSystem } from "../bit-systems/pdf-system";

import { particleEmitterSystem } from "../bit-systems/particle-emitter";

declare global {
interface Window {
Expand Down Expand Up @@ -192,6 +192,7 @@ export function mainTick(xrFrame: XRFrame, renderer: WebGLRenderer, scene: Scene
hubsSystems.animationMixerSystem.tick(dt);

billboardSystem(world, hubsSystems.cameraSystem.viewingCamera);
particleEmitterSystem(world);
waypointSystem(world, hubsSystems.characterController, sceneEl.is("frozen"));
hubsSystems.characterController.tick(t, dt);
hubsSystems.cursorTogglingSystem.tick(aframeSystems.interaction, aframeSystems.userinput, hubsSystems.el);
Expand Down Expand Up @@ -243,7 +244,7 @@ export function mainTick(xrFrame: XRFrame, renderer: WebGLRenderer, scene: Scene
hubsSystems.gainSystem.tick();
hubsSystems.nameTagSystem.tick();
simpleWaterSystem(world);

videoTextureSystem(world);

deleteEntitySystem(world, aframeSystems.userinput);
Expand Down
7 changes: 5 additions & 2 deletions src/systems/remove-object3D-system.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import {
Skybox,
Slice9,
Text,
VideoMenu
VideoMenu,
ParticleEmitterTag
} from "../bit-components";
import { gltfCache } from "../components/gltf-model-plus";
import { releaseTextureByKey } from "../utils/load-texture";
import { disposeMaterial, traverseSome } from "../utils/three-utils";
import { disposeMaterial, traverseSome, disposeNode } from "../utils/three-utils";
import { forEachMaterial } from "../utils/material-utils";

function cleanupObjOnExit(Component, f) {
Expand Down Expand Up @@ -73,6 +74,7 @@ const cleanupSimpleWaters = cleanupObjOnExit(SimpleWater, obj => {
forEachMaterial(obj.material, material => material.dispose());
});
const cleanupMirrors = cleanupObjOnExit(Mirror, obj => obj.dispose());
const cleanupParticleSystem = cleanupObjOnExit(ParticleEmitterTag, obj => disposeNode(obj));

// TODO This feels messy and brittle
//
Expand Down Expand Up @@ -135,6 +137,7 @@ export function removeObject3DSystem(world) {
cleanupSkyboxes(world);
cleanupSimpleWaters(world);
cleanupMirrors(world);
cleanupParticleSystem(world);

// Finally remove all the entities we just removed from the eid2obj map
entities.forEach(removeObjFromMap);
Expand Down
5 changes: 4 additions & 1 deletion src/utils/jsx-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import { inflateUVScroll, UVScrollParams } from "../inflators/uv-scroll";
import { SimpleWaterParams, inflateSimpleWater } from "../inflators/simple-water";
import { inflatePDF, PDFParams } from "../inflators/pdf";
import { MirrorParams, inflateMirror } from "../inflators/mirror";
import { inflateParticleEmitter, ParticleEmitterParams } from "../inflators/particle-emitter";

preload(
new Promise(resolve => {
Expand Down Expand Up @@ -230,6 +231,7 @@ export interface ComponentData {
billboard?: { onlyY: boolean };
simpleWater?: SimpleWaterParams;
mirror?: MirrorParams;
particleEmitter?: ParticleEmitterParams;
}

export interface JSXComponentData extends ComponentData {
Expand Down Expand Up @@ -373,7 +375,8 @@ export const commonInflators: Required<{ [K in keyof ComponentData]: InflatorFn
ambientLight: inflateAmbientLight,
directionalLight: inflateDirectionalLight,
simpleWater: inflateSimpleWater,
mirror: inflateMirror
mirror: inflateMirror,
particleEmitter: inflateParticleEmitter
};

const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = {
Expand Down
8 changes: 8 additions & 0 deletions types/lib-hubs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare module "lib-hubs/packages/three-particle-emitter/lib/esm/index" {
import { ParticleEmitter as PE } from "lib-hubs/packages/three-particle-emitter/lib/types";

export class ParticleEmitter extends PE {
material: ShaderMaterial;
constructor(texture: Texture);
}
}

0 comments on commit acfc970

Please sign in to comment.