Convert SVG group into a 3D shape | adding thickness with Three.js - svg

Is there a way to convert an SVG group of paths into its 3D counterpart?
In a way, It's like adding thickness to it.
I used SVGLoader to import my svg path into THREE.JS library:
loader.load(
'./test.svg',
function ( data ) {
const paths = data.paths;
for ( let i = 0; i < paths.length; i ++ ) {
const path = paths[ i ];
const material = new THREE.MeshBasicMaterial( {
color: 0x337ab7,
side: THREE.DoubleSide,
depthWrite: false,
opacity: 1,
transparent: true
} );
const shapes = path.toShapes( true );
for ( let j = 0; j < shapes.length; j ++ ) {
const shape = shapes[ j ];
const geometry = new THREE.ShapeBufferGeometry( shape );
const mesh = new THREE.Mesh( geometry, material );
group.add( mesh );
}
}...
The SVG upload works fine.
Do you know any library or trickery to accomplish this 3D transformation?

Instead of creating ShapeBufferGeometry try it with ExtrudeBufferGeometry. It should be exactly what you are looking for.

Related

html canvas clip but with an image

I have been working with html canvas compositing trying to clip a pattern with a mask.
The main issue that I have is that the mask I have comes from an svg with transparencies within the outer most border. I want the entire inside from the outer most border to be filled with the pattern.
Take this SVG for example you can see that there is a single pixel border, then some transparency, and then an opaque red inner blob. The compositing I have done works as the documentation says it should, the single pixel border and the red inner portion pick up the pattern that I want to mask into this shape. The problem is that I want to mask the entire innards starting from the single pixel border.
This is where I think clip might help. But it seems clip only works with manually drawn paths, not paths from an svg (at least that I am aware of).
Is there a way to accomplish what I am trying to do?
Regards,
James
The Path2D constructor accepts an SVG path data argument, that it will parse as the d attribute of an SVG <path> element.
You can then use this Path2D object with the clip() method:
(async () => {
// fetch the svg's path-data
const markup = await fetch("https://upload.wikimedia.org/wikipedia/commons/7/76/Autism_spectrum_infinity_awareness_symbol.svg").then(resp => resp.ok && resp.text());
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
const pathData = doc.querySelector("[d]").getAttribute("d");
// build our Path2D object and use it
const path = new Path2D(pathData);
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.clip(path);
// draw something that will get clipped
const rad = 30;
for(let y = 0; y < canvas.height; y += rad * 2 ) {
for(let x = 0; x < canvas.width; x += rad * 2 ) {
ctx.moveTo(x+rad, y);
ctx.arc(x, y, rad, 0, Math.PI*2);
}
}
ctx.fillStyle = "red";
ctx.fill();
})().catch(console.error);
<canvas width="792" height="612"></canvas>
If you need to transform this path-data (e.g scale, or rotate), then you can create a second Path2D object, and use its .addPath(path, matrix) method to do so:
// same as above, but smaller
(async () => {
const markup = await fetch("https://upload.wikimedia.org/wikipedia/commons/7/76/Autism_spectrum_infinity_awareness_symbol.svg").then(resp => resp.ok && resp.text());
const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
const pathData = doc.querySelector("[d]").getAttribute("d");
const originalPath = new Path2D(pathData);
const path = new Path2D();
// scale by 0.5
path.addPath(originalPath, { a: 0.5, d: 0.5 });
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.clip(path);
// draw something that will get clipped
const rad = 15;
for(let y = 0; y < canvas.height; y += rad * 2 ) {
for(let x = 0; x < canvas.width; x += rad * 2 ) {
ctx.moveTo(x+rad, y);
ctx.arc(x, y, rad, 0, Math.PI*2);
}
}
ctx.fillStyle = "red";
ctx.fill();
})().catch(console.error);
<canvas width="396" height="306"></canvas>

How draw plane in three js with two diagonal position vectors known?

We can add plane geometry with this code and position in anywhere:
var geometry = new THREE.PlaneGeometry( 5, 20, 32 );
var material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} );
var plane = new THREE.Mesh( geometry, material );
scene.add( plane );
But how can we add plane when two diagonal positions are known. Is this possible?
The closest to this so far I have is to get center of two positions and it positions the plane correctly but does not size correctly:
var dir = pointB.clone().subtract(pointA);
var length = dir.length();
dir = dir.normalize().scale(length * 50);
center_position = pointA.clone().add(dir);
Direction is not important in my case, can be set by lookat().
Find the subtraction vector of those two, its absolute values are plane size.
Average vector of those two is plane's center.
Example (r120):
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://threejs.org/build/three.module.js";
import { OrbitControls } from "https://threejs.org/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 5, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.GridHelper());
let vec1 = new THREE.Vector3(0, 2, 0);
let vec2 = new THREE.Vector3(4, 0, 0);
let size = new THREE.Vector3().subVectors(vec2, vec1);
let center = new THREE.Vector3().addVectors(vec1, vec2).multiplyScalar(0.5);
let planeWidth = Math.abs(size.x);
let planeHeight = Math.abs(size.y);
let planeGeom = new THREE.PlaneBufferGeometry(planeWidth, planeHeight, planeWidth, planeHeight);
let planeMat = new THREE.MeshBasicMaterial({color: "aqua", wireframe: true});
let plane = new THREE.Mesh(planeGeom, planeMat);
plane.position.copy(center);
scene.add(plane);
renderer.setAnimationLoop(()=>{
renderer.render(scene, camera);
});
</script>

