I am trying to make an object chase another moving object and stop when it reaches the position of the object being chased. I have tried using TranslateonAxis as shown below
this.el.object3D.translateOnAxis(targetposition, distance);
to make the chaser reach the position of the target position (position of the object being chased) but the chaser object ends up moving in another direction altogether. I think the reason could be due to difference in world position and the local position of the objects.
Here is my code
<a-scene physics="gravity: 0">
<a-assets>
<a-asset-item id="boat" src="../images/models/surfboard/scene.gltf"></a-
asset-item>
<a-asset-item id="orca1" src="../images/models/orca/scene.gltf"></a-asset-
item>
</a-assets>
<a-entity position="0 1.8 0">
<a-camera id="camera" look-controls="enabled: false">
<a-entity id="boats" position="0 -2 -4" rotation="0 -90 0"
scale=".02 .02 .02" gltf-model="#boat" static-body></a-entity>
</a-camera>
</a-entity>
<a-entity id="orca" position="-1 0.7 -40" gltf-model="#orca1" static-body move></a-entity>
</a-scene>
I try to move the #orca towards the #boats (which can be moved using WASD) using the move component. Code for that -
AFRAME.registerComponent('move', {
schema: {
speed: { type: 'number', default: 2 }
},
tick: function(t, dt) {
var target = this.el.sceneEl.querySelector('#boats');
var vec3 = new THREE.Vector3();
var currentPosition = this.el.object3D.position;
target.object3D.getWorldPosition(vec3);
var distance = dt*this.data.speed / 100;
this.el.object3D.translateOnAxis(vec3, distance);
if (currentPosition.z > 30) {
this.el.setAttribute('position', {
z: -60
});
}
}
});
On a side note - I wish there were better explanatory material available explaining worldToLocal and LocaltoWorld methods.
1) You need to get the boat's position by translating the world space to local space.
// Get orca's object in regard to the target object
vec3 = this.el.object3D.worldToLocal(target.object3D.position.clone())
To better understand this, consider the same setup seen from two points of view.
From the T point of view, it does not need to move towards [0, 0, 0]. It needs to calculate where the empty box is from its own point of view, which is [1, -0.7, 5]. Furthermore, the space is not only about position, but also about rotation (and scale, not important here). When T rotates, the empty box position will be different.
2) Check the distance using THREEs a.distanceTo(b). If the distance is bigger than you want, move the orca:
var target = this.el.sceneEl.querySelector('a-camera'); // this should be in the init()
var vec3 = new THREE.Vector3();
var currentPosition = this.el.object3D.position;
// clone the position to operate on a copy
vec3 = this.el.object3D.worldToLocal(target.object3D.position.clone())
var distance = dt*this.data.speed / 1000;
var camFromOrca = currentPosition.distanceTo( target.object3D.position );
// if the distance is more than one meter, move the orca
if (camFromOrca > 1) {
this.el.object3D.translateOnAxis(vec3, distance);
}
fiddle here. Actually quite spooky :)
var vec3 = new THREE.Vector3();
var obj = this.el.object3D;
var targ = target.object3D;
obj.position.add(vec3.copy(targ.position).sub(obj.position).multiplyScalar(0.1));
Related
What I'm trying to achieve is a rotation of the geometry around pivot point and make that the new definition of the geometry. I do not want te keep editing the rotationZ but I want to have the current rotationZ to be the new rotationZ 0.
This way when I create a new rotation task, it will start from the new given pivot point and the newly given rad.
What I've tried, but then the rotation point moves:
// Add cube to do calculations
var box = new THREE.Box3().setFromObject( o );
var size = box.getSize();
var offsetZ = size.z / 2;
o.geometry.translate(0, -offsetZ, 0)
// Do ratation
o.rotateZ(CalcUtils.degreeToRad(degree));
o.geometry.translate(0, offsetZ, 0)
I also tried to add a Group and rotate that group and then remove the group. But I need to keep the rotation without all the extra objects. The code I created
var box = new THREE.Box3().setFromObject( o );
var size = box.size();
var geometry = new THREE.BoxGeometry( 20, 20, 20 );
var material = new THREE.MeshBasicMaterial( { color: 0xcc0000 } );
var cube = new THREE.Mesh( geometry, material );
cube.position.x = o.position.x;
cube.position.y = 0; // Height / 2
cube.position.z = -size.z / 2;
o.position.x = 0;
o.position.y = 0;
o.position.z = size.z / 2;
cube.add(o);
scene.add(cube);
// Do ratation
cube.rotateY(CalcUtils.degreeToRad(degree));
// Remove cube, and go back to single object
var position = o.getWorldPosition();
scene.add(o)
scene.remove(cube);
console.log(o);
o.position.x = position.x;
o.position.y = position.y;
o.position.z = position.z;
So my question, how do I save the current rotation as the new 0 rotation point. Make the rotation final
EDIT
I added an image of what I want to do. The object is green. I have a 0 point of the world (black). I have a 0 point of the object (red). And I have rotation point (blue).
How can I rotate the object around the blue point?
I wouldn't recommend updating the vertices, because you'll run into trouble with the normals (unless you keep them up-to-date, too). Basically, it's a lot of hassle to perform an action for which the transformation matrices were intended.
You came pretty close by translating, rotating, and un-translating, so you were on the right track. There are some built-in methods which can help make this super easy.
// obj - your object (THREE.Object3D or derived)
// point - the point of rotation (THREE.Vector3)
// axis - the axis of rotation (normalized THREE.Vector3)
// theta - radian value of rotation
// pointIsWorld - boolean indicating the point is in world coordinates (default = false)
function rotateAboutPoint(obj, point, axis, theta, pointIsWorld){
pointIsWorld = (pointIsWorld === undefined)? false : pointIsWorld;
if(pointIsWorld){
obj.parent.localToWorld(obj.position); // compensate for world coordinate
}
obj.position.sub(point); // remove the offset
obj.position.applyAxisAngle(axis, theta); // rotate the POSITION
obj.position.add(point); // re-add the offset
if(pointIsWorld){
obj.parent.worldToLocal(obj.position); // undo world coordinates compensation
}
obj.rotateOnAxis(axis, theta); // rotate the OBJECT
}
After this method completes, the rotation/position IS persisted. The next time you call the method, it will transform the object from its current state to wherever your inputs define next.
Also note the compensation for using world coordinates. This allows you to use a point in either world coordinates or local space by converting the object's position vector into the correct coordinate system. It's probably best to use it this way any time your point and object are in different coordinate systems, though your observations may differ.
As a simple solution for anyone trying to quickly change the pivot point of an object, I would recommend creating a group and adding the mesh to the group, and rotating around that.
Full example
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube)
Right now, this will just rotate around its center
cube.rotation.z = Math.PI / 4
Create a new group and add the cube
const group = new THREE.Group();
group.add(cube)
scene.add(group)
At this point we are back where we started. Now move the mesh:
cube.position.set(0.5,0.5,0)
Then move the group
group.position.set(-0.5, -0.5, 0)
Now use your group to rotate the object:
group.rotation.z = Math.PI / 4
I'm trying to move a cube first towards to the camera, then turn left, and then move forward, to the right side of the screen. I'm building a maze where a turtle will move forward and rotate and I want to use tweenjs for the animations.
A fiddle is here https://jsfiddle.net/edapx/o6mvg0d5/1/
As you see, the first two animation are executed, but the last one does not move the cube as expected. I'm chaining the animations like this:
function testChained(){
var radians = 90 * THREE.Math.DEG2RAD;
var pos_copy_a = new THREE.Vector3().copy(mesh.position);
var target_a = pos_copy_a.add(new THREE.Vector3(0.0, 0.0, 50.0));
var rotationMatrix_a = new THREE.Matrix4().makeRotationY(mesh.rotation.y);
target_a.applyMatrix4(rotationMatrix_a);
var a = new TWEEN.Tween(mesh.position).to(target_a, 900);
var target_b = {y:mesh.rotation.y + radians};
var b = new TWEEN.Tween(mesh.rotation).to(target_b, 900);
var pos_copy_c = new THREE.Vector3().copy(mesh.position);
var target_c = pos_copy_c.add(new THREE.Vector3(0.0, 0.0, 50.0));
var rotationMatrix_c = new THREE.Matrix4().makeRotationY(mesh.rotation.y);
target_c.applyMatrix4(rotationMatrix_c);
var c = new TWEEN.Tween(mesh.position).to(target_c, 900);
a.chain(b);
b.chain(c);
a.start();
};
Am I using tween.js wrong?
This is, because you set target_c before all animations start. So, target_c is the same as target_a. When animation c starts, the mesh is at position 0 0 50, but target_c is 0 0 50, too. So, you need to set target_c after animation a has completed. Animation c relies on the "result" of the previous ones, I guess, chaining like isn't that easy.
In jsfiddle, I just just wrapped the instantiation of animation c into the complete callback of animation b to make it work: https://jsfiddle.net/327bcLgp/
EDITED Question
What I'm looking for it is called is animation with relative values. https://github.com/tweenjs/tween.js/blob/master/docs/user_guide.md#relative-values It works when piling up rotations, but mixing rotation and translation is tricky. Here an updated fiddle that still does not works https://jsfiddle.net/edapx/o6mvg0d5/5/
EDITED Answer
It's not just a string (String(distance_a.x)), you need to prefix it with a plus sign to make it relative: '+' + distance_a.x;
And I wouldn't use mesh.rotation.y to rotate the vector. That's the same as I described above. When you set set rotationMatrix_c, mesh.rotation.y will be still 0. Instead, use fix values.
var radians = 90 * THREE.Math.DEG2RAD;
...
var rotationMatrix_c = new THREE.Matrix4().makeRotationY(radians);
var distance_c = new THREE.Vector3(0.0, 0.0, 50.0);
distance_c.applyMatrix4(rotationMatrix_c);
var target_c = {
x: '+' + distance_c.x,
y: '+' + distance_c.y,
z: '+' + distance_c.z
};
https://jsfiddle.net/o6mvg0d5/6/
If you have more rotations, you need to sum up the radians value, e.g. radians += 90 * THREE.Math.DEG2RAD;
I'm working my way through this book, and I'm doing okay I guess, but I've hit something I do not really get.
Below is how you can log to the console and object in 3D space that you click on:
renderer.domElement.addEventListener('mousedown', function(event) {
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize()
);
var intersects = raycaster.intersectObjects(OBJECTS);
if (intersects.length) {
console.log(intersects[0]);
}
}, false);
Here's the book's explanation on how this code works:
The previous code listens to the mousedown event on the renderer's canvas.
Get that, we're finding the domElement the renderer is using by using renderer.domElement. We're then binding an event listener to it with addEventListner, and specifing we want to listening for a mousedown. When the mouse is clicked, we launch an anonymous function and pass the eventvariable into the function.
Then,
it creates a new Vector3 instance with the mouse's coordinates on the screen
relative to the center of the canvas as a percent of the canvas width.
What? I get how we're creating a new instance with new THREE.Vector3, and I get that the three arguments Vector3 takes are its x, y and z coordinates, but that's where my understanding completely and utterly breaks down.
Firstly, I'm making an assumption here, but to plot a vector, surely you need two points in space in order to project? If you give it just one set of coords, how does it know what direction to project from? My guess is that you actually use the Raycaster to plot the "vector"...
Now onto the arguments we're passing to Vector3... I get how z is 0. Because we're only interested in where we're clicking on the screen. We can either click up or down, left or right, but not into or out of the screen, so we set that to zero. Now let's tackle x:
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
We're getting the PixelRatio of the device, timsing it by where we clicked along the x axis, dividing by the renderer's domElement width, timsing this by two and taking away one.
When you don't get something, you need to say what you do get so people can best help you out. So I feel like such a fool when I say:
I don't get why we even need the pixel ratio I don't get why we times that by where we've clicked along the x
I don't get why we divide that by the width
I utterly do not get why we need to times by 2 and take away 1. Times by 2, take away 1. That could genuinely could be times by an elephant, take away peanut and it would make as much sense.
I get y even less:
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
Why are we now randomly using -devicePixelRatio? Why are now deciding to add one rather than minus one?
That vector is then un-projected (from 2D into 3D space) relative to the camera.
What?
Once we have the point in 3D space representing the mouse's location,
we draw a line to it using the Raycaster. The two arguments that it
receives are the starting point and the direction to the ending point.
Okay, I get that, it's what I was mentioning above. How we need two points to plot a "vector". In THREE talk, a vector appears to be called a "raycaster".
However, the two points we're passing to it as arguments don't make much sense. If we were passing in the camera's position and the vector's position and drawing the projection from those two points I'd get that, and indeed we are using the camera.position for the first points, but
vector.sub(camera.position).normalize()
Why are we subtracting the camera.position? Why are we normalizing? Why does this useless f***** book not think to explain anything?
We get the direction by subtracting the mouse and camera positions and
then normalizing the result, which divides each dimension by the
length of the vector to scale it so that no dimension has a value
greater than 1.
What? I'm not being lazy, not a word past by makes sense here.
Finally, we use the ray to check which objects are located in the
given direction (that is, under the mouse) with the intersectObjects
method. OBJECTS is an array of objects (generally meshes) to check; be
sure to change it appropriately for your code. An array of objects
that are behind the mouse are returned and sorted by distance, so the
first result is the object that was clicked. Each object in the
intersects array has an object, point, face, and distance property.
Respectively, the values of these properties are the clicked object
(generally a Mesh), a Vector3 instance representing the clicked
location in space, the Face3 instance at the clicked location, and the
distance from the camera to the clicked point.
I get that. We grab all the objects the vector passes through, put them to an array in distance order and log the first one, i.e. the nearest one:
console.log(intersects[0]);
And, in all honestly, do you think I should give up with THREE? I mean, I've gotten somewhere with it certainly, and I understand all the programming aspects of it, creating new instances, using data objects such as arrays, using anonymous functions and passing in variables, but whenever I hit something mathematical I seem to grind to a soul-crushing halt.
Or is this actually difficult? Did you find this tricky? It's just the book doesn't feel it's necessary to explain in much detail, and neither do other answers , as though this stuff is just normal for most people. I feel like such an idiot. Should I give up? I want to create 3D games. I really, really want to, but I am drawn to the poetic idea of creating an entire world. Not math. If I said I didn't find math difficult, I would be lying.
I understand your troubles and I'm here to help. It seems you have one principal question: what operations are performed on the vector to prepare it for click detection?
Let's look back at the original declaration of vector:
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
);
renderer.devicePixelRatio relates to a ratio of virtual site pixels /
real device pixels
event.pageX and .pageY are mouseX, mouseY
The this context is renderer.domElement, so .width, .height, .offsetLeft/Right relate to that
1 appears to be a corrective "magic" number for the calculation (for the purpose of being as visually exact as possible)
We don't care about the z-value, THREE will handle that for us. X and Y are our chief concern. Let's derive them:
We first find the distance of the mouse to the edge of the canvas: event.pageX - this.offsetLeft
We divide that by this.width to get the mouseX as a percentage of the screen width
We multiply by renderer.devicePixelRatio to convert from device pixels to site pixels
I'm not sure why we multiply by 2, but it might have to do with an assumption that the user has a retina display (someone can feel free to correct me on this if it's wrong).
1 is, again, magic to fix what might be just an offset error
For y, we multiply the whole expression by -1 to compensate for the inverted coordinate system (0 is top, this.height is bottom)
Thus you get the following arguments for the vector:
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
-renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0
Now, for the next bit, a few terms:
Normalizing a vector means simplifying it into x, y, and z components less than one. To do so, you simply divide the x, y, and z components of the vector by the magnitude of the vector. It seems useless, but it's important because it creates a unit vector (magnitude = 1) in the direction of the mouse vector!
A Raycaster casts a vector through the 3D landscape produced in the canvas. Its constructor is THREE.Raycaster( origin, direction )
With these terms in mind, I can explain why we do this: vector.sub(camera.position).normalize(). First, we get the vector describing the distance from the mouse position vector to the camera position vector, vector.sub(camera.position). Then, we normalize it to make it a direction vector (again, magnitude = 1). This way, we're casting a vector from the camera to the 3D space in the direction of the mouse position! This operation allows us to then figure out any objects that are under the mouse by comparing the object position to the ray's vector.
I hope this helps. If you have any more questions, feel free to comment and I will answer them as soon as possible.
Oh, and don't let the math discourage you. THREE.js is by nature a math-heavy language because you're manipulating objects in 3D space, but experience will help you get past these kinds of understanding roadblocks. I would continue learning and return to Stack Overflow with your questions. It may take some time to develop an aptitude for the math, but you won't learn if you don't try!
This is more universal no matter the render dom location, and the dom and its ancesters's padding margin.
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
here is a demo, scroll to the bottom to click the cube.
<!DOCTYPE html>
<html>
<head>
<script src="http://threejs.org/build/three.min.js"></script>
<link rel="stylesheet" href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" />
<style>
body {
font-family: Monospace;
background-color: #fff;
margin: 0px;
}
#canvas {
background-color: #000;
width: 200px;
height: 200px;
border: 1px solid black;
margin: 10px;
padding: 0px;
top: 10px;
left: 100px;
}
.border {
padding:10px;
margin:10px;
height:3000px;
overflow:scroll;
}
</style>
</head>
<body>
<div class="border">
<div style="min-height:1000px;"></div>
<div class="border">
<div id="canvas"></div>
</div>
</div>
<script>
// Three.js ray.intersects with offset canvas
var container, camera, scene, renderer, mesh,
objects = [],
count = 0,
CANVAS_WIDTH = 200,
CANVAS_HEIGHT = 200;
// info
info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '30px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.style.color = '#f00';
info.style.backgroundColor = 'transparent';
info.style.zIndex = '1';
info.style.fontFamily = 'Monospace';
info.innerHTML = 'INTERSECT Count: ' + count;
info.style.userSelect = "none";
info.style.webkitUserSelect = "none";
info.style.MozUserSelect = "none";
document.body.appendChild( info );
container = document.getElementById( 'canvas' );
renderer = new THREE.WebGLRenderer();
renderer.setSize( CANVAS_WIDTH, CANVAS_HEIGHT );
container.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 45, CANVAS_WIDTH / CANVAS_HEIGHT, 1, 1000 );
camera.position.y = 250;
camera.position.z = 500;
camera.lookAt( scene.position );
scene.add( camera );
scene.add( new THREE.AmbientLight( 0x222222 ) );
var light = new THREE.PointLight( 0xffffff, 1 );
camera.add( light );
mesh = new THREE.Mesh(
new THREE.BoxGeometry( 200, 200, 200, 1, 1, 1 ),
new THREE.MeshPhongMaterial( { color : 0x0080ff }
) );
scene.add( mesh );
objects.push( mesh );
// find intersections
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
// mouse listener
document.addEventListener( 'mousedown', function( event ) {
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
info.innerHTML = 'INTERSECT Count: ' + ++count;
}
}, false );
function render() {
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
(function animate() {
requestAnimationFrame( animate );
render();
})();
</script>
</body>
</html>
I looked at many examples but so far nothing worked. I want the circle to rotate on mousemove and it is rotating centered, so no problems so far. But, it does a 180 jump when I pass half of the stage. So everything is fine till I pass the half of the stage, clearly I'm missing something. Math.atan2 gives me an error: the circle jumps to the (0,0) of the stage.
Please help I really need this badly.
Thanks in advance!
new Kinetic.Tween({
node: cGrad1,
x: stage.getWidth()/2,
y: stage.getHeight()/2,
duration: 1,
opacity:1,
easing: Kinetic.Easings.EaseInOut
}).play();
clickO.on('mousemove', function() {
for(var n = 0; n < 16; n++) {
var shape = cGradlayer1.getChildren()[n];
var stage = shape.getStage();
var mousePos = stage.getMousePosition();
var x = mousePos.x - shape.getPosition().x;
var y = mousePos.y -shape.getPosition().y ;
var degree = (Math.PI)+(Math.atan(y/x));
shape.setAttrs({
rotation: degree
})
cGradlayer1.draw()
}
})
Well this is what I came up with, and hopefully it's close to what you were looking for: jsfiddle
Basically, to calculate the angle you want to rotate to, we need to store two points:
The origin of the shape (centre coordinate)
The coordinate of the mouse click
Once you have that, you can calculate the angle between the two points with a little bit of trigonometry (Sorry if I am not accurate here, Trigonometry is not my strong suit). Calculate the distance between the two points (dx, dy) and then use the trig formula to find the angle in degrees.
layer.on('click', function() {
var mousePos = stage.getMousePosition();
var x = mousePos.x;
var y = mousePos.y;
var rectX = rect.getX()+rect.getWidth()/2;
var rectY = rect.getY()+rect.getHeight()/2;
var dx = x - rectX;
var dy = y - rectY;
var rotation = (Math.atan2(dy, dx)*180/Math.PI+360)%360;
var rotateOnClick = new Kinetic.Tween({
node: rect,
duration: 1,
rotationDeg: rotation,
easing: Kinetic.Easings.EaseInOut
});
rotateOnClick.play();
});
EDIT:
Based off the gathered information below (which I came to the same conclusion as) I have updated my fiddle: jsfiddle
As markE mentioned below in the comments, KineticJS does not support "force-clockwise flag", so the rotation always jumps when rotating past 360 in the clockwise direction, or past 0 in the counter clockwise position. Otherwise, we know that the rotation works properly.
So, to fix this manually there are two cases we need to consider:
When we rotate past 360 in the clockwise direction.
When we rotate past 0 in the counter clockwise direction.
And this is the math I used to calculate whether to counter the rotation or not:
var currentDeg = rect.getRotationDeg();
var oneWay = rotation - currentDeg;
var oneWayAbsolute = Math.abs(rotation - currentDeg);
var otherWay = 360-oneWayAbsolute;
if (otherWay < oneWayAbsolute) {
//Take the other way
if (oneWay > 0) {
//Clicked direction was positive/clockwise
var trueRotation = currentDeg - otherWay;
} else {
//Clicked direction was negative/counter clockwise
var trueRotation = currentDeg + otherWay;
}
} else {
//Take the clicked way
var trueRotation = rotation;
}
Basically we want to figure out which way to rotate, by comparing the angle degrees of which direction would be closer, the direction we clicked in, or the opposite way.
If we determined that the otherWay was closer to the currentDeg, then we need to see if the direction we clicked in was in the counter clockwise (negative) or clockwise (positive) direction, and we set the otherWay direction to go in the opposite direction.
And then you can normalise the rotationDeg onFinish event.
var rotateOnClick = new Kinetic.Tween({
node: rect,
duration: 1,
rotationDeg: trueRotation,
easing: Kinetic.Easings.EaseInOut,
onFinish: function() {
trueRotation = (trueRotation+360)%360;
rect.setRotationDeg(trueRotation);
layer.draw();
}
});
You might need to set the x and y position of the Tween as follows:
new Kinetic.Tween({
x: mousePos.x,
y: mousePos.y,
node: shape,
rotation: degree,
easing: Kinetic.Easings.EaseInOut,
duration:1,
}).play();
See this tutorial for reference http://www.html5canvastutorials.com/kineticjs/html5-canvas-stop-and-resume-transitions-with-kineticjs/
I looked at many examples but so far nothing worked. I want the circle to rotate on mousemove and it is rotating centered, so no problems so far. But, it does a 180 jump when I pass half of the stage. So everything is fine till I pass the half of the stage, clearly I'm missing something. Math.atan2 gives me an error: the circle jumps to the (0,0) of the stage.
Please help I really need this badly.
Thanks in advance!
new Kinetic.Tween({
node: cGrad1,
x: stage.getWidth()/2,
y: stage.getHeight()/2,
duration: 1,
opacity:1,
easing: Kinetic.Easings.EaseInOut
}).play();
clickO.on('mousemove', function() {
for(var n = 0; n < 16; n++) {
var shape = cGradlayer1.getChildren()[n];
var stage = shape.getStage();
var mousePos = stage.getMousePosition();
var x = mousePos.x - shape.getPosition().x;
var y = mousePos.y -shape.getPosition().y ;
var degree = (Math.PI)+(Math.atan(y/x));
shape.setAttrs({
rotation: degree
})
cGradlayer1.draw()
}
})
Well this is what I came up with, and hopefully it's close to what you were looking for: jsfiddle
Basically, to calculate the angle you want to rotate to, we need to store two points:
The origin of the shape (centre coordinate)
The coordinate of the mouse click
Once you have that, you can calculate the angle between the two points with a little bit of trigonometry (Sorry if I am not accurate here, Trigonometry is not my strong suit). Calculate the distance between the two points (dx, dy) and then use the trig formula to find the angle in degrees.
layer.on('click', function() {
var mousePos = stage.getMousePosition();
var x = mousePos.x;
var y = mousePos.y;
var rectX = rect.getX()+rect.getWidth()/2;
var rectY = rect.getY()+rect.getHeight()/2;
var dx = x - rectX;
var dy = y - rectY;
var rotation = (Math.atan2(dy, dx)*180/Math.PI+360)%360;
var rotateOnClick = new Kinetic.Tween({
node: rect,
duration: 1,
rotationDeg: rotation,
easing: Kinetic.Easings.EaseInOut
});
rotateOnClick.play();
});
EDIT:
Based off the gathered information below (which I came to the same conclusion as) I have updated my fiddle: jsfiddle
As markE mentioned below in the comments, KineticJS does not support "force-clockwise flag", so the rotation always jumps when rotating past 360 in the clockwise direction, or past 0 in the counter clockwise position. Otherwise, we know that the rotation works properly.
So, to fix this manually there are two cases we need to consider:
When we rotate past 360 in the clockwise direction.
When we rotate past 0 in the counter clockwise direction.
And this is the math I used to calculate whether to counter the rotation or not:
var currentDeg = rect.getRotationDeg();
var oneWay = rotation - currentDeg;
var oneWayAbsolute = Math.abs(rotation - currentDeg);
var otherWay = 360-oneWayAbsolute;
if (otherWay < oneWayAbsolute) {
//Take the other way
if (oneWay > 0) {
//Clicked direction was positive/clockwise
var trueRotation = currentDeg - otherWay;
} else {
//Clicked direction was negative/counter clockwise
var trueRotation = currentDeg + otherWay;
}
} else {
//Take the clicked way
var trueRotation = rotation;
}
Basically we want to figure out which way to rotate, by comparing the angle degrees of which direction would be closer, the direction we clicked in, or the opposite way.
If we determined that the otherWay was closer to the currentDeg, then we need to see if the direction we clicked in was in the counter clockwise (negative) or clockwise (positive) direction, and we set the otherWay direction to go in the opposite direction.
And then you can normalise the rotationDeg onFinish event.
var rotateOnClick = new Kinetic.Tween({
node: rect,
duration: 1,
rotationDeg: trueRotation,
easing: Kinetic.Easings.EaseInOut,
onFinish: function() {
trueRotation = (trueRotation+360)%360;
rect.setRotationDeg(trueRotation);
layer.draw();
}
});
You might need to set the x and y position of the Tween as follows:
new Kinetic.Tween({
x: mousePos.x,
y: mousePos.y,
node: shape,
rotation: degree,
easing: Kinetic.Easings.EaseInOut,
duration:1,
}).play();
See this tutorial for reference http://www.html5canvastutorials.com/kineticjs/html5-canvas-stop-and-resume-transitions-with-kineticjs/