From 8f82240856bf2b5ebb2b729d4ace57374523d1e4 Mon Sep 17 00:00:00 2001 From: Alexander Goryushkin Date: Tue, 20 Feb 2024 01:07:42 -0300 Subject: [PATCH 1/3] create svg-extruder component --- src/components/svg-extruder.js | 72 + src/index.js | 1 + src/lib/SVGLoader.js | 3173 ++++++++++++++++++++++++++++++++ 3 files changed, 3246 insertions(+) create mode 100644 src/components/svg-extruder.js create mode 100644 src/lib/SVGLoader.js diff --git a/src/components/svg-extruder.js b/src/components/svg-extruder.js new file mode 100644 index 000000000..802f9a544 --- /dev/null +++ b/src/components/svg-extruder.js @@ -0,0 +1,72 @@ +/* global AFRAME */ +var { SVGLoader } = require('../lib/SVGLoader.js'); + +AFRAME.registerComponent('svg-extruder', { + schema: { + svgString: { type: 'string' }, + depth: { type: 'number', default: 4 } + }, + init: function () { + const el = this.el; + const svgString = this.data.svgString; + + if (svgString) { + this.extrudeFromSVG(svgString); + } + }, + extrudeFromSVG: function (svgString) { + const depth = this.data.depth; + const el = this.el; + const loader = new SVGLoader(); + const svgData = loader.parse(svgString); + const svgGroup = new THREE.Group(); + const updateMap = []; + const fillMaterial = new THREE.MeshBasicMaterial({ color: "#F3FBFB" }); + const stokeMaterial = new THREE.LineBasicMaterial({ + color: "#00A5E6", + }); + + svgGroup.scale.y *= -1; + svgData.paths.forEach((path) => { + const shapes = SVGLoader.createShapes(path); + + shapes.forEach((shape) => { + const meshGeometry = new THREE.ExtrudeGeometry(shape, { + depth: depth, + bevelEnabled: false, + }); + const linesGeometry = new THREE.EdgesGeometry(meshGeometry); + const mesh = new THREE.Mesh(meshGeometry, fillMaterial); + const lines = new THREE.LineSegments(linesGeometry, stokeMaterial); + + updateMap.push({ shape, mesh, lines }); + svgGroup.add(mesh, lines); + }); + }); + + const box = new THREE.Box3().setFromObject(svgGroup); + const size = box.getSize(new THREE.Vector3()); + 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.rotateX(-Math.PI / 2); + + el.setObject3D('mesh', svgGroup); + }, + update: function (oldData) { + // If `oldData` is empty, then this means we're in the initialization process. + // No need to update. + if (Object.keys(oldData).length === 0) { return; } + + const svgString = this.data.svgString; + + if (svgString) { + this.extrudeFromSVG(svgString); + } + } +}); diff --git a/src/index.js b/src/index.js index e1b51f153..d39f66d62 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ var streetmixParsers = require('./aframe-streetmix-parsers'); var streetmixUtils = require('./tested/streetmix-utils'); require('./components/gltf-part'); require('./components/ocean'); +require('./components/svg-extruder.js'); require('./lib/aframe-cursor-teleport-component.min.js'); require('./lib/animation-mixer.js'); require('./assets.js'); diff --git a/src/lib/SVGLoader.js b/src/lib/SVGLoader.js new file mode 100644 index 000000000..bbcb101dd --- /dev/null +++ b/src/lib/SVGLoader.js @@ -0,0 +1,3173 @@ +const { + Box2, + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + Matrix3, + Path, + Shape, + ShapePath, + ShapeUtils, + SRGBColorSpace, + Vector2, + Vector3 +} = THREE; + +const COLOR_SPACE_SVG = SRGBColorSpace; + +class SVGLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + // Default dots per inch + this.defaultDPI = 90; + + // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' + this.defaultUnit = 'px'; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( text ) { + + const scope = this; + + function parseNode( node, style ) { + + if ( node.nodeType !== 1 ) return; + + const transform = getNodeTransform( node ); + + let isDefsNode = false; + + let path = null; + + switch ( node.nodeName ) { + + case 'svg': + style = parseStyle( node, style ); + break; + + case 'style': + parseCSSStylesheet( node ); + break; + + case 'g': + style = parseStyle( node, style ); + break; + + case 'path': + style = parseStyle( node, style ); + if ( node.hasAttribute( 'd' ) ) path = parsePathNode( node ); + break; + + case 'rect': + style = parseStyle( node, style ); + path = parseRectNode( node ); + break; + + case 'polygon': + style = parseStyle( node, style ); + path = parsePolygonNode( node ); + break; + + case 'polyline': + style = parseStyle( node, style ); + path = parsePolylineNode( node ); + break; + + case 'circle': + style = parseStyle( node, style ); + path = parseCircleNode( node ); + break; + + case 'ellipse': + style = parseStyle( node, style ); + path = parseEllipseNode( node ); + break; + + case 'line': + style = parseStyle( node, style ); + path = parseLineNode( node ); + break; + + case 'defs': + isDefsNode = true; + break; + + case 'use': + style = parseStyle( node, style ); + + const href = node.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) || ''; + const usedNodeId = href.substring( 1 ); + const usedNode = node.viewportElement.getElementById( usedNodeId ); + if ( usedNode ) { + + parseNode( usedNode, style ); + + } else { + + console.warn( 'SVGLoader: \'use node\' references non-existent node id: ' + usedNodeId ); + + } + + break; + + default: + // console.log( node ); + + } + + if ( path ) { + + if ( style.fill !== undefined && style.fill !== 'none' ) { + + path.color.setStyle( style.fill, COLOR_SPACE_SVG ); + + } + + transformPath( path, currentTransform ); + + paths.push( path ); + + path.userData = { node: node, style: style }; + + } + + const childNodes = node.childNodes; + + for ( let i = 0; i < childNodes.length; i ++ ) { + + const node = childNodes[ i ]; + + if ( isDefsNode && node.nodeName !== 'style' && node.nodeName !== 'defs' ) { + + // Ignore everything in defs except CSS style definitions + // and nested defs, because it is OK by the standard to have + //