three-stdlib 2.8.4 → 2.8.5

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/index.d.ts CHANGED
@@ -233,12 +233,16 @@ export * from './postprocessing/ShaderPass';
233
233
  export * from './postprocessing/SSAARenderPass';
234
234
  export * from './postprocessing/RenderPass';
235
235
  export * from './postprocessing/BloomPass';
236
- export * from './webxr/XRHandModelFactory';
237
- export * from './webxr/XREstimatedLight';
238
- export * from './webxr/XRHandOculusMeshModel';
236
+ export * from './webxr/ARButton';
237
+ export * from './webxr/OculusHandModel';
238
+ export * from './webxr/OculusHandPointerModel';
239
+ export * from './webxr/Text2D';
239
240
  export * from './webxr/VRButton';
241
+ export * from './webxr/XRControllerModelFactory';
242
+ export * from './webxr/XREstimatedLight';
243
+ export * from './webxr/XRHandMeshModel';
244
+ export * from './webxr/XRHandModelFactory';
240
245
  export * from './webxr/XRHandPrimitiveModel';
241
- export * from './webxr/ARButton';
242
246
  export * from './geometries/ParametricGeometries';
243
247
  export * from './geometries/ConvexGeometry';
244
248
  export * from './geometries/LightningStrike';
@@ -384,3 +388,4 @@ export * from './curves/NURBSSurface';
384
388
  export * from './curves/CurveExtras';
385
389
  export * from './deprecated/Geometry';
386
390
  export * from './libs/MeshoptDecoder';
391
+ export * from './libs/MotionControllers';
package/index.js CHANGED
@@ -178,12 +178,16 @@ export { ShaderPass } from './postprocessing/ShaderPass.js';
178
178
  export { SSAARenderPass } from './postprocessing/SSAARenderPass.js';
179
179
  export { RenderPass } from './postprocessing/RenderPass.js';
180
180
  export { BloomPass } from './postprocessing/BloomPass.js';
181
- export { XRHandModelFactory } from './webxr/XRHandModelFactory.js';
182
- export { XREstimatedLight } from './webxr/XREstimatedLight.js';
183
- export { XRHandOculusMeshModel } from './webxr/XRHandOculusMeshModel.js';
181
+ export { ARButton } from './webxr/ARButton.js';
182
+ export { OculusHandModel } from './webxr/OculusHandModel.js';
183
+ export { OculusHandPointerModel } from './webxr/OculusHandPointerModel.js';
184
+ export { createText } from './webxr/Text2D.js';
184
185
  export { VRButton } from './webxr/VRButton.js';
186
+ export { XRControllerModelFactory } from './webxr/XRControllerModelFactory.js';
187
+ export { XREstimatedLight } from './webxr/XREstimatedLight.js';
188
+ export { XRHandMeshModel } from './webxr/XRHandMeshModel.js';
189
+ export { XRHandModelFactory } from './webxr/XRHandModelFactory.js';
185
190
  export { XRHandPrimitiveModel } from './webxr/XRHandPrimitiveModel.js';
186
- export { ARButton } from './webxr/ARButton.js';
187
191
  export { ParametricGeometries } from './geometries/ParametricGeometries.js';
188
192
  export { ConvexGeometry } from './geometries/ConvexGeometry.js';
189
193
  export { LightningStrike } from './geometries/LightningStrike.js';
@@ -327,3 +331,4 @@ export { NURBSSurface } from './curves/NURBSSurface.js';
327
331
  export { CinquefoilKnot, DecoratedTorusKnot4a, DecoratedTorusKnot4b, DecoratedTorusKnot5a, DecoratedTorusKnot5c, FigureEightPolynomialKnot, GrannyKnot, HeartCurve, HelixCurve, KnotCurve, TorusKnot, TrefoilKnot, TrefoilPolynomialKnot, VivianiCurve, scaleTo } from './curves/CurveExtras.js';
328
332
  export { Face3, Geometry } from './deprecated/Geometry.js';
329
333
  export { MeshoptDecoder } from './libs/MeshoptDecoder.js';
