@opsydyn/effect-three 0.0.0-stage → 0.2.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/CHANGELOG.md +69 -0
- package/LICENSE +21 -0
- package/README.md +189 -2
- package/dist/index-01kwfekm.js +5118 -0
- package/dist/index-01kwfekm.js.map +40 -0
- package/dist/index-4045wme0.js +129 -0
- package/dist/index-4045wme0.js.map +10 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1361 -0
- package/dist/index.js.map +14 -0
- package/dist/kernel/animation.d.ts +14 -0
- package/dist/kernel/animation.d.ts.map +1 -0
- package/dist/kernel/behaviours.d.ts +16 -0
- package/dist/kernel/behaviours.d.ts.map +1 -0
- package/dist/kernel/bodies.d.ts +60 -0
- package/dist/kernel/bodies.d.ts.map +1 -0
- package/dist/kernel/bodySources.d.ts +47 -0
- package/dist/kernel/bodySources.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +564 -0
- package/dist/kernel/build.d.ts.map +1 -0
- package/dist/kernel/cameraMotion.d.ts +39 -0
- package/dist/kernel/cameraMotion.d.ts.map +1 -0
- package/dist/kernel/clock.d.ts +17 -0
- package/dist/kernel/clock.d.ts.map +1 -0
- package/dist/kernel/contactShadowPasses.d.ts +47 -0
- package/dist/kernel/contactShadowPasses.d.ts.map +1 -0
- package/dist/kernel/description.d.ts +1120 -0
- package/dist/kernel/description.d.ts.map +1 -0
- package/dist/kernel/diff.d.ts +90 -0
- package/dist/kernel/diff.d.ts.map +1 -0
- package/dist/kernel/environmentPresets.d.ts +30 -0
- package/dist/kernel/environmentPresets.d.ts.map +1 -0
- package/dist/kernel/equality.d.ts +64 -0
- package/dist/kernel/equality.d.ts.map +1 -0
- package/dist/kernel/frames.d.ts +55 -0
- package/dist/kernel/frames.d.ts.map +1 -0
- package/dist/kernel/index.d.ts +37 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +335 -0
- package/dist/kernel/index.js.map +9 -0
- package/dist/kernel/instances.d.ts +26 -0
- package/dist/kernel/instances.d.ts.map +1 -0
- package/dist/kernel/labels.d.ts +51 -0
- package/dist/kernel/labels.d.ts.map +1 -0
- package/dist/kernel/loop.d.ts +49 -0
- package/dist/kernel/loop.d.ts.map +1 -0
- package/dist/kernel/materials.d.ts +24 -0
- package/dist/kernel/materials.d.ts.map +1 -0
- package/dist/kernel/motion.d.ts +36 -0
- package/dist/kernel/motion.d.ts.map +1 -0
- package/dist/kernel/orbit.d.ts +127 -0
- package/dist/kernel/orbit.d.ts.map +1 -0
- package/dist/kernel/overlays.d.ts +16 -0
- package/dist/kernel/overlays.d.ts.map +1 -0
- package/dist/kernel/performance.d.ts +41 -0
- package/dist/kernel/performance.d.ts.map +1 -0
- package/dist/kernel/physics.d.ts +166 -0
- package/dist/kernel/physics.d.ts.map +1 -0
- package/dist/kernel/physicsDebug.d.ts +18 -0
- package/dist/kernel/physicsDebug.d.ts.map +1 -0
- package/dist/kernel/pointer.d.ts +357 -0
- package/dist/kernel/pointer.d.ts.map +1 -0
- package/dist/kernel/renderKit.d.ts +49 -0
- package/dist/kernel/renderKit.d.ts.map +1 -0
- package/dist/kernel/renderer.d.ts +83 -0
- package/dist/kernel/renderer.d.ts.map +1 -0
- package/dist/kernel/resources.d.ts +112 -0
- package/dist/kernel/resources.d.ts.map +1 -0
- package/dist/kernel/sceneGraph.d.ts +155 -0
- package/dist/kernel/sceneGraph.d.ts.map +1 -0
- package/dist/kernel/sceneIndex.d.ts +90 -0
- package/dist/kernel/sceneIndex.d.ts.map +1 -0
- package/dist/kernel/stage.d.ts +87 -0
- package/dist/kernel/stage.d.ts.map +1 -0
- package/dist/kernel/textLayout.d.ts +29 -0
- package/dist/kernel/textLayout.d.ts.map +1 -0
- package/dist/kernel/tween.d.ts +35 -0
- package/dist/kernel/tween.d.ts.map +1 -0
- package/dist/kernel/xrInput.d.ts +107 -0
- package/dist/kernel/xrInput.d.ts.map +1 -0
- package/dist/kernel/xrLocomotion.d.ts +38 -0
- package/dist/kernel/xrLocomotion.d.ts.map +1 -0
- package/dist/runtime/FrameSource.d.ts +29 -0
- package/dist/runtime/FrameSource.d.ts.map +1 -0
- package/dist/runtime/ResourceRegistry.d.ts +18 -0
- package/dist/runtime/ResourceRegistry.d.ts.map +1 -0
- package/dist/runtime/SceneRuntime.d.ts +68 -0
- package/dist/runtime/SceneRuntime.d.ts.map +1 -0
- package/dist/runtime/SceneSchema.d.ts +2536 -0
- package/dist/runtime/SceneSchema.d.ts.map +1 -0
- package/dist/runtime/errors.d.ts +19 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/webgl/contactShadows.d.ts +5 -0
- package/dist/webgl/contactShadows.d.ts.map +1 -0
- package/dist/webgl/index.d.ts +11 -0
- package/dist/webgl/index.d.ts.map +1 -0
- package/dist/webgl/index.js +239 -0
- package/dist/webgl/index.js.map +13 -0
- package/dist/webgl/shaderGrid.d.ts +27 -0
- package/dist/webgl/shaderGrid.d.ts.map +1 -0
- package/dist/webgl/sky.d.ts +4 -0
- package/dist/webgl/sky.d.ts.map +1 -0
- package/dist/webgpu/contactShadows.d.ts +5 -0
- package/dist/webgpu/contactShadows.d.ts.map +1 -0
- package/dist/webgpu/index.d.ts +10 -0
- package/dist/webgpu/index.d.ts.map +1 -0
- package/dist/webgpu/index.js +216 -0
- package/dist/webgpu/index.js.map +13 -0
- package/dist/webgpu/shaderGrid.d.ts +5 -0
- package/dist/webgpu/shaderGrid.d.ts.map +1 -0
- package/dist/webgpu/sky.d.ts +4 -0
- package/dist/webgpu/sky.d.ts.map +1 -0
- package/package.json +82 -4
- package/src/index.ts +11 -0
- package/src/kernel/animation.ts +24 -0
- package/src/kernel/behaviours.ts +36 -0
- package/src/kernel/bodies.ts +338 -0
- package/src/kernel/bodySources.ts +203 -0
- package/src/kernel/build.ts +1325 -0
- package/src/kernel/cameraMotion.ts +202 -0
- package/src/kernel/clock.ts +35 -0
- package/src/kernel/contactShadowPasses.ts +207 -0
- package/src/kernel/description.ts +1197 -0
- package/src/kernel/diff.ts +383 -0
- package/src/kernel/environmentPresets.ts +38 -0
- package/src/kernel/equality.ts +604 -0
- package/src/kernel/frames.ts +124 -0
- package/src/kernel/index.ts +274 -0
- package/src/kernel/instances.ts +97 -0
- package/src/kernel/labels.ts +54 -0
- package/src/kernel/loop.ts +248 -0
- package/src/kernel/materials.ts +258 -0
- package/src/kernel/motion.ts +75 -0
- package/src/kernel/orbit.ts +554 -0
- package/src/kernel/overlays.ts +130 -0
- package/src/kernel/performance.ts +101 -0
- package/src/kernel/physics.ts +361 -0
- package/src/kernel/physicsDebug.ts +68 -0
- package/src/kernel/pointer.ts +785 -0
- package/src/kernel/renderKit.ts +54 -0
- package/src/kernel/renderer.ts +136 -0
- package/src/kernel/resources.ts +504 -0
- package/src/kernel/sceneGraph.ts +1442 -0
- package/src/kernel/sceneIndex.ts +219 -0
- package/src/kernel/stage.ts +157 -0
- package/src/kernel/textLayout.ts +102 -0
- package/src/kernel/tween.ts +80 -0
- package/src/kernel/xrInput.ts +230 -0
- package/src/kernel/xrLocomotion.ts +72 -0
- package/src/runtime/FrameSource.ts +41 -0
- package/src/runtime/ResourceRegistry.ts +24 -0
- package/src/runtime/SceneRuntime.ts +229 -0
- package/src/runtime/SceneSchema.ts +983 -0
- package/src/runtime/errors.ts +18 -0
- package/src/runtime/index.ts +10 -0
- package/src/webgl/contactShadows.ts +67 -0
- package/src/webgl/index.ts +19 -0
- package/src/webgl/shaderGrid.ts +196 -0
- package/src/webgl/sky.ts +42 -0
- package/src/webgpu/contactShadows.ts +83 -0
- package/src/webgpu/index.ts +19 -0
- package/src/webgpu/shaderGrid.ts +158 -0
- package/src/webgpu/sky.ts +35 -0
|
@@ -0,0 +1,1325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constructors that fill in defaults and return fully specified descriptions.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type {
|
|
7
|
+
AmbientLightNode,
|
|
8
|
+
Animation,
|
|
9
|
+
BasicMaterial,
|
|
10
|
+
Body,
|
|
11
|
+
Behave,
|
|
12
|
+
BorrowedNode,
|
|
13
|
+
Axis,
|
|
14
|
+
BoxGeometry,
|
|
15
|
+
Camera,
|
|
16
|
+
ContactShadows,
|
|
17
|
+
Environment,
|
|
18
|
+
Gestures,
|
|
19
|
+
GridNode,
|
|
20
|
+
Sky,
|
|
21
|
+
Xr,
|
|
22
|
+
LabelAlign,
|
|
23
|
+
LabelAnchorX,
|
|
24
|
+
LabelAnchorY,
|
|
25
|
+
LabelColorRange,
|
|
26
|
+
LabelNode,
|
|
27
|
+
MaterialSide,
|
|
28
|
+
ShaderGridNode,
|
|
29
|
+
TextAlign,
|
|
30
|
+
TextBevel,
|
|
31
|
+
TextGeometry,
|
|
32
|
+
Overlay,
|
|
33
|
+
Physics,
|
|
34
|
+
BodyRef,
|
|
35
|
+
Joint,
|
|
36
|
+
JointKind,
|
|
37
|
+
JointLimits,
|
|
38
|
+
JointMotor,
|
|
39
|
+
InteractionGroups,
|
|
40
|
+
ColliderDescription,
|
|
41
|
+
ColliderShapeDescription,
|
|
42
|
+
CapsuleGeometry,
|
|
43
|
+
CircleGeometry,
|
|
44
|
+
ConeGeometry,
|
|
45
|
+
CylinderGeometry,
|
|
46
|
+
DirectionalLightNode,
|
|
47
|
+
HemisphereLightNode,
|
|
48
|
+
Instance,
|
|
49
|
+
InstancesNode,
|
|
50
|
+
LambertMaterial,
|
|
51
|
+
Easing,
|
|
52
|
+
GeometryRef,
|
|
53
|
+
GroupNode,
|
|
54
|
+
MaterialRef,
|
|
55
|
+
MeshNode,
|
|
56
|
+
NormalMaterial,
|
|
57
|
+
ShadowMaterial,
|
|
58
|
+
OrthographicCamera,
|
|
59
|
+
PhongMaterial,
|
|
60
|
+
PhysicalMaterial,
|
|
61
|
+
PerspectiveCamera,
|
|
62
|
+
PlaneGeometry,
|
|
63
|
+
PointLightNode,
|
|
64
|
+
PostEffect,
|
|
65
|
+
RenderSettings,
|
|
66
|
+
RenderTexture,
|
|
67
|
+
RingGeometry,
|
|
68
|
+
ResourceDefinitions,
|
|
69
|
+
Rgb,
|
|
70
|
+
Rotation,
|
|
71
|
+
SceneDescription,
|
|
72
|
+
SceneNode,
|
|
73
|
+
SphereGeometry,
|
|
74
|
+
SpotLightNode,
|
|
75
|
+
TorusGeometry,
|
|
76
|
+
StandardMaterial,
|
|
77
|
+
Transition,
|
|
78
|
+
Vec3,
|
|
79
|
+
View,
|
|
80
|
+
ViewRect,
|
|
81
|
+
XrAnchor,
|
|
82
|
+
XrGlide,
|
|
83
|
+
XrSpace,
|
|
84
|
+
Handedness,
|
|
85
|
+
HandJoint,
|
|
86
|
+
} from './description.ts';
|
|
87
|
+
|
|
88
|
+
const origin: Vec3 = [0, 0, 0];
|
|
89
|
+
const unitScale: Vec3 = [1, 1, 1];
|
|
90
|
+
const noRotation: Rotation = { _tag: 'Fixed', euler: origin };
|
|
91
|
+
const noResources: ResourceDefinitions = { geometries: [], materials: [] };
|
|
92
|
+
const instantTransition: Transition = { _tag: 'Instant' };
|
|
93
|
+
|
|
94
|
+
/** Model-owned Euler angles in radians. */
|
|
95
|
+
export const fixed = (euler: Vec3): Rotation => ({ _tag: 'Fixed', euler });
|
|
96
|
+
|
|
97
|
+
/** A spin behaviour: `phase + radiansPerSecond * elapsedSeconds` about `axis`. */
|
|
98
|
+
export const spin = (options: {
|
|
99
|
+
readonly axis: Axis;
|
|
100
|
+
readonly radiansPerSecond: number;
|
|
101
|
+
readonly phase?: number;
|
|
102
|
+
}): Rotation => ({
|
|
103
|
+
_tag: 'Spin',
|
|
104
|
+
axis: options.axis,
|
|
105
|
+
radiansPerSecond: options.radiansPerSecond,
|
|
106
|
+
phase: options.phase ?? 0,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/** Box geometry. Each dimension defaults to 1. */
|
|
110
|
+
export const box = (
|
|
111
|
+
size: { readonly width?: number; readonly height?: number; readonly depth?: number } = {},
|
|
112
|
+
): BoxGeometry => ({
|
|
113
|
+
_tag: 'Box',
|
|
114
|
+
width: size.width ?? 1,
|
|
115
|
+
height: size.height ?? 1,
|
|
116
|
+
depth: size.depth ?? 1,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/** Sphere geometry. Defaults (as three.js): radius 1, 32 × 16 segments. */
|
|
120
|
+
export const sphere = (
|
|
121
|
+
options: {
|
|
122
|
+
readonly radius?: number;
|
|
123
|
+
readonly widthSegments?: number;
|
|
124
|
+
readonly heightSegments?: number;
|
|
125
|
+
} = {},
|
|
126
|
+
): SphereGeometry => ({
|
|
127
|
+
_tag: 'Sphere',
|
|
128
|
+
radius: options.radius ?? 1,
|
|
129
|
+
widthSegments: options.widthSegments ?? 32,
|
|
130
|
+
heightSegments: options.heightSegments ?? 16,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/** Plane geometry in the XY plane, facing +Z. Each dimension defaults to 1. */
|
|
134
|
+
export const plane = (
|
|
135
|
+
size: { readonly width?: number; readonly height?: number } = {},
|
|
136
|
+
): PlaneGeometry => ({ _tag: 'Plane', width: size.width ?? 1, height: size.height ?? 1 });
|
|
137
|
+
|
|
138
|
+
/** Cylinder geometry along Y. Defaults (as three.js): radius 1 top and bottom, height 1, 32 segments. */
|
|
139
|
+
export const cylinder = (
|
|
140
|
+
options: {
|
|
141
|
+
readonly radiusTop?: number;
|
|
142
|
+
readonly radiusBottom?: number;
|
|
143
|
+
readonly height?: number;
|
|
144
|
+
readonly radialSegments?: number;
|
|
145
|
+
} = {},
|
|
146
|
+
): CylinderGeometry => ({
|
|
147
|
+
_tag: 'Cylinder',
|
|
148
|
+
radiusTop: options.radiusTop ?? 1,
|
|
149
|
+
radiusBottom: options.radiusBottom ?? 1,
|
|
150
|
+
height: options.height ?? 1,
|
|
151
|
+
radialSegments: options.radialSegments ?? 32,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/** Torus geometry. Defaults (as three.js): radius 1, tube 0.4, 12 × 48 segments. */
|
|
155
|
+
export const torus = (
|
|
156
|
+
options: {
|
|
157
|
+
readonly radius?: number;
|
|
158
|
+
readonly tube?: number;
|
|
159
|
+
readonly radialSegments?: number;
|
|
160
|
+
readonly tubularSegments?: number;
|
|
161
|
+
} = {},
|
|
162
|
+
): TorusGeometry => ({
|
|
163
|
+
_tag: 'Torus',
|
|
164
|
+
radius: options.radius ?? 1,
|
|
165
|
+
tube: options.tube ?? 0.4,
|
|
166
|
+
radialSegments: options.radialSegments ?? 12,
|
|
167
|
+
tubularSegments: options.tubularSegments ?? 48,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
/** Cone geometry along Y. Defaults (as three.js): radius 1, height 1, 32 segments. */
|
|
171
|
+
export const cone = (
|
|
172
|
+
options: {
|
|
173
|
+
readonly radius?: number;
|
|
174
|
+
readonly height?: number;
|
|
175
|
+
readonly radialSegments?: number;
|
|
176
|
+
} = {},
|
|
177
|
+
): ConeGeometry => ({
|
|
178
|
+
_tag: 'Cone',
|
|
179
|
+
radius: options.radius ?? 1,
|
|
180
|
+
height: options.height ?? 1,
|
|
181
|
+
radialSegments: options.radialSegments ?? 32,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/** Capsule geometry along Y. Defaults (as three.js): radius 1, height 1, 4 cap and 8 radial segments. */
|
|
185
|
+
export const capsule = (
|
|
186
|
+
options: {
|
|
187
|
+
readonly radius?: number;
|
|
188
|
+
readonly height?: number;
|
|
189
|
+
readonly capSegments?: number;
|
|
190
|
+
readonly radialSegments?: number;
|
|
191
|
+
} = {},
|
|
192
|
+
): CapsuleGeometry => ({
|
|
193
|
+
_tag: 'Capsule',
|
|
194
|
+
radius: options.radius ?? 1,
|
|
195
|
+
height: options.height ?? 1,
|
|
196
|
+
capSegments: options.capSegments ?? 4,
|
|
197
|
+
radialSegments: options.radialSegments ?? 8,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/** Ring geometry facing +Z. Defaults (as three.js): radii 0.5 and 1, 32 segments. */
|
|
201
|
+
export const ring = (
|
|
202
|
+
options: {
|
|
203
|
+
readonly innerRadius?: number;
|
|
204
|
+
readonly outerRadius?: number;
|
|
205
|
+
readonly thetaSegments?: number;
|
|
206
|
+
} = {},
|
|
207
|
+
): RingGeometry => ({
|
|
208
|
+
_tag: 'Ring',
|
|
209
|
+
innerRadius: options.innerRadius ?? 0.5,
|
|
210
|
+
outerRadius: options.outerRadius ?? 1,
|
|
211
|
+
thetaSegments: options.thetaSegments ?? 32,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
/** Circle geometry facing +Z. Defaults (as three.js): radius 1, 32 segments. */
|
|
215
|
+
export const circle = (
|
|
216
|
+
options: { readonly radius?: number; readonly segments?: number } = {},
|
|
217
|
+
): CircleGeometry => ({
|
|
218
|
+
_tag: 'Circle',
|
|
219
|
+
radius: options.radius ?? 1,
|
|
220
|
+
segments: options.segments ?? 32,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Text from the typeface font lent under `font`. Defaults as Drei's `Text3D`: size 1,
|
|
225
|
+
* depth 0.2, 8 curve segments, no bevel (a bevel given defaults to thickness 0.1, size
|
|
226
|
+
* 0.01, offset 0, 4 segments), not centred. Depth 0 draws flat text. Letter spacing (in
|
|
227
|
+
* ems), line height (a multiple of the font's) and alignment default to the font's own
|
|
228
|
+
* layout, left aligned; unlike Drei, where three ignores them, they apply.
|
|
229
|
+
*/
|
|
230
|
+
export const text = (options: {
|
|
231
|
+
readonly text: string;
|
|
232
|
+
readonly font: string;
|
|
233
|
+
readonly size?: number;
|
|
234
|
+
readonly depth?: number;
|
|
235
|
+
readonly curveSegments?: number;
|
|
236
|
+
readonly bevel?: Partial<TextBevel>;
|
|
237
|
+
readonly center?: boolean;
|
|
238
|
+
readonly letterSpacing?: number;
|
|
239
|
+
readonly lineHeight?: number;
|
|
240
|
+
readonly align?: TextAlign;
|
|
241
|
+
}): TextGeometry => ({
|
|
242
|
+
_tag: 'Text',
|
|
243
|
+
text: options.text,
|
|
244
|
+
font: options.font,
|
|
245
|
+
size: options.size ?? 1,
|
|
246
|
+
depth: options.depth ?? 0.2,
|
|
247
|
+
curveSegments: options.curveSegments ?? 8,
|
|
248
|
+
bevel:
|
|
249
|
+
options.bevel === undefined
|
|
250
|
+
? null
|
|
251
|
+
: {
|
|
252
|
+
thickness: options.bevel.thickness ?? 0.1,
|
|
253
|
+
size: options.bevel.size ?? 0.01,
|
|
254
|
+
offset: options.bevel.offset ?? 0,
|
|
255
|
+
segments: options.bevel.segments ?? 4,
|
|
256
|
+
},
|
|
257
|
+
center: options.center ?? false,
|
|
258
|
+
letterSpacing: options.letterSpacing ?? 0,
|
|
259
|
+
lineHeight: options.lineHeight ?? 1,
|
|
260
|
+
align: options.align ?? 'left',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
/** Position and scale changes apply immediately. The default. */
|
|
264
|
+
export const instant = (): Transition => instantTransition;
|
|
265
|
+
|
|
266
|
+
/** Position and scale changes ease to their new values over `seconds` of scene time. */
|
|
267
|
+
export const ease = (options: {
|
|
268
|
+
readonly seconds: number;
|
|
269
|
+
readonly easing?: Easing;
|
|
270
|
+
}): Transition => ({
|
|
271
|
+
_tag: 'Ease',
|
|
272
|
+
seconds: options.seconds,
|
|
273
|
+
easing: options.easing ?? 'easeInOutCubic',
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Unlit material. Defaults (as three.js): white, opaque, front faces.
|
|
278
|
+
*/
|
|
279
|
+
export const basic = (options: Partial<Omit<BasicMaterial, '_tag'>> = {}): BasicMaterial => ({
|
|
280
|
+
_tag: 'Basic',
|
|
281
|
+
color: options.color ?? 0xffffff,
|
|
282
|
+
map: options.map ?? null,
|
|
283
|
+
opacity: options.opacity ?? 1,
|
|
284
|
+
transparent: options.transparent ?? false,
|
|
285
|
+
side: options.side ?? 'front',
|
|
286
|
+
wireframe: options.wireframe ?? false,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Matte material. Defaults (as three.js): white, no emission, smooth shading.
|
|
291
|
+
*/
|
|
292
|
+
export const lambert = (options: Partial<Omit<LambertMaterial, '_tag'>> = {}): LambertMaterial => ({
|
|
293
|
+
_tag: 'Lambert',
|
|
294
|
+
color: options.color ?? 0xffffff,
|
|
295
|
+
map: options.map ?? null,
|
|
296
|
+
emissive: options.emissive ?? 0x000000,
|
|
297
|
+
opacity: options.opacity ?? 1,
|
|
298
|
+
transparent: options.transparent ?? false,
|
|
299
|
+
side: options.side ?? 'front',
|
|
300
|
+
wireframe: options.wireframe ?? false,
|
|
301
|
+
flatShading: options.flatShading ?? false,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Shiny material. Defaults (as three.js): white, specular `0x111111`, shininess 30.
|
|
306
|
+
*/
|
|
307
|
+
export const phong = (options: Partial<Omit<PhongMaterial, '_tag'>> = {}): PhongMaterial => ({
|
|
308
|
+
_tag: 'Phong',
|
|
309
|
+
color: options.color ?? 0xffffff,
|
|
310
|
+
map: options.map ?? null,
|
|
311
|
+
emissive: options.emissive ?? 0x000000,
|
|
312
|
+
specular: options.specular ?? 0x111111,
|
|
313
|
+
shininess: options.shininess ?? 30,
|
|
314
|
+
opacity: options.opacity ?? 1,
|
|
315
|
+
transparent: options.transparent ?? false,
|
|
316
|
+
side: options.side ?? 'front',
|
|
317
|
+
wireframe: options.wireframe ?? false,
|
|
318
|
+
flatShading: options.flatShading ?? false,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Physically based material. Defaults (as three.js): white, roughness 1, metalness 0.
|
|
323
|
+
*/
|
|
324
|
+
export const standard = (
|
|
325
|
+
options: Partial<Omit<StandardMaterial, '_tag'>> = {},
|
|
326
|
+
): StandardMaterial => ({
|
|
327
|
+
_tag: 'Standard',
|
|
328
|
+
color: options.color ?? 0xffffff,
|
|
329
|
+
map: options.map ?? null,
|
|
330
|
+
roughness: options.roughness ?? 1,
|
|
331
|
+
metalness: options.metalness ?? 0,
|
|
332
|
+
emissive: options.emissive ?? 0x000000,
|
|
333
|
+
emissiveIntensity: options.emissiveIntensity ?? 1,
|
|
334
|
+
opacity: options.opacity ?? 1,
|
|
335
|
+
transparent: options.transparent ?? false,
|
|
336
|
+
side: options.side ?? 'front',
|
|
337
|
+
wireframe: options.wireframe ?? false,
|
|
338
|
+
flatShading: options.flatShading ?? false,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Physically based material with clearcoat and transmission. Defaults (as three.js):
|
|
343
|
+
* the standard defaults, no clearcoat or transmission, index of refraction 1.5.
|
|
344
|
+
*/
|
|
345
|
+
export const physical = (
|
|
346
|
+
options: Partial<Omit<PhysicalMaterial, '_tag'>> = {},
|
|
347
|
+
): PhysicalMaterial => ({
|
|
348
|
+
_tag: 'Physical',
|
|
349
|
+
color: options.color ?? 0xffffff,
|
|
350
|
+
map: options.map ?? null,
|
|
351
|
+
roughness: options.roughness ?? 1,
|
|
352
|
+
metalness: options.metalness ?? 0,
|
|
353
|
+
emissive: options.emissive ?? 0x000000,
|
|
354
|
+
emissiveIntensity: options.emissiveIntensity ?? 1,
|
|
355
|
+
clearcoat: options.clearcoat ?? 0,
|
|
356
|
+
clearcoatRoughness: options.clearcoatRoughness ?? 0,
|
|
357
|
+
transmission: options.transmission ?? 0,
|
|
358
|
+
ior: options.ior ?? 1.5,
|
|
359
|
+
opacity: options.opacity ?? 1,
|
|
360
|
+
transparent: options.transparent ?? false,
|
|
361
|
+
side: options.side ?? 'front',
|
|
362
|
+
wireframe: options.wireframe ?? false,
|
|
363
|
+
flatShading: options.flatShading ?? false,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Normal-coloured material, for debugging geometry. Defaults as three.js.
|
|
368
|
+
*/
|
|
369
|
+
export const normalMaterial = (
|
|
370
|
+
options: Partial<Omit<NormalMaterial, '_tag'>> = {},
|
|
371
|
+
): NormalMaterial => ({
|
|
372
|
+
_tag: 'Normal',
|
|
373
|
+
opacity: options.opacity ?? 1,
|
|
374
|
+
transparent: options.transparent ?? false,
|
|
375
|
+
side: options.side ?? 'front',
|
|
376
|
+
wireframe: options.wireframe ?? false,
|
|
377
|
+
flatShading: options.flatShading ?? false,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* A shadow catcher: shows only the shadows cast onto it (three.js's `ShadowMaterial`).
|
|
382
|
+
* Defaults as three.js: black, opacity 1. Needs `render.shadows` and `receiveShadow`.
|
|
383
|
+
*/
|
|
384
|
+
export const shadowMaterial = (
|
|
385
|
+
options: { readonly color?: Rgb; readonly opacity?: number } = {},
|
|
386
|
+
): ShadowMaterial => ({
|
|
387
|
+
_tag: 'Shadow',
|
|
388
|
+
color: options.color ?? 0x000000,
|
|
389
|
+
opacity: options.opacity ?? 1,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* A grid of lines on the XZ plane (three.js's `GridHelper`). Defaults as three.js: 10
|
|
394
|
+
* across, 10 divisions, lines 0x888888 and centre lines 0x444444, at the origin.
|
|
395
|
+
*/
|
|
396
|
+
export const grid = (options: {
|
|
397
|
+
readonly key: string;
|
|
398
|
+
readonly position?: Vec3;
|
|
399
|
+
readonly size?: number;
|
|
400
|
+
readonly divisions?: number;
|
|
401
|
+
readonly color?: Rgb;
|
|
402
|
+
readonly centerColor?: Rgb;
|
|
403
|
+
}): GridNode => ({
|
|
404
|
+
_tag: 'Grid',
|
|
405
|
+
key: options.key,
|
|
406
|
+
position: options.position ?? origin,
|
|
407
|
+
size: options.size ?? 10,
|
|
408
|
+
divisions: options.divisions ?? 10,
|
|
409
|
+
color: options.color ?? 0x888888,
|
|
410
|
+
centerColor: options.centerColor ?? 0x444444,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Drei's `Grid`, with its defaults: a 1 × 1 plane (pass `size`, or `infinite`), cells 0.5
|
|
415
|
+
* apart in black at thickness 0.5, sections 1 apart in `#2080ff` at thickness 1, fading
|
|
416
|
+
* over 100 units from the camera at strength 1, drawn on the back side.
|
|
417
|
+
*/
|
|
418
|
+
export const shaderGrid = (options: {
|
|
419
|
+
readonly key: string;
|
|
420
|
+
readonly position?: Vec3;
|
|
421
|
+
readonly rotation?: Vec3;
|
|
422
|
+
readonly size?: readonly [width: number, height: number];
|
|
423
|
+
readonly cellSize?: number;
|
|
424
|
+
readonly cellThickness?: number;
|
|
425
|
+
readonly cellColor?: Rgb;
|
|
426
|
+
readonly sectionSize?: number;
|
|
427
|
+
readonly sectionThickness?: number;
|
|
428
|
+
readonly sectionColor?: Rgb;
|
|
429
|
+
readonly infinite?: boolean;
|
|
430
|
+
readonly followCamera?: boolean;
|
|
431
|
+
readonly fadeDistance?: number;
|
|
432
|
+
readonly fadeStrength?: number;
|
|
433
|
+
readonly fadeFrom?: number;
|
|
434
|
+
readonly side?: MaterialSide;
|
|
435
|
+
}): ShaderGridNode => ({
|
|
436
|
+
_tag: 'ShaderGrid',
|
|
437
|
+
key: options.key,
|
|
438
|
+
position: options.position ?? origin,
|
|
439
|
+
rotation: options.rotation ?? origin,
|
|
440
|
+
size: options.size ?? [1, 1],
|
|
441
|
+
cellSize: options.cellSize ?? 0.5,
|
|
442
|
+
cellThickness: options.cellThickness ?? 0.5,
|
|
443
|
+
cellColor: options.cellColor ?? 0x000000,
|
|
444
|
+
sectionSize: options.sectionSize ?? 1,
|
|
445
|
+
sectionThickness: options.sectionThickness ?? 1,
|
|
446
|
+
sectionColor: options.sectionColor ?? 0x2080ff,
|
|
447
|
+
infinite: options.infinite ?? false,
|
|
448
|
+
followCamera: options.followCamera ?? false,
|
|
449
|
+
fadeDistance: options.fadeDistance ?? 100,
|
|
450
|
+
fadeStrength: options.fadeStrength ?? 1,
|
|
451
|
+
fadeFrom: options.fadeFrom ?? 1,
|
|
452
|
+
side: options.side ?? 'back',
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
/** A reference to a shared geometry defined in the scene's resources. */
|
|
456
|
+
export const sharedGeometry = (id: string): GeometryRef => ({ _tag: 'SharedGeometry', id });
|
|
457
|
+
/** A geometry lent under `id` (`store.lendGeometry`): empty until it is lent. */
|
|
458
|
+
export const lentGeometry = (id: string): GeometryRef => ({ _tag: 'LentGeometry', id });
|
|
459
|
+
|
|
460
|
+
/** A reference to a shared material defined in the scene's resources. */
|
|
461
|
+
export const sharedMaterial = (id: string): MaterialRef => ({ _tag: 'SharedMaterial', id });
|
|
462
|
+
|
|
463
|
+
/** Key and optional transform shared by `group` and `mesh`. */
|
|
464
|
+
export type TransformOptions = {
|
|
465
|
+
readonly key: string;
|
|
466
|
+
readonly position?: Vec3;
|
|
467
|
+
readonly rotation?: Rotation;
|
|
468
|
+
readonly scale?: Vec3;
|
|
469
|
+
/** How position and scale changes are shown. Defaults to `instant()`. */
|
|
470
|
+
readonly transition?: Transition /** A registered behaviour to apply on top of the transform. */;
|
|
471
|
+
readonly behaviour?: Behave | null;
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
/** A group of child nodes. */
|
|
475
|
+
export const group = (
|
|
476
|
+
options: TransformOptions & {
|
|
477
|
+
readonly interactive?: boolean;
|
|
478
|
+
readonly children?: ReadonlyArray<SceneNode>;
|
|
479
|
+
readonly body?: Body | null;
|
|
480
|
+
readonly follow?: XrSpace | null;
|
|
481
|
+
},
|
|
482
|
+
): GroupNode => ({
|
|
483
|
+
_tag: 'Group',
|
|
484
|
+
key: options.key,
|
|
485
|
+
position: options.position ?? origin,
|
|
486
|
+
rotation: options.rotation ?? noRotation,
|
|
487
|
+
scale: options.scale ?? unitScale,
|
|
488
|
+
transition: options.transition ?? instantTransition,
|
|
489
|
+
behaviour: options.behaviour ?? null,
|
|
490
|
+
interactive: options.interactive ?? false,
|
|
491
|
+
children: options.children ?? [],
|
|
492
|
+
body: options.body ?? null,
|
|
493
|
+
follow: options.follow ?? null,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
/** The head (the viewer), for content that stays in view. */
|
|
497
|
+
export const headSpace = (): XrSpace => ({ _tag: 'Head' });
|
|
498
|
+
|
|
499
|
+
/** A tracked hand's joint (`index-finger-tip`, say). */
|
|
500
|
+
export const jointSpace = (hand: Handedness, joint: HandJoint): XrSpace => ({
|
|
501
|
+
_tag: 'Joint',
|
|
502
|
+
hand,
|
|
503
|
+
joint,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
/** A controller's grip (where it is held) or its pointing ray. */
|
|
507
|
+
export const controllerSpace = (hand: Handedness, space: 'grip' | 'ray' = 'grip'): XrSpace => ({
|
|
508
|
+
_tag: 'Controller',
|
|
509
|
+
hand,
|
|
510
|
+
space,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
/** An anchor the scene placed with `xr({ anchors })`. */
|
|
514
|
+
export const anchorSpace = (id: string): XrSpace => ({ _tag: 'Anchor', id });
|
|
515
|
+
|
|
516
|
+
/** A detected plane, by the id an `XrPlanes` event gave it. */
|
|
517
|
+
export const planeSpace = (id: string): XrSpace => ({ _tag: 'Plane', id });
|
|
518
|
+
/** A mesh the session detected (`xr({ meshes: true })`), by its id from `XrMeshes`. */
|
|
519
|
+
export const meshSpace = (id: string): XrSpace => ({ _tag: 'Mesh', id });
|
|
520
|
+
|
|
521
|
+
/** The id a detected mesh's geometry is lent under, for `xrMesh` and the adapter. */
|
|
522
|
+
export const xrMeshGeometryId = (id: string): string => `xr-mesh/${id}`;
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* A detected mesh's geometry (`xr({ meshes: true })`), in its own space: put it in a
|
|
526
|
+
* group following `meshSpace(id)`. Empty until the session has detected it.
|
|
527
|
+
*/
|
|
528
|
+
export const xrMesh = (id: string): GeometryRef => lentGeometry(xrMeshGeometryId(id));
|
|
529
|
+
|
|
530
|
+
/** An anchor at a place in the scene (Euler rotation, unturned by default). */
|
|
531
|
+
export const xrAnchor = (options: {
|
|
532
|
+
readonly id: string;
|
|
533
|
+
readonly position: Vec3;
|
|
534
|
+
readonly rotation?: Vec3;
|
|
535
|
+
}): XrAnchor => ({
|
|
536
|
+
id: options.id,
|
|
537
|
+
position: options.position,
|
|
538
|
+
rotation: options.rotation ?? origin,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
/** A mesh. Not interactive unless `interactive` is true. */
|
|
542
|
+
export const mesh = (
|
|
543
|
+
options: TransformOptions & {
|
|
544
|
+
readonly geometry: GeometryRef;
|
|
545
|
+
readonly material: MaterialRef;
|
|
546
|
+
readonly interactive?: boolean;
|
|
547
|
+
readonly castShadow?: boolean;
|
|
548
|
+
readonly receiveShadow?: boolean;
|
|
549
|
+
readonly body?: Body | null;
|
|
550
|
+
},
|
|
551
|
+
): MeshNode => ({
|
|
552
|
+
_tag: 'Mesh',
|
|
553
|
+
key: options.key,
|
|
554
|
+
position: options.position ?? origin,
|
|
555
|
+
rotation: options.rotation ?? noRotation,
|
|
556
|
+
scale: options.scale ?? unitScale,
|
|
557
|
+
transition: options.transition ?? instantTransition,
|
|
558
|
+
behaviour: options.behaviour ?? null,
|
|
559
|
+
geometry: options.geometry,
|
|
560
|
+
material: options.material,
|
|
561
|
+
interactive: options.interactive ?? false,
|
|
562
|
+
castShadow: options.castShadow ?? false,
|
|
563
|
+
receiveShadow: options.receiveShadow ?? false,
|
|
564
|
+
body: options.body ?? null,
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* A rigid body for a mesh. Defaults as react-three-rapier's `RigidBody` and Rapier: dynamic,
|
|
569
|
+
* a cuboid collider from the bounding box, density 1, friction 0.5, restitution 0, no
|
|
570
|
+
* damping, gravity scale 1, no CCD, not a sensor, at rest, no impulse.
|
|
571
|
+
*/
|
|
572
|
+
export const body = (options: Partial<Body> = {}): Body => ({
|
|
573
|
+
type: options.type ?? 'dynamic',
|
|
574
|
+
collider: options.collider ?? 'cuboid',
|
|
575
|
+
mass: options.mass ?? null,
|
|
576
|
+
friction: options.friction ?? 0.5,
|
|
577
|
+
restitution: options.restitution ?? 0,
|
|
578
|
+
linearDamping: options.linearDamping ?? 0,
|
|
579
|
+
angularDamping: options.angularDamping ?? 0,
|
|
580
|
+
gravityScale: options.gravityScale ?? 1,
|
|
581
|
+
ccd: options.ccd ?? false,
|
|
582
|
+
sensor: options.sensor ?? false,
|
|
583
|
+
linearVelocity: options.linearVelocity ?? origin,
|
|
584
|
+
angularVelocity: options.angularVelocity ?? origin,
|
|
585
|
+
impulse: options.impulse ?? null,
|
|
586
|
+
colliders: options.colliders ?? null,
|
|
587
|
+
contactForces: options.contactForces ?? null,
|
|
588
|
+
collisionGroups: options.collisionGroups ?? null,
|
|
589
|
+
solverGroups: options.solverGroups ?? null,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Collision groups, as react-three-rapier's `interactionGroups(memberships, filters)`:
|
|
594
|
+
* in `memberships` (0 to 15), interacting with `filters` (every group when omitted).
|
|
595
|
+
*/
|
|
596
|
+
export const interactionGroups = (
|
|
597
|
+
memberships: number | ReadonlyArray<number>,
|
|
598
|
+
filters?: number | ReadonlyArray<number>,
|
|
599
|
+
): InteractionGroups => ({
|
|
600
|
+
memberships: typeof memberships === 'number' ? [memberships] : memberships,
|
|
601
|
+
filters: filters === undefined ? null : typeof filters === 'number' ? [filters] : filters,
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
const placedCollider = (
|
|
605
|
+
shape: ColliderShapeDescription,
|
|
606
|
+
options: { readonly position?: Vec3; readonly rotation?: Vec3; readonly name?: string | null },
|
|
607
|
+
): ColliderDescription => ({
|
|
608
|
+
shape,
|
|
609
|
+
position: options.position ?? origin,
|
|
610
|
+
rotation: options.rotation ?? origin,
|
|
611
|
+
name: options.name ?? null,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
/** A box collider, `halfExtents` from its middle, as react-three-rapier's `CuboidCollider`. */
|
|
615
|
+
export const cuboidCollider = (options: {
|
|
616
|
+
readonly halfExtents: Vec3;
|
|
617
|
+
readonly position?: Vec3;
|
|
618
|
+
readonly rotation?: Vec3;
|
|
619
|
+
readonly name?: string | null;
|
|
620
|
+
}): ColliderDescription =>
|
|
621
|
+
placedCollider({ _tag: 'Cuboid', halfExtents: options.halfExtents }, options);
|
|
622
|
+
|
|
623
|
+
/** A ball collider, as `BallCollider`. */
|
|
624
|
+
export const ballCollider = (options: {
|
|
625
|
+
readonly radius: number;
|
|
626
|
+
readonly position?: Vec3;
|
|
627
|
+
readonly name?: string | null;
|
|
628
|
+
}): ColliderDescription => placedCollider({ _tag: 'Ball', radius: options.radius }, options);
|
|
629
|
+
|
|
630
|
+
type Upright = {
|
|
631
|
+
readonly halfHeight: number;
|
|
632
|
+
readonly radius: number;
|
|
633
|
+
readonly position?: Vec3;
|
|
634
|
+
readonly rotation?: Vec3;
|
|
635
|
+
/** The name collisions report this collider by. */
|
|
636
|
+
readonly name?: string | null;
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
/** A capsule collider along y: `halfHeight` of cylinder, capped by `radius`. */
|
|
640
|
+
export const capsuleCollider = (options: Upright): ColliderDescription =>
|
|
641
|
+
placedCollider(
|
|
642
|
+
{ _tag: 'Capsule', halfHeight: options.halfHeight, radius: options.radius },
|
|
643
|
+
options,
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
/** A cylinder collider along y. */
|
|
647
|
+
export const cylinderCollider = (options: Upright): ColliderDescription =>
|
|
648
|
+
placedCollider(
|
|
649
|
+
{ _tag: 'Cylinder', halfHeight: options.halfHeight, radius: options.radius },
|
|
650
|
+
options,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
/** A cone collider along y, its point up. */
|
|
654
|
+
export const coneCollider = (options: Upright): ColliderDescription =>
|
|
655
|
+
placedCollider({ _tag: 'Cone', halfHeight: options.halfHeight, radius: options.radius }, options);
|
|
656
|
+
|
|
657
|
+
/** A motor turning (or sliding) at `velocity`, with `factor` how hard it holds the speed. */
|
|
658
|
+
export const velocityMotor = (velocity: number, factor = 1): JointMotor => ({
|
|
659
|
+
position: 0,
|
|
660
|
+
velocity,
|
|
661
|
+
stiffness: 0,
|
|
662
|
+
damping: factor,
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
/** A motor holding `position`, as a spring of `stiffness` damped by `damping`. */
|
|
666
|
+
export const positionMotor = (
|
|
667
|
+
position: number,
|
|
668
|
+
stiffness: number,
|
|
669
|
+
damping: number,
|
|
670
|
+
): JointMotor => ({
|
|
671
|
+
position,
|
|
672
|
+
velocity: 0,
|
|
673
|
+
stiffness,
|
|
674
|
+
damping,
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const noJoints: ReadonlyArray<Joint> = [];
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* A physics world. Defaults as react-three-rapier's `Physics`: Earth gravity
|
|
681
|
+
* `[0, -9.81, 0]`, running, fixed steps of 1/60 s, interpolated; no joints.
|
|
682
|
+
*/
|
|
683
|
+
export const physics = (options: Partial<Physics> = {}): Physics => ({
|
|
684
|
+
gravity: options.gravity ?? [0, -9.81, 0],
|
|
685
|
+
paused: options.paused ?? false,
|
|
686
|
+
timeStep: options.timeStep ?? 1 / 60,
|
|
687
|
+
interpolate: options.interpolate ?? true,
|
|
688
|
+
debug: options.debug ?? false,
|
|
689
|
+
joints: options.joints ?? noJoints,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
/** A node's body (`instance` omitted), or one instance's of an instances node. */
|
|
693
|
+
export const bodyRef = (key: string, instance: string | null = null): BodyRef => ({
|
|
694
|
+
key,
|
|
695
|
+
instance,
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const refOf = (ref: string | BodyRef): BodyRef => (typeof ref === 'string' ? bodyRef(ref) : ref);
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* A joint between two bodies, named by node key or `bodyRef`. Anchors default to each
|
|
702
|
+
* body's origin; the bodies still collide with each other unless `contacts` is false.
|
|
703
|
+
*/
|
|
704
|
+
export const joint = (options: {
|
|
705
|
+
readonly key: string;
|
|
706
|
+
readonly a: string | BodyRef;
|
|
707
|
+
readonly b: string | BodyRef;
|
|
708
|
+
readonly anchorA?: Vec3;
|
|
709
|
+
readonly anchorB?: Vec3;
|
|
710
|
+
readonly kind: JointKind;
|
|
711
|
+
readonly contacts?: boolean;
|
|
712
|
+
}): Joint => ({
|
|
713
|
+
key: options.key,
|
|
714
|
+
a: refOf(options.a),
|
|
715
|
+
b: refOf(options.b),
|
|
716
|
+
anchorA: options.anchorA ?? origin,
|
|
717
|
+
anchorB: options.anchorB ?? origin,
|
|
718
|
+
kind: options.kind,
|
|
719
|
+
contacts: options.contacts ?? true,
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
/** Holds two bodies still relative to each other. Frames default to unturned. */
|
|
723
|
+
export const fixedJoint = (
|
|
724
|
+
options: { readonly frameA?: Vec3; readonly frameB?: Vec3 } = {},
|
|
725
|
+
): JointKind => ({
|
|
726
|
+
_tag: 'Fixed',
|
|
727
|
+
frameA: options.frameA ?? origin,
|
|
728
|
+
frameB: options.frameB ?? origin,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
/** A ball and socket at the anchors. */
|
|
732
|
+
export const sphericalJoint = (): JointKind => ({ _tag: 'Spherical' });
|
|
733
|
+
|
|
734
|
+
/** A hinge about `axis` (each body's own), limited to `limits` radians and driven by `motor` if given. */
|
|
735
|
+
export const revoluteJoint = (options: {
|
|
736
|
+
readonly axis: Vec3;
|
|
737
|
+
readonly limits?: JointLimits | null;
|
|
738
|
+
readonly motor?: JointMotor | null;
|
|
739
|
+
}): JointKind => ({
|
|
740
|
+
_tag: 'Revolute',
|
|
741
|
+
axis: options.axis,
|
|
742
|
+
limits: options.limits ?? null,
|
|
743
|
+
motor: options.motor ?? null,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
/** A slider along `axis`, limited to `limits` and driven by `motor` if given. */
|
|
747
|
+
export const prismaticJoint = (options: {
|
|
748
|
+
readonly axis: Vec3;
|
|
749
|
+
readonly limits?: JointLimits | null;
|
|
750
|
+
readonly motor?: JointMotor | null;
|
|
751
|
+
}): JointKind => ({
|
|
752
|
+
_tag: 'Prismatic',
|
|
753
|
+
axis: options.axis,
|
|
754
|
+
limits: options.limits ?? null,
|
|
755
|
+
motor: options.motor ?? null,
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
/** Keeps the anchors at most `length` apart. */
|
|
759
|
+
export const ropeJoint = (length: number): JointKind => ({ _tag: 'Rope', length });
|
|
760
|
+
|
|
761
|
+
/** Pulls the anchors towards `restLength` apart. */
|
|
762
|
+
export const springJoint = (options: {
|
|
763
|
+
readonly restLength: number;
|
|
764
|
+
readonly stiffness: number;
|
|
765
|
+
readonly damping: number;
|
|
766
|
+
}): JointKind => ({ _tag: 'Spring', ...options });
|
|
767
|
+
|
|
768
|
+
const noAnchors: ReadonlyArray<XrAnchor> = [];
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Gliding with the thumbsticks while presenting. Defaults as pmndrs/xr's
|
|
772
|
+
* `useXRControllerLocomotion`: 2 m/s, a snap turn of 45° (smooth turns at 2 rad/s).
|
|
773
|
+
*/
|
|
774
|
+
export const glide = (options: Partial<XrGlide> = {}): XrGlide => ({
|
|
775
|
+
speed: options.speed ?? 2,
|
|
776
|
+
turn: options.turn ?? 'snap',
|
|
777
|
+
turnSpeed: options.turnSpeed ?? 2,
|
|
778
|
+
snapDegrees: options.snapDegrees ?? 45,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Immersive presentation for a scene. Defaults: the player at the scene's origin facing
|
|
783
|
+
* −z, `local-floor` (as pmndrs/xr), the device's framebuffer scale, inputs drawn (rays
|
|
784
|
+
* and hand joints, where pmndrs/xr draws controller and hand models), no AR hit tests.
|
|
785
|
+
*/
|
|
786
|
+
export const xr = (options: Partial<Xr> = {}): Xr => ({
|
|
787
|
+
origin: options.origin ?? origin,
|
|
788
|
+
heading: options.heading ?? 0,
|
|
789
|
+
referenceSpace: options.referenceSpace ?? 'local-floor',
|
|
790
|
+
framebufferScale: options.framebufferScale ?? 1,
|
|
791
|
+
hitTest: options.hitTest ?? false,
|
|
792
|
+
showInputs: options.showInputs ?? true,
|
|
793
|
+
glide: options.glide ?? null,
|
|
794
|
+
anchors: options.anchors ?? noAnchors,
|
|
795
|
+
planes: options.planes ?? false,
|
|
796
|
+
meshes: options.meshes ?? false,
|
|
797
|
+
domOverlay: options.domOverlay ?? false,
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
/** One instance. Defaults: at the origin, unrotated, unit scale, white. */
|
|
801
|
+
export const instance = (options: {
|
|
802
|
+
readonly key: string;
|
|
803
|
+
readonly position?: Vec3;
|
|
804
|
+
readonly rotation?: Vec3;
|
|
805
|
+
readonly scale?: Vec3;
|
|
806
|
+
readonly color?: Rgb;
|
|
807
|
+
}): Instance => ({
|
|
808
|
+
key: options.key,
|
|
809
|
+
position: options.position ?? origin,
|
|
810
|
+
rotation: options.rotation ?? origin,
|
|
811
|
+
scale: options.scale ?? unitScale,
|
|
812
|
+
color: options.color ?? 0xffffff,
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
/** Many instances of one geometry and material, drawn together. Not interactive by default. */
|
|
816
|
+
export const instances = (
|
|
817
|
+
options: TransformOptions & {
|
|
818
|
+
readonly geometry: GeometryRef;
|
|
819
|
+
readonly material: MaterialRef;
|
|
820
|
+
readonly instances: ReadonlyArray<Instance>;
|
|
821
|
+
readonly interactive?: boolean;
|
|
822
|
+
readonly castShadow?: boolean;
|
|
823
|
+
readonly receiveShadow?: boolean;
|
|
824
|
+
readonly body?: Body | null;
|
|
825
|
+
},
|
|
826
|
+
): InstancesNode => ({
|
|
827
|
+
_tag: 'Instances',
|
|
828
|
+
key: options.key,
|
|
829
|
+
position: options.position ?? origin,
|
|
830
|
+
rotation: options.rotation ?? noRotation,
|
|
831
|
+
scale: options.scale ?? unitScale,
|
|
832
|
+
transition: options.transition ?? instantTransition,
|
|
833
|
+
behaviour: options.behaviour ?? null,
|
|
834
|
+
geometry: options.geometry,
|
|
835
|
+
material: options.material,
|
|
836
|
+
interactive: options.interactive ?? false,
|
|
837
|
+
castShadow: options.castShadow ?? false,
|
|
838
|
+
receiveShadow: options.receiveShadow ?? false,
|
|
839
|
+
instances: options.instances,
|
|
840
|
+
body: options.body ?? null,
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
/** Applies a registered behaviour by name. Parameters default to the behaviour's own. */
|
|
844
|
+
export const behave = (name: string, params: Readonly<Record<string, number>> = {}): Behave => ({
|
|
845
|
+
name,
|
|
846
|
+
params,
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
/** Plays an animation clip by name. Defaults: normal speed, looping. */
|
|
850
|
+
export const play = (
|
|
851
|
+
clip: string,
|
|
852
|
+
options: { readonly speed?: number; readonly loop?: boolean } = {},
|
|
853
|
+
): Animation => ({ clip, speed: options.speed ?? 1, loop: options.loop ?? true });
|
|
854
|
+
|
|
855
|
+
/** An object lent to the resource store under `id`. Not interactive by default. */
|
|
856
|
+
export const borrowed = (
|
|
857
|
+
options: TransformOptions & {
|
|
858
|
+
readonly id: string;
|
|
859
|
+
readonly interactive?: boolean;
|
|
860
|
+
readonly animation?: Animation | null;
|
|
861
|
+
readonly body?: Body | null;
|
|
862
|
+
},
|
|
863
|
+
): BorrowedNode => ({
|
|
864
|
+
_tag: 'Borrowed',
|
|
865
|
+
key: options.key,
|
|
866
|
+
id: options.id,
|
|
867
|
+
animation: options.animation ?? null,
|
|
868
|
+
position: options.position ?? origin,
|
|
869
|
+
rotation: options.rotation ?? noRotation,
|
|
870
|
+
scale: options.scale ?? unitScale,
|
|
871
|
+
transition: options.transition ?? instantTransition,
|
|
872
|
+
behaviour: options.behaviour ?? null,
|
|
873
|
+
interactive: options.interactive ?? false,
|
|
874
|
+
body: options.body ?? null,
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Text drawn from distance fields (Drei's `Text`). Defaults as Drei's: size 1, anchored at
|
|
879
|
+
* its centre and middle, troika's default font; white, no outline, left-aligned lines,
|
|
880
|
+
* never wrapped, the font's own line height. Not interactive by default.
|
|
881
|
+
*/
|
|
882
|
+
const noColorRanges: ReadonlyArray<LabelColorRange> = [];
|
|
883
|
+
|
|
884
|
+
export const label = (
|
|
885
|
+
options: TransformOptions & {
|
|
886
|
+
readonly text: string;
|
|
887
|
+
readonly font?: string | null;
|
|
888
|
+
readonly fontSize?: number;
|
|
889
|
+
readonly color?: Rgb;
|
|
890
|
+
readonly fillOpacity?: number;
|
|
891
|
+
readonly anchorX?: LabelAnchorX;
|
|
892
|
+
readonly anchorY?: LabelAnchorY;
|
|
893
|
+
readonly maxWidth?: number | null;
|
|
894
|
+
readonly lineHeight?: number | null;
|
|
895
|
+
readonly letterSpacing?: number;
|
|
896
|
+
readonly textAlign?: LabelAlign;
|
|
897
|
+
readonly outlineWidth?: number;
|
|
898
|
+
readonly outlineColor?: Rgb;
|
|
899
|
+
readonly outlineOpacity?: number;
|
|
900
|
+
readonly outlineBlur?: number;
|
|
901
|
+
readonly colorRanges?: ReadonlyArray<LabelColorRange>;
|
|
902
|
+
readonly curveRadius?: number;
|
|
903
|
+
readonly material?: MaterialRef | null;
|
|
904
|
+
readonly interactive?: boolean;
|
|
905
|
+
},
|
|
906
|
+
): LabelNode => ({
|
|
907
|
+
_tag: 'Label',
|
|
908
|
+
key: options.key,
|
|
909
|
+
position: options.position ?? origin,
|
|
910
|
+
rotation: options.rotation ?? noRotation,
|
|
911
|
+
scale: options.scale ?? unitScale,
|
|
912
|
+
transition: options.transition ?? instantTransition,
|
|
913
|
+
behaviour: options.behaviour ?? null,
|
|
914
|
+
interactive: options.interactive ?? false,
|
|
915
|
+
text: options.text,
|
|
916
|
+
font: options.font ?? null,
|
|
917
|
+
fontSize: options.fontSize ?? 1,
|
|
918
|
+
color: options.color ?? 0xffffff,
|
|
919
|
+
fillOpacity: options.fillOpacity ?? 1,
|
|
920
|
+
anchorX: options.anchorX ?? 'center',
|
|
921
|
+
anchorY: options.anchorY ?? 'middle',
|
|
922
|
+
maxWidth: options.maxWidth ?? null,
|
|
923
|
+
lineHeight: options.lineHeight ?? null,
|
|
924
|
+
letterSpacing: options.letterSpacing ?? 0,
|
|
925
|
+
textAlign: options.textAlign ?? 'left',
|
|
926
|
+
outlineWidth: options.outlineWidth ?? 0,
|
|
927
|
+
outlineColor: options.outlineColor ?? 0x000000,
|
|
928
|
+
outlineOpacity: options.outlineOpacity ?? 1,
|
|
929
|
+
outlineBlur: options.outlineBlur ?? 0,
|
|
930
|
+
colorRanges: options.colorRanges ?? noColorRanges,
|
|
931
|
+
curveRadius: options.curveRadius ?? 0,
|
|
932
|
+
material: options.material ?? null,
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
/** A run of a label's characters in `color`, from character index `start`. */
|
|
936
|
+
export const colorRange = (start: number, color: Rgb): LabelColorRange => ({ start, color });
|
|
937
|
+
|
|
938
|
+
/** Ambient light. Defaults: white, intensity 1. */
|
|
939
|
+
export const ambientLight = (options: {
|
|
940
|
+
readonly key: string;
|
|
941
|
+
readonly color?: Rgb;
|
|
942
|
+
readonly intensity?: number;
|
|
943
|
+
}): AmbientLightNode => ({
|
|
944
|
+
_tag: 'AmbientLight',
|
|
945
|
+
key: options.key,
|
|
946
|
+
color: options.color ?? 0xffffff,
|
|
947
|
+
intensity: options.intensity ?? 1,
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Directional light. Defaults: white, intensity 1, shining from `[0, 1, 0]`; no shadow,
|
|
952
|
+
* or a 512² shadow map covering ±5 world units (three.js's defaults) when casting.
|
|
953
|
+
*/
|
|
954
|
+
export const directionalLight = (options: {
|
|
955
|
+
readonly key: string;
|
|
956
|
+
readonly color?: Rgb;
|
|
957
|
+
readonly intensity?: number;
|
|
958
|
+
readonly position?: Vec3;
|
|
959
|
+
readonly castShadow?: boolean;
|
|
960
|
+
readonly shadowMapSize?: number;
|
|
961
|
+
readonly shadowBias?: number;
|
|
962
|
+
readonly shadowArea?: number;
|
|
963
|
+
}): DirectionalLightNode => ({
|
|
964
|
+
_tag: 'DirectionalLight',
|
|
965
|
+
key: options.key,
|
|
966
|
+
color: options.color ?? 0xffffff,
|
|
967
|
+
intensity: options.intensity ?? 1,
|
|
968
|
+
position: options.position ?? [0, 1, 0],
|
|
969
|
+
castShadow: options.castShadow ?? false,
|
|
970
|
+
shadowMapSize: options.shadowMapSize ?? 512,
|
|
971
|
+
shadowBias: options.shadowBias ?? 0,
|
|
972
|
+
shadowArea: options.shadowArea ?? 5,
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
/** Point light. Defaults (as three.js): white, intensity 1, at the origin, no cut-off, decay 2. */
|
|
976
|
+
export const pointLight = (options: {
|
|
977
|
+
readonly key: string;
|
|
978
|
+
readonly color?: Rgb;
|
|
979
|
+
readonly intensity?: number;
|
|
980
|
+
readonly position?: Vec3;
|
|
981
|
+
readonly distance?: number;
|
|
982
|
+
readonly decay?: number;
|
|
983
|
+
readonly castShadow?: boolean;
|
|
984
|
+
readonly shadowMapSize?: number;
|
|
985
|
+
readonly shadowBias?: number;
|
|
986
|
+
}): PointLightNode => ({
|
|
987
|
+
_tag: 'PointLight',
|
|
988
|
+
key: options.key,
|
|
989
|
+
color: options.color ?? 0xffffff,
|
|
990
|
+
intensity: options.intensity ?? 1,
|
|
991
|
+
position: options.position ?? origin,
|
|
992
|
+
distance: options.distance ?? 0,
|
|
993
|
+
decay: options.decay ?? 2,
|
|
994
|
+
castShadow: options.castShadow ?? false,
|
|
995
|
+
shadowMapSize: options.shadowMapSize ?? 512,
|
|
996
|
+
shadowBias: options.shadowBias ?? 0,
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Spot light. Defaults (as three.js): white, intensity 1, at `[0, 1, 0]` aimed at the
|
|
1001
|
+
* origin, no cut-off, a π/3 half-angle, a hard edge and decay 2.
|
|
1002
|
+
*/
|
|
1003
|
+
export const spotLight = (options: {
|
|
1004
|
+
readonly key: string;
|
|
1005
|
+
readonly color?: Rgb;
|
|
1006
|
+
readonly intensity?: number;
|
|
1007
|
+
readonly position?: Vec3;
|
|
1008
|
+
readonly target?: Vec3;
|
|
1009
|
+
readonly distance?: number;
|
|
1010
|
+
readonly angle?: number;
|
|
1011
|
+
readonly penumbra?: number;
|
|
1012
|
+
readonly decay?: number;
|
|
1013
|
+
readonly castShadow?: boolean;
|
|
1014
|
+
readonly shadowMapSize?: number;
|
|
1015
|
+
readonly shadowBias?: number;
|
|
1016
|
+
}): SpotLightNode => ({
|
|
1017
|
+
_tag: 'SpotLight',
|
|
1018
|
+
key: options.key,
|
|
1019
|
+
color: options.color ?? 0xffffff,
|
|
1020
|
+
intensity: options.intensity ?? 1,
|
|
1021
|
+
position: options.position ?? [0, 1, 0],
|
|
1022
|
+
target: options.target ?? origin,
|
|
1023
|
+
distance: options.distance ?? 0,
|
|
1024
|
+
angle: options.angle ?? Math.PI / 3,
|
|
1025
|
+
penumbra: options.penumbra ?? 0,
|
|
1026
|
+
decay: options.decay ?? 2,
|
|
1027
|
+
castShadow: options.castShadow ?? false,
|
|
1028
|
+
shadowMapSize: options.shadowMapSize ?? 512,
|
|
1029
|
+
shadowBias: options.shadowBias ?? 0,
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
/** Hemisphere light. Defaults (as three.js): white sky and ground, intensity 1, from `[0, 1, 0]`. */
|
|
1033
|
+
export const hemisphereLight = (options: {
|
|
1034
|
+
readonly key: string;
|
|
1035
|
+
readonly color?: Rgb;
|
|
1036
|
+
readonly groundColor?: Rgb;
|
|
1037
|
+
readonly intensity?: number;
|
|
1038
|
+
readonly position?: Vec3;
|
|
1039
|
+
}): HemisphereLightNode => ({
|
|
1040
|
+
_tag: 'HemisphereLight',
|
|
1041
|
+
key: options.key,
|
|
1042
|
+
color: options.color ?? 0xffffff,
|
|
1043
|
+
groundColor: options.groundColor ?? 0xffffff,
|
|
1044
|
+
intensity: options.intensity ?? 1,
|
|
1045
|
+
position: options.position ?? [0, 1, 0],
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Perspective camera. Defaults: fov 50, near 0.1, far 100, at `[0, 0, 5]` looking at the
|
|
1050
|
+
* origin, no damping, not turning.
|
|
1051
|
+
*/
|
|
1052
|
+
export const perspectiveCamera = (
|
|
1053
|
+
options: Partial<Omit<PerspectiveCamera, '_tag'>> = {},
|
|
1054
|
+
): PerspectiveCamera => ({
|
|
1055
|
+
_tag: 'Perspective',
|
|
1056
|
+
fov: options.fov ?? 50,
|
|
1057
|
+
near: options.near ?? 0.1,
|
|
1058
|
+
far: options.far ?? 100,
|
|
1059
|
+
position: options.position ?? [0, 0, 5],
|
|
1060
|
+
lookAt: options.lookAt ?? origin,
|
|
1061
|
+
damping: options.damping ?? 0,
|
|
1062
|
+
autoRotate: options.autoRotate ?? 0,
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Orthographic camera. Defaults (as React Three Fiber): zoom 1 (one CSS pixel per world
|
|
1067
|
+
* unit, so set it), near 0.1, far 1000, at `[0, 0, 5]` looking at the origin.
|
|
1068
|
+
*/
|
|
1069
|
+
export const orthographicCamera = (
|
|
1070
|
+
options: Partial<Omit<OrthographicCamera, '_tag'>> = {},
|
|
1071
|
+
): OrthographicCamera => ({
|
|
1072
|
+
_tag: 'Orthographic',
|
|
1073
|
+
zoom: options.zoom ?? 1,
|
|
1074
|
+
near: options.near ?? 0.1,
|
|
1075
|
+
far: options.far ?? 1000,
|
|
1076
|
+
position: options.position ?? [0, 0, 5],
|
|
1077
|
+
lookAt: options.lookAt ?? origin,
|
|
1078
|
+
damping: options.damping ?? 0,
|
|
1079
|
+
autoRotate: options.autoRotate ?? 0,
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
const noEffects: ReadonlyArray<PostEffect> = [];
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Bloom: bright areas glow. Defaults: strength 1 (as three.js), radius 0.4 and threshold
|
|
1086
|
+
* 0.85, so only the brightest areas bloom (three.js leaves these two unset).
|
|
1087
|
+
*/
|
|
1088
|
+
export const bloom = (
|
|
1089
|
+
options: {
|
|
1090
|
+
readonly strength?: number;
|
|
1091
|
+
readonly radius?: number;
|
|
1092
|
+
readonly threshold?: number;
|
|
1093
|
+
} = {},
|
|
1094
|
+
): PostEffect => ({
|
|
1095
|
+
_tag: 'Bloom',
|
|
1096
|
+
strength: options.strength ?? 1,
|
|
1097
|
+
radius: options.radius ?? 0.4,
|
|
1098
|
+
threshold: options.threshold ?? 0.85,
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
/** Vignette: darker edges. Defaults (as three.js's `VignetteShader`): offset 1, darkness 1. */
|
|
1102
|
+
export const vignette = (
|
|
1103
|
+
options: { readonly offset?: number; readonly darkness?: number } = {},
|
|
1104
|
+
): PostEffect => ({
|
|
1105
|
+
_tag: 'Vignette',
|
|
1106
|
+
offset: options.offset ?? 1,
|
|
1107
|
+
darkness: options.darkness ?? 1,
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
/** Film grain. Defaults (as three.js's `FilmPass`): intensity 0.5, in colour. */
|
|
1111
|
+
export const film = (
|
|
1112
|
+
options: { readonly intensity?: number; readonly grayscale?: boolean } = {},
|
|
1113
|
+
): PostEffect => ({
|
|
1114
|
+
_tag: 'Film',
|
|
1115
|
+
intensity: options.intensity ?? 0.5,
|
|
1116
|
+
grayscale: options.grayscale ?? false,
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* Render settings. Defaults: frames on demand, full resolution, shadows off, no tone
|
|
1121
|
+
* mapping, exposure 1, sRGB output. Two deliberate differences from React Three Fiber:
|
|
1122
|
+
*
|
|
1123
|
+
* - Frames default to `demand`, not `always`. Every visual change passes through the
|
|
1124
|
+
* kernel, which knows when a frame is needed, so nothing has to call `invalidate` and
|
|
1125
|
+
* idle scenes stop drawing.
|
|
1126
|
+
* - Tone mapping defaults to `none`, not ACES, so a validated palette renders as given.
|
|
1127
|
+
*/
|
|
1128
|
+
export const renderSettings = (options: Partial<RenderSettings> = {}): RenderSettings => ({
|
|
1129
|
+
frames: options.frames ?? 'demand',
|
|
1130
|
+
effects: options.effects ?? noEffects,
|
|
1131
|
+
resolution: options.resolution ?? 1,
|
|
1132
|
+
shadows: options.shadows ?? 'off',
|
|
1133
|
+
toneMapping: options.toneMapping ?? 'none',
|
|
1134
|
+
exposure: options.exposure ?? 1,
|
|
1135
|
+
colorSpace: options.colorSpace ?? 'srgb',
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
const defaultRenderSettings = renderSettings();
|
|
1139
|
+
|
|
1140
|
+
const fullRect: ViewRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
1141
|
+
const noViews: ReadonlyArray<View> = [];
|
|
1142
|
+
|
|
1143
|
+
/** A view of the scene through `camera` into `rect` of the canvas (default: all of it). */
|
|
1144
|
+
export const view = (options: {
|
|
1145
|
+
readonly key: string;
|
|
1146
|
+
readonly camera: Camera;
|
|
1147
|
+
readonly rect?: Partial<ViewRect>;
|
|
1148
|
+
}): View => ({
|
|
1149
|
+
key: options.key,
|
|
1150
|
+
camera: options.camera,
|
|
1151
|
+
rect: {
|
|
1152
|
+
x: options.rect?.x ?? fullRect.x,
|
|
1153
|
+
y: options.rect?.y ?? fullRect.y,
|
|
1154
|
+
width: options.rect?.width ?? fullRect.width,
|
|
1155
|
+
height: options.rect?.height ?? fullRect.height,
|
|
1156
|
+
},
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
const noRenderTextures: ReadonlyArray<RenderTexture> = [];
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* A scene rendered into a texture, lent under `id` for materials' `map`. Defaults:
|
|
1163
|
+
* 512 × 512, the default camera, transparent.
|
|
1164
|
+
*/
|
|
1165
|
+
export const renderTexture = (options: {
|
|
1166
|
+
readonly id: string;
|
|
1167
|
+
readonly width?: number;
|
|
1168
|
+
readonly height?: number;
|
|
1169
|
+
readonly camera?: Camera;
|
|
1170
|
+
readonly background?: Rgb | null;
|
|
1171
|
+
readonly children?: ReadonlyArray<SceneNode>;
|
|
1172
|
+
}): RenderTexture => ({
|
|
1173
|
+
id: options.id,
|
|
1174
|
+
width: options.width ?? 512,
|
|
1175
|
+
height: options.height ?? 512,
|
|
1176
|
+
camera: options.camera ?? perspectiveCamera(),
|
|
1177
|
+
background: options.background ?? null,
|
|
1178
|
+
children: options.children ?? [],
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
/** A scene description. */
|
|
1182
|
+
/**
|
|
1183
|
+
* Image-based lighting (Drei's `Environment`). Defaults as Drei: lighting only, no blur,
|
|
1184
|
+
* intensities 1. Without `map`, three.js's `RoomEnvironment`; with it, the equirectangular
|
|
1185
|
+
* texture lent under that id.
|
|
1186
|
+
*/
|
|
1187
|
+
export const environment = (
|
|
1188
|
+
options: {
|
|
1189
|
+
readonly map?: string;
|
|
1190
|
+
readonly background?: boolean | 'only';
|
|
1191
|
+
readonly backgroundBlurriness?: number;
|
|
1192
|
+
readonly backgroundIntensity?: number;
|
|
1193
|
+
readonly environmentIntensity?: number;
|
|
1194
|
+
} = {},
|
|
1195
|
+
): Environment => ({
|
|
1196
|
+
map: options.map ?? null,
|
|
1197
|
+
background: options.background ?? false,
|
|
1198
|
+
backgroundBlurriness: options.backgroundBlurriness ?? 0,
|
|
1199
|
+
backgroundIntensity: options.backgroundIntensity ?? 1,
|
|
1200
|
+
environmentIntensity: options.environmentIntensity ?? 1,
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Where Drei puts the sun from an inclination and azimuth, both 0 to 1
|
|
1205
|
+
* (`calcPosFromAngles` in `src/core/Sky.tsx`).
|
|
1206
|
+
*/
|
|
1207
|
+
export const sunPositionFromAngles = (inclination: number, azimuth: number): Vec3 => {
|
|
1208
|
+
const theta = Math.PI * (inclination - 0.5);
|
|
1209
|
+
const phi = 2 * Math.PI * (azimuth - 0.5);
|
|
1210
|
+
return [Math.cos(phi), Math.sin(theta), Math.sin(phi)];
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* A daylight sky. Defaults as Drei's `Sky`: the sun from inclination 0.6 and azimuth 0.1,
|
|
1215
|
+
* turbidity 10, rayleigh 0.5, mie coefficient 0.005 and directional G 0.8, distance 1000,
|
|
1216
|
+
* and no clouds. The sky is high dynamic range: use tone mapping (`aces`, as three.js's
|
|
1217
|
+
* example and React Three Fiber's default), or it washes out to white.
|
|
1218
|
+
*/
|
|
1219
|
+
export const sky = (options: Partial<Sky> = {}): Sky => ({
|
|
1220
|
+
sunPosition: options.sunPosition ?? sunPositionFromAngles(0.6, 0.1),
|
|
1221
|
+
turbidity: options.turbidity ?? 10,
|
|
1222
|
+
rayleigh: options.rayleigh ?? 0.5,
|
|
1223
|
+
mieCoefficient: options.mieCoefficient ?? 0.005,
|
|
1224
|
+
mieDirectionalG: options.mieDirectionalG ?? 0.8,
|
|
1225
|
+
clouds: options.clouds ?? 0,
|
|
1226
|
+
distance: options.distance ?? 1000,
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Soft ground shadows (Drei's `ContactShadows`). Defaults as Drei: a 10 × 10 plane at the
|
|
1231
|
+
* origin (Drei's `scale` 10), shadows from 0 to 10 units above, blur 1 with the smooth
|
|
1232
|
+
* pass, opacity 1, black, 512 texels.
|
|
1233
|
+
*/
|
|
1234
|
+
export const contactShadows = (
|
|
1235
|
+
options: {
|
|
1236
|
+
readonly position?: Vec3;
|
|
1237
|
+
readonly size?: number | readonly [number, number];
|
|
1238
|
+
readonly near?: number;
|
|
1239
|
+
readonly far?: number;
|
|
1240
|
+
readonly blur?: number;
|
|
1241
|
+
readonly smooth?: boolean;
|
|
1242
|
+
readonly opacity?: number;
|
|
1243
|
+
readonly color?: Rgb;
|
|
1244
|
+
readonly resolution?: number;
|
|
1245
|
+
} = {},
|
|
1246
|
+
): ContactShadows => {
|
|
1247
|
+
const size = options.size ?? 10;
|
|
1248
|
+
return {
|
|
1249
|
+
position: options.position ?? origin,
|
|
1250
|
+
size: typeof size === 'number' ? [size, size] : size,
|
|
1251
|
+
near: options.near ?? 0,
|
|
1252
|
+
far: options.far ?? 10,
|
|
1253
|
+
blur: options.blur ?? 1,
|
|
1254
|
+
smooth: options.smooth ?? true,
|
|
1255
|
+
opacity: options.opacity ?? 1,
|
|
1256
|
+
color: options.color ?? 0x000000,
|
|
1257
|
+
resolution: options.resolution ?? 512,
|
|
1258
|
+
};
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
const noOverlays: ReadonlyArray<Overlay> = [];
|
|
1262
|
+
const noMeasures: ReadonlyArray<string> = [];
|
|
1263
|
+
|
|
1264
|
+
/** Drei's `zIndexRange` default: near points on top, far ones at 0. */
|
|
1265
|
+
const defaultZIndexRange: readonly [number, number] = [16777271, 0];
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* A point to show HTML at (Drei's `Html`). Defaults: world space at the origin, not
|
|
1269
|
+
* centred, constant size, never occluded, Drei's z-index range, the main camera.
|
|
1270
|
+
*/
|
|
1271
|
+
export const overlay = (options: {
|
|
1272
|
+
readonly key: string;
|
|
1273
|
+
readonly node?: string;
|
|
1274
|
+
readonly position?: Vec3;
|
|
1275
|
+
readonly center?: boolean;
|
|
1276
|
+
readonly distanceFactor?: number;
|
|
1277
|
+
readonly occlude?: boolean;
|
|
1278
|
+
readonly zIndexRange?: readonly [number, number];
|
|
1279
|
+
readonly view?: string;
|
|
1280
|
+
}): Overlay => ({
|
|
1281
|
+
key: options.key,
|
|
1282
|
+
node: options.node ?? null,
|
|
1283
|
+
position: options.position ?? origin,
|
|
1284
|
+
center: options.center ?? false,
|
|
1285
|
+
distanceFactor: options.distanceFactor ?? null,
|
|
1286
|
+
occlude: options.occlude ?? false,
|
|
1287
|
+
zIndexRange: options.zIndexRange ?? defaultZIndexRange,
|
|
1288
|
+
view: options.view ?? null,
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
export const scene = (options: {
|
|
1292
|
+
readonly camera?: Camera;
|
|
1293
|
+
readonly gestures?: Gestures;
|
|
1294
|
+
readonly views?: ReadonlyArray<View>;
|
|
1295
|
+
readonly renderTextures?: ReadonlyArray<RenderTexture>;
|
|
1296
|
+
readonly overlays?: ReadonlyArray<Overlay>;
|
|
1297
|
+
/** Optional staging: leave out or pass `null` for none, so a Model can switch it off. */
|
|
1298
|
+
readonly environment?: Environment | null;
|
|
1299
|
+
readonly sky?: Sky | null;
|
|
1300
|
+
readonly contactShadows?: ContactShadows | null;
|
|
1301
|
+
readonly physics?: Physics | null;
|
|
1302
|
+
readonly xr?: Xr | null;
|
|
1303
|
+
readonly measure?: ReadonlyArray<string>;
|
|
1304
|
+
readonly render?: RenderSettings;
|
|
1305
|
+
readonly resources?: Partial<ResourceDefinitions>;
|
|
1306
|
+
readonly children?: ReadonlyArray<SceneNode>;
|
|
1307
|
+
}): SceneDescription => ({
|
|
1308
|
+
camera: options.camera ?? perspectiveCamera(),
|
|
1309
|
+
gestures: options.gestures ?? 'page',
|
|
1310
|
+
views: options.views ?? noViews,
|
|
1311
|
+
renderTextures: options.renderTextures ?? noRenderTextures,
|
|
1312
|
+
overlays: options.overlays ?? noOverlays,
|
|
1313
|
+
environment: options.environment ?? null,
|
|
1314
|
+
sky: options.sky ?? null,
|
|
1315
|
+
contactShadows: options.contactShadows ?? null,
|
|
1316
|
+
physics: options.physics ?? null,
|
|
1317
|
+
xr: options.xr ?? null,
|
|
1318
|
+
measure: options.measure ?? noMeasures,
|
|
1319
|
+
render: options.render ?? defaultRenderSettings,
|
|
1320
|
+
resources: {
|
|
1321
|
+
geometries: options.resources?.geometries ?? noResources.geometries,
|
|
1322
|
+
materials: options.resources?.materials ?? noResources.materials,
|
|
1323
|
+
},
|
|
1324
|
+
children: options.children ?? [],
|
|
1325
|
+
});
|