I am new to THREEJS and at the moment I am trying to move a cube using arrow keys. Please see this fiddle: https://jsfiddle.net/mauricederegt/y6cw7foj/26/
All works, I can move the cube using the arrow keys and I even managed to rotate the cube around the correct axis when moving it around. The problem is with the animations. I can’t seem to get them to work. At the moment when you press the left arrow key, the cube moves to the left and also rolls around the axis. Well…at the moment it snaps into position, instead of smoothly transitioning.
What I want is that it smoothly moves to the left while it rotates, but how to do that? At the end of the code I do call for the
requestAnimationFrame
but that doesn’t do much. I have a fiddle here of my attempt doing this with CSS. Here the animations work (but never got the rotating direction correct): https://jsfiddle.net/mauricederegt/5ozqg9uL/3/ This does show what animations I want to have.
So what am I missing in the THREEjs? Thanks a lot
What you're looking for is something called "tweening," where you draw intermediary steps, rather than jumping immediately to the end result. There are several JavaScript libraries that will do this for you, but I'll cover some of the basics of implementing it yourself.
Take your example. When you tap the left arrow, you move the mesh 1 unit along the -x axis, and rotate -PI/2 about the +y axis. Rather than snapping to these positions/rotations, consider how long you want the animation to take, and start dividing out steps.
Let's say you want to to take 500ms (half a second). Your browser tries to run at about 60fps, so you have 30 frames (about 500ms) to work with at that rate. So for every frame, you can move the box 1/30 units, and rotate it by -PI/60. After 30 frames, the box should be in about the right place, give or take some rounding.
I use "about" when talking about the framerate of the browser because you aren't always guaranteed to get 60FPS. If your framerates dip, and you're locked to the framerate to draw your animation, then it too will slow down and take longer than you wanted. So what can be done about that?
Rather than relying on requestAnimationFrame as your timer, you can set a real timer to step through your animation. Toss calculating the frames you need to complete the animation, and instead calculate the steps needed.
We already know that 60fps is roughly 1 frame every 16.6ms, so that's the absolute maximum target that can expect the browser to draw. But when we do our updates by steps, nothing stops us from going faster. To make things easier to calculate, let's say we want to do 50 update steps rather than the 30 from before. This means that for the 500ms play time, we will need to perform an update every 10ms (slightly faster than the framerate). Also, because we are performing 50 steps, we will be updating the position by 1/50 units, and rotating by -PI/100.
let animationId = setInterval( ()=>{
// update position by 1/50 units
// update rotation by -PI/100
}, 10 ); // every 10 ms
As the interval runs, it will update the object. Meanwhile, the animation loop churns out new frames whenever it can.
Here's a full, running example, with only left-arrow support:
let W = window.innerWidth;
let H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(0, 0, 50);
camera.lookAt(scene.position);
scene.add(camera);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1, 1, 1),
new THREE.MeshPhongMaterial({
color: "red"
})
);
cube.position.set(10, 0, 0);
scene.add(cube);
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
function animate() {
requestAnimationFrame(animate);
render();
}
requestAnimationFrame(animate);
const yAxis = new THREE.Vector3(0, 1, 0);
function updateCube() {
//cube.position.x -= 1;
//cube.rotateOnWorldAxis(yAxis, THREE.Math.degToRad(-90));
cube.position.x -= 1 / 50;
cube.rotateOnWorldAxis(yAxis, -(Math.PI / 100));
}
let step = 0;
let animationId = null;
function startStepping() {
animationId = setInterval(() => {
updateCube();
if (++step === 50) {
clearInterval(animationId);
animationId = null;
}
}, 10)
}
function handleKeyboard(e) {
//if (e.keyCode == 65 || e.keyCode == 37) {
// updateCube();
//}
if (animationId === null && (e.keyCode == 65 || e.keyCode == 37)) {
step = 0;
startStepping();
}
}
document.addEventListener("keydown", handleKeyboard, false);
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>
Now there is a downside to this method. Sometimes you may see updates skipped (if the browser's framerate drops), or the same position drawn twice (if your update rate is significantly lower than the browser's framerate). You could try to get the best of both worlds but computing the framerate live from your render loop, and adjusting your frame steps accordingly, but at that point you must ask whether the extra time spent computing those statistics are actually hurting trying to achieve a rock-steady framerate-locked draw rate.
Switching based on key input
Because your key input is now disjointed from drawing, you now need some kind of flag to determine the action being taken. Your key press handler will set that flag, and then updateCube will act based on that flag. Something like:
let action = null
function startStepping(){
// set up the interval...
// but then also ensure the action stops after the animation plays:
setTimeout( () => action = null, 500 );
}
function handleKeyboard(e){
if (animationId === null) {
step = 0;
switch(e.keyCode){
case 37:
case 65:
action = "left";
break;
// other keys...
}
startStepping();
}
}
function updateCube(){
switch(action){
case "left":
// move as if it's rolling left
break;
case "right":
// move as if it's rolling right
break;
// etc. for the other directions
}
}
Related
I want to rotate an object3D with hammerjs gestures.
Basically the rotation work, but with two issues I can't figure out.
The direction of the rotation changes randomly. It turns left. I stop and than move my fingers in the same diction again and suddenly instead of continuing to rotate left its going right. Happens sometimes but not always.
Once the rotation is started it only rotates to the same direction, despite me moving my fingers to different directions.
Here is how I handle the rotation:
public rotateObject3D (e: HammerInput): void {
if (rotationEnabled) {
const translation = new THREE.Vector3();
const rotation = new THREE.Quaternion();
const scale = new THREE.Vector3();
const rotateMatrix = new THREE.Matrix4();
const deltaRotationQuaternion = new THREE.Quaternion();
this._myObject.matrix.decompose(translation, rotation, scale);
this._deltaRot = (e.rotation * 0.01);
deltaRotationQuaternion.setFromEuler(new THREE.Euler(
0,
this._deltaRot * (Math.PI / 180),
0,
'XYZ'
));
deltaRotationQuaternion.multiplyQuaternions(deltaRotationQuaternion, rotation);
this._myObject.matrix = rotateMatrix.compose(translation, deltaRotationQuaternion, scale);
}
}
And this is the call of it:
this._hammerManager.on('rotate', (e) => {
this._arTools.rotateObject3D(e);
});
Is there anything I am missing?
It looks like you're using the absolute rotation value, instead of the change in rotation. For instance, consider the following scenario:
Rotation1: 12°
Rotation2: 10°
Rotation3: 5°
By multiplying your quaternions, your object is being rotated to 12°, then 22°, then finally 27°, even though you were turning back towards 0. This is because you're adding the new rotation to the last rotation on each event.
What you should do is save the previous rotation value, and subtract it from the new one, to get the rotation delta:
var previousRot = 0;
var newRot = 0;
rotateObject3D(e) {
newRot = e.rotation - previousRot;
// You could use newRot for your quaternion calculations
// but modifying .rotation.y is simpler
myObject.rotation.y += newRot
// Save value to be used on next event
previousRot = newRot;
}
With this method, the scenario above will give you the change in rotation. Your object will first rotate by +12°, then -2°, then -5°, for a more natural behavior.
Just make sure to reset previousRot = 0 on HammerJS' rotateend event so you don't use the value from previous gestures when a new one begins.
Let's say I have a shape at position.x = 0 and I want to smoothly animate it in the render loop to position.x = 2.435. How would I go about that?
You can use the THREE AnimationMixer. The function below sets up the animation. Example jsFiddle.
const createMoveAnimation = ({ mesh, startPosition, endPosition }) => {
mesh.userData.mixer = new AnimationMixer(mesh);
let track = new VectorKeyframeTrack(
'.position',
[0, 1],
[
startPosition.x,
startPosition.y,
startPosition.z,
endPosition.x,
endPosition.y,
endPosition.z,
]
);
const animationClip = new AnimationClip(null, 5, [track]);
const animationAction = mesh.userData.mixer.clipAction(animationClip);
animationAction.setLoop(LoopOnce);
animationAction.play();
mesh.userData.clock = new Clock();
this.animationsObjects.push(mesh);
};
Set your target position as a variable (outside the render loop):
var targetPositionX = 2.435;
Then in your render loop, create an if statement that checks if the object's X position is less than the targetPositionX. If it is , it will add an increment (which you can change based on how fast you want it to move) to the object's X position. When the object's X position becomes greater or equal to the targetPositionX, it will stop moving.
Something like this:
if (object.position.x <= targetPositionX) {
object.position.x += 0.001; // You decide on the increment, higher value will mean the objects moves faster
}
Here is the full code for the render loop:
function loop(){
// render the scene
renderer.render(scene, camera);
// Check the object's X position
if (object.position.x <= targetPositionX) {
object.position.x += 0.001; // You decide on the increment, higher value will mean the objects moves faster
}
// call the loop function again
requestAnimationFrame(loop);
}
Side note
For more detailed/complex animations you may want to look into Tween.js for Three.js which makes animation easier and also allows you to add easing functions to your animation.
You can find it here:
https://github.com/sole/tween.js
I would recommend reading into it if you are getting into Three.js.
I've written my own HTML5 canvas - javaScript based physics engine to simulate a number of points connected by springs. The current general structure of the program is
function init(){
// A bunch of event listeners
renderer();
physics();
}
var frameTime = 1;
function physics(){
// iterate the physics
parts.update();
setTimeout(physics, frameTime);
}
// render loop
function renderer(){
// draws a rectangle over the last frame
drawBackground();
// renders the objects themselves
parts.draw();
// update the timeout according to an onscreen slider
frameTime = Math.ceil(101 - speed_r.value) / 2;
setTimeout(renderer, 15);
}
The rationale behind the 2 different loops is that the human eye only needs to see 60fps, but doing more updates per second yields better physics.
I've since done more research, and found that the standard way to render animations with javaScript is to call requestAnimationFrame(), which as I understand it has the advantage of not rendering while the tab is deselected, improving battery life. However, due to the dual loop structure, the physics will continue to be calculated and will probably outweigh the renderer overhead.
The question is: What is the most performant and ideally most efficient way to achieve this?
To sync your physics simulation with the wall clock, and render the animation smoothly, you need to use a fixed time step and interpolation. Read this excellent article (see also: archive.org) about both subjects.
Using requestAnimationFrame is a good idea to save battery (it will lower the frame rate if the battery is low on most devices). You can use it for both the physics and rendering loop.
What you have to do is compute the time elapsed since the last frame and then use zero or many fixed steps to keep the physics loop in sync with the current (wall-clock) time. This is how all real-time physics engines work, including Box2D and Bullet Physics.
I made a complete JSFiddle using HTML5 Canvas and JavaScript that implements what you need, based on the article mentioned above. See the code below or open it on JSFiddle.
The integrate function is where you update your physics. In the code it is used to step a spring simulation forward.
var t = 0;
var dt = 0.01;
var currentTime;
var accumulator = 0;
var previousState = { x: 100, v: 0 };
var currentState = { x: 100, v: 0 };
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// start animation loop
requestAnimationFrame(animate);
function animate(newTime){
requestAnimationFrame(animate);
if (currentTime) {
var frameTime = newTime - currentTime;
if ( frameTime > 250 )
frameTime = 250;
accumulator += frameTime;
while ( accumulator >= dt )
{
previousState = currentState;
currentState = integrate( currentState, t, dt );
t += dt;
accumulator -= dt;
}
var alpha = accumulator / dt;
var interpolatedPosition = currentState.x * alpha + previousState.x * (1 - alpha);
render( interpolatedPosition );
}
currentTime = newTime;
}
// Move simulation forward
function integrate(state, time, fixedDeltaTime){
var fixedDeltaTimeSeconds = fixedDeltaTime / 1000;
var f = (200 - state.x) * 3;
var v = state.v + f * fixedDeltaTimeSeconds;
var x = state.x + v * fixedDeltaTimeSeconds;
return { x: x, v: v };
}
// Render the scene
function render(position){
// Clear
ctx.fillStyle = 'white';
ctx.fillRect(0,0,canvas.width,canvas.height);
// Draw circle
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(position,100,50,0,2*Math.PI);
ctx.closePath();
ctx.fill();
}
I think I'd look at putting the physics part in a web worker, and having it post updates to the main UI thread, which renders them on a requestAnimationFrame callback. That allows the physics code to run constantly (you don't even need setTimeout looping; although having it yield periodically so it can access messages from the front end — not least "stop"! — would be a good idea), while only updating the display as often as actually needed.
2018 update: As of ES2018, the worker and the main thread could share memory via a SharedArrayBuffer and the features of the Atomics object. Rather than having to break the work up so the worker could process messages, it could just check a location in the shared memory for flags (for instance, the flag saying it needs to stop). The worker could even be suspended right in the middle of the calculation (even in the middle of a standard loop, such as a for or while) and then resumed via Atomics.wait and Atomics.notify.
I'm develop my first phaser game for a client. The game is a car moving forward and it have two minutes to get the goal.
I want to increase progressive the car speed while the Up key is pressed until the speed limit has reached.
I'm moving the racetrack not the car by doing:
this.highway2 = game.add.tileSprite(game.world.centerX,game.world.height/2, game.cache.getImage('highway').width, game.cache.getImage('highway').height, 'highway');
this.highway2.anchor.setTo(0.5,0.5);
this.highway2.autoScroll(0,1000);
So, my questions are:
how can I control the speed of the autoScroll to simulate acceleration?
Is there a way to know how much time was a key pressed?
Is this the right approach to get this done?
Thanks in advanced.
Well, I don't know if this is the better way to do this, but it's work pretty well.
Just set speed limit and track it in the update function.
var playState = {
create: function(){
this.setInitialValues();
game.physics.startSystem(Phaser.Physics.ARCADE);
this.cursor = game.input.keyboard.createCursorKeys();
//highway
this.highway2 = game.add.tileSprite(game.world.centerX,game.world.height/2, game.cache.getImage('highway').width, game.cache.getImage('highway').height, 'highway');
this.highway2.anchor.setTo(0.5,0.5);
this.highway2.autoScroll(0,0);
//car
this.player = game.add.sprite(game.world.centerX+10, game.world.height-150, 'player');
this.player.anchor.setTo(0.5,0.5);
game.physics.arcade.enable(this.player);
//other things
},
update: function(){
this.movePlayer();
},
movePlayer: function(){
// move left and right
// If the left arrow key is pressed
if (this.cursor.left.isDown)
{
// Move the player to the left
this.player.body.velocity.x = -200;
}
// If the right arrow key is pressed
else if (this.cursor.right.isDown)
{ // Move the player to the right
this.player.body.velocity.x = 200;
}
// If neither the right or left arrow key is pressed
else
{
// Stop the player
this.player.body.velocity.x = 0;
}
//speed up and speed down
if (this.cursor.up.isDown)
{
if(this.currentSpeed < this.maxSpeed )
{
this.currentSpeed+=10;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
else{
if(this.currentSpeed > 0 )
{
this.currentSpeed-=10;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
if (this.cursor.down.isDown)
{
if(this.currentSpeed > 0 )
{
this.currentSpeed-=30;
this.highway2.autoScroll(0,this.currentSpeed);
}
}
},
setInitialValues: function(){
this.maxSpeed=1500;
this.currentSpeed=0;
}
}
You should apply a strict separation of concerns to 1) simplify coding 2) ease the fine tuning and even make the game more interesting and 3) easily 'plug' you logic with another controller (touch event instead of keyboard).
So here you have two separate concerns :
* measure for how long the user has been accelerating.
* given current speed, min/max speed, thrust time, decide what is current acceleration (== speed change).
For 1) it's pretty straightForward : record the time when input start, now duration is current time - start time. You'd better use game time if you have one (rather than Date.now()), so that you avoid surprises after the game resumes from a long tab-out.
For 2) You should fine tune the acceleration/deceleration of your game, it will make it more interesting.
Most obvious is not to have a constant acceleration : it must be harder and harder to reach the last % of the max speed. This way you give and incentive/reward to the player not to touch obstacle.
What you should do if the player is not thrusting, i don't know : slowly decelerate or quickly return to normal speed ?
Also you'll have to decide wether the boost is infinite or not, and maybe of a cool-down time.
So the function that compute current acceleration depends on thrusting (bool), thrust time (double) regularSpeed, maxSpeed, minAcc, maxAcc.
There are a lot of options here, but the code to compute acceleration could look like :
if (thrusting) {
// if we're not even at regular speed, max acceleration
if (speed<regularSpeed) { acc = maxAcc; return; }
// if we are above maxSpeed, no acceleration (?? or friction ??)
if (speed>maxSpeed) { acc=0; return; }
// compute current speed ratio
// a figure in [0;1] representing where is the speed in [minSpeed; maxSpeed]
var speedRatio = (currSpeed-regularSpeed)/(maxSpeed-regularSpeed);
// ease this ratio as you like
speedRatio = Math.sqrt(speedRatio);
// compute acceleration : the more speed, the less acceleration
// you might want to put one/some threshold this formula.
acc= minAcc + (1-speedRatio)*(maxAcc-minAcc);
return;
} else {
// do nothing if <= regularSpeed.
if (speed<=regularSpeed) { acc=0 ; return;}
// reduce speed if above regular speed
acc = breakAcc ; // or with friction => acc = - k * currSpeed;
return;
}
I have a full-screen canvas with 3 images drawn on it. When I resize the window, these images change position; however, it appears to be very glitchy, more so in Firefox.
I've been reading that double-buffering should resolve this issue, but I'm wondering how I would double buffer when the next position is unknown. That is to say, I cannot determine what should be buffered in the future, so how would this be possible?
Here is one source that seems doable, but I do not fully understand the concept Fedor is trying to explain.
Does HTML5/Canvas Support Double Buffering?
So far I have,
$canvas = $('#myclouds')[0];
$canvas_buffer = $('canvas')[0].insertAfter($canvas).css('visibility', 'hidden');
context = $canvas.getContext('2d');
context_buffer = $canvas_buffer.getContext('2d');
clouds_arr = [$canvas, $canvas_buffer];
$(window).resize(function () {
drawCanvas();
};
function initCanvas() {
// Sources for cloud images
var cloud1 = '/js/application/home/images/cloud1.png',
cloud2 = '/js/application/home/images/cloud2.png',
cloud3 = '/js/application/home/images/cloud3.png';
// add clouds to be drawn
// parameters are as follows:
// image source, x, y, ratio, adjustment)
addCloud(cloud1, null, 125, .03);
addCloud(cloud2, null, 75, .15);
addCloud(cloud3, null, 50, .55);
addCloud(cloud1, null, 125, .97, 300);
addCloud(cloud2, null, 70, .85, 300);
addCloud(cloud3, null, 45, .5, 300);
// Draw the canvas
drawCanvas();
}
function drawCanvas() {
// Reset
$canvas.attr('height', $window.height()).attr('width', $window.width());
// draw the clouds
var l = clouds.length;
for (var i = 0; i < l; i++) {
clouds[i].x = ($window.width() * clouds[i].ratio) - clouds[i].offset;
drawimage(context, clouds[i]);
}
}
function Cloud() {
this.x = 0;
this.y = 0;
}
function addCloud(path, x, y, ratio, offset) {
var c = new Cloud;
c.x = x;
c.y = y;
c.path = path;
c.ratio = ratio || 0;
c.offset = offset || 0;
clouds.push(c);
}
function drawimage(ctx, image) {
var clouds_obj = new Image();
clouds_obj.src = image.path;
clouds_obj.onload = function() {
ctx.drawImage(clouds_obj, image.x, image.y);
};
}
I think maybe you are misunderstanding what double buffering is. Its a technique for smooth real-time rendering of graphics on a display.
The concept is you have two buffers. Only one is visible at any one time. When you go to draw the elements that make up a frame you draw them to the invisible buffer. In you case the clouds. Then you flip the buffers making the hidden one visible and the visible one hidden. Then on the next frame you draw to the now newly hidden buffer. Then at the end of drawing you flip back.
What this does is stop the user seeing partial rendering of elements before a frame is complete. On gaming systems this would also be synced up with the vertical refresh of the display to be really smooth and stop artefacts such as tearing to occur.
Looking at you code above you seem to have created the two canvas elements, but you're only using the first Context object. I assume this is incomplete as no flipping is taking place.
Its also worth noting that the window resize event can fire continuously when dragging which can cause frantic rendering. I usually create a timer on the resize event to actually re-render. This way the re-render only happens once the user stops resizing for a few milliseconds.
Also, your draw routine is creating new Image objects every time which you don't need to do. You can use one image object and render to the canvas multiple times. This will speed up your render considerably.
Hope this helps.