elation-engine
Version:
WebGL/WebVR engine written in Javascript
751 lines (712 loc) • 33.7 kB
JavaScript
elation.require(['engine.things.generic', 'engine.things.camera', 'engine.things.label2d', 'engine.things.objecttracker'], function() {
elation.component.add('engine.things.player', function() {
this.targetrange = 8;
this.postinit = function() {
this.defineProperties({
height: { type: 'float', default: 1.9 },
fatness: { type: 'float', default: .25 },
mass: { type: 'float', default: 10.0 },
movestrength: { type: 'float', default: 200.0 },
movespeed: { type: 'float', default: 1.8 },
runstrength: { type: 'float', default: 250.0 },
runspeed: { type: 'float', default: 5.4 },
crouchspeed: { type: 'float', default: 150.0 },
turnspeed: { type: 'float', default: Math.PI/2 },
jumpstrength: { type: 'float', default: 300.0 },
jumptime: { type: 'float', default: 150 },
movefriction: { type: 'float', default: 4.0 },
defaultplayer: { type: 'boolean', default: true },
startposition: { type: 'vector3', default: new THREE.Vector3() },
startorientation: { type: 'quaternion', default: new THREE.Quaternion() },
startcameraorientation: { type: 'quaternion', default: new THREE.Quaternion() },
walking: { type: 'boolean', default: true },
running: { type: 'boolean', default: false },
flying: { type: 'boolean', default: false, set: function(key, value) { this.properties.flying = value; this.toggle_flying(value); }},
dynamicfriction:{ type: 'float', default: 2.0, comment: 'Dynamic friction inherent to this object' },
staticfriction: { type: 'float', default: 1.9, comment: 'Static friction inherent to this object' },
fov: { type: 'float', default: 75, set: this.updateCamera },
turnhead: { type: 'boolean', default: false, set: this.updateHeadLock },
});
this.controlstate = this.engine.systems.controls.addContext('player', {
'move_forward': ['keyboard_w', elation.bind(this, this.updateControls)],
'move_backward': ['keyboard_s,gamepad_any_axis_1', elation.bind(this, this.updateControls)],
'move_left': ['keyboard_a', elation.bind(this, this.updateControls)],
'move_right': ['keyboard_d,gamepad_any_axis_0', elation.bind(this, this.updateControls)],
'move_up': ['keyboard_r', elation.bind(this, this.updateControls)],
'move_down': ['keyboard_f', elation.bind(this, this.updateControls)],
'turn_left': ['keyboard_left', elation.bind(this, this.updateControls)],
'turn_right': ['keyboard_right,gamepad_any_axis_2', elation.bind(this, this.updateControls)],
//'mouse_turn': ['mouse_delta_x', elation.bind(this, this.updateMouseControls)],
//'mouse_pitch': ['mouse_delta_y', elation.bind(this, this.updateMouseControls)],
'mouse_look': ['mouse_delta', elation.bind(this, this.updateMouseControls)],
'look_up': ['keyboard_up', elation.bind(this, this.updateControls)],
'look_down': ['keyboard_down,gamepad_any_axis_3', elation.bind(this, this.updateControls)],
'run': ['keyboard_shift,gamepad_any_button_10', elation.bind(this, this.updateControls)],
//'crouch': ['keyboard_c', elation.bind(this, this.updateControls)],
'jump': ['keyboard_space,gamepad_any_button_1', elation.bind(this, this.handleJump)],
//'toss': ['keyboard_space,gamepad_any_button_0,mouse_button_0', elation.bind(this, this.toss)],
//'toss_cube': ['keyboard_shift_space,gamepad_any_button_1', elation.bind(this, this.toss_cube)],
//'use': ['keyboard_e,gamepad_any_button_0,mouse_button_0', elation.bind(this, this.handleUse)],
'toggle_flying': ['keyboard_g', (ev) => { if (ev.value == 1) this.toggle_flying(); }],
'reset_position': ['keyboard_backspace', elation.bind(this, this.reset_position)],
//'pointerlock': ['mouse_0', elation.bind(this, this.updateControls)],
});
// Separate HMD context so it can remain active when player controls are disabled
this.hmdstate = this.engine.systems.controls.addContext('playerhmd', {
'hmd': ['hmd_0', elation.bind(this, this.refresh)],
'orientation': ['orientation', elation.bind(this, this.refresh)],
});
this.mousedelta = [0,0];
this.moveVector = new THREE.Vector3();
this.turnVector = new THREE.Euler(0, 0, 0);
this.lookVector = new THREE.Euler(0, 0, 0);
//this.engine.systems.controls.activateContext('player');
this.engine.systems.controls.activateContext('playerhmd');
this.charging = false;
this.usegravity = false;
this.flying = true;
this.lights = [];
this.lightnum = 0;
this.target = false;
this.addTag('player');
this.viewfrustum = new THREE.Frustum();
this.viewmatrix = new THREE.Matrix4();
this.gravityVector = new THREE.Vector3();
this.lastjumptime = 0;
elation.events.add(this.engine, 'engine_frame', elation.bind(this, this.engine_frame));
elation.events.add(this.engine, 'engine_frame', elation.bind(this, this.handleTargeting));
elation.events.add(this, 'thing_create', elation.bind(this, this.handleCreate));
elation.events.add(document, "pointerlockchange", elation.bind(this, this.handlePointerLockChange));
}
this.createObjectDOM = function() {
//this.strengthmeter = elation.ui.progressbar(null, elation.html.create({append: document.body, classname: 'player_strengthmeter'}), {orientation: 'vertical'});
}
this.getCharge = function() {
return Math.max(0, Math.min(100, Math.pow((new Date().getTime() - this.charging) / 1000 * 5, 2)));
}
this.updateHUD = function(ev) {
if (this.charging !== false) {
var charge = this.getCharge();
this.strengthmeter.set(charge);
} else if (this.strengthmeter.value != 0) {
this.strengthmeter.set(0);
}
}
this.toss = function(ev) {
if (this.holding) {
if (ev.value == 1) {
this.charging = new Date().getTime();
} else if (this.charging) {
var bounds = this.holding.getBoundingSphere();
var campos = this.camera.localToWorld(new THREE.Vector3(0,0,-bounds.radius));
var camdir = this.camera.localToWorld(new THREE.Vector3(0,0,-2)).sub(campos).normalize();
var velocity = 0 + this.getCharge() / 10;
camdir.multiplyScalar(velocity);
camdir.add(this.objects.dynamics.velocity);
//console.log('pew!', velocity);
//var foo = this.spawn('ball', 'ball_' + Math.round(Math.random() * 100000), { radius: .125, mass: 1, position: campos, velocity: camdir, lifetime: 30, gravity: true, player_id: this.properties.player_id, tags: 'local_sync' }, true);
//var foo = this.spawn('ball', 'ball_' + Math.round(Math.random() * 100000), { radius: .08, mass: 1, position: campos, velocity: camdir, lifetime: 30, gravity: this.usegravity, player_id: this.properties.player_id, tags: 'local_sync' }, true);
//foo.addTag('enemy');
this.holding.reparent(this.engine.client.world);
//this.holding.properties.position.copy(campos);
this.holding.objects.dynamics.setVelocity(camdir);
console.log('throw it!', this.holding, campos, camdir);
this.holding = false;
this.charging = false;
}
} else {
if (ev.value == 1) {
this.charging = new Date().getTime();
} else if (this.charging) {
var campos = this.camera.localToWorld(new THREE.Vector3(0,0,-1));
var camdir = this.camera.localToWorld(new THREE.Vector3(0,0,-2)).sub(campos).normalize();
var velocity = 1 + this.getCharge() / 4;
camdir.multiplyScalar(velocity);
camdir.add(this.objects.dynamics.velocity);
var foo = this.spawn('ball', 'ball_' + Math.round(Math.random() * 100000), { radius: .125, mass: 1, position: campos, velocity: camdir, lifetime: 120, gravity: false, player_id: this.properties.player_id, tags: 'local_sync' }, true);
}
}
}
this.toss_cube = function(ev) {
if (ev.value == 1) {
this.charging = new Date().getTime();
} else {
var cam = this.engine.systems.render.views['main'].camera;
var campos = cam.localToWorld(new THREE.Vector3(0,0,-2));
var camdir = cam.localToWorld(new THREE.Vector3(0,0,-3)).sub(campos).normalize();
var velocity = 5 + this.getCharge();
camdir.multiplyScalar(velocity);
camdir.add(this.objects.dynamics.velocity);
//console.log('pew!', velocity);
var foo = this.spawn('crate', 'crate_' + Math.round(Math.random() * 100000), { mass: 1, position: campos, velocity: camdir, angular: this.getspin(), lifetime: 30, gravity: this.usegravity }, true);
this.charging = false;
}
}
this.toggle_flying = function(value) {
if (value === undefined) value = !this.flying;
this.flying = value;
this.usegravity = !this.flying;
if (this.gravityForce) {
this.gravityForce.update(this.gravityVector.set(0,this.usegravity * -9.8 , 0));
}
//console.log('toggle flying', this.flying, this.gravityForce);
}
this.reset_position = function(ev) {
if (!ev || ev.value == 1) {
this.properties.position.copy(this.properties.startposition);
this.properties.orientation.copy(this.properties.startorientation);
this.head.properties.orientation.copy(this.properties.startcameraorientation);
this.properties.velocity.set(0,0,0);
this.objects.dynamics.angular.set(0,0,0);
this.engine.systems.controls.calibrateHMDs();
this.refresh();
}
}
this.getspin = function() {
//return new THREE.Vector3();
return new THREE.Vector3((Math.random() - .5) * 4 * Math.PI, (Math.random() - .5) * 4 * Math.PI, (Math.random() - .5) * 4 * Math.PI);
}
this.createObject3D = function() {
this.objects['3d'] = new THREE.Object3D();
this.ears = new THREE.Object3D();
//this.camera.rotation.set(-Math.PI/16, 0, 0);
return this.objects['3d'];
}
this.createChildren = function() {
// place camera at head height
this.headconstraint = this.head.objects.dynamics.addConstraint('axis', { axis: new THREE.Vector3(1,0,0), min: -Math.PI/2, max: Math.PI/2 });
this.neckconstraint = this.neck.objects.dynamics.addConstraint('axis', { axis: new THREE.Vector3(0,1,0), min: 3/4 * -Math.PI/2, max: 3/4 * Math.PI/2 });
this.reset_position();
}
this.createForces = function() {
this.frictionForce = this.objects.dynamics.addForce("friction", this.properties.movefriction);
this.gravityForce = this.objects.dynamics.addForce("gravity", this.gravityVector);
this.moveForce = this.objects.dynamics.addForce("static", {});
this.jumpForce = this.objects.dynamics.addForce("static", {});
this.objects.dynamics.restitution = 0;
/*
this.objects.dynamics.linearDamping = 1.5;
this.objects.dynamics.angularDamping = .1;
this.objects.dynamics.material.staticfriction = 1.1;
this.objects.dynamics.material.dynamicfriction = 1;
*/
//this.objects.dynamics.setCollider('sphere', {radius: this.properties.fatness, length: this.height, /*offset: new THREE.Vector3(0, this.fatness, 0) */});
this.objects.dynamics.addConstraint('axis', { axis: new THREE.Vector3(0,1,0) });
// FIXME - should be in createChildren
this.createBodyParts();
// FIXME - the object tracker needs some work. Some systems report tracked objects relative to the world, and some relative to the head
// The hardcoded offset is also specific to my own personal set-up, and helps to keep leap motion and tracked controllers in sync
this.tracker = this.head.spawn('objecttracker', null, {player: this, position: [0, -.05, -.185]});
this.camera = this.head.spawn('camera', this.name + '_camera', { position: [0,0,0], mass: 0.1, player_id: this.properties.player_id } );
this.head.objects['3d'].add(this.ears);
//var camhelper = new THREE.CameraHelper(this.camera.camera);
//this.engine.systems.world.scene['world-3d'].add(camhelper);
}
this.createBodyParts = function() {
this.torso = this.spawn('generic', this.properties.player_id + '_torso', {
'position': [0,0,0]
});
this.shoulders = this.torso.spawn('generic', this.properties.player_id + '_shoulders', {
'position': [0,0.3,-0.2]
});
this.neck = this.torso.spawn('generic', this.properties.player_id + '_neck', {
'position': [0,1.45,0]
});
this.head = this.neck.spawn('generic', this.properties.player_id + '_head', {
'position': [0,.2,0],
'mass': 1
});
/*
this.placeholder_body = new THREE.Mesh(new THREE.CylinderGeometry(this.fatness, this.fatness, this.height), new THREE.MeshPhongMaterial({color: 0xcccccc, transparent: true, opacity: .5}));
this.placeholder_body.position.y = this.height / 2;
this.placeholder_body.layers.set(10);
this.objects['3d'].add(this.placeholder_body);
this.vrcalibrate = new THREE.Object3D();
this.vrposetarget = new THREE.Object3D();
let vrposedebug = new THREE.Mesh(new THREE.CylinderGeometry(0, 1, 2), new THREE.MeshPhongMaterial({color: 0xffcccc, transparent: true, opacity: .5}));
vrposedebug.position.z = -1;
vrposedebug.rotation.x = Math.PI/2;
this.vrcalibrate.add(this.vrposetarget);
//this.vrposetarget.add(vrposedebug);
vrposedebug.layers.set(10);
this.objects['3d'].add(this.vrcalibrate);
*/
/*
let renderer = this.engine.systems.render.renderer;
if (renderer.vr && renderer.vr.setPoseTarget) {
renderer.vr.setPoseTarget(this.vrposetarget);
}
*/
}
this.getGroundHeight = function() {
}
this.enable = function() {
var controls = this.engine.systems.controls;
this.gravityForce.update(this.gravityVector.set(0,this.usegravity * -9.8 , 0));
controls.activateContext('player');
if (this.engine.systems.render.views.main) {
//this.engine.systems.render.views.main.disablePicking();
}
controls.enablePointerLock(true);
this.controlstate._reset();
this.lookVector.set(0,0,0);
this.turnVector.set(0,0,0);
this.enableuse = false;
this.enabled = true;
//controls.requestPointerLock();
// FIXME - quick hack to ensure we don't refresh before everything is initialized
if (this.objects.dynamics) {
this.refresh();
}
}
this.disable = function() {
var controls = this.engine.systems.controls;
controls.deactivateContext('player');
controls.releasePointerLock();
if (this.engine.systems.render.views.main) {
//this.engine.systems.render.views.main.enablePicking();
}
this.enableuse = false;
if (this.objects.dynamics) {
this.moveForce.update(this.moveVector.set(0,0,0));
this.gravityForce.update(this.gravityVector.set(0,0,0));
this.objects.dynamics.angular.set(0,0,0);
this.objects.dynamics.velocity.set(0,0,0);
this.objects.dynamics.updateState();
this.head.objects.dynamics.velocity.set(0,0,0);
this.head.objects.dynamics.angular.set(0,0,0);
this.head.objects.dynamics.updateState();
}
this.lookVector.set(0,0,0);
this.turnVector.set(0,0,0);
this.hideUseDialog();
this.controlstate._reset();
this.enabled = false;
this.refresh();
}
this.engine_frame = (function() {
var _dir = new THREE.Euler(); // Closure scratch variable
var _moveforce = new THREE.Vector3();
return function(ev) {
if (this.camera && (this.enabled || (this.hmdstate && this.hmdstate.hmd))) {
var diff = ev.data.delta;
fps = Math.max(1/diff, 20);
this.moveVector.x = (this.controlstate.move_right - this.controlstate.move_left);
this.moveVector.y = (this.controlstate.move_up - this.controlstate.move_down);
this.moveVector.z = -(this.controlstate.move_forward - this.controlstate.move_backward);
this.turnVector.y = (this.controlstate.turn_left - this.controlstate.turn_right) * this.properties.turnspeed;
this.lookVector.x = (this.controlstate.look_up - this.controlstate.look_down) * this.properties.turnspeed;
if (this.controlstate.crouch) {
if (this.flying) {
this.moveVector.y -= 1;
} else {
//this.head.properties.position.y = this.properties.height * .4 - this.properties.fatness;
}
} else {
if (!this.flying) {
//this.head.properties.position.y = this.properties.height * .8 - this.properties.fatness;
}
}
if (this.moveForce) {
var moveSpeed = Math.min(1.0, this.moveVector.length());
//if (moveSpeed !== 0) debugger;
var dumbhack = false;
if (this.flying || this.canJump()) {
this.frictionForce.update(this.properties.movefriction);
if (this.controlstate['jump']) {
this.jumpForce.update(new THREE.Vector3(0, this.jumpstrength, 0));
//console.log('jump up!', this.jumpForce.force.toArray());
setTimeout(elation.bind(this, function() {
this.jumpForce.update(new THREE.Vector3(0, 0, 0));
}), this.jumptime);
}
var velsq = this.velocity.lengthSq();
if (this.controlstate.crouch) {
moveSpeed *= this.crouchspeed;
} else if (this.controlstate.run) {
moveSpeed *= this.runstrength;
} else {
moveSpeed *= this.movestrength;
}
if (moveSpeed == 0 && velsq > 0) {
// The player isn't actively moving via control input, so apply opposing force to stop us
//this.objects.dynamics.worldToLocalDir(this.moveVector.copy(this.velocity).negate());
//moveSpeed = Math.min(this.moveVector.length(), 1) * this.movestrength;
//this.moveVector.normalize();
dumbhack = true;
}
} else {
this.frictionForce.update(0);
//this.moveVector.set(0,0,0);
moveSpeed *= this.movestrength / 32;
}
_moveforce.copy(this.moveVector).normalize().multiplyScalar(moveSpeed);
if (this.flying && !dumbhack) {
if (this.vrdevice && this.vrdevice.isPresenting) {
this.head.properties.orientation.copy(this.engine.systems.render.views.main.effects.default.camera.quaternion);
}
_moveforce.applyQuaternion(this.head.properties.orientation);
}
this.moveForce.update(_moveforce);
this.objects.dynamics.setAngularVelocity(this.turnVector);
this.head.objects.dynamics.setAngularVelocity(this.lookVector);
//this.head.objects.dynamics.updateState();
//this.neck.refresh();
}
if (this.headconstraint) this.headconstraint.enabled = (!this.vrdevice || !this.vrdevice.isPresenting);
}
//this.handleTargeting();
//this.refresh();
//elation.events.fire({type: 'thing_change', element: this});
// Store the player's current view frustum so we can do visibility testing in scripts
this.camera.camera.updateProjectionMatrix(); // FIXME - this should only be needed if camera parameters change
this.viewfrustum.setFromProjectionMatrix(this.viewmatrix.multiplyMatrices(this.camera.camera.projectionMatrix, this.camera.camera.matrixWorldInverse));
this.objects.dynamics.gravity = !this.flying;
if (ev.data.pose) {
this.updateXR(ev.data.pose, ev.data.xrspace);
}
if (this.headconstraint) {
if (this.engine.client.xrsession) {
this.headconstraint.enabled = false;
} else {
this.headconstraint.enabled = true;
}
}
}
})();
this.updateHMD = (function() {
// closure scratch vars
var standingMatrix = new THREE.Matrix4();
return function(vrdevice, camera) {
var hmd = this.hmdstate.hmd;
if (vrdevice && vrdevice.stageParameters) {
//this.stage.scale.set(vrdevice.stageParameters.sizeX, .1, vrdevice.stageParameters.sizeZ);
}
if (vrdevice && vrdevice.isPresenting) {
var pose = false;
if (!this.framedata) {
this.framedata = (vrdevice.isPolyfilled ? new WebVRPolyfillFrameData() : new VRFrameData());
}
if (vrdevice.getFrameData && this.framedata) {
if (vrdevice.getFrameData(this.framedata)) {
pose = this.framedata.pose;
}
} else if (vrdevice.getPose) {
pose = vrdevice.getPose();
}
if (pose) this.hmdstate.hmd = pose;
this.vrdevice = vrdevice;
if (this.headconstraint) this.headconstraint.enabled = false;
if (pose.position && !pose.position.includes(NaN)) {
var pos = this.neck.objects.dynamics.position;
pos.fromArray(pose.position);
//pos.y += this.properties.height * .8 - this.properties.fatness;
}
if (pose.linearVelocity && !pose.linearVelocity.includes(NaN)) {
this.head.objects.dynamics.velocity.fromArray(pose.linearVelocity);
} else {
this.head.objects.dynamics.velocity.set(0,0,0);
}
var o = pose.orientation;
// FIXME - why am I getting NaN / Infinity values here? This makes no sense.
if (o && !o.includes(NaN) && !o.includes(Infinity) && !o.includes(-Infinity)) {
this.head.objects.dynamics.orientation.fromArray(o);
}
if (pose.angularVelocity) {
//this.head.objects.dynamics.angular.fromArray(hmd.angularVelocity);
}
this.waspresentingvr = true;
this.head.objects.dynamics.updateState();
this.refresh();
} else {
if (this.headconstraint) this.headconstraint.enabled = true;
if (this.waspresentingvr) {
this.resetHead();
this.waspresentingvr = false;
}
}
var view = this.engine.systems.render.views.main;
if (view.size[0] == 0 || view.size[1] == 0) {
view.getsize();
}
//view.mousepos = [view.size[0] / 2, view.size[1] / 2, 0];
view.pickingactive = true;
}
})();
this.updateXR = function(framedata, xrspace) {
if (framedata && framedata.getViewerPose && xrspace) {
let pose = framedata.getViewerPose(xrspace);
if (!pose) return;
let p = pose.transform.position;
var o = pose.transform.orientation;
if (p) {
var pos = this.neck.objects.dynamics.position;
//pos.copy(p);
//pos.y += this.properties.height * .8 - this.properties.fatness;
}
if (o) {
//this.head.objects.dynamics.orientation.copy(o);
}
this.waspresentingvr = true;
this.head.objects.dynamics.updateState();
this.refresh();
}
}
this.updateControls = function() {
}
this.updateMouseControls = (function() {
var angular = new THREE.Vector3(),
tmpquat = new THREE.Quaternion();
return function(ev, force) {
if (this.engine.systems.controls.pointerLockActive || force) {
var mouselook = ev.data.mouse_look;
var changed = false;
if (mouselook[0]) {
angular.set(0, -mouselook[0] * this.properties.turnspeed / 60, 0);
var theta = angular.length();
angular.divideScalar(theta);
tmpquat.setFromAxisAngle(angular, theta);
if (this.turnhead) {
this.neck.properties.orientation.multiply(tmpquat);
} else {
this.properties.orientation.multiply(tmpquat);
this.neck.properties.orientation.set(0,0,0,1);
}
ev.data.mouse_turn = 0;
ev.data.mouse_look[0] = 0;
changed = true;
}
if (mouselook[1]) {
angular.set(-mouselook[1] * this.properties.turnspeed / 60, 0, 0)
theta = angular.length();
angular.divideScalar(theta);
tmpquat.setFromAxisAngle(angular, theta);
this.head.properties.orientation.multiply(tmpquat);
this.head.refresh();
ev.data.mouse_pitch = 0;
ev.data.mouse_look[1] = 0;
changed = true;
}
if (changed) {
this.refresh();
}
}
};
})();
this.handleCreate = function(ev) {
if (this.properties.defaultplayer) {
this.engine.client.setActiveThing(this);
//this.enable();
this.engine.systems.controls.enablePointerLock(true);
}
}
this.handleTargeting = function() {
if (this.enableuse) {
var targetinfo = this.getUsableTarget('usable');
if (targetinfo) {
var target = this.getThingByObject(targetinfo.object);
if (target !== this.target) {
this.setUseTarget(target);
}
} else if (this.target != false || this.distanceTo(this.target) > this.targetrange) {
this.setUseTarget(false);
}
}
}
this.setUseTarget = function(target) {
if (!target && this.target) {
// deselect current target
elation.events.fire({type: 'thing_use_blur', element: this.target, data: this});
this.target = target;
this.hideUseDialog();
} else if (target && !this.target) {
elation.events.fire({type: 'thing_use_focus', element: target, data: this});
this.target = target;
this.showUseDialog('play', target.properties.gamename); // FIXME - hardcoded for arcade games...
}
}
this.handleUse = function(ev) {
if (this.holding && !(this.target && this.target.canUse(this))) {
this.toss(ev);
} else {
if (ev.value == 1) {
this.activateUseTarget();
}
}
}
this.activateUseTarget = function() {
if (this.target && this.target.canUse(this)) {
elation.events.fire({type: 'thing_use_activate', element: this.target, data: this});
//this.disable(); // FIXME - temporary
}
}
this.getUsableTarget = (function() {
// closure scratch variables
var _pos = new THREE.Vector3(),
_dir = new THREE.Vector3(),
_caster = new THREE.Raycaster(_pos, _dir, .01, this.targetrange);
return function(tagname) {
if (!this.camera) return; // FIXME - hack to make sure we don't try to execute if our camera isn't initialized
var things = (tagname ? this.engine.getThingsByTag(tagname) : this.engine.getThingsByProperty('pickable', true));
if (things.length > 0) {
var objects = things.map(function(t) { return t.objects['3d']; });
// Get my position and direction in world space
var pos = this.camera.localToWorld(_pos.set(0,0,0));
var dir = this.camera.localToWorld(_dir.set(0,0,-1)).sub(pos).normalize();
var intersects = _caster.intersectObjects(objects, true);
if (intersects.length > 0) {
for (var i = 0; i < intersects.length; i++) {
if (intersects[i].object.visible)
return intersects[i];
}
}
}
return false;
}
}.bind(this))();
this.showUseDialog = function(verb, noun) {
var useable = this.target.canUse(this);
if (useable) {
var verb = useable.verb || 'use';
var noun = useable.noun || '';
var content = 'Press E or click to ' + verb + '\n' + noun;
if (!this.uselabel) {
this.uselabel = this.head.spawn('generic', null, {
position: [0,-.15,-.5],
scale: [0.5,0.5,0.5]
});
this.toplabel = this.uselabel.spawn('label2d', null, {
text: 'Press E or click to ' + verb,
color: 0x00ff00,
size: 16,
scale: [.5,.5,.5],
});
this.uselabelnoun = this.uselabel.spawn('label2d', null, {
position: [0,-0.1,0],
color: 0x000099,
text: noun,
size: 64
});
} else {
this.toplabel.setText('Press E or click to ' + verb);
this.uselabelnoun.setText(noun);
if (!this.uselabel.parent) {
this.head.add(this.uselabel);
}
}
}
/*
// FIXME - hack for arcade games
if (this.target && !this.target.properties.working) {
content = 'Sorry, ' + (this.target.properties.gamename || 'this machine') + ' is temporarily out of order!';
}
*/
}
this.handleJump = function(ev) {
var keydown = ev.value;
if (keydown) {
if (ev.timeStamp - this.lastjumptime < 250) {
console.log('toggle flying');
this.toggle_flying();
}
this.lastjumptime = ev.timeStamp;
} else {
this.jumpForce.update(new THREE.Vector3(0, 0, 0));
}
}
this.hideUseDialog = function() {
if (this.uselabel && this.uselabel.parent) {
this.uselabel.parent.remove(this.uselabel);
}
}
this.pickup = function(object, force) {
if (this.holding) {
//this.holding.reparent(this.engine.systems.world);
this.charging = 0.0001; // fixme - hardcoded value is silly here, this lets us just drop the item
this.toss({value: 0});
}
this.holding = object;
object.reparent(this.camera);
object.properties.position.set(0,-.075,-.15);
object.properties.velocity.set(0,0,0);
object.properties.angular.set(0,0,0);
object.properties.orientation.setFromEuler(new THREE.Euler(Math.PI/2,0,0)); // FIXME - probably not the best way to do this
}
this.canJump = (function() {
// Cast a ray downwards to see if we're touching a surface and can jump
var _pos = new THREE.Vector3(),
_dir = new THREE.Vector3(0, -1, 0),
_caster = new THREE.Raycaster(_pos, _dir, .01, 1 + this.fatness);
return function(tagname) {
if (!this.camera) return; // FIXME - hack to make sure we don't try to execute if our camera isn't initialized
//var things = this.engine.getThingsByProperty('pickable', true);
var objects = [this.engine.systems.world.scene['colliders']];
if (objects.length > 0) {
//var objects = things.map(function(t) { return t.objects['3d']; });
// Get my position and direction in world space
var pos = this.localToWorld(_pos.set(0,1,0));
var intersects = _caster.intersectObjects(objects, true);
if (intersects.length > 0) {
for (var i = 0; i < intersects.length; i++) {
if (intersects[i].distance <= 1 + this.fatness/* && (intersects[i].object.userData.thing && intersects[i].object.userData.thing !== this) */) {
return intersects[i];
}
}
}
}
return false;
}
})();
this.resetHead = function() {
this.head.objects.dynamics.position.set(0,0,0);
this.head.objects.dynamics.velocity.set(0,0,0);
this.head.objects.dynamics.angular.set(0,0,0);
this.head.objects.dynamics.orientation.set(0,0,0,1);
}
this.handlePointerLockChange = function(ev) {
if (document.pointerLockElement) {
this.enable();
if (document.pointerLockElement.tabIndex == -1) {
document.pointerLockElement.setAttribute( 'tabindex', 0 );
}
document.pointerLockElement.focus();
} else {
this.disable();
}
}
this.calibrateVR = function() {
//this.vrcalibrate.position.copy(this.vrposetarget.position).multiplyScalar(-1);
//this.vrcalibrate.quaternion.copy(this.vrposetarget.quaternion).conjugate();
//this.vrcalibrate.matrix.getInverse(this.vrposetarget.matrix);
//this.vrcalibrate.matrix.decompose(this.vrcalibrate.position, this.vrcalibrate.rotation, this.vrcalibrate.scale);
}
this.getViewFrustum = (function() {
let frustum = new THREE.Frustum(),
mat4 = new THREE.Matrix4();
return function() {
//let camera = this.camera.camera;
//let camera = this.engine.systems.render.views.main.actualcamera;
let camera = this.engine.systems.render.views.main.effects.default.camera; // FIXME - come on, really?
frustum.setFromProjectionMatrix( mat4.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) );
return frustum;
}
})();
this.updateCamera = function() {
if (this.camera) {
this.camera.fov = this.fov;
}
}
this.updateHeadLock = function() {
if (this.headconstraint && this.neckconstraint) {
if (this.turnhead) {
//this.headconstraint.enabled = false;
this.neckconstraint.min = 3/4 * -Math.PI/2;
this.neckconstraint.max = 3/4 * Math.PI/2;
} else {
this.neckconstraint.min = 0;
this.neckconstraint.max = 0;
}
}
}
}, elation.engine.things.generic);
});