505 lines
19 KiB
JavaScript
505 lines
19 KiB
JavaScript
|
|
function VRGamepads(opts) {
|
|
var options = Object.assign({
|
|
drag: function(position,direction) {
|
|
return false;
|
|
},
|
|
click: function(position,direction) {
|
|
},
|
|
reset: function() {
|
|
},
|
|
speed: 10,
|
|
move: function(delta) {
|
|
},
|
|
visionCrosshairAngle: -Math.PI/8,
|
|
movementMin: .2,
|
|
},opts);
|
|
|
|
var harborpad = null;
|
|
|
|
function VRGamepad(gamepad) {
|
|
|
|
THREE.Object3D.call( this );
|
|
|
|
this.matrixAutoUpdate = false;
|
|
this.isVRPad = false;
|
|
|
|
var axes = [];
|
|
var buttons = [];
|
|
var buttonsIndexes = {
|
|
move: -1,
|
|
click: -1,
|
|
reset: -1
|
|
}
|
|
|
|
this.getGamepad = function () {
|
|
return gamepad;
|
|
}
|
|
|
|
this.getButtonState = function ( button ) {
|
|
return false;
|
|
}
|
|
|
|
this.drag = function() {
|
|
var pointer = this.getPointer();
|
|
var progress = this.progressObject;
|
|
var pointed = options.drag(pointer.position,pointer.direction);
|
|
if(pointed) {
|
|
if(this.pointerRescale) {
|
|
var distance = pointer.position.distanceTo(pointed.point);
|
|
var thickness = harborpad ? .1 : 1;
|
|
this.pointerObject.scale.set(thickness,distance,thickness);
|
|
}
|
|
this.pointerObject.material.color.setRGB(0,1,0);
|
|
|
|
if(progress) {
|
|
var oid = pointed.object.id;
|
|
if(this.pointedId==oid) {
|
|
const pointingTime = 2000;
|
|
var now = Date.now();
|
|
var ratio = 1-(now-this.pointedTime)/pointingTime;
|
|
if(ratio<0) {
|
|
options.click(pointer.position,pointer.direction);
|
|
this.pointedId = null;
|
|
} else
|
|
progress.scale.set(ratio,ratio,ratio);
|
|
} else {
|
|
this.pointedId = oid;
|
|
progress.scale.set(1,1,1);
|
|
this.pointedTime = Date.now();
|
|
progress.visible = true;
|
|
}
|
|
}
|
|
} else {
|
|
this.pointerObject.material.color.setRGB(1,.75,0);
|
|
if(this.pointerRescale) {
|
|
var thickness = harborpad ? .1 : 1;
|
|
this.pointerObject.scale.set(thickness,100,thickness);
|
|
}
|
|
|
|
if(progress && this.pointedId) {
|
|
progress.visible = false;
|
|
this.pointedId = null;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
this.update = function() {
|
|
var $this = this;
|
|
|
|
if(this.crosshairNeedsUpdate) {
|
|
var pointer = this.getPointer();
|
|
this.pointerObject.position.copy(pointer.position);
|
|
this.pointerObject.position.add(pointer.direction);
|
|
}
|
|
|
|
if(this.progressNeedsUpdate) {
|
|
var pointer = this.getPointer();
|
|
this.progressObject.position.copy(pointer.position);
|
|
this.progressObject.position.add(pointer.direction);
|
|
}
|
|
|
|
var pose = gamepad.pose;
|
|
if(pose) {
|
|
if(pose.position)
|
|
this.position.fromArray(pose.position);
|
|
if(pose.orientation)
|
|
this.quaternion.fromArray(pose.orientation);
|
|
this.matrix.compose(this.position,this.quaternion,this.scale );
|
|
this.matrixWorldNeedsUpdate = true;
|
|
}
|
|
if(gamepad.buttons) {
|
|
var changedButtons = false;
|
|
gamepad.buttons.forEach(function(button,index) {
|
|
if(index===buttonsIndexes.click && button.pressed)
|
|
$this.drag();
|
|
if(button.pressed!==buttons[index]) {
|
|
buttons[index] = button.pressed;
|
|
if(buttons[index]!==undefined) {
|
|
changedButtons = true;
|
|
if(index===buttonsIndexes.move)
|
|
$this.moveButtonChanged(button.pressed);
|
|
if(index===buttonsIndexes.click)
|
|
$this.clickButtonChanged(button.pressed);
|
|
if(index===buttonsIndexes.reset)
|
|
$this.resetButtonChanged(button.pressed);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if(gamepad.axes) {
|
|
var changedAxes = false;
|
|
gamepad.axes.forEach(function(axe,index) {
|
|
if(axe!==axes[index]) {
|
|
axes[index] = axe;
|
|
changedAxes = true;
|
|
}
|
|
});
|
|
if(buttonsIndexes.move>=0 && buttons[buttonsIndexes.move]) {
|
|
var now = window.performance.now();
|
|
var last = this.lastThumbpadTimestamp;
|
|
var deltaT = now - last;
|
|
var rotation = new THREE.Matrix4().extractRotation(this.matrixWorld);
|
|
var direction = new THREE.Vector3(axes[0],0,-axes[1]).applyMatrix4(rotation);
|
|
direction.multiplyScalar(options.speed*deltaT/1000);
|
|
options.move(direction);
|
|
this.lastThumbpadTimestamp = now;
|
|
}
|
|
if(buttonsIndexes.move<0) {
|
|
var now = window.performance.now();
|
|
var movement = new THREE.Vector3(axes[0],0,axes[1]);
|
|
if(movement.length()>options.movementMin) {
|
|
var last = this.lastThumbpadTimestamp;
|
|
var deltaT = now - last;
|
|
// yeah i know, it could have been simpler
|
|
var direction = options.camera.getWorldDirection();
|
|
var xzDirection = new THREE.Vector3(direction.x,0,direction.z);
|
|
xzDirection.normalize();
|
|
xzDirection.applyAxisAngle(new THREE.Vector3(0,1,0),-Math.PI/2);
|
|
var rotateAxis = new THREE.Vector3(xzDirection.x,0,xzDirection.z)
|
|
rotateAxis.normalize();
|
|
var rotateAxis2 = new THREE.Vector3().copy(direction);
|
|
rotateAxis2.applyAxisAngle(rotateAxis,Math.PI/2);
|
|
var angle = Math.atan2(-axes[0],-axes[1]);
|
|
direction.applyAxisAngle(rotateAxis2,angle);
|
|
direction.multiplyScalar(options.speed*deltaT/1000);
|
|
options.move(direction);
|
|
}
|
|
this.lastThumbpadTimestamp = now;
|
|
}
|
|
}
|
|
|
|
if(this.alwaysDrag)
|
|
this.drag();
|
|
}
|
|
|
|
this.getPointer = function() {
|
|
var position = this.getWorldPosition();
|
|
if(gamepad.pose && gamepad.pose.hasOrientation) {
|
|
var line = this.pointerObject;
|
|
var pos0 = new THREE.Vector3(0,0,0);
|
|
pos0.applyMatrix4(line.matrixWorld);
|
|
var direction = new THREE.Vector3(0,-1,0);
|
|
direction.applyMatrix4(line.matrixWorld);
|
|
direction.sub(pos0);
|
|
direction.normalize();
|
|
return {
|
|
position: position,
|
|
direction: direction
|
|
}
|
|
} else {
|
|
var position = options.camera.getWorldPosition();
|
|
var direction = options.camera.getWorldDirection();
|
|
position.add(direction);
|
|
return {
|
|
position: position,
|
|
direction: direction
|
|
}
|
|
}
|
|
}
|
|
|
|
this.moveButtonChanged = function(on) {
|
|
if(on)
|
|
this.lastThumbpadTimestamp = window.performance.now();
|
|
}
|
|
|
|
this.clickButtonChanged = function(on) {
|
|
if(on)
|
|
this.pointerObject.visible = true;
|
|
else {
|
|
this.pointerObject.visible = false;
|
|
var pointer = this.getPointer();
|
|
options.click(pointer.position,pointer.direction);
|
|
}
|
|
}
|
|
|
|
this.resetButtonChanged = function(on) {
|
|
|
|
}
|
|
|
|
this.destroyGamepad = function() {
|
|
if(this.parent)
|
|
this.parent.remove(this);
|
|
if(this.pointerObject && this.pointerObject.parent)
|
|
this.pointerObject.parent.remove(this.pointerObject);
|
|
if(this.progressObject && this.progressObject.parent)
|
|
this.progressObject.parent.remove(this.progressObject);
|
|
}
|
|
|
|
var cache = {}
|
|
|
|
this.createCrosshair = function() {
|
|
var crosshair = cache["crosshair"];
|
|
if(crosshair===undefined) {
|
|
var geometry = new THREE.SphereGeometry(.02);
|
|
var material = new THREE.MeshBasicMaterial( {color: 0xff0000 } );
|
|
crosshair = new THREE.Mesh(geometry,material);
|
|
cache["crosshair"] = crosshair;
|
|
}
|
|
crosshair = crosshair.clone();
|
|
this.crosshairNeedsUpdate = true;
|
|
this.pointerObject = crosshair;
|
|
options.scene.add(crosshair);
|
|
this.pointerRescale = false;
|
|
}
|
|
|
|
this.createProgress = function() {
|
|
var progress = cache["progress"];
|
|
if(progress===undefined) {
|
|
var geometry = new THREE.SphereGeometry(.2,16,12);
|
|
var material = new THREE.MeshBasicMaterial( {
|
|
color: 0xff0000,
|
|
opacity: .5,
|
|
transparent: true
|
|
} );
|
|
progress = new THREE.Mesh(geometry,material);
|
|
progress.visible = false;
|
|
cache["progress"] = progress;
|
|
}
|
|
progress = progress.clone();
|
|
this.progressNeedsUpdate = true;
|
|
this.progressObject = progress;
|
|
options.scene.add(progress);
|
|
}
|
|
|
|
this.createViveControllerMesh = function() {
|
|
|
|
var $this = this;
|
|
// create clicking ray
|
|
var line = cache["vive-controller-ray"];
|
|
if(line===undefined) {
|
|
var geometry = new THREE.CylinderGeometry(.008,.008,1,8);
|
|
geometry.translate(0,-.5,0);
|
|
var material = new THREE.MeshBasicMaterial( {color: 0x80ff80} );
|
|
line = new THREE.Mesh( geometry, material );
|
|
line.scale.set(1,100,1);
|
|
line.rotateX(Math.PI/6);
|
|
line.visible = false;
|
|
cache["vive-controller-ray"] = line;
|
|
}
|
|
this.pointerObject = line.clone();
|
|
this.add(this.pointerObject);
|
|
this.pointerRescale = true;
|
|
|
|
// create controller
|
|
function AddController(object) {
|
|
$this.add(object);
|
|
}
|
|
var controllerObject = cache["vive-controller"];
|
|
if(controllerObject===undefined) {
|
|
cache["vive-controller"] = [AddController];
|
|
var loader = new THREE.JSONLoader();
|
|
loader.load( options.resBase + 'vive-controller/vr_controller_vive_1_5.js',
|
|
function(geometry) {
|
|
var loader = new THREE.TextureLoader();
|
|
loader.setPath( options.resBase + 'vive-controller/' );
|
|
var material = new THREE.MeshBasicMaterial({ color: 0xffffff });
|
|
material.map = loader.load( 'onepointfive_texture.png');
|
|
material.specularMap = loader.load( 'onepointfive_spec.png');
|
|
var controller = new THREE.Mesh(geometry,material);
|
|
var object = new THREE.Object3D();
|
|
object.add(controller);
|
|
cache["vive-controller"].forEach(function(callback) {
|
|
callback(object.clone());
|
|
});
|
|
cache["vive-controller"] = object;
|
|
});
|
|
} else if(Array.isArray(controllerObject)) {
|
|
cache["vive-controller"].push(AddController);
|
|
} else
|
|
AddController(controllerObject.clone());
|
|
options.camera.parent.add(this);
|
|
}
|
|
|
|
this.createOculusTouchMesh = function() {
|
|
var $this = this;
|
|
// create clicking ray
|
|
var line = cache["touch-controller-ray"];
|
|
if(line===undefined) {
|
|
var geometry = new THREE.CylinderGeometry(.008,.008,1,8);
|
|
geometry.translate(0,-.5,0);
|
|
var material = new THREE.MeshBasicMaterial( {color: 0x80ff80} );
|
|
line = new THREE.Mesh( geometry, material );
|
|
line.scale.set(1,100,1);
|
|
line.rotateX(Math.PI/6);
|
|
line.visible = false;
|
|
cache["touch-controller-ray"] = line;
|
|
}
|
|
this.pointerObject = line.clone();
|
|
this.add(this.pointerObject);
|
|
this.pointerRescale = true;
|
|
|
|
// create controller
|
|
function AddController(object) {
|
|
$this.add(object);
|
|
}
|
|
var cacheName = "touch-controller-"+this.whichHand;
|
|
var controllerObject = cache[cacheName];
|
|
if(controllerObject===undefined) {
|
|
cache[cacheName] = [AddController];
|
|
var loader = new THREE.MTLLoader();
|
|
loader.setPath(options.resBase + 'touch-controller/');
|
|
loader.load("oculus-touch-controller-"+this.whichHand+".mtl",function(materials) {
|
|
materials.preload();
|
|
var objLoader = new THREE.OBJLoader();
|
|
objLoader.setMaterials( materials );
|
|
objLoader.setPath(options.resBase + 'touch-controller/');
|
|
objLoader.load( "oculus-touch-controller-"+$this.whichHand+".obj" , function ( controller ) {
|
|
var object = new THREE.Object3D();
|
|
controller.position.add(new THREE.Vector3(.008,0.03,-.03));
|
|
object.add(controller);
|
|
cache[cacheName].forEach(function(callback) {
|
|
callback(object.clone());
|
|
});
|
|
cache[cacheName] = object;
|
|
});
|
|
|
|
});
|
|
} else if(Array.isArray(controllerObject)) {
|
|
cache[cacheName].push(AddController);
|
|
} else
|
|
AddController(controllerObject.clone());
|
|
options.camera.parent.add(this);
|
|
}
|
|
|
|
this.getAxes = function() {
|
|
return axes;
|
|
}
|
|
|
|
if(/left/i.test(gamepad.id))
|
|
this.whichHand = "left";
|
|
if(/right/i.test(gamepad.id))
|
|
this.whichHand = "right";
|
|
|
|
if(/openvr/i.test(gamepad.id)) {
|
|
this.createViveControllerMesh();
|
|
buttonsIndexes = {
|
|
move: 0,
|
|
click: 1,
|
|
reset: 3
|
|
}
|
|
this.isVRPad = true;
|
|
} else if(/touch/i.test(gamepad.id)) {
|
|
this.createOculusTouchMesh();
|
|
buttonsIndexes = {
|
|
move: -1,
|
|
click: 1,
|
|
reset: 3
|
|
}
|
|
this.isVRPad = true;
|
|
} else if(gamepad.id=="simulated") {
|
|
this.createCrosshair();
|
|
this.createProgress();
|
|
this.alwaysDrag = true;
|
|
} else if(gamepad.id==/gear vr/i.test(gamepad.id)) {
|
|
this.createCrosshair();
|
|
buttonsIndexes = {
|
|
move: -1,
|
|
click: 1,
|
|
reset: 1
|
|
}
|
|
} else {
|
|
this.createCrosshair();
|
|
buttonsIndexes = {
|
|
move: -1,
|
|
click: 7,
|
|
reset: 1
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
VRGamepad.prototype = Object.create( THREE.Object3D.prototype );
|
|
VRGamepad.prototype.constructor = VRGamepad;
|
|
|
|
var knownGamepads = [];
|
|
|
|
function mapGamepads() {
|
|
var newGamepads = [];
|
|
if(typeof navigator.getGamepads=="function") {
|
|
var gamepadsList = navigator.getGamepads();
|
|
for(var i = 0;i<gamepadsList.length;i++) {
|
|
var gamepad = gamepadsList[i];
|
|
if(gamepad) {
|
|
var isNewGamepad = true;
|
|
for(var j=0;j<knownGamepads.length;j++) {
|
|
var knownGamepad = knownGamepads[j];
|
|
if(gamepad===knownGamepad.getGamepad()) {
|
|
newGamepads.push(knownGamepad);
|
|
knownGamepads.splice(j,1);
|
|
isNewGamepad = false;
|
|
break;
|
|
}
|
|
}
|
|
if(isNewGamepad)
|
|
newGamepads.push(new VRGamepad(gamepad));
|
|
}
|
|
}
|
|
}
|
|
if(newGamepads.length==0) {
|
|
var needSimulated = true;
|
|
for(var j=0;j<knownGamepads.length;j++) {
|
|
var knownGamepad = knownGamepads[j];
|
|
if(knownGamepad.getGamepad().id=="simulated") {
|
|
newGamepads.push(knownGamepad);
|
|
knownGamepads.splice(j,1);
|
|
needSimulated = false;
|
|
}
|
|
}
|
|
if(needSimulated)
|
|
newGamepads.push(new VRGamepad({
|
|
id: "simulated"
|
|
}));
|
|
}
|
|
knownGamepads.forEach(function(gamepad) {
|
|
gamepad.destroyGamepad();
|
|
});
|
|
knownGamepads = newGamepads;
|
|
harborpad = null;
|
|
var firstVRPad = null;
|
|
for(var i=0;i<knownGamepads.length;i++) {
|
|
var gamepad = knownGamepads[i];
|
|
if(gamepad.isVRPad) {
|
|
if(firstVRPad) {
|
|
harborpad = gamepad;
|
|
break;
|
|
} else
|
|
firstVRPad = gamepad;
|
|
}
|
|
}
|
|
if(harborpad) {
|
|
if(harborpad.whichHand=="right" && firstVRPad.whichHand=="left") {
|
|
var tmpPad = harborpad;
|
|
harborpad = firstVRPad;
|
|
firstVRPad = tmpPad;
|
|
}
|
|
firstVRPad.pointerObject.scale.setX(.1);
|
|
firstVRPad.pointerObject.scale.setZ(.1);
|
|
} else if(firstVRPad) {
|
|
firstVRPad.pointerObject.scale.setX(1);
|
|
firstVRPad.pointerObject.scale.setZ(1);
|
|
}
|
|
|
|
}
|
|
|
|
this.getHarborPad = function() {
|
|
return harborpad;
|
|
}
|
|
|
|
this.update = function() {
|
|
mapGamepads();
|
|
knownGamepads.forEach(function(gamepad) {
|
|
gamepad.update();
|
|
});
|
|
}
|
|
|
|
this.clearAll = function() {
|
|
knownGamepads.forEach(function(gamepad) {
|
|
gamepad.destroyGamepad();
|
|
});
|
|
knownGamepads = [];
|
|
}
|
|
|
|
}
|