three-stdlib
Version:
stand-alone library of threejs examples
327 lines (326 loc) • 12.3 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const MotionControllerConstants = {
Handedness: {
NONE: "none",
LEFT: "left",
RIGHT: "right"
},
ComponentState: {
DEFAULT: "default",
TOUCHED: "touched",
PRESSED: "pressed"
},
ComponentProperty: {
BUTTON: "button",
X_AXIS: "xAxis",
Y_AXIS: "yAxis",
STATE: "state"
},
ComponentType: {
TRIGGER: "trigger",
SQUEEZE: "squeeze",
TOUCHPAD: "touchpad",
THUMBSTICK: "thumbstick",
BUTTON: "button"
},
ButtonTouchThreshold: 0.05,
AxisTouchThreshold: 0.1,
VisualResponseProperty: {
TRANSFORM: "transform",
VISIBILITY: "visibility"
}
};
async function fetchJsonFile(path) {
const response = await fetch(path);
if (!response.ok) {
throw new Error(response.statusText);
} else {
return response.json();
}
}
async function fetchProfilesList(basePath) {
if (!basePath) {
throw new Error("No basePath supplied");
}
const profileListFileName = "profilesList.json";
const profilesList = await fetchJsonFile(`${basePath}/${profileListFileName}`);
return profilesList;
}
async function fetchProfile(xrInputSource, basePath, defaultProfile = null, getAssetPath = true) {
if (!xrInputSource) {
throw new Error("No xrInputSource supplied");
}
if (!basePath) {
throw new Error("No basePath supplied");
}
const supportedProfilesList = await fetchProfilesList(basePath);
let match = void 0;
xrInputSource.profiles.some((profileId) => {
const supportedProfile = supportedProfilesList[profileId];
if (supportedProfile) {
match = {
profileId,
profilePath: `${basePath}/${supportedProfile.path}`,
deprecated: !!supportedProfile.deprecated
};
}
return !!match;
});
if (!match) {
if (!defaultProfile) {
throw new Error("No matching profile name found");
}
const supportedProfile = supportedProfilesList[defaultProfile];
if (!supportedProfile) {
throw new Error(`No matching profile name found and default profile "${defaultProfile}" missing.`);
}
match = {
profileId: defaultProfile,
profilePath: `${basePath}/${supportedProfile.path}`,
deprecated: !!supportedProfile.deprecated
};
}
const profile = await fetchJsonFile(match.profilePath);
let assetPath = void 0;
if (getAssetPath) {
let layout;
if (xrInputSource.handedness === "any") {
layout = profile.layouts[Object.keys(profile.layouts)[0]];
} else {
layout = profile.layouts[xrInputSource.handedness];
}
if (!layout) {
throw new Error(`No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}`);
}
if (layout.assetPath) {
assetPath = match.profilePath.replace("profile.json", layout.assetPath);
}
}
return { profile, assetPath };
}
const defaultComponentValues = {
xAxis: 0,
yAxis: 0,
button: 0,
state: MotionControllerConstants.ComponentState.DEFAULT
};
function normalizeAxes(x = 0, y = 0) {
let xAxis = x;
let yAxis = y;
const hypotenuse = Math.sqrt(x * x + y * y);
if (hypotenuse > 1) {
const theta = Math.atan2(y, x);
xAxis = Math.cos(theta);
yAxis = Math.sin(theta);
}
const result = {
normalizedXAxis: xAxis * 0.5 + 0.5,
normalizedYAxis: yAxis * 0.5 + 0.5
};
return result;
}
class VisualResponse {
constructor(visualResponseDescription) {
__publicField(this, "value");
__publicField(this, "componentProperty");
__publicField(this, "states");
__publicField(this, "valueNodeName");
__publicField(this, "valueNodeProperty");
__publicField(this, "minNodeName");
__publicField(this, "maxNodeName");
__publicField(this, "valueNode");
__publicField(this, "minNode");
__publicField(this, "maxNode");
this.componentProperty = visualResponseDescription.componentProperty;
this.states = visualResponseDescription.states;
this.valueNodeName = visualResponseDescription.valueNodeName;
this.valueNodeProperty = visualResponseDescription.valueNodeProperty;
if (this.valueNodeProperty === MotionControllerConstants.VisualResponseProperty.TRANSFORM) {
this.minNodeName = visualResponseDescription.minNodeName;
this.maxNodeName = visualResponseDescription.maxNodeName;
}
this.value = 0;
this.updateFromComponent(defaultComponentValues);
}
/**
* Computes the visual response's interpolation weight based on component state
* @param {Object} componentValues - The component from which to update
* @param {number | undefined} xAxis - The reported X axis value of the component
* @param {number | undefined} yAxis - The reported Y axis value of the component
* @param {number | undefined} button - The reported value of the component's button
* @param {string} state - The component's active state
*/
updateFromComponent({
xAxis,
yAxis,
button,
state
}) {
const { normalizedXAxis, normalizedYAxis } = normalizeAxes(xAxis, yAxis);
switch (this.componentProperty) {
case MotionControllerConstants.ComponentProperty.X_AXIS:
this.value = this.states.includes(state) ? normalizedXAxis : 0.5;
break;
case MotionControllerConstants.ComponentProperty.Y_AXIS:
this.value = this.states.includes(state) ? normalizedYAxis : 0.5;
break;
case MotionControllerConstants.ComponentProperty.BUTTON:
this.value = this.states.includes(state) && button ? button : 0;
break;
case MotionControllerConstants.ComponentProperty.STATE:
if (this.valueNodeProperty === MotionControllerConstants.VisualResponseProperty.VISIBILITY) {
this.value = this.states.includes(state);
} else {
this.value = this.states.includes(state) ? 1 : 0;
}
break;
default:
throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`);
}
}
}
class Component {
/**
* @param {string} componentId - Id of the component
* @param {InputProfileComponent} componentDescription - Description of the component to be created
*/
constructor(componentId, componentDescription) {
__publicField(this, "id");
__publicField(this, "values");
__publicField(this, "type");
__publicField(this, "gamepadIndices");
__publicField(this, "rootNodeName");
__publicField(this, "visualResponses");
__publicField(this, "touchPointNodeName");
__publicField(this, "touchPointNode");
if (!componentId || !componentDescription || !componentDescription.visualResponses || !componentDescription.gamepadIndices || Object.keys(componentDescription.gamepadIndices).length === 0) {
throw new Error("Invalid arguments supplied");
}
this.id = componentId;
this.type = componentDescription.type;
this.rootNodeName = componentDescription.rootNodeName;
this.touchPointNodeName = componentDescription.touchPointNodeName;
this.visualResponses = {};
Object.keys(componentDescription.visualResponses).forEach((responseName) => {
const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]);
this.visualResponses[responseName] = visualResponse;
});
this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices);
this.values = {
state: MotionControllerConstants.ComponentState.DEFAULT,
button: this.gamepadIndices.button !== void 0 ? 0 : void 0,
xAxis: this.gamepadIndices.xAxis !== void 0 ? 0 : void 0,
yAxis: this.gamepadIndices.yAxis !== void 0 ? 0 : void 0
};
}
get data() {
const data = { id: this.id, ...this.values };
return data;
}
/**
* @description Poll for updated data based on current gamepad state
* @param {Object} gamepad - The gamepad object from which the component data should be polled
*/
updateFromGamepad(gamepad) {
this.values.state = MotionControllerConstants.ComponentState.DEFAULT;
if (this.gamepadIndices.button !== void 0 && gamepad.buttons.length > this.gamepadIndices.button) {
const gamepadButton = gamepad.buttons[this.gamepadIndices.button];
this.values.button = gamepadButton.value;
this.values.button = this.values.button < 0 ? 0 : this.values.button;
this.values.button = this.values.button > 1 ? 1 : this.values.button;
if (gamepadButton.pressed || this.values.button === 1) {
this.values.state = MotionControllerConstants.ComponentState.PRESSED;
} else if (gamepadButton.touched || this.values.button > MotionControllerConstants.ButtonTouchThreshold) {
this.values.state = MotionControllerConstants.ComponentState.TOUCHED;
}
}
if (this.gamepadIndices.xAxis !== void 0 && gamepad.axes.length > this.gamepadIndices.xAxis) {
this.values.xAxis = gamepad.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;
if (this.values.state === MotionControllerConstants.ComponentState.DEFAULT && Math.abs(this.values.xAxis) > MotionControllerConstants.AxisTouchThreshold) {
this.values.state = MotionControllerConstants.ComponentState.TOUCHED;
}
}
if (this.gamepadIndices.yAxis !== void 0 && gamepad.axes.length > this.gamepadIndices.yAxis) {
this.values.yAxis = gamepad.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;
if (this.values.state === MotionControllerConstants.ComponentState.DEFAULT && Math.abs(this.values.yAxis) > MotionControllerConstants.AxisTouchThreshold) {
this.values.state = MotionControllerConstants.ComponentState.TOUCHED;
}
}
Object.values(this.visualResponses).forEach((visualResponse) => {
visualResponse.updateFromComponent(this.values);
});
}
}
class MotionController {
/**
* @param {XRInputSource} xrInputSource - The XRInputSource to build the MotionController around
* @param {Profile} profile - The best matched profile description for the supplied xrInputSource
* @param {string} assetUrl
*/
constructor(xrInputSource, profile, assetUrl) {
__publicField(this, "xrInputSource");
__publicField(this, "assetUrl");
__publicField(this, "layoutDescription");
__publicField(this, "id");
__publicField(this, "components");
if (!xrInputSource) {
throw new Error("No xrInputSource supplied");
}
if (!profile) {
throw new Error("No profile supplied");
}
if (!profile.layouts[xrInputSource.handedness]) {
throw new Error("No layout for " + xrInputSource.handedness + " handedness");
}
this.xrInputSource = xrInputSource;
this.assetUrl = assetUrl;
this.id = profile.profileId;
this.layoutDescription = profile.layouts[xrInputSource.handedness];
this.components = {};
Object.keys(this.layoutDescription.components).forEach((componentId) => {
const componentDescription = this.layoutDescription.components[componentId];
this.components[componentId] = new Component(componentId, componentDescription);
});
this.updateFromGamepad();
}
get gripSpace() {
return this.xrInputSource.gripSpace;
}
get targetRaySpace() {
return this.xrInputSource.targetRaySpace;
}
/**
* @description Returns a subset of component data for simplified debugging
*/
get data() {
const data = [];
Object.values(this.components).forEach((component) => {
data.push(component.data);
});
return data;
}
/**
* @description Poll for updated data based on current gamepad state
*/
updateFromGamepad() {
Object.values(this.components).forEach((component) => {
component.updateFromGamepad(this.xrInputSource.gamepad);
});
}
}
export {
MotionController,
MotionControllerConstants,
fetchProfile,
fetchProfilesList
};
//# sourceMappingURL=MotionControllers.js.map