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();
}
}
Related
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 );
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!
I have looked into using three.js but could not find any examples specific to what I need.
I am trying to create a 3D Map (on a globe) that has 3 different pin points around the world. I'd like to be able to detect which pinpoint was clicked on, in order to display information elsewhere on the page.
Is anyone aware of a way to do this? Any demos or tutorials would be very helpful.
Thank you.
Use an event listener to fire off a raycaster on mouse click. I quickly cut and pasted some code from one of my projects so that it does a raycast from the mouse in screenspace to an object in 3D space and updates the color of the material to red.
This should at least get you started in the right direction:
var mouse = new THREE.Vector2(0,0);
var raygun = new THREE.Raycaster();
var useRaycast = true;
// Raycast when we click the mouse
function onClick() {
// Get mouse position in screen space
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// Only raycast if not panning (optimization)
var hits;
if (useRaycast) {
raygun.setFromCamera(mouse, camera);
// Raycast to single object
hits = raygun.intersectObject(myTargetObect, false);
// Raycast to multiple objects
// hits = raygun.intersectObjects([myTargetObect, myTargetObect2]);
}
// Run if we have intersections
if (hits.length > 0) {
for (var i = 0; i < hits.length; i++ ) {
// Change material color of item we clicked on
hits[i].object.material.color.set(0xff0000);
}
renderer.render(scene, camera);
}
}
window.addEventListener('click', onClick, false);
MrDoob also has an example on github with a web demo.
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.
I'm working on a map editor/ line of sight calculator for an online game using an "infinite" canvas that the user can scroll. My goal is to draw a line from a player's position (currently static) to the cursor position. So far, I can draw the line just fine, but when I scroll the canvas just far enough, I get a pretty bad visual bug. It appears as if the draw() function is no longer clearing the screen correctly. I can confirm that the line is the culprit by removing that block of code.
My (stripped down) drawLine() function:
function drawLine() {
context.beginPath();
context.moveTo(150, 300);
context.lineTo(Mouse.x, -Mouse.y);
context.lineWidth = 1;
context.stroke();
context.closePath();
}
My draw() function (variable grid is just a repeating pattern):
function draw() {
context.clearRect(-translatedX, -translatedY, window.innerWidth, window.innerHeight);
context.rect(-window.innerWidth, -window.innerHeight, window.innerWidth * 2, window.innerHeight * 2);
context.fillStyle = grid;
context.fill();
}
And my pan() function that triggers onmouseove:
function pan(e) {
var evt = e || event;
if(dragging == true) {
deltaX = evt.offsetX - lastX;
deltaY = evt.offsetY - lastY;
translatedX += deltaX;
translatedY += deltaY;
context.translate(deltaX, -deltaY);
lastX = evt.offsetX;
lastY = evt.offsetY;
}
Mouse = {
x: evt.offsetX - (window.innerWidth / 2 + translatedX),
y: evt.offsetY - (window.innerHeight / 2 + translatedY)
}
draw();
}
If any more information is required, I will be happy to supply.
EDIT: Updated with screenshot and link:
http://iamchristopher.ca/editor/
I've been working away at this problem and I think I've found a solution. I still don't know why my line was causing the issue, but the answer was the modify the context.rect() to include the translated coordinates:
context.rect(-window.innerWidth - translatedX, -window.innerHeight + translatedY, window.innerWidth * 2, window.innerHeight * 2);
Everything seems to be working fine now. Thanks again to #Patashu.