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

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.

Related

Three js - the best way to draw text and line

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

Texturing down plane (WebGL, Three.JS)

I am using three.js. with webGL. I have a single texture file called support.jpg 100x100.
I am creating planes on the fly, with different heights and widths. I need the support.jpg texture to scale to the width and then repeat down the plane. (as soon in image below)
For Example. If the plane was (height:10, width: 10) it would the texture once fiting. If the plane was (height:100, width: 10) it would have 10 of the textures repeating 10by10. If the plane was (height:100, width: 50) it would have 2 of the textures repeating 50by50.
Question: How Do I Create a plane that will have the correct texture mapping.
Here is what I have so far, but it is only rendering a single texture. (this is a width 200, height 800)
function CreateSupportBeam() {
var mesh, texture, material;
texture = THREE.ImageUtils.loadTexture("images/support.png");
material = new THREE.MeshBasicMaterial({ map: texture, transparent: true });
var uvs = [];
uvs.push(new THREE.Vector2(0,0));
uvs.push(new THREE.Vector2(1,0));
uvs.push(new THREE.Vector2(1,4));
uvs.push(new THREE.Vector2(0,4));
var geo = new THREE.PlaneGeometry(200, 800);
geo.faceVertexUvs[0].push([uvs[0], uvs[1], uvs[2], uvs[3]]);
mesh = new THREE.Mesh(geo, material);
scene.add(mesh);
}
rollercoaster.dickinsonbros.com/ <- This is the project I am working on.
You do not need to change the UVs.
Use a pattern like the following to avoid distortion and ensure that the pattern repeats and starts at the "top".
var geometry = new THREE.PlaneGeometry( length, height );
var scale = height / length;
var offset = Math.floor( scale ) - scale;;
var texture = THREE.ImageUtils.loadTexture( ... );
texture.wrapT = THREE.RepeatWrapping;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.repeat.set( 1, scale );
texture.offset.set( 0, offset );
If that is not exactly what you are looking for, experiment until you get it the way you want it.
three.js r.66

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

threejs merge plane geometries or create a texture from multiple images

I have a set of plane geometries, each having a their own texture from a url. At any point during navigation(zoom/pan) the container (THREE.Object3D) contains 26 plane geometries. How will i merge them to a single big plane so that i could apply a heighmap for all tiles in the merge geometry.
Or How could I get all the texture from the 36 images.(currently as a map property for MeshPhongMaterial) in to a single geometry?
EDIT:
Currently I create a Big geometry as suggested by Dainel. and put a texture to the geometry which is a combined texture of a set of images via canvas.
context = canvas.getContext( '2d' );
var img = Image();
img.src = url //url of the texture tile
context.drawImage(img,xpos, ypos);
This is done or every images. Each image have a url, xpos, ypos. Images are preloaded and after loading every images a callback is called which creates the geometry(plane) and add texture from the canvas.
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var material = new THREE.MeshPhongMaterial({ map : texture });
var geom = new THREE.PlaneGeometry(canvas.width, canvas.height, 57, 40);
var mesh = new THREE.Mesh(geom, material);
scene.add( mesh );
also i update the z vertex of geometry with the height values.
u may create one "big" plane and apply a merged texture to it
or u could manually set the vertex Y-values of each plane to match the corresponding part of the height map. so u have a set of separate planes that all share the same "base" height map.
regards,
daniel
Using terrainLoader class for threejs and canvas texture I was able to acheive
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, mywidth / myheight, 0.01, 10000);
camera.position.set(0, 0, 240);
scene.add(new THREE.AmbientLight(0xeeeeee));
renderer = new THREE.WebGLRenderer();
renderer.setSize(mywidth, myheight);
var group = new THREE.Object3D();
var canvas = document.createElement( 'canvas' );
canvas.width = mywidth;
canvas.height = myheight;
var ctx = canvas.getContext('2d');
/* adjust width, height, segments based on height data;*/
/* create a plane geometry */
var geom = new THREE.PlaneGeometry(800, 692, 40, 55);
/*
read height values from .bin file uing terrainLoader
and update the Z values of the plane
*/
heightdata = 'assets/heightmap.bin';
terrainLoader.load(heightdata, function(data) {
/* bin file generated from USGS SRTM tile */
for (var i = 0, l = geom.vertices.length ; i < l; i++) {
geom.vertices[i].z = data[i] / 200; /*simple scale */
}
geom.verticesNeedUpdate = true;
});
/*
create a texture from canvas created above with width and height
of the of the scene. This is to be careful as the size of texture
image may not be the same but as same ratio as height map.
Atleast for my case it was the situation. But geometry width and
texture width are in same ratio. So that i need a small array of
heightmap from bin but a much bigger texture
*/
var texture = new THREE.Texture(canvas);
var material = new THREE.MeshPhongMaterial({ map : texture });
var mesh = new THREE.Mesh(geom, material);
group.add( mesh );
scene.add(group);
Now to draw multiple images to canvas check this page
I will just duplicate the code from HTML5 Canvas Image Loader Tutorial
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg',
yoda: 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg'
};
You can call the loadImages function after updating the Plane geometry from heightmap in above code.
loadImages(sources, function(images) {
//context = context of the texture put in threejs texture
context.drawImage(images.darthVader, 100, 30, 200, 137);
context.drawImage(images.yoda, 350, 55, 93, 104);
});

Resources