three-stdlib 2.8.4 → 2.8.5

Sign up to get free protection for your applications and to get access to all the features.
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
+ }