diff --git a/rollup.config.js b/rollup.config.js index 48c59450a1e..be240a12499 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -48,7 +48,7 @@ export default ({watch}) => { onwarn: production ? onwarn : false, treeshake: production ? { moduleSideEffects: (id, external) => { - return !id.endsWith("tracked_parameters.ts") && !id.endsWith("draw_snow.ts") && !id.endsWith("draw_rain.ts") && !id.endsWith("vignette.ts"); + return !id.endsWith("tracked_parameters.ts"); }, preset: "recommended" } : false, diff --git a/src/precipitation/common.ts b/src/precipitation/common.ts new file mode 100644 index 00000000000..1c62015bd24 --- /dev/null +++ b/src/precipitation/common.ts @@ -0,0 +1,172 @@ +import {earthRadius} from '../geo/lng_lat.js'; +import {degToRad, clamp} from '../util/util.js'; +import {mulberry32} from '../style-spec/util/random'; +import {vec3, quat, mat4} from 'gl-matrix'; +import {Vignette} from './vignette'; + +import type Transform from "../geo/transform"; +import type IndexBuffer from '../gl/index_buffer'; +import type VertexBuffer from '../gl/vertex_buffer'; +import type Painter from '../render/painter'; + +export class Movement { + _offsetXPrev: number | undefined; + _offsetYPrev: number | undefined; + _elevationPrev: number | undefined; + + _accumulatedOffsetX: number; + _accumulatedOffsetY: number; + _accumulatedElevation: number; + + constructor() { + this._accumulatedOffsetX = 0; + this._accumulatedOffsetY = 0; + this._accumulatedElevation = 0; + } + + update(tr: Transform, ppmScaleFactor: number) { + const options = tr.getFreeCameraOptions(); + const cameraMercatorPos = options.position; + + const elevation = cameraMercatorPos.toAltitude(); + + const latLng = cameraMercatorPos.toLngLat(); + const lng = degToRad(latLng.lng); + const lat = degToRad(latLng.lat); + + // Mercator meters + const ppmScale = tr.pixelsPerMeter / ppmScaleFactor; + + const offsetXCur = lng * earthRadius; + const offsetYCur = earthRadius * Math.log(Math.tan(Math.PI / 4 + lat / 2)); + + if (this._offsetXPrev === undefined) { + this._offsetXPrev = 0; + this._offsetYPrev = 0; + this._elevationPrev = 0; + + this._accumulatedOffsetX = 0; + this._accumulatedOffsetY = 0; + this._accumulatedElevation = 0; + } else { + const deltaX = -this._offsetXPrev + offsetXCur; + const deltaY = -this._offsetYPrev + offsetYCur; + const deltaE = -this._elevationPrev + elevation; + + this._accumulatedOffsetX += deltaX * ppmScale; + this._accumulatedOffsetY += deltaY * ppmScale; + this._accumulatedElevation += deltaE * ppmScale; + + this._offsetXPrev = offsetXCur; + this._offsetYPrev = offsetYCur; + this._elevationPrev = elevation; + } + } + + getPosition(): vec3 { + return [this._accumulatedOffsetX, this._accumulatedOffsetY, this._accumulatedElevation]; + } +} + +export function boxWrap(unwrappedPos: vec3, boxSize: number): vec3 { + const wrappedOffsetX = unwrappedPos[0] - Math.floor(unwrappedPos[0] / boxSize) * boxSize; + const wrappedOffsetY = unwrappedPos[1] - Math.floor(unwrappedPos[1] / boxSize) * boxSize; + const wrappedOffsetZ = unwrappedPos[2] - Math.floor(unwrappedPos[2] / boxSize) * boxSize; + + return [-wrappedOffsetX, -wrappedOffsetY, -wrappedOffsetZ]; + +} + +export function generateUniformDistributedPointsInsideCube(pointsCount: number): Array { + const sRand = mulberry32(1323123451230); + + const points: Array = []; + for (let i = 0; i < pointsCount; ++i) { + const vx = -1 + 2 * sRand(); + const vy = -1 + 2 * sRand(); + const vz = -1 + 2 * sRand(); + + points.push(vec3.fromValues(vx, vy, vz)); + } + + return points; +} + +export function lerpClamp(a: number, b: number, t1: number, t2: number, tMid: number) { + const t = clamp((tMid - t1) / (t2 - t1), 0, 1); + return (1 - t) * a + t * b; +} + +class DrawParams { + projectionMatrix: mat4; + modelviewMatrix: mat4; +} + +export class PrecipitationBase { + particlesVx: VertexBuffer | null | undefined; + particlesIdx: IndexBuffer | null | undefined; + particlesCount: number; + + _movement: Movement; + + _prevTime: number; + _accumulatedTimeFromStart: number; + + _vignette: Vignette; + + _ppmScaleFactor: number; + + constructor(ppmScaleFactor: number) { + this._movement = new Movement(); + + this._accumulatedTimeFromStart = 0; + this._prevTime = Date.now() / 1000; + + this._vignette = new Vignette(); + + this._ppmScaleFactor = ppmScaleFactor; + } + + destroy() { + if (this.particlesVx) { + this.particlesVx.destroy(); + } + + if (this.particlesIdx) { + this.particlesIdx.destroy(); + } + + if (this._vignette) { + this._vignette.destroy(); + } + } + + updateOnRender(painter: Painter, timeFactor: number) : DrawParams { + const tr = painter.transform; + + this._movement.update(tr, this._ppmScaleFactor); + + const projectionMatrix = tr.starsProjMatrix; + + const orientation = quat.identity([] as any); + + quat.rotateX(orientation, orientation, degToRad(90) - tr._pitch); + quat.rotateZ(orientation, orientation, -tr.angle); + + const rotationMatrix = mat4.fromQuat(new Float32Array(16), orientation); + + const swapAxesT = mat4.fromValues(1, 0, 0, 0, + 0, 0, 1, 0, + 0, -1, 0, 0, + 0, 0, 0, 1); + const swapAxes = mat4.transpose([] as any, swapAxesT); + + const modelviewMatrix = mat4.multiply([] as any, swapAxes, rotationMatrix); + + const curTime = Date.now() / 1000; + this._accumulatedTimeFromStart += (curTime - this._prevTime) * timeFactor; + this._prevTime = curTime; + + return {projectionMatrix, modelviewMatrix}; + } +} diff --git a/src/precipitation/draw_rain.ts b/src/precipitation/draw_rain.ts index ca01a38bfc1..14e742581bb 100644 --- a/src/precipitation/draw_rain.ts +++ b/src/precipitation/draw_rain.ts @@ -4,60 +4,27 @@ import StencilMode from '../gl/stencil_mode.js'; import DepthMode from '../gl/depth_mode.js'; import {default as ColorMode} from '../gl/color_mode.js'; import CullFaceMode from '../gl/cull_face_mode.js'; -import {degToRad, clamp} from '../util/util.js'; -import {vec3, mat4, quat} from 'gl-matrix'; +import {vec3} from 'gl-matrix'; import SegmentVector from '../data/segment.js'; import {TriangleIndexArray, RainVertexArray} from '../data/array_types.js'; import {rainUniformValues} from './rain_program.js'; import {mulberry32} from '../style-spec/util/random.js'; import {rainLayout} from "./rain_attributes.js"; -import {earthRadius} from '../geo/lng_lat.js'; import Texture from '../render/texture.js'; import {PrecipitationRevealParams} from './precipitation_reveal_params.js'; import {createTpBindings, defaultVignetteParams} from './vignette'; -import {Vignette, type VignetteParams} from './vignette'; +import {PrecipitationBase, boxWrap, generateUniformDistributedPointsInsideCube, lerpClamp} from './common'; +import {Debug} from '../util/debug'; +import {type VignetteParams} from './vignette'; -import type VertexBuffer from '../gl/vertex_buffer.js'; import type {vec4} from 'gl-matrix'; -import type IndexBuffer from '../gl/index_buffer.js'; -import type Painter from '../render/painter.js'; - -function generateUniformDistributedPointsInsideCube(pointsCount: number): Array { - const sRand = mulberry32(1323123451230); - - const points: Array = []; - for (let i = 0; i < pointsCount; ++i) { - const vx = -1 + 2 * sRand(); - const vy = -1 + 2 * sRand(); - const vz = -1 + 2 * sRand(); - - points.push(vec3.fromValues(vx, vy, vz)); - } - - return points; -} - -export class Rain { - particlesVx: VertexBuffer | null | undefined; - particlesIdx: IndexBuffer | null | undefined; - particlesCount: number; - particlesSegments: SegmentVector; - startTime: number; - prevTime: number; - accumulatedTimeFromStart: number; +import type Painter from '../render/painter'; +export class Rain extends PrecipitationBase { screenTexture: Texture | null | undefined; _revealParams: PrecipitationRevealParams; - _offsetXPrev: number | undefined; - _offsetYPrev: number | undefined; - _elevationPrev: number | undefined; - - _accumulatedOffsetX: number; - _accumulatedOffsetY: number; - _accumulatedElevation: number; - _params: { overrideStyleParameters: boolean, intensity: number, @@ -82,20 +49,10 @@ export class Rain { shapeNormalPower: number; }; - vignetteParams: VignetteParams; - vignette: Vignette; + _vignetteParams: VignetteParams; constructor(painter: Painter) { - - this.accumulatedTimeFromStart = 0; - this.startTime = Date.now() / 1000; - this.prevTime = Date.now() / 1000; - - this._accumulatedOffsetX = 0; - this._accumulatedOffsetY = 0; - this._accumulatedElevation = 0; - - this.vignette = new Vignette(); + super(4.25); this._params = { overrideStyleParameters: false, @@ -125,45 +82,49 @@ export class Rain { const scope = ["Precipitation", "Rain"]; this._revealParams = new PrecipitationRevealParams(painter.tp, scope); - tp.registerParameter(this._params, scope, 'overrideStyleParameters'); - tp.registerParameter(this._params, scope, 'intensity', {min: 0.0, max: 1.0}); - tp.registerParameter(this._params, scope, 'timeFactor', {min: 0.0, max: 3.0, step: 0.01}); - tp.registerParameter(this._params, scope, 'velocityConeAperture', {min: 0.0, max: 160.0, step: 1.0}); - tp.registerParameter(this._params, scope, 'velocity', {min: 0.0, max: 1500.0, step: 5}); - tp.registerParameter(this._params, scope, 'boxSize', {min: 100.0, max: 4400.0, step: 10.0}); - tp.registerParameter(this._params, scope, 'dropletSizeX', {min: 0.1, max: 10.0, step: 0.1}); - tp.registerParameter(this._params, scope, 'dropletSizeYScale', {min: 0.1, max: 10.0, step: 0.1}); - tp.registerParameter(this._params, scope, 'distortionStrength', {min: 0.0, max: 100.0, step: 0.5}); - - tp.registerParameter(this._params, scope, 'direction', { - picker: 'inline', - expanded: true, - x: {min: -200, max: 200}, - y: {min: -200, max: 200}, - }); + this._vignetteParams = defaultVignetteParams([0.4, 0.4, 0.4, 0.3]); - const shapeScope = [...scope, "Shape"]; - tp.registerParameter(this._params, shapeScope, 'shapeDirPower', {min: 1.0, max: 10.0, step: 0.01}); - tp.registerParameter(this._params, shapeScope, 'shapeNormalPower', {min: 1.0, max: 10.0, step: 0.01}); + this.particlesCount = 16000; - tp.registerParameter(this._params, scope, 'color', { - color: {type: 'float'}, - }); + Debug.run(() => { + + tp.registerParameter(this._params, scope, 'overrideStyleParameters'); + tp.registerParameter(this._params, scope, 'intensity', {min: 0.0, max: 1.0}); + tp.registerParameter(this._params, scope, 'timeFactor', {min: 0.0, max: 3.0, step: 0.01}); + tp.registerParameter(this._params, scope, 'velocityConeAperture', {min: 0.0, max: 160.0, step: 1.0}); + tp.registerParameter(this._params, scope, 'velocity', {min: 0.0, max: 1500.0, step: 5}); + tp.registerParameter(this._params, scope, 'boxSize', {min: 100.0, max: 4400.0, step: 10.0}); + tp.registerParameter(this._params, scope, 'dropletSizeX', {min: 0.1, max: 10.0, step: 0.1}); + tp.registerParameter(this._params, scope, 'dropletSizeYScale', {min: 0.1, max: 10.0, step: 0.1}); + tp.registerParameter(this._params, scope, 'distortionStrength', {min: 0.0, max: 100.0, step: 0.5}); + + tp.registerParameter(this._params, scope, 'direction', { + picker: 'inline', + expanded: true, + x: {min: -200, max: 200}, + y: {min: -200, max: 200}, + }); - const thinningScope = [...scope, "ScreenThinning"]; + const shapeScope = [...scope, "Shape"]; + tp.registerParameter(this._params, shapeScope, 'shapeDirPower', {min: 1.0, max: 10.0, step: 0.01}); + tp.registerParameter(this._params, shapeScope, 'shapeNormalPower', {min: 1.0, max: 10.0, step: 0.01}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'intensity', {min: 0.0, max: 1.0}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'start', {min: 0.0, max: 2.0}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'range', {min: 0.0, max: 2.0}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'fadePower', {min: -1.0, max: 1.0, step: 0.01}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'affectedRatio', {min: 0.0, max: 1.0, step: 0.01}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'particleOffset', {min: -1.0, max: 1.0, step: 0.01}); + tp.registerParameter(this._params, scope, 'color', { + color: {type: 'float'}, + }); - const vignetteScope = [...scope, "Vignette"]; - this.vignetteParams = defaultVignetteParams([0.4, 0.4, 0.4, 0.3]); - createTpBindings(this.vignetteParams, painter, vignetteScope); + const thinningScope = [...scope, "ScreenThinning"]; - this.particlesCount = 16000; + tp.registerParameter(this._params.screenThinning, thinningScope, 'intensity', {min: 0.0, max: 1.0}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'start', {min: 0.0, max: 2.0}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'range', {min: 0.0, max: 2.0}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'fadePower', {min: -1.0, max: 1.0, step: 0.01}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'affectedRatio', {min: 0.0, max: 1.0, step: 0.01}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'particleOffset', {min: -1.0, max: 1.0, step: 0.01}); + + const vignetteScope = [...scope, "Vignette"]; + createTpBindings(this._vignetteParams, painter, vignetteScope); + }); } update(painter: Painter) { @@ -201,13 +162,9 @@ export class Rain { this.particlesVx = context.createVertexBuffer(vertices, rainLayout.members); this.particlesIdx = context.createIndexBuffer(triangles); - this.particlesSegments = SegmentVector.simpleSegment(0, 0, vertices.length, triangles.length); } } - destroy() { - } - draw(painter: Painter) { if (!this._params.overrideStyleParameters && !painter.style.rain) { return; @@ -216,10 +173,6 @@ export class Rain { // Global parameters const gp = this._params.overrideStyleParameters ? this._revealParams : {revealStart: 0, revealRange: 0.01}; const zoom = painter.transform.zoom; - const lerpClamp = (a: number, b: number, t1: number, t2: number, tMid: number,) => { - const t = clamp((tMid - t1) / (t2 - t1), 0, 1); - return (1 - t) * a + t * b; - }; if (gp.revealStart > zoom) { return; } const revealFactor = lerpClamp(0, 1, gp.revealStart, gp.revealStart + gp.revealRange, zoom); @@ -232,7 +185,7 @@ export class Rain { let rainDirection: vec3 = [-params.direction.x, params.direction.y, -100]; vec3.normalize(rainDirection, rainDirection); - const vignetteParams = structuredClone(this.vignetteParams); + const vignetteParams = structuredClone(this._vignetteParams); vignetteParams.strength *= revealFactor; @@ -251,6 +204,8 @@ export class Rain { vignetteParams.color = structuredClone(painter.style.rain.state.vignetteColor); } + const drawData = this.updateOnRender(painter, params.timeFactor); + const context = painter.context; const gl = context.gl; @@ -270,85 +225,17 @@ export class Rain { gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, painter.width, painter.height); } - const curTime = Date.now() / 1000; - this.accumulatedTimeFromStart += (curTime - this.prevTime) * params.timeFactor; - - this.prevTime = curTime; - - const ppmScale = tr.pixelsPerMeter / 4.25; - const program = painter.getOrCreateProgram('rainParticle'); - const projectionMatrix = tr.starsProjMatrix; - - const orientation = quat.identity([] as any); - - quat.rotateX(orientation, orientation, degToRad(90) - tr._pitch); - quat.rotateZ(orientation, orientation, -tr.angle); - - const rotationMatrix = mat4.fromQuat(new Float32Array(16), orientation); - const swapAxesT = mat4.fromValues(1, 0, 0, 0, - 0, 0, 1, 0, - 0, -1, 0, 0, - 0, 0, 0, 1); - const swapAxes = mat4.transpose([] as any, swapAxesT); - const modelviewMatrix = mat4.multiply([] as any, swapAxes, rotationMatrix); - - const options = tr.getFreeCameraOptions(); - - const cameraMercatorPos = options.position; - - const elevation = cameraMercatorPos.toAltitude(); - - const latLng = cameraMercatorPos.toLngLat(); - const lng = degToRad(latLng.lng); - const lat = degToRad(latLng.lat); - - // Mercator meters - const offsetXCur = lng * earthRadius; - const offsetYCur = earthRadius * Math.log(Math.tan(Math.PI / 4 + lat / 2)); - - if (this._offsetXPrev === undefined) { - this._offsetXPrev = 0; - this._offsetYPrev = 0; - this._elevationPrev = 0; - - this._accumulatedOffsetX = 0; - this._accumulatedOffsetY = 0; - this._accumulatedElevation = 0; - } else { - const deltaX = -this._offsetXPrev + offsetXCur; - const deltaY = -this._offsetYPrev + offsetYCur; - const deltaE = -this._elevationPrev + elevation; - - this._accumulatedOffsetX += deltaX * ppmScale; - this._accumulatedOffsetY += deltaY * ppmScale; - this._accumulatedElevation += deltaE * ppmScale; - - this._offsetXPrev = offsetXCur; - this._offsetYPrev = offsetYCur; - this._elevationPrev = elevation; - } - painter.uploadCommonUniforms(context, program); context.activeTexture.set(gl.TEXTURE0); this.screenTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); - const depthMode = new DepthMode(painter.context.gl.ALWAYS, DepthMode.ReadOnly, painter.depthRangeFor3D); - const colorVec: vec4 = [params.color.r, params.color.g, params.color.b, params.color.a]; const drawParticlesBox = (boxSize: number, distortionOnly: boolean) => { - const offsetX = this._accumulatedOffsetX; - const offsetY = this._accumulatedOffsetY; - const offsetZ = this._accumulatedElevation; - - const wrappedOffsetX = offsetX - Math.floor(offsetX / boxSize) * boxSize; - const wrappedOffsetY = offsetY - Math.floor(offsetY / boxSize) * boxSize; - const wrappedOffsetZ = offsetZ - Math.floor(offsetZ / boxSize) * boxSize; - - const camPos: vec3 = [-wrappedOffsetX, -wrappedOffsetY, -wrappedOffsetZ]; + const camPos = boxWrap(this._movement.getPosition(), boxSize); const sizeX = params.dropletSizeX; const sizeY = params.dropletSizeX * params.dropletSizeYScale; @@ -361,10 +248,10 @@ export class Rain { const thinningParticleOffset = lerpClamp(0.0, params.screenThinning.particleOffset, 0, 1, params.screenThinning.intensity); const uniforms = rainUniformValues({ - modelview: modelviewMatrix, - projection: projectionMatrix, - time: this.accumulatedTimeFromStart, - camPos, + modelview: drawData.modelviewMatrix, + projection: drawData.projectionMatrix, + time: this._accumulatedTimeFromStart, + camPos: camPos as [number, number, number], velocityConeAperture: params.velocityConeAperture, velocity: params.velocity, boxSize, @@ -385,7 +272,7 @@ export class Rain { const count = Math.round(revealFactor * params.intensity * this.particlesCount); const particlesSegments = SegmentVector.simpleSegment(0, 0, count * 4, count * 2); - program.draw(painter, gl.TRIANGLES, depthMode, StencilMode.disabled, + program.draw(painter, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, ColorMode.alphaBlended, CullFaceMode.disabled, uniforms, "rain_particles", this.particlesVx, this.particlesIdx, particlesSegments, {}); }; @@ -398,7 +285,7 @@ export class Rain { // Same data alpha blended only drawParticlesBox(params.boxSize, false); - this.vignette.draw(painter, vignetteParams); + this._vignette.draw(painter, vignetteParams); } } diff --git a/src/precipitation/draw_snow.ts b/src/precipitation/draw_snow.ts index 1673dda625f..95b85d63d30 100644 --- a/src/precipitation/draw_snow.ts +++ b/src/precipitation/draw_snow.ts @@ -3,62 +3,24 @@ import StencilMode from '../gl/stencil_mode'; import DepthMode from '../gl/depth_mode'; import {default as ColorMode} from '../gl/color_mode'; import CullFaceMode from '../gl/cull_face_mode'; -import {degToRad, clamp} from '../util/util'; -import {vec3, mat4, quat} from 'gl-matrix'; +import {vec3} from 'gl-matrix'; import SegmentVector from '../data/segment'; import {TriangleIndexArray, SnowVertexArray} from '../data/array_types'; import {snowUniformValues} from './snow_program'; import {mulberry32} from '../style-spec/util/random'; import {snowLayout} from "./snow_attributes"; -import {earthRadius} from '../geo/lng_lat'; import {PrecipitationRevealParams} from './precipitation_reveal_params'; import {createTpBindings, defaultVignetteParams} from './vignette'; -import {Vignette, type VignetteParams} from './vignette'; +import {type VignetteParams} from './vignette'; +import {boxWrap, generateUniformDistributedPointsInsideCube, lerpClamp, PrecipitationBase} from './common'; +import {Debug} from '../util/debug'; import type Painter from '../render/painter'; -import type IndexBuffer from '../gl/index_buffer'; -import type VertexBuffer from '../gl/vertex_buffer'; import type {vec2, vec4} from 'gl-matrix'; -function generateUniformDistributedPointsInsideCube(pointsCount: number): Array { - const sRand = mulberry32(1323123451230); - // const sRand = Math.random; - - const points: Array = []; - for (let i = 0; i < pointsCount; ++i) { - const vx = -1 + 2 * sRand(); - const vy = -1 + 2 * sRand(); - const vz = -1 + 2 * sRand(); - - points.push(vec3.fromValues(vx, vy, vz)); - } - - return points; -} - -export class Snow { - particlesVx: VertexBuffer | null | undefined; - particlesIdx: IndexBuffer | null | undefined; - particlesCount: number; - particlesSegments: SegmentVector; - startTime: number; - prevTime: number; - accumulatedTimeFromStart: number; - +export class Snow extends PrecipitationBase { _revealParams: PrecipitationRevealParams; - _offsetX: number | undefined; - _offsetY: number | undefined; - _elevation: number | undefined; - - _offsetYPrev: number | undefined; - _offsetXPrev: number | undefined; - _elevationPrev: number | undefined; - - _offsetXAccum: number | undefined; - _offsetYAccum: number | undefined; - _elevationAccum: number | undefined; - _params: { overrideStyleParameters: boolean, intensity: number, @@ -83,27 +45,10 @@ export class Snow { direction: {x: number, y: number}, }; - vignetteParams: VignetteParams; - vignette: Vignette; + _vignetteParams: VignetteParams; constructor(painter: Painter) { - this.accumulatedTimeFromStart = 0; - this.startTime = Date.now() / 1000; - this.prevTime = Date.now() / 1000; - - this._offsetX = undefined; - this._offsetY = undefined; - this._elevation = undefined; - - this._offsetXAccum = undefined; - this._offsetYAccum = undefined; - this._elevationAccum = undefined; - - this._offsetXPrev = undefined; - this._offsetYPrev = undefined; - this._elevationPrev = undefined; - - this.vignette = new Vignette(); + super(2.25); this._params = { overrideStyleParameters: false, @@ -132,45 +77,47 @@ export class Snow { const tp = painter.tp; const scope = ["Precipitation", "Snow"]; this._revealParams = new PrecipitationRevealParams(painter.tp, scope); - tp.registerParameter(this._params, scope, 'overrideStyleParameters'); - tp.registerParameter(this._params, scope, 'intensity', {min: 0.0, max: 1.0}); - tp.registerParameter(this._params, scope, 'timeFactor', {min: 0.0, max: 1.0, step: 0.01}); - tp.registerParameter(this._params, scope, 'velocityConeAperture', {min: 0.0, max: 160.0, step: 1.0}); - tp.registerParameter(this._params, scope, 'velocity', {min: 0.0, max: 500.0, step: 0.5}); - tp.registerParameter(this._params, scope, 'horizontalOscillationRadius', {min: 0.0, max: 10.0, step: 0.1}); - tp.registerParameter(this._params, scope, 'horizontalOscillationRate', {min: 0.3, max: 3.0, step: 0.05}); - tp.registerParameter(this._params, scope, 'boxSize', {min: 100.0, max: 10000.0, step: 50.0}); - tp.registerParameter(this._params, scope, 'billboardSize', {min: 0.1, max: 10.0, step: 0.01}); - - const thinningScope = [...scope, "ScreenThinning"]; - - tp.registerParameter(this._params.screenThinning, thinningScope, 'intensity', {min: 0.0, max: 1.0}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'start', {min: 0.0, max: 2.0}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'range', {min: 0.0, max: 2.0}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'fadePower', {min: -1.0, max: 1.0, step: 0.01}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'affectedRatio', {min: 0.0, max: 1.0, step: 0.01}); - tp.registerParameter(this._params.screenThinning, thinningScope, 'particleOffset', {min: -1.0, max: 1.0, step: 0.01}); - - const shapeScope = [...scope, "Shape"]; - tp.registerParameter(this._params, shapeScope, 'shapeFadeStart', {min: 0.0, max: 1.0, step: 0.01}); - tp.registerParameter(this._params, shapeScope, 'shapeFadePower', {min: -1.0, max: 0.99, step: 0.01}); - - tp.registerParameter(this._params, scope, 'color', { - color: {type: 'float'}, - }); - - const vignetteScope = [...scope, "Vignette"]; - this.vignetteParams = defaultVignetteParams([1, 0, 0, 0.3]); - createTpBindings(this.vignetteParams, painter, vignetteScope); + this._vignetteParams = defaultVignetteParams([1, 0, 0, 0.3]); + this.particlesCount = 16000; - tp.registerParameter(this._params, scope, 'direction', { - picker: 'inline', - expanded: true, - x: {min: -200, max: 200}, - y: {min: -200, max: 200}, + Debug.run(() => { + tp.registerParameter(this._params, scope, 'overrideStyleParameters'); + tp.registerParameter(this._params, scope, 'intensity', {min: 0.0, max: 1.0}); + tp.registerParameter(this._params, scope, 'timeFactor', {min: 0.0, max: 1.0, step: 0.01}); + tp.registerParameter(this._params, scope, 'velocityConeAperture', {min: 0.0, max: 160.0, step: 1.0}); + tp.registerParameter(this._params, scope, 'velocity', {min: 0.0, max: 500.0, step: 0.5}); + tp.registerParameter(this._params, scope, 'horizontalOscillationRadius', {min: 0.0, max: 10.0, step: 0.1}); + tp.registerParameter(this._params, scope, 'horizontalOscillationRate', {min: 0.3, max: 3.0, step: 0.05}); + tp.registerParameter(this._params, scope, 'boxSize', {min: 100.0, max: 10000.0, step: 50.0}); + tp.registerParameter(this._params, scope, 'billboardSize', {min: 0.1, max: 10.0, step: 0.01}); + + const thinningScope = [...scope, "ScreenThinning"]; + + tp.registerParameter(this._params.screenThinning, thinningScope, 'intensity', {min: 0.0, max: 1.0}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'start', {min: 0.0, max: 2.0}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'range', {min: 0.0, max: 2.0}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'fadePower', {min: -1.0, max: 1.0, step: 0.01}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'affectedRatio', {min: 0.0, max: 1.0, step: 0.01}); + tp.registerParameter(this._params.screenThinning, thinningScope, 'particleOffset', {min: -1.0, max: 1.0, step: 0.01}); + + const shapeScope = [...scope, "Shape"]; + tp.registerParameter(this._params, shapeScope, 'shapeFadeStart', {min: 0.0, max: 1.0, step: 0.01}); + tp.registerParameter(this._params, shapeScope, 'shapeFadePower', {min: -1.0, max: 0.99, step: 0.01}); + + tp.registerParameter(this._params, scope, 'color', { + color: {type: 'float'}, + }); + + const vignetteScope = [...scope, "Vignette"]; + createTpBindings(this._vignetteParams, painter, vignetteScope); + + tp.registerParameter(this._params, scope, 'direction', { + picker: 'inline', + expanded: true, + x: {min: -200, max: 200}, + y: {min: -200, max: 200}, + }); }); - - this.particlesCount = 16000; } update(painter: Painter) { @@ -184,7 +131,6 @@ export class Snow { let base = 0; const sRand = mulberry32(1323123451230); - // const sRand = Math.random; for (let i = 0; i < positions.length; ++i) { const p = positions[i]; @@ -207,21 +153,6 @@ export class Snow { this.particlesVx = context.createVertexBuffer(vertices, snowLayout.members); this.particlesIdx = context.createIndexBuffer(triangles); - this.particlesSegments = SegmentVector.simpleSegment(0, 0, vertices.length, triangles.length); - } - } - - destroy() { - if (this.particlesVx) { - this.particlesVx.destroy(); - } - - if (this.particlesIdx) { - this.particlesIdx.destroy(); - } - - if (this.vignette) { - this.vignette.destroy(); } } @@ -235,16 +166,12 @@ export class Snow { let snowDirection: vec3 = [-params.direction.x, params.direction.y, -100]; vec3.normalize(snowDirection, snowDirection); - const vignetteParams = structuredClone(this.vignetteParams); + const vignetteParams = structuredClone(this._vignetteParams); // Global parameters const gp = params.overrideStyleParameters ? this._revealParams : {revealStart: 0, revealRange: 0.01}; const zoom = painter.transform.zoom; - const lerpClamp = (a: number, b: number, t1: number, t2: number, tMid: number,) => { - const t = clamp((tMid - t1) / (t2 - t1), 0, 1); - return (1 - t) * a + t * b; - }; if (gp.revealStart > zoom) { return; } const revealFactor = lerpClamp(0, 1, gp.revealStart, gp.revealStart + gp.revealRange, zoom); @@ -264,96 +191,22 @@ export class Snow { vignetteParams.color = structuredClone(painter.style.snow.state.vignetteColor); } + const drawData = this.updateOnRender(painter, params.timeFactor); + if (!this.particlesVx || !this.particlesIdx) { return; } - const curTime = Date.now() / 1000; - this.accumulatedTimeFromStart += (curTime - this.prevTime) * params.timeFactor; - - this.prevTime = curTime; - const context = painter.context; const gl = context.gl; const tr = painter.transform; const program = painter.getOrCreateProgram('snowParticle'); - const projectionMatrix = tr.starsProjMatrix; - - const orientation = quat.identity([] as any); - - quat.rotateX(orientation, orientation, degToRad(90) - tr._pitch); - quat.rotateZ(orientation, orientation, -tr.angle); - - const rotationMatrix = mat4.fromQuat(new Float32Array(16), orientation); - - const swapAxesT = mat4.fromValues(1, 0, 0, 0, - 0, 0, 1, 0, - 0, -1, 0, 0, - 0, 0, 0, 1); - const swapAxes = mat4.transpose([] as any, swapAxesT); - - const modelviewMatrix = mat4.multiply([] as any, swapAxes, rotationMatrix); - - const options = tr.getFreeCameraOptions(); - - const cameraMercatorPos = options.position; - - if (!cameraMercatorPos) { - return; - } - - const ppmScale = tr.pixelsPerMeter / 2.25; - const altitudeMeters = cameraMercatorPos.toAltitude(); - const elevation = altitudeMeters; - - // Calculate mercator meters - const latLngF = cameraMercatorPos.toLngLat(); - const lng = degToRad(latLngF.lng); - const lat = degToRad(latLngF.lat); - - // Mercator meters - const offsetXCur = lng * earthRadius; - const offsetYCur = earthRadius * Math.log(Math.tan(Math.PI / 4 + lat / 2)); - - if (this._offsetXPrev === undefined) { - this._offsetXPrev = 0; - this._offsetYPrev = 0; - this._elevationPrev = 0; - - this._offsetXAccum = 0; - this._offsetYAccum = 0; - this._elevationAccum = 0; - } else { - const deltaX = -this._offsetXPrev + offsetXCur; - const deltaY = -this._offsetYPrev + offsetYCur; - const deltaE = -this._elevationPrev + elevation; - - this._offsetXAccum += deltaX * ppmScale; - this._offsetYAccum += deltaY * ppmScale; - this._elevationAccum += deltaE * ppmScale; - - this._offsetXPrev = offsetXCur; - this._offsetYPrev = offsetYCur; - this._elevationPrev = elevation; - } - painter.uploadCommonUniforms(context, program); - const depthMode = new DepthMode(painter.context.gl.ALWAYS, DepthMode.ReadOnly, painter.depthRangeFor3D); - const drawParticlesBox = (boxSize: number, sizeScale: number, dp: any) => { - - const offsetX = this._offsetXAccum; - const offsetY = this._offsetYAccum; - const offsetZ = this._elevationAccum; - - const wrappedOffsetX = offsetX - Math.floor(offsetX / boxSize) * boxSize; - const wrappedOffsetY = offsetY - Math.floor(offsetY / boxSize) * boxSize; - const wrappedOffsetZ = offsetZ - Math.floor(offsetZ / boxSize) * boxSize; - - const camPos: [number, number, number] = [-wrappedOffsetX, -wrappedOffsetY, -wrappedOffsetZ]; + const camPos = boxWrap(this._movement.getPosition(), boxSize); const thinningX = tr.width / 2; const thinningY = tr.height / 2; @@ -363,10 +216,10 @@ export class Snow { const thinningParticleOffset = lerpClamp(0.0, dp.screenThinning.particleOffset, 0, 1, dp.screenThinning.intensity); const uniforms = snowUniformValues({ - modelview: modelviewMatrix, - projection: projectionMatrix, - time: this.accumulatedTimeFromStart, - camPos, + modelview: drawData.modelviewMatrix, + projection: drawData.projectionMatrix, + time: this._accumulatedTimeFromStart, + camPos: camPos as [number, number, number], velocityConeAperture: dp.velocityConeAperture, velocity: dp.velocity, horizontalOscillationRadius: dp.horizontalOscillationRadius, @@ -388,7 +241,7 @@ export class Snow { const particlesSegments = SegmentVector.simpleSegment(0, 0, count * 4, count * 2); if (this.particlesVx && this.particlesIdx) { - program.draw(painter, gl.TRIANGLES, depthMode, StencilMode.disabled, + program.draw(painter, gl.TRIANGLES, DepthMode.disabled, StencilMode.disabled, ColorMode.alphaBlended, CullFaceMode.disabled, uniforms, "snow_particles", this.particlesVx, this.particlesIdx, particlesSegments, {}); } @@ -396,6 +249,6 @@ export class Snow { drawParticlesBox(params.boxSize, 1.0, params); - this.vignette.draw(painter, vignetteParams); + this._vignette.draw(painter, vignetteParams); } } diff --git a/src/precipitation/precipitation_reveal_params.ts b/src/precipitation/precipitation_reveal_params.ts index 99aab0c1d07..a0bca8b4bc3 100644 --- a/src/precipitation/precipitation_reveal_params.ts +++ b/src/precipitation/precipitation_reveal_params.ts @@ -1,4 +1,3 @@ -// @flow import {type ITrackedParameters} from '../tracked-parameters/tracked_parameters_base'; export class PrecipitationRevealParams { diff --git a/src/precipitation/rain_attributes.ts b/src/precipitation/rain_attributes.ts index 76126af4542..8db713bcb82 100644 --- a/src/precipitation/rain_attributes.ts +++ b/src/precipitation/rain_attributes.ts @@ -1,4 +1,3 @@ -// @flow import {createLayout} from '../util/struct_array.js'; import type {StructArrayLayout} from '../util/struct_array.js'; diff --git a/src/precipitation/rain_program.ts b/src/precipitation/rain_program.ts index 777e974df82..afa20cfab24 100644 --- a/src/precipitation/rain_program.ts +++ b/src/precipitation/rain_program.ts @@ -1,5 +1,3 @@ -// @flow - import { Uniform4f, Uniform3f, diff --git a/src/precipitation/snow_attributes.ts b/src/precipitation/snow_attributes.ts index 0d6c2359ddc..542d1d09f83 100644 --- a/src/precipitation/snow_attributes.ts +++ b/src/precipitation/snow_attributes.ts @@ -1,4 +1,3 @@ -// @flow import {createLayout} from '../util/struct_array.js'; import type {StructArrayLayout} from '../util/struct_array.js'; diff --git a/src/precipitation/snow_program.ts b/src/precipitation/snow_program.ts index e6f689e5ca1..4087e5f9f56 100644 --- a/src/precipitation/snow_program.ts +++ b/src/precipitation/snow_program.ts @@ -1,5 +1,3 @@ -// @flow - import { Uniform4f, Uniform3f, diff --git a/src/precipitation/vignette_attributes.ts b/src/precipitation/vignette_attributes.ts index 1d51d557c72..a143cf23d2b 100644 --- a/src/precipitation/vignette_attributes.ts +++ b/src/precipitation/vignette_attributes.ts @@ -1,4 +1,3 @@ -// @flow import {createLayout} from '../util/struct_array.js'; import type {StructArrayLayout} from '../util/struct_array.js'; diff --git a/src/precipitation/vignette_program.ts b/src/precipitation/vignette_program.ts index 5f38a876c11..9a8e8cf2ce4 100644 --- a/src/precipitation/vignette_program.ts +++ b/src/precipitation/vignette_program.ts @@ -1,5 +1,3 @@ -// @flow - import { Uniform4f, Uniform3f, diff --git a/src/render/painter.ts b/src/render/painter.ts index 92c666d7a1b..651e191cddb 100644 --- a/src/render/painter.ts +++ b/src/render/painter.ts @@ -1019,22 +1019,28 @@ class Painter { const snow = this._debugParams.forceEnablePrecipitation || !!(this.style && this.style.snow); const rain = this._debugParams.forceEnablePrecipitation || !!(this.style && this.style.rain); - Debug.run(() => { - if (snow && !this._snow) { - this._snow = new Snow(this); - } + if (snow && !this._snow) { + this._snow = new Snow(this); + } + if (!snow && this._snow) { + this._snow.destroy(); + delete this._snow; + } - if (rain && !this._rain) { - this._rain = new Rain(this); - } + if (rain && !this._rain) { + this._rain = new Rain(this); + } + if (!rain && this._rain) { + this._rain.destroy(); + delete this._rain; + } - if (this._snow) { - this._snow.update(this); - } - if (this._rain) { - this._rain.update(this); - } - }); + if (this._snow) { + this._snow.update(this); + } + if (this._rain) { + this._rain.update(this); + } // Following line is billing related code. Do not change. See LICENSE.txt if (!isMapAuthenticated(this.context.gl)) return; @@ -1296,15 +1302,13 @@ class Painter { this.terrain.postRender(); } - Debug.run(() => { - if (this._snow) { - this._snow.draw(this); - } + if (this._snow) { + this._snow.draw(this); + } - if (this._rain) { - this._rain.draw(this); - } - }); + if (this._rain) { + this._rain.draw(this); + } if (this.options.showTileBoundaries || this.options.showQueryGeometry || this.options.showTileAABBs) { // Use source with highest maxzoom let selectedSource = null; diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index fe662d54890..344a80b59d9 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -107,12 +107,12 @@ }, "snow": { "type": "snow", - "doc": "Global precipitation particle-based snow effect. For uninterrupted animations map should be created with constant repaint mode enabled.", + "doc": "Global precipitation particle-based snow. Having snow present in the style forces constant map repaint mode", "experimental": true }, "rain": { "type": "rain", - "doc": "Global precipitation particle-based rain effect. For uninterrupted animations map should be created with constant repaint mode enabled.", + "doc": "Global precipitation particle-based rain effect. Having rain present in the style forces constant map repaint mode.", "experimental": true }, "camera": { diff --git a/src/ui/map.ts b/src/ui/map.ts index 545bf5fab51..d4c75087f91 100644 --- a/src/ui/map.ts +++ b/src/ui/map.ts @@ -3714,10 +3714,8 @@ export class Map extends Camera { * }); * */ setSnow(snow?: SnowSpecification | null): this { - Debug.run(() => { - this._lazyInitEmptyStyle(); - this.style.setSnow(snow); - }); + this._lazyInitEmptyStyle(); + this.style.setSnow(snow); return this._update(true); } @@ -3750,10 +3748,8 @@ export class Map extends Camera { * }); * */ setRain(rain?: RainSpecification | null): this { - Debug.run(() => { - this._lazyInitEmptyStyle(); - this.style.setRain(rain); - }); + this._lazyInitEmptyStyle(); + this.style.setRain(rain); return this._update(true); } @@ -4372,6 +4368,11 @@ export class Map extends Camera { this._styleDirty = true; } + // Whenever precipitation effects are present -> force constant redraw + if (this.style && (this.style.snow || this.style.rain)) { + this._styleDirty = true; + } + if (this.style && !this._placementDirty) { // Since no fade operations are in progress, we can release // all tiles held for fading. If we didn't do this, the tiles diff --git a/test/release/local_release_page_list.txt b/test/release/local_release_page_list.txt index 2ad471289d7..3c8d3823d57 100644 --- a/test/release/local_release_page_list.txt +++ b/test/release/local_release_page_list.txt @@ -17,3 +17,4 @@ standard-style.html raster-array.html raster-particle-layer.html featuresets.html +precipitation.html