Three.js OrbitControls tween animation to face(front) of the target object - javascript

When I click a button, I want OrbitControls(camera) be in front of the object(face side) smoothly by using tween.js.
I use this code but find that after changing controls.target and panning far from the target object, I only can zoom in a little level, and after I zoom in, I can't pan.
Is there another way to look at a object? Thanks!
var from = {
x: controls.target.x,
y: controls.target.y,
z: controls.target.z
};
var to = {
x: object.getWorldPosition().x,
y: object.getWorldPosition().y,
z: object.getWorldPosition().z
};
var tween = new TWEEN.Tween(from)
.to(to, 1000)
.easing(TWEEN.Easing.Quadratic.InOut) // | TWEEN.Easing.Linear.None
.onUpdate(function () {
controls.target.set( this.x, this.y, this.z )
})
.onComplete(function () {
controls.target.set( this.x, this.y, this.z )
})
.start();

Try the following: When the animation starts, disable the controls via controls.enabled = false; to prevent any interference with your animation. Next, implement the onComplete() callback of one of your tweens like so:
.onComplete(function() {
controls.enabled = true;
camera.getWorldDirection( lookDirection );
controls.target.copy( camera.position ).add( lookDirection.multiplyScalar( 10 ) );
})
The idea is enabling the controls again, computing the current look direction of your view and then setting a new target for the controls. You can also set a predefined target if you need an exact target spot for each camera position. The value 10 is in world units and determines how far away the target should be along the look direction.
Updated fiddle: https://jsfiddle.net/ckgo7qu8/

Related

Phaser 3: Change "Hitbox"/Interactive area of sprite without physics

The game I'm creating doesn't require any physics, however you are able to interact when hovering over/clicking on the sprite by using sprite.setInteractive({cursor: "pointer"});, sprite.on('pointermove', function(activePointer) {...}); and similar. However I noticed two issues with that:
The sprite has some area which are transparent. The interactive functions will still trigger when clicking on those transparent areas, which is unideal.
When playing a sprite animation, the interactive area doesn't seem to entirely (at all?) change, thus if the sprite ends on a frame bigger than the previous, there end up being small areas I can't interact with.
One option I thought of was to create a polygon over my sprite, which covers the area I want to be interactive. However before I do that, I simply wanted to ask if there are simpler ways to fix these issues.
Was trying to find an answer for this myself just now..
Think Make Pixel Perfect is what you're looking for.
this.add.sprite(x, y, key).setInteractive(this.input.makePixelPerfect());
https://newdocs.phaser.io/docs/3.54.0/focus/Phaser.Input.InputPlugin-makePixelPerfect
This might not be the best solution, but I would solve this problem like this. (If I don't want to use physics, and if it doesn't impact the performance too much)
I would check in the event-handler, if at the mouse-position the pixel is transparent or so, this is more exact and less work, than using bounding-boxes.
You would have to do some minor calculations, but it should work well.
btw.: if the origin is not 0, you would would have to compensate in the calculations for this. (in this example, the origin offset is implemented)
Here is a demo, for the click event:
let Scene = {
preload ()
{
this.load.spritesheet('brawler', 'https://labs.phaser.io/assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
},
create ()
{
// Animation set
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 0, 1, 2, 3 ] }),
frameRate: 8,
repeat: -1
});
// create sprite
const cody = this.add.sprite(200, 100).setOrigin(0);
cody.play('walk');
cody.setInteractive();
// just info text
this.mytext = this.add.text(10, 10, 'Click the Sprite, or close to it ...', { fontFamily: 'Arial' });
// event to watch
cody.on('pointerdown', function (pointer) {
// calculate x,y position of the sprite to check
let x = (pointer.x - cody.x) / (cody.displayWidth / cody.width)
let y = (pointer.y - cody.y) / (cody.displayHeight / cody.height);
// just checking if the properties are set
if(cody.anims && cody.anims.currentFrame){
let currentFrame = cody.anims.currentFrame;
let pixelColor = this.textures.getPixel(x, y, currentFrame.textureKey, currentFrame.textureFrame);
// alpha > 0 a visible pixel of the sprite, is clicked
if(pixelColor.a > 0) {
this.mytext.text = 'Hit';
} else {
this.mytext.text = 'No Hit';
}
// just reset the textmessage
setTimeout(_ => this.mytext.text = 'Click the Sprite, or close to it ...' , 1000);
}
}, this);
}
};
const config = {
type: Phaser.AUTO,
width: 400,
height: 200,
scene: Scene
};
const game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

How to convert screen coordinates to scene coordinates

