Skip to content

Commit

Permalink
Xmas: Let it snow (#3731)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mikehrn authored Dec 20, 2024
1 parent 1851f74 commit d54eeb5
Show file tree
Hide file tree
Showing 12 changed files with 925 additions and 4 deletions.
1 change: 1 addition & 0 deletions packages/frontend-2/components/viewer/menu/Item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<span v-if="shortcut" class="text-body-2xs text-foreground-2">
{{ shortcut }}
</span>
<slot />
</button>
</template>

Expand Down
19 changes: 17 additions & 2 deletions packages/frontend-2/components/viewer/view-modes/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
@click="handleViewModeChange(shortcut.viewMode)"
/>
</div>
<ViewerMenuItem
label="Let it snow"
:active="snowingEnabled"
@click="onSnowModeClick"
>
🎅
</ViewerMenuItem>
</div>
</ViewerMenu>
</template>
Expand All @@ -32,7 +39,7 @@ import { ViewModeShortcuts } from '~/lib/viewer/helpers/shortcuts/shortcuts'

const open = defineModel<boolean>('open', { default: false })

const { setViewMode, currentViewMode } = useViewModeUtilities()
const { setViewMode, currentViewMode, letItSnow } = useViewModeUtilities()
const { getShortcutDisplayText, registerShortcuts } = useViewerShortcuts()
const mp = useMixpanel()

Expand All @@ -55,15 +62,18 @@ registerShortcuts({
SetViewModeColors: () => handleViewModeChange(ViewMode.COLORS, true)
})

const isActiveMode = (mode: ViewMode) => mode === currentViewMode.value
const isActiveMode = (mode: ViewMode) =>
snowingEnabled.value ? false : mode === currentViewMode.value

const viewModeShortcuts = Object.values(ViewModeShortcuts)
const snowingEnabled = ref(false)

const emit = defineEmits<{
(e: 'force-close-others'): void
}>()

const handleViewModeChange = (mode: ViewMode, isShortcut = false) => {
snowingEnabled.value = false
setViewMode(mode)
cancelCloseTimer()

Expand All @@ -80,6 +90,11 @@ const handleViewModeChange = (mode: ViewMode, isShortcut = false) => {
})
}

const onSnowModeClick = () => {
snowingEnabled.value = true
letItSnow()
}

onUnmounted(() => {
cancelCloseTimer()
})
Expand Down
10 changes: 9 additions & 1 deletion packages/frontend-2/lib/viewer/composables/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {
ViewerShortcutAction
} from '~/lib/viewer/helpers/shortcuts/types'
import { useActiveElement } from '@vueuse/core'
import { SnowPipeline } from '~/lib/viewer/pipelines/snow/SnowPipeline'

export function useSectionBoxUtilities() {
const { instance } = useInjectedViewer()
Expand Down Expand Up @@ -501,9 +502,16 @@ export function useViewModeUtilities() {
}
}

const letItSnow = () => {
const snowPipeline = new SnowPipeline(instance.getRenderer())
instance.getRenderer().pipeline = snowPipeline
void snowPipeline.start()
}

return {
currentViewMode,
setViewMode
setViewMode,
letItSnow
}
}

Expand Down
68 changes: 68 additions & 0 deletions packages/frontend-2/lib/viewer/pipelines/snow/SnowFallPass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { BaseGPass } from '@speckle/viewer'
import {
AdditiveBlending,
type OrthographicCamera,
type PerspectiveCamera,
ShaderMaterial,
Vector2,
type WebGLRenderer
} from 'three'
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js'
import { snowfallFrag } from './snowfallFrag'
import { snowfallVert } from './snowfallVert'

