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,141 @@
|
|
|
1
|
+
// Tutorial 2 (easy2.ts)
|
|
2
|
+
// Goal for this animation:
|
|
3
|
+
// 1) Render a time-varying mathematical surface (z = f(x, y, t))
|
|
4
|
+
// 2) Add a glowing orb with a point light
|
|
5
|
+
// 3) Animate the camera on a smooth orbit while the surface deforms
|
|
6
|
+
|
|
7
|
+
import * as THREE from 'three'
|
|
8
|
+
|
|
9
|
+
import { AnimatedScene, HotReloadSetting, SpaceSetting } from '$renderer/lib/scene/sceneClass'
|
|
10
|
+
|
|
11
|
+
// If your helpers live elsewhere, tweak these paths:'
|
|
12
|
+
import { createFunctionSurface, updateFunctionSurface } from '$renderer/lib/rendering/objects3d'
|
|
13
|
+
import { addBackgroundGradient } from '$renderer/lib/rendering/lighting3d'
|
|
14
|
+
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
// Step 0: Materials used by our surface and our glowing sphere
|
|
17
|
+
// MeshStandardMaterial gives us physically-based shading that reacts to lights.
|
|
18
|
+
// For the sphere, we use a strong emissive color so it "glows" even without light.
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
const surfaceMaterial = new THREE.MeshStandardMaterial({
|
|
21
|
+
color: 0xffffff,
|
|
22
|
+
metalness: 0.8,
|
|
23
|
+
roughness: 0.1,
|
|
24
|
+
side: THREE.DoubleSide
|
|
25
|
+
}) as any // cast to any to satisfy TS if createFunctionSurface has a stricter type
|
|
26
|
+
|
|
27
|
+
const sphereMaterial = new THREE.MeshStandardMaterial({
|
|
28
|
+
color: 0x000000, // base color (almost irrelevant since emissive dominates)
|
|
29
|
+
emissive: 0xffffff, // self-illumination color
|
|
30
|
+
emissiveIntensity: 200 // strong glow
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
// Step 1: A time-dependent function that returns a surface height function.
|
|
35
|
+
// We return a function (x, y) => z that changes smoothly over time.
|
|
36
|
+
// Try tweaking constants to see different wave behaviors.
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
const sineTimeFunction = (time: number): ((x: number, y: number) => number) => {
|
|
39
|
+
return (x: number, y: number) =>
|
|
40
|
+
(5 * (Math.sin(x * 2 + time) * Math.cos(y * 2 + time))) /
|
|
41
|
+
(Math.pow(Math.abs(x) + Math.abs(y), 2) + 5) +
|
|
42
|
+
3
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// Step 2: Export an AnimatedScene just like in tutorial 1, but in 3D.
|
|
47
|
+
// We use HotReloadSetting.BeginFromCurrent since it will be much faster during debug.
|
|
48
|
+
// Since this has accumulative effects (angle += 0.005) notice that the angle is not correct at hot reload.
|
|
49
|
+
// Regardless of our HotReloadSetting, the renders will always be correct
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
export function tutorial_easy2(): AnimatedScene {
|
|
52
|
+
return new AnimatedScene(
|
|
53
|
+
1500, // width (square clip)
|
|
54
|
+
1500, // height
|
|
55
|
+
SpaceSetting.ThreeDim, // 3D scene
|
|
56
|
+
HotReloadSetting.BeginFromCurrent,
|
|
57
|
+
async (scene) => {
|
|
58
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
59
|
+
// Step 3: Basic environment (background gradient)
|
|
60
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
61
|
+
addBackgroundGradient({
|
|
62
|
+
scene,
|
|
63
|
+
topColor: 0x0c8ccd, // blue-ish
|
|
64
|
+
bottomColor: 0x000000, // black
|
|
65
|
+
lightingIntensity: 10
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// We use three.js directly to create a grid and axes
|
|
69
|
+
const gridHelper = new THREE.GridHelper(20, 20)
|
|
70
|
+
const axesHelper = new THREE.AxesHelper(20)
|
|
71
|
+
scene.add(gridHelper, axesHelper)
|
|
72
|
+
|
|
73
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
74
|
+
// Step 4: Create a function surface over a domain.
|
|
75
|
+
// We start with t = 0, then update it every frame in onEachTick.
|
|
76
|
+
// The returned object is a THREE.Mesh we can style like any other mesh.
|
|
77
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
78
|
+
const DOMAIN: [number, number, number, number] = [-7, 7, -7, 7] // [xMin, xMax, yMin, yMax]
|
|
79
|
+
const surface = createFunctionSurface(sineTimeFunction(0), ...DOMAIN)
|
|
80
|
+
surface.material = surfaceMaterial
|
|
81
|
+
scene.add(surface)
|
|
82
|
+
|
|
83
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
84
|
+
// Step 5: Add a glowing sphere with a point light.
|
|
85
|
+
// We put them in a Group so they move together if we want.
|
|
86
|
+
// The light’s position is kept in sync with the sphere.
|
|
87
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
88
|
+
const sphere = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 32), sphereMaterial)
|
|
89
|
+
const pointLight = new THREE.PointLight(0xffffff, 50) // (color, intensity)
|
|
90
|
+
pointLight.position.copy(sphere.position)
|
|
91
|
+
|
|
92
|
+
const orbGroup = new THREE.Group()
|
|
93
|
+
orbGroup.add(sphere, pointLight)
|
|
94
|
+
orbGroup.position.y = 6 // float above the surface a bit
|
|
95
|
+
scene.add(orbGroup)
|
|
96
|
+
|
|
97
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
98
|
+
// Step 6: Camera setup + gentle orbit animation.
|
|
99
|
+
// We place the camera, measure its distance to the origin, and then
|
|
100
|
+
// slowly orbit around (0,0,0). The wobble changes radius over time.
|
|
101
|
+
// Thees numbers for the position is difficult to guess,
|
|
102
|
+
// the workflow is therefore to navigate the scene to where you want it and then copy the positions it prints.
|
|
103
|
+
// This avoids awkward value guessing for position and rotation
|
|
104
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
105
|
+
scene.camera.position.set(3.889329, 7.895859, 10.51772)
|
|
106
|
+
scene.camera.rotation.set(-0.6027059, 0.3079325, 0.2056132)
|
|
107
|
+
|
|
108
|
+
const LOOK_AT = new THREE.Vector3(0, 0, 0)
|
|
109
|
+
const baseRadius = scene.camera.position.distanceTo(LOOK_AT)
|
|
110
|
+
let angle = 0
|
|
111
|
+
|
|
112
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
113
|
+
// Step 7: Per-frame updates.
|
|
114
|
+
// - Update the surface with the current time (tick)
|
|
115
|
+
// - Keep the orb group floating
|
|
116
|
+
// - Orbit the camera and keep it looking at the center
|
|
117
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
118
|
+
scene.onEachTick((tick) => {
|
|
119
|
+
const t = tick / 20
|
|
120
|
+
const f = sineTimeFunction(t)
|
|
121
|
+
updateFunctionSurface(surface, f, ...DOMAIN)
|
|
122
|
+
|
|
123
|
+
// Keep the orb hovering above the surface
|
|
124
|
+
orbGroup.position.y = 6
|
|
125
|
+
|
|
126
|
+
// Camera orbit with a subtle radius wobble
|
|
127
|
+
angle += 0.005
|
|
128
|
+
const wobble = (Math.sin(tick / 50) + 2) / 2 // ranges roughly [0.5, 1.5]
|
|
129
|
+
scene.camera.position.x = Math.sin(angle) * baseRadius * wobble
|
|
130
|
+
scene.camera.position.z = Math.cos(angle) * baseRadius * wobble
|
|
131
|
+
scene.camera.lookAt(LOOK_AT)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
135
|
+
// Step 8: Let the animation run for a while before finishing.
|
|
136
|
+
// This adds 10 seconds of "play time" to the scene’s schedule.
|
|
137
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
138
|
+
scene.addWait(10_000)
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Tutorial 3 (easy3.ts)
|
|
2
|
+
// Goal for this animation:
|
|
3
|
+
// 1) Show a fullscreen-ish color card
|
|
4
|
+
// 2) Swap the headline text every few beats
|
|
5
|
+
// 3) Play a short tick sound on each swap
|
|
6
|
+
|
|
7
|
+
import * as THREE from 'three'
|
|
8
|
+
|
|
9
|
+
import { easeLinear } from '$renderer/lib/animation/interpolations'
|
|
10
|
+
import { createAnim } from '$renderer/lib/animation/protocols'
|
|
11
|
+
import { createFastText, createRectangle, updateText } from '$renderer/lib/rendering/objects2d'
|
|
12
|
+
import { AnimatedScene, HotReloadSetting, SpaceSetting } from '$renderer/lib/scene/sceneClass'
|
|
13
|
+
|
|
14
|
+
// Adjust this path if your assets are elsewhere:
|
|
15
|
+
import tickSound from '$assets/audio/tick_sound.mp3'
|
|
16
|
+
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
// Step 0: Slides = headline alternatives (translated to English)
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
const ALTERNATIVES = [
|
|
21
|
+
'Introductory Mathematical Analysis',
|
|
22
|
+
'Linear Algebra',
|
|
23
|
+
'The Physicist’s Toolkit',
|
|
24
|
+
'Mathematical Analysis, Continued',
|
|
25
|
+
'Mechanics 1',
|
|
26
|
+
'Programming Techniques and Numerical Analysis',
|
|
27
|
+
'Multivariable Calculus',
|
|
28
|
+
'Probability and Statistics',
|
|
29
|
+
'Mechanics 2',
|
|
30
|
+
'Complex Analysis',
|
|
31
|
+
'Experimental Physics 1',
|
|
32
|
+
'Electrical Circuits and Systems',
|
|
33
|
+
'Vector Fields and Electromagnetic Field Theory',
|
|
34
|
+
'Control Engineering (F)',
|
|
35
|
+
'Bayesian Inference and Machine Learning',
|
|
36
|
+
'Fourier Analysis',
|
|
37
|
+
'Optics',
|
|
38
|
+
'Continuum Mechanics',
|
|
39
|
+
'Thermodynamics and Statistical Mechanics',
|
|
40
|
+
'Quantum Physics',
|
|
41
|
+
'Data Structures and Algorithms',
|
|
42
|
+
'Experimental Physics 2',
|
|
43
|
+
'Solid State Physics',
|
|
44
|
+
'Subatomic Physics',
|
|
45
|
+
'Environmental Physics',
|
|
46
|
+
'Algorithms',
|
|
47
|
+
'Logic for Computer Science',
|
|
48
|
+
'Introduction to Data Science and AI',
|
|
49
|
+
'Programming Languages',
|
|
50
|
+
'Design of AI Systems',
|
|
51
|
+
'Algorithms for Machine Learning and Inference',
|
|
52
|
+
'Computational Methods for Large-Scale Data',
|
|
53
|
+
'Compiler Construction'
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
// Pleasant, varied card colors
|
|
57
|
+
const SLIDE_COLORS = [
|
|
58
|
+
'#3D6680',
|
|
59
|
+
'#2F6666',
|
|
60
|
+
'#2F3D66',
|
|
61
|
+
'#592659',
|
|
62
|
+
'#3D2F66',
|
|
63
|
+
'#2F665C',
|
|
64
|
+
'#5C662F',
|
|
65
|
+
'#66332F',
|
|
66
|
+
'#3D2F66',
|
|
67
|
+
'#2F4D66'
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
// How long each slide stays on screen, measured in ticks/frames
|
|
71
|
+
const TICKS_PER_SLIDE = 300
|
|
72
|
+
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
// Step 1: Export an AnimatedScene (2D, begin from current = fast hot reload)
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
export function tutorial_easy3(): AnimatedScene {
|
|
77
|
+
return new AnimatedScene(
|
|
78
|
+
1000,
|
|
79
|
+
1000,
|
|
80
|
+
SpaceSetting.TwoDim,
|
|
81
|
+
HotReloadSetting.BeginFromCurrent,
|
|
82
|
+
async (scene) => {
|
|
83
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
84
|
+
// Step 2: Create a background card and a headline text
|
|
85
|
+
// The rectangle acts like a slide background; we’ll change its color.
|
|
86
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
87
|
+
const card = createRectangle(200, 200) // simple square card
|
|
88
|
+
const title = await createFastText('', 1.5) // scale = 1.5 ⇒ nice big headline
|
|
89
|
+
scene.add(card, title)
|
|
90
|
+
|
|
91
|
+
// Optional: center them if your renderer doesn’t already
|
|
92
|
+
card.position.set(0, 0, 0)
|
|
93
|
+
title.position.set(0, 0, 0)
|
|
94
|
+
|
|
95
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
96
|
+
// Step 3: Load the tick SFX and register it once
|
|
97
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
98
|
+
scene.registerAudio(tickSound)
|
|
99
|
+
|
|
100
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Step 4: Build a simple “switcher” animation
|
|
102
|
+
// We map a linear 0→1 over (ALTERNATIVES.length * TICKS_PER_SLIDE) ticks.
|
|
103
|
+
// On each whole-index step we:
|
|
104
|
+
// - update the background color
|
|
105
|
+
// - update the headline text
|
|
106
|
+
// - play a tick sound
|
|
107
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
108
|
+
let lastIndex = -1
|
|
109
|
+
const totalTicks = ALTERNATIVES.length * TICKS_PER_SLIDE
|
|
110
|
+
|
|
111
|
+
const switcher = createAnim(easeLinear(0, 1, totalTicks), (value) => {
|
|
112
|
+
// Convert 0..1 into a slide index. Modulo makes the final frame safe.
|
|
113
|
+
const i = Math.floor(value * ALTERNATIVES.length) % ALTERNATIVES.length
|
|
114
|
+
if (i === lastIndex) return
|
|
115
|
+
lastIndex = i
|
|
116
|
+
|
|
117
|
+
// Update color + text
|
|
118
|
+
const color = SLIDE_COLORS[i % SLIDE_COLORS.length]
|
|
119
|
+
;(card.material as THREE.MeshBasicMaterial).color = new THREE.Color(color)
|
|
120
|
+
updateText(title, ALTERNATIVES[i])
|
|
121
|
+
|
|
122
|
+
// Click!
|
|
123
|
+
scene.playAudio(tickSound)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// Start the slide show
|
|
127
|
+
scene.addAnim(switcher)
|
|
128
|
+
|
|
129
|
+
// Let it run a bit after the last change (nice tail for render/export)
|
|
130
|
+
scene.addWait(1_000)
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Tutorial 3 (medium1.ts)
|
|
2
|
+
// Goal for this animation:
|
|
3
|
+
// 1) Light the scene with an HDRI (image-based lighting) + soft gradient background
|
|
4
|
+
// 2) Spawn a flock of spheres that wander smoothly with damped random acceleration
|
|
5
|
+
// 3) Draw dependency lines between neighbors to form a living chain
|
|
6
|
+
// 4) Orbit the camera around the action
|
|
7
|
+
|
|
8
|
+
import * as THREE from 'three'
|
|
9
|
+
|
|
10
|
+
import { AnimatedScene, HotReloadSetting, SpaceSetting } from '$renderer/lib/scene/sceneClass'
|
|
11
|
+
|
|
12
|
+
// If your helpers live elsewhere, tweak these paths:
|
|
13
|
+
import {
|
|
14
|
+
addBackgroundGradient,
|
|
15
|
+
addHDRI,
|
|
16
|
+
loadHDRIData,
|
|
17
|
+
HDRIs
|
|
18
|
+
} from '$renderer/lib/rendering/lighting3d'
|
|
19
|
+
import { setOpacity } from '$renderer/lib/animation/animations'
|
|
20
|
+
import { createLine } from '$renderer/lib/rendering/objects2d'
|
|
21
|
+
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// Step 0: Scene constants and materials
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
const NODE_COUNT = 200
|
|
26
|
+
const BOUNDARY_RADIUS = 10
|
|
27
|
+
const ORBIT_SPEED = 0.01
|
|
28
|
+
|
|
29
|
+
const sphereMaterial = new THREE.MeshStandardMaterial({
|
|
30
|
+
color: 0xffffff,
|
|
31
|
+
roughness: 0.3,
|
|
32
|
+
metalness: 1.0
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
type Node = {
|
|
36
|
+
mesh: THREE.Mesh
|
|
37
|
+
velocity: THREE.Vector3
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
// Step 1: Helper to make a moving node (sphere + velocity)
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
function createNode(): Node {
|
|
44
|
+
const geometry = new THREE.SphereGeometry(0.15, 32, 32)
|
|
45
|
+
const mesh = new THREE.Mesh(geometry, sphereMaterial.clone())
|
|
46
|
+
// Start near the origin with a tiny random offset so lines aren’t degenerate
|
|
47
|
+
mesh.position.set(
|
|
48
|
+
THREE.MathUtils.randFloatSpread(1),
|
|
49
|
+
THREE.MathUtils.randFloatSpread(1),
|
|
50
|
+
THREE.MathUtils.randFloatSpread(1)
|
|
51
|
+
)
|
|
52
|
+
return { mesh, velocity: new THREE.Vector3() }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
// Step 2: Export an AnimatedScene (3D, hot-reload from current frame)
|
|
57
|
+
// We don’t need to replay long histories, so BeginFromCurrent makes iteration fast.
|
|
58
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
export function tutorial_medium1(): AnimatedScene {
|
|
60
|
+
return new AnimatedScene(
|
|
61
|
+
1080, // width (vertical clip like easy1)
|
|
62
|
+
1920, // height
|
|
63
|
+
SpaceSetting.ThreeDim, // 3D scene
|
|
64
|
+
HotReloadSetting.BeginFromCurrent,
|
|
65
|
+
async (scene) => {
|
|
66
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
67
|
+
// Step 3: Lighting & background
|
|
68
|
+
// HDRI gives nice reflections/IBL; gradient provides a subtle backdrop.
|
|
69
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
70
|
+
const hdriData = await loadHDRIData(HDRIs.photoStudio1, 2, 0.5) // (lods, intensity normalization)
|
|
71
|
+
await addHDRI(scene, hdriData, 0.3) // envMap intensity
|
|
72
|
+
|
|
73
|
+
addBackgroundGradient({
|
|
74
|
+
scene,
|
|
75
|
+
topColor: 0xaa9775, // warm top
|
|
76
|
+
bottomColor: 0x483924, // rich brown base
|
|
77
|
+
backgroundOpacity: 0.7
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Optional helpers if you like visual anchors:
|
|
81
|
+
//scene.add(new THREE.GridHelper(30, 30))
|
|
82
|
+
|
|
83
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
84
|
+
// Step 4: Create moving nodes and dependency lines (neighbor chain)
|
|
85
|
+
// We connect each node i to node (i+1) to visualize a simple dependency.
|
|
86
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
87
|
+
const nodes: Node[] = Array.from({ length: NODE_COUNT }, createNode)
|
|
88
|
+
const lines = nodes.map(() => setOpacity(createLine(), 0.12)) // faint lines
|
|
89
|
+
|
|
90
|
+
scene.add(...nodes.map((n) => n.mesh), ...lines)
|
|
91
|
+
|
|
92
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
93
|
+
// Step 5: Camera setup — start out on the +X axis and orbit around origin
|
|
94
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
95
|
+
scene.camera.position.set(30, 20, 0)
|
|
96
|
+
const center = new THREE.Vector3(0, 0, 0)
|
|
97
|
+
const orbitRadius = scene.camera.position.distanceTo(center)
|
|
98
|
+
let angle = 0
|
|
99
|
+
|
|
100
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Step 6: Per-frame simulation
|
|
102
|
+
// - Each node gets a tiny random acceleration (smooth wander)
|
|
103
|
+
// - Velocity is damped for stability
|
|
104
|
+
// - Soft boundary: gentle pull toward center if too far
|
|
105
|
+
// - Lines update to connect neighbors (i -> i+1)
|
|
106
|
+
// - Camera orbits and keeps looking at the center
|
|
107
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
108
|
+
scene.onEachTick((tick) => {
|
|
109
|
+
// Physics update
|
|
110
|
+
for (const n of nodes) {
|
|
111
|
+
// Small random acceleration
|
|
112
|
+
const ax = (Math.random() - 0.5) * 0.05
|
|
113
|
+
const ay = (Math.random() - 0.5) * 0.05
|
|
114
|
+
const az = (Math.random() - 0.5) * 0.05
|
|
115
|
+
n.velocity.x += ax
|
|
116
|
+
n.velocity.y += ay
|
|
117
|
+
n.velocity.z += az
|
|
118
|
+
|
|
119
|
+
// Damping (retain ~95% of previous velocity)
|
|
120
|
+
n.velocity.multiplyScalar(0.95)
|
|
121
|
+
|
|
122
|
+
// Integrate
|
|
123
|
+
n.mesh.position.add(n.velocity)
|
|
124
|
+
|
|
125
|
+
// Soft boundary: if outside radius, nudge back toward origin
|
|
126
|
+
const len = n.mesh.position.length()
|
|
127
|
+
if (len > BOUNDARY_RADIUS) {
|
|
128
|
+
const pull = n.mesh.position.clone().negate().normalize().multiplyScalar(0.02)
|
|
129
|
+
n.velocity.add(pull)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Update dependency lines (chain)
|
|
134
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
135
|
+
const a = nodes[i].mesh.position
|
|
136
|
+
const b = nodes[(i + 1) % nodes.length].mesh.position // wrap to form a loop
|
|
137
|
+
// createLine() returns a handle with updatePositions(start, end)
|
|
138
|
+
lines[i].updatePositions(a, b)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Camera orbit
|
|
142
|
+
angle += ORBIT_SPEED
|
|
143
|
+
scene.camera.position.x = Math.sin(angle) * orbitRadius
|
|
144
|
+
scene.camera.position.z = Math.cos(angle) * orbitRadius
|
|
145
|
+
scene.camera.lookAt(center)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
149
|
+
// Step 7: Let it play for 20 seconds before finishing
|
|
150
|
+
// ───────────────────────────────────────────────────────────────────────
|
|
151
|
+
scene.addWait(20_000)
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { linspace } from '../renderer/src/lib/mathHelpers/vectors'
|
|
2
|
+
import { COLORS } from '../renderer/src/lib/rendering/helpers'
|
|
3
|
+
import {
|
|
4
|
+
addBackgroundGradient,
|
|
5
|
+
addHDRI,
|
|
6
|
+
addSceneLighting,
|
|
7
|
+
HDRIs,
|
|
8
|
+
loadHDRIData
|
|
9
|
+
} from '../renderer/src/lib/rendering/lighting3d'
|
|
10
|
+
import { createFastText } from '../renderer/src/lib/rendering/objects2d'
|
|
11
|
+
import { AnimatedScene, HotReloadSetting, SpaceSetting } from '../renderer/src/lib/scene/sceneClass'
|
|
12
|
+
import * as THREE from 'three'
|
|
13
|
+
|
|
14
|
+
// 1. Define a Vector3 type
|
|
15
|
+
export interface Vector3 {
|
|
16
|
+
x: number
|
|
17
|
+
y: number
|
|
18
|
+
z: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 2. Define a type alias for any 3D vector field: a function that
|
|
22
|
+
// takes a point (x, y, z) and returns a vector at that point.
|
|
23
|
+
export type VectorField3D = (x: number, y: number, z: number, time: number) => Vector3
|
|
24
|
+
|
|
25
|
+
// 3. Example: a simple swirling field around the z‑axis
|
|
26
|
+
export const field: VectorField3D = (x, y, z, time) => {
|
|
27
|
+
const scale = 10
|
|
28
|
+
const target = new THREE.Vector3(
|
|
29
|
+
Math.sin(time),
|
|
30
|
+
Math.cos(time),
|
|
31
|
+
Math.sin(time) * Math.cos(time)
|
|
32
|
+
).multiplyScalar(scale) // the center point to attract objects to
|
|
33
|
+
const position = new THREE.Vector3(x, y, z)
|
|
34
|
+
const direction = target.sub(position).normalize() // calculate the direction towards the target
|
|
35
|
+
const strength = 0.1 // adjust the strength of the attraction
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
x: direction.x * strength,
|
|
39
|
+
y: direction.y * strength,
|
|
40
|
+
z: direction.z * strength
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const hdriData = await loadHDRIData(HDRIs.outdoor1, 2, 1)
|
|
45
|
+
|
|
46
|
+
export const vectorFieldScene = (): AnimatedScene => {
|
|
47
|
+
return new AnimatedScene(
|
|
48
|
+
1080,
|
|
49
|
+
2160,
|
|
50
|
+
SpaceSetting.ThreeDim,
|
|
51
|
+
HotReloadSetting.BeginFromCurrent,
|
|
52
|
+
async (scene) => {
|
|
53
|
+
addSceneLighting(scene.scene)
|
|
54
|
+
await addHDRI(scene, hdriData, 1)
|
|
55
|
+
addBackgroundGradient({
|
|
56
|
+
scene,
|
|
57
|
+
topColor: '#00639d',
|
|
58
|
+
bottomColor: COLORS.black,
|
|
59
|
+
backgroundOpacity: 0.5
|
|
60
|
+
})
|
|
61
|
+
const max = 25
|
|
62
|
+
const xs = linspace(-max, max, 6)
|
|
63
|
+
const ys = [...xs]
|
|
64
|
+
const zs = [...xs]
|
|
65
|
+
|
|
66
|
+
const length = 2 // arrow length units
|
|
67
|
+
const color = 0xffffff // red
|
|
68
|
+
|
|
69
|
+
const arrows: (THREE.ArrowHelper | null)[][][] = Array.from({ length: xs.length }, () =>
|
|
70
|
+
Array.from({ length: ys.length }, () => Array.from({ length: zs.length }, () => null))
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < xs.length; i++) {
|
|
74
|
+
for (let j = 0; j < ys.length; j++) {
|
|
75
|
+
for (let k = 0; k < zs.length; k++) {
|
|
76
|
+
const x = xs[i],
|
|
77
|
+
y = ys[j],
|
|
78
|
+
z = zs[k]
|
|
79
|
+
const value = field(x, y, z, 0)
|
|
80
|
+
// 1. define direction, origin, length and color
|
|
81
|
+
const dir = new THREE.Vector3(value.x, value.y, value.z).normalize() // arrow points along +X
|
|
82
|
+
const origin = new THREE.Vector3(x, y, z) // from world‑origin
|
|
83
|
+
|
|
84
|
+
// 2. create helper
|
|
85
|
+
const arrowHelper = new THREE.ArrowHelper(dir, origin, length, color, 6, 1)
|
|
86
|
+
|
|
87
|
+
// remove the low‑res cone
|
|
88
|
+
arrowHelper.cone.geometry.dispose()
|
|
89
|
+
|
|
90
|
+
// create a new high‑res cone: (radiusTop, height, radialSegments, heightSegments)
|
|
91
|
+
const highResSegments = 32
|
|
92
|
+
const highResConeGeo = new THREE.ConeGeometry(0.15, 1, highResSegments, 1)
|
|
93
|
+
|
|
94
|
+
arrowHelper.cone.geometry = highResConeGeo
|
|
95
|
+
|
|
96
|
+
const coneMaterial = new THREE.MeshStandardMaterial({
|
|
97
|
+
color: 0xffffff,
|
|
98
|
+
metalness: 0.5,
|
|
99
|
+
roughness: 0.7,
|
|
100
|
+
transparent: true
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const lineMaterial = new THREE.MeshStandardMaterial({
|
|
104
|
+
color: 0xffffff,
|
|
105
|
+
metalness: 0.5,
|
|
106
|
+
roughness: 0.7,
|
|
107
|
+
transparent: true
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
arrowHelper.line.material = lineMaterial
|
|
111
|
+
arrowHelper.cone.material = coneMaterial
|
|
112
|
+
|
|
113
|
+
arrowHelper.cone.castShadow
|
|
114
|
+
|
|
115
|
+
arrows[i][j][k] = arrowHelper
|
|
116
|
+
|
|
117
|
+
// 3. add to scene
|
|
118
|
+
scene.add(arrowHelper)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const textNode = await createFastText('Vector Field', 5)
|
|
124
|
+
textNode.position.y = max * 1.5
|
|
125
|
+
scene.add(textNode)
|
|
126
|
+
|
|
127
|
+
// const axesHelper = new THREE.AxesHelper(20)
|
|
128
|
+
// scene.add(axesHelper)
|
|
129
|
+
|
|
130
|
+
scene.camera.position.set(-116.443, 35.10142, 68.73468)
|
|
131
|
+
scene.camera.quaternion.set(-0.109989, -0.491824, -0.06279674, 0.8614338)
|
|
132
|
+
|
|
133
|
+
const geometry = new THREE.SphereGeometry(0.3, 32, 32)
|
|
134
|
+
const material = new THREE.MeshStandardMaterial({
|
|
135
|
+
color: 0x000000,
|
|
136
|
+
emissive: 0xffffff,
|
|
137
|
+
emissiveIntensity: 20.0
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
interface GroupData {
|
|
141
|
+
group: THREE.Group
|
|
142
|
+
acceleration: THREE.Vector3
|
|
143
|
+
velocity: THREE.Vector3
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const groups: GroupData[] = []
|
|
147
|
+
|
|
148
|
+
Array(30)
|
|
149
|
+
.fill(0)
|
|
150
|
+
.forEach(() => {
|
|
151
|
+
const sphere = new THREE.Mesh(geometry, material)
|
|
152
|
+
const pointLight = new THREE.PointLight(0xffffff, 2000) // color, intensity, distance
|
|
153
|
+
pointLight.position.copy(sphere.position)
|
|
154
|
+
const group = new THREE.Group()
|
|
155
|
+
group.add(sphere, pointLight)
|
|
156
|
+
|
|
157
|
+
group.position.y = 8 * (Math.random() - 0.5)
|
|
158
|
+
group.position.z = 8 * (Math.random() - 0.5)
|
|
159
|
+
|
|
160
|
+
let velocity = new THREE.Vector3(0, 0, 0) // Initial velocity
|
|
161
|
+
let acceleration = new THREE.Vector3(0, 0, 0) // Initialize acceleration
|
|
162
|
+
|
|
163
|
+
groups.push({
|
|
164
|
+
group,
|
|
165
|
+
acceleration,
|
|
166
|
+
velocity
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
scene.add(group)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
scene.onEachTick((tick, time) => {
|
|
173
|
+
textNode.lookAt(scene.camera.position)
|
|
174
|
+
const editedTime = time / 500
|
|
175
|
+
|
|
176
|
+
for (const groupData of groups) {
|
|
177
|
+
const fieldValue = field(
|
|
178
|
+
groupData.group.position.x,
|
|
179
|
+
groupData.group.position.y,
|
|
180
|
+
groupData.group.position.z,
|
|
181
|
+
editedTime
|
|
182
|
+
)
|
|
183
|
+
groupData.acceleration.set(fieldValue.x, fieldValue.y, fieldValue.z)
|
|
184
|
+
|
|
185
|
+
// Update velocity with acceleration (basic kinematic equation)
|
|
186
|
+
groupData.velocity.add(groupData.acceleration.clone().multiplyScalar(2)) // Adjust time factor for smoothness
|
|
187
|
+
|
|
188
|
+
// Update position of point light based on velocity
|
|
189
|
+
groupData.group.position.add(groupData.velocity)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < xs.length; i++) {
|
|
193
|
+
for (let j = 0; j < ys.length; j++) {
|
|
194
|
+
for (let k = 0; k < zs.length; k++) {
|
|
195
|
+
const x = xs[i],
|
|
196
|
+
y = ys[j],
|
|
197
|
+
z = zs[k]
|
|
198
|
+
const v = field(x, y, z, editedTime)
|
|
199
|
+
const dir = new THREE.Vector3(v.x, v.y, v.z).normalize()
|
|
200
|
+
arrows[i][j][k]?.setDirection(dir)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
scene.addWait(20000)
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
}
|