EDIT: I've decided to not use DragControls or TransformControls as both are not doing what I require them to do. For now I've added a pivot Object3D at the cueball's position and made cue (stick) a child of that, and try to make my own mousehandler to rotate, and to pull back the stick.
I'm creating a Pool game in ThreeJS as a school project (GitHub Source).
I currently have the following meshes:
- a table
- a set of balls on their starting position
- a cue (stick).
To rotate the scene, I am using OrbitControls from ThreeJS.
I also have a raycaster in place with which I can click on the cue (using this question).
Now, I want to keep the OrbitControls ability to rotate the world, but I also want to use the ability to rotate the cue around the white ball to shoot.
What I have in mind:
- use an onDocumentMouseDrag() something to detect if I'm clicking on the cue and if so, to where I'm dragging it (rotated)
- In the same function, see if the cue was dragged backwards and then "hit" the white ball with that vector.
Can you help me achieve this?
See below for my "main".js: GitHub: Poolgame.js
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var controls = new THREE.OrbitControls( camera );
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xcee0ff);
document.body.appendChild(renderer.domElement);
var objects = []; //for raycaster
//Table
var pooltable = new PoolTable();
scene.add(pooltable);
//Lighting
scene.add(new BarLighting() );
//ambient light
scene.add( new THREE.AmbientLight( 0x909090 ) ); // soft white light
//Balls
var poolballs = [], ball;
for(var i = 0; i<=15; i++) {
ball = new PoolBall(i);
scene.add(ball);
poolballs.push(ball);
}
var cue = new PoolStick();
cue.position.copy(poolballs[0].position);
cue.rotateZ(- (100 / 90) * Math.PI / 2); //10 degrees above table
scene.add(cue);
objects.push(cue);
var clock = new THREE.Clock();
document.addEventListener( 'mousedown', onDocumentMouseDown );
function onDocumentMouseDown( event ) {
event.preventDefault();
var mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1,
-( event.clientY / window.innerHeight ) * 2 + 1,
0.5 );
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera( mouse3D, camera );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
console.log(intersects[0]);
//has found intersect, i.e. we clicked something: the stick.
}
} // end of onDocumentMouseDown
function render() {
requestAnimationFrame(render);
controls.target.copy(poolballs[0].position);
// controls.target.copy(pooltable.position);
controls.update();
renderer.render(scene,camera);
}
var mouse = new THREE.Vector2();
document.addEventListener('mousemove', onDocumentMouseMove, false);
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
}
camera.position.x = -150;
camera.position.y = 120;
camera.lookAt(pooltable.position);
controls.update();
render();
Related
I would like to select objects in ThreeJs with the mouse like when using a raycaster, but instead of having a single ray, i would like to have circular area around the mouse coordinates, and everything inside that area gets selected.
I created this snippet to visualize what I'm trying to achieve.
const canvas = document.createElement("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
canvas.style.position = "fixed";
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
let selectionRadius = 50;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
camera.position.z = 5;
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2(-1, -1);
function animate() {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
raycaster.setFromCamera( pointer, camera );
const intersects = raycaster.intersectObjects( scene.children );
for ( let i = 0; i < intersects.length; i ++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
}
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
animate();
function onPointerMove( event ) {
pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;
pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.strokeStyle="blue";
ctx.beginPath();
ctx.arc(event.clientX, event.clientY, selectionRadius, 0, 2*Math.PI);
ctx.stroke();
ctx.closePath();
}
window.addEventListener('pointermove', onPointerMove);
html, body {
margin: 0;
padding: 0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.145.0/three.min.js"></script>
As you can see, with the raycaster, I am able to select objects, but not within that radius, so how can I achieve that?
You would need to fire multiple rays... you're currently only firing one. The first argument of raycaster.setFromCamera is a vec2, you can modify this with your values circling the pointer.
As your circle appears to be a 2D element drawn on top it should be straight forward to match the radius. You could use the ctx.arc function to get your new pointer values, just update the start and end angles in a loop.
I would keep the number of rays you cast to a minimum for performance.
I am working with ThreeJS to create a solar system. I have a sun in the middle and 8 orbits around it. Now I want to get the nearest ring poisiton when the users clicks anywhere on the map!
Here is an image to describe it visually what I mean
The arrows stands for the "click" of the user, then there should be a function to get the nearest orbit and its coordinates (the white dots) where the line between the click point and middle collides.
I tried many different functions I found here, but non of them gave me the result I want.
Thanks for your help!
The code looks currently like this:
var container, stats, parent, pivots, domEvents, twins, planets, sun, fleets, raycaster, mouse;
var camera, controls, scene, renderer;
var cross;
planets = new Array();
init();
animate();
function init()
{
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
//init
camera = new THREE.PerspectiveCamera(10, 1, 1, 4000);
camera.position.z = 200;
camera.position.x = 200;
camera.position.y = 200;
controls = new THREE.OrbitControls(camera);
controls.addEventListener('change', render);
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0);
// renderer
renderer = new THREE.WebGLRenderer({antialias: false, alpha: true});
renderer.setSize(document.getElementById('canvasreference').offsetWidth, document.getElementById('canvasreference').offsetWidth);
renderer.setClearColor(0x787878, 0.5); // the default
container = document.getElementById('canvasreference');
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
domEvents = new THREEx.DomEvents(camera, renderer.domElement);
//axihelper
scene.add(new THREE.AxisHelper(130));
// parent
parent = new THREE.Object3D();
scene.add(parent);
//arrays
orbits = new Array();
addOrbit();
window.addEventListener('click', onMouseMove, false);
}
function onMouseMove(event) {
canvas = renderer.domElement;
raycaster = new THREE.Raycaster();
mousePosition = new THREE.Vector2();
canvasPosition = $("#canvasreference canvas").position();
console.log(canvasPosition);
mousePosition.x = ((event.clientX - canvasPosition.left) / canvas.width) * 2 - 1;
mousePosition.y = -((event.clientY - canvasPosition.top) / canvas.height) * 2 + 1;
raycaster.setFromCamera(mousePosition, camera);
var geometry = new THREE.Geometry();
var origin = new THREE.Vector3(raycaster.ray.origin.x, 0, raycaster.ray.origin.y);
geometry.vertices.push(origin);
var vektor = new THREE.Vector3(raycaster.ray.direction.x, 0, raycaster.ray.direction.y);
for (var i = 0; i < 1000; i++)
{
origin.add(vektor);
geometry.vertices.push(vektor);
}
var material = new THREE.LineBasicMaterial({
color: 0xffffff, linewidth: 20
});
var line = new THREE.Line(geometry, material);
scene.add(line);
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(600, 600);
render();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
function render() {
renderer.render(scene, camera);
}
/**
* add Orbit line
* #param {type} orbit
* #returns {undefined}
*/
function addOrbit(orbit)
{
for (var i = 0; i < 8; i++)
{
//make orbit line
var orbit = new THREE.EllipseCurve(
0, 0, // ax, aY
i * 10 + 30, i * 10 + 30, // xRadius, yRadius
0, 2 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
);
var path = new THREE.Path(orbit.getPoints(100));
var geometry = path.createPointsGeometry(100);
var material = new THREE.LineBasicMaterial({color: 0xffffff});
var ellipse = new THREE.Line(geometry, material);
ellipse.rotation.x = 1.5708;
scene.add(ellipse);
}
}
</script>
Cast a ray from your camera to a point where your mouse is. Then check closest distance from that ray to the middle of you solar system using distanceToPoint function. The length of output vector will be the radius of a sphere to which your ray is tangent. Using this length you can determine how close you are to a sphere that is an orbit and if it should be selected. Here's some pseudo code of that check:
var length = getDistanceToCenter(...);
var closestSphere = _(orbits).min(function(orbit) { return Math.abs(length - orbit.radius); });
if (Math.abs(closestSphere.radius - length) < EPSILON) {
selectOrbit(closestSphere);
}
So I've been trying to make a spherical 360 panorama using three.js which implements clickable objects, which at the moment I would like to make hyperlinks.I've read many of the previous examples of raycasting and such, but have had no luck in getting the object to actually redirect me to the site. If someone could tell me where I'm going wrong in the code I'd greatly appreciate it.
I have a feeling the orbiting/panning function under "onDocumentMouseDown" is interfering with the raycasting? I'm still new to this and figuring it out.
<div id="container"></div>
<script src="three.min.js"></script>
<script>
var container, stats;
var camera, controls, scene, renderer;
var objects = [], plane;
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2(),
offset = new THREE.Vector3();
var fov = 70,
texture_placeholder,
isUserInteracting = false,
onMouseDownMouseX = 0, onMouseDownMouseY = 0,
lon = 0, onMouseDownLon = 0,
lat = 0, onMouseDownLat = 0,
phi = 0, theta = 0;
init();
animate();
function init() {
var container, mesh1, sphere1, cube1;
container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 1, 1100 );
camera.target = new THREE.Vector3( 0, 0, 0 );
scene = new THREE.Scene();
mesh1 = new THREE.Mesh( new THREE.SphereGeometry( 500, 60, 40 ), new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'spherical_map_small.jpg' ), transparent: true} ) );
mesh1.scale.x = -1;
scene.add( mesh1 );
meshMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff});
var sphere1 = new THREE.Mesh( new THREE.SphereGeometry( 2.5,20,20 ), meshMaterial );
sphere1.position.set( 50, 10, 0 );
scene.add( sphere1 );
sphere1.userData = { URL:"http://www.google.com"};
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
document.addEventListener( 'DOMMouseScroll', onDocumentMouseWheel, false);
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseDown( event ) {
event.preventDefault();
isUserInteracting = true;
onPointerDownPointerX = event.clientX;
onPointerDownPointerY = event.clientY;
onPointerDownLon = lon;
onPointerDownLat = lat;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( sphere1 );
if ( intersects.length > 0 ) {
controls.enabled = true;
SELECTED = intersects[ 0 ].sphere1;
var intersects = raycaster.intersectObject( sphere1 );
if ( intersects.length > 0 ) {
window.open(intersects[0].object.userData.URL);
}
}
}
function onDocumentMouseMove( event ) {
if ( isUserInteracting ) {
lon = ( onPointerDownPointerX - event.clientX ) * 0.1 + onPointerDownLon;
lat = ( event.clientY - onPointerDownPointerY ) * 0.1 + onPointerDownLat;
}
}
function onDocumentMouseUp( event ) {
isUserInteracting = false;
}
function onDocumentMouseWheel( event ) {
isUserInteracting = false;
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
lat = Math.max( - 85, Math.min( 85, lat ) );
phi = THREE.Math.degToRad( 90 - lat );
theta = THREE.Math.degToRad( lon );
camera.target.x = 500 * Math.sin( phi ) * Math.cos( theta );
camera.target.y = 500 * Math.cos( phi );
camera.target.z = 500 * Math.sin( phi ) * Math.sin( theta );
camera.lookAt( camera.target );
renderer.render( scene, camera );
}
</script>
Looking at your code I notice that you create var mouse = new THREE.Vector2(), then you don't set its values at any point in the supplied code. Then in onDocumentMouseDown() you cast a ray into the scene with undefined mouse coordinates raycaster.setFromCamera( mouse, camera ); As var mouse has not been set it is very likely that that is the reason you are not getting the navigation to start.
What you need to do is get the normalised screen coordiantes of the mouse and pass that into the raycaster.setFromCamera I can not quite remenber if the screen is one or two units but something like
mouse.x = (event.clientX / window.innerWidth); // normalise the mouse screen pos
mouse.y = (event.clientY / window.innerHeight); // same
mouse.x *= 2; // 0 is the center. -1 is left and 1 is right
mouse.y -= 1; // center
mouse.y *= -2; // Think this is upside down If it does not work try positive 2
mouse.y += 1; // center
if it does not work try switching mouse.y the otherway around;
mouse.y *= 2; // remove the -2 and put in 2
mouse.y -= 1; // remove the += and put in -=
What I find very handy when messing about in 3D is to have a spare debug object in the scene. Something simple like a box or sphere. Use it to show a point on the raycaster's ray.
Something like
// creat a box
var box... blah blah box creation code/
boxP = new vector3(); // position of debug object
// position it halfway in the raycasters range
boxP.x = camera.x + rayCaster.ray.x* ((rayCaster.near+rayCaster.far)/2);
boxP.y = camera.y + rayCaster.ray.y* ((rayCaster.near+rayCaster.far)/2);
boxP.z = camera.z + rayCaster.ray.z* ((rayCaster.near+rayCaster.far)/2);
box.position.set(boxP.x,boxP.y,boxP.z);
Now with luck you should see where you clicks are going.
Also, I am not sure but if you are looking at the sphere from the inside you may have to set the material to doubleSided (I can't see it in your code) as the raycaster ignores faces with normals pointing away. Or try reversing the direction of each polygon.
That's about all I can suggest at the moment. Hope you find the problem.
I'm trying to create a clouds fly through similar to this, only with more maneuverability:
http://mrdoob.com/lab/javascript/webgl/clouds/
I had no problem creating that using sprites, but I want to improve the performance so I'm trying to use particles with THREE.PointCloud.
The problem is that when I fly toward the particles, instead of "colliding" in the camera there's some weird effect when the particle hangs in front of the camera and then slides to the side, I made an example here: http://jsfiddle.net/6avpd6me/36/
All the particles are moving toward the camera in a straight line, but non of them actually colliding in it, changing the size doesn't help.
Anybody knows what's causing this and maybe how can I fix that?
Thanks
JSFiddle code:
var geometry, renderer, scene, camera, cloud, uniforms, attributes;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// scene
scene = new THREE.Scene();
//camera
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 600;
var particleMaterial = new THREE.PointCloudMaterial({
color:0xffffff,
size: 100,
transparent:true,
opacity:0.5,
sizeAttenuation:false
});
particleMaterial.depthTest = true;
particleMaterial.depthWrite = false;
// point cloud geometry
geometry = new THREE.PlaneGeometry( 10, 10, 5, 5);
for( var i = 0; i < geometry.vertices.length; i++)
{
geometry.vertices[i].velocity = new THREE.Vector3(0, 0, random(50, 300) / 100);
}
cloud = new THREE.PointCloud(geometry, particleMaterial);
scene.add( cloud );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
for( var i = 0; i < geometry.vertices.length; i++ )
{
geometry.vertices[i].add(geometry.vertices[i].velocity);
if (geometry.vertices[i].z > 650) geometry.vertices[i].z = 0;
}
geometry.verticesNeedUpdate = true;
renderer.render( scene, camera );
}
function random(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
I have been struggling with issues concerning raycasting on small circlegeometries on a sphere.
I know raycasting can't be done with sprites and this is why I use circlegeometries, but it doesn't work all the time, and moreover the raycasting doesn't always work on circles but sometimes around them as well.
Does anybody have an idea ? Here is a JSBin to show you basically
Edit :
I updated my previous version of JSBin, you can click any circleGeometries it will work here, run it with output tab only open for better results
This is related to the renderer width and height properties, my sphere isn't in fullscreen and this is why it fails.
Does anybody have an idea on how to set up it right in order to get this to work perfectly ?
The formula used to compute intersections wasn't the good one, here is the one that works :
mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1;
mouse x and y have slightly changed from the examples you can get, and are now fine.
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
projector.unprojectVector(vector, camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(
camera.position).normalize());
var intersects = ray.intersectObjects(objects);
if ( intersects.length > 0 ) {
//do something
}
if you looking for some thing like this....Your code might need little changes... check this link http://jsfiddle.net/ebeit303/rjJ6q/
// standard global variables
var container, scene, camera, renderer, controls, stats;
var clock = new THREE.Clock();
// custom global variables
var targetList = [];
var projector, mouse = { x: 0, y: 0 };
init();
animate();
// FUNCTIONS
function init()
{
// SCENE
scene = new THREE.Scene();
// CAMERA
var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 100000;
camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
scene.add(camera);
camera.position.set(600,0,-1200);
camera.lookAt(scene.position);
// RENDERER
renderer = new THREE.CanvasRenderer();
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.getElementById( 'ThreeJS' );
container.appendChild( renderer.domElement );
// EVENTS
// CONTROLS
// this material causes a mesh to use colors assigned to faces
var faceColorMaterial = new THREE.MeshBasicMaterial(
{ color: 0xffffff, vertexColors: THREE.FaceColors } );
var sphereGeometry = new THREE.SphereGeometry( 500, 64, 64 );
for ( var i = 0; i < sphereGeometry.faces.length; i++ )
{
face = sphereGeometry.faces[ i ];
face.color.setRGB( 0, 0, 0.8 * Math.random() + 0.2 );
}
var sphere = new THREE.Mesh( sphereGeometry, faceColorMaterial );
sphere.rotation.set(0, 14.5, 0);
scene.add(sphere);
//targetList.push(sphere);
var j=0;
for (var i =0; i<100;i+=5){
//var circle = new THREE.CubeGeometry(5,5,5);
var circle = new THREE.CircleGeometry(5, 8, 0, Math.PI * 2);
//THREE.GeometryUtils.triangulateQuads(circle);
var circleMaterial = new THREE.MeshBasicMaterial({color: 0xDEF2EF});
circleMaterial.side = THREE.DoubleSide;
var mesh = new THREE.Mesh(circle, circleMaterial);
var Alon = i - 90;
var Alat = j;
var Aphi = Math.PI/2 - Alat * Math.PI / 180;
var Atheta = 2 * Math.PI - Alon * Math.PI / 180;
mesh.position.x = Math.sin(Aphi) * Math.cos(Atheta) * (501);
mesh.position.y = Math.cos(Aphi) * (501);
mesh.position.z = Math.sin(Aphi) * Math.sin(Atheta) * (501);
mesh.verticesNeedUpdate = true;
mesh.lookAt( sphere.position );
sphere.add(mesh);
targetList.push(mesh);
j++;
}
// initialize object to perform world/screen calculations
projector = new THREE.Projector();
// when the mouse moves, call the given function
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( event )
{
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersects = ray.intersectObjects( targetList );
if ( intersects.length > 0 )
{
intersects[ 0 ].object.material.color.setRGB( 0.8 * Math.random() + 0.2,
0.8 * Math.random() + 0.2,
0.8 * Math.random() + 0.2 );
}
}
function animate()
{
requestAnimationFrame( animate );
render();
}
function render()
{
renderer.render( scene, camera );
}