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

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.

Related

How to use x2 canvas elements and rendering paper.js on img capture of html5 video to base64

Still very much a newbie to coding, so please be gentle :)
I'm hoping someone might be able to help how to use Paper.js on a second canvas after the first one has been executed?
I'm trying to use x2 canvas elements:
Canvas 1 - to capture a html5 video image still and convert to base64 (tick :-) = done)
Canvas 2 - Use the base64 image and perform the 'Working with Rasters to find the colors of pixels' and convert to circle paths (boo = fail :-( )
Something like this:
The code:
<script src="https://cdn.jsdelivr.net/npm/hls.js#latest"></script>
<video id="video" preload="auto" muted="" playsinline="" width="580" src="blob:https://www.georgefisher.co.uk/78e3a45c-ae07-4ea5-af56-45a5ed9cf1b0"></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'https://camsecure.co/HLS/georgefisher.m3u8';
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
}
video.play()
</script>
<br>
<button onclick="capture()">Capture</button>
<br>
<canvas id="canvas" style="overflow:auto">
</canvas>
<canvas id="canvas2" resize>
<img src="" id="myImg"/></canvas>
var resultb64="";
function capture() {
var canvas = document.getElementById('canvas');
var video = document.getElementById('video');
canvas.width = video.videoWidth/4;
canvas.height = video.videoHeight/4;
canvas.getContext('2d').drawImage(video, 0, 0, video.videoWidth/4, video.videoHeight/4);
resultb64=canvas.toDataURL();
document.querySelector("#myImg").src = canvas.toDataURL();
}
/*Paper JS Setup for working in CodePen */
/* ====================== *
* 0. Initiate Canvas *
* ====================== */
// expose paperjs classes into global scope
paper.install(window);
// Only executed our code once the DOM is ready.
window.onload = function() {
// bind paper to the canvas
paper.setup('canvas2');
// paper.activate();
// Get a reference to the canvas object
var canvas = document.getElementById('canvas2');
var ctx = canvas.getContext('2d');
// console.log(ctx, image);
// ctx.drawImage(image, 0, 0);
// return;
// }
// Create a raster item using the image id='' tag
var image = document.querySelector('img');
var raster = new Raster(image);
// Hide the raster:
raster.visible = false;
// The size of our grid cells:
var gridSize = 15;
// Space the cells by 120%:
var spacing = 1
;
// As the web is asynchronous, we need to wait for the raster to load before we can perform any operation on its pixels.
raster.onLoad = function() {
// Since the example image we're using is much too large, and therefore has way too many pixels, lets downsize it to 40 pixels wide and 30 pixels high:
raster.size = new Size(40, 30);
for (var y = 0; y < raster.height; y++) {
for(var x = 0; x < raster.width; x++) {
// Get the color of the pixel:
var color = raster.getPixel(x, y);
// Create a circle shaped path:
var path = new Path.Circle({
center: new Point(x, y).multiply(gridSize),
radius: gridSize / 2 / spacing,
});
// Set the fill color of the path to the color
// of the pixel:
path.fillColor = color;
}
}
// Move the active layer to the center of the view, so all the created paths in it appear centered.
project.activeLayer.position = view.center;
}
}
I've tried giving the second canvas a different Id="canvas2" and referencing that, which I can see in the console. However, nothing appears in the second canvas and the paper.js script doesn't seem to execute, can someone help me understand why?
Please see also see link to the fiddle below:
https://jsfiddle.net/jmnes/o4Lpkfs6/1/
Alternatives method.
You don't need to capture the video, you don't need to capture the pixels using paper.js and raster. You don't need to find the color of each circle and draw it.
All these methods are slow, complex, and power hungry.
You can create a mask and mask out the circles, with the colors drawn from a smaller canvas with a res that matches the number off circles.
How to
Add one (main canvas) canvas to the DOM. This will display the result
Create 2 offscreen canvas.
One (color canvas) has the same resolution as the circles you want to display. Eg if you have 30 by 40 circle the canvas res should be 30 by 40
One (mask canvas) is the circle mask. It is the same resolution as the main canvas. Draw the circles all in one color on this canvas.
Then rendering once a frame
Draw the video on the color canvas to fit.
Turn off smoothing on the main canvas eg ctxMain.imageSmoothingEnabled = false
Draw the color canvas onto the main canvas to fit.
This will draw a color square at each circle position. ctx.drawImage(colorCanvas, 0, 0, mainCanvas.width, mainCanvas.height)
Set composite operation "destination-in" eg ctxMain.globalCompositeOperation = "destination-in"
Draw the mask canvas (canvas with circles on it) onto the main canvas. This will remove pixels outside each circle.
Restore default composite operation for the main canvas ctxMain.globalCompositeOperation = "source-over"
All done for a real-time FX on almost any device.
The above methods is the fastest way to render the effect you are after using the 2D API

