Three.js - Inconsistency in updating plane size after its created has me confused - three.js

Im setting the height and width to 100 when setting the PlaneGemoetry. Then I am scaling to the exact same size later using plane.scale(100, 100, 1);. But the scaled plane is much bigger. Why is the exact same dimentions so diffirent? Am I doing something wrong, or is the scale values really completley diffirent?
My expectation is that when I set the scale to 100, 100 it should not change size because thats the same size I created it at.
var geometry = new THREE.PlaneGeometry(100, 100, 2, 2 ); //<--- sets scale here
var plane = new THREE.Mesh( geometry, material );
plane.scale.set( 100, 100, 1 ); //< -- why does 100, 100 suddenly turn into a completley
Heres a codepen for the issue:
https://codepen.io/carelesscourage/pen/zYLbZBv

Scaling is multiplicative. So your original size * scale is the equivalent of doing
100 * 100 = 10000
You’re getting a plane that’s ten thousand units wide. If you want to return to its initial size, just set the scale to 1, since 100 * 1 = 100

Related

three-globe SphereBufferGeometry/Mesh is offset on globe, but lines up if flat

I have a three-globe, and lat/long points perfectly go to the correct locations. The base (Earth) map is 1600x800.
However, I also have a RainViewer map (storm radar) which is square (4096x4096). If I scale that to 1600x1600 and overlay the Earth map, it fits perfectly lined up (top 800 and bottom 800 are outside the boundaries, but that is blank anyway, so perfect).
When I use the TextureLoader/SphereBufferGeometry/MeshPhongMaterial/Mesh, and add it to the scene, it locates itself completely in the wrong spot. No amount of rotateX/Y/Z, or phi/theta shifting seems to work to get it to position correctly.
How can one map this correctly on the globe?
Relevant code (url hardcoded to a timestamp for clarity):
this.myGlobe = new ThreeGlobe()
.globeImageUrl(myImageUrl)
.polygonsData(this.polyData)
.pointsData(gData)
.pointColor('color');
const renderer = new THREE.WebGLRenderer();
console.log('width=' + width);
renderer.setSize(width, width / 2);
document.getElementById('globeViz').appendChild(renderer.domElement);
const myScene = new THREE.Scene();
myScene.add(this.myGlobe);
myScene.add(new THREE.AmbientLight(0xbbbbbb));
myScene.add(new THREE.DirectionalLight(0xffffff, 0.6));
const camera = new THREE.PerspectiveCamera();
camera.aspect = 2; //window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
camera.translateZ(300);
const globeMaterial = new THREE.MeshPhongMaterial();
globeMaterial.bumpScale = 10;
new THREE.TextureLoader().load('//unpkg.com/three-globe/example/img/earth-water.png',
texture => {
globeMaterial.specularMap = texture;
globeMaterial.specular = new THREE.Color('grey');
globeMaterial.shininess = 15;
});
this.myGlobe.globeMaterial = globeMaterial;
new THREE.TextureLoader().load('https://tilecache.rainviewer.com/v2/radar/1652860800/4096/2/0_1.png',
cloudsTexture => {
const geo = new THREE.SphereBufferGeometry(this.myGlobe.getGlobeRadius() * (1 + 0.004), 80, 80);
const mesh = new THREE.MeshPhongMaterial({ map: cloudsTexture, transparent: true });
const weather = new THREE.Mesh(geo, mesh);
myScene.add(weather);
});
Correct placement:
In color (harder to see) to show apples-to-apples:
Incorrect placement when Globified:
I believe Marquizzo is correct in the comments, one of the projected images is rotated 90 degrees (plus or minus, but probably minus in your case) compared to the other. Since you said that your earth map is not rotated at all, this means the RainViewer map is.
This is consistent with how other NASA weather maps I recently projected on my own Earth globe had to be dealt with - in my case, the cloud cover simulation movie applied on the globe started with the prime meridian aka 0 degree of longitude to the left side of the image (instead of being positioned in the horizontal middle of the image as its customary in nearly all maps), and I'm guessing something similar is happening here, except for the direction of the angle needed to make it look right.
The assumption is supported by the fact that in your screenshots, the big orange spot that should be positioned close to the North American Great Lakes (i.e. 90 degrees West) is placed precisely on the prime meridian (i.e. 0 degrees of longitude). Yup, I know this thanks to my own globe... :)
To (partially, see below) fix this, you should construct your geometry so that the phiStart parameter of the constructor is set to the correct rotation angle, something like:
const geo = new THREE.SphereBufferGeometry(this.myGlobe.getGlobeRadius() * (1 + 0.004), 80, 80, - Math.PI / 2);
This will project the map starting from 90 degrees to the "left" as its left side, if this makes sense.
That being said, I don't think this is the entire extent of the issue, because that orange spot is also displaced at around 23 degrees of latitude North (i.e. at the Tropic of Cancer in your Globified screenshot) compared to the correct 46 degrees of latitude North (i.e. more or less where the left side of Lake Superior lies). This fits well with the fact that the projected image is a 1600 x 1600 px square, instead of an expected 1600 x 800 px rectangle, as the most probable cause of the latitudinal aka vertical displacement, so you might want to appropriately "crop" the RainViewer map to have the expected 2:1 horizontal to vertical size that's expected from a plane projection on a sphere. You could probably use the thetaStart and thetaLength parameters of the sphere geometry constructor to adjust things here as well, if that yields what you want.
Or, it might just be that both the longitudinal and latitudinal displacements are somehow caused by the usage of a 1600 x 1600 px square image source instead of a 1600 x 800 px one. The cause of the issue shouldn't affect the way it can be fixed though.

