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,148 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'
|
|
3
|
+
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
|
|
4
|
+
import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
5
|
+
|
|
6
|
+
// Helper function to load an MTL file using a Promise
|
|
7
|
+
function loadMTL(mtlPath, mtlFile) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const mtlLoader = new MTLLoader()
|
|
10
|
+
mtlLoader.setPath(mtlPath)
|
|
11
|
+
mtlLoader.load(
|
|
12
|
+
mtlFile,
|
|
13
|
+
(materials) => {
|
|
14
|
+
materials.preload()
|
|
15
|
+
resolve(materials)
|
|
16
|
+
},
|
|
17
|
+
undefined,
|
|
18
|
+
(err) => {
|
|
19
|
+
reject(new Error(`Error loading MTL: ${err}`))
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Helper function to load an OBJ file using a Promise
|
|
26
|
+
function loadOBJ(objPath, objFile, materials) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const objLoader = new OBJLoader()
|
|
29
|
+
if (materials) {
|
|
30
|
+
objLoader.setMaterials(materials)
|
|
31
|
+
}
|
|
32
|
+
objLoader.setPath(objPath)
|
|
33
|
+
objLoader.load(
|
|
34
|
+
objFile,
|
|
35
|
+
(object) => {
|
|
36
|
+
resolve(object)
|
|
37
|
+
},
|
|
38
|
+
undefined,
|
|
39
|
+
(err) => {
|
|
40
|
+
reject(new Error(`Error loading OBJ: ${err}`))
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Define an interface for the parameters
|
|
47
|
+
interface LoadParams {
|
|
48
|
+
mtlPath: string
|
|
49
|
+
mtlFile: string
|
|
50
|
+
objPath: string
|
|
51
|
+
objFile: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Generic async function that loads an OBJ with its MTL
|
|
55
|
+
export async function loadOBJWithMTL({
|
|
56
|
+
mtlPath,
|
|
57
|
+
mtlFile,
|
|
58
|
+
objPath,
|
|
59
|
+
objFile
|
|
60
|
+
}: LoadParams): Promise<THREE.Object3D> {
|
|
61
|
+
try {
|
|
62
|
+
// Load the MTL file and get the materials
|
|
63
|
+
const materials = await loadMTL(mtlPath, mtlFile)
|
|
64
|
+
// Load the OBJ file with the preloaded materials
|
|
65
|
+
const object = await loadOBJ(objPath, objFile, materials)
|
|
66
|
+
return object as any
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(error)
|
|
69
|
+
throw error
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Generic async function that loads an OBJ with its MTL given full file paths
|
|
74
|
+
export async function loadOBJWithMTLPaths(
|
|
75
|
+
objFullPath: string,
|
|
76
|
+
mtlFullPath: string
|
|
77
|
+
): Promise<THREE.Object3D> {
|
|
78
|
+
try {
|
|
79
|
+
// Load the MTL file and get the materials
|
|
80
|
+
const materials = await new Promise<any>((resolve, reject) => {
|
|
81
|
+
const mtlLoader = new MTLLoader()
|
|
82
|
+
mtlLoader.load(
|
|
83
|
+
mtlFullPath,
|
|
84
|
+
(materials) => {
|
|
85
|
+
materials.preload()
|
|
86
|
+
resolve(materials)
|
|
87
|
+
},
|
|
88
|
+
undefined,
|
|
89
|
+
(error) => reject(new Error(`Error loading MTL file: ${error}`))
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Load the OBJ file with the preloaded materials
|
|
94
|
+
const object = await new Promise<THREE.Object3D>((resolve, reject) => {
|
|
95
|
+
const objLoader = new OBJLoader()
|
|
96
|
+
objLoader.setMaterials(materials)
|
|
97
|
+
objLoader.load(
|
|
98
|
+
objFullPath,
|
|
99
|
+
(object) => resolve(object),
|
|
100
|
+
undefined,
|
|
101
|
+
(error) => reject(new Error(`Error loading OBJ file: ${error}`))
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return object
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(error)
|
|
108
|
+
throw error
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Generic async function that loads a GLB file given its full path/URL
|
|
113
|
+
export async function loadGLB(
|
|
114
|
+
glbPath: string,
|
|
115
|
+
overrideMaterial?: THREE.Material
|
|
116
|
+
): Promise<THREE.Object3D> {
|
|
117
|
+
try {
|
|
118
|
+
const gltf: GLTF = await new Promise((resolve, reject) => {
|
|
119
|
+
const loader = new GLTFLoader()
|
|
120
|
+
loader.load(
|
|
121
|
+
glbPath,
|
|
122
|
+
(gltfData) => resolve(gltfData),
|
|
123
|
+
undefined,
|
|
124
|
+
(error) => reject(new Error(`Error loading GLB file: ${error}`))
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
gltf.scene.traverse((child) => {
|
|
129
|
+
if ((child as THREE.Mesh).isMesh) {
|
|
130
|
+
child.castShadow = true
|
|
131
|
+
child.receiveShadow = true
|
|
132
|
+
|
|
133
|
+
if (overrideMaterial) {
|
|
134
|
+
child.material = overrideMaterial
|
|
135
|
+
// if the material instance has textures or other maps,
|
|
136
|
+
// make sure to mark it for update
|
|
137
|
+
child.material.needsUpdate = true
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Return the scene (or top-level object) from the GLB file
|
|
143
|
+
return gltf.scene
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(error)
|
|
146
|
+
throw error
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
import { COLORS } from './helpers'
|
|
3
|
+
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
|
|
4
|
+
import { FontLoader, LineGeometry, LineMaterial, ThreeMFLoader } from 'three/examples/jsm/Addons.js'
|
|
5
|
+
import fontJSON from '../fonts/montserrat.json'
|
|
6
|
+
import fontTroika from '$assets/fonts/Montserrat-Medium.woff'
|
|
7
|
+
|
|
8
|
+
import { Text } from 'troika-three-text'
|
|
9
|
+
import { preloadFont, configureTextBuilder } from 'troika-three-text'
|
|
10
|
+
import { Line2 } from 'three/examples/jsm/lines/webgpu/Line2.js'
|
|
11
|
+
import { Vector3 } from 'three'
|
|
12
|
+
|
|
13
|
+
let hasLoadedFonts = false
|
|
14
|
+
|
|
15
|
+
export const loadFonts = (): Promise<void> => {
|
|
16
|
+
if (hasLoadedFonts) new Promise((resolve) => resolve)
|
|
17
|
+
configureTextBuilder({
|
|
18
|
+
useWorker: false
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
preloadFont(
|
|
23
|
+
{
|
|
24
|
+
font: fontTroika,
|
|
25
|
+
characters: 'abcdefghijklmnopqrstuvwxyz'
|
|
26
|
+
},
|
|
27
|
+
() => {
|
|
28
|
+
console.log('preload font complete')
|
|
29
|
+
hasLoadedFonts = true
|
|
30
|
+
resolve()
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ObjectOptions {
|
|
37
|
+
color?: THREE.ColorRepresentation
|
|
38
|
+
material?: THREE.Material
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let loadedFont: any = null
|
|
42
|
+
|
|
43
|
+
const loader = new FontLoader()
|
|
44
|
+
const loadFont = () => {
|
|
45
|
+
loadedFont = loader.parse(fontJSON as any)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface MeshWithColorMaterial extends THREE.Mesh {
|
|
49
|
+
material: {
|
|
50
|
+
color: THREE.Color
|
|
51
|
+
} & THREE.Material
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const createMesh = (
|
|
55
|
+
geometry: THREE.BufferGeometry,
|
|
56
|
+
options?: ObjectOptions
|
|
57
|
+
): MeshWithColorMaterial => {
|
|
58
|
+
const meshMaterial = options?.material
|
|
59
|
+
? options.material
|
|
60
|
+
: new THREE.MeshBasicMaterial({
|
|
61
|
+
color: options?.color ? options.color : new THREE.Color(1, 1, 1),
|
|
62
|
+
transparent: true
|
|
63
|
+
})
|
|
64
|
+
const mesh = new THREE.Mesh(geometry, meshMaterial)
|
|
65
|
+
return mesh as any
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface RectangleOptions extends ObjectOptions {
|
|
69
|
+
stroke?: {
|
|
70
|
+
color?: THREE.ColorRepresentation
|
|
71
|
+
width?: number
|
|
72
|
+
placement?: 'inside' | 'outside' | 'center'
|
|
73
|
+
visible?: boolean
|
|
74
|
+
resolution?: THREE.Vector2
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const createRectangle = (
|
|
79
|
+
width: number = 10,
|
|
80
|
+
height: number = 10,
|
|
81
|
+
options?: RectangleOptions
|
|
82
|
+
) => {
|
|
83
|
+
const geometry = new THREE.PlaneGeometry(width, height)
|
|
84
|
+
const mesh = createMesh(geometry, options)
|
|
85
|
+
|
|
86
|
+
if (options?.stroke) {
|
|
87
|
+
const strokeParams = options.stroke
|
|
88
|
+
const placement = strokeParams.placement ?? 'outside'
|
|
89
|
+
const strokeWidth = strokeParams.width ?? 0.1
|
|
90
|
+
|
|
91
|
+
// Calculate adjusted dimensions
|
|
92
|
+
let strokeWidthAdj = width
|
|
93
|
+
let strokeHeightAdj = height
|
|
94
|
+
switch (placement) {
|
|
95
|
+
case 'inside':
|
|
96
|
+
strokeWidthAdj = width - strokeWidth / 2
|
|
97
|
+
strokeHeightAdj = height - strokeWidth / 2
|
|
98
|
+
break
|
|
99
|
+
case 'outside':
|
|
100
|
+
strokeWidthAdj = width + strokeWidth / 2
|
|
101
|
+
strokeHeightAdj = height + strokeWidth / 2
|
|
102
|
+
break
|
|
103
|
+
case 'center':
|
|
104
|
+
strokeWidthAdj = width
|
|
105
|
+
strokeHeightAdj = height
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create rectangle path with adjusted corners
|
|
110
|
+
const halfW = strokeWidthAdj / 2
|
|
111
|
+
const halfH = strokeHeightAdj / 2
|
|
112
|
+
const cornerCut = strokeWidth * 0.2 // Adjust this to control corner shape
|
|
113
|
+
const zValue = 0.001
|
|
114
|
+
|
|
115
|
+
const points = [
|
|
116
|
+
// Bottom-left to bottom-right with corner cut
|
|
117
|
+
new THREE.Vector3(-halfW + cornerCut, -halfH, zValue),
|
|
118
|
+
new THREE.Vector3(halfW - cornerCut, -halfH, zValue),
|
|
119
|
+
|
|
120
|
+
// Bottom-right to top-right with corner cut
|
|
121
|
+
new THREE.Vector3(halfW, -halfH + cornerCut, zValue),
|
|
122
|
+
new THREE.Vector3(halfW, halfH - cornerCut, zValue),
|
|
123
|
+
|
|
124
|
+
// Top-right to top-left with corner cut
|
|
125
|
+
new THREE.Vector3(halfW - cornerCut, halfH, zValue),
|
|
126
|
+
new THREE.Vector3(-halfW + cornerCut, halfH, zValue),
|
|
127
|
+
|
|
128
|
+
// Top-left to bottom-left with corner cut
|
|
129
|
+
new THREE.Vector3(-halfW, halfH - cornerCut, zValue),
|
|
130
|
+
new THREE.Vector3(-halfW, -halfH + cornerCut, zValue),
|
|
131
|
+
|
|
132
|
+
// Close the loop
|
|
133
|
+
new THREE.Vector3(-halfW + cornerCut, -halfH, zValue)
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
const lineGeometry = new LineGeometry()
|
|
137
|
+
lineGeometry.setPositions(points.flatMap((p) => [p.x, p.y, p.z]))
|
|
138
|
+
|
|
139
|
+
const lineMaterial = new LineMaterial({
|
|
140
|
+
color: strokeParams.color ?? 0x000000,
|
|
141
|
+
linewidth: strokeWidth,
|
|
142
|
+
worldUnits: true,
|
|
143
|
+
resolution:
|
|
144
|
+
strokeParams.resolution || new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
145
|
+
depthTest: false,
|
|
146
|
+
depthWrite: false,
|
|
147
|
+
transparent: true
|
|
148
|
+
} as THREE.LineBasicMaterialParameters) // Type assertion here
|
|
149
|
+
|
|
150
|
+
const stroke = new Line2(lineGeometry, lineMaterial as any)
|
|
151
|
+
stroke.renderOrder = 1
|
|
152
|
+
|
|
153
|
+
if (mesh.material instanceof THREE.Material) {
|
|
154
|
+
mesh.material.depthWrite = true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
mesh.add(stroke)
|
|
158
|
+
;(mesh as any).stroke = stroke
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return mesh
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Define interface extending Line with your custom method
|
|
165
|
+
export interface PaddedLine extends THREE.Line {
|
|
166
|
+
updatePositions: (
|
|
167
|
+
newPoint1?: THREE.Vector3,
|
|
168
|
+
newPoint2?: THREE.Vector3,
|
|
169
|
+
newPadding?: number
|
|
170
|
+
) => void
|
|
171
|
+
userData: {
|
|
172
|
+
point1: THREE.Vector3
|
|
173
|
+
point2: THREE.Vector3
|
|
174
|
+
padding: number
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const createLine = ({
|
|
179
|
+
point1 = new THREE.Vector3(0, 0, 0),
|
|
180
|
+
point2 = new THREE.Vector3(0, 0, 0),
|
|
181
|
+
color = new THREE.Color(1, 1, 1),
|
|
182
|
+
width = 1,
|
|
183
|
+
padding = 0
|
|
184
|
+
}: {
|
|
185
|
+
point1?: THREE.Vector3
|
|
186
|
+
point2?: THREE.Vector3
|
|
187
|
+
color?: THREE.ColorRepresentation
|
|
188
|
+
width?: number
|
|
189
|
+
padding?: number
|
|
190
|
+
} = {}): PaddedLine => {
|
|
191
|
+
// Create the line geometry
|
|
192
|
+
const geometry = new THREE.BufferGeometry()
|
|
193
|
+
|
|
194
|
+
// Set initial positions (will be updated immediately)
|
|
195
|
+
const positions = new Float32Array(6)
|
|
196
|
+
const posAttribute = new THREE.BufferAttribute(positions, 3)
|
|
197
|
+
geometry.setAttribute('position', posAttribute)
|
|
198
|
+
|
|
199
|
+
// Create the line material
|
|
200
|
+
const material = new THREE.LineBasicMaterial({
|
|
201
|
+
color: color,
|
|
202
|
+
linewidth: width,
|
|
203
|
+
transparent: true // Note: line width only works in WebGLRenderer with GL_LINES (limited browser support)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Create the line
|
|
207
|
+
const line = new THREE.Line(geometry, material as any) as PaddedLine
|
|
208
|
+
|
|
209
|
+
// Store the original points and padding as properties
|
|
210
|
+
line.userData = {
|
|
211
|
+
point1: point1.clone(),
|
|
212
|
+
point2: point2.clone(),
|
|
213
|
+
padding: padding
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Initial position update
|
|
217
|
+
updateLinePositions(line)
|
|
218
|
+
|
|
219
|
+
// Add update method to the line
|
|
220
|
+
line.updatePositions = function (
|
|
221
|
+
newPoint1?: THREE.Vector3,
|
|
222
|
+
newPoint2?: THREE.Vector3,
|
|
223
|
+
newPadding?: number
|
|
224
|
+
) {
|
|
225
|
+
// Update stored values if provided
|
|
226
|
+
if (newPoint1) this.userData.point1.copy(newPoint1)
|
|
227
|
+
if (newPoint2) this.userData.point2.copy(newPoint2)
|
|
228
|
+
if (newPadding !== undefined) this.userData.padding = newPadding
|
|
229
|
+
|
|
230
|
+
// Update the line geometry
|
|
231
|
+
updateLinePositions(this)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return line
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Reusable vectors to avoid creating objects each time
|
|
238
|
+
const tempDir = new THREE.Vector3()
|
|
239
|
+
const tempAdjusted1 = new THREE.Vector3()
|
|
240
|
+
const tempAdjusted2 = new THREE.Vector3()
|
|
241
|
+
const tempMidPoint = new THREE.Vector3()
|
|
242
|
+
|
|
243
|
+
function updateLinePositions(line: PaddedLine) {
|
|
244
|
+
const { point1, point2, padding } = line.userData
|
|
245
|
+
const positions = line.geometry.attributes.position.array as Float32Array
|
|
246
|
+
|
|
247
|
+
// Don't apply padding if points are too close
|
|
248
|
+
const distance = point1.distanceTo(point2)
|
|
249
|
+
|
|
250
|
+
if (distance <= padding * 2) {
|
|
251
|
+
// Points are too close for padding, use midpoint
|
|
252
|
+
tempMidPoint.addVectors(point1, point2).multiplyScalar(0.5)
|
|
253
|
+
|
|
254
|
+
positions[0] = positions[3] = tempMidPoint.x
|
|
255
|
+
positions[1] = positions[4] = tempMidPoint.y
|
|
256
|
+
positions[2] = positions[5] = tempMidPoint.z
|
|
257
|
+
} else {
|
|
258
|
+
// Calculate direction vector
|
|
259
|
+
tempDir.subVectors(point2, point1).normalize()
|
|
260
|
+
|
|
261
|
+
// Create adjusted points with padding
|
|
262
|
+
tempAdjusted1.copy(point1).addScaledVector(tempDir, padding)
|
|
263
|
+
tempAdjusted2.copy(point2).addScaledVector(tempDir, -padding)
|
|
264
|
+
|
|
265
|
+
// Update the positions array
|
|
266
|
+
positions[0] = tempAdjusted1.x
|
|
267
|
+
positions[1] = tempAdjusted1.y
|
|
268
|
+
positions[2] = tempAdjusted1.z
|
|
269
|
+
positions[3] = tempAdjusted2.x
|
|
270
|
+
positions[4] = tempAdjusted2.y
|
|
271
|
+
positions[5] = tempAdjusted2.z
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Mark the attribute as needing update
|
|
275
|
+
line.geometry.attributes.position.needsUpdate = true
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
type StrokePlacement = 'inside' | 'outside' | 'center'
|
|
279
|
+
|
|
280
|
+
interface CircleOptions extends ObjectOptions {
|
|
281
|
+
stroke?: {
|
|
282
|
+
color?: THREE.ColorRepresentation
|
|
283
|
+
width?: number
|
|
284
|
+
placement?: StrokePlacement
|
|
285
|
+
visible?: boolean
|
|
286
|
+
resolution?: THREE.Vector2
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const createCircle = (radius: number = 10, options?: CircleOptions) => {
|
|
291
|
+
const geometry = new THREE.CircleGeometry(radius, 100)
|
|
292
|
+
const mesh = createMesh(geometry, options)
|
|
293
|
+
|
|
294
|
+
if (options?.stroke) {
|
|
295
|
+
const strokeParams = options.stroke
|
|
296
|
+
const placement = strokeParams.placement ?? 'center'
|
|
297
|
+
const strokeWidth = strokeParams.width ?? 0.1
|
|
298
|
+
|
|
299
|
+
let strokeRadius = radius
|
|
300
|
+
switch (placement) {
|
|
301
|
+
case 'inside':
|
|
302
|
+
strokeRadius = radius - strokeWidth / 2
|
|
303
|
+
break
|
|
304
|
+
case 'outside':
|
|
305
|
+
strokeRadius = radius + strokeWidth / 2
|
|
306
|
+
break
|
|
307
|
+
case 'center':
|
|
308
|
+
strokeRadius = radius
|
|
309
|
+
break
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const path = new THREE.Path()
|
|
313
|
+
path.absarc(0, 0, strokeRadius, 0, Math.PI * 2)
|
|
314
|
+
const points = path.getPoints(100).map((p) => new THREE.Vector3(p.x, p.y, 0.001))
|
|
315
|
+
|
|
316
|
+
const lineGeometry = new LineGeometry()
|
|
317
|
+
lineGeometry.setPositions(points.flatMap((p) => [p.x, p.y, p.z]))
|
|
318
|
+
|
|
319
|
+
const lineMaterial = new LineMaterial({
|
|
320
|
+
color: strokeParams.color ?? 0x000000,
|
|
321
|
+
linewidth: strokeWidth,
|
|
322
|
+
worldUnits: true,
|
|
323
|
+
resolution:
|
|
324
|
+
strokeParams.resolution || new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
325
|
+
depthTest: false, // Disable depth checking
|
|
326
|
+
depthWrite: false, // Don't affect depth buffer
|
|
327
|
+
transparent: true // Allow overdraw
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
const stroke = new Line2(lineGeometry, lineMaterial as any)
|
|
331
|
+
stroke.renderOrder = 1 // Higher than default 0
|
|
332
|
+
|
|
333
|
+
// Make parent mesh's material depthWrite: true
|
|
334
|
+
if (mesh.material instanceof THREE.Material) {
|
|
335
|
+
mesh.material.depthWrite = true
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
mesh.add(stroke)
|
|
339
|
+
;(mesh as any).stroke = stroke
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return mesh
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export const updateText = async (text: any, newText: string) => {
|
|
346
|
+
if (text.text === newText) return
|
|
347
|
+
text.text = newText
|
|
348
|
+
|
|
349
|
+
await new Promise<void>((resolve) => {
|
|
350
|
+
text.sync(() => {
|
|
351
|
+
resolve()
|
|
352
|
+
})
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export const createFastText = async (text: string, size: number = 10, color: number = 0xffffff) => {
|
|
359
|
+
// Create a Troika Text instance
|
|
360
|
+
const textMesh = new Text()
|
|
361
|
+
|
|
362
|
+
// Set basic properties
|
|
363
|
+
textMesh.text = text
|
|
364
|
+
textMesh.fontSize = size
|
|
365
|
+
textMesh.color = color
|
|
366
|
+
textMesh.font = fontTroika
|
|
367
|
+
|
|
368
|
+
// Center the text
|
|
369
|
+
textMesh.anchorX = 'center'
|
|
370
|
+
textMesh.anchorY = 'middle'
|
|
371
|
+
|
|
372
|
+
// Create a wrapper object to maintain compatibility with your existing code
|
|
373
|
+
|
|
374
|
+
// Sync any changes and make text visible
|
|
375
|
+
await new Promise<void>((resolve) => {
|
|
376
|
+
textMesh.sync(() => {
|
|
377
|
+
resolve()
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
return textMesh
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export const createMeshText = (text: string, size: number = 10, options?: ObjectOptions) => {
|
|
385
|
+
if (!loadedFont) {
|
|
386
|
+
loadFont()
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const textOptions = {
|
|
390
|
+
font: loadedFont,
|
|
391
|
+
size: size,
|
|
392
|
+
depth: size / 10,
|
|
393
|
+
curveSegments: 12,
|
|
394
|
+
bevelEnabled: true,
|
|
395
|
+
bevelThickness: 0,
|
|
396
|
+
bevelSize: 1,
|
|
397
|
+
bevelOffset: 0,
|
|
398
|
+
bevelSegments: 5
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const geometry = new TextGeometry(text, textOptions)
|
|
402
|
+
|
|
403
|
+
// Compute the bounding box
|
|
404
|
+
geometry.computeBoundingBox()
|
|
405
|
+
|
|
406
|
+
// Calculate the center offset based on the bounding box
|
|
407
|
+
const centerX = -0.5 * (geometry.boundingBox!.max.x + geometry.boundingBox!.min.x)
|
|
408
|
+
const centerY = -0.5 * (geometry.boundingBox!.max.y + geometry.boundingBox!.min.y)
|
|
409
|
+
|
|
410
|
+
// Apply the translation to center the text
|
|
411
|
+
geometry.translate(centerX, centerY, 0)
|
|
412
|
+
|
|
413
|
+
return createMesh(geometry, options)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export const createChars = (text: string, size: number = 10, options?: ObjectOptions) => {
|
|
417
|
+
if (!loadedFont) {
|
|
418
|
+
loadFont()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
console.log('Font loaded:', loadedFont)
|
|
422
|
+
|
|
423
|
+
const letterSpacing = 0.1 * size // Default spacing is 5% of character size
|
|
424
|
+
const centerText = true
|
|
425
|
+
|
|
426
|
+
// Create a group to hold all character meshes
|
|
427
|
+
const textGroup = new THREE.Group()
|
|
428
|
+
|
|
429
|
+
// Track the total width to position characters correctly
|
|
430
|
+
let currentPosition = 0
|
|
431
|
+
|
|
432
|
+
// Create individual characters
|
|
433
|
+
for (let i = 0; i < text.length; i++) {
|
|
434
|
+
const char = text[i]
|
|
435
|
+
|
|
436
|
+
// Skip if it's a space, but add spacing
|
|
437
|
+
if (char === ' ') {
|
|
438
|
+
currentPosition += size * 0.5 // Space width is half the character size
|
|
439
|
+
continue
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const textOptions = {
|
|
443
|
+
font: loadedFont,
|
|
444
|
+
size: size,
|
|
445
|
+
depth: 3,
|
|
446
|
+
curveSegments: 12,
|
|
447
|
+
bevelEnabled: true,
|
|
448
|
+
bevelThickness: 10,
|
|
449
|
+
bevelSize: 1,
|
|
450
|
+
bevelOffset: 0,
|
|
451
|
+
bevelSegments: 5
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Create geometry for this character
|
|
455
|
+
const geometry = new TextGeometry(char, textOptions)
|
|
456
|
+
|
|
457
|
+
// Compute bounding box for positioning
|
|
458
|
+
geometry.computeBoundingBox()
|
|
459
|
+
|
|
460
|
+
// Center the character vertically
|
|
461
|
+
const centerY = -0.5 * (geometry.boundingBox!.max.y + geometry.boundingBox!.min.y)
|
|
462
|
+
|
|
463
|
+
// We don't center horizontally - we'll position each character sequentially
|
|
464
|
+
geometry.translate(0, centerY, 0)
|
|
465
|
+
|
|
466
|
+
// Create mesh for this character
|
|
467
|
+
const charMesh = createMesh(geometry, options)
|
|
468
|
+
|
|
469
|
+
// Position character at the current offset
|
|
470
|
+
charMesh.position.x = currentPosition
|
|
471
|
+
|
|
472
|
+
// Add to group
|
|
473
|
+
textGroup.add(charMesh)
|
|
474
|
+
|
|
475
|
+
// Update position for next character
|
|
476
|
+
const charWidth = geometry.boundingBox!.max.x - geometry.boundingBox!.min.x
|
|
477
|
+
currentPosition += charWidth + letterSpacing
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Calculate total width (minus the last letter spacing)
|
|
481
|
+
const totalWidth = currentPosition - letterSpacing
|
|
482
|
+
|
|
483
|
+
// Center the entire text group if requested
|
|
484
|
+
if (centerText) {
|
|
485
|
+
textGroup.position.x = -totalWidth / 2
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return textGroup
|
|
489
|
+
}
|