create-definedmotion 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +3 -0
- package/package.json +37 -0
- package/src/cli.js +100 -0
- package/template/.editorconfig +9 -0
- package/template/.prettierignore +6 -0
- package/template/.prettierrc.yaml +10 -0
- package/template/_gitignore +10 -0
- package/template/build/entitlements.mac.plist +12 -0
- package/template/build/icon.icns +0 -0
- package/template/build/icon.ico +0 -0
- package/template/build/icon.png +0 -0
- package/template/electron-builder.yml +43 -0
- package/template/electron.vite.config.ts +50 -0
- package/template/eslint.config.mjs +24 -0
- package/template/package-lock.json +10299 -0
- package/template/package.json +64 -0
- package/template/resources/icon.png +0 -0
- package/template/src/assets/audio/fadeSound.mp3 +0 -0
- package/template/src/assets/audio/fadeSound2.mp3 +0 -0
- package/template/src/assets/audio/interstellar.mp3 +0 -0
- package/template/src/assets/audio/keyboard1.mp3 +0 -0
- package/template/src/assets/audio/keyboard2.mp3 +0 -0
- package/template/src/assets/audio/keyboard3.mp3 +0 -0
- package/template/src/assets/audio/tick_sound.mp3 +0 -0
- package/template/src/assets/base.css +67 -0
- package/template/src/assets/electron.svg +10 -0
- package/template/src/assets/fonts/Geo-Regular.woff +0 -0
- package/template/src/assets/fonts/Montserrat-Italic-VariableFont_wght.woff2 +0 -0
- package/template/src/assets/fonts/Montserrat-Medium.ttf +0 -0
- package/template/src/assets/fonts/Montserrat-Medium.woff +0 -0
- package/template/src/assets/fonts/Montserrat-VariableFont_wght.woff2 +0 -0
- package/template/src/assets/fonts/glitch.ttf +0 -0
- package/template/src/assets/hdri/indoor1.hdr +0 -0
- package/template/src/assets/hdri/metro1.hdr +0 -0
- package/template/src/assets/hdri/outdoor1.hdr +0 -0
- package/template/src/assets/hdri/photo-studio1.hdr +0 -0
- package/template/src/assets/hdri/photo-studio2.hdr +0 -0
- package/template/src/assets/hdri/photo-studio3.hdr +0 -0
- package/template/src/assets/objects/keyboardScene/ibm-keyboard.glb +0 -0
- package/template/src/assets/wavy-lines.svg +25 -0
- package/template/src/entry.ts +20 -0
- package/template/src/example_scenes/alternativesScene.ts +88 -0
- package/template/src/example_scenes/dependencyScene.ts +116 -0
- package/template/src/example_scenes/fourierMachineScene.ts +108 -0
- package/template/src/example_scenes/fourierSeriesScene.ts +678 -0
- package/template/src/example_scenes/keyboardScene.ts +447 -0
- package/template/src/example_scenes/surfaceScene.ts +88 -0
- package/template/src/example_scenes/tutorials/easy1.ts +59 -0
- package/template/src/example_scenes/tutorials/easy2.ts +141 -0
- package/template/src/example_scenes/tutorials/easy3.ts +133 -0
- package/template/src/example_scenes/tutorials/medium1.ts +154 -0
- package/template/src/example_scenes/vectorField.ts +209 -0
- package/template/src/example_scenes/visulizingFunctions.ts +246 -0
- package/template/src/main/index.ts +101 -0
- package/template/src/main/rendering.ts +219 -0
- package/template/src/main/storage.ts +35 -0
- package/template/src/preload/index.d.ts +8 -0
- package/template/src/preload/index.ts +36 -0
- package/template/src/renderer/index.html +17 -0
- package/template/src/renderer/src/App.svelte +130 -0
- package/template/src/renderer/src/app.css +24 -0
- package/template/src/renderer/src/env.d.ts +2 -0
- package/template/src/renderer/src/lib/animation/animations.ts +214 -0
- package/template/src/renderer/src/lib/animation/captureCanvas.ts +85 -0
- package/template/src/renderer/src/lib/animation/helpers.ts +7 -0
- package/template/src/renderer/src/lib/animation/interpolations.ts +155 -0
- package/template/src/renderer/src/lib/animation/protocols.ts +79 -0
- package/template/src/renderer/src/lib/audio/loader.ts +104 -0
- package/template/src/renderer/src/lib/fonts/Roboto_Regular.json +1 -0
- package/template/src/renderer/src/lib/fonts/montserrat-medium.json +1 -0
- package/template/src/renderer/src/lib/fonts/montserrat.json +1 -0
- package/template/src/renderer/src/lib/general/helpers.ts +77 -0
- package/template/src/renderer/src/lib/general/onDestory.ts +10 -0
- package/template/src/renderer/src/lib/mathHelpers/vectors.ts +18 -0
- package/template/src/renderer/src/lib/rendering/bumpMaps/noise.ts +84 -0
- package/template/src/renderer/src/lib/rendering/helpers.ts +35 -0
- package/template/src/renderer/src/lib/rendering/lighting3d.ts +387 -0
- package/template/src/renderer/src/lib/rendering/materials.ts +6 -0
- package/template/src/renderer/src/lib/rendering/objects/import.ts +148 -0
- package/template/src/renderer/src/lib/rendering/objects2d.ts +489 -0
- package/template/src/renderer/src/lib/rendering/objects3d.ts +89 -0
- package/template/src/renderer/src/lib/rendering/protocols.ts +21 -0
- package/template/src/renderer/src/lib/rendering/setup.ts +71 -0
- package/template/src/renderer/src/lib/rendering/svg/drawing.ts +213 -0
- package/template/src/renderer/src/lib/rendering/svg/parsing.ts +717 -0
- package/template/src/renderer/src/lib/rendering/svg/rastered.ts +42 -0
- package/template/src/renderer/src/lib/rendering/svgObjects.ts +1137 -0
- package/template/src/renderer/src/lib/scene/helpers.ts +89 -0
- package/template/src/renderer/src/lib/scene/sceneClass.ts +648 -0
- package/template/src/renderer/src/lib/shaders/background_gradient/frag.glsl +12 -0
- package/template/src/renderer/src/lib/shaders/background_gradient/vert.glsl +6 -0
- package/template/src/renderer/src/lib/shaders/hdri_blur/frag.glsl +45 -0
- package/template/src/renderer/src/lib/shaders/hdri_blur/vert.glsl +5 -0
- package/template/src/renderer/src/main.ts +9 -0
- package/template/svelte.config.mjs +7 -0
- package/template/tsconfig.json +4 -0
- package/template/tsconfig.node.json +10 -0
- package/template/tsconfig.web.json +32 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AnimatedScene } from '../scene/sceneClass'
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
|
|
4
|
+
const frameValueString = 'frameValueIndex'
|
|
5
|
+
|
|
6
|
+
export const generateID = (numCharacters: number = 10) =>
|
|
7
|
+
Math.random().toString(numCharacters).substr(2, 9)
|
|
8
|
+
|
|
9
|
+
export const updateStateInUrl = (stateValue: number) => {
|
|
10
|
+
const url = new URL(window.location.href)
|
|
11
|
+
url.searchParams.set(frameValueString, stateValue.toString())
|
|
12
|
+
window.history.replaceState(null, '', url.toString())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const setStateInScene = async (scene: AnimatedScene) => {
|
|
16
|
+
const url = new URL(window.location.href)
|
|
17
|
+
const stateParam = url.searchParams.get(frameValueString)
|
|
18
|
+
|
|
19
|
+
if (stateParam) {
|
|
20
|
+
const stateValue = parseInt(stateParam, 10)
|
|
21
|
+
|
|
22
|
+
if (!isNaN(stateValue)) {
|
|
23
|
+
console.log('Restored state:', stateValue)
|
|
24
|
+
await scene.jumpToFrameAtIndex(stateValue)
|
|
25
|
+
return
|
|
26
|
+
} else {
|
|
27
|
+
console.error('Invalid state parameter in URL')
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
await scene.jumpToFrameAtIndex(0)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let lastStateText = ''
|
|
34
|
+
|
|
35
|
+
export const logCameraState = (
|
|
36
|
+
camera: THREE.PerspectiveCamera | THREE.OrthographicCamera
|
|
37
|
+
): void => {
|
|
38
|
+
// Clone all values to prevent reference-related issues
|
|
39
|
+
const position = camera.position.clone()
|
|
40
|
+
const rotation = camera.rotation.clone()
|
|
41
|
+
const quaternion = camera.quaternion.clone()
|
|
42
|
+
|
|
43
|
+
// Format numbers to 7 significant digits and create code-ready string
|
|
44
|
+
const stateCode = `
|
|
45
|
+
scene.camera.position.set(
|
|
46
|
+
${position.x.toPrecision(7)},
|
|
47
|
+
${position.y.toPrecision(7)},
|
|
48
|
+
${position.z.toPrecision(7)}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
scene.camera.rotation.set(
|
|
52
|
+
${rotation.x.toPrecision(7)},
|
|
53
|
+
${rotation.y.toPrecision(7)},
|
|
54
|
+
${rotation.z.toPrecision(7)}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
scene.camera.quaternion.set(
|
|
58
|
+
${quaternion.x.toPrecision(7)},
|
|
59
|
+
${quaternion.y.toPrecision(7)},
|
|
60
|
+
${quaternion.z.toPrecision(7)},
|
|
61
|
+
${quaternion.w.toPrecision(7)}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
scene.camera.rotation.order = '${rotation.order}';
|
|
65
|
+
`
|
|
66
|
+
|
|
67
|
+
// Only update DOM if state changed
|
|
68
|
+
if (stateCode !== lastStateText) {
|
|
69
|
+
lastStateText = stateCode
|
|
70
|
+
const output = document.getElementById('cameraPositionTextID')
|
|
71
|
+
if (output) {
|
|
72
|
+
output.textContent = stateCode
|
|
73
|
+
// Add CSS to preserve whitespace and line breaks
|
|
74
|
+
output.style.whiteSpace = 'pre'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
let allDestroyFunctionsToCall: (() => any)[] = []
|
|
2
|
+
|
|
3
|
+
export const addDestroyFunction = (func: () => any) => allDestroyFunctionsToCall.push(func)
|
|
4
|
+
|
|
5
|
+
export const callAllDestroyFunctions = async () => {
|
|
6
|
+
for (let i = 0; i < allDestroyFunctionsToCall.length; i++) {
|
|
7
|
+
await allDestroyFunctionsToCall[i]()
|
|
8
|
+
}
|
|
9
|
+
allDestroyFunctionsToCall = []
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// NON TESTED FUNCTIONS!
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate `num` evenly‑spaced numbers from `start` to `end` (inclusive).
|
|
5
|
+
*/
|
|
6
|
+
export function linspace(start: number, end: number, num: number): number[] {
|
|
7
|
+
if (num < 2) return num === 1 ? [start] : []
|
|
8
|
+
const step = (end - start) / (num - 1)
|
|
9
|
+
return Array.from({ length: num }, (_v, i) => start + step * i)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Like numpy.arange: start (inclusive) to stop (exclusive) by step.
|
|
14
|
+
*/
|
|
15
|
+
export function arange(start: number, stop: number, step = 1): number[] {
|
|
16
|
+
const length = Math.max(Math.ceil((stop - start) / step), 0)
|
|
17
|
+
return Array.from({ length }, (_v, i) => start + step * i)
|
|
18
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
|
|
3
|
+
export interface BumpMapOptions {
|
|
4
|
+
/** Width of the bump map canvas (default: 256) */
|
|
5
|
+
width?: number
|
|
6
|
+
/** Height of the bump map canvas (default: 256) */
|
|
7
|
+
height?: number
|
|
8
|
+
/**
|
|
9
|
+
* Noise algorithm to use; currently supports:
|
|
10
|
+
* - "random": pure random noise (default)
|
|
11
|
+
* - "perlin": placeholder (requires additional implementation)
|
|
12
|
+
*/
|
|
13
|
+
noiseAlgorithm?: 'random' | 'perlin'
|
|
14
|
+
/**
|
|
15
|
+
* Intensity multiplier for the noise values.
|
|
16
|
+
* Higher values result in a wider contrast between low and high bumps (default: 1).
|
|
17
|
+
*/
|
|
18
|
+
intensity?: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generates a bump map as a THREE.CanvasTexture.
|
|
23
|
+
*
|
|
24
|
+
* @param options - Customization options for the bump map
|
|
25
|
+
* @returns A THREE.CanvasTexture that can be assigned as a bumpMap in a material.
|
|
26
|
+
*/
|
|
27
|
+
export function createBumpMap(options?: BumpMapOptions): THREE.CanvasTexture {
|
|
28
|
+
// Set default options
|
|
29
|
+
const { width = 256, height = 256, noiseAlgorithm = 'random', intensity = 1 } = options || {}
|
|
30
|
+
|
|
31
|
+
// Create a canvas element and get its 2d rendering context
|
|
32
|
+
const canvas: HTMLCanvasElement = document.createElement('canvas')
|
|
33
|
+
canvas.width = width
|
|
34
|
+
canvas.height = height
|
|
35
|
+
const ctx = canvas.getContext('2d')
|
|
36
|
+
|
|
37
|
+
if (!ctx) {
|
|
38
|
+
throw new Error('Could not get canvas 2D context.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create an ImageData object to manipulate pixel values directly
|
|
42
|
+
const imageData = ctx.createImageData(width, height)
|
|
43
|
+
const data = imageData.data
|
|
44
|
+
|
|
45
|
+
// Depending on the noise algorithm choose how to fill the canvas
|
|
46
|
+
if (noiseAlgorithm === 'random') {
|
|
47
|
+
// For each pixel, set a random grayscale value
|
|
48
|
+
for (let i = 0; i < width * height; i++) {
|
|
49
|
+
// Compute the index in the data array. Each pixel takes 4 values (RGBA)
|
|
50
|
+
const index = i * 4
|
|
51
|
+
// A random value in [0, 255] multiplied by the intensity factor,
|
|
52
|
+
// clamped to the [0, 255] range.
|
|
53
|
+
const value = Math.min(255, Math.floor(Math.random() * 255 * intensity))
|
|
54
|
+
data[index] = value // R
|
|
55
|
+
data[index + 1] = value // G
|
|
56
|
+
data[index + 2] = value // B
|
|
57
|
+
data[index + 3] = 255 // A (opaque)
|
|
58
|
+
}
|
|
59
|
+
} else if (noiseAlgorithm === 'perlin') {
|
|
60
|
+
// Placeholder for perlin noise: you would implement or integrate
|
|
61
|
+
// a Perlin noise function here. For now, we'll default to random noise.
|
|
62
|
+
console.warn('Perlin noise algorithm not implemented. Falling back to random noise.')
|
|
63
|
+
for (let i = 0; i < width * height; i++) {
|
|
64
|
+
const index = i * 4
|
|
65
|
+
const value = Math.min(255, Math.floor(Math.random() * 255 * intensity))
|
|
66
|
+
data[index] = value
|
|
67
|
+
data[index + 1] = value
|
|
68
|
+
data[index + 2] = value
|
|
69
|
+
data[index + 3] = 255
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Put the generated pixel data back into the canvas
|
|
74
|
+
ctx.putImageData(imageData, 0, 0)
|
|
75
|
+
|
|
76
|
+
// Create a THREE.CanvasTexture from the canvas and return it.
|
|
77
|
+
const bumpTexture = new THREE.CanvasTexture(canvas)
|
|
78
|
+
|
|
79
|
+
// Optionally, if you need specific properties such as wrapping:
|
|
80
|
+
// bumpTexture.wrapS = THREE.RepeatWrapping;
|
|
81
|
+
// bumpTexture.wrapT = THREE.RepeatWrapping;
|
|
82
|
+
|
|
83
|
+
return bumpTexture
|
|
84
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
|
|
3
|
+
export const COLORS = {
|
|
4
|
+
red: 0xff0000,
|
|
5
|
+
green: 0x00ff00,
|
|
6
|
+
blue: 0x0000ff,
|
|
7
|
+
white: 0xffffff,
|
|
8
|
+
black: 0x000000,
|
|
9
|
+
yellow: 0xffff00,
|
|
10
|
+
cyan: 0x00ffff,
|
|
11
|
+
magenta: 0xff00ff,
|
|
12
|
+
silver: 0xc0c0c0,
|
|
13
|
+
gray: 0x808080,
|
|
14
|
+
grey: 0x808080,
|
|
15
|
+
orange: 0xffa500,
|
|
16
|
+
pink: 0xffc0cb,
|
|
17
|
+
purple: 0x800080,
|
|
18
|
+
brown: 0xa52a2a,
|
|
19
|
+
gold: 0xffd700,
|
|
20
|
+
indigo: 0x4b0082,
|
|
21
|
+
violet: 0xee82ee,
|
|
22
|
+
maroon: 0x800000,
|
|
23
|
+
teal: 0x008080,
|
|
24
|
+
lime: 0x00ff00,
|
|
25
|
+
olive: 0x808000,
|
|
26
|
+
navy: 0x000080
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function sleep(ms: number) {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const hexColor = (hex: string): THREE.Color => {
|
|
34
|
+
return new THREE.Color(hex)
|
|
35
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
|
|
3
|
+
import _photoStudio1 from '$assets/hdri/photo-studio1.hdr?url'
|
|
4
|
+
import _photoStudio2 from '$assets/hdri/photo-studio2.hdr?url'
|
|
5
|
+
import _photoStudio3 from '$assets/hdri/photo-studio3.hdr?url'
|
|
6
|
+
|
|
7
|
+
import _outdoor1 from '$assets/hdri/outdoor1.hdr?url'
|
|
8
|
+
import _indoor1 from '$assets/hdri/indoor1.hdr?url'
|
|
9
|
+
|
|
10
|
+
import _metro1 from '$assets/hdri/metro1.hdr?url'
|
|
11
|
+
|
|
12
|
+
import { AnimatedScene } from '../scene/sceneClass'
|
|
13
|
+
import vert_blur_hdri from '../shaders/hdri_blur/vert.glsl?raw'
|
|
14
|
+
import frag_blur_hdri from '../shaders/hdri_blur/frag.glsl?raw'
|
|
15
|
+
|
|
16
|
+
import vert_background_gradient from '../shaders/background_gradient/vert.glsl?raw'
|
|
17
|
+
import frag_background_gradient from '../shaders/background_gradient/frag.glsl?raw'
|
|
18
|
+
import { COLORS } from './helpers'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for the scene lighting
|
|
22
|
+
*/
|
|
23
|
+
interface LightingOptions {
|
|
24
|
+
/** Whether to add ambient light (default: true) */
|
|
25
|
+
addAmbient?: boolean
|
|
26
|
+
/** Whether to add directional lights (default: true) */
|
|
27
|
+
addDirectional?: boolean
|
|
28
|
+
/** Whether to add spot lights (default: true) */
|
|
29
|
+
addSpot?: boolean
|
|
30
|
+
/** Whether to add light helper objects (default: false) */
|
|
31
|
+
addHelpers?: boolean
|
|
32
|
+
/** Preset color scheme (default: 'cool') */
|
|
33
|
+
colorScheme?: 'cool' | 'warm' | 'contrast' | 'studio' | 'dramatic'
|
|
34
|
+
/** Base intensity multiplier for all lights (default: 1.0) */
|
|
35
|
+
intensity?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collection of lights created by the lighting function
|
|
40
|
+
*/
|
|
41
|
+
interface LightCollection {
|
|
42
|
+
ambient: THREE.AmbientLight | null
|
|
43
|
+
directional: THREE.DirectionalLight[]
|
|
44
|
+
spot: THREE.SpotLight[]
|
|
45
|
+
helpers: THREE.Object3D[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Color scheme definition
|
|
50
|
+
*/
|
|
51
|
+
interface ColorScheme {
|
|
52
|
+
ambient: number
|
|
53
|
+
key: number
|
|
54
|
+
fill: number
|
|
55
|
+
rim: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adds multiple light sources to create a visually appealing lighting setup
|
|
60
|
+
* Scaled appropriately for a standard THREE.GridHelper scene
|
|
61
|
+
*
|
|
62
|
+
* @param scene - The Three.js scene to add lights to
|
|
63
|
+
* @param options - Optional configuration settings
|
|
64
|
+
* @returns Collection of created lights for further manipulation
|
|
65
|
+
*/
|
|
66
|
+
export function addSceneLighting(
|
|
67
|
+
scene: THREE.Scene,
|
|
68
|
+
options: LightingOptions = {}
|
|
69
|
+
): LightCollection {
|
|
70
|
+
// Set default options
|
|
71
|
+
const config: Required<LightingOptions> = {
|
|
72
|
+
addAmbient: true,
|
|
73
|
+
addDirectional: true,
|
|
74
|
+
addSpot: true,
|
|
75
|
+
addHelpers: false,
|
|
76
|
+
colorScheme: 'cool',
|
|
77
|
+
intensity: 1.0,
|
|
78
|
+
...options
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Store all created lights for reference
|
|
82
|
+
const lights: LightCollection = {
|
|
83
|
+
ambient: null,
|
|
84
|
+
directional: [],
|
|
85
|
+
spot: [],
|
|
86
|
+
helpers: []
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Define color schemes
|
|
90
|
+
const colorSchemes: Record<Required<LightingOptions>['colorScheme'], ColorScheme> = {
|
|
91
|
+
cool: {
|
|
92
|
+
ambient: 0x445570,
|
|
93
|
+
key: 0xffffff,
|
|
94
|
+
fill: 0x6495ed, // cornflower blue
|
|
95
|
+
rim: 0x00ffff // cyan
|
|
96
|
+
},
|
|
97
|
+
warm: {
|
|
98
|
+
ambient: 0x553322,
|
|
99
|
+
key: 0xffcc88,
|
|
100
|
+
fill: 0xff8844,
|
|
101
|
+
rim: 0xff3333
|
|
102
|
+
},
|
|
103
|
+
contrast: {
|
|
104
|
+
ambient: 0x222222,
|
|
105
|
+
key: 0xffffff,
|
|
106
|
+
fill: 0x00aaff,
|
|
107
|
+
rim: 0xff0088
|
|
108
|
+
},
|
|
109
|
+
studio: {
|
|
110
|
+
ambient: 0xaaaaaa,
|
|
111
|
+
key: 0xffffff,
|
|
112
|
+
fill: 0xdddddd,
|
|
113
|
+
rim: 0x8888ff
|
|
114
|
+
},
|
|
115
|
+
dramatic: {
|
|
116
|
+
ambient: 0x111122,
|
|
117
|
+
key: 0xffffaa,
|
|
118
|
+
fill: 0x2222ff,
|
|
119
|
+
rim: 0xff00ff
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Get the selected color scheme
|
|
124
|
+
const colors = colorSchemes[config.colorScheme]
|
|
125
|
+
|
|
126
|
+
// Add ambient light
|
|
127
|
+
if (config.addAmbient) {
|
|
128
|
+
const ambient = new THREE.AmbientLight(colors.ambient, 0.5 * config.intensity)
|
|
129
|
+
scene.add(ambient)
|
|
130
|
+
lights.ambient = ambient
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Add directional lights (key, fill, rim setup)
|
|
134
|
+
if (config.addDirectional) {
|
|
135
|
+
// Key light (main light)
|
|
136
|
+
const keyLight = new THREE.DirectionalLight(colors.key, 1.0 * config.intensity)
|
|
137
|
+
// Position scaled for standard grid (10x10)
|
|
138
|
+
keyLight.position.set(5, 7, 5)
|
|
139
|
+
keyLight.castShadow = true
|
|
140
|
+
|
|
141
|
+
// Configure shadows for better quality
|
|
142
|
+
keyLight.shadow.mapSize.width = 2048
|
|
143
|
+
keyLight.shadow.mapSize.height = 2048
|
|
144
|
+
keyLight.shadow.camera.near = 0.5
|
|
145
|
+
keyLight.shadow.camera.far = 30
|
|
146
|
+
keyLight.shadow.camera.left = -10
|
|
147
|
+
keyLight.shadow.camera.right = 10
|
|
148
|
+
keyLight.shadow.camera.top = 10
|
|
149
|
+
keyLight.shadow.camera.bottom = -10
|
|
150
|
+
keyLight.shadow.bias = -0.0001
|
|
151
|
+
|
|
152
|
+
scene.add(keyLight)
|
|
153
|
+
lights.directional.push(keyLight)
|
|
154
|
+
|
|
155
|
+
// Fill light (secondary light to fill shadows)
|
|
156
|
+
const fillLight = new THREE.DirectionalLight(colors.fill, 0.6 * config.intensity)
|
|
157
|
+
fillLight.position.set(-5, 4, -3)
|
|
158
|
+
scene.add(fillLight)
|
|
159
|
+
lights.directional.push(fillLight)
|
|
160
|
+
|
|
161
|
+
// Rim light (edge highlighting from behind)
|
|
162
|
+
const rimLight = new THREE.DirectionalLight(colors.rim, 0.8 * config.intensity)
|
|
163
|
+
rimLight.position.set(2, 3, -8)
|
|
164
|
+
scene.add(rimLight)
|
|
165
|
+
lights.directional.push(rimLight)
|
|
166
|
+
|
|
167
|
+
// Add helpers if requested
|
|
168
|
+
if (config.addHelpers) {
|
|
169
|
+
const keyHelper = new THREE.DirectionalLightHelper(keyLight, 1)
|
|
170
|
+
const fillHelper = new THREE.DirectionalLightHelper(fillLight, 1)
|
|
171
|
+
const rimHelper = new THREE.DirectionalLightHelper(rimLight, 1)
|
|
172
|
+
|
|
173
|
+
scene.add(keyHelper)
|
|
174
|
+
scene.add(fillHelper)
|
|
175
|
+
scene.add(rimHelper)
|
|
176
|
+
|
|
177
|
+
lights.helpers.push(keyHelper, fillHelper, rimHelper)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Add spot lights for additional emphasis
|
|
182
|
+
if (config.addSpot) {
|
|
183
|
+
// Spot from above
|
|
184
|
+
const topSpot = new THREE.SpotLight(
|
|
185
|
+
colors.key,
|
|
186
|
+
0.8 * config.intensity,
|
|
187
|
+
30, // distance
|
|
188
|
+
Math.PI / 6, // angle
|
|
189
|
+
0.5, // penumbra
|
|
190
|
+
1 // decay
|
|
191
|
+
)
|
|
192
|
+
topSpot.position.set(0, 15, 0)
|
|
193
|
+
topSpot.castShadow = true
|
|
194
|
+
|
|
195
|
+
// Configure spotlight shadows
|
|
196
|
+
topSpot.shadow.mapSize.width = 1024
|
|
197
|
+
topSpot.shadow.mapSize.height = 1024
|
|
198
|
+
topSpot.shadow.camera.near = 1
|
|
199
|
+
topSpot.shadow.camera.far = 30
|
|
200
|
+
|
|
201
|
+
scene.add(topSpot)
|
|
202
|
+
lights.spot.push(topSpot)
|
|
203
|
+
|
|
204
|
+
// Accent spot
|
|
205
|
+
const accentSpot = new THREE.SpotLight(
|
|
206
|
+
colors.rim,
|
|
207
|
+
0.7 * config.intensity,
|
|
208
|
+
20,
|
|
209
|
+
Math.PI / 8,
|
|
210
|
+
0.6,
|
|
211
|
+
1.5
|
|
212
|
+
)
|
|
213
|
+
accentSpot.position.set(-7, 4, -7)
|
|
214
|
+
scene.add(accentSpot)
|
|
215
|
+
lights.spot.push(accentSpot)
|
|
216
|
+
|
|
217
|
+
// Add helpers if requested
|
|
218
|
+
if (config.addHelpers) {
|
|
219
|
+
const topSpotHelper = new THREE.SpotLightHelper(topSpot)
|
|
220
|
+
const accentSpotHelper = new THREE.SpotLightHelper(accentSpot)
|
|
221
|
+
|
|
222
|
+
scene.add(topSpotHelper)
|
|
223
|
+
scene.add(accentSpotHelper)
|
|
224
|
+
|
|
225
|
+
lights.helpers.push(topSpotHelper, accentSpotHelper)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Return all created lights for further manipulation
|
|
230
|
+
return lights
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Creates a scene with standard grid, axes, and lighting
|
|
235
|
+
*
|
|
236
|
+
* @param scene - The Three.js scene to set up
|
|
237
|
+
* @param lightingOptions - Optional lighting configuration
|
|
238
|
+
* @returns The created lights collection
|
|
239
|
+
*/
|
|
240
|
+
export function setupStandardScene(
|
|
241
|
+
scene: THREE.Scene,
|
|
242
|
+
lightingOptions: LightingOptions = {}
|
|
243
|
+
): LightCollection {
|
|
244
|
+
// Add grid
|
|
245
|
+
const gridHelper = new THREE.GridHelper(10, 10)
|
|
246
|
+
scene.add(gridHelper)
|
|
247
|
+
|
|
248
|
+
// Add axes
|
|
249
|
+
const axesHelper = new THREE.AxesHelper(5)
|
|
250
|
+
scene.add(axesHelper)
|
|
251
|
+
|
|
252
|
+
// Add lights and return them
|
|
253
|
+
return addSceneLighting(scene, lightingOptions)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface HDRIData {
|
|
257
|
+
texture: THREE.DataTexture
|
|
258
|
+
material: THREE.Material
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export const loadHDRIData = async (
|
|
262
|
+
path: HDRIs | string,
|
|
263
|
+
blurAmount: number,
|
|
264
|
+
opacity: number = 1
|
|
265
|
+
): Promise<HDRIData> => {
|
|
266
|
+
const rgbeLoader: RGBELoader = new RGBELoader()
|
|
267
|
+
|
|
268
|
+
return new Promise((resolve, reject) => {
|
|
269
|
+
rgbeLoader.setDataType(THREE.FloatType).load(
|
|
270
|
+
path as any,
|
|
271
|
+
(texture: THREE.DataTexture): void => {
|
|
272
|
+
const blurredMaterial = new THREE.ShaderMaterial({
|
|
273
|
+
uniforms: {
|
|
274
|
+
uTexture: { value: texture },
|
|
275
|
+
uBlurAmount: { value: blurAmount }, // Increase for more blur
|
|
276
|
+
uTextureSize: {
|
|
277
|
+
value: new THREE.Vector2(texture.image.width, texture.image.height)
|
|
278
|
+
},
|
|
279
|
+
sigma: { value: 3.0 }, // Add this uniform
|
|
280
|
+
opacity: { value: opacity }
|
|
281
|
+
},
|
|
282
|
+
vertexShader: vert_blur_hdri,
|
|
283
|
+
fragmentShader: frag_blur_hdri,
|
|
284
|
+
side: THREE.BackSide,
|
|
285
|
+
transparent: true
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
resolve({
|
|
289
|
+
texture,
|
|
290
|
+
material: blurredMaterial
|
|
291
|
+
})
|
|
292
|
+
},
|
|
293
|
+
(_) => {},
|
|
294
|
+
// Optional error callback
|
|
295
|
+
(error): void => {
|
|
296
|
+
console.error('Error loading HDRI:', error)
|
|
297
|
+
reject(error)
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export enum HDRIs {
|
|
304
|
+
photoStudio1 = _photoStudio1 as any,
|
|
305
|
+
photoStudio2 = _photoStudio2 as any,
|
|
306
|
+
photoStudio3 = _photoStudio3 as any,
|
|
307
|
+
outdoor1 = _outdoor1 as any,
|
|
308
|
+
indoor1 = _indoor1 as any,
|
|
309
|
+
metro1 = _metro1 as any
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export async function addHDRI(scene: AnimatedScene, hdriData: HDRIData, lightingIntensity = 1.0) {
|
|
313
|
+
// Create PMREM Generator for converting equirectangular HDRI to cubemap
|
|
314
|
+
const pmremGenerator: THREE.PMREMGenerator = new THREE.PMREMGenerator(scene.renderer)
|
|
315
|
+
pmremGenerator.compileEquirectangularShader()
|
|
316
|
+
|
|
317
|
+
// Process the HDRI texture
|
|
318
|
+
const envMap: THREE.Texture = pmremGenerator.fromEquirectangular(hdriData.texture).texture
|
|
319
|
+
|
|
320
|
+
// Create background sphere
|
|
321
|
+
const geometry = new THREE.SphereGeometry(scene.farLimitRender / 2, 40, 40)
|
|
322
|
+
|
|
323
|
+
const backgroundSphere = new THREE.Mesh(geometry, hdriData.material)
|
|
324
|
+
backgroundSphere.renderOrder = -1 // Render before other objects
|
|
325
|
+
|
|
326
|
+
// Attach to camera
|
|
327
|
+
scene.scene.add(backgroundSphere)
|
|
328
|
+
|
|
329
|
+
// Apply to scene environment (for reflections)
|
|
330
|
+
scene.scene.environment = envMap
|
|
331
|
+
|
|
332
|
+
scene.scene.environmentIntensity = lightingIntensity
|
|
333
|
+
|
|
334
|
+
// Clean up resources
|
|
335
|
+
// texture.dispose()
|
|
336
|
+
pmremGenerator.dispose()
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function addBackgroundGradient({
|
|
340
|
+
scene,
|
|
341
|
+
topColor, // Sky blue
|
|
342
|
+
bottomColor, // White
|
|
343
|
+
backgroundOpacity = 1.0,
|
|
344
|
+
lightingIntensity = 1.0,
|
|
345
|
+
addLighting = true
|
|
346
|
+
}: {
|
|
347
|
+
scene: AnimatedScene
|
|
348
|
+
topColor: THREE.ColorRepresentation
|
|
349
|
+
bottomColor: THREE.ColorRepresentation
|
|
350
|
+
backgroundOpacity?: number
|
|
351
|
+
lightingIntensity?: number
|
|
352
|
+
radius?: number
|
|
353
|
+
addLighting?: boolean
|
|
354
|
+
}): void {
|
|
355
|
+
// Convert ColorRepresentation to Color instances
|
|
356
|
+
const top = new THREE.Color(topColor)
|
|
357
|
+
const bottom = new THREE.Color(bottomColor)
|
|
358
|
+
|
|
359
|
+
const sphereRadius = scene.farLimitRender / 2.2
|
|
360
|
+
|
|
361
|
+
// Create background sphere
|
|
362
|
+
const geometry = new THREE.SphereGeometry(sphereRadius, 40, 40)
|
|
363
|
+
const gradientMaterial = new THREE.ShaderMaterial({
|
|
364
|
+
vertexShader: vert_background_gradient,
|
|
365
|
+
fragmentShader: frag_background_gradient,
|
|
366
|
+
uniforms: {
|
|
367
|
+
topColor: { value: top },
|
|
368
|
+
bottomColor: { value: bottom },
|
|
369
|
+
opacity: { value: backgroundOpacity }
|
|
370
|
+
},
|
|
371
|
+
side: THREE.BackSide,
|
|
372
|
+
transparent: backgroundOpacity < 1.0
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const backgroundSphere = new THREE.Mesh(geometry, gradientMaterial)
|
|
376
|
+
backgroundSphere.renderOrder = -1
|
|
377
|
+
scene.scene.add(backgroundSphere)
|
|
378
|
+
|
|
379
|
+
// Add hemisphere lighting that matches the gradient colors
|
|
380
|
+
if (addLighting) {
|
|
381
|
+
const hemisphereLight = new THREE.HemisphereLight(top, bottom, lightingIntensity)
|
|
382
|
+
|
|
383
|
+
// Optional: Position the light if needed, though HemisphereLight works from everywhere
|
|
384
|
+
hemisphereLight.position.set(0, 1, 0)
|
|
385
|
+
scene.scene.add(hemisphereLight)
|
|
386
|
+
}
|
|
387
|
+
}
|