Changing the way three.js projects a texture onto a halfsphere - javascript

I am currently trying to project an image onto the inside of a halfsphere in a three.js project. The half sphere is created via
const geometry = new THREE.SphereGeometry(Component.radius, this.resolution, this.resolution,
Math.PI, Math.PI, 0, Math.PI);
this.material = new THREE.MeshStandardMaterial({color: 0xffffff});
this.material.side = THREE.DoubleSide;
this.sphere = new THREE.Mesh(geometry, this.material);
// The image of the texture is set later dynamically via
// this.material.map = textureLoader.load(...);
With radius and resolution being constants. This works fine, except for one issue: The image becomes distorted around the "top" and "bottom" of the sphere, like this:
Simple example of distorted texture:
The texture originally had the value "0,0" in the bottom left and "0,1" in the bottom right, and with the camera facing down from the center of the demisphere the bottom left and bottom right corner are squished onto the "bottom" point of the sphere.
I want to change this behavior so the texture corners are instead on where they would be if you place the square texture into a sphere, with the corners touching the circle, then stretching the lines between the corners to match the circle. Simple mock of what i mean:
I have tried playing with the mapping Attribute of my texture, but that doesn't change the behaviour from my understanding.

After changing the UV coordinates, my half sphere texture is't stretching on border as well:
this.sphereGeometry = new THREE.SphereGeometry(
10,
32,
24,
0,
Math.PI,
0,
Math.PI
);
const {
uv: uvAttribute,
normal
} = this.sphereGeometry.attributes;
for (let i = 0; i < normal.count; i += 1) {
let u = normal.getX(i);
let v = normal.getY(i);
u = u / 2 + 0.5;
v = v / 2 + 0.5;
uvAttribute.setXY(i, u, v);
}
const texture = new THREE.TextureLoader().load(
'https://i.imgur.com/CslEXIS.jpg'
);
texture.flipY = false;
texture.mapping = THREE.CubeUVRefractionMapping;
texture.needsUpdate = true;
const basicMaterial = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
});
this.sphere = new THREE.Mesh(this.sphereGeometry, basicMaterial);
this.scene.add(this.sphere);

Related

Rotating icosahedron with circles located at every vertex in three.js