I created a a-scene with some objects to drag. The final purpose is exactly what aframe-click-drag-component does. Unfortunately, this component is not compatible with the last version of A-Frame.
I created a custom component.
AFRAME.registerComponent('draggable', {
init: function () {
/* Some code */
}
});
I use the aframe-mouse-cursor-component to be able to get the mouseenter and mouseleave events on the draggable object, and detect when the mouse position allows the user to select the object.
I added an EventListener on document.body to know when the dragging starts:
document.body.addEventListener('mousedown', function (e) {
// start dragging
});
I continuously update a global variable to update the mouse position when a mousemove occurs:
document.addEventListener('DOMContentLoaded', function () {
document.body.addEventListener('mousemove', function (e) {
window.mouseX = e.clientX;
window.mouseY = e.clientY;
});
});
This way, I can easily get the position of the mouse during the dragging. But I do not know how to convert the position of the mouse on the client to a position in the Virtual Reality (restricted to a 2D plan to make it possible).
I solved this issue by using the raycaster coming from the cursor in the middle of the a-camera, but I want to drag the objects with the mouse-cursor, and this component does not have a raycaster.
I also tried to use some maths to convert the mouse coordinates to a coordinates set relative to the camera, without success (essentially because of the screen size which can vary).
What solutions are available? I would like to update the click-drag or the mouse-cursor, but I have no knowledge of THREE.js.
See https://github.com/mayognaise/aframe-mouse-cursor-component or https://github.com/mrdoob/three.js/blob/dev/examples/js/controls/DragControls.js or https://www.npmjs.com/package/aframe-click-drag-component for examples
The main chunk of code is like:
canvas.addEventListener( 'mousemove', function () {
var mouse = new THREE.Vector2();
var rect = canvas.getBoundingClientRect();
mouse.x = ( (event.clientX - rect.left) / rect.width ) * 2 - 1;
mouse.y = - ( (event.clientY - rect.top) / rect.height ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
}, false);

orbitcontrols.js: How to Zoom In/ Zoom Out

Using orbitcontrols.js (with THREE.js), I want to achieve the same effect in code as rotating the mouse wheel. For example, I want to call something like camera.zoomIn() and have it move a set distance toward the target. Anyone know how to do this?
At least for simple cases, you can change the position of the camera (e.g. multiply x, y and z by a factor), which automagically updates the OrbitControls.
Example with a <input type="range"> slider:
zoomer.addEventListener('input', function(e) {
var zoomDistance = Number(zoomer.value),
currDistance = camera.position.length(),
factor = zoomDistance/currDistance;
camera.position.x *= factor;
camera.position.y *= factor;
camera.position.z *= factor;
});
https://codepen.io/Sphinxxxx/pen/yPZQMV
From looking at the source code you can call .dollyIn(dollyScale) and .dollyOut(dollyScale) on the OrbitalControls object.
Edit: these aren't public methods; one can access them by editing OrbitalControls.js though.
I added this.dIn = dollyIn; to the THREE.OrbitControls function, I can now zoom in by calling controls.dIn(1.05); controls.update(); from outside.
You can set values on OrbitControls.maxDistance and OrbitControls.minDistance to zoom in and out programmatically.
For example, if you wanted to animate a zoom out with gsap you could do something like this:
gsap.to(myOrbitControls, {
minDistance: 20, // target min distance
duration: 1,
overwrite: 'auto',
ease: 'power1.inOut',
onComplete: () => {
myOrbitControls.minDistance = 0 // reset to initial min distance
},
})

How to make a realistic roulette ball spinning animation

I'm using PhysicsJS to make a 2D roulette ball spinning animation.
So far, I've implemented the following:
used a constraint so that the ball wouldn't "fly away":
rigidConstraints.distanceConstraint( wheel, ball, 1 );
used drag to slow down the ball:
world.add(Physics.integrator('verlet', { drag: 0.9 }));
made the wheel attract the ball, so that it would fall towards it when the drag has slowed down the ball enough
My questions:
how do I gradually slow down the ball spinning?
I have already a very high drag value, but it doesn't look like it's doing anything
how do I make attraction towards the wheel work?
The distance constraint should keep the ball from escaping, not from getting closer to the wheel.
why does angularVelocity: -0.005 not work at all on the wheel?
My code, also on JSfiddle
Physics(function (world) {
var viewWidth = window.innerWidth
,viewHeight = window.innerHeight
,renderer
;
world.add(Physics.integrator('verlet', {
drag: 0.9
}));
var rigidConstraints = Physics.behavior('verlet-constraints', {
iterations: 10
});
// create a renderer
renderer = Physics.renderer('canvas', {
el: 'viewport'
,width: viewWidth
,height: viewHeight
});
// add the renderer
world.add(renderer);
// render on each step
world.on('step', function () {
world.render();
});
// create some bodies
var ball = Physics.body('circle', {
x: viewWidth / 2
,y: viewHeight / 2 - 300
,vx: -0.05
,mass: 0.1
,radius: 10
,cof: 0.99
,styles: {
fillStyle: '#cb4b16'
,angleIndicator: '#72240d'
}
})
var wheel = Physics.body('circle', {
x: viewWidth / 2
,y: viewHeight / 2
,angularVelocity: -0.005
,radius: 100
,mass: 100
,restitution: 0.35
// ,cof: 0.99
,styles: {
fillStyle: '#6c71c4'
,angleIndicator: '#3b3e6b'
}
,treatment: "static"
});
world.add(ball);
world.add(wheel);
rigidConstraints.distanceConstraint( wheel, ball, 1 );
world.add( rigidConstraints );
// add things to the world
world.add([
Physics.behavior('interactive', { el: renderer.el })
,Physics.behavior('newtonian', { strength: 5 })
,Physics.behavior('body-impulse-response')
,Physics.behavior('body-collision-detection')
,Physics.behavior('sweep-prune')
]);
// subscribe to ticker to advance the simulation
Physics.util.ticker.on(function( time ) {
world.step( time );
});
// start the ticker
Physics.util.ticker.start();
});
Drag has a bug in that version of PhysicsJS, try using the most updated version from github. https://github.com/wellcaffeinated/PhysicsJS/issues/94
Unfortunately the distance constraint imposes a fixed distance. So to prevent the ball's escape in that way, you'd need to implement your own behavior. (more below)
You'll have to change behavior: "static" to be behavior: "kinematic". Static bodies don't ever move on their own.
To create a custom behavior check out the documentation here: https://github.com/wellcaffeinated/PhysicsJS/wiki/Behaviors#creating-a-custom-behavior
In order to get the functionality you're describing, you'll need to do something like this:
// in the behave method
// calculate the displacement of the ball from the wheel... something like....
disp.clone( wheel.state.pos ).vsub( ball.state.pos );
// if it's greater than max distance, then move it back inside the max radius
if ( disp.norm() > maxDist ){
var moveBy = disp.norm() - maxDist;
disp.normalize(); // unit vector towards the wheel
disp.mult( moveBy );
ball.state.pos.vadd( disp ); // move it back inside the max radius
}
Of course, this is a "just get it done" way of doing this but it should work.

javascript canvas detect click on shape

I have a problem with the click function in javascript. This is my code:
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext('2d');
BigCircle = function(x, y, color, circleSize) {
ctx.shadowBlur = 10;
ctx.shadowColor = color;
ctx.beginPath();
ctx.arc(x, y, circleSize, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
};
var bigGreen = new BigCircle(1580, 800, '#5eb62b', 180);
function init() {
$("#bigGreen").click(function(e){
alert("test");
});
}
$(document).ready(function() {
init();
});
But the click event is not working! Does anybody know why? Thank you so much in advance!
You can now use hit regions in Chrome and Firefox:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility#Hit_regions
(Edit: Hit regions are now obsolete)
or just use one of the many canvas APIs:
http://www.fabricjs.com/
http://www.createjs.com/easeljs
http://www.paperjs.org
etc...
without seeing your html this question is a little bit unclear, it seems you would like to draw something on a canvas and use jquery to add click events for the circle, this isn't possible.
you can use jquery to get the click event ON the canvas and from the cursor position you can calculate if the user clicked the circle or not, but jquery won't help you here you have to do the math yourself.
jquery does only work for dom elements.
BigCircle = function(ctx,x, y, color, circleSize) {
ctx.beginPath();
ctx.arc(x, y, circleSize, 0, Math.PI * 2, true);
ctx.fillStyle=color
ctx.fill();
ctx.closePath();
this.clicked=function(){
ctx.fillStyle='#ff0000'
ctx.fill();
}
};
function init() {
var canvas = document.getElementsByTagName("canvas")[0];
var ctx = canvas.getContext('2d');
var bigGreen = new BigCircle(ctx,50, 50, '#5eb62b', 50);
$('#canvas').click(function(e){
var x = e.clientX
, y = e.clientY
if(Math.pow(x-50,2)+Math.pow(y-50,2) < Math.pow(50,2))
bigGreen.clicked()
})
}
$(document).ready(function() {
init();
});
​
jsfiddle is here
http://jsfiddle.net/yXVrk/1/
Canvas API function isPointInPath() can be used to assist with hit detection. This function can at least tell if mouse coordinates are within a complex shape without doing sophisticated math yourself. May work for simple shapes as well but my use case was for on a bezier curve path. However, you need to either incorporate this function in your drawing logic to test while paths are open or keep an array of Path2d objects to test against. I redraw on onClick handler and pass in mouse coords from event args, but I think I could have kept an array of Path2d objects instead.
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath
bigGreen is not in the HTML, so $("#bigGreen") selects nothing. You can't put a click function on things like JavaScript functions; since they don't exist in the DOM, how could you click one? You should replace #bigGreen with #canvas, since "canvas" is your HTML element.
I forked your fiddle to show this here.
Edit: If you want to see that the user clicked on a particular circle, you use the canvas click event, and then, you determine which circle was clicked by the coordinates passed into the click event.

Categories

Resources