THREE.js - How to TWEEN individual paths/polygons of imported SVG?

I have imported a test SVG using the SVGLoader and am able to tween the SVG object using Tween.js.
I would like to tween the individual paths and polygons within the loaded SVG object - is this possible?
SVGLoader:
var loader = new THREE.SVGLoader();
loader.load( 'svg/test.svg', function ( paths ) {
var group = new THREE.Group();
group.scale.multiplyScalar( 0.25 );
group.position.x = -150;
group.position.y = 70;
group.scale.y *= - 1;
for ( var i = 0; i < paths.length; i ++ ) {
var path = paths[ i ];
var material = new THREE.MeshBasicMaterial( {
color: path.color,
side: THREE.DoubleSide,
depthWrite: false
} );
var shapes = path.toShapes( true );
for ( var j = 0; j < shapes.length; j ++ ) {
var shape = shapes[ j ];
var geometry = new THREE.ShapeBufferGeometry( shape );
var mesh = new THREE.Mesh( geometry, material );
group.add( mesh );
}
}
scene.add( group );
} );
Tween Function:
triggerTweens();
function triggerTweens() {
new TWEEN.Tween( title.position ).to( { x: 10 }, 5000 ).start();
}
I have found the following solution to this. Essentially adding the tween trigger into the loader function and passing the variable mesh, ( not group ) - allows tweening on the constituent paths and polygons etc of the SVG. To take this a step further I will figure out how to perform the tween for each separately which shouldn't be too tricky at this point.
New SVG Loader:
var loader = new THREE.SVGLoader();
loader.load( 'svg/test.svg', function ( paths ) {
var group = new THREE.Group();
group.scale.multiplyScalar( 0.25 );
group.position.x = -150;
group.position.y = 70;
group.scale.y *= - 1;
for ( var i = 0; i < paths.length; i ++ ) {
var path = paths[ i ];
var material = new THREE.MeshBasicMaterial( {
// color: path.color,
color: 0xff0000,
side: THREE.DoubleSide,
depthWrite: false
} );
var shapes = path.toShapes( true );
for ( var j = 0; j < shapes.length; j ++ ) {
var shape = shapes[ j ];
var geometry = new THREE.ExtrudeGeometry(shape, {
depth: 1,
bevelEnabled: false
});
var mesh = new THREE.Mesh( geometry, material );
// Randomly position each constituent mesh to demo
randX = Math.random() * (20000 - -20000) + -20000;
randY = Math.random() * (20000 - -20000) + -20000;
randZ = Math.random() * (20000 - -20000) + -20000;
mesh.position.set( randX, randY, randZ );
group.add( mesh );
}
triggerTweens(mesh); // Trigger tweens passing mesh variable
}
scene.add( group );
} );
New Tween Function:
// Bring all meshes back to position 0,0,0 Over 5000ms
function triggerTweens(mesh) {
new TWEEN.Tween( mesh.position ).to( { x: 0, y: 0, z: 0 }, 5000 ).easing(TWEEN.Easing.Cubic.In).start();
}

Three JS SVG Renderer with grid and transparent surfaces

