Rotate an object like OrbitControls but only the object itself - three.js

We love three.js! And here is a page we built using it a few years ago.
https://www.jgprolock.com
We are in the process of revising the animations on this site.
Once the page loads, the user has the ability to drag and rotate the object. But it is really a trick. We are using orbit controls to rotate the camera around our scene, and thus our main object which is centered in the scene (positions x,y,z all equal to 0). If we did not place the object in the center, it starts to look uneven in its rotation as the camera now is rotating around a center that the object doesn't have.
In order to make it look like the object is on the left side, we ended up moving the canvas to the left and then we bring it back to the right or left as the animation continues after scrolling.
So, my question is .. does anyone have an example how to achieve this functionality just by rotating the actual object itself, instead of rotating the camera around the entire scene using the orbit controls plugin?
Or is there away to modify the orbit controls to rotate around an object and not the entire scene?

I've been searching for this for a while but right after asking this question I came across this link, which actually has an example of what we are trying to do.
https://jsfiddle.net/n6u6asza/1205/
The key to making this work as copied from the link: (although I am not 100% sure what this all means)
/* */
var isDragging = false;
var previousMousePosition = {
x: 0,
y: 0
};
$(renderer.domElement).on('mousedown', function(e) {
isDragging = true;
})
.on('mousemove', function(e) {
//console.log(e);
var deltaMove = {
x: e.offsetX-previousMousePosition.x,
y: e.offsetY-previousMousePosition.y
};
if(isDragging) {
var deltaRotationQuaternion = new three.Quaternion()
.setFromEuler(new three.Euler(
toRadians(deltaMove.y * 1),
toRadians(deltaMove.x * 1),
0,
'XYZ'
));
cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion);
}
previousMousePosition = {
x: e.offsetX,
y: e.offsetY
};
});
/* */

If you want an article on how to achieve this without the use of unnecessary jquery dependencies you can have a look here
This uses the eventListener to find a mousemove event whilst a mousedown event is occurring, and then passes the coordinates to a custom function.
var mouseDown = false,
mouseX = 0,
mouseY = 0;
var canvas = renderer.domElement
canvas.addEventListener('mousemove', function (evt) {
if (!mouseDown) {return}
//console.log('drag')
evt.preventDefault();
var deltaX = evt.clientX - mouseX,
deltaY = evt.clientY - mouseY;
mouseX = evt.clientX;
mouseY = evt.clientY;
// DO SOMETHING HERE WITH X and Y
object.rotation.x += deltaX
}, false);
canvas.addEventListener('mousedown', function (evt) {
evt.preventDefault();
mouseDown = true;
mouseX = evt.clientX;
mouseY = evt.clientY;
}, false);
canvas.addEventListener('mouseup', function (evt) {
evt.preventDefault();
mouseDown = false;
}, false);
}
But not that this will not work if you have OrbitControls or DragControls imported!

Related

Rotate webGL canvas to appear landscape-oriented on a portrait-oriented mobile phone