export class SnowFallPass extends BaseGPass {
public snowfallMaterial: ShaderMaterial
private fsQuad: FullScreenQuad
private lastFrameTime: number = 0
private totalTime: number = 0

public constructor() {
super()

this.snowfallMaterial = new ShaderMaterial({
fragmentShader: snowfallFrag,
vertexShader: snowfallVert,
uniforms: {
iTime: { value: 0 },
iResolution: { value: new Vector2(512, 512) }
}
})
this.snowfallMaterial.depthWrite = false
this.snowfallMaterial.blending = AdditiveBlending
this.snowfallMaterial.transparent = true

this.fsQuad = new FullScreenQuad(this.snowfallMaterial)
}

public get displayName(): string {
return 'SNOWFALL'
}

public update(_camera: PerspectiveCamera | OrthographicCamera) {
if (this.lastFrameTime === 0) {
this.lastFrameTime = performance.now()
return
}
const now = performance.now()
this.totalTime += now - this.lastFrameTime
this.lastFrameTime = now
this.snowfallMaterial.uniforms['iTime'].value = this.totalTime / 1000
this.snowfallMaterial.needsUpdate = true
}

public render(renderer: WebGLRenderer): boolean {
if (this.onBeforeRender) this.onBeforeRender()

this.fsQuad.render(renderer)
if (this.onAfterRender) this.onAfterRender()
return true
}

public setSize(width: number, height: number) {
super.setSize(width, height)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
this.snowfallMaterial.uniforms['iResolution'].value.set(width, height)
this.snowfallMaterial.needsUpdate = true
}
}
76 changes: 76 additions & 0 deletions packages/frontend-2/lib/viewer/pipelines/snow/SnowMaterial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { SpeckleStandardMaterial, type SpeckleWebGLRenderer } from '@speckle/viewer'
import {
type MeshStandardMaterialParameters,
type Scene,
type Camera,
type BufferGeometry,
type Object3D,
Box3,
Vector3
} from 'three'
import { objectSnowVert } from './objectSnowVert'
import { objectSnowFrag } from './objectSnowFrag'

