Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
import {oimo} from './OimoPhysics.js';
// dynamics
export const World = oimo.dynamics.World;
export const RigidBodyType = oimo.dynamics.rigidbody.RigidBodyType;
export const RigidBodyConfig = oimo.dynamics.rigidbody.RigidBodyConfig;
export const ShapeConfig = oimo.dynamics.rigidbody.ShapeConfig;
export const RigidBody = oimo.dynamics.rigidbody.RigidBody;
export const Shape = oimo.dynamics.rigidbody.Shape;
export const SphericalJoint = oimo.dynamics.constraint.joint.SphericalJoint;
export const RevoluteJointConfig = oimo.dynamics.constraint.joint.RevoluteJointConfig;
export const UniversalJointConfig = oimo.dynamics.constraint.joint.UniversalJointConfig;
export const CylindricalJoint = oimo.dynamics.constraint.joint.CylindricalJoint;
export const PrismaticJoint = oimo.dynamics.constraint.joint.PrismaticJoint;
export const PrismaticJointConfig = oimo.dynamics.constraint.joint.PrismaticJointConfig;
export const RevoluteJoint = oimo.dynamics.constraint.joint.RevoluteJoint;
export const RagdollJoint = oimo.dynamics.constraint.joint.RagdollJoint;
export const CylindricalJointConfig = oimo.dynamics.constraint.joint.CylindricalJointConfig;
export const SphericalJointConfig = oimo.dynamics.constraint.joint.SphericalJointConfig;
export const RagdollJointConfig = oimo.dynamics.constraint.joint.RagdollJointConfig;
export const SpringDamper = oimo.dynamics.constraint.joint.SpringDamper;
export const TranslationalLimitMotor = oimo.dynamics.constraint.joint.TranslationalLimitMotor;
export const RotationalLimitMotor = oimo.dynamics.constraint.joint.RotationalLimitMotor;
export const UniversalJoint = oimo.dynamics.constraint.joint.UniversalJoint;
// common
export const Vec3 = oimo.common.Vec3;
export const Quat = oimo.common.Quat;
export const Mat3 = oimo.common.Mat3;
export const MathUtil = oimo.common.MathUtil;
export const Transform = oimo.common.Transform;
// collision
export const OCapsuleGeometry = oimo.collision.geometry.CapsuleGeometry;
export const OConvexHullGeometry = oimo.collision.geometry.ConvexHullGeometry;
export const OBoxGeometry = oimo.collision.geometry.BoxGeometry;
export const OSphereGeometry = oimo.collision.geometry.SphereGeometry;
export const OCylinderGeometry = oimo.collision.geometry.CylinderGeometry;
export const OConeGeometry = oimo.collision.geometry.ConeGeometry;
export const OGeometry = oimo.collision.geometry.Geometry;
// callback
export const RayCastClosest = oimo.dynamics.callback.RayCastClosest;

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,397 @@
/**
* @webxr-input-profiles/motion-controllers 1.0.0 https://github.com/immersive-web/webxr-input-profiles
*/
const Constants = {
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: 0.05,
AxisTouchThreshold: 0.1,
VisualResponseProperty: Object.freeze({
TRANSFORM: 'transform',
VISIBILITY: 'visibility'
})
};
/**
* @description Static helper function to fetch a JSON file and turn it into a JS object
* @param {string} path - Path to JSON file to be fetched
*/
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');
}
// Get the list of profiles
const supportedProfilesList = await fetchProfilesList(basePath);
// Find the relative path to the first requested profile that is recognized
let match;
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;
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 };
}
/** @constant {Object} */
const defaultComponentValues = {
xAxis: 0,
yAxis: 0,
button: 0,
state: Constants.ComponentState.DEFAULT
};
/**
* @description Converts an X, Y coordinate from the range -1 to 1 (as reported by the Gamepad
* API) to the range 0 to 1 (for interpolation). Also caps the X, Y values to be bounded within
* a circle. This ensures that thumbsticks are not animated outside the bounds of their physical
* range of motion and touchpads do not report touch locations off their physical bounds.
* @param {number} x The original x coordinate in the range -1 to 1
* @param {number} y The original y coordinate in the range -1 to 1
*/
function normalizeAxes(x = 0, y = 0) {
let xAxis = x;
let yAxis = y;
// Determine if the point is outside the bounds of the circle
// and, if so, place it on the edge of the circle
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);
}
// Scale and move the circle so values are in the interpolation range. The circle's origin moves
// from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5.
const result = {
normalizedXAxis: (xAxis * 0.5) + 0.5,
normalizedYAxis: (yAxis * 0.5) + 0.5
};
return result;
}
/**
* Contains the description of how the 3D model should visually respond to a specific user input.
* This is accomplished by initializing the object with the name of a node in the 3D model and
* property that need to be modified in response to user input, the name of the nodes representing
* the allowable range of motion, and the name of the input which triggers the change. In response
* to the named input changing, this object computes the appropriate weighting to use for
* interpolating between the range of motion nodes.
*/
class VisualResponse {
constructor(visualResponseDescription) {
this.componentProperty = visualResponseDescription.componentProperty;
this.states = visualResponseDescription.states;
this.valueNodeName = visualResponseDescription.valueNodeName;
this.valueNodeProperty = visualResponseDescription.valueNodeProperty;
if (this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM) {
this.minNodeName = visualResponseDescription.minNodeName;
this.maxNodeName = visualResponseDescription.maxNodeName;
}
// Initializes the response's current value based on default data
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} xAxis - The reported X axis value of the component
* @param {number} yAxis - The reported Y axis value of the component
* @param {number} 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 Constants.ComponentProperty.X_AXIS:
this.value = (this.states.includes(state)) ? normalizedXAxis : 0.5;
break;
case Constants.ComponentProperty.Y_AXIS:
this.value = (this.states.includes(state)) ? normalizedYAxis : 0.5;
break;
case Constants.ComponentProperty.BUTTON:
this.value = (this.states.includes(state)) ? button : 0;
break;
case Constants.ComponentProperty.STATE:
if (this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY) {
this.value = (this.states.includes(state));
} else {
this.value = this.states.includes(state) ? 1.0 : 0.0;
}
break;
default:
throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`);
}
}
}
class Component {
/**
* @param {Object} componentId - Id of the component
* @param {Object} componentDescription - Description of the component to be created
*/
constructor(componentId, componentDescription) {
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;
// Build all the visual responses for this component
this.visualResponses = {};
Object.keys(componentDescription.visualResponses).forEach((responseName) => {
const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]);
this.visualResponses[responseName] = visualResponse;
});
// Set default values
this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices);
this.values = {
state: Constants.ComponentState.DEFAULT,
button: (this.gamepadIndices.button !== undefined) ? 0 : undefined,
xAxis: (this.gamepadIndices.xAxis !== undefined) ? 0 : undefined,
yAxis: (this.gamepadIndices.yAxis !== undefined) ? 0 : undefined
};
}
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) {
// Set the state to default before processing other data sources
this.values.state = Constants.ComponentState.DEFAULT;
// Get and normalize button
if (this.gamepadIndices.button !== undefined
&& 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;
// Set the state based on the button
if (gamepadButton.pressed || this.values.button === 1) {
this.values.state = Constants.ComponentState.PRESSED;
} else if (gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold) {
this.values.state = Constants.ComponentState.TOUCHED;
}
}
// Get and normalize x axis value
if (this.gamepadIndices.xAxis !== undefined
&& 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 the state is still default, check if the xAxis makes it touched
if (this.values.state === Constants.ComponentState.DEFAULT
&& Math.abs(this.values.xAxis) > Constants.AxisTouchThreshold) {
this.values.state = Constants.ComponentState.TOUCHED;
}
}
// Get and normalize Y axis value
if (this.gamepadIndices.yAxis !== undefined
&& 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 the state is still default, check if the yAxis makes it touched
if (this.values.state === Constants.ComponentState.DEFAULT
&& Math.abs(this.values.yAxis) > Constants.AxisTouchThreshold) {
this.values.state = Constants.ComponentState.TOUCHED;
}
}
// Update the visual response weights based on the current component data
Object.values(this.visualResponses).forEach((visualResponse) => {
visualResponse.updateFromComponent(this.values);
});
}
}
/**
* @description Builds a motion controller with components and visual responses based on the
* supplied profile description. Data is polled from the xrInputSource's gamepad.
* @author Nell Waliczek / https://github.com/NellWaliczek
*/
class MotionController {
/**
* @param {Object} xrInputSource - The XRInputSource to build the MotionController around
* @param {Object} profile - The best matched profile description for the supplied xrInputSource
* @param {Object} assetUrl
*/
constructor(xrInputSource, profile, assetUrl) {
if (!xrInputSource) {
throw new Error('No xrInputSource supplied');
}
if (!profile) {
throw new Error('No profile supplied');
}
this.xrInputSource = xrInputSource;
this.assetUrl = assetUrl;
this.id = profile.profileId;
// Build child components as described in the profile description
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);
});
// Initialize components based on current gamepad state
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 { Constants, MotionController, fetchProfile, fetchProfilesList };

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,125 @@
/**
* potpack - by [@mourner](https://github.com/mourner)
*
* A tiny JavaScript function for packing 2D rectangles into a near-square container,
* which is useful for generating CSS sprites and WebGL textures. Similar to
* [shelf-pack](https://github.com/mapbox/shelf-pack), but static (you can't add items
* once a layout is generated), and aims for maximal space utilization.
*
* A variation of algorithms used in [rectpack2D](https://github.com/TeamHypersomnia/rectpack2D)
* and [bin-pack](https://github.com/bryanburgers/bin-pack), which are in turn based
* on [this article by Blackpawn](http://blackpawn.com/texts/lightmaps/default.html).
*
* @license
* ISC License
*
* Copyright (c) 2018, Mapbox
*
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted, provided that the above copyright notice
* and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*/
function potpack(boxes) {
// calculate total box area and maximum box width
let area = 0;
let maxWidth = 0;
for (const box of boxes) {
area += box.w * box.h;
maxWidth = Math.max(maxWidth, box.w);
}
// sort the boxes for insertion by height, descending
boxes.sort((a, b) => b.h - a.h);
// aim for a squarish resulting container,
// slightly adjusted for sub-100% space utilization
const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
// start with a single empty space, unbounded at the bottom
const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}];
let width = 0;
let height = 0;
for (const box of boxes) {
// look through spaces backwards so that we check smaller spaces first
for (let i = spaces.length - 1; i >= 0; i--) {
const space = spaces[i];
// look for empty spaces that can accommodate the current box
if (box.w > space.w || box.h > space.h) continue;
// found the space; add the box to its top-left corner
// |-------|-------|
// | box | |
// |_______| |
// | space |
// |_______________|
box.x = space.x;
box.y = space.y;
height = Math.max(height, box.y + box.h);
width = Math.max(width, box.x + box.w);
if (box.w === space.w && box.h === space.h) {
// space matches the box exactly; remove it
const last = spaces.pop();
if (i < spaces.length) spaces[i] = last;
} else if (box.h === space.h) {
// space matches the box height; update it accordingly
// |-------|---------------|
// | box | updated space |
// |_______|_______________|
space.x += box.w;
space.w -= box.w;
} else if (box.w === space.w) {
// space matches the box width; update it accordingly
// |---------------|
// | box |
// |_______________|
// | updated space |
// |_______________|
space.y += box.h;
space.h -= box.h;
} else {
// otherwise the box splits the space into two spaces
// |-------|-----------|
// | box | new space |
// |_______|___________|
// | updated space |
// |___________________|
spaces.push({
x: space.x + box.w,
y: space.y,
w: space.w - box.w,
h: box.h
});
space.y += box.h;
space.h -= box.h;
}
break;
}
}
return {
w: width, // container width
h: height, // container height
fill: (area / (width * height)) || 0 // space utilization
};
}
export { potpack };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,167 @@
var Stats = function () {
var mode = 0;
var container = document.createElement( 'div' );
container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000';
container.addEventListener( 'click', function ( event ) {
event.preventDefault();
showPanel( ++ mode % container.children.length );
}, false );
//
function addPanel( panel ) {
container.appendChild( panel.dom );
return panel;
}
function showPanel( id ) {
for ( var i = 0; i < container.children.length; i ++ ) {
container.children[ i ].style.display = i === id ? 'block' : 'none';
}
mode = id;
}
//
var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) );
var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) );
if ( self.performance && self.performance.memory ) {
var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) );
}
showPanel( 0 );
return {
REVISION: 16,
dom: container,
addPanel: addPanel,
showPanel: showPanel,
begin: function () {
beginTime = ( performance || Date ).now();
},
end: function () {
frames ++;
var time = ( performance || Date ).now();
msPanel.update( time - beginTime, 200 );
if ( time >= prevTime + 1000 ) {
fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 );
prevTime = time;
frames = 0;
if ( memPanel ) {
var memory = performance.memory;
memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 );
}
}
return time;
},
update: function () {
beginTime = this.end();
},
// Backwards Compatibility
domElement: container,
setMode: showPanel
};
};
Stats.Panel = function ( name, fg, bg ) {
var min = Infinity, max = 0, round = Math.round;
var PR = round( window.devicePixelRatio || 1 );
var WIDTH = 80 * PR, HEIGHT = 48 * PR,
TEXT_X = 3 * PR, TEXT_Y = 2 * PR,
GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;
var canvas = document.createElement( 'canvas' );
canvas.width = WIDTH;
canvas.height = HEIGHT;
canvas.style.cssText = 'width:80px;height:48px';
var context = canvas.getContext( '2d' );
context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
context.textBaseline = 'top';
context.fillStyle = bg;
context.fillRect( 0, 0, WIDTH, HEIGHT );
context.fillStyle = fg;
context.fillText( name, TEXT_X, TEXT_Y );
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
context.fillStyle = bg;
context.globalAlpha = 0.9;
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
return {
dom: canvas,
update: function ( value, maxValue ) {
min = Math.min( min, value );
max = Math.max( max, value );
context.fillStyle = bg;
context.globalAlpha = 1;
context.fillRect( 0, 0, WIDTH, GRAPH_Y );
context.fillStyle = fg;
context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y );
context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT );
context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT );
context.fillStyle = bg;
context.globalAlpha = 0.9;
context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) );
}
};
};
export default Stats;

File diff suppressed because one or more lines are too long