I’m using a-frame and trying to accomplish this task - force the canvas to be rendered as “landscape” when a mobile device is in portrait orientation (ie. device-width = 414px and device-height = 736px).
I have successfully accomplished this with the following steps
camera.aspect = 736 / 414;
camera.updateProjectionMatrix();
renderer.setSize(736, 414);
In css...
.a-canvas {
transform: rotate(90deg) translate(161px, 161px);
height: 414px !important;
width: 736px !important;
}
This all works great except for one major thing…I have 3D buttons in my scene and when I go to click them they don’t line up with the rotated canvas, instead their clickable position remains in the same place as before the canvas was rotated.
I’ve tried to set matrixWorldNeedsUpdate = true on the scene’s object3D along with updateWorldMatrix() with no luck. I tried calling refreshObjects on the raycaster with no luck. I tried rotating the scene and the camera with no luck.
I’m not sure what else to do. Any help would be greatly appreciated!
ANSWER:
Thanks to Marquizzo and gman for the help. Here's the updated a-frame source code (v1.0.4) to make the raycaster handle this forced landscape canvas properly
// line: 66884
onMouseMove: (function () {
var direction = new THREE.Vector3();
var mouse = new THREE.Vector2();
var origin = new THREE.Vector3();
var rayCasterConfig = {origin: origin, direction: direction};
return function (evt) {
var bounds = this.canvasBounds;
var camera = this.el.sceneEl.camera;
var left;
var point;
var top;
camera.parent.updateMatrixWorld();
// Calculate mouse position based on the canvas element
if (evt.type === 'touchmove' || evt.type === 'touchstart') {
// Track the first touch for simplicity.
point = evt.touches.item(0);
} else {
point = evt;
}
left = point.clientX - bounds.left;
top = point.clientY - bounds.top;
// mouse.x = (left / bounds.width) * 2 - 1;
// mouse.y = -(top / bounds.height) * 2 + 1;
// HAYDEN's CODE: flipping x and y coordinates to force landscape
// --------------------------------------------------------------
let clickX = (left / bounds.width) * 2 - 1;
let clickY = - (top / bounds.height) * 2 + 1;
mouse.x = -clickY;
mouse.y = clickX;
// --------------------------------------------------------------
origin.setFromMatrixPosition(camera.matrixWorld);
direction.set(mouse.x, mouse.y, 0.5).unproject(camera).sub(origin).normalize();
this.el.setAttribute('raycaster', rayCasterConfig);
if (evt.type === 'touchmove') { evt.preventDefault(); }
};
})(),
A-Frame uses a Raycaster internally to determine if the spot you clicked has hit an object. You can see in the Three.js documentation the raycaster needs the mouse x&y coordinates to determine where you clicked. Here's a working demo of that concept. However with your setup, x, y turns into -y, x.
I think you'll have to write your own Raycaster function to trigger on the click event instead of relying on the built-in AFrame functionality, and then swap the x&y values:
function onClick() {
let clickX = ( event.clientX / window.innerWidth ) * 2 - 1;
let clickY = - ( event.clientY / window.innerHeight ) * 2 + 1;
mouse.x = -clickY;
mouse.y = clickX;
// Then continue with raycaster.setFromCamera(), etc...
}
window.addEventListener( 'click', onClick, false );

Set events on canvas pushpin of Bing Map

