elation-engine
Version:
WebGL/WebVR engine written in Javascript
237 lines (216 loc) • 9.8 kB
JavaScript
elation.require(['engine.things.generic'], function() {
elation.component.add('engine.things.manipulator', function() {
this.postinit = function() {
elation.events.add(this, "mouseover,mouseout,mousedown", this);
this.defaultsize = 4;
this.opacities = [0.5, 0.8];
this.origin = new THREE.Vector3(0,0,0);
this.axes = {
x: new THREE.Vector3(1,0,0),
y: new THREE.Vector3(0,1,0),
z: new THREE.Vector3(0,0,1)
};
this.dragline = new THREE.Vector2();
this.projector = new THREE.Projector();
}
this.createObject3D = function() {
var obj = new THREE.Object3D();
var size = this.defaultsize;
//size = this.parent.objects['3d'].boundingBox;
//console.log('size is', size);
this.movehelper = {
x: this.getArrow(this.axes.x, this.origin, size*1.1, 0xaa0000),
y: this.getArrow(this.axes.y, this.origin, size*1.1, 0x00aa00),
z: this.getArrow(this.axes.z, this.origin, size*1.1, 0x0000aa)
};
this.rotatehelper = {
x: this.getRing(this.axes.x, this.origin, size, 0xaa0000),
y: this.getRing(this.axes.y, this.origin, size, 0x00aa00),
z: this.getRing(this.axes.z, this.origin, size, 0x0000aa)
};
for (var k in this.axes) {
obj.add(this.movehelper[k]);
obj.add(this.rotatehelper[k]);
}
return obj;
}
this.getArrow = function(dir, origin, size, color) {
var mat = new THREE.MeshBasicMaterial({color: color, opacity: this.opacities[0], transparent: true, depthWrite: false, depthTest: false});
//var mat = elation.engine.materials.getShaderMaterial("manipulator", {color: new THREE.Color(color), opacity: this.opacities[0]});
var arrowgeo = new THREE.Geometry();
var cone = new THREE.Mesh(new THREE.CylinderGeometry( 0, 0.05 * size, 0.25 * size, 5, 1 ), mat);
cone.position.set(0,1.125 * size,0);
THREE.GeometryUtils.merge(arrowgeo, cone);
var shaft = new THREE.Mesh(new THREE.CylinderGeometry( .005 * size, .005 * size, size, 5, 1 ), mat);
shaft.position.set(0, size/2, 0);
THREE.GeometryUtils.merge(arrowgeo, shaft);
var rotation = new THREE.Matrix4();
if ( dir.y > 0.999 ) {
rotation.makeRotationFromEuler(new THREE.Euler( 0, 0, 0 ));
} else if ( dir.y < -0.999 ) {
rotation.makeRotationFromEuler(new THREE.Euler( Math.PI, 0, 0 ));
} else {
var axis = new THREE.Vector3( dir.z, 0, -dir.x ).normalize();
var radians = Math.acos( dir.y );
var quaternion = new THREE.Quaternion().setFromAxisAngle( axis, radians );
rotation.makeRotationFromQuaternion( quaternion );
}
arrowgeo.applyMatrix(rotation);
return new THREE.Mesh(arrowgeo, mat);
}
this.getRing = function(dir, origin, size, color) {
var ringgeo = new THREE.TorusGeometry(size, .01 * size, 8, 32, Math.PI*2);
var ringmat = new THREE.MeshBasicMaterial({color: color, opacity: this.opacities[0], transparent: true, depthWrite: false, depthTest: false});
//var ringmat = elation.engine.materials.getShaderMaterial("manipulator", {color: new THREE.Color(color), opacity: this.opacities[0]});
var ring = new THREE.Mesh(ringgeo, ringmat);
// FIXME - this could be made to work with arbitrary axes...
if (dir == this.axes.x) {
ring.rotation.y = Math.PI/2;
} else if (dir == this.axes.y) {
ring.rotation.x = Math.PI/2;
}
return ring;
}
this.mouseover = function(ev) {
if (ev.data && ev.data.object && ev.data.object.material) {
ev.data.object.material.opacity = this.opacities[1];
}
}
this.mouseout = function(ev) {
if (ev.data && ev.data.object && ev.data.object.material) {
ev.data.object.material.opacity = this.opacities[0];
}
}
this.mousedown = function(ev) {
ev.stopPropagation();
ev.preventDefault();
elation.events.add(window, 'mousemove,mouseup', this);
var mesh = ev.data.object;
if (!this.camera) this.camera = this.engine.systems.render.views['main'].camera; // FIXME - ugly;
this.engine.systems.admin.setCameraActive(false); // disable camera controls
this.parent.objects.dynamics.mass = 0;
var action = false;
if (mesh == this.movehelper.x) action = ['position', 'x'];
if (mesh == this.movehelper.y) action = ['position', 'y'];
if (mesh == this.movehelper.z) action = ['position', 'z'];
if (mesh == this.rotatehelper.x) action = ['orientation', 'x'];
if (mesh == this.rotatehelper.y) action = ['orientation', 'y'];
if (mesh == this.rotatehelper.z) action = ['orientation', 'z'];
this.action = action;
if (action) {
this.dragstartpos = [ev.clientX, ev.clientY];
//console.log('Start ' + action[0] + ': ' + action[1]);
switch (action[0]) {
case 'position':
// Project the start and end point of this axis into screen space, and store the drag line for reference during movement
var start2d = this.projector.projectVector(this.localToWorld(this.origin.clone()), this.camera);
var end2d = this.projector.projectVector(this.localToWorld(this.axes[action[1]].clone()), this.camera);
this.dragline.set(end2d.x - start2d.x, end2d.y - start2d.y).normalize();
elation.events.fire({type: 'thing_drag_start', element: this.parent});
break;
case 'orientation':
// Calculate the tangent vector at the point on the ring where the user clicked, and use that for the drag line
var center3d = this.localToWorld(this.origin.clone());
var point = ev.data.point;
var radial = point.clone().sub(center3d);
var tangent = radial.cross(this.localToWorld(this.axes[action[1]].clone())).normalize();
var start2d = this.projector.projectVector(point.clone(), this.camera);
var end2d = this.projector.projectVector(point.clone().add(tangent), this.camera);
this.dragline.set(end2d.x - start2d.x, end2d.y - start2d.y).normalize();
elation.events.fire({type: 'thing_rotate_start', element: this.parent});
break;
}
} else {
console.log('unknown action:', ev);
}
}
this.mousemove = function(ev) {
ev.stopPropagation();
ev.preventDefault();
if (this.action) {
var dragdiff = new THREE.Vector2(ev.clientX - this.dragstartpos[0], this.dragstartpos[1] - ev.clientY);
if (ev.shiftKey) {
dragdiff.x *= .1;
dragdiff.y *= .1;
}
switch (this.action[0]) {
case 'position':
// project the dragged vector onto the line formed by the axis to determine movement amount
// FIXME - speed should scale based on distance to the object, but the formula below isn't 100% correct
var camerapos = new THREE.Vector3().getPositionFromMatrix(this.camera.matrixWorld);
var dist = this.localToWorld(this.origin.clone()).sub(camerapos);
var move = new THREE.Vector3();
move[this.action[1]] = dragdiff.dot(this.dragline) / Math.log(dist.length());
//this.parent.properties.position.copy(this.localToWorld(move));
var mat = this.parent.objects['3d'].matrix;//new THREE.Matrix4().getInverse(this.parent.matrix);
move.applyMatrix4(mat);
this.parent.properties.position.copy(move);
elation.events.fire({type: 'thing_drag_move', element: this.parent});
break;
case 'orientation':
// FIXME - for some axes and camera locations, the rotations seem to get reversed...
var euler = new THREE.Euler();
euler[this.action[1]] = -this.dragline.dot(dragdiff) * Math.PI/180;
var quat = new THREE.Quaternion().setFromEuler(euler);
this.parent.properties.orientation.multiply(quat);
elation.events.fire({type: 'thing_rotate_move', element: this.parent});
break;
}
this.dragstartpos = [ev.clientX, ev.clientY];
}
}
this.mouseup = function(ev) {
elation.events.remove(window, 'mousemove,mouseup', this);
switch (this.action[0]) {
case 'position':
elation.events.fire({type: 'thing_drag_end', element: this.parent});
break;
case 'orientation':
elation.events.fire({type: 'thing_rotate_end', element: this.parent});
break;
}
ev.stopPropagation();
ev.preventDefault();
this.engine.systems.admin.setCameraActive(true); // re-enable camera controls
this.parent.objects.dynamics.mass = this.parent.properties.mass;
}
this.click = function(ev) {
//ev.stopPropagation();
}
}, elation.engine.things.generic);
/*
elation.engine.materials.addChunk("manipulator", {
uniforms: {
"color": { type: "c", value: new THREE.Color(0xcccccc) },
"opacity": { type: "f", value: 1.0 },
},
vertex_pars: [
"uniform vec3 color;",
"uniform float opacity;",
].join('\n'),
vertex: [
"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
"gl_Position = projectionMatrix * mvPosition;"
].join('\n'),
fragment_pars: [
"uniform vec3 color;",
"uniform float opacity;",
].join('\n'),
fragment: [
"gl_FragColor = vec4( color, opacity);",
].join('\n'),
});
elation.engine.materials.buildShader("manipulator", {
uniforms: [
'common',
'manipulator'
],
chunks_vertex: [
'manipulator',
],
chunks_fragment: [
'manipulator',
]
});
*/
});