Thickness of lines using THREE.LineBasicMaterial - javascript

I am using the code below to create hundreds of lines in my three.js scene
edgeGeometry[i] = new THREE.Geometry();
edgeGeometry[i].vertices[0] = v(x1,y1,z1);
edgeGeometry[i].vertices[1] = v(x2,y2,z2);
edgesMat[i] = new THREE.LineBasicMaterial({
color: 0x6699FF, linewidth: 1, fog:true});
edge[i] = new THREE.Line(edgeGeometry[i], edgesMat[i]);
edge[i].type = THREE.Lines;
scene2.add(edge[i]);
It works just fine, but when i change the value of "linewidth" to a bigger OR smaller value, i see NO difference in the scene.
How should i change the thickness of the lines? Any ideas?
Thanks, Dimitris

1) Use native OpenGL
You can achieve rendering of line thicknesses with a workaround by setting your browser to use native OpenGL instead of ANGLE. You can read here on how to do this on Chrome.
Keep in mind that you will experience performance differences if you swap to native OpenGL.
EDIT:
The master MrDoob himself posted here how to do this for both Chrome and Firefox.
Note: This first option is no longer a valid solution since the latest OpenGL versions no longer support line thickness either. Check also #gman his answer. This means if you want to use line thickness the second option is the way to go.
2) Use THREE.MeshLine class
There is also another solution; this THREE.MeshLine class on github is a nice workaround. It comes with a special THREE.MeshLineMaterial. According to the docs it is as simple as:
Create and populate a geometry
Create a THREE.MeshLine and assign the geometry
Create a THREE.MeshLineMaterial
Use THREE.MeshLine and THREE.MeshLineMaterial to create a THREE.Mesh

Are you using Windows?
I remember this not working on Windows because it wasn't implemented in ANGLE.

This occurs in Windows Chrome and Firefox, both using ANGLE (WebGL to DirectX wrapper).
The issue is still not solved by the ANGLE project. You can star the issue here to get higher priority and get a notification if it's going to be implemented:
https://code.google.com/p/angleproject/issues/detail?id=119

I use TubeGeometry to create a Thick line between two points:
See Green lines in Helix
// line material
var lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
let startVector = new THREE.Vector3(
RADI * Math.cos(t),
RADI * Math.sin(t),
3 * t
);
let endVector = new THREE.Vector3(
RADI * Math.cos(t + 10),
RADI * Math.sin(t + 10),
3 * t
);
let linePoints = [];
linePoints.push(startVector, endVector);
// Create Tube Geometry
var tubeGeometry = new THREE.TubeGeometry(
new THREE.CatmullRomCurve3(linePoints),
512,// path segments
0.5,// THICKNESS
8, //Roundness of Tube
false //closed
);
let line = new THREE.Line(tubeGeometry, lineMaterial);
scene.add(line);

This is no longer an issue just in ANGLE it's an issue on all platforms. Browsers needed to switching to the OpenGL 4+ core profile to support WebGL2 and the OpenGL 4+ core profile does not support line widths greater than 1. From the OpenGL 4.0+ spec, section E.2.1
E.2.1 Deprecated But Still Supported Features
The following features are deprecated, but still present in the core profile. They may be removed from a future version of OpenGL, and are removed in a forward compatible context implementing the core profile.
Wide lines - LineWidth values greater than 1.0 will generate an INVALID_VALUE error.
To draw thicker lines you need generate geometry. For three.js there is this library (pointed out by Wilt as well)
https://github.com/spite/THREE.MeshLine

You can use CanvasRenderer instead of Webglrenderer. Check out the ifficial documentation here where each shape has a border of linewidth = 10;