Event issues when rendering three js on a div

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);

KineticJS rotating/animating text

I have this fiddle where I have text arranged in a circle, I would like now to animate it and rotate the text in a clockwise/counter clockwise motion.
Every animation demo I have seen uses a container as the starting point however all the examples i could find about manipulating text in a circular arrangement have all started with the element. I have tried 100's of variations trying to get this working but I am either missing something or it's not possible with the construction i have used thus far.
Here is the fiddle for the circular text I have so far:
http://jsfiddle.net/jamesburt/Sa2G8/
<canvas id="canvas1" width="500" height="500"></canvas>
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
Where as the animation examples start off with:
<div id="container"></div>
var stage = new Kinetic.Stage({container: 'container'});
I'm open to any ideas / rewrites needed as ultimately my goal is an animated text circle.
Also if this is easily accomplished in an alternative to KineticJS I'd be interested in trying that out.
Here is a demo I made using KineticJS: http://jsfiddle.net/Moonseeker/Xf7hp/
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500
});
var layer = new Kinetic.Layer();
var myText = "My text in a circle. ";
var centerCoords = {x:250, y:250};
for(var i=0;i<myText.length;i++){
var rotation = i*360/myText.length;
var oneChar = new Kinetic.Text({
x: centerCoords.x,
y: centerCoords.y,
text: myText[i],
fontSize: 30,
fontFamily: 'Calibri',
fill: 'green',
offset: {x:0, y:100},
rotationDeg: rotation
});
layer.add(oneChar);
}
// add the layer to the stage
stage.add(layer);
var angularSpeed = Math.PI / 2;
var anim = new Kinetic.Animation(function(frame){
var angleDiff = frame.timeDiff * angularSpeed / 1000;
for(var i=0;i<layer.children.length;i++){
layer.children[i].rotate(angleDiff);
};
}, layer);
anim.start();
You can rotate at every direction or speed you wish, you can change the style of the circle.
You should be able to use layer.find('Text').each() instead of the for-loop for looping through the text to rotate but the KineticJS library at jsfiddle seems outdated as it doesn't know the find method.
One efficient way:
Render your text-around-a-circle on an offscreen canvas.
Save that offscreen canvas as an image using .toDataURL
Create a Kinetic.Image from that offscreen image.
You can then efficiently rotate/animate the Kinetic.Image as you need.

Positioning an HTML5 Canvas Element

I am completely new to HTML5 and have been reading about it for the past few days mainly because I wanted to create a rotating image to put in a <div>. I found a code that does exactly what I want, but it throws the canvas on to the bottom left corner of my page (I'm not sure why, but I think it has something to do with the very first line of the code below). I'm not sure how to adapt the code to a element so that I can put it where I want. From looking at other people's scripts and trying to emulate them, I know you're supposed to do this sort of thing to hold the canvas "<canvas width="100" height="100" id="pageCanvas"></canvas>," but I don't know how to name the below code in order to do that. I greatly appreciate any help anyone can offer me - thank you so much for reading! :)
<script>
window.addEventListener("load", init);
var counter = 0,
logoImage = new Image(),
TO_RADIANS = Math.PI/180;
logoImage.src = 'IMG URL';
var canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
var context = canvas.getContext('2d');
document.body.appendChild(canvas);
function init(){
setInterval(loop, 1000/30);
}
function loop() {
context.clearRect(0,0,canvas.width, canvas.height);
drawRotatedImage(logoImage,100,100,counter);
drawRotatedImage(logoImage,300,100,counter+90);
drawRotatedImage(logoImage,500,100,counter+180);
counter+=2;
}
function drawRotatedImage(image, x, y, angle) {
// save the current co-ordinate system
// before we screw with it
context.save();
// move to the middle of where we want to draw our image
context.translate(x, y);
// rotate around that point, converting our
// angle from degrees to radians
context.rotate(angle * TO_RADIANS);
// draw it up and to the left by half the width
// and height of the image
context.drawImage(image, -(image.width/2), -(image.height/2));
// and restore the co-ords to how they were when we began
context.restore();
}
</script>
Create a canvas element in your HTML code so you can place it exactly where you want (with html + css) :
<canvas id='canvas' height='100' width='100'> Your browser does not support HTML5 canvas </canvas>
And replace this javascript code :
var canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
var context = canvas.getContext('2d');
document.body.appendChild(canvas);
by this one :
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

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