UNPKG

@itwin/frontend-devtools

Version:

Debug menu and supporting UI widgets

199 lines • 9.81 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Effects */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SnowEffect = exports.SnowDecorator = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const core_frontend_1 = require("@itwin/core-frontend"); const parseToggle_1 = require("../tools/parseToggle"); const Random_1 = require("./Random"); /** The default snow effect parameters used by newly-created SnowDecorators. */ const defaultSnowParams = { numParticles: 2000, sizeRange: core_geometry_1.Range1d.createXX(3, 22), transparencyRange: core_geometry_1.Range1d.createXX(0, 50), velocityRange: new core_geometry_1.Range2d(-30, 50, 30, 130), accelerationRange: new core_geometry_1.Range2d(-1, -0.25, 1, 0.25), windVelocity: 0, }; /** Simulates snowfall in a [Viewport]($frontend) using particle effects. * @see [[SnowEffect]] for a [Tool]($frontend) that toggles this decorator. * @see [ParticleCollectionBuilder]($frontend) for defining custom particle effects. * @beta */ class SnowDecorator { /** The viewport being decorated. */ viewport; /** Invoked when this decorator is to be destroyed. */ [Symbol.dispose]; /** The initial width and height of the viewport, from which we randomly select each particle's initial position. */ _dimensions; /** The list of particles being drawn. */ _particles = []; /** The image to display for each particle. */ _texture; /** The last time `updateParticles()` was invoked, in milliseconds. */ _lastUpdateTime; _params; constructor(viewport, texture) { this._params = { ...defaultSnowParams }; this.viewport = viewport; this._dimensions = new core_geometry_1.Point2d(viewport.viewRect.width, viewport.viewRect.height); this._lastUpdateTime = Date.now(); this._texture = texture; // Tell the viewport to re-render the decorations every frame so that the snow particles animate smoothly. const removeOnRender = viewport.onRender.addListener(() => viewport.invalidateDecorations()); // When the viewport is resized, replace this decorator with a new one to match the new dimensions. const removeOnResized = viewport.onResized.addListener(() => { // Transfer ownership of the texture to the new decorator. const tex = this._texture; this._texture = undefined; this[Symbol.dispose](); new SnowDecorator(viewport, tex); }); // When the viewport is destroyed, dispose of this decorator too. const removeOnDispose = viewport.onDisposed.addListener(() => this[Symbol.dispose]()); const removeDecorator = core_frontend_1.IModelApp.viewManager.addDecorator(this); this[Symbol.dispose] = () => { removeDecorator(); removeOnRender(); removeOnDispose(); removeOnResized(); this._texture = (0, core_bentley_1.dispose)(this._texture); SnowDecorator._decorators.delete(viewport); }; SnowDecorator._decorators.set(viewport, this); // Initialize the particles. for (let i = 0; i < this._params.numParticles; i++) this._particles.push(this.emit(true)); } decorate(context) { if (context.viewport !== this.viewport || !this._texture) return; // Update the particles. const now = Date.now(); const deltaMillis = now - this._lastUpdateTime; this._lastUpdateTime = now; this.updateParticles(deltaMillis / 1000); // Create particle graphics. const builder = core_frontend_1.ParticleCollectionBuilder.create({ viewport: this.viewport, isViewCoords: true, texture: this._texture, size: (this._params.sizeRange.high - this._params.sizeRange.low) / 2, }); for (const particle of this._particles) builder.addParticle(particle); const graphic = builder.finish(); if (graphic) context.addDecoration(core_frontend_1.GraphicType.ViewOverlay, graphic); } /** Change some of the parameters affecting this decorator. */ configure(params) { for (const key of Object.keys(params)) { const val = params[key]; if (undefined !== val) this._params[key] = val; } } /** Emit a new particle with randomized properties. */ emit(randomizeHeight) { return { x: (0, Random_1.randomInteger)(0, this._dimensions.x), y: randomizeHeight ? (0, Random_1.randomInteger)(0, this._dimensions.y) : 0, z: 0, size: (0, Random_1.randomInteger)(this._params.sizeRange.low, this._params.sizeRange.high), transparency: (0, Random_1.randomInteger)(this._params.transparencyRange.low, this._params.transparencyRange.high), velocity: new core_geometry_1.Vector2d((0, Random_1.randomFloat)(this._params.velocityRange.low.x, this._params.velocityRange.high.x), (0, Random_1.randomFloat)(this._params.velocityRange.low.y, this._params.velocityRange.high.y)), }; } // Update the positions and velocities of all the particles based on the amount of time that has passed since the last update. updateParticles(elapsedSeconds) { // Determine if someone changed the desired number of particles. const particleDiscrepancy = this._params.numParticles - this._particles.length; if (particleDiscrepancy > 0) { // Birth new particles up to the new maximum. for (let i = 0; i < particleDiscrepancy; i++) this._particles.push(this.emit(true)); } else { // Destroy extra particles. this._particles.length = this._params.numParticles; } const acceleration = new core_geometry_1.Vector2d(); const velocity = new core_geometry_1.Vector2d(); for (let i = 0; i < this._particles.length; i++) { // Apply some acceleration to produce random drift. const particle = this._particles[i]; acceleration.set((0, Random_1.randomFloat)(this._params.accelerationRange.low.x, this._params.accelerationRange.high.x), (0, Random_1.randomFloat)(this._params.accelerationRange.low.y, this._params.accelerationRange.high.y)); acceleration.scale(elapsedSeconds, acceleration); particle.velocity.plus(acceleration, particle.velocity); // Apply velocity. particle.velocity.clone(velocity); velocity.scale(elapsedSeconds, velocity); particle.x += velocity.x; particle.y += velocity.y; // Apply wind particle.x += this._params.windVelocity * elapsedSeconds; // Particles that travel beyond the viewport's left or right edges wrap around to the other side. if (particle.x < 0) particle.x = this._dimensions.x - 1; else if (particle.x >= this._dimensions.x) particle.x = 0; // Particles that travel beyond the viewport's bottom or top edges are replaced by newborn particles. if (particle.y < 0 || particle.y >= this._dimensions.y) this._particles[i] = this.emit(false); } } static _decorators = new Map(); /** Toggle this decorator for the specified viewport. * @param viewport The viewport to which the effect should be applied or removed. * @param enable `true` to enable the effect, `false` to disable it, or `undefined` to toggle the current state. */ static async toggle(viewport, enable) { const decorator = this._decorators.get(viewport); if (undefined === enable) enable = undefined === decorator; if (undefined !== decorator && !enable) decorator[Symbol.dispose](); else if (undefined === decorator && enable) { // Create a texture to use for the particles. // Note: the decorator takes ownership of the texture, and disposes of it when the decorator is disposed. const image = await (0, core_frontend_1.imageElementFromUrl)(`${core_frontend_1.IModelApp.publicPath}sprites/particle_snow.png`); const texture = core_frontend_1.IModelApp.renderSystem.createTexture({ ownership: "external", image: { source: image, transparency: core_common_1.TextureTransparency.Mixed }, }); new SnowDecorator(viewport, texture); } } } exports.SnowDecorator = SnowDecorator; /** Toggles a decorator that simulates snow using particle effects. * @see [[SnowDecorator]] for the implementation of the decorator. * @beta */ class SnowEffect extends core_frontend_1.Tool { static toolId = "SnowEffect"; async run(enable) { const vp = core_frontend_1.IModelApp.viewManager.selectedView; if (vp) await SnowDecorator.toggle(vp, enable); return true; } async parseAndRun(...args) { const enable = (0, parseToggle_1.parseToggle)(args[0]); if (typeof enable !== "string") await this.run(enable); return true; } } exports.SnowEffect = SnowEffect; //# sourceMappingURL=Snow.js.map