├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── public └── index.html └── src └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Baoxuan(Brian) Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PaperjsToThreejs 2 | simple demo to use Paper.js as 2D input for Three.js extrusion mesh 3 | 4 | ## Live demo 5 | 6 | [Demo](https://brianxu.github.io/PaperjsToThreejs/) 7 | 8 | ## Install dependencies 9 | 10 | Run `npm install` 11 | 12 | ## Running demo 13 | 14 | Simply open index.html file with browser and start drawing! -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svgeditor", 3 | "version": "0.1.0", 4 | "description": "js svg editor", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "test" 8 | }, 9 | "keywords": [ 10 | "javascript", 11 | "svg", 12 | "paperjs" 13 | ], 14 | "author": "baoxuan xu", 15 | "license": "MIT", 16 | "dependencies": { 17 | "paper": "^0.11.5", 18 | "three": "^0.89.0" 19 | } 20 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Paper 4 | 5 | 6 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Draw 47 | Erase 48 | Modify 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | // 2D draw view 2 | paper.install(window); 3 | var modifyTool, drawTool, eraseTool; 4 | window.onload = function () { 5 | // Get a reference to the canvas object 6 | var canvas = document.getElementById('myCanvas'); 7 | // Create an empty project and a view for the canvas: 8 | paper.setup(canvas); 9 | // // Create a Paper.js Path to draw a line into it: 10 | // var path = new paper.Path(); 11 | // // Give the stroke a color 12 | // path.strokeColor = 'black'; 13 | // var start = new paper.Point(100, 100); 14 | // // Move to start and draw a line from there 15 | // path.moveTo(start); 16 | // // Note that the plus operator on Point objects does not work 17 | // // in JavaScript. Instead, we need to call the add() function: 18 | // path.lineTo(start.add([200, -50])); 19 | // // Draw the view now: 20 | // paper.view.draw(); 21 | 22 | // Create a simple drawing tool: 23 | 24 | var view = paper.view; 25 | 26 | 27 | var values = { 28 | paths: 50, 29 | minPoints: 5, 30 | maxPoints: 15, 31 | minRadius: 30, 32 | maxRadius: 90 33 | }; 34 | 35 | paper.settings.handleSize = 10; 36 | 37 | var hitOptions = { 38 | segments: true, 39 | stroke: true, 40 | fill: true, 41 | tolerance: 5 42 | }; 43 | 44 | var compoundPath = null; 45 | 46 | modifyTool = new Tool(); 47 | var segment, path; 48 | var movePath = false; 49 | modifyTool.onMouseDown = function (event) { 50 | segment = path = null; 51 | var hitResult = project.hitTest(event.point, hitOptions); 52 | if (!hitResult) 53 | return; 54 | 55 | if (event.modifiers.shift) { 56 | if (hitResult.type == 'segment') { 57 | hitResult.segment.remove(); 58 | }; 59 | return; 60 | } 61 | 62 | if (hitResult) { 63 | path = hitResult.item; 64 | if (hitResult.type == 'segment') { 65 | segment = hitResult.segment; 66 | } else if (hitResult.type == 'stroke') { 67 | var location = hitResult.location; 68 | segment = path.insert(location.index + 1, event.point); 69 | path.smooth(); 70 | } 71 | } 72 | movePath = hitResult.type == 'fill'; 73 | if (movePath) 74 | project.activeLayer.addChild(hitResult.item); 75 | } 76 | modifyTool.onMouseMove = function (event) { 77 | project.activeLayer.selected = false; 78 | if (event.item) 79 | event.item.selected = true; 80 | } 81 | modifyTool.onMouseDrag = function (event) { 82 | if (segment) { 83 | segment.point = segment.point.add(event.delta); 84 | path.smooth(); 85 | } else if (path) { 86 | path.position = path.position.add(event.delta); 87 | } 88 | } 89 | modifyTool.onMouseUp = function (event) { 90 | updateGeometry(); 91 | } 92 | 93 | drawTool = new Tool(); 94 | eraseTool = new Tool(); 95 | 96 | var addAndMoveToolDown = function (event) { 97 | path = new Path(); 98 | path.strokeColor = 'black'; 99 | path.strokeWidth = 5; 100 | path.add(event.point); 101 | }; 102 | var addAndMoveToolDrag = function (event) { 103 | path.add(event.point); 104 | } 105 | drawTool.onMouseDown = addAndMoveToolDown; 106 | drawTool.onMouseDrag = addAndMoveToolDrag; 107 | drawTool.onMouseUp = function (event) { 108 | // path.selected = true; 109 | path.fillColor = 'red'; 110 | path.closePath(); 111 | path.simplify(); 112 | if (compoundPath !== null) { 113 | var temp = compoundPath.unite(path); 114 | compoundPath.remove(); 115 | compoundPath = temp; 116 | } else { 117 | compoundPath = path.clone(); 118 | } 119 | path.remove(); 120 | updateGeometry(); 121 | } 122 | 123 | eraseTool.onMouseDown = addAndMoveToolDown; 124 | eraseTool.onMouseDrag = addAndMoveToolDrag; 125 | eraseTool.onMouseUp = function (event) { 126 | path.closePath(); 127 | path.simplify(); 128 | if (compoundPath !== null) { 129 | var temp = compoundPath.subtract(path); 130 | compoundPath.remove(); 131 | compoundPath = temp; 132 | } 133 | path.remove(); 134 | updateGeometry(); 135 | } 136 | 137 | drawTool.activate(); 138 | var updateGeometry = function () { 139 | var paths; 140 | if (compoundPath.children) { 141 | paths = compoundPath.children; 142 | } else { 143 | if (!compoundPath.clockwise) { 144 | compoundPath.reverse(); 145 | } 146 | paths = [compoundPath]; 147 | } 148 | var solidsHolesMap = getsolidsHolesMap(paths); 149 | console.log(compoundPath); 150 | var geometry = getExtrusionGeometry(JSON.parse(JSON.stringify(solidsHolesMap))); 151 | var material = new THREE.MeshPhongMaterial({ color: 0xdddddd, specular: 0x009900, shininess: 30, flatShading: true }) 152 | var mesh = new THREE.Mesh(geometry, material); 153 | scene.add(mesh); 154 | scene.remove(extrudedMesh); 155 | extrudedMesh = mesh; 156 | 157 | } 158 | } 159 | 160 | // 3D extruded view 161 | var renderer, stats, scene, camera, extrudedMesh; 162 | function init() { 163 | var container = document.getElementById('canvas3D'); 164 | 165 | // 166 | scene = new THREE.Scene(); 167 | scene.background = new THREE.Color(0xb0b0b0); 168 | // 169 | camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000); 170 | camera.position.set(0, 0, 200); 171 | // 172 | var group = new THREE.Group(); 173 | scene.add(group); 174 | // 175 | var directionalLight = new THREE.DirectionalLight(0xffffff, 0.6); 176 | directionalLight.position.set(0.75, 0.75, 1.0).normalize(); 177 | scene.add(directionalLight); 178 | var ambientLight = new THREE.AmbientLight(0xcccccc, 0.2); 179 | scene.add(ambientLight); 180 | 181 | var material = new THREE.MeshPhongMaterial({ color: 0xdddddd, specular: 0x009900, shininess: 30, flatShading: true }) 182 | var geometry = new THREE.Geometry(); 183 | extrudedMesh = new THREE.Mesh(geometry, material); 184 | scene.add(extrudedMesh); 185 | // 186 | var helper = new THREE.GridHelper(160, 10); 187 | helper.rotation.x = Math.PI / 2; 188 | group.add(helper); 189 | 190 | // 191 | renderer = new THREE.WebGLRenderer({ antialias: true }); 192 | renderer.setPixelRatio(window.devicePixelRatio); 193 | renderer.setSize(window.innerWidth / 2, window.innerHeight / 2); 194 | container.appendChild(renderer.domElement); 195 | // 196 | var controls = new THREE.OrbitControls(camera, renderer.domElement); 197 | // 198 | stats = new Stats(); 199 | stats.dom.style.left = "50%"; 200 | container.appendChild(stats.dom); 201 | // 202 | window.addEventListener('resize', onWindowResize, false); 203 | } 204 | function onWindowResize() { 205 | camera.aspect = window.innerWidth / window.innerHeight; 206 | camera.updateProjectionMatrix(); 207 | renderer.setSize(window.innerWidth / 2, window.innerHeight / 2); 208 | } 209 | 210 | function animate() { 211 | requestAnimationFrame(animate); 212 | render(); 213 | stats.update(); 214 | } 215 | function render() { 216 | renderer.render(scene, camera); 217 | } 218 | init(); 219 | animate(); 220 | 221 | // methods to transfer data from paperjs to threejs 222 | var getsolidsHolesMap = function (paths) { 223 | var solids = []; 224 | var holes = []; 225 | var solidsHolesMap = {}; 226 | var usedHolesMap = {}; 227 | var allPaths = paths; 228 | allPaths.sort(function (a, b) { return Math.abs(b.area) - Math.abs(a.area) }); 229 | //console.log(allPaths) 230 | allPaths.forEach(function (path) { 231 | // console.log(path.clockwise, path.area); 232 | if (path.clockwise) solids.push(path); 233 | else holes.push(path); 234 | }) 235 | // console.log(solids, holes) 236 | // find solid and holes set 237 | solids.forEach(function (path) { 238 | solidsHolesMap[path.id] = { 239 | solids: path, 240 | holes: [] 241 | }; 242 | for (var i = 0; i < holes.length; ++i) { 243 | if (path.bounds.contains(holes[i].bounds) && !usedHolesMap[holes[i].id]) { 244 | solidsHolesMap[path.id].holes.push(holes[i]); 245 | usedHolesMap[holes[i].id] = true; 246 | } 247 | } 248 | }); 249 | return solidsHolesMap; 250 | } 251 | 252 | var getExtrusionGeometry = function (solidsHolesMapData, thickness, curveSegments) { 253 | var shapes = []; 254 | var width = window.innerWidth / 2; 255 | var height = window.innerHeight / 2; 256 | var thickness = thickness || 10; 257 | var curveSegments = curveSegments || 10; 258 | var transferCoord = function (pos) { 259 | // move to the center 260 | pos.x -= width / 2; 261 | pos.y = height - pos.y - height / 2; 262 | // scale to half for better view 263 | pos.x /= 2; 264 | pos.y /= 2; 265 | return pos; 266 | } 267 | var getBezierPath = function (segments) { 268 | var bezierPath = new THREE.Shape(); 269 | for (let i = 0; i < segments.length; ++i) { 270 | var curr = segments[i]; 271 | var next = segments[(i + 1) % segments.length]; 272 | var p = []; 273 | p[0] = transferCoord(new THREE.Vector2(curr[0][0], curr[0][1])); 274 | p[1] = transferCoord(new THREE.Vector2(curr[2][0] + curr[0][0], curr[2][1] + curr[0][1])); 275 | p[2] = transferCoord(new THREE.Vector2(next[1][0] + next[0][0], next[1][1] + next[0][1])); 276 | p[3] = transferCoord(new THREE.Vector2(next[0][0], next[0][1])); 277 | if (i == 0) { 278 | bezierPath.moveTo(p[0].x, p[0].y); 279 | } 280 | bezierPath.bezierCurveTo( 281 | p[1].x, p[1].y, 282 | p[2].x, p[2].y, 283 | p[3].x, p[3].y); 284 | } 285 | 286 | bezierPath.closePath(); 287 | return bezierPath; 288 | } 289 | for (id in solidsHolesMapData) { 290 | var shape = solidsHolesMapData[id]; 291 | var solidSegments = shape.solids[1].segments; 292 | var solidShape = getBezierPath(solidSegments); 293 | var holes = shape.holes; 294 | var holePaths = []; 295 | for (var i = 0; i < holes.length; ++i) { 296 | var holeSegments = holes[i][1].segments; 297 | var holePath = getBezierPath(holeSegments); 298 | holePaths.push(holePath); 299 | } 300 | solidShape.holes = holePaths; 301 | shapes.push(solidShape); 302 | } 303 | var settings = { 304 | amount: thickness, 305 | curveSegments: curveSegments, 306 | bevelEnabled: false, 307 | material: 0, 308 | extrudeMaterial: 1 309 | }; 310 | 311 | var geometry = new THREE.ExtrudeGeometry(shapes, settings); 312 | return geometry; 313 | } --------------------------------------------------------------------------------