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.cjs.js +1 -1
- package/index.d.ts +9 -4
- package/index.js +9 -4
- package/libs/MotionControllers.cjs.js +1 -0
- package/libs/MotionControllers.js +403 -0
- package/package.json +2 -3
- package/webxr/OculusHandModel.cjs.js +1 -0
- package/webxr/OculusHandModel.d.ts +19 -0
- package/webxr/OculusHandModel.js +72 -0
- package/webxr/OculusHandPointerModel.cjs.js +1 -0
- package/webxr/OculusHandPointerModel.d.ts +63 -0
- package/webxr/OculusHandPointerModel.js +248 -0
- package/webxr/Text2D.cjs.js +1 -0
- package/webxr/Text2D.d.ts +3 -0
- package/webxr/Text2D.js +32 -0
- package/webxr/VRButton.cjs.js +1 -1
- package/webxr/VRButton.js +17 -1
- package/webxr/XRControllerModelFactory.cjs.js +1 -1
- package/webxr/XRControllerModelFactory.js +65 -66
- package/webxr/XREstimatedLight.cjs.js +1 -1
- package/webxr/XREstimatedLight.js +1 -0
- package/webxr/XRHandMeshModel.cjs.js +1 -0
- package/webxr/XRHandMeshModel.d.ts +11 -0
- package/webxr/XRHandMeshModel.js +55 -0
- package/webxr/XRHandModelFactory.cjs.js +1 -1
- package/webxr/XRHandModelFactory.js +47 -50
- package/webxr/XRHandPrimitiveModel.cjs.js +1 -1
- package/webxr/XRHandPrimitiveModel.js +31 -41
- package/webxr/XRHandOculusMeshModel.cjs.js +0 -1
- package/webxr/XRHandOculusMeshModel.js +0 -89
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/
|
237
|
-
export * from './webxr/
|
238
|
-
export * from './webxr/
|
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 {
|
182
|
-
export {
|
183
|
-
export {
|
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.
|
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.
|
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
|
+
}
|