├── .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 | }
--------------------------------------------------------------------------------