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.
Related
I have a 3d model(.obj) of a house that is being rendered using 3js. The raycaster in my code is working but I am having an issue of it selecting every object behind and in front of the object I am trying to select. I only want to return the name of the object I select instead. Any help would be appreciated. Currently I'm just having the name of the object getting sent to the console for now.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const renderer2 = new THREE.WebGLRenderer({canvas});
var kitchenCameraActive = false;
document.getElementById("roomSelect").addEventListener("change", changeIt);
function changeIt(e) {
//e.target.value
document.getElementById(e.target.value).click();
console.log(e);
}
var fov = 45;
var aspect = 2; // the canvas default
var near = 0.1;
var far = 100;
var camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(-97.570, 5.878, -5.289);
camera.rotation.set(0,0,0);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();
document.getElementById("kitchen").addEventListener("click", changeCamera);
document.getElementById("bathroom").addEventListener("click", changeCamera);
document.getElementById("deck").addEventListener("click", changeCamera);
document.getElementById("livingRoom").addEventListener("click", changeCamera);
document.getElementById("bedRoom").addEventListener("click", changeCamera);
document.getElementById("walkway").addEventListener("click", changeCamera);
document.getElementById("sideHouse").addEventListener("click", changeCamera);
document.getElementById("frontPorch").addEventListener("click", changeCamera);
document.getElementById("garageDoor").addEventListener("click", changeCamera);
document.getElementById("insideGarage").addEventListener("click", changeCamera);
function changeCamera(e) {
camera.rotation.set(e.toElement.attributes[5].nodeValue, e.toElement.attributes[6].nodeValue, e.toElement.attributes[7].nodeValue);
camera.fov = e.toElement.attributes[4].nodeValue;
camera.position.set(e.toElement.attributes[1].nodeValue, e.toElement.attributes[2].nodeValue, e.toElement.attributes[3].nodeValue);
camera.updateProjectionMatrix();
if (e.target.id == "walkway" || e.target.id == "frontPorch" || e.target.id == "garageDoor" || e.target.id == "insideGarage")
{
controls.target.set(0, 5, 0);
controls.update();
}
//controls.target.set(0, 5, 0);
//controls.update();
//controls.minPolarAngle = Math.PI/2;
//controls.maxPolarAngle = Math.PI/2;
console.log(e);
//controls.enabled = false;
}
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
{
const planeSize = 40;
}
function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
const halfFovY = THREE.Math.degToRad(camera.fov * .5);
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
// compute a unit vector that points in the direction the camera is now
// in the xz plane from the center of the box
const direction = (new THREE.Vector3()).subVectors(camera.position, boxCenter).multiply(new THREE.Vector3(1, 0, 1)).normalize();
// move the camera to a position distance units way from the center
// in whatever direction the camera was from the center already
camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
// pick some near and far values for the frustum that
// will contain the box.
camera.near = boxSize / 100;
camera.far = boxSize * 100;
camera.updateProjectionMatrix();
// point the camera to look at the center of the box
camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
}
var loader = new THREE.OBJLoader();
// load a resource
loader.load(
// resource URL
'dapHouseGood5.obj',
// called when resource is loaded
function ( object ) {
scene.add( object );
},
// called when loading is in progresses
function ( xhr ) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
// called when loading has errors
function ( error ) {
console.log( 'An error happened' );
});
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function onPositionChange(o) {
console.log("position changed in object");
console.log(o);
}
controls.addEventListener('change', onPositionChange);
var mouse = new THREE.Vector2();
var raycaster, mouse = { x : 0, y : 0};
init();
function init () {
//Usual setup code here.
raycaster = new THREE.Raycaster();
renderer.domElement.addEventListener( 'click', raycast, false );
//Next setup code there.
}
function raycast ( e ) {
//1. sets the mouse position with a coordinate system where the center
// of the screen is the origin
mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
//2. set the picking ray from the camera position and mouse coordinates
raycaster.setFromCamera( mouse, camera );
//var mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
//raycaster.setFromCamera( mouse3D, camera );
//3. compute intersections
var intersects = raycaster.intersectObjects( scene.children, true );
for ( var i = 0; i < intersects.length; i++ ) {
//console.log( intersects[ i ].object.id );
console.log( intersects[ i ].object.name );
//console.log( intersects[ i ] );
}
}
}
main();
The raycaster.intersectObjects( objects, recursive = true ) call returns intersections against all of the objects you provide, and their children. If you only want to find intersections on a specific object, only include that object when calling intersectObjects. This will improve performance, as well.
If you don't know which object you want, the intersection results are returned sorted by distance. The closest object is first, and you can just use that.
See: https://threejs.org/docs/#api/en/core/Raycaster
I want to display different texts at different-different coordinates using three js. And I want to show the text like in the following image file at different-different location.
I want that function should display text at different-different location as like in linked image file
Javascript
var container, camera, scene, renderer, controls;
var text2;
init();
animate();
function init(){
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 1000 );
camera.position.z = 1 ;
scene = new THREE.Scene();
text2 = createLabel("Tower",30,90,30,10,50,"blue","white");
text3 = createLabel("Hello",30,90,50,60,50,"black","white");
text4 = createLabel("Tower",30,90,120,100,50,"red","white");
console.log( text2 );
scene.add( text2 );
scene.add( text3 );
scene.add( text4 );
text2.lookAt( camera.position );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setClearColor(0xffffff);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.append( renderer.domElement );
}
function createLabel(text, x, y, canvasPosition_x,canvasPosition_y, size, color, backGroundColor, backgroundMargin) {
var canvas = document.createElement("canvas");
// set resolution
canvas.width = 512;
canvas.height = 512;
var context = canvas.getContext("2d");
context.font = size + "pt Arial";
context.font = size + "pt Calibri";
context.fillStyle = "white";
canvas.position = "absolute";
context.fillRect( 0, 0, canvas.width, canvas.height );
context.fillStyle = color;
context.fillText( text, x, y );
context.position = "absolute";
var texture = new THREE.CanvasTexture( canvas );
var material = new THREE.MeshBasicMaterial({
map: texture,
// color: 0xff0000 // useful for debugging. in this way you see, how much are of the plane is used for the text
});
var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry(), material);
mesh.position.x = canvasPosition_x;
mesh.position.y = canvasPosition_y;
return mesh;
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
Here is link of fiddle
You can use the position-attribute of the mesh to change the location. So for instance mesh.position.x = 0.5;.
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();
I am using THREE.js to display a 3d rotating earth in the browser. I also want a image to appear around the rotating earth.
I tried using the built in method,
var img = new THREE.ImageLoader();
img.load("texture/circle.png");
But the image does not appear only. It's just the rotating sphere.
I want something like this
Here is my script tag,
<script>
var container, stats;
var camera, scene, renderer;
var group;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
function init() {
container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 650;
scene = new THREE.Scene();
group = new THREE.Object3D();
scene.add( group );
// earth
var loader = new THREE.TextureLoader();
loader.load( 'textures/land_ocean_ice_cloud_2048.jpg', function ( texture ) {
var geometry = new THREE.SphereGeometry( 200, 20, 20 );
var material = new THREE.MeshBasicMaterial( { map: texture, overdraw: true } );
var mesh = new THREE.Mesh( geometry, material );
group.add( mesh );
} );
// shadow
var canvas = document.createElement( 'canvas' );
canvas.width = 128;
canvas.height = 128;
var context = canvas.getContext( '2d' );
var gradient = context.createRadialGradient(
canvas.width / 2,
canvas.height / 2,
0,
canvas.width / 2,
canvas.height / 2,
canvas.width / 2
);
//gradient.addColorStop( 0.1, 'rgba(210,210,210,1)' );
//gradient.addColorStop( 1, 'rgba(255,255,255,1)' );
context.fillStyle = gradient;
context.fillRect( 0, 0, canvas.width, canvas.height );
var texture = new THREE.Texture( canvas );
texture.needsUpdate = true;
var geometry = new THREE.PlaneGeometry( 300, 300, 3, 3 );
var material = new THREE.MeshBasicMaterial( { map: texture, overdraw: true } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.y = - 250;
mesh.rotation.x = - Math.PI / 2;
group.add( mesh );
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove( event ) {
mouseX = ( event.clientX - windowHalfX );
mouseY = ( event.clientY - windowHalfY );
}
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
//camera.position.x += ( mouseX - camera.position.x ) * 0.05;
//camera.position.y += ( - mouseY - camera.position.y ) * 0.05;
camera.lookAt( scene.position );
group.rotation.y -= 0.003;
renderer.render( scene, camera );
}
</script>
The plane is horizontal. Remove this line:
mesh.rotation.x = - Math.PI / 2;
Make sure the gradient is set up the way you want it, e.g.,
gradient.addColorStop( 0.9, 'rgba( 210, 210, 210, 1 )' );
gradient.addColorStop( 1, 'rgba( 0, 0, 0, 0 )' );
Also, CanvasRenderer will have artifacts with intersecting objects.
three.js r.62
This site is loading a maya model using three.js.
This model has Below texture pictures
Here is the JS
var SCREEN_WIDTH = window.innerWidth;
var SCREEN_HEIGHT = window.innerHeight;
var container;
var camera, scene;
var canvasRenderer, webglRenderer;
var mesh, zmesh, geometry, materials;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var meshes = [];
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 100000);
camera.position.x = 400;
camera.position.y = 200;
camera.position.z = 400;
scene = new THREE.Scene();
// LIGHTS
var ambient = new THREE.AmbientLight(0x666666);
scene.add(ambient);
var directionalLight = new THREE.DirectionalLight(0xffeedd);
directionalLight.position.set(0, 70, 100).normalize();
scene.add(directionalLight);
// RENDERER
webglRenderer = new THREE.WebGLRenderer();
webglRenderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
webglRenderer.domElement.style.position = "relative";
container.appendChild(webglRenderer.domElement);
var loader = new THREE.JSONLoader(),
callbackKey = function (geometry, materials) {
createScene(geometry, materials, 0, 0, 0, 6)
};
loader.load("chameleon.js", callbackKey);
window.addEventListener('resize', onWindowResize, false);
}
function createScene(geometry, materials, x, y, z, scale) {
zmesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
zmesh.position.set(x, y, z);
zmesh.scale.set(scale, scale, scale);
meshes.push(zmesh);
scene.add(zmesh);
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
webglRenderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
for (var i = 0; i < meshes.length; i++) {
meshes[i].rotation.y += .01;
}
requestAnimationFrame(animate);
render();
}
function render() {
camera.lookAt(scene.position);
webglRenderer.render(scene, camera);
}
$(document).ready(function () {
init();
animate();
});
now i want to change the 1st texture picture to some other texture and rest of the texture remains same on runtime! how to do it?
if you'd like to change the texture at runtime. All you need to do is look at the zmesh objects material. Find the appropriate index of the blue dress material and swap it out. Your model is a little tricky in that you have an array of materials but no matter. For a single material object you simply change the mesh.material.map and update it, in your case we need mesh.material.materials[index].map. So try adding this to the bottom of your createScene function. It will replace the dress with the eyeball texture:
zmesh.material.materials[1].map = THREE.ImageUtils.loadTexture( 'c006_10.jpg' );
Of course, replace 'c006_10.jpg' with the appropriate path to your eyeball texture. One added Note, if you hook up the texture swap to an onclick for example you'll want to have an active render loop or call renderer's render function to get it to display.