I've looked for resources online, but I have not seen a way to extrude a colored image in Three.js. I'm trying to create something like a Minecraft item where the image is used to then create an extruded geometry. An example would be: https://minecraft.gamepedia.com/File:BowSpinning3.gif
I've tried looking at this resource: https://muffinman.io/three-js-extrude-svg-path/ but this only extrudes uncolored SVGs.
loader.load('./textures/diamondbleu.svg', function (data) {
// Group we'll use for all SVG paths
const svgGroup = new THREE.Group();
// When importing SVGs paths are inverted on Y axis
// it happens in the process of mapping from 2d to 3d coordinate system
svgGroup.scale.y *= -1;
const material = new THREE.MeshLambertMaterial();
// Loop through all of the parsed paths
data.paths.forEach((path, i) => {
const shapes = path.toShapes(true);
// Each path has array of shapes
shapes.forEach((shape, j) => {
// Finally we can take each shape and extrude it
const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 20,
bevelEnabled: false
});
// Create a mesh and add it to the group
const mesh = new THREE.Mesh(geometry, material);
svgGroup.add(mesh);
});
});
// Get group's size
const box = new THREE.Box3().setFromObject(svgGroup);
const size = new THREE.Vector3();
box.getSize(size);
const yOffset = size.y / -2;
const xOffset = size.x / -2;
// Offset all of group's elements, to center them
svgGroup.children.forEach(item => {
item.position.x = xOffset;
item.position.y = yOffset;
});
svgGroup.position.set(0, blockSize*75, 0);
// Finally we add svg group to the scene
scene.add(svgGroup);
})
Is there a way to modify the code to allow for colored SVGs? Thanks!
You can use the SVGLoader that's available in the "examples/jsm/loaders/" folder.
The docs have outlined how to generate SVGs in 3D space, it looks like your code sample is missing the part where the paths loop makes a new material and assigns a color for each path:
var material = new THREE.MeshBasicMaterial( {
color: path.color,
side: THREE.DoubleSide,
depthWrite: false
} );
Your code seems to create a single LambertMaterial with no colors assigned, and no lights. Lambert materials need lights to be illuminated, whereas BasicMaterial just shows the color without need of lights.
Look at the code in this demo for another example. Instead of using path.color, this demo finds the color by accessing path.userData.style.fill. I think you'll want the latter approach, depending on your SVG file.
Related
Default wrapS and wrapT is THREE.ClampToEdgeWrapping, which mean, that if I have square picture and geometry like that const geometry = new BoxGeometry(3, 2, 1); I will lose proportion of the source. THREE.RepeatWrapping and THREE.MirroredRepeatWrapping are not suitable for my case, because I need only one image on each side in the center. Is there any way to set width and height of image that is loaded (and maybe change size on each side of сuboid)? The way I'm trying to do it now (not working):
const geometry = new BoxGeometry(9.89326190948486, 0.953460395336151, 3);
const material = new MeshStandardMaterial({
map: new TextureLoader().load(
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMjU2cHgiIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0IiA+PHJlY3QgaWQ9InN2Z0VkaXRvckJhY2tncm91bmQiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBzdHlsZT0iZmlsbDogbm9uZTsgc3Ryb2tlOiBub25lOyIvPjxjaXJjbGUgaWQ9ImUxX2NpcmNsZSIgY3g9IjEyMC4wMCIgY3k9IjExNi4wMCIgc3R5bGU9ImZpbGw6d2hpdGU7c3Ryb2tlOmJsYWNrO3N0cm9rZS13aWR0aDo5cHgiIHI9IjEyNy40NiIgdHJhbnNmb3JtPSJtYXRyaXgoMC43MDcxMDcgMC43MDcxMDcgLTAuNzA3MTA3IDAuNzA3MTA3IDEyOC4xNzIgLTQxLjg3NzIpIi8+PGNpcmNsZSBpZD0iZTJfY2lyY2xlIiBjeD0iMTEzLjAwIiBjeT0iMTEyLjAwIiBzdHlsZT0iZmlsbDpyb3N5YnJvd247c3Ryb2tlOmJsYWNrO3N0cm9rZS13aWR0aDoxcHgiIHI9IjQzLjg2Ii8+PC9zdmc+',
function () {
// texture still lose proportion and behaves as THREE.ClampToEdgeWrapping
material.map.image.height = 3;
material.map.image.width = 3;
material.map.needsUpdate = true;
}),
color: new Color(0.7, 0.7, 0.7)
});
Screenshot of what I got with setting height and width to 128. This thing should be a circle
Screenshot of what I got with setting height and width to 0.9. Also should be a circle.
I am using LineSegmentsGeometry and LineMaterial to create thick cube edges. I want to change the color of the edge on hover.
const edgesGeometry = new LineSegmentsGeometry().fromEdgesGeometry(
new THREE.EdgesGeometry(mesh.geometry, 40)
);
const colors = [];
for (let i = 0; i < edgesGeometry.attributes.position.count; i++) {
colors.push(0, 0, 0);
}
edgesGeometry.setAttribute(
"color",
new THREE.Float32BufferAttribute(colors, 3)
);
const edgesMaterial = new LineMaterial({
color: "black",
vertexColors: true,
linewidth: 0.001
});
const line = new THREE.LineSegments(edgesGeometry, edgesMaterial);
const setLineColor = (color) => {
const { index, object } = intersected;
object.geometry.attributes.color.setXYZ(index, color.r, color.g, color.b);
object.geometry.attributes.color.setXYZ(index + 1, color.r, color.g, color.b);
object.geometry.attributes.color.needsUpdate = true;
};
This code only works if using thin lines with LineBasicMaterial. Can I do it somehow with bold lines?
I also have other shapes with this logic
sandbox here
https://codesandbox
You can do it with fat lines! LineSegmentsGeometry (fat lines) is structured quite a bit differently from EdgesGeometry, though, so the approach must be updated.
Looking at your example there are a few things to note:
When creating fat lines instanced BufferAttributes are created for the start and end of each line (instanceStart and instanceEnd). You cannot use geometry.attributes.position to determine the number of colors needed for a segment. Instead you should use attributes.instanceStart.count and use the LineSegmentsGeometry.setColors function to ensure the correct instanced color attributes for each segment are set up.
The LineMaterial color should be set to white so the vertex colors show when multiplied.
"index" is not provided when intersecting with fat lines. Instead you should use "faceIndex" and use that to set the color fields on the instanceColorStart and instanceColorEnd attributes and update them accordingly.
Here's demo modified for your provided code showing how to accomplish this with some short inline comments:
https://jsfiddle.net/juoz5yLv/1/
I'm making a project to make simple 3d models with dots and lines (curved or not). For a first version I used SVG elements for simple render, smooth curves and mouse events.
Now I'm trying to use Three.js renderer instead of SVG. I got to create 3d tubes to replace the curved lines, but I don't know how to create 3d surfaces based on multiple xyz coordinates.
Here is an example of a model made of 4 points and 4 lines (3 straights and 1 curved):
We could imagine that the curve is extruded to more points, to something like this:
Then I would like to create a surface (or plane), just like the blue shape:
I got inspired of this topic: convert bezier into a plane road in three.js
var material = new THREE.MeshBasicMaterial({ color: 0xc0c0c0 });
var path = new THREE.CatmullRomCurve3([
new THREE.Vector3(dots[0].x, dots[0].y, -dots[0].z),
new THREE.Vector3(dots[1].x, dots[1].y, -dots[1].z),
new THREE.Vector3(dots[2].x, dots[2].y, -dots[2].z),
new THREE.Vector3(dots[3].x, dots[3].y, -dots[3].z),
new THREE.Vector3(dots[0].x, dots[0].y, -dots[0].z)
]);
var pts = [],
a ;
for (var i = 0 ; i < 3 ; i++) {
a = i / 3 * Math.PI;
pts.push(new THREE.Vector2(Math.cos(a) * 1, Math.sin(a) * 1));
}
var shape = new THREE.Shape(pts);
var geometry = new THREE.ExtrudeGeometry(shape, {
steps : 10,
bevelEnabled : false,
extrudePath : path
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
But this sample code only creates another tube-like shape.
Instead of using four (Bezier) curves to create a surface, use Bezier SURFACES or NURBS — they are mathematically designed for it. Here is a demo with source code.
I am writing a trace-line function for a visualization project that requires jumping between time step values. My issue is that during rendering, the line created using THREE.js's BufferGeometry and the setDrawRange method, will only be visible if the origin of the line is in the camera's view. Panning away will result in the line disappearing and panning toward the origin of the line (usually 0,0,0) will make it appear again. Is there a reason for this and a way around it? I have tried playing around with render settings.
The code I have included is being used in testing and draws the trace of the object as time progresses.
var traceHandle = {
/* setup() returns trace-line */
setup : function (MAX_POINTS) {
var lineGeo = new THREE.BufferGeometry();
//var MAX_POINTS = 500*10;
var positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
lineGeo.addAttribute('position', new THREE.BufferAttribute(positions, 3));
var lineMaterial = new THREE.LineBasicMaterial({color:0x00ff00 });
var traceLine = new THREE.Line(lineGeo, lineMaterial);
scene.add(traceLine);
return traceLine;
},
/****
* updateTrace() updates and draws trace line
* Need 'index' saved globally for this
****/
updateTrace : function (traceLine, obj, timeStep, index) {
traceLine.geometry.setDrawRange( 0, timeStep );
traceLine.geometry.dynamic = true;
var positions = traceLine.geometry.attributes.position.array;
positions[index++]=obj.position.x;
positions[index++]=obj.position.y;
positions[index++]=obj.position.z;
// required after the first render
traceLine.geometry.attributes.position.needsUpdate = true;
return index;
}
};
Thanks a lot!
Likely, the bounding sphere is not defined or has radius zero. Since you are adding points dynamically, you can set:
traceLine.frustumCulled = false;
The other option is to make sure the bounding sphere is current, but given your use case, that seems too computationally expensive.
three.js r.73
In three.js, I want to add a mesh to a position in the scene
I've tried:
// mesh is an instance of THREE.Mesh
// scene is an instance of THREE.Scene
scene.add(mesh)
scene.updateMatrixWorld(true)
mesh.matrixWorld.setPosition(new THREE.Vector3(100, 100, 100))
scene.updateMatrix()
BUT it didn't affect anything.
What should I do ?
I would recommend you to check the documentation over here:
http://threejs.org/docs/#Reference/Objects/Mesh
As you can see on the top of the docu-page, Mesh inherits from "Object3D". That means that you can use all methods or properties that are provided by Object3D. So click on the "Object3D" link on the docu-page and check the properties list. You will find the property ".position". Click on ".position" to see what data-type it is. Paha..its Vector3.
So try to do the following:
// scene is an instance of THREE.Scene
scene.add(mesh);
mesh.position.set(100, 100, 100);
i saw it on a github earlier. (three.js r71 )
mesh.position.set(100, 100, 100);
and can be done for individuals
mesh.position.setX(200);
mesh.position.setZ(200);
reference: https://threejs.org/docs/#api/math/Vector3
detailed explanation is below:
since mesh.position is "Vector3". Vector3() has setX() setY() and setZ() methods. we can use it like this.
mesh.position = new THREE.Vector3() ; //see position is Vector3()
vector1 = new THREE.Vector3();
mesh.position.setX(100); //or this
vector1.setX(100) // because all of them is Vector3()
camera1.position.setZ(100); // or this
light1.position.setY(100) // applicable to any object.position
I prefer to use Vector3 to set position.
let group = new THREE.Group();
// position of box
let vector = new THREE.Vector3(10, 10, 10);
// add wooden Box
let woodenBox = new THREE.Mesh(boxGeometry, woodMaterial);
//update postion
woodenBox.position.copy(vector);
// add to scene
group.add(woodenBox)
this.scene.add(group);
If some one looking for way to update position from Vector3
const V3 = new THREE.Vector3(0,0,0) // Create variable in zero position
const box = new THREE.Mesh(geometry, material) // Create an object
Object.assign(box.position, V3) // Put the object in zero position
OR
const V3 = new THREE.Vector3(0,0,0) // Create variable in zero position
const box = new THREE.Mesh(geometry, material) // Create an object
box.position.copy(V3)