334
+ export { MotionController, MotionControllerConstants, fetchProfile, fetchProfilesList } from './libs/MotionControllers.js';
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e={Handedness:Object.freeze({NONE:"none",LEFT:"left",RIGHT:"right"}),ComponentState:Object.freeze({DEFAULT:"default",TOUCHED:"touched",PRESSED:"pressed"}),ComponentProperty:Object.freeze({BUTTON:"button",X_AXIS:"xAxis",Y_AXIS:"yAxis",STATE:"state"}),ComponentType:Object.freeze({TRIGGER:"trigger",SQUEEZE:"squeeze",TOUCHPAD:"touchpad",THUMBSTICK:"thumbstick",BUTTON:"button"}),ButtonTouchThreshold:.05,AxisTouchThreshold:.1,VisualResponseProperty:Object.freeze({TRANSFORM:"transform",VISIBILITY:"visibility"})};async function t(e){const t=await fetch(e);if(t.ok)return t.json();throw new Error(t.statusText)}async function s(e){if(!e)throw new Error("No basePath supplied");return await t(`${e}/profilesList.json`)}const o={xAxis:0,yAxis:0,button:0,state:e.ComponentState.DEFAULT};class a{constructor(t){this.componentProperty=t.componentProperty,this.states=t.states,this.valueNodeName=t.valueNodeName,this.valueNodeProperty=t.valueNodeProperty,this.valueNodeProperty===e.VisualResponseProperty.TRANSFORM&&(this.minNodeName=t.minNodeName,this.maxNodeName=t.maxNodeName),this.value=0,this.updateFromComponent(o)}updateFromComponent({xAxis:t,yAxis:s,button:o,state:a}){const{normalizedXAxis:i,normalizedYAxis:n}=function(e=0,t=0){let s=e,o=t;if(Math.sqrt(e*e+t*t)>1){const a=Math.atan2(t,e);s=Math.cos(a),o=Math.sin(a)}return{normalizedXAxis:.5*s+.5,normalizedYAxis:.5*o+.5}}(t,s);switch(this.componentProperty){case e.ComponentProperty.X_AXIS:this.value=this.states.includes(a)?i:.5;break;case e.ComponentProperty.Y_AXIS:this.value=this.states.includes(a)?n:.5;break;case e.ComponentProperty.BUTTON:this.value=this.states.includes(a)?o:0;break;case e.ComponentProperty.STATE:this.valueNodeProperty===e.VisualResponseProperty.VISIBILITY?this.value=this.states.includes(a):this.value=this.states.includes(a)?1:0;break;default:throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`)}}}class i{constructor(t,s){if(!(t&&s&&s.visualResponses&&s.gamepadIndices&&0!==Object.keys(s.gamepadIndices).length))throw new Error("Invalid arguments supplied");this.id=t,this.type=s.type,this.rootNodeName=s.rootNodeName,this.touchPointNodeName=s.touchPointNodeName,this.visualResponses={},Object.keys(s.visualResponses).forEach((e=>{const t=new a(s.visualResponses[e]);this.visualResponses[e]=t})),this.gamepadIndices=Object.assign({},s.gamepadIndices),this.values={state:e.ComponentState.DEFAULT,button:void 0!==this.gamepadIndices.button?0:void 0,xAxis:void 0!==this.gamepadIndices.xAxis?0:void 0,yAxis:void 0!==this.gamepadIndices.yAxis?0:void 0}}get data(){return{id:this.id,...this.values}}updateFromGamepad(t){if(this.values.state=e.ComponentState.DEFAULT,void 0!==this.gamepadIndices.button&&t.buttons.length>this.gamepadIndices.button){const s=t.buttons[this.gamepadIndices.button];this.values.button=s.value,this.values.button=this.values.button<0?0:this.values.button,this.values.button=this.values.button>1?1:this.values.button,s.pressed||1===this.values.button?this.values.state=e.ComponentState.PRESSED:(s.touched||this.values.button>e.ButtonTouchThreshold)&&(this.values.state=e.ComponentState.TOUCHED)}void 0!==this.gamepadIndices.xAxis&&t.axes.length>this.gamepadIndices.xAxis&&(this.values.xAxis=t.axes[this.gamepadIndices.xAxis],this.values.xAxis=this.values.xAxis<-1?-1:this.values.xAxis,this.values.xAxis=this.values.xAxis>1?1:this.values.xAxis,this.values.state===e.ComponentState.DEFAULT&&Math.abs(this.values.xAxis)>e.AxisTouchThreshold&&(this.values.state=e.ComponentState.TOUCHED)),void 0!==this.gamepadIndices.yAxis&&t.axes.length>this.gamepadIndices.yAxis&&(this.values.yAxis=t.axes[this.gamepadIndices.yAxis],this.values.yAxis=this.values.yAxis<-1?-1:this.values.yAxis,this.values.yAxis=this.values.yAxis>1?1:this.values.yAxis,this.values.state===e.ComponentState.DEFAULT&&Math.abs(this.values.yAxis)>e.AxisTouchThreshold&&(this.values.state=e.ComponentState.TOUCHED)),Object.values(this.visualResponses).forEach((e=>{e.updateFromComponent(this.values)}))}}exports.MotionController=class{constructor(e,t,s){if(!e)throw new Error("No xrInputSource supplied");if(!t)throw new Error("No profile supplied");this.xrInputSource=e,this.assetUrl=s,this.id=t.profileId,this.layoutDescription=t.layouts[e.handedness],this.components={},Object.keys(this.layoutDescription.components).forEach((e=>{const t=this.layoutDescription.components[e];this.components[e]=new i(e,t)})),this.updateFromGamepad()}get gripSpace(){return this.xrInputSource.gripSpace}get targetRaySpace(){return this.xrInputSource.targetRaySpace}get data(){const e=[];return Object.values(this.components).forEach((t=>{e.push(t.data)})),e}updateFromGamepad(){Object.values(this.components).forEach((e=>{e.updateFromGamepad(this.xrInputSource.gamepad)}))}},exports.MotionControllerConstants=e,exports.fetchProfile=async function(e,o,a=null,i=!0){if(!e)throw new Error("No xrInputSource supplied");if(!o)throw new Error("No basePath supplied");const n=await s(o);let r;if(e.profiles.some((e=>{const t=n[e];return t&&(r={profileId:e,profilePath:`${o}/${t.path}`,deprecated:!!t.deprecated}),!!r})),!r){if(!a)throw new Error("No matching profile name found");const e=n[a];if(!e)throw new Error(`No matching profile name found and default profile "${a}" missing.`);r={profileId:a,profilePath:`${o}/${e.path}`,deprecated:!!e.deprecated}}const h=await t(r.profilePath);let u;if(i){let t;if(t="any"===e.handedness?h.layouts[Object.keys(h.layouts)[0]]:h.layouts[e.handedness],!t)throw new Error(`No matching handedness, ${e.handedness}, in profile ${r.profileId}`);t.assetPath&&(u=r.profilePath.replace("profile.json",t.assetPath))}return{profile:h,assetPath:u}},exports.fetchProfilesList=s;
@@ -0,0 +1,403 @@
1
+ /**
2
+ * @webxr-input-profiles/motion-controllers 1.0.0 https://github.com/immersive-web/webxr-input-profiles
3
+ */
4
+ const MotionControllerConstants = {
5
+ Handedness: Object.freeze({
6
+ NONE: 'none',
7
+ LEFT: 'left',
8
+ RIGHT: 'right'
9
+ }),
10
+ ComponentState: Object.freeze({
11
+ DEFAULT: 'default',
12
+ TOUCHED: 'touched',
13
+ PRESSED: 'pressed'
14
+ }),
15
+ ComponentProperty: Object.freeze({
16
+ BUTTON: 'button',
17
+ X_AXIS: 'xAxis',
18
+ Y_AXIS: 'yAxis',
19
+ STATE: 'state'
20
+ }),
21
+ ComponentType: Object.freeze({
22
+ TRIGGER: 'trigger',
23
+ SQUEEZE: 'squeeze',
24
+ TOUCHPAD: 'touchpad',
25
+ THUMBSTICK: 'thumbstick',
26
+ BUTTON: 'button'
27
+ }),
28
+ ButtonTouchThreshold: 0.05,
29
+ AxisTouchThreshold: 0.1,
30
+ VisualResponseProperty: Object.freeze({
31
+ TRANSFORM: 'transform',
32
+ VISIBILITY: 'visibility'
33
+ })
34
+ };
35
+ /**
36
+ * @description Static helper function to fetch a JSON file and turn it into a JS object
37
+ * @param {string} path - Path to JSON file to be fetched
38
+ */
39
+
40
+ async function fetchJsonFile(path) {
41
+ const response = await fetch(path);
42
+
43
+ if (!response.ok) {
44
+ throw new Error(response.statusText);
45
+ } else {
46
+ return response.json();
47
+ }
48
+ }
49
+
50
+ async function fetchProfilesList(basePath) {
51
+ if (!basePath) {
52
+ throw new Error('No basePath supplied');
53
+ }
54
+
55
+ const profileListFileName = 'profilesList.json';
56
+ const profilesList = await fetchJsonFile(`${basePath}/${profileListFileName}`);
57
+ return profilesList;
58
+ }
59
+
60
+ async function fetchProfile(xrInputSource, basePath, defaultProfile = null, getAssetPath = true) {
61
+ if (!xrInputSource) {
62
+ throw new Error('No xrInputSource supplied');
63
+ }
64
+
65
+ if (!basePath) {
66
+ throw new Error('No basePath supplied');
67
+ } // Get the list of profiles
68
+
69
+
70
+ const supportedProfilesList = await fetchProfilesList(basePath); // Find the relative path to the first requested profile that is recognized
71
+
72
+ let match;
73
+ xrInputSource.profiles.some(profileId => {
74
+ const supportedProfile = supportedProfilesList[profileId];
75
+
76
+ if (supportedProfile) {
77
+ match = {
78
+ profileId,
79
+ profilePath: `${basePath}/${supportedProfile.path}`,
80
+ deprecated: !!supportedProfile.deprecated
81
+ };
82
+ }
83
+
84
+ return !!match;
85
+ });
86
+
87
+ if (!match) {
88
+ if (!defaultProfile) {
89
+ throw new Error('No matching profile name found');
90
+ }
91
+
92
+ const supportedProfile = supportedProfilesList[defaultProfile];
93
+
94
+ if (!supportedProfile) {
95
+ throw new Error(`No matching profile name found and default profile "${defaultProfile}" missing.`);
96
+ }
97
+
98
+ match = {
99
+ profileId: defaultProfile,
100
+ profilePath: `${basePath}/${supportedProfile.path}`,
101
+ deprecated: !!supportedProfile.deprecated
102
+ };
103
+ }
104
+
105
+ const profile = await fetchJsonFile(match.profilePath);
106
+ let assetPath;
107
+
108
+ if (getAssetPath) {
109
+ let layout;
110
+
111
+ if (xrInputSource.handedness === 'any') {
112
+ layout = profile.layouts[Object.keys(profile.layouts)[0]];
113
+ } else {
114
+ layout = profile.layouts[xrInputSource.handedness];
115
+ }
116
+
117
+ if (!layout) {
118
+ throw new Error(`No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}`);
119
+ }
120
+
121
+ if (layout.assetPath) {
122
+ assetPath = match.profilePath.replace('profile.json', layout.assetPath);
123
+ }
124
+ }
125
+
126
+ return {
127
+ profile,
128
+ assetPath
129
+ };
130
+ }
131
+ /** @constant {Object} */
132
+
133
+
134
+ const defaultComponentValues = {
135
+ xAxis: 0,
136
+ yAxis: 0,
137
+ button: 0,
138
+ state: MotionControllerConstants.ComponentState.DEFAULT
139
+ };
140
+ /**
141
+ * @description Converts an X, Y coordinate from the range -1 to 1 (as reported by the Gamepad
142
+ * API) to the range 0 to 1 (for interpolation). Also caps the X, Y values to be bounded within
143
+ * a circle. This ensures that thumbsticks are not animated outside the bounds of their physical
144
+ * range of motion and touchpads do not report touch locations off their physical bounds.
145
+ * @param {number} x The original x coordinate in the range -1 to 1
146
+ * @param {number} y The original y coordinate in the range -1 to 1
147
+ */
148
+
149
+ function normalizeAxes(x = 0, y = 0) {
150
+ let xAxis = x;
151
+ let yAxis = y; // Determine if the point is outside the bounds of the circle
152
+ // and, if so, place it on the edge of the circle
153
+
154
+ const hypotenuse = Math.sqrt(x * x + y * y);
155
+
156
+ if (hypotenuse > 1) {
157
+ const theta = Math.atan2(y, x);
158
+ xAxis = Math.cos(theta);
159
+ yAxis = Math.sin(theta);
160
+ } // Scale and move the circle so values are in the interpolation range. The circle's origin moves
161
+ // from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5.
162
+
163
+
164
+ const result = {
165
+ normalizedXAxis: xAxis * 0.5 + 0.5,
166
+ normalizedYAxis: yAxis * 0.5 + 0.5
167
+ };
168
+ return result;
169
+ }
170
+ /**
171
+ * Contains the description of how the 3D model should visually respond to a specific user input.
172
+ * This is accomplished by initializing the object with the name of a node in the 3D model and
173
+ * property that need to be modified in response to user input, the name of the nodes representing
174
+ * the allowable range of motion, and the name of the input which triggers the change. In response
175
+ * to the named input changing, this object computes the appropriate weighting to use for
176
+ * interpolating between the range of motion nodes.
177
+ */
178
+
179
+
180
+ class VisualResponse {
181
+ constructor(visualResponseDescription) {
182
+ this.componentProperty = visualResponseDescription.componentProperty;
183
+ this.states = visualResponseDescription.states;
184
+ this.valueNodeName = visualResponseDescription.valueNodeName;
185
+ this.valueNodeProperty = visualResponseDescription.valueNodeProperty;
186
+
187
+ if (this.valueNodeProperty === MotionControllerConstants.VisualResponseProperty.TRANSFORM) {
188
+ this.minNodeName = visualResponseDescription.minNodeName;
189
+ this.maxNodeName = visualResponseDescription.maxNodeName;
190
+ } // Initializes the response's current value based on default data
191
+
192
+
193
+ this.value = 0;
194
+ this.updateFromComponent(defaultComponentValues);
195
+ }
196
+ /**
197
+ * Computes the visual response's interpolation weight based on component state
198
+ * @param {Object} componentValues - The component from which to update
199
+ * @param {number} xAxis - The reported X axis value of the component
200
+ * @param {number} yAxis - The reported Y axis value of the component
201
+ * @param {number} button - The reported value of the component's button
202
+ * @param {string} state - The component's active state
203
+ */
204
+
205
+
206
+ updateFromComponent({
207
+ xAxis,
208
+ yAxis,
209
+ button,
210
+ state
211
+ }) {
212
+ const {
213
+ normalizedXAxis,
214
+ normalizedYAxis
215
+ } = normalizeAxes(xAxis, yAxis);
216
+
217
+ switch (this.componentProperty) {
218
+ case MotionControllerConstants.ComponentProperty.X_AXIS:
219
+ this.value = this.states.includes(state) ? normalizedXAxis : 0.5;
220
+ break;
221
+
222
+ case MotionControllerConstants.ComponentProperty.Y_AXIS:
223
+ this.value = this.states.includes(state) ? normalizedYAxis : 0.5;
224
+ break;
225
+
226
+ case MotionControllerConstants.ComponentProperty.BUTTON:
227
+ this.value = this.states.includes(state) ? button : 0;
228
+ break;
229
+
230
+ case MotionControllerConstants.ComponentProperty.STATE:
231
+ if (this.valueNodeProperty === MotionControllerConstants.VisualResponseProperty.VISIBILITY) {
232
+ this.value = this.states.includes(state);
233
+ } else {
234
+ this.value = this.states.includes(state) ? 1.0 : 0.0;
235
+ }
236
+
237
+ break;
238
+
239
+ default:
240
+ throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`);
241
+ }
242
+ }
243
+
244
+ }
245
+
246
+ class Component {
247
+ /**
248
+ * @param {Object} componentId - Id of the component
249
+ * @param {Object} componentDescription - Description of the component to be created
250
+ */
251
+ constructor(componentId, componentDescription) {
252
+ if (!componentId || !componentDescription || !componentDescription.visualResponses || !componentDescription.gamepadIndices || Object.keys(componentDescription.gamepadIndices).length === 0) {
253
+ throw new Error('Invalid arguments supplied');
254
+ }
255
+
256
+ this.id = componentId;
257
+ this.type = componentDescription.type;
258
+ this.rootNodeName = componentDescription.rootNodeName;
259
+ this.touchPointNodeName = componentDescription.touchPointNodeName; // Build all the visual responses for this component
260
+
261
+ this.visualResponses = {};
262
+ Object.keys(componentDescription.visualResponses).forEach(responseName => {
263
+ const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]);
264
+ this.visualResponses[responseName] = visualResponse;
265
+ }); // Set default values
266
+
267
+ this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices);
268
+ this.values = {
269
+ state: MotionControllerConstants.ComponentState.DEFAULT,
270
+ button: this.gamepadIndices.button !== undefined ? 0 : undefined,
271
+ xAxis: this.gamepadIndices.xAxis !== undefined ? 0 : undefined,
272
+ yAxis: this.gamepadIndices.yAxis !== undefined ? 0 : undefined
273
+ };
274
+ }
275
+
276
+ get data() {
277
+ const data = {
278
+ id: this.id,
279
+ ...this.values
280
+ };
281
+ return data;
282
+ }
283
+ /**
284
+ * @description Poll for updated data based on current gamepad state
285
+ * @param {Object} gamepad - The gamepad object from which the component data should be polled
286
+ */
287
+
288
+
289
+ updateFromGamepad(gamepad) {
290
+ // Set the state to default before processing other data sources
291
+ this.values.state = MotionControllerConstants.ComponentState.DEFAULT; // Get and normalize button
292
+
293
+ if (this.gamepadIndices.button !== undefined && gamepad.buttons.length > this.gamepadIndices.button) {
294
+ const gamepadButton = gamepad.buttons[this.gamepadIndices.button];
295
+ this.values.button = gamepadButton.value;
296
+ this.values.button = this.values.button < 0 ? 0 : this.values.button;
297
+ this.values.button = this.values.button > 1 ? 1 : this.values.button; // Set the state based on the button
298
+
299
+ if (gamepadButton.pressed || this.values.button === 1) {
300
+ this.values.state = MotionControllerConstants.ComponentState.PRESSED;
301
+ } else if (gamepadButton.touched || this.values.button > MotionControllerConstants.ButtonTouchThreshold) {
302
+ this.values.state = MotionControllerConstants.ComponentState.TOUCHED;
303
+ }
304
+ } // Get and normalize x axis value
305
+
306
+
307
+ if (this.gamepadIndices.xAxis !== undefined && gamepad.axes.length > this.gamepadIndices.xAxis) {
308
+ this.values.xAxis = gamepad.axes[this.gamepadIndices.xAxis];
309
+ this.values.xAxis = this.values.xAxis < -1 ? -1 : this.values.xAxis;
310
+ this.values.xAxis = this.values.xAxis > 1 ? 1 : this.values.xAxis; // If the state is still default, check if the xAxis makes it touched
311
+
312
+ if (this.values.state === MotionControllerConstants.ComponentState.DEFAULT && Math.abs(this.values.xAxis) > MotionControllerConstants.AxisTouchThreshold) {
313
+ this.values.state = MotionControllerConstants.ComponentState.TOUCHED;
314
+ }
315
+ } // Get and normalize Y axis value
316
+
317
+
318
+ if (this.gamepadIndices.yAxis !== undefined && gamepad.axes.length > this.gamepadIndices.yAxis) {
319
+ this.values.yAxis = gamepad.axes[this.gamepadIndices.yAxis];
320
+ this.values.yAxis = this.values.yAxis < -1 ? -1 : this.values.yAxis;
321
+ this.values.yAxis = this.values.yAxis > 1 ? 1 : this.values.yAxis; // If the state is still default, check if the yAxis makes it touched
322
+
323
+ if (this.values.state === MotionControllerConstants.ComponentState.DEFAULT && Math.abs(this.values.yAxis) > MotionControllerConstants.AxisTouchThreshold) {
324
+ this.values.state = MotionControllerConstants.ComponentState.TOUCHED;
325
+ }
326
+ } // Update the visual response weights based on the current component data
327
+
328
+
329
+ Object.values(this.visualResponses).forEach(visualResponse => {
330
+ visualResponse.updateFromComponent(this.values);
331
+ });
332
+ }
333
+
334
+ }
335
+ /**
336
+ * @description Builds a motion controller with components and visual responses based on the
337
+ * supplied profile description. Data is polled from the xrInputSource's gamepad.
338
+ * @author Nell Waliczek / https://github.com/NellWaliczek
339
+ */
340
+
341
+
342
+ class MotionController {
343
+ /**
344
+ * @param {Object} xrInputSource - The XRInputSource to build the MotionController around
345
+ * @param {Object} profile - The best matched profile description for the supplied xrInputSource
346
+ * @param {Object} assetUrl
347
+ */
348
+ constructor(xrInputSource, profile, assetUrl) {
349
+ if (!xrInputSource) {
350
+ throw new Error('No xrInputSource supplied');
351
+ }
352
+
353
+ if (!profile) {
354
+ throw new Error('No profile supplied');
355
+ }
356
+
357
+ this.xrInputSource = xrInputSource;
358
+ this.assetUrl = assetUrl;
359
+ this.id = profile.profileId; // Build child components as described in the profile description
360
+
361
+ this.layoutDescription = profile.layouts[xrInputSource.handedness];
362
+ this.components = {};
363
+ Object.keys(this.layoutDescription.components).forEach(componentId => {
364
+ const componentDescription = this.layoutDescription.components[componentId];
365
+ this.components[componentId] = new Component(componentId, componentDescription);
366
+ }); // Initialize components based on current gamepad state
367
+
368
+ this.updateFromGamepad();
369
+ }
370
+
371
+ get gripSpace() {
372
+ return this.xrInputSource.gripSpace;
373
+ }
374
+
375
+ get targetRaySpace() {
376
+ return this.xrInputSource.targetRaySpace;
377
+ }
378
+ /**
379
+ * @description Returns a subset of component data for simplified debugging
380
+ */
381
+
382
+
383
+ get data() {
384
+ const data = [];
385
+ Object.values(this.components).forEach(component => {
386
+ data.push(component.data);
387
+ });
388
+ return data;
389
+ }
390
+ /**
391
+ * @description Poll for updated data based on current gamepad state
392
+ */
393
+
394
+
395
+ updateFromGamepad() {
396
+ Object.values(this.components).forEach(component => {
397
+ component.updateFromGamepad(this.xrInputSource.gamepad);
398
+ });
399
+ }
400
+
401
+ }
402
+
403
+ export { MotionController, MotionControllerConstants, fetchProfile, fetchProfilesList };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-stdlib",
3
- "version": "2.8.4",
3
+ "version": "2.8.5",
4
4
  "private": false,
