I'm trying to get the basic ThreeJS Water2 example setup that you can find here: https://threejs.org/examples/?q=water#webgl_water
Source code here: https://github.com/mrdoob/three.js/blob/master/examples/webgl_water.html
The process seems so straightforward it hurts that it's not working: Supply the geometry of a plane to the constructor, supply parameters, and then add that object to the scene. You can see an example of this on line 86 of the examples' source above.
Here's my attempt at doing it inside an Aframe component:
init() {
let mesh;
let waterObj;
this.el.object3D.traverse(obj => {
if (obj.type == "Mesh") {
mesh = obj;
}
});
waterObj = new Water(mesh, {
scale: 4,
flowDirection: new THREE.Vector2(1, 1),
textureWidth: 1024,
textureHeight: 1024
});
this.el.object3D.attach(waterObj);
}
});
but it doesn't seem to work. If I use object3D.attach() it produces an error that says sphere is undefined (no idea what "sphere," is), and if I use object3D = waterObj then the color of the plane slightly changes, but nothing else.
Does anyone have experience with getting this setup?
It should be working as long as you provide the geometry in the constructor, not the mesh:
waterObj = new Water(mesh.geometry, {
scale: 4,
flowDirection: new THREE.Vector2(1, 1),
textureWidth: 1024,
textureHeight: 1024
});
Also keep in mind that attach won't apply the parent transform to the waterObj, so the plane might appear at (0,0,0).
Other than that - it should be working - check out
this a-frame + THREE.Water example
Related
I am trying to implement a dice roller in three js and cannon-es.
It works perfectly fine if I have only one dice. It rolls against the ground plane in a reasonable way.
When I add another dice it also works fine against the ground plane, but it completely breaks down and crashes the simulation as soon as the two CANNON.ConvexPolyhedron objects collide.
The error I am getting spammed with after the collision is this
cannon-es.js:920 Uncaught TypeError: Cannot read properties of undefined (reading 'x')
at Vec3.copy (cannon-es.js:920:21)
at ConvexPolyhedron.clipFaceAgainstHull (cannon-es.js:2662:24)
at ConvexPolyhedron.clipAgainstHull (cannon-es.js:2404:12)
at Narrowphase.convexConvex (cannon-es.js:10916:10)
at Narrowphase.getContacts (cannon-es.js:10608:33)
at World.internalStep (cannon-es.js:12649:22)
at World.step (cannon-es.js:12515:12)
at updatePhysics (rolling.svelte:154:11)
at animate (rolling.svelte:142:5)
I am using a STL loader from three js to load a stl file of a twenty sided dice, and then I am creating a cannon body from that like this
function createConvexHull(mesh: THREE.Mesh) {
const position = mesh.geometry.attributes.position.array
const points: CANNON.Vec3[] = []
const faces: number[][] = []
for (let i = 0; i < position.length; i += 3) {
points.push(new CANNON.Vec3(position[i], position[i + 1], position[i + 2]))
}
for (let i = 0; i < position.length / 3; i += 3) {
faces.push([i, i + 1, i + 2])
}
const convexGeometry = new CANNON.ConvexPolyhedron({
vertices: points,
faces: faces
})
const body = new CANNON.Body({ mass: 1 })
body.addShape(convexGeometry)
return body
}
I have little to no idea what is going wrong, as the error message is not saying much, but I've tried computing vertex normals on the ThreeJS mesh, which did nothing.
I've also tried merging verticies as I read that others have had issues with that (merged before creating the cannon body)
geometry = BufferGeometryUtils.mergeVertices(geometry, 0.01)
but again, it did nothing
I'm just guessing, but you may try defining the normals
new ConvexPolyhedron({ vertices, faces, normals });
I experienced weird behaviors with convex shape collisions (not errors, but like launching one of the colliding objects into the void), that I could resolve by re calculating the normal vectors
https://github.com/tomo0613/offroadJS_v2/blob/355b6aabf0446deefffba6d60e24a257836916ea/src/mapModules/baseMapElementComponents.ts#L137
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.
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.
All I want to do is load an OBJ file and translate its coordinates to the world origins (0,0,0) so that orbit controls work perfectly (no Pivot points please).
I'd like to load random OBJ objects with different geometries/center points and have them translated automatically to the scene origin. In other words, a 'hard coded' translate solution for a specific model won't work
This has got to be one of the most common scenarios for Three JS (basic 3d object viewer), so I'm surprised I can't find a definitive solution on SO.
Unfortunately there are a lot of older answers with deprecated functions, so I would really appreciate a new answer even if there are similar solutions out there.
Things I've tried
the code below fits the object nicely to the camera, but doesn't solve the translation/orbiting problem.
// fit camera to object
var bBox = new THREE.Box3().setFromObject(scene);
var height = bBox.size().y;
var dist = height / (2 * Math.tan(camera.fov * Math.PI / 360));
var pos = scene.position;
// fudge factor so the object doesn't take up the whole view
camera.position.set(pos.x, pos.y, dist * 0.5);
camera.lookAt(pos);
Apparently the geometry.center() is good for translating an object's coordinates back to the origin, but the THREE.GeometryUtils.center has been replaced by geometry.center() and I keep getting errors when trying to use it.
when loading OBJs, geometry has now been replaced by bufferGeometry. I can't seem to cast the buffergeometry into geometry in order to use the center() function. do I have to place this in the object traverse > child loop like so? this seems unnecessarily complicated.
geometry = new THREE.Geometry().fromBufferGeometry( child.geometry );
My code is just a very simple OBJLoader.
var objLoader = new THREE.OBJLoader();
objLoader.setPath('assets/');
objLoader.load('BasketballNet_Skull.obj', function (object) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material = material;
}
} );
scene.add(object);
});
(BTW first real question on SO so forgive any formatting / noob issues)
Why not object.geometry.center()?
var objLoader = new THREE.OBJLoader();
objLoader.setPath('assets/');
objLoader.load('BasketballNet_Skull.obj', function (object) {
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material = material;
child.geometry.center();
}
} );
scene.add(object);
OK figured this out, using some very useful functions from Meshviewer Master, an older Three JS object viewer.
https://github.com/ideesculture/meshviewer
All credit to Gautier Michelin for this code
https://github.com/gautiermichelin
After loading the OBJ, you need to do 3 things:
1. Create a Bounding Box based on the OBJ
boundingbox = new THREE.BoundingBoxHelper(object, 0xff0000);
boundingbox.update();
sceneRadiusForCamera = Math.max(
boundingbox.box.max.y - boundingbox.box.min.y,
boundingbox.box.max.z - boundingbox.box.min.z,
boundingbox.box.max.x - boundingbox.box.min.x
)/2 * (1 + Math.sqrt(5)) ; // golden number to beautify display
2. Setup the Camera based on this bounding box / scene radius
function showFront() {
if (objectCopy !== undefined) objectCopy.rotation.z = 0;
controls.reset();
camera.position.z = 0;
camera.position.y = 0;
camera.position.x = sceneRadiusForCamera;
camera.lookAt(scene.position);
}
(the mesh viewer code also contains functions for viewing left, top, etc)
3. Reposition the OBJ to the scene origin
Like any centering exercise, the position is then the width and height divided by 2
function resetObjectPosition(){
boundingbox.update();
size.x = boundingbox.box.max.x - boundingbox.box.min.x;
size.y = boundingbox.box.max.y - boundingbox.box.min.y;
size.z = boundingbox.box.max.z - boundingbox.box.min.z;
// Repositioning object
objectCopy.position.x = -boundingbox.box.min.x - size.x/2;
objectCopy.position.y = -boundingbox.box.min.y - size.y/2;
objectCopy.position.z = -boundingbox.box.min.z - size.z/2;
boundingbox.update();
if (objectCopy !== undefined) objectCopy.rotation.z = 0;
}
From my understanding of your question, you want the objects that are added to the scene in the origin of the camera view. I believe the common way of achieving an object viewer solution is adding camera controls to your camera in the scene mostly THREE.OrbitControls and specifying the target for the camera as the object that you want to focus on. This makes the object focused to be in the center and the camera rotation and movement will be based on that object.
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.