Three js - the best way to draw text and line - three.js

after gone through three.js docs, I found it is quite hard to draw line with stroke width, and text.
For the line with stroke width I have to draw an rectange and adjust it to look likes a thick line
For the text, there only two option, one is using font loader, which I found it very hard to handle the promise of font loader, and it become slower if I have so many text.
But if I use canvas to draw the text then use sprite to add that canvas, then I got my texts keep rotates whenever I rotate my camera
so it is 2018, is there any way to make life more easier with three js.
Thanks all
my code to create a thick curve line
function curveLine(start, mid, end, width) {
var curveLine = new THREE.Shape();
curveLine.moveTo(start.x, start.y + width);
curveLine.quadraticCurveTo(mid.x, mid.y + width, end.x, end.y + width);
curveLine.lineTo(end.x, end.y);
curveLine.quadraticCurveTo(mid.x, mid.y, start.x, start.y);
curveLine.lineTo(start.x, start.y);
return curveLine;
}
var line = curveLine({x:0,y:0}, {x:120,y:80}, {x:240,y:0}, 20);
var lineGeo = new THREE.ExtrudeGeometry(line, extrudeSettings);
var lineMesh = new THREE.Mesh(lineGeo, new THREE.MeshBasicMaterial({color: 'red'}));
scene.add(lineMesh);
And this my code to draw text, current using canvas to draws
function text2D(text, params) {
var canvas = document.createElement('canvas');
canvas.width = params.radius * 4;
canvas.height = params.radius * 2;
var context = canvas.getContext('2d');
context.font = '20px Verdana';
var metrics = context.measureText(text);
var textWidth = metrics.width;
context.fillStyle = params.color;
context.fillText(text, (canvas.width - textWidth) / 2, params.radius);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial({map: texture});
var sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(canvas.width, canvas.height, 1);
return sprite;
}
and then whenever i rotate the camera, the text keep face the camera. is there any way to prevent that
Cheers all

Related

Change sprite text dynamic in three.js

I use sprite create sprite in three.js.
And it works. But i want to change the canvas texture dynamic.
Maybe i can use fillText to change the text.But it works nor very well.
I just want it looks like electronic watch.
let canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
let ctx = canvas.getContext('2d');
drawBg(ctx, bg);
drawText(ctx, data);
let texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true;
let material = new THREE.SpriteMaterial({map: texture});
let spriteOject = new THREE.Sprite(material);
spriteOject.scale.set(scale * w / h, scale)
spriteOject.position.set(positions[0], positions[1], positions[2]);

Sprite not showing

I have some code at https://jsfiddle.net/72mnd2yt/1/ that doesn't display the sprite I'm trying to draw. I tried to follow the code over at https://stemkoski.github.io/Three.js/Sprite-Text-Labels.html and read it line by line, but I'm not sure where I went wrong. would someone mind taking a look at it?
Here is some relevant code:
// picture
var getPicture = function(message){
var canvas = document.createElement('canvas');
canvas.width = "100%";
canvas.height = "100%";
var context = canvas.getContext('2d');
context.font = "10px";
context.fillText(message, 0, 10);
var picture = canvas.toDataURL();
// return picture;
return canvas;
};
// let there be light and so forth...
var getScene = function(){
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
70,
$('body').width(),
$('body').height(),
1,
1000
);
var light = new THREE.PointLight(0xeeeeee);
scene.add(camera);
scene.add(light);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xefefef);
renderer.setSize($('body').width(), $('body').height());
camera.position.z = -10;
camera.lookAt(new THREE.Vector3(0, 0, 0));
return [scene, renderer, camera];
};
// now for the meat
var getLabel = function(message){
var texture = new THREE.Texture(getPicture(message));
var spriteMaterial = new THREE.SpriteMaterial(
{map: texture }
);
var sprite = new THREE.Sprite(spriteMaterial);
//sprite.scale.set(100, 50, 1.0);
return sprite
};
var setup = function(){
var scene;
var renderer;
var camera;
[scene, renderer, camera] = getScene();
$('body').append(renderer.domElement);
var label = getLabel("Hello, World!");
scene.add(label);
var animate = function(){
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
};
setup();
A few points:
1) canvas.width = "100%" should be canvas.width = "100" (canvas sizes are assumed to be px). Same with canvas.height.
2) $('body').height() is 0, so the renderer canvas is not visible (you can check this out in dev tools, it's in the element tree but squashed to 0px high). I know nothing about jQuery, so not sure why this is, but I would recommend using window.innerHeight and window.innerWidth instead anyways. So renderer.setSize(window.innerWidth, window.innerHeight). You'll also want to make this change in the camera initialization.
3) Speaking of the camera initialization, you are passing in width and height as separate arguments, when there should only be an aspect ratio argument. See the docs. So
var camera = new THREE.PerspectiveCamera(70, window.innerWidth, window.innerHeight, 1, 1000)
becomes
var camera = new THREE.PerspectiveCamera(70, window.innerWidth/window.innerHeight, 1, 1000)
4) Because textures are assumed to be static, you need to add something to this part:
var texture = new THREE.Texture(getPicture(message))
texture.needsUpdate = true // this signals that the texture has changed
That's a one-time flag, so you need to set it every time the canvas changes if you want a dynamic texture. You don't have to make a new THREE.Texture each time, just add texture.needsUpdate in the render loop (or in an event that only fires when you want the texture to change, if you're going for efficiency). See the docs, under .needsUpdate.
At this point, it should work. Here are some further things to consider:
5) Instead of using Texture you could use CanvasTexture, which sets .needsUpdate for you. The fiddle you posted is using Three.js r71, which doesn't have it, but newer versions do. That would look like this:
var texture = new THREE.CanvasTexture(getPicture(message));
// no needsUpdate necessary
6) It looks like you were on this path already based on the commented out return picture, but you can use either canvas element, or a data url generated from the canvas for a texture. If getPicture now returns a data url, try this:
var texture = new THREE.TextureLoader().load(getPicture(message))
You can also indirectly use a data url with Texture:
var img = document.createElement('img')
var img = new Image()
img.src = getPicture(message)
var texture = new THREE.Texture(img);
texture.needsUpdate = true
If not, just stick with Texture or CanvasTexture, both will take a canvas element. Texture can indirectly take a url by passing in an image element whose src is set to the data url.
Fiddle with the outlined fixes:
https://jsfiddle.net/jbjw/x0uL1kbh/

