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

feat: add cloud #56

Merged
merged 1 commit into from
Mar 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
165 changes: 165 additions & 0 deletions .storybook/stories/Clouds.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import * as THREE from 'three'
import { Setup } from '../Setup'
import GUI from 'lil-gui'
import { Meta } from '@storybook/html'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { CLOUD_URL, Clouds, Cloud } from '../../src/core/Cloud'

export default {
title: 'Staging/Clouds',
} as Meta // TODO: this should be `satisfies Meta` but commit hooks lag behind TS

let gui: GUI
let scene: THREE.Scene,
camera: THREE.PerspectiveCamera,
renderer: THREE.WebGLRenderer,
clock: THREE.Clock,
animateLoop: (arg0: (time: number) => void) => void

const textureLoader = new THREE.TextureLoader()

export const CloudStory = async () => {
const setupResult = Setup()
scene = setupResult.scene
camera = setupResult.camera
renderer = setupResult.renderer
animateLoop = setupResult.render
clock = new THREE.Clock()

gui = new GUI({ title: CloudStory.storyName })
renderer.shadowMap.enabled = true
renderer.toneMapping = THREE.ACESFilmicToneMapping
camera.position.set(12, 3, 12)

new OrbitControls(camera, renderer.domElement)

scene.background = new THREE.Color('skyblue')
setupLights()
setupCloud()
}

const setupLights = () => {
// cloud's default material does not react to hdri, so we need to add punctual lights

const lightFol = gui.addFolder('Lights').close()
const ambientLight = new THREE.AmbientLight()
scene.add(ambientLight)
lightFol.add(ambientLight, 'intensity', 0, 3)
const guiParams = {
lightHelpers: false,
}

const lightHelpers: THREE.SpotLightHelper[] = []

lightFol.add(guiParams, 'lightHelpers').onChange((v: boolean) => {
lightHelpers.forEach((helper) => (helper.visible = v))
})

function addSpotlightGui(spotLight: THREE.SpotLight) {
const fol = lightFol.addFolder('spotlight')
fol.onChange(() => {
lightHelpers.forEach((helper) => helper.update())
})

fol.addColor(spotLight, 'color')
fol.add(spotLight, 'intensity', 0, 30)
fol.add(spotLight, 'angle', 0, Math.PI / 8)

fol.add(spotLight, 'penumbra', -1, 1)
}

const spotLight1 = new THREE.SpotLight()
spotLight1.intensity = 30
spotLight1.position.fromArray([0, 40, 0])
spotLight1.distance = 45
spotLight1.decay = 0
spotLight1.penumbra = 1
spotLight1.intensity = 30
const helper1 = new THREE.SpotLightHelper(spotLight1)
addSpotlightGui(spotLight1)
helper1.visible = false
lightHelpers.push(helper1)
scene.add(spotLight1, helper1)

const spotLight2 = new THREE.SpotLight('red')
spotLight2.intensity = 30
spotLight2.position.fromArray([-20, 0, 10])
spotLight2.angle = 0.15
spotLight2.decay = 0
spotLight2.penumbra = -1
spotLight2.intensity = 30
addSpotlightGui(spotLight2)

const helper2 = new THREE.SpotLightHelper(spotLight2)
helper2.visible = false
lightHelpers.push(helper2)
scene.add(spotLight2, helper2)

const spotLight3 = new THREE.SpotLight('green')
spotLight3.intensity = 30
spotLight3.position.fromArray([20, -10, 10])
spotLight3.angle = 0.2
spotLight3.decay = 0
spotLight3.penumbra = -1
spotLight3.intensity = 20
addSpotlightGui(spotLight3)

const helper3 = new THREE.SpotLightHelper(spotLight3)
helper3.visible = false
lightHelpers.push(helper3)
scene.add(spotLight3, helper3)
}

const setupCloud = async () => {
const cloudTexture = await textureLoader.loadAsync(CLOUD_URL)

// create main clouds group
const clouds = new Clouds({ texture: cloudTexture })
scene.add(clouds)

// create first cloud
const cloud0 = new Cloud()
clouds.add(cloud0)
addCloudGui(cloud0)

// create second cloud
const cloud1 = new Cloud()
cloud1.color.set('#111111')
cloud1.position.set(-10, 4, -5)
clouds.add(cloud1)
addCloudGui(cloud1)

animateLoop(() => {
// update clouds on each frame
clouds.update(camera, clock.getElapsedTime(), clock.getDelta())
})
}