I am trying to render SVG Files with threeJS. As PNG files don't look pretty nice I am using now the SVGRenderer.
My SVG Files looks sth like this:
When I render the Image it appears to be completely black (the colour I have chosen though)
I found the option to render it as a wireframe, but that shows some framelines I don't want to see
My JS looks like this:
var loader = new THREE.SVGLoader();
loader.load( 'media/qpartdark/SVG/qpart2.svg', function ( paths ) {
var group = new THREE.Group();
group.scale.multiplyScalar( 0.25 );
group.position.x = - 70;
group.position.y = 0;
group.position.z = 0;
group.scale.y *= -1;
for ( var i = 0; i < paths.length; i ++ ) {
var path = paths[ i ];
var material = new THREE.MeshBasicMaterial( {
color: path.color,
side: THREE.DoubleSide,
depthWrite: false,
wireframe: false,
wireframeLinewidth: 0.01
} );
var shapes = path.toShapes( true );
for ( var j = 0; j < shapes.length; j ++ ) {
var shape = shapes[ j ];
var geometry = new THREE.ShapeBufferGeometry( shape );
var mesh = new THREE.Mesh( geometry, material );
group.add( mesh );
}
}
scene.add( group );
} );
//
renderer = new THREE.WebGLRenderer( { alpha: true, antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
Do you have any hints how I can render the svg as in the example?
here is the original SVG:

Three.js Server Rendering

I am trying to create a script that loads my .obj and .mtl files in three.js and then takes a screenshot of it. I'm doing this for rendering items and characters on my website. I made a function called renderItem that calls the .php page on which the item is rendered and the screenshot is taken but the only way to get it to work is by echoing the contents of the page (not really what I wanted to do).
Now I am looking at using three.js with node to render my items on the server. I've been researching different ways people have done this for days and I can get it to work using a CanvasRenderer or headless gl, but now my issue is that only loading simple boxes works with that. My .obj files and my .mtl files load but do not show up at all. I have been working on this code for over a week and I cannot find anyone else with the same issue I am having. Idk what else to do so any help would be appreciated. I will paste the code below:
var MockBrowser = require('mock-browser').mocks.MockBrowser;
var mock = new MockBrowser();
global.document = mock.getDocument();
global.window = MockBrowser.createWindow();
global.THREE = require('three');
var Canvas = require('canvas');
var gl = require("gl")(500, 500);
var OBJLoader = require('three/examples/js/loaders/OBJLoader.js');
var MTLLoader = require('three/examples/js/loaders/MTLLoader.js');
require('three/examples/js/renderers/CanvasRenderer.js');
require('three/examples/js/renderers/Projector.js');
global.XMLHttpRequest = require('xhr2').XMLHttpRequest;
var scene, camera, renderer, controls;
var express = require('express');
var app = express();
const fitCameraToObject = function ( camera, object, offset, controls ) {
offset = offset || 1.25;
const boundingBox = new THREE.Box3();
// get bounding box of object - this will be used to setup controls and camera
boundingBox.setFromObject( object );
const center = boundingBox.getCenter();
const size = boundingBox.getSize();
// get the max side of the bounding box (fits to width OR height as needed )
const maxDim = Math.max( size.x, size.y, size.z );
const fov = camera.fov * ( Math.PI / 180 );
let cameraZ = Math.abs( maxDim / 4 * Math.tan( fov * 2 ) );
cameraZ *= offset; // zoom out a little so that objects don't fill the screen
camera.position.z = cameraZ;
const minZ = boundingBox.min.z;
const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ;
camera.far = cameraToFarEdge * 3;
camera.updateProjectionMatrix();
if ( controls ) {
// set camera to rotate around center of loaded object
controls.target = center;
// prevent camera from zooming out far enough to create far plane cutoff
controls.maxDistance = cameraToFarEdge * 2;
controls.saveState();
} else {
camera.lookAt( center )
}
}
function init() {
var manager = new THREE.LoadingManager();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 70, 1, 0.01, 100 );
camera.position.set(-0.25,0.86,0.76);
var canvas = new Canvas.createCanvas(500, 500);
canvas.style = {}; // dummy shim to prevent errors during render.setSize
var renderer = new THREE.CanvasRenderer({
canvas: canvas
});
renderer.setSize( 500, 500 );
// lighting
ambientLight = new THREE.AmbientLight(0xffffff,1.4);
scene.add(ambientLight);
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setTexturePath('texturepath');
mtlLoader.setPath('mtlpath');
mtlLoader.load('Boots.mtl', function(materials) {
materials.preload();
var objLoader = new THREE.OBJLoader(manager);
objLoader.setMaterials(materials);
objLoader.setPath('objpath');
objLoader.load('Boots.obj', function(object) {
scene.add(object);
fitCameraToObject(camera, object, 7, controls);
renderer.render( scene, camera );
});
});
/*
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var cube = new THREE.Mesh( geometry, material );
scene.add( cube );
camera.position.z = 5;*/
var animate = function () {
//requestAnimationFrame( animate );
renderer.render( scene, camera );
};
animate();
manager.onLoad = function () {
renderer.render( scene, camera );
console.log('Image: ' + renderer.domElement.toDataURL('image/png'));
};
}
app.get('/', function(req, res) {
res.send(req.params);
init();
});
app.listen(3000, function() {
console.log('Server started on Port 3000...');
});
I changed the paths because I don't want my website name to be shown but I can confirm that the object and mtl loads correctly. I commented out the code that makes a box but when it isn't commented it prints out a base64 code which I just paste into a website I found online and it shows me an image of the green box. The MTL/OBJ Loader part prints out a base64 code but doesn't show anything. If I need to give more info let me know but I am completely lost.

Resources