You can achieve the same effect using extrude-polyline to generate a simplicial complex for the thickened (poly)line and three-simplicial-complex to convert this to a three.js Mesh:
const THREE = require('three');
const extrudePolyline = require('extrude-polyline');
const Complex = require('three-simplicial-complex')(THREE);
function thickPolyline(points, lineWidth) {
const simplicialComplex = extrudePolyline({
// Adjust to taste!
thickness: lineWidth,
cap: 'square', // or 'butt'
join: 'bevel', // or 'miter',
miterLimit: 10,
}).build(points);
// Add a z-coordinate.
for (const position of simplicialComplex.positions) {
position[2] = 0;
}
return Complex(simplicialComplex);
}
const vertices = [[0, 0], [10, 0], [10, 10], [20, 10], [30, 00]];
const geometry = thickPolyline(vertices, 10);
const material = new THREE.MeshBasicMaterial({
color: 0x009900,
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
If you want to texture map the polyline, things get a little more complicated.

Thanks to Wilt's answer for pointing me in the right direction with THREE.MeshLine.
It can be slightly trickier than they make it out to be, however... So here's my solution following their docs and their demo code very carefully... (assuming you've already included Three and MeshLine):
renderer = new THREE.WebGLRenderer({ canvas });
//...
function createCircle(resolution) {
let circleGeometry = new THREE.Geometry();
for (let rotation = 0; rotation <= Math.PI * 2.0; rotation += Math.PI * 0.1) {
circleGeometry.vertices.push(
new THREE.Vector3(Math.cos(rotation), Math.sin(rotation), 0));
}
let circleLine = new MeshLine();
circleLine.setGeometry(circleGeometry);
//Bonus: parabolic width! (See Z rotation below.)
//circleLine.setGeometry(circleGeometry, function(point) {
//return Math.pow(4 * point * (1 - point), 1);
//});
//Note: resolution is *required*!
return new THREE.Mesh(circleLine.geometry,
new MeshLineMaterial({
color: 'blue',
resolution,
sizeAttenuation: 0,
lineWidth: 5.0,
side: THREE.DoubleSide
}));
}
let circle = createCircle(new THREE.Vector2(canvas.width, canvas.height));
circle.rotation.x = Math.PI * 0.5;
circle.position.y = 20.0;
scene.add(circle);
//In update, to rotate the circle (e.g. if using parabola above):
world.circle.rotation.z += 0.05;
With size attenuation off and using THREE.DoubleSide, like I did above, the circle will look like a nice, consistent circle no matter where you're looking at it from (not "true 3D").
For just a line, you can obviously easily adapt.

Why not set the opacity to something like 0.1?
NOTE: This only works if you are giving borders to something, if there's nothing behind it the it won't work.

Related

Three.js shadows acting weirdly

I am trying to create a little solar system but found a bug... or a feature. I'd like for all of my planets to be able to cast and receive shadows from all other planets. However, it seems as if it depends on instancing order if shadows are cast or not.
Code for the light and shadows:
const sunLight = new THREE.PointLight(0xffffff, 3, 100);
sunLight.position.set(0, 0, 0);
sunLight.castShadow = true
scene.add(sunLight);
//Set up shadow properties for the light
sunLight.shadow.mapSize.width = 512; // default
sunLight.shadow.mapSize.height = 512; // default
sunLight.shadow.camera.near = 0.5; // default
sunLight.shadow.camera.far = 500; // default
const sphereSize = 1;
const pointLightHelper = new THREE.PointLightHelper(sunLight, sphereSize);
scene.add(pointLightHelper);
const shadowHelper = new THREE.CameraHelper( sunLight.shadow.camera );
scene.add( shadowHelper );
Basic code for the code objects:
var earth = new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 16),
new THREE.MeshStandardMaterial({
map: tLoader.load("/textures/nasa-world.jpg"),
bumpMap: tLoader.load("/textures/nasa-jpl-world-bump.png"),
bumpScale: 0.01,
}));
earth.castShadow = true
earth.receiveShadow = true
// position goes here
scene.add(earth);
var mars = new THREE.Mesh(
new THREE.SphereGeometry(0.53, 32, 16),
new THREE.MeshStandardMaterial({
map: tLoader.load("/textures/nasa-mars.jpg"),
bumpMap: tLoader.load("/textures/nasa-mars-bump.png"),
bumpScale: 0.01,
}))
mars.castShadow = true
mars.receiveShadow = true
//position goes here
scene.add(mars);
Case 1 (working shadow):
earth.position.x = 18
mars.position.x = 15
(https://ibb.co/gS26Sfz)
Case 2 (not working):
earth.position.x = 15
mars.position.x = 18
(https://ibb.co/PZrh2wS)
Case 3 (not sure why, but it works):
When I switch around the instancing (I first instance mars, then earth, Case 2 DOES work).
(https://ibb.co/pRz06b1)
It does seem to me that only objects that are instanced BEFORE the objects that drop shadows can actually receive shadows. I cannot imagine, though, that this is truly a limitation, I am probably doing something wrong.
Please help me, how can I make both objects cast and receive shadows from one another?
After playing around and reading some more documentation, it seems as if this problem is hard coded. The instancing order does seem to determine what can cast and receive shadows. In other words: the meshes that are to receive the shadow must be instanced before the objects casting the shadows. This is quite a limitation, in some ways.

In THREE.js, how to map one texture to a 3D rectangle

I'm trying to make a box in THREE that represents a box of 2x4 Legos, 24 pieces wide by 48 pieces long and and arbitrary number of pieces tall. I've generated a texture that shows this pattern using random colors:
I need to show two sides this cube, but the textures have to align so that the pieces on the edges are the same colors, like so (generated in Blender):
I'd really prefer not to make six images for a CubeTexture, particularly since four are not visible. Is it possible to flip the texture on one side so that they appear to align? (We're just going for visual effect here.)
Further, not all 3D rectangles will be cubes, but I can't quite figure out how to set the texture.repeat.x and texture.repeat.y so that the x is scaled correctly and the y is at the same scale, but just cuts off when the height of the object ends, like so:
Thanks!
You can flip an image by flipping the UVs.
You'll need to figure out which UVs correspond to the face you're trying to mirror, and which direction to flip them (not sure how your geometry is created).
Here's an example using a basic BoxBufferGeometry and modifying its uv attribute. (The face on the right is the mirrored-by-UV-flipping face.)
var textureURL = "https://upload.wikimedia.org/wikipedia/commons/0/02/Triangular_hebesphenorotunda.png";
// attribution and license here: https://commons.wikimedia.org/wiki/File:Triangular_hebesphenorotunda.png
var renderer = new THREE.WebGLRenderer({antialias:true});
document.body.appendChild(renderer.domElement);
renderer.setSize(500, 500);
var textureLoader = new THREE.TextureLoader();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(50, 25, 50);
camera.lookAt(scene.position);
scene.add(camera);
camera.add(new THREE.PointLight(0xffffff, 1, Infinity));
var cubeGeo = new THREE.BoxBufferGeometry(20, 20, 20);
var uvs = cubeGeo.attributes.uv;
// originally:
// [0] = 0,1
// [1] = 1,1
// [2] = 0,0
// [3] = 1,0
// convert to:
// [0] = 1,1
// [1] = 0,1
// [2] = 1,0
// [3] = 0.0
uvs.setX(0, 1);
uvs.setY(0, 1);
uvs.setX(1, 0);
uvs.setY(1, 1);
uvs.setX(2, 1);
uvs.setY(2, 0);
uvs.setX(3, 0);
uvs.setY(3, 0);
uvs.needsUpdate = true;
var mat = new THREE.MeshLambertMaterial({
color: "white",
map: textureLoader.load(textureURL, function(){
animate();
})
});
var mesh = new THREE.Mesh(cubeGeo, mat);
scene.add(mesh);
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>
You can create six PlaneBufferGeometries assign the same material, and then position them to form a cube. Rotate them in 90deg increments until you reach the desired result. For performance reasons, you could merge these back into a single BufferGeometry.
You can export the model you made in blender, either using the THREE.js json exporter, or a format like OBJ or GLTF, and load and render it directly.
What you are talking about is simply having the UV's laid out the way you have them in blender.. so if you need that level of control.. it's probably easier to just load the model instead of trying to generate it.
If you use either three.js .json or .gltf, both exporters have an option to embed the textures directly in the export. This can make it easier to get things working quicker, at the expense of possibly less efficient storage.

Three.js - how can I use an EllipseCurve as an extrusion path?

The problem:
In the awesome Three.js, I can't figure out how to convert an EllipseCurve into a path that I can extrude along.
In the example below, if I uncomment the LineCurve3, my square extrudes along it nicely. If I run it as the EllipseCurve, there are no errors but nothing shows on screen. I have tried zooming the camera right out to make sure it's not off the screen for any reason.
I know the EllipseCurve is being generated correctly as I can write it out with a line material (not shown in the code below).
The code
var radius = 1100;
var degreesStart = 75;
var degreesEnd = 30;
var radiansStart = (degreesStart * Math.PI) / 180;
var radiansEnd = ((degreesEnd) * Math.PI) / 180;
// this won't seem to work as an extrude path, but doesn't give any errors
var path = new THREE.EllipseCurve(0, 0, radius, radius, radiansStart, radiansEnd, true);
// this works fine as an extrude path
//var path = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(1000, 1000, 0));
var extrusionSettings = { steps: 100, bevelEnabled: false, extrudePath: path };
// draw a square to extrude along the path
var sectionSize = [];
sectionSize.push(new THREE.Vector2(0, 0));
sectionSize.push(new THREE.Vector2(1000, 0));
sectionSize.push(new THREE.Vector2(1000, 1000));
sectionSize.push(new THREE.Vector2(0, 1000));
var sectionShape = new THREE.Shape(sectionSize);
var componentGeometry = new THREE.ExtrudeGeometry(sectionShape, extrusionSettings);
var component = new THREE.Mesh(componentGeometry, material);
group.add(component);
What I have tried:
My attempts to make it work have all tried to extract the points from the curve into a path to use in the extrusion. The closest I felt I got was
var ellipsePath = new THREE.CurvePath(path.getSpacedPoints(20));
// where 'path' is my EllipseCurve in the code above
// (and then changed the extrusion settings to use 'ellipsePath ' instead).
This gave the error "Cannot read property 'distanceTo' of null".
I can't seem to get my head around how the EllipseCurve relates to points that relate to a path.
Can anyone point me in the right direction please, or have code where you've come across the same problem? Many thanks.
I ran into the same problem. After experimenting with EllipseCurve and CurvePath, I concluded that these two are building 2D paths which lead to problems inside ExtrudeGeometry. Examining the source of three.js and the example extrusion based on a 3D spline, I built my own Curve and defined a 3D .getPoint function. This solved the problem and rendered a perfect extrusion. Replace the "var path" line of your code with the following:
var path = new THREE.Curve();
path.getPoint = function (t) {
// trace the arc as t ranges from 0 to 1
var segment = (radiansStart - radiansEnd) * t;
return new THREE.Vector3(radius * Math.cos(segment), radius * Math.sin(segment), 0);
};
Inspired by #james-dunn’s solution, I implemented a Path3D class that’s the same as a regular THREE.Path except that getPoint() returns a Vector3 with z = 0:
class Path3D extends THREE.Path {
constructor(pts) {
super(pts);
}
getPoint(t) {
const pt2d = super.getPoint(t);
const pt3d = new THREE.Vector3(pt2d.x, pt2d.y, 0);
return pt3d;
}
}

Three.js Mirrored Normal Maps Flipped Channel

I have added a normal map to a model in Three.js that is mirrored down the middle. It looks like one of the channels (green perhaps?) is flipped on the mirrored side.
I have one ambient light, one directional headlight, and one spotlight. Here is the code that I use to make the material:
// Create a MeshPhongMaterial for the model
var material = new THREE.MeshPhongMaterial();
material.map = THREE.ImageUtils.loadTexture(texture_color);
// Wrapping modes
//THREE.RepeatWrapping = 1000;
//THREE.ClampToEdgeWrapping = 1001;
//THREE.MirroredRepeatWrapping = 1002;
material.map.wrapS = THREE.RepeatWrapping;
material.map.wrapT = THREE.MirroredRepeatWrapping;
if (texture_normal != null) {
material.normalMap = THREE.ImageUtils.loadTexture(texture_normal);
material.normalMap.wrapS = THREE.RepeatWrapping;
material.normalMap.wrapT = THREE.MirroredRepeatWrapping;
}
material.wrapAround = true;
material.morphTargets = true;
material.shininess = 15;
material.specular = new THREE.Color(0.1, 0.1, 0.1);
material.ambient = new THREE.Color(0, 0, 0);
material.alphaTest = 0.5;
var mesh = new THREE.MorphAnimMesh( geometry, material );
// Turn on shadows
mesh.castShadow = true;
if (shadows) {
mesh.receiveShadow = true;
}
scene.add( mesh );
I tried all of the different combinations of material.normalMap.wrapS and material.normalMap.wrapT but that didn't solve it (tried diffuse map too). What am I doing wrong?
Thank you!
Normal maps are dependent on the geometry, so you can't just mirror it and expect it to work like a diffuse texture would.
To make it work, you need to flip the normal map's red channel wherever the UVWs are mirrored on the model.
http://www.polycount.com/forum/showthread.php?t=116922
Turns out I was using an older version (1.2) of the Blender Three.js exporter. By switching to the latest version (1.5) of the exporter from the r67 repository, Three.js now correctly handles mirrored normal maps with its Phong shader out of the box.
Edit: The Phong Shader was still having issues with the flipped channel. I ended up using the "Normal Map Shader" (see the Three.js examples) and that gave me correct results. Unfortunately the Normal Map Shader doesn't work with Morph animations, only Skeletal.

How to get correct values for normals in threejs?

I don’t understand how normals are computed in threejs.
Here is my problem :
I create a simple plane
var plane = new THREE.PlaneGeometry(10, 100, 10, 10);
var material = new THREE.MeshBasicMaterial();
material.setValues({side: THREE.DoubleSide, color: 0xaabbcc});
var mesh = new THREE.Mesh(plane, material);
mesh.rotateY(Math.PI / 2);
scene.add(mesh);
When I read the normal of this plane, I get (0, 0, 1).
But the plane is parallel to the z axis so the value is wrong.
I tried adding
mesh.geometry.computeFaceNormals();
mesh.geometry.computeVertexNormals();
but I still get the same result.
Did I miss anything ?
How can I get correct values for normals from threejs ?
Thanks.
Geometry normals are in object space. To transform them to world space, first make sure the object matrix is updated.
object.updateMatrixWorld();
(The renderer does this for you in each render loop, so you may be able to skip this step.)
Then, compute the normal matrix:
var normalMatrix = new THREE.Matrix3().getNormalMatrix( object.matrixWorld );
Now transform the normal to world space like so:
var newNormal = normal.clone().applyMatrix3( normalMatrix ).normalize();
three.js r.66

Categories