HTML canvas fillRect with low opacity doesn't affect dark pixels

Repeatedly drawing a semi-opaque black rectangle over the entire canvas before each animation frame is an easy way to get an afterimage effect for moving shapes and it gives me exactly what I need - up to a point. With too slow a fade it doesn't fade all the way to black. Here's an example:
var canv = document.createElement('canvas');
document.body.appendChild(canv);
var ctx = canv.getContext('2d');
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, 100, 100);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(20, 20, 60, 60);
window.requestAnimationFrame(doFade);
function doFade() {
// Never fades away completely
ctx.fillStyle = 'rgba(0, 0, 0, 0.02)';
ctx.fillRect(20, 20, 60, 60);
window.requestAnimationFrame(doFade);
}
jsfiddle
This looks to me like a numeric precision problem - you can't expect the canvas to keep floating point pixel values around - but I'm not sure how to get around this.
I tried reading the image into a pattern, blanking the canvas, and then filling with the pattern at lower opacity in the hope that I could make rounding error work in my favor, but it seems to have the same result.
Short of reading out the image data and setting to black any pixels below a certain threshold, which would be prohibitively slow, I'm running out of ideas and could use some suggestions.
Thanks!
I thought I'd share my solution for the benefit of anyone else who might run into this problem. I was hoping to avoid doing any pixel-level manipulation, but beyond a certain threshold it's just not possible with the built-in canvas operations because the underlying bitmap is only 8 bits per channel and small fades will work out to less than one least significant bit and won't have any effect on the image data.
My solution was to create an array representing the age of each pixel. After each frame is drawn, I scan the imageData array, looking only at the alpha channel. If the alpha is 255 I know the pixel has just been written, so I set the age to 0 and set the alpha to 254. For any other non-zero alpha values, I increment the pixel age and then set the new alpha based on the pixel age.
The mapping of pixel age to alpha value is done with a lookup table that's populated when the fade rate is set. This lets me use whatever decay curve I want without extra math during the rendering loop.
The CPU utilization is a bit higher, but it's not too much of a performance hit and it can do smooth fades over several seconds and always fades entirely to black eventually.

arc radius ignored for decimal values

I have some values that I need to plot into a 2D HTML5 <canvas>. All values are in the range [-1, +1] so I decided to set a transformation (scale + displacement) on the canvas 2D-context before drawing:
var scale = Math.min(canvas.width, canvas.height) / 2;
ctx.setTransform(scale, 0, 0, scale, canvas.width / 2, canvas.height / 2);
Each value is drawn using the arc method, but since I want a fixed arc-radius (no matter what scaling is used) I'm dividing the radius with the current scale value:
ctx.arc(value.X, value.Y, 2 / scale, 0, 2 * Math.PI, false);
Now, a canvas of size 200 x 200 will result in scale factor of 100, which in turn results in a arc-radius of 0.02. Unfortunately, it seems that values like 0.2 or 0.02 don't make any difference to the resulting arc-radius, only the stroke thickness is changing.
You can see this behavior in the JsFiddle. Is this a bug or am I doing something wrong?
The issue is that after scaling by a huge factor your lines you now have a lineWidth far too big to be drawn correctly with stroke.
Just adjust the lineWidth to 1/scale before drawing, and all will work fine.

Three.js: change the pivot point of a sprite