class SnowMaterial extends SpeckleStandardMaterial {
private minSnowValue = 0
private maxSnowValue = 0
private lastFrameTime = 0
private increaseFactor = 500000

protected get vertexProgram(): string {
return objectSnowVert
}

protected get fragmentProgram(): string {
return objectSnowFrag
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected get uniformsDef(): Record<string, any> {
return {
...super.uniformsDef,
height: 1,
minSnow: this.minSnowValue,
maxSnow: this.maxSnowValue
}
}

constructor(parameters: MeshStandardMaterialParameters, defines = ['USE_RTE']) {
super(parameters, defines)
}

/** Called by three.js render loop */
public onBeforeRender(
_this: SpeckleWebGLRenderer,
_scene: Scene,
_camera: Camera,
_geometry: BufferGeometry,
object: Object3D
) {
super.onBeforeRender(_this, _scene, _camera, _geometry, object)

const sceneHeight = new Box3().setFromObject(_scene).getSize(new Vector3())
this.userData.height.value = sceneHeight.y

const now = performance.now()
if (this.lastFrameTime === 0) {
this.lastFrameTime = now
return
}
const delta = now - this.lastFrameTime
this.lastFrameTime = now

this.minSnowValue += 1 / (this.increaseFactor + delta) + 1 / this.increaseFactor
this.maxSnowValue +=
1 / (this.increaseFactor * 0.5 + delta) + 1 / (this.increaseFactor * 0.5)

this.userData.minSnow.value = Math.min(this.minSnowValue, 0.8)
this.userData.maxSnow.value = Math.min(this.maxSnowValue, 0.9)

this.increaseFactor -= 1000 - this.increaseFactor / 1000
this.increaseFactor = Math.max(this.increaseFactor, 5000)
}
}

export default SnowMaterial
148 changes: 148 additions & 0 deletions packages/frontend-2/lib/viewer/pipelines/snow/SnowPipeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import type {
SpeckleRenderer,
MeshBatch,
SpeckleStandardMaterial
} from '@speckle/viewer'
import {
ProgressivePipeline,
DepthPass,
ObjectLayers,
ObjectVisibility,
ClearFlags,
GeometryPass,
ProgressiveAOPass,
BlendPass,
StencilPass,
StencilMaskPass,
GeometryType,
Assets,
AssetType
} from '@speckle/viewer'
import SnowMaterial from './SnowMaterial'
import type SpeckleMesh from '@speckle/viewer/dist/modules/objects/SpeckleMesh'
import { RepeatWrapping, NearestFilter } from 'three'
import snowTex from './snow.png'
import { SnowFallPass } from './SnowFallPass'

export class SnowPipeline extends ProgressivePipeline {
constructor(speckleRenderer: SpeckleRenderer) {
super(speckleRenderer)

const depthPass = new DepthPass()
depthPass.setLayers([ObjectLayers.STREAM_CONTENT_MESH])
depthPass.setVisibility(ObjectVisibility.DEPTH)
depthPass.setJitter(true)
depthPass.setClearColor(0x000000, 1)
depthPass.setClearFlags(ClearFlags.COLOR | ClearFlags.DEPTH)

const opaqueColorPass = new GeometryPass()
opaqueColorPass.setLayers([
ObjectLayers.STREAM_CONTENT,
ObjectLayers.STREAM_CONTENT_MESH,
ObjectLayers.STREAM_CONTENT_LINE,
ObjectLayers.STREAM_CONTENT_POINT,
ObjectLayers.STREAM_CONTENT_POINT_CLOUD,
ObjectLayers.STREAM_CONTENT_TEXT
])
opaqueColorPass.setVisibility(ObjectVisibility.OPAQUE)

const transparentColorPass = new GeometryPass()
transparentColorPass.setLayers([
ObjectLayers.STREAM_CONTENT,
ObjectLayers.STREAM_CONTENT_MESH,
ObjectLayers.STREAM_CONTENT_LINE,
ObjectLayers.STREAM_CONTENT_POINT,
ObjectLayers.STREAM_CONTENT_POINT_CLOUD,
ObjectLayers.STREAM_CONTENT_TEXT,
ObjectLayers.SHADOWCATCHER
])
transparentColorPass.setVisibility(ObjectVisibility.TRANSPARENT)

const progressiveAOPass = new ProgressiveAOPass()
progressiveAOPass.setTexture('tDepth', depthPass.outputTarget?.texture)
progressiveAOPass.accumulationFrames = this.accumulationFrameCount
progressiveAOPass.setClearColor(0xffffff, 1)

const blendPass = new BlendPass()
blendPass.options = { blendAO: true, blendEdges: false }
blendPass.setTexture('tAo', progressiveAOPass.outputTarget?.texture)
blendPass.accumulationFrames = this.accumulationFrameCount

const stencilPass = new StencilPass()
stencilPass.setVisibility(ObjectVisibility.STENCIL)
stencilPass.setLayers([ObjectLayers.STREAM_CONTENT_MESH])

const stencilMaskPass = new StencilMaskPass()
stencilMaskPass.setVisibility(ObjectVisibility.STENCIL)
stencilMaskPass.setLayers([ObjectLayers.STREAM_CONTENT_MESH])
stencilMaskPass.setClearFlags(ClearFlags.DEPTH)

const overlayPass = new GeometryPass()
overlayPass.setLayers([
ObjectLayers.PROPS,
ObjectLayers.OVERLAY,
ObjectLayers.MEASUREMENTS
])

const snowfallPass = new SnowFallPass()
snowfallPass.setClearColor(0x000000, 1)

this.dynamicStage.push(
stencilPass,
opaqueColorPass,
transparentColorPass,
stencilMaskPass,
overlayPass,
snowfallPass
)
this.progressiveStage.push(
depthPass,
stencilPass,
opaqueColorPass,
transparentColorPass,
stencilMaskPass,
progressiveAOPass,
blendPass,
overlayPass,
snowfallPass
)
this.passthroughStage.push(
stencilPass,
opaqueColorPass,
transparentColorPass,
stencilMaskPass,
blendPass,
overlayPass,
snowfallPass
)

this.passList = this.dynamicStage
}

public async start() {
const snowTexture = await Assets.getTexture({
id: 'snow',
src: snowTex,
type: AssetType.TEXTURE_8BPP
})
snowTexture.wrapS = RepeatWrapping
snowTexture.wrapT = RepeatWrapping
snowTexture.minFilter = NearestFilter
snowTexture.magFilter = NearestFilter

const batches: MeshBatch[] = this.speckleRenderer.batcher.getBatches(
undefined,
GeometryType.MESH
)

for (let k = 0; k < batches.length; k++) {
const batchRenderable: SpeckleMesh = batches[k].renderObject as SpeckleMesh
const batchMaterial: SpeckleStandardMaterial = batches[k]
.batchMaterial as SpeckleStandardMaterial
const snowMaterial = new SnowMaterial({}, ['USE_RTE'])
snowMaterial.copy(batchMaterial)
snowMaterial.normalMap = snowTexture
batchRenderable.setOverrideBatchMaterial(snowMaterial)
}
}
}
Loading

0 comments on commit d54eeb5

Please sign in to comment.