I am facing one issue while setting event on Pushpin which was created by canvas. To set the environment please goto Bing Map - Pushpin Event Example. And copy below code in Javascript tab and run the example.
var map = new Microsoft.Maps.Map(document.getElementById('myMap'), {});
var pushpin = new Microsoft.Maps.Pushpin(map.getCenter(), {
icon: createRedArrow(110),
anchor: new Microsoft.Maps.Point(12, 12)
});
map.entities.push(pushpin);
function createRedArrow(heading) {
var c = document.createElement('canvas');
c.width = 24;
c.height = 24;
var ctx = c.getContext('2d');
// Offset the canvas such that we will rotate around the center of our arrow
ctx.translate(c.width * 0.5, c.height * 0.5);
// Rotate the canvas by the desired heading
ctx.rotate(heading * Math.PI / 180);
//Return the canvas offset back to it's original position
ctx.translate(-c.width * 0.5, -c.height * 0.5);
ctx.fillStyle = '#f00';
// Draw a path in the shape of an arrow.
ctx.beginPath();
ctx.moveTo(12, 0);
ctx.lineTo(5, 20);
ctx.lineTo(12, 15);
ctx.lineTo(19, 20);
ctx.lineTo(12, 0);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Generate the base64 image URL from the canvas.
return c.toDataURL();
}
// Binding the events
Microsoft.Maps.Events.addHandler(pushpin, 'click', function () { highlight('pushpinClick'); });
Microsoft.Maps.Events.addHandler(pushpin, 'dblclick', function () { highlight('pushpinDblclick'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mousedown', function () { highlight('pushpinMousedown'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mouseout', function () { highlight('pushpinMouseout'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mouseover', function () { highlight('pushpinMouseover'); });
Microsoft.Maps.Events.addHandler(pushpin, 'mouseup', function () { highlight('pushpinMouseup'); });
// Setting up the printout panel
document.getElementById('printoutPanel').innerHTML =
'<div id="pushpinClick">click</div><div id="pushpinDblclick">dblclick</div><div id="pushpinMousedown">mousedown</div><div id="pushpinMouseout">mouseout</div><div id="pushpinMouseover">mouseover</div><div id="pushpinMouseup">mouseup</div>';
function highlight(id) {
document.getElementById(id).style.background = 'LightGreen';
setTimeout(function () { document.getElementById(id).style.background = 'white'; }, 1000);
}
You can notice that all pushpin events are working on canvas's width/height (24/24) area as shown in below image
And as per my requirement events should only work on drawn part of canvas and also canvas w/h will be like 100. So, how do I achieve that?
Also, if two or more pushpins are nearer (see below image) and if both canvas are overlap to each other then how we can identify both pushpins are different for pushpin event?
Here, I have provided my requirement with Bing Map's exisiting example. But here is actual pushpin secnario.
Unfortunately this is a limitation in how pushpins are rendered in Bing Maps. There is an option to modify the click area to be round rather than square which will help a bit here. See the roundClickableArea pushpin option: https://learn.microsoft.com/en-us/bingmaps/v8-web-control/map-control-api/pushpinoptions-object

Animate point with paper.js

I’d like to rebuild this animation http://imgur.com/l5Vhswe in paper.js.
I already tried SVG animations (http://codepen.io/magglomag/pen/jrVwzy) but despite from the fact that they’ll be deprecated soon I was not able to move the two points asynchronously.
What I have so far is the shape and I know that I can animate with the onFrame event handler. But I have no clue how to say that the point should animate between the coordinates [43,168.7] and [43,35.3].
http://codepen.io/magglomag/pen/yaVXrr
var firstSegment = new Segment({
point: [109,3.7]
});
var secondSegment = new Segment({
point: [43,168.7]
});
var thirdSegment = new Segment({
point: [109,202.2]
});
var path = new Path({
segments: [firstSegment, secondSegment, thirdSegment],
fillColor: '#2dfd9a',
closed: true
});
secondSegment.onFrame = function(event) {
this.point = [43,35.3]
}
The error you are making is that you are trying to bind an handler to segment.onFrame event.
But only item.onFrame and view.onFrame are available.
In PaperScript context, you can even use a global onframe named function as a convenient way to animate things.
Here is a simple example demonstrating how a path segment can be animated.
// create a triangle
var triangle = new Path.RegularPolygon({
center: view.center,
sides: 3,
radius: 50,
fillColor: 'orange'
});
// store initial first point position
var initialPoint = triangle.firstSegment.point.clone();
// on frame
function onFrame(event) {
// use sine function as a convenient way to demonstrate animation
var newPoint = initialPoint + Math.sin(event.count * 0.05) * 30;
// update first point
triangle.firstSegment.point = newPoint;
}

zooming based on the position of cursor in mousewheel

I am currently working on a THREE.js project.While using trackball control zooming it is zooming relative to the center of the model.But i want to zoom according to the position of cursor with mouse wheel.Any one who is familiar please help me over this.
Thank you in advance.
In mousewheel add this code which helps you in zooming based on the cursor position and not relative to the model.
If you are using trackball controls,you could use that for panning and rotating.
So set trackball controls enabled to false in mousewheel.
renderer.domElement.addEventListener('mousewheel',
function (e) {
mousewheel(e);
},
false);
function mousewheel(event) {
trackBallControls.noZoom = true;
event.preventDefault();
var factor = 50;
var mX = (event.clientX / jQuery(container).width()) * 2 - 1;
var mY = -(event.clientY / jQuery(container).height()) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
//console.log(vector);
vector.unproject(camera);
vector.sub(camera.position);
if (event.deltaY < 0) {
camera.position.addVectors(camera.position,
vector.setLength(factor));
trackBallControls.target.addVectors(trackBallControls.target,
vector.setLength(factor));
camera.updateProjectionMatrix();
} else {
camera.position.subVectors(camera.position,
vector.setLength(factor));
trackBallControls.target.subVectors(trackBallControls.target,
vector.setLength(factor));
camera.updateProjectionMatrix();
}
}

Smoothing a line and creating a gradient

I'm making this for a class and I have managed to create a drawing app in which the stroke reacts to the speed of the mouse and the colour of the stroke changes depending on the angle.
Link here.
It looks really rough though, and I'd like to understand how I could smoothen the lines by connecting the strokes (so it would appear like a single stroke that is changing width) and if it would be possible to create a gradient, going from red to green. I tried to get some help from the teacher but we have approximately 10 minutes a week dedicated to getting individual assistance for our projects and it's quite hard to ask all these question and understand what is happening in the code...
Please note that I had a lot of help doing this. I previously did something similar using paper.js, but my teacher prefers me to use "pure" canvas for this. I have a background in webdesign, but it's far from programming, I just know markup language and working with html and css, occasionally using some jquery slider. So I'm entirely confused by even the simplest tutorials, I tried to follow this but I don't even understand WHERE to put everything and it just didn't work.
I'd be really glad if someone could give me some help with this... ELI5, please. I'm a fast learner but I'm still in confusion mode, overwhelmed by all these lines of code I don't (yet) understand, but would really like to.
Thank you in advance!
Several hints:
Set context.lineCap='round'. This rounds the beginning & end of each line. That rounding helps visually merge one line into the next line.
Limit your lineWidth to a small range. This makes your line visually flow better because there isn't a huge jump in size when the user makes rapid speed changes.
Here's refactored code and a Demo:
// cache a reference to the canvas element & its context
// because they are used often
var canvas=$('#canvas')[0];
var context = canvas.getContext('2d');
//full-screen
canvas.width = 500;
canvas.height = 500;
// style lines with rounded end-caps & rounded joins
context.lineJoin='round';
context.lineCap='round';
var mouseX = undefined;
var mouseY = undefined;
var mouseIsDown = false;
var PI2=Math.PI*2;
var speed=-1000;
(function(){Math.clamp=function(a,b,c){return Math.max(b,Math.min(c,a));}})();
onMouseDown = function( event ){
// tell the browser we're handling this event
event.preventDefault();
event.stopPropagation();
mouseIsDown = true;
}
onMouseMove = function( event ){
// tell the browser we're handling this event
event.preventDefault();
event.stopPropagation();
var previousMouseX = mouseX;
var previousMouseY = mouseY;
mouseX = event.pageX;
mouseY = event.pageY;
var dx = Math.abs( mouseX - previousMouseX );
var dy = Math.abs( mouseY - previousMouseY );
var speed = Math.sqrt( dx * dx + dy * dy );
// limit the min/max width of the line
speed=Math.clamp(speed,2,12);
var angle = 2 * Math.PI * Math.acos( dx / ( speed + 0.00001 ) );
if( angle < Math.PI ){
context.strokeStyle = 'rgb( 0, 255, 0 )';
}
else{
context.strokeStyle = 'rgb( 255, 0, 0 )';
}
if( mouseIsDown ){
context.lineWidth=speed;
context.beginPath();
context.moveTo( previousMouseX, previousMouseY );
context.lineTo( mouseX, mouseY );
context.stroke();
}
}
//drawing only is the mouse is down
onMouseUp = function( event ){
// tell the browser we're handling this event
event.preventDefault();
event.stopPropagation();
mouseIsDown = false;
}
$( '#canvas' ).on( 'mousedown', onMouseDown );
$( '#canvas' ).on( 'mousemove', onMouseMove );
$( '#canvas' ).on( 'mouseup', onMouseUp );
$( '#canvas' ).on( 'mouseout', onMouseUp );
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="canvas" width=500 height=500></canvas>
Applying a gradient is much harder. You will have to:
Save each mouse point
Interpolate points between each mouse point so that each new point is 1px distant from the previous.
Clear the canvas
Redraw a line between each mouse point with each line's strokeStyle changing according to your desired gradient.

Resources