I've created a 3D map and I'm labelling points on this map through Sprites. This in itself works fine, except for the positioning of the sprite labels.
Because I'm creating a map the camera can tilt from 0 to 90 degrees, while ideally the label always stays some distance directly above the item it is labelling on the screen. But unfortunately, as sprites are always centred around their origin and that overlaps the item, I have to move the sprite up on the Y world axis and with that the centre location of the sprite changes as the camera is tilted. This looks weird if the item looked at is off centre, and doesn't work too well when the camera is looking straight down.
No jsfiddle handy, but my application at http://leeft.eu/starcitizen/ should give a good impression of what it looks like.
The code of THREE.SpritePlugin suggests to me it should be possible to use "matrixWorld" to shift the sprite some distance up on the screen's Y axis while rendering, but I can't work out how to use that, nor am I entirely sure that's what I need to use in the first place.
Is it possible to shift the sprites up on the screen while rendering, or perhaps change their origin? Or is there maybe some other way I can achieve the same effect?
Three.js r.67
As suggested by WestLangley, I've created a workable solution by changing the sprite position based on the viewing angle though it took me hours to work out the few lines of code needed to get the math working. I've updated my application too, so see that for a live demo.
With the tilt angle phi and the heading angle theta as computed from the camera in OrbitControls.js the following code computes a sprite offset that does exactly what I want it to:
// Given:
// phi = tilt; 0 = top down view, 1.48 = 85 degrees (almost head on)
// theta = heading; 0 = north, < 0 looking east, > 0 looking west
// Compute an "opposite" angle; note the 'YXZ' axis order is important
var euler = new THREE.Euler( phi + Math.PI / 2, theta, 0, 'YXZ' );
// Labels are positioned 5.5 units up the Y axis relative to its parent
var spriteOffset = new THREE.Vector3( 0, -5.5, 0 );
// Rotate the offset vector to be opposite to the camera
spriteOffset.applyMatrix4( new THREE.Matrix4().makeRotationFromEuler( euler ) );
scene.traverse( function ( object ) {
if ( ( object instanceof THREE.Sprite ) && object.userData.isLabel ) {
object.position.copy( spriteOffset );
}
} );
Note for anyone using this code: that the sprite labels are children of the object group they're referring to, and this only sets a local offset from that parent object.
I had a similar problem, but with flat sprites; I put trees on a map and wanted them to rotate in such a way that they'd rotate around their base, rather than their center. To do that, i simply edited the image files of the trees to be twice as tall, with the bottom as just a transparency:
http://imgur.com/ogFxyFw
if you turn the first image into a sprite, it'll rotate around the tree's center when the camera rotates. The second tree will rotate around it's base when the camera rotates.
For your application, if you resize the textbox in such a way that the center of it would be coincide with the star; perhaps by adding a few newlines or editing the height of the sprite
This is very much a hack, but if you will only use sprites in this way, and could tolerate a global change to how sprites were rendered, you could change the following line in the compiled three.js script:
Find (ctrl+F) THREE.SpritePlugin = function, and you'll see:
this.init = function ( renderer ) {
_gl = renderer.context;
_renderer = renderer;
vertices = new Float32Array( [
- 0.5, - 0.5, 0, 0,
0.5, - 0.5, 1, 0,
0.5, 0.5, 1, 1,
- 0.5, 0.5, 0, 1
] );
I changed the definition of the array to the following:
var vertices = new Float32Array( [
- 0.5, - 0.0, 0, 0,
0.5, - 0.0, 1, 0,
0.5, 1.0, 1, 1,
- 0.5, 1.0, 0, 1
] );
And now all my sprites render with the rotation origin at the bottom.
If you use the minified version, search for THREE.SpritePlugin=function and move the cursor right until you find the Float32Array defined, and make the same changes there.
Note: this changes how things render only when using WebGL. For the canvas renderer you'll have to play a function called renderSprite() in the THREE.CanvasRenderer. I suspect playing with these lines will do it:
var dist = 0.5 * Math.sqrt( scaleX * scaleX + scaleY * scaleY ); // allow for rotated sprite
_elemBox.min.set( v1.x - dist, v1.y - dist );
_elemBox.max.set( v1.x + dist, v1.y + dist );
This function will also be a lot more difficult to find in the minified version, since renderSprite() is not an outward facing function, it'll likely be renamed to something obscure and small.
Note 2: I did try making these modifications with "polyfills" (or rather, redefining the SpritePlugin after Three is defined), but it caused major problems with things not being properly defined for some reason. Scoping is also an issue with the "polyfill" method.
Note 3: My version of three.js is r69. So there may be differences above.

what is the unit for the Three js objects?

var camera = new THREE.PerspectiveCamera(
35, // Field of view
800 / 640, // Aspect ratio
.1, // Near
10000 // Far
);
var cube = new THREE.Mesh(
new THREE.CubeGeometry( 5, 5, 5 ),
new THREE.MeshLambertMaterial( { color: 0xFF0000 } )
);
In camera, what is the unit for Near and Far parameters.
In Cube what is the unit of the parameters for CubeGeometry
Please point me to the url where, i can find details about
Object space - (the local axis used to draw an object)
World space - (the global axis)
Pretty sure these parameters are units in OpenGL. If you look at the OpenGL red book, for each in chapter 3
http://www.glprogramming.com/red/chapter03.html
There's a short paragraph here:
The preceding paragraph mentions inches and millimeters - do these
really have anything to do with OpenGL? The answer is, in a word, no.
The projection and other transformations are inherently unitless. If
you want to think of the near and far clipping planes as located at
1.0 and 20.0 meters, inches, kilometers, or leagues, it's up to you. The only rule is that you have to use a consistent unit of
measurement. Then the resulting image is drawn to scale.
So to answer your question, the units are whatever you think they should be, they are a purely relative scale.
The official answer is that ThreeJS uses SI units for all measures, see here:
https://github.com/mrdoob/three.js/issues/6259

Resources