I have an icosahedron mesh which I am rotating and then adding circle geometries and setting their location to each vertex at every frame in the animation loop.
geometry = new THREE.IcosahedronGeometry(isoRadius, 1);
var material = new THREE.MeshBasicMaterial({
color: wireframeColor,
wireframe: true
});
isoMesh = new THREE.Mesh(geometry, material);
scene.add(isoMesh);
Set each circle geometries location as the icosahedron mesh rotates:
function animate() {
isoMesh.rotation.x += 0.005;
isoMesh.rotation.y += 0.002;
// update vertices
isoMesh.updateMatrix();
isoMesh.geometry.applyMatrix(isoMesh.matrix);
isoMesh.rotation.set(0, 0, 0);
for (var i = 0; i < geometry.vertices.length; i++) {
nodes[i].position.copy(geometry.vertices[i]);
nodes[i].lookAt(camera.position);
}
Where var geometry is the geometry of the icosahedron. If I remove the line "isoMesh.rotation.set(0, 0, 0);", the icosahedron rotates correctly, but the rotation of the nodes compounds and spins way too quickly. If I add that line, the nodes rotate correctly, but the icosahedron does not move at all.
I do not understand three.js well enough yet to understand what is happening. Why would adding and removing this affect the nodes' and icosahedron's rotations separately? I believe it has something to do with the difference between the mesh and the geometry since I am using the geometry to position the nodes, but the rotation of the mesh is what shows visually. Any idea what is happening here?
The solution it multi-layered.
Your Icosahedron:
You were half-way there with rotating your icosahedron and its vertices. Rather than applying the rotation to all the vertices (which would actually cause some pretty extreme rotation), apply the rotation to the mesh only. But that doesn't update the vertices, right? Right. More on that in a moment.
Your Circles:
You have the right idea of placing them at each vertex, but as WestLangley said, you can't use lookAt for objects with rotated/translated parents, so you'll need to add them directly to the scene. Also, if you can't get the new positions of the vertices for the rotated icosahedron, the circles will simply remain in place. So let's get those updated vertices.
Getting Updated Vertex Positions:
Like I said above, rotating the mesh updates its transformation matrix, not the vertices. But we can USE that updated transformation matrix to get the updated matrix positions for the circles. Object3D.localToWorld allows us to transform a local THREE.Vector3 (like your icosahedron's vertices) into world coordinates. (Also note that I did a clone of each vertex, because localToWorld overwrites the given THREE.Vector3).
Takeaways:
I've tried to isolate the parts relative to your question into the JavaScript portion of the snippet below.
Try not to update geometry unless you have to.
Only use lookAt with objects in the world coordinate system
Use localToWorld and worldToLocal to transform vectors between
coordinate systems.
// You already had this part
var geometry = new THREE.IcosahedronGeometry(10, 1);
var material = new THREE.MeshBasicMaterial({
color: "blue",
wireframe: true
});
var isoMesh = new THREE.Mesh(geometry, material);
scene.add(isoMesh);
// Add your circles directly to the scene
var nodes = [];
for(var i = 0, l = geometry.vertices.length; i < l; ++i){
nodes.push(new THREE.Mesh(new THREE.CircleGeometry(1, 32), material));
scene.add(nodes[nodes.length - 1]);
}
// This is called in render. Get the world positions of the vertices and apply them to the circles.
var tempVector = new THREE.Vector3();
function updateVertices(){
if(typeof isoMesh !== "undefined" && typeof nodes !== "undefined" && nodes.length === isoMesh.geometry.vertices.length){
isoMesh.rotation.x += 0.005;
isoMesh.rotation.y += 0.002;
for(var i = 0, l = nodes.length; i < l; ++i){
tempVector.copy(isoMesh.geometry.vertices[i]);
nodes[i].position.copy(isoMesh.localToWorld(tempVector));
nodes[i].lookAt(camera.position);
}
}
}
html *{
padding: 0;
margin: 0;
width: 100%;
overflow: hidden;
}
#host {
width: 100%;
height: 100%;
}
<script src="http://threejs.org/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>
<div id="host"></div>
<script>
// INITIALIZE
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(WIDTH, HEIGHT);
document.getElementById('host').appendChild(renderer.domElement);
var stats= new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
var camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 50;
var trackballControl = new THREE.TrackballControls(camera, renderer.domElement);
trackballControl.rotateSpeed = 5.0; // need to speed it up a little
var scene = new THREE.Scene();
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
scene.add(light);
function render(){
if(typeof updateVertices !== "undefined"){
updateVertices();
}
renderer.render(scene, camera);
stats.update();
}
function animate(){
requestAnimationFrame(animate);
trackballControl.update();
render();
}
animate();
</script>

SpotLight not working in ThreeJS

I have a ribbon that displays a few thumbnails. Just to give a background, the thumbnail images are painted on a canvas, which is then added to Texture.
var texture = new THREE.Texture(textureCanvas);
The mesh is created as follows
loader.load('mesh_blender.js', function(geom) {
var mesh = new THREE.Mesh(geom, new THREE.MeshLambertMaterial({
color: 0xffffffff,
opacity: 0.7,
overdraw: 0.21,
side: THREE.DoubleSide,
transparent: true,
//shading: THREE.SmoothShading,
map: texture
}));
Everything till here is fine. The ribbon is created as follows
I have no complaints with this. But I need this additional effect you see in the image below. As it can be seen, the thumbnail that is in the centre (focus) needs to have a darker effect to show it is being highlighted/selectable. All the remaining thumbnails have a transparent effect depicting they are not selectable.
I am trying to wrap my head around this using Lights in Threejs but not very successful. I thought of using an AmbientLight to throw light on the entire ribbon and then an additional SpotLight only on the centre image (with a darker color maybe) to achieve the desired effect. But that didn't work. I have got something like this
But the centre focused image has no effect. As you can see in the image, I have used a helper to show the Light direction but I can't really see any light on the image. This is the code I use to develop that SpotLight
var spotLight = new THREE.SpotLight( 0xffeedd );
spotLight.position.set( 0, -50, 50 );
spotLight.castShadow = true;
//spotLight.penumbra = 0.2;
spotLight.decay = 2;
spotLight.distance = 300;
spotLight.angle = 0.5;
var helper = new THREE.SpotLightHelper( spotLight, 2.5 );
scene.add(helper);
scene.add( spotLight );
I am very new to Threejs and 3d graphics. Any help will be appreciated. Thanks.I am open to any other suggestion as well, if Lights are not to be used to achieve the end result.
You have given the opacity of the material as 0.7 so adding another light will not exactly give you the expected result. I would suggest using a raycaster to identify the object in the center and making the opacity of that object as 1 and the rest as 0.7.
var intersects = raycaster.intersectObjects( objects );
use this to get the objects that are intersecting in an array. And in the renderer function set the opacity of the first element in the array that is the object in the middle to 1. This only works if the thumbnails are all separate objects.
//set as the center of you vision
var mouse = new THREE.Vector2();
mouse.x = 0;
mouse.y = 0;
function highlightObject() {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
// calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( scene.children );
//sets the object in the center opacity = 0.2
if(intersects.length > 0) {
intersects[0].object.material.opacity = 0.2;
}
//sets the opacity of the rest of the objects in scene to opacity = 1.0
for (var i = scene.children.length - 1; i >= 0; i--) {
var obj = scene.children[i];
obj.material.opacity = 1.0;
}
}

Three.js part of video as texture

I'm trying to use part of a video as a texture in a Three.js mesh.
Video is here, http://video-processing.s3.amazonaws.com/example.MP4 it's a fisheye lens and I want to only use the part with actual content, i.e. the circle in the middle.
I want to somehow mask, crop or position and stretch the video on the mesh so that only this part shows and the black part is ignored.
Video code
var video = document.createElement( 'video' );
video.loop = true;
video.crossOrigin = 'anonymous';
video.preload = 'auto';
video.src = "http://video-processing.s3.amazonaws.com/example.MP4";
video.play();
var texture = new THREE.VideoTexture( video );
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
var material = new THREE.MeshBasicMaterial( { map : texture } );
The video is then projected onto a 220 degree sphere, to give the VR impression.
var geometry = new THREE.SphereGeometry( 200,100,100, 0, 220 * Math.PI / 180, 0, Math.PI);
Here is a code pen
http://codepen.io/bknill/pen/vXBWGv
Can anyone let me know how I'm best to do this?
You can use texture.repeat to scale the texture
http://threejs.org/docs/#Reference/Textures/Texture
for example, to scale 2x on both axis
texture.repeat.set(0.5, 0.5);
In short, you need to update the UV-Map of the sphere so that the relevant area of your texture is assigned to the corresponding vertices of the sphere.
The UV-coordinates for each vertex define the coordinates within the texture that is assigned to that vertex (in a range [0..1], so coordinates (0, 0) are the top left corner and (1,1) the bottom right corner of your video). This example should give you an Idea what this is about.
Those UV-coordinates are stored in your geometry as geometry.faceVertexUvs[0] such that every vertex of every face has a THREE.Vector2 value for the UV-coordinate. This is a two-dimensional array, the first index is the face-index and the second one the vertex-index for the face (see example).
As for generating the UV-map there are at least two ways to do this. The probably easier way (ymmv, but I'd always go this route) would be to create the UV-map using 3D-editing software like blender and export the resulting object using the three.js exporter-plugin.
The other way is to compute the values by hand. I would suggest you first try to simply use an orthographic projection of the sphere. So basically, if you have a unit-sphere at the origin, simply drop the z-coordinate of the vertices and use u = x/2 + 0.5 and v = y/2 + 0.5 as UV-coordinates.
In JS that would be something like this:
// create the geometry (note that for simplicity, we're
// a) using a unit-sphere and
// b) use an exact half-sphere)
const geometry = new THREE.SphereGeometry(1, 18, 18, Math.PI, Math.PI)
const uvs = geometry.faceVertexUvs[0];
const vertices = geometry.vertices;
// compute the UV from the vertices of the sphere. You will probably need
// something a bit more elaborate than this for the 220degree FOV, also maybe
// some lens-distorion, but it will boild down to something like this:
for(let i = 0; i<geometry.faces.length; i++) {
const face = geometry.faces[i];
const faceVertices = [vertices[face.a], vertices[face.b], vertices[face.c]];
for(let j = 0; j<3; j++) {
const vertex = faceVertices[j];
uvs[i][j].set(vertex.x/2 + 0.5, vertex.y/2 + 0.5);
}
}
geometry.uvsNeedUpdate = true;
(if you need more information in either direction, drop a comment and i will elaborate)

Label on AxisHelper withTextGeometry and rotation issue

I have a main scene with a sphere and another subwindow (in right bottom) where I have drawn the (x,y,z) axis of main scene.
In this subwindow, I would like to draw the labels "X" "Y" and "Z" on each axis (more precisely located on the end of each AxisHelper). I know how to use TextGeometry but the issue is that I can't get to make rotate these labels in order to make them appear always in face on the user.
You can see the problem on the [following link][1] : label "X" is fixed relatively to axis and is rotating with camera, so it is not always in face of user.
From these two links link1 and link2, I tried to add (in my example, I tried with only "X" label) :
function addLabelAxes() {
// Axes label
var loader = new THREE.FontLoader();
loader.load( 'js/helvetiker_regular.typeface.js', function ( font ) {
var textGeo1 = new THREE.TextGeometry( 'X', {
font: font,
size: 5,
height: 0.1,
bevelThickness: 0.1,
bevelSize: 0.1,
bevelEnabled: true,
} );
var color = new THREE.Color();
color.setRGB(255, 255, 255);
textMaterial = new THREE.MeshBasicMaterial({ color: color });
var meshText1 = new THREE.Mesh(textGeo1, textMaterial);
// Position of axes extremities
var positionEndAxes = axes2.geometry.attributes.position;
var label1X = positionEndAxes.getX(0);
meshText1.position.x = label1X + axisLength;
meshText1.position.y = 0;
meshText1.position.z = 0;
// Rotation of "X" label
//meshText1.rotation = zoomCamera.rotation;
meshText1.lookAt(zoomCamera.position);
// Add meshText to zoomScene
zoomScene.add(meshText1);
});
}
zoomCamera represents a PerspectiveCamera which is the camera of subwindow (i.e zoomScene) ;I add TextGeometry to zoomScene by doing :
zoomScene.add(meshText1);
What might be wrong in my code ? I wonder if I can make rotate the "X" label on itself, i.e the "X" label is rotating like axis but a self (local) orientation is applied as a function of rotation theta angle, so the label is always kept in face of user during camera rotation ?
You are probably looking for THREE.SPRITE. From the docs:
Object3D -> Sprite: A sprite is a plane in an 3d scene which faces always towards the camera.
Here's a simple example of how to use it:
var map = new THREE.TextureLoader().load( "sprite.png" );
var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
var sprite = new THREE.Sprite( material );
scene.add( sprite );
Here's a working example of a similar scenario (3 scaled sprites with different positioning). You can find the code on github.

Draw a circle (not shaded) with Three.js

I am trying to draw a circle very similar to the orbital patterns on this website. I would like to use Three.js instead of pure WebGL.
Three.js r50 added CircleGeometry. It can be seen (albeit with a face) in the WebGL Geometries example.
The first vertex in the geometry is created at the center of the circle (in r84, see CircleGeometry.js line 71, in r65, see CircleGeometry.js line 18), which is nifty if you are going for that "full Pac-Man" or "uninformative pie chart" look. Oh, and it appears to be necessary if you are going to use any material aside from LineBasicMaterial / LineDashedMaterial.
I've verified that the following code works in both r60 & r65:
var radius = 100,
segments = 64,
material = new THREE.LineBasicMaterial( { color: 0x0000ff } ),
geometry = new THREE.CircleGeometry( radius, segments );
// Remove center vertex
geometry.vertices.shift();
// Non closed circle with one open segment:
scene.add( new THREE.Line( geometry, material ) );
// To get a closed circle use LineLoop instead (see also #jackrugile his comment):
scene.add( new THREE.LineLoop( geometry, material ) );
PS: The "docs" now include a nice CircleGeometry interactive example: https://threejs.org/docs/#api/geometries/CircleGeometry
The API changed slightly in newer versions of threejs.
var segmentCount = 32,
radius = 100,
geometry = new THREE.Geometry(),
material = new THREE.LineBasicMaterial({ color: 0xFFFFFF });
for (var i = 0; i <= segmentCount; i++) {
var theta = (i / segmentCount) * Math.PI * 2;
geometry.vertices.push(
new THREE.Vector3(
Math.cos(theta) * radius,
Math.sin(theta) * radius,
0));
}
scene.add(new THREE.Line(geometry, material));
Modify segmentCount to make the circle smoother or more jagged as needed by your scene. 32 segments will be quite smooth for small circles. For orbits such as those on the site you link you, you may want to have a few hundred.
Modify the order of the three components within the Vector3 constructor to choose the orientation of the circle. As given here, the circle will be aligned to the x/y plane.
I used code that Mr.doob references in this github post.
var resolution = 100;
var amplitude = 100;
var size = 360 / resolution;
var geometry = new THREE.Geometry();
var material = new THREE.LineBasicMaterial( { color: 0xFFFFFF, opacity: 1.0} );
for(var i = 0; i <= resolution; i++) {
var segment = ( i * size ) * Math.PI / 180;
geometry.vertices.push( new THREE.Vertex( new THREE.Vector3( Math.cos( segment ) * amplitude, 0, Math.sin( segment ) * amplitude ) ) );
}
var line = new THREE.Line( geometry, material );
scene.add(line);
This example is in the Three.js documentation:
var material = new THREE.MeshBasicMaterial({
color: 0x0000ff
});
var radius = 5;
var segments = 32; //<-- Increase or decrease for more resolution I guess
var circleGeometry = new THREE.CircleGeometry( radius, segments );
var circle = new THREE.Mesh( circleGeometry, material );
scene.add( circle );
I had to do this lol:
function createCircle() {
let circleGeometry = new THREE.CircleGeometry(1.0, 30.0);
circleGeometry.vertices.splice(0, 1); //<= This.
return new THREE.LineLoop(circleGeometry,
new THREE.LineBasicMaterial({ color: 'blue' }));
}
let circle = createCircle();
Reason: Otherwise, it doesn't draw a "pure" circle, there's a line coming from the center to the rim of the circle, even if you use LineLoop instead of Line. Splicing (removing) the first vertex from the array is a hack but seems to do the trick. :)
(Note that apparently, according to mrienstra's answer, "Oh, and it appears to be necessary if you are going to use any material aside from LineBasicMaterial / LineDashedMaterial.")
If you want thickness, though, you're screwed ("Due to limitations of the OpenGL Core Profile with the WebGL renderer on most platforms linewidth will always be 1 regardless of the set value.")... Unless you use: https://github.com/spite/THREE.MeshLine
Code example for that is here: https://stackoverflow.com/a/61312721/1599699
Well, I dunno when they added it - but TorusGeometry should do the job...
THREE TorusGeometry
const geometry = new THREE.TorusGeometry( 10, 3, 16, 100 );
const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
const torus = new THREE.Mesh( geometry, material );
scene.add( torus );
Dunno, but I think it shouldn't be (much) more expensive than the line thingy and it's a buffer geometry and you may adjust size and material etc...
See the three.js sample http://mrdoob.github.com/three.js/examples/webgl_lines_colors.html to see how to draw colored lines.
A circle like the ones you cite is drawn as a large # of little straight segments. (Actually, the ones you show may be ellipses)
var getStuffDashCircle2 = function () {
var segment = 100, radius = 100;
var lineGeometry = new THREE.Geometry();
var vertArray = lineGeometry.vertices;
var angle = 2 * Math.PI / segment;
for (var i = 0; i < segment; i++) {
var x = radius * Math.cos(angle * i);
var y = radius * Math.sin(angle * i);
vertArray.push(new THREE.Vector3(x, y, 0));
}
lineGeometry.computeLineDistances();
var lineMaterial = new THREE.LineDashedMaterial({ color: 0x00cc00, dashSize: 4, gapSize: 2 });
var circle = new THREE.Line(lineGeometry, lineMaterial);
circle.rotation.x = Math.PI / 2;
circle.position.y = cylinderParam.trackHeight+20;
return circle;
}
I had some issues getting the other answers to work here -- in particular, CircleGeometry had an extra point at the center of the circle, and I didn't like the hack of trying to remove that vertex.
EllipseCurve does what I wanted (verified in r135):
const curve = new THREE.EllipseCurve(
0.0, 0.0, // Center x, y
10.0, 10.0, // x radius, y radius
0.0, 2.0 * Math.PI, // Start angle, stop angle
);
const pts = curve.getSpacedPoints(256);
const geo = new THREE.BufferGeometry().setFromPoints(pts);
const mat = new THREE.LineBasicMaterial({ color: 0xFF00FF });
const circle = new THREE.LineLoop(geo, mat);
scene.add(circle);

Categories

Resources