5
5
  "description": "stand-alone library of threejs examples",
6
6
  "main": "index.cjs.js",
@@ -22,7 +22,6 @@
22
22
  "dependencies": {
23
23
  "@babel/runtime": "^7.16.7",
24
24
  "@webgpu/glslang": "^0.0.15",
25
- "@webxr-input-profiles/motion-controllers": "^1.0.0",
26
25
  "chevrotain": "^9.0.2",
27
26
  "draco3d": "^1.4.1",
28
27
  "fflate": "^0.6.9",
@@ -33,6 +32,6 @@
33
32
  "zstddec": "^0.0.2"
34
33
  },
35
34
  "peerDependencies": {
36
- "three": ">=0.128.0"
35
+ "three": ">=0.137.0"
37
36
  }
38
37
  }
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("three"),t=require("./XRHandMeshModel.cjs.js");require("../loaders/GLTFLoader.cjs.js");class o extends e.Object3D{constructor(e){super(),this.controller=e,this.motionController=null,this.envMap=null,this.mesh=null,e.addEventListener("connected",(o=>{const n=o.data;n.hand&&!this.motionController&&(this.xrInputSource=n,this.motionController=new t.XRHandMeshModel(this,e,this.path,n.handedness))})),e.addEventListener("disconnected",(()=>{this.clear(),this.motionController=null}))}updateMatrixWorld(e){super.updateMatrixWorld(e),this.motionController&&this.motionController.updateMesh()}getPointerPosition(){const e=this.controller.joints["index-finger-tip"];return e?e.position:null}intersectBoxObject(t){const o=this.getPointerPosition();if(o){const n=new e.Sphere(o,.01),r=(new e.Box3).setFromObject(t);return n.intersectsBox(r)}return!1}checkButton(e){this.intersectBoxObject(e)?e.onPress():e.onClear(),e.isPressed()&&e.whilePressed()}}exports.OculusHandModel=o;
@@ -0,0 +1,19 @@
1
+ import { Object3D, Sphere, Box3, Mesh, Texture, Vector3 } from 'three';
2
+
3
+ export class OculusHandModel extends Object3D {
4
+ controller: Object3D;
5
+ motionController: Object3D | null;
6
+ envMap: Texture | null;
7
+
8
+ mesh: Mesh | null;
9
+
10
+ constructor(controller: Object3D);
11
+
12
+ updateMatrixWorld(force?: boolean): void;
13
+
14
+ getPointerPosition(): Vector3 | null;
15
+
16
+ intersectBoxObject(boxObject: Object3D): boolean;
17
+
18
+ checkButton(button: Object3D): void;
19
+ }
@@ -0,0 +1,72 @@
1
+ import { Object3D, Sphere, Box3 } from 'three';
2
+ import { XRHandMeshModel } from './XRHandMeshModel.js';
3
+
4
+ const TOUCH_RADIUS = 0.01;
5
+ const POINTING_JOINT = 'index-finger-tip';
6
+
7
+ class OculusHandModel extends Object3D {
8
+ constructor(controller) {
9
+ super();
10
+ this.controller = controller;
11
+ this.motionController = null;
12
+ this.envMap = null;
13
+ this.mesh = null;
14
+ controller.addEventListener('connected', event => {
15
+ const xrInputSource = event.data;
16
+
17
+ if (xrInputSource.hand && !this.motionController) {
18
+ this.xrInputSource = xrInputSource;
19
+ this.motionController = new XRHandMeshModel(this, controller, this.path, xrInputSource.handedness);
20
+ }
21
+ });
22
+ controller.addEventListener('disconnected', () => {
23
+ this.clear();
24
+ this.motionController = null;
25
+ });
26
+ }
27
+
28
+ updateMatrixWorld(force) {
29
+ super.updateMatrixWorld(force);
30
+
31
+ if (this.motionController) {
32
+ this.motionController.updateMesh();
33
+ }
34
+ }
35
+
36
+ getPointerPosition() {
37
+ const indexFingerTip = this.controller.joints[POINTING_JOINT];
38
+
39
+ if (indexFingerTip) {
40
+ return indexFingerTip.position;
41
+ } else {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ intersectBoxObject(boxObject) {
47
+ const pointerPosition = this.getPointerPosition();
48
+
49
+ if (pointerPosition) {
50
+ const indexSphere = new Sphere(pointerPosition, TOUCH_RADIUS);
51
+ const box = new Box3().setFromObject(boxObject);
52
+ return indexSphere.intersectsBox(box);
53
+ } else {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ checkButton(button) {
59
+ if (this.intersectBoxObject(button)) {
60
+ button.onPress();
61
+ } else {
62
+ button.onClear();
63
+ }
64
+
65
+ if (button.isPressed()) {
66
+ button.whilePressed();
67
+ }
68
+ }
69
+
70
+ }
71
+
72
+ export { OculusHandModel };
@@ -0,0 +1 @@
1
+ "use strict";function t(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach((function(i){if("default"!==i){var s=Object.getOwnPropertyDescriptor(t,i);Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:function(){return t[i]}})}})),e.default=t,Object.freeze(e)}Object.defineProperty(exports,"__esModule",{value:!0});var e=t(require("three"));const i=new e.Vector3(0,1,0),s=new e.Vector3(0,0,1);class r extends e.Object3D{constructor(t,e){super(),this.hand=t,this.controller=e,this.motionController=null,this.envMap=null,this.mesh=null,this.pointerGeometry=null,this.pointerMesh=null,this.pointerObject=null,this.pinched=!1,this.attached=!1,this.cursorObject=null,this.raycaster=null,t.addEventListener("connected",(t=>{const e=t.data;e.hand&&(this.visible=!0,this.xrInputSource=e,this.createPointer())}))}_drawVerticesRing(t,e,i){const r=e.clone();for(var o=0;o<16;o++){r.applyAxisAngle(s,2*Math.PI/16);const e=16*i+o;t[3*e]=r.x,t[3*e+1]=r.y,t[3*e+2]=r.z}}_updatePointerVertices(t){const s=this.pointerGeometry.attributes.position.array,r=new e.Vector3(.002,0,-1*(.035-t));this._drawVerticesRing(s,r,0);const o=new e.Vector3(Math.sin(110*Math.PI/180)*t,Math.cos(110*Math.PI/180)*t,0);for(var n=0;n<12;n++)this._drawVerticesRing(s,o,n+1),o.applyAxisAngle(i,110*Math.PI/180/-24);const a=new e.Vector3(0,0,-1*(.035-t));s[624]=a.x,s[625]=a.y,s[626]=a.z;const c=new e.Vector3(0,0,t);s[627]=c.x,s[628]=c.y,s[629]=c.z,this.pointerGeometry.setAttribute("position",new e.Float32BufferAttribute(s,3))}createPointer(){var t,i;const s=new Array(630).fill(0),r=[];for(this.pointerGeometry=new e.BufferGeometry,this.pointerGeometry.setAttribute("position",new e.Float32BufferAttribute(s,3)),this._updatePointerVertices(.01),t=0;t<12;t++){for(i=0;i<15;i++)r.push(16*t+i,16*t+i+1,16*(t+1)+i),r.push(16*t+i+1,16*(t+1)+i+1,16*(t+1)+i);r.push(16*(t+1)-1,16*t,16*(t+2)-1),r.push(16*t,16*(t+1),16*(t+2)-1)}for(t=0;t<15;t++)r.push(208,t+1,t),r.push(209,t+192,t+192+1);r.push(208,0,15),r.push(209,207,192);const o=new e.MeshBasicMaterial;o.transparent=!0,o.opacity=.4,this.pointerGeometry.setIndex(r),this.pointerMesh=new e.Mesh(this.pointerGeometry,o),this.pointerMesh.position.set(0,0,-.01),this.pointerObject=new e.Object3D,this.pointerObject.add(this.pointerMesh),this.raycaster=new e.Raycaster;const n=new e.SphereGeometry(.02,10,10),a=new e.MeshBasicMaterial;a.transparent=!0,a.opacity=.4,this.cursorObject=new e.Mesh(n,a),this.pointerObject.add(this.cursorObject),this.add(this.pointerObject)}_updateRaycaster(){if(this.raycaster){const t=this.pointerObject.matrixWorld,i=new e.Matrix4;i.identity().extractRotation(t),this.raycaster.ray.origin.setFromMatrixPosition(t),this.raycaster.ray.direction.set(0,0,-1).applyMatrix4(i)}}_updatePointer(){this.pointerObject.visible=this.controller.visible;const t=this.hand.joints["index-finger-tip"],e=this.hand.joints["thumb-tip"],i=t.position.distanceTo(e.position),s=t.position.clone().add(e.position).multiplyScalar(.5);this.pointerObject.position.copy(s),this.pointerObject.quaternion.copy(this.controller.quaternion),this.pinched=i<=.02;const r=(i-.01)/.04,o=(i-.01)/.01;if(r>1)this._updatePointerVertices(.01),this.pointerMesh.position.set(0,0,-.01),this.pointerMesh.material.opacity=.4;else if(r>0){const t=.007*r+.003;this._updatePointerVertices(t),o<1?(this.pointerMesh.position.set(0,0,-1*t-.02*(1-o)),this.pointerMesh.material.opacity=.4+.6*(1-o)):(this.pointerMesh.position.set(0,0,-1*t),this.pointerMesh.material.opacity=.4)}else this._updatePointerVertices(.003),this.pointerMesh.position.set(0,0,-.023),this.pointerMesh.material.opacity=1;this.cursorObject.material.opacity=this.pointerMesh.material.opacity}updateMatrixWorld(t){super.updateMatrixWorld(t),this.pointerGeometry&&(this._updatePointer(),this._updateRaycaster())}isPinched(){return this.pinched}setAttached(t){this.attached=t}isAttached(){return this.attached}intersectObject(t,e=!0){if(this.raycaster)return this.raycaster.intersectObject(t,e)}intersectObjects(t,e=!0){if(this.raycaster)return this.raycaster.intersectObjects(t,e)}checkIntersections(t,i=!1){if(this.raycaster&&!this.attached){const s=this.raycaster.intersectObjects(t,i),r=new e.Vector3(0,0,-1);if(s.length>0){const t=s[0].distance;this.cursorObject.position.copy(r.multiplyScalar(t))}else this.cursorObject.position.copy(r.multiplyScalar(1.5))}}setCursor(t){const i=new e.Vector3(0,0,-1);this.raycaster&&!this.attached&&this.cursorObject.position.copy(i.multiplyScalar(t))}}exports.OculusHandPointerModel=r;
@@ -0,0 +1,63 @@
1
+ import {
2
+ BufferGeometry,
3
+ Intersection,
4
+ Mesh,
5
+ MeshBasicMaterial,
6
+ Object3D,
7
+ Raycaster,
8
+ SphereBufferGeometry,
9
+ Texture,
10
+ Vector3,
11
+ } from 'three';
12
+
13
+ export class OculusHandPointerModel extends Object3D {
14
+ hand: Object3D;
15
+ controller: Object3D;
16
+ motionController: Object3D | null;
17
+
18
+ envMap: Texture | null;
19
+
20
+ mesh: Mesh | null;
21
+
22
+ pointerGeometry: BufferGeometry | null;
23
+ pointerMesh: Mesh<BufferGeometry, MeshBasicMaterial> | null;
24
+ pointerObject: Object3D | null;
25
+
26
+ pinched: boolean;
27
+ attached: boolean;
28
+
29
+ cursorObject: Mesh<SphereBufferGeometry, MeshBasicMaterial> | null;
30
+
31
+ raycaster: Raycaster;
32
+
33
+ visible: boolean;
34
+ xrInputSource: unknown;
35
+
36
+ constructor(hand: Object3D, controller: Object3D);
37
+
38
+ private _drawVerticesRing(vertices: number[], baseVector: Vector3, ringIndex: number): void;
39
+
40
+ private _updatePointerVertices(rearRadius: number): void;
41
+
42
+ public createPointer(): void;
43
+
44
+ private _updateRaycaster(): void;
45
+
46
+ private _updatePointer(): void;
47
+
48
+ public updateMatrixWorld(force?: boolean): void;
49
+
50
+ public isPinched(): boolean;
51
+
52
+ public setAttached(attached: boolean): void;
53
+
54
+ public isAttached(): boolean;
55
+
56
+ public intersectObject(object: Object3D, recursive?: boolean): Intersection[] | void;
57
+
58
+ public intersectObjects(objects: Object3D[], recursive?: boolean): Intersection[] | void;
59
+
60
+ public checkIntersections(objects: Object3D[], recursive?: boolean): void;
61
+
62
+ public setCursor(distance: number): void;
63
+ }