Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xmas: Let it snow #3731

Merged
merged 9 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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