Event issues when rendering three js on a div - three.js

I built my scene in three js and everything was working properly when I rendered the scene on the . But now I need the scene to be on a div, 70% of the viewport to be specific, the other 30% being a left side panel with some info. I replaced every window.height and width for the width and height of the div itself, but now the events are all broken. I tried bubbling and capturing but the problem seems to be related with the camera or something in three js, because when I hover on a corner something is happening...but nothing is happening when i hover/click on the 3d globe. I google everything and didn't find an answer or people with similar issues..I'm hopeless, help? Thanks.
Edit: here's a fiddle: https://jsfiddle.net/2L686cnw/

Did you update all for the renderer relevant variables on window resize?
Just like this:
var onWindowResize = function(){
renderWidth = element.offsetWidth;
renderHeight = element.offsetHeight;
camera.aspect = renderWidth/renderHeight;
camera.updateProjectionMatrix();
renderer.setSize(renderWidth, renderHeight);
};
$(window).on('resize', function() {
onWindowResize();
});

The issue sounds like that you get wrong viewport coordinates for your click position. Most three.js examples add event listeners to window or document object and using event.clientX (and clientY) to transform the mouse screen coordinates to viewport coordinates to use it for raycasting.
Having the 3D scene within a separate div covering only part of the page, it should be a bit different:
function onClick(event) {
var mouse = new THREE.Vector2();
mouse.x = (event.offsetX / panelWidth) * 2 - 1;
mouse.y = - (event.offsetY / panelHeight) * 2 + 1;
// do raycasting
}
canvas.addEventListener('click', onClick);

Related

Drawing mode on lower canvas and how to render controls on upper canvas instead of lower