const addCloudGui = (cloud: Cloud) => {
const fol = gui.addFolder('Edit: ' + cloud.name)

// during runtime call "cloud.updateCloud()" after changing any cloud property
fol.onChange(() => cloud.updateClouds())

fol.addColor(cloud, 'color')
fol.add(cloud, 'seed', 0, 100, 1)
fol.add(cloud, 'segments', 1, 80, 1)
fol.add(cloud, 'volume', 0, 100, 0.1)
fol.add(cloud, 'opacity', 0, 1, 0.01)
fol.add(cloud, 'fade', 0, 400, 1)
fol.add(cloud, 'growth', 0, 20, 1)
fol.add(cloud, 'speed', 0, 1, 0.01)

const bFol = fol.addFolder('bounds').close()
bFol.add(cloud.bounds, 'x', 0, 25, 0.5)
bFol.add(cloud.bounds, 'y', 0, 25, 0.5)
bFol.add(cloud.bounds, 'z', 0, 25, 0.5)

const pFol = fol.addFolder('position').close()
pFol.add(cloud.position, 'x', -10, 10, 0.1)
pFol.add(cloud.position, 'y', -10, 10, 0.1)
pFol.add(cloud.position, 'z', -10, 10, 0.1)
return fol
}

CloudStory.storyName = 'Two Clouds'
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { pcss, ... } from '@pmndrs/vanilla'
<ul>
<li><a href="#accumulativeshadows">AccumulativeShadows</a></li>
<li><a href="#caustics">Caustics</a></li>
<li><a href="#cloud">Cloud</a></li>
</ul>
<li><a href="#staging">Abstractions</a></li>
<ul>
Expand All @@ -58,7 +59,7 @@ import { pcss, ... } from '@pmndrs/vanilla'
<li><a href="#splat">Splat</a></li>
</ul>
<li><a href="#gizmos">Gizmos</a></li>
<ul>
<ul>
<li><a href="#grid">Grid</a></li>
</ul>
<li><a href="#misc">Misc</a></li>
Expand Down Expand Up @@ -359,6 +360,76 @@ export type CausticsType = {
}
```

#### Cloud

[![storybook](https://img.shields.io/badge/-storybook-%23ff69b4)](https://pmndrs.github.io/drei-vanilla/?path=/story/staging-clouds--cloud-story)

[drei counterpart](https://github.com/pmndrs/drei#cloud)

Instanced Mesh/Particle based cloud.

```tsx
type CloudsProps = {
/** cloud texture*/
texture?: Texture | undefined
/** Maximum number of segments, default: 200 (make this tight to save memory!) */
limit?: number
/** How many segments it renders, default: undefined (all) */
range?: number
/** Which material it will override, default: MeshLambertMaterial */
material?: typeof Material
/** Frustum culling, default: true */
frustumCulled?: boolean
}
```

```ts
type CloudProps = {
/** A seeded random will show the same cloud consistently, default: Math.random() */
seed?: number
/** How many segments or particles the cloud will have, default: 20 */
segments?: number
/** The box3 bounds of the cloud, default: [5, 1, 1] */
bounds?: Vector3
/** How to arrange segment volume inside the bounds, default: inside (cloud are smaller at the edges) */
concentrate?: 'random' | 'inside' | 'outside'
/** The general scale of the segments */
scale?: Vector3
/** The volume/thickness of the segments, default: 6 */
volume?: number
/** The smallest volume when distributing clouds, default: 0.25 */
smallestVolume?: number
/** An optional function that allows you to distribute points and volumes (overriding all settings), default: null
* Both point and volume are factors, point x/y/z can be between -1 and 1, volume between 0 and 1 */
distribute?: ((cloud: CloudState, index: number) => { point: Vector3; volume?: number }) | null
/** Growth factor for animated clouds (speed > 0), default: 4 */
growth?: number
/** Animation factor, default: 0 */
speed?: number
/** Camera distance until the segments will fade, default: 10 */
fade?: number
/** Opacity, default: 1 */
opacity?: number
/** Color, default: white */
color?: Color
}
```

Usage

```jsx
// create main clouds group
clouds = new Clouds({ texture: cloudTexture })
scene.add(clouds)

// create cloud and add it to clouds group
cloud0 = new Cloud()
clouds.add(cloud0)

// call in animate loop
clouds.update(camera, clock.getElapsedTime(), clock.getDelta())
```

#### Grid

[![storybook](https://img.shields.io/badge/-storybook-%23ff69b4)](https://pmndrs.github.io/drei-vanilla/?path=/story/gizmos-grid--grid-story)
Expand Down
Loading