Three.js - Why is my sprite drawing somewhere other than its position?

I'm using Three.js r69.
I'm drawing several sprites, each with a canvas texture so that I can draw text onto it. I'm following the suggestions found in this question, with a few simple modifications to make it work in r69. When I set the position of the sprite, it doesn't draw it at its position. Here's a screenshot of what's happening:
I'll also include the code I'm using to generate the sprite:
this.text = _text;
this.textColor = new THREE.Color();
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
//...set the font
//...measure the text width
//...get the aspect ratio of width to height of the text
//...set the background color
//...set the border color
var borderThickness = 2;
ctx.lineWidth = borderThickness;
roundRect(ctx,
borderThickness * 0.5, // x
borderThickness * 0.5, // y
this.textWidth + borderThickness, // width
fontsize * 1.4 + borderThickness, // height
6 // corner radius
);
//...set font color
ctx.fillText(this.text, borderThickness, fontsize + borderThickness);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial(
{map: texture}
);
this.object = new THREE.Sprite(spriteMaterial);
this.object.scale.set(2, 2/this.aspect, 1.0);
Am I doing something wrong when I'm generating the canvas and SpriteMaterial? Or is it something else? I'd like the sprite to pivot around its center, not some point that's completely outside the canvas.
Looks like I wasn't explicitly setting the size of the canvas, so it was defaulting to 300 x 150.
I set the canvas size to that of just the text label, and it positions it correctly.

How to add an image to canvas, and put a rotated text on top of it?

Im trying to get canvas to work, what i'm trying to do is make an image(from an existing image) and place a text on it. I want the text to be rotated on the left side of the image. The moment i try to rotate the text, i can't see it anymore in the canvas. Im using the following solution:
var ctx = canvas.getContext("2d");
ctx.drawImage(img,0,0);
ctx.save();
ctx.rotate(-0.5*Math.PI);
ctx.font = "12px Arial";
ctx.fillStyle = 'white';
ctx.textBaseline = 'top';
ctx.fillText("copyright", 0, 0);
ctx.restore();
var image = canvas.toDataURL("image/jpeg");
With this solution i cannot see the text anymore. When i delete the rotation and make the code into the following, everything works fine the image is rendered and the text is rendered on the image.
var ctx = canvas.getContext("2d");
ctx.drawImage(img,0,0);
ctx.rotate(-0.5*Math.PI);
ctx.font = "12px Arial";
ctx.fillStyle = 'white';
ctx.textBaseline = 'top';
ctx.fillText("copyright", 0, 0);
var image = canvas.toDataURL("image/jpeg");
Can anyone see the mistake im making, or does someone have a solution to this problem of mine?
[edit]
I've made a jsfiddle showing the problem http://jsfiddle.net/7kzuN/4/
Before rotating you should always set the rotation point.
Think of the rotation point as a pencil-tip pressed on on a piece of paper.
When you rotate, the paper will rotate around the point of the pencil-tip.
You set the rotation point using context.translate(x,y).
To rotate on the left side of the image, you would translate something like this:
// set the rotation point
ctx.translate(6,img.height/2);
This sets your rotation point 6 pixels off the left side and at the vertical-center of the image.
Here's example code and a demo: http://jsfiddle.net/m1erickson/ANpPm/
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var img=new Image();
img.crossOrigin="anonymous";
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/houseIcon.png";
function start(){
canvas.width=img.width;
canvas.height=img.height;
// draw the image
ctx.drawImage(img,0,0);
// save the unrotated context
ctx.save();
// set the rotation point with translate
ctx.translate(6,img.height/2);
// rotate by -90 degrees
ctx.rotate(-0.5*Math.PI);
// draw the copyright bar
ctx.fillStyle="black";
ctx.fillRect(-img.height/2,-6,img.height,14);
ctx.font = "12px Arial";
ctx.fillStyle = 'white';
ctx.textBaseline = 'top';
ctx.fillText("copyright", -img.height/2+5,-6);
// restore the context to its unrotated state
ctx.restore();
// save the image+text to a dataURL
var image = canvas.toDataURL("image/jpeg");
}

Get the color of a pixel by its xyz coordinates

I need to get the color of an image texture on a mesh at a given xyz point (mouse click + ray cast). How can I achieve it in THREE.js?
I know I could use gl.readPixels from plain webgl, but it's not a valid option for me.
Thanks!
So I ended using a separate canvas, in which I load the image texture, translate the three.js coordinates into canvas one and read the pixel. Something like this:
// point is a THREE.Vector3
var getColor = function(point, callback) {
var img = new Image();
img.src = 'assets/img/myImage.png';
img.onload = function() {
// get the xy coords from the point
var xyCoords = convertVector3ToXY(point);
// create a canvas to manipulate the image
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
// get the pixel data and callback
var pixelData = canvas.getContext('2d').getImageData(x, y, 1, 1).data;
callback(pixelData);
}
};
Thanks

Resources