Im started to develop my project, where fabric js canvas used as a texture on my 3d object, loaded by three js library. Unfortunately I don`t have a full working snippet now, maybe you can show me solution or right way to solve my problem
The problem is the controls drawing on the lower canvas and its transfer on my 3d object, i want to render it on the upper canvas. Is it possible?
The second problem is the brush at first rendered on the upper canvas and after event 'mouse: up' its rendered on the lower canvas. How to render brush line by event 'mouse; move' on the lower canvas at the moment drawing?
p.s. sorry for my english, its really hard to say for me. Hope, you understand my mind
Here you can see my first issue:
https://1drv.ms/u/s!AqtIFlFVWz4MhNtBc3g1DQElfA6_7Q?e=y7ly37
Thank you for you response!
Woow! this is a very good code example! And it works pretty smooth too. It shows the problem a lot better.
I found a solution for the first problem (that is kind of a three.js problem) in the fabric js demo: http://fabricjs.com/controls-customization.
I'm not good with fabric so there might be a more elegant solution than this. You can hide the controls, render to three.js and then show them again. You also have to force the canvas to render, it won't work otherwise.
function animate() {
requestAnimationFrame(animate);
//find the selected object on the fabric canvas
let selected = canvas.getActiveObject();
//hide controles and render on the fabric canvas;
if (selected){
selected.hasControls = false;
selected.hasBorders = false;
canvas.renderAll();
}
//update texture and render
texture.needsUpdate = true;
renderer.render(scene, camera);
//show controls and render on the fabric canvas
if (selected){
selected.hasControls = true;
selected.hasBorders = true;
canvas.renderAll();
}
}
The second problem is a bit harder to understand. I'm not sure what the 'lower canvas' is and if that is a part of fabric that i don't understand. This is the closest i can get. I almost made it work on the three.js renderer. If i understood the idea correctly...
You need to have a variable to know if you are pressing the mouse or not. Only if you are pressing the mouse AND moving the mouse, it draws a circle.
To do the same with fabric 'mouse:move' you will at least have to disable that selectionbox on the fabric canvas. But i do not know how that works in fabric.
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var mouseDown = false;///////////////////////////// global boolean
var onClickPosition = new THREE.Vector2();
container.addEventListener("mousedown", function(){
mouseDown = true;
canvas.selection = false;
onMouseEvt(); //draw circle
}, false);
container.addEventListener("mouseup", function(){
mouseDown = false
canvas.selection = true;
}, false);
container.addEventListener("mousemove", function(e){
if (mouseDown){
onMouseEvt(); //draw circle
}
}, false);
///This code does not work, it goes into selection mode..
canvas.on('mouse:down', function(){
mouseDown = true;
addCircle();
}, false);
canvas.on('mouse:up', function(){
mouseDown = false;
}, false);
canvas.on('mouse:move', function(){
if ( mouseDown ){
addCircle();
}
}, false);
This is a hard question and it's not really a three.js thing, it has more to do with fabric.js and drawing to a canvas. The three.js part of texturing the object seems to work fine.
If I understand correctly you have an overlaying canvas on which you want to render the controls, but they are also rendered to the actual texture canvas? If in case you are actually rendering the controls on the texture; don't do that :p
It should be possible to make it work the way you want to. It would be helpful to post a small code example on https://jsfiddle.net/ or something.

Clickable points on Three.js globe

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.

Display HTML text annotation on click of three.js geometry?

I would like to add 2d text annotations to a 3d object similar to this.
I haven't found any tutorials on overlaying a 3d object with HTML elements that can be turned off and on with click. I did find this but wasn't sure if there was another way other than rendering to the canvas.
Display text over Canvas only onmouseover
Seems like I should be able to toggle display styles of absolute-positioned elements, but I can't figure it out. Any pointers are greatly appreciated
There are a lot tutorials to determine the object when clicking on the canvas (using raycaster and projection). Basically, you want it the other way round: having a 3D coordinate and want to get the 2D coordinate above the canvas where you can postion a html element.
I would do it the following way:
Html structure
<div style="position: relative">
<canvas></canvas>
<div id="tip" style="position: absolute; display: none">Some text</div>
</div>
Determine the clicked object
var selectedObject;
// using jquery
$('canvas').on('click', function(event) {
// get 2D viewport coordinates
var mouse = new THREE.Vector2();
mouse.x = (event.offsetX / SCREEN_WIDTH) * 2 - 1;
mouse.y = - (event.offsetY / SCREEN_HEIGHT) * 2 + 1;
// raycast
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5).unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(arrayOfObjects, true);
if(intersects.length > 0 ) {
selectedObject = intersects[0].object;
// toggle html element
$('#tip').css('display', 'block');
$('#tip').text(selectedObject.userData.note);
// assuming that the actual position is at the center of the mesh, otherwise the text will display somewhere else
// alternatively you could store and pass the point of click: intersects[0].point
positionTip(selectedObject.position);
}
});
position the html element (if you manipulate the camera, then you need to call this each time, so the element's position will update too)
function positionTip(pos3D) {
var v = pos3D.project(camera);
var left = SCREEN_WIDTH * (v.x + 1) / 2;
var top = SCREEN_HEIGHT * (-v.y + 1) / 2;
$('#tip').css({ left: left, top: top });
}
DISCLAIMER: I cannot guarantee that these code snippets work out of the box. I just copied some pieces of code from my project and made some additions/modifications for generalisation.

d3 zoom behaviour is overwriten by mousemove event

I am implementing d3.js zoom behaviour on a svg, on the same svg I also need to capture mouse coordinate so I can set position of a tool tips that follow mouse cursor's position.
The problem is that my 'mousemove' event has override d3.js zoom behaviour.
With 'mousemove' event added, zoom behaviour stop working.
It is either I have 'mousemove' event or 'zoom' event, but not both. Any suggestion how to get around this? Thanks.
// bind zoom behavior
selection_svg.call(
d3.behavior.zoom()
.translate([0, 0])
.scale(1)
.scaleExtent([1, 14])
.on('zoom', ()=>{ selection_plotArea.attr('transform', 'translate('+d3.event.translate+')scale('+d3.event.scale+')'); })
);
// tool tip coordinate
const toolTipNode = selection_toolTip.node();
toolTipNode.style.position = 'absolute';
selection_svg.node().addEventListener('mousemove',function(evt){
const coord_client = {
x:evt.clientX,
y:evt.clientY,
}
toolTipNode.style.left = `${coord_client.x}px`;
toolTipNode.style.top = `${coord_client.y}px`;
}, false);
I have added the code for this problem to fiddle:
https://jsfiddle.net/apollotang/rt9t1vdj/
The problem seems to be related to tooltipNode catching mouse events. By adding some offset to coord_client your problem would be gone.
selection_svg.on('mousemove', function () {
const coord_client = {
x: d3.event.layerX + 10,
y: d3.event.layerY + 10
};
toolTipNode.style.left = `${coord_client.x}px`;
toolTipNode.style.top = `${coord_client.y}px`;
}, false);
Note: I also changed clientX and clientY to layerX and layerY since there was a bug when scrolling changed mouse position and tooltipNode would have been separated from mouse. Besides the event handling code changed to be handled by d3.
If you have a mousemove event attached to your svg, be sure you aren't calling d3.event.stopPropagation() within the mousemove event, or else you will prevent the zoom behavior.

HTML 5 Canvas Mouse over event on element (show tooltip)

I am working on a visualization project. Based on my data I am plotting hundreds of small circle on canvas. I want to add a mouse over event so that whenever a mouse is the enclosing area of a circle it will show some node property from my data as a tool tip or as text on the canvas.
My current drawCircle method
function drawCircle(canvas,x,y,r)
{
canvas.strokeStyle = "#000000";
canvas.fillStyle = "#FFFF00";
canvas.lineWidth = 2;
canvas.beginPath();
canvas.arc(x,y,r,0,Math.PI*2,true);
canvas.stroke();
canvas.fill();
canvas.closePath();
}
I have looked into kinetic.js
But can't figure it out how I can call my drawCircle [repetitively] method using their library.
Any help will be highly appreciated.
If you still want to use KineticJS, you would put the Kinetic shape stuff inside your drawCircle routine. This is basically pulled out of their tutorial and stripped down:
function drawCircle(stage,x,y,r) {
var circle = new Kinetic.Shape(function(){
var context = this.getContext();
// draw the circle here: strokeStyle, beginPath, arc, etc...
});
circle.addEventListener("mouseover", function(){
// do something
});
stage.add(circle);
}
If you don't want to use KineticJS after all, you will need to remember for yourself the positions and radii of every circle you drew, and then do something like this:
canvas.onmouseover = function onMouseover(e) {
var mx = e.clientX - canvas.clientLeft;
var my = e.clientY - canvas.clientTop;
// for each circle...
if ((mx-cx)*(mx-cx)+(my-cy)*(my-cy) < cr*cr)
// the mouse is over that circle
}

Resources