Commit 8b770d9559eef0523c4af53bf3a4e84088750bb1
Merge branch 'feat/3D-model-editor' into 'main_dev'
feat:加入3D编辑器 See merge request yunteng/thingskit-view!289
Showing
25 changed files
with
4783 additions
and
3 deletions
Too many changes to show.
To preserve performance only 25 of 159 files are displayed.
1 | -import path from 'path' | 1 | +import path, { resolve } from 'path' |
2 | import { BuildOptions } from 'vite' | 2 | import { BuildOptions } from 'vite' |
3 | export const OUTPUT_DIR = 'dist' | 3 | export const OUTPUT_DIR = 'dist' |
4 | 4 | ||
@@ -13,6 +13,10 @@ export const brotliSize = false | @@ -13,6 +13,10 @@ export const brotliSize = false | ||
13 | 13 | ||
14 | // 分包 | 14 | // 分包 |
15 | export const rollupOptions: BuildOptions['rollupOptions'] = { | 15 | export const rollupOptions: BuildOptions['rollupOptions'] = { |
16 | + input: { | ||
17 | + main: resolve(__dirname, '../index.html'), | ||
18 | + editor: resolve(__dirname, '../editor/index.html'), | ||
19 | + }, | ||
16 | output: { | 20 | output: { |
17 | chunkFileNames: 'static/js/[name]-[hash].js', | 21 | chunkFileNames: 'static/js/[name]-[hash].js', |
18 | entryFileNames: 'static/js/[name]-[hash].js', | 22 | entryFileNames: 'static/js/[name]-[hash].js', |
1 | +import { Plugin } from 'vite' | ||
1 | import { createHtmlPlugin } from 'vite-plugin-html' | 2 | import { createHtmlPlugin } from 'vite-plugin-html' |
2 | 3 | ||
3 | const GLOB_CONFIG_FILE_NAME = '_app.config.js' | 4 | const GLOB_CONFIG_FILE_NAME = '_app.config.js' |
4 | 5 | ||
5 | -export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { | 6 | +export function configHtmlPlugin(env: ViteEnv, isBuild: boolean): Plugin { |
6 | const { VITE_GLOB_APP_TITLE, VITE_GLOB_PUBLIC_PATH } = env | 7 | const { VITE_GLOB_APP_TITLE, VITE_GLOB_PUBLIC_PATH } = env |
7 | const getAppConfigSrc = () => { | 8 | const getAppConfigSrc = () => { |
8 | const path = VITE_GLOB_PUBLIC_PATH | 9 | const path = VITE_GLOB_PUBLIC_PATH |
@@ -24,5 +25,21 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { | @@ -24,5 +25,21 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { | ||
24 | } | 25 | } |
25 | }) | 26 | }) |
26 | 27 | ||
27 | - return htmlPlugin | 28 | + |
29 | + | ||
30 | + return { | ||
31 | + name: 'html-plugin', | ||
32 | + transformIndexHtml: (code, ctx) => { | ||
33 | + return { | ||
34 | + html: code, | ||
35 | + tags: isBuild ? [ | ||
36 | + { | ||
37 | + tag: 'script', | ||
38 | + attrs: { src: getAppConfigSrc() } | ||
39 | + } | ||
40 | + ] : [] | ||
41 | + } | ||
42 | + }, | ||
43 | + | ||
44 | + } | ||
28 | } | 45 | } |
dist.zip
0 → 100644
No preview for this file type
editor/.eslintrc.json
0 → 100644
editor/css/main.css
0 → 100644
1 | +:root { | ||
2 | + color-scheme: light dark; | ||
3 | +} | ||
4 | + | ||
5 | +[hidden] { | ||
6 | + display: none !important; | ||
7 | +} | ||
8 | + | ||
9 | +body { | ||
10 | + font-family: Helvetica, Arial, sans-serif; | ||
11 | + font-size: 14px; | ||
12 | + margin: 0; | ||
13 | + overflow: hidden; | ||
14 | +} | ||
15 | + | ||
16 | +hr { | ||
17 | + border: 0; | ||
18 | + border-top: 1px solid #ccc; | ||
19 | +} | ||
20 | + | ||
21 | +button { | ||
22 | + position: relative; | ||
23 | +} | ||
24 | + | ||
25 | +input { | ||
26 | + vertical-align: middle; | ||
27 | +} | ||
28 | + | ||
29 | + input[type="color"]::-webkit-color-swatch-wrapper { | ||
30 | + padding: 0; | ||
31 | + } | ||
32 | + input[type="color"]::-webkit-color-swatch { | ||
33 | + border: none; | ||
34 | + } | ||
35 | + | ||
36 | +textarea { | ||
37 | + tab-size: 4; | ||
38 | + white-space: pre; | ||
39 | + word-wrap: normal; | ||
40 | +} | ||
41 | + | ||
42 | + textarea.success { | ||
43 | + border-color: #8b8 !important; | ||
44 | + } | ||
45 | + | ||
46 | + textarea.fail { | ||
47 | + border-color: #f00 !important; | ||
48 | + background-color: rgba(255,0,0,0.05); | ||
49 | + } | ||
50 | + | ||
51 | +textarea, input { outline: none; } /* osx */ | ||
52 | + | ||
53 | +.Panel { | ||
54 | + -moz-user-select: none; | ||
55 | + -webkit-user-select: none; | ||
56 | + -ms-user-select: none; | ||
57 | + | ||
58 | + /* No support for these yet */ | ||
59 | + -o-user-select: none; | ||
60 | + user-select: none; | ||
61 | +} | ||
62 | + | ||
63 | +.TabbedPanel { | ||
64 | + -moz-user-select: none; | ||
65 | + -webkit-user-select: none; | ||
66 | + -ms-user-select: none; | ||
67 | + | ||
68 | + /* No support for these yet */ | ||
69 | + -o-user-select: none; | ||
70 | + user-select: none; | ||
71 | + position: relative; | ||
72 | + display: block; | ||
73 | + width: 100%; | ||
74 | + min-width: 335px; | ||
75 | +} | ||
76 | + | ||
77 | +.TabbedPanel .Tabs { | ||
78 | + position: relative; | ||
79 | + z-index: 1; /** Above .Panels **/ | ||
80 | + display: block; | ||
81 | + width: 100%; | ||
82 | + white-space: pre; | ||
83 | + overflow: hidden; | ||
84 | + overflow-x: auto; | ||
85 | +} | ||
86 | + | ||
87 | + .TabbedPanel .Tabs::-webkit-scrollbar { | ||
88 | + height: 5px; | ||
89 | + background: #eee; | ||
90 | + } | ||
91 | + .TabbedPanel .Tabs::-webkit-scrollbar-thumb { | ||
92 | + background: #08f3; | ||
93 | + } | ||
94 | + .TabbedPanel .Tabs:hover::-webkit-scrollbar-thumb { | ||
95 | + background: #08f; | ||
96 | + cursor: ew-resize; | ||
97 | + } | ||
98 | + | ||
99 | + .TabbedPanel .Tabs .Tab { | ||
100 | + padding: 10px 9px; | ||
101 | + text-transform: uppercase; | ||
102 | + } | ||
103 | + | ||
104 | + .TabbedPanel .Panels { | ||
105 | + position: absolute; | ||
106 | + top: 40px; | ||
107 | + display: block; | ||
108 | + width: 100%; | ||
109 | + } | ||
110 | + | ||
111 | +/* Listbox */ | ||
112 | +.Listbox { | ||
113 | + color: #444; | ||
114 | + background-color: #fff; | ||
115 | + padding: 0; | ||
116 | + width: 100%; | ||
117 | + min-height: 180px; | ||
118 | + font-size: 12px; | ||
119 | + cursor: default; | ||
120 | + overflow: auto; | ||
121 | +} | ||
122 | + | ||
123 | +.Listbox .ListboxItem { | ||
124 | + padding: 6px; | ||
125 | + color: #666; | ||
126 | + white-space: nowrap; | ||
127 | +} | ||
128 | + | ||
129 | +.Listbox .ListboxItem.active { | ||
130 | + background-color: rgba(0, 0, 0, 0.04); | ||
131 | +} | ||
132 | + | ||
133 | +/* CodeMirror */ | ||
134 | + | ||
135 | +.CodeMirror { | ||
136 | + | ||
137 | + position: absolute !important; | ||
138 | + top: 37px; | ||
139 | + width: 100% !important; | ||
140 | + height: calc(100% - 37px) !important; | ||
141 | + | ||
142 | +} | ||
143 | + | ||
144 | + .CodeMirror .errorLine { | ||
145 | + | ||
146 | + background: rgba(255,0,0,0.25); | ||
147 | + | ||
148 | + } | ||
149 | + | ||
150 | + .CodeMirror .esprima-error { | ||
151 | + | ||
152 | + color: #f00; | ||
153 | + text-align: right; | ||
154 | + padding: 0 20px; | ||
155 | + | ||
156 | + } | ||
157 | + | ||
158 | +/* outliner */ | ||
159 | + | ||
160 | +#outliner .opener { | ||
161 | + display: inline-block; | ||
162 | + width: 14px; | ||
163 | + height: 14px; | ||
164 | + margin: 0px 4px; | ||
165 | + vertical-align: top; | ||
166 | + text-align: center; | ||
167 | +} | ||
168 | + | ||
169 | + #outliner .opener.open:after { | ||
170 | + content: '−'; | ||
171 | + } | ||
172 | + | ||
173 | + #outliner .opener.closed:after { | ||
174 | + content: '+'; | ||
175 | + } | ||
176 | + | ||
177 | +#outliner .option { | ||
178 | + | ||
179 | + border: 1px solid transparent; | ||
180 | + | ||
181 | +} | ||
182 | + | ||
183 | +#outliner .option.drag { | ||
184 | + | ||
185 | + border: 1px dashed #999; | ||
186 | + | ||
187 | +} | ||
188 | + | ||
189 | +#outliner .option.dragTop { | ||
190 | + | ||
191 | + border-top: 1px dashed #999; | ||
192 | + | ||
193 | +} | ||
194 | + | ||
195 | +#outliner .option.dragBottom { | ||
196 | + | ||
197 | + border-bottom: 1px dashed #999; | ||
198 | + | ||
199 | +} | ||
200 | + | ||
201 | +#outliner .type { | ||
202 | + display: inline-block; | ||
203 | + width: 14px; | ||
204 | + height: 14px; | ||
205 | + color: #ddd; | ||
206 | + text-align: center; | ||
207 | +} | ||
208 | + | ||
209 | +#outliner .type:after { | ||
210 | + content: '●'; | ||
211 | +} | ||
212 | + | ||
213 | +/* */ | ||
214 | + | ||
215 | +#outliner .Scene { | ||
216 | + color: #8888dd; | ||
217 | +} | ||
218 | + | ||
219 | +#outliner .Camera { | ||
220 | + color: #dd8888; | ||
221 | +} | ||
222 | + | ||
223 | +#outliner .Light { | ||
224 | + color: #dddd88; | ||
225 | +} | ||
226 | + | ||
227 | +/* */ | ||
228 | + | ||
229 | +#outliner .Object3D { | ||
230 | + color: #aaaaee; | ||
231 | +} | ||
232 | + | ||
233 | +#outliner .Mesh { | ||
234 | + color: #8888ee; | ||
235 | +} | ||
236 | + | ||
237 | +#outliner .Line { | ||
238 | + color: #88ee88; | ||
239 | +} | ||
240 | + | ||
241 | +#outliner .LineSegments { | ||
242 | + color: #88ee88; | ||
243 | +} | ||
244 | + | ||
245 | +#outliner .Points { | ||
246 | + color: #ee8888; | ||
247 | +} | ||
248 | + | ||
249 | +/* */ | ||
250 | + | ||
251 | +#outliner .Geometry { | ||
252 | + color: #aaeeaa; | ||
253 | +} | ||
254 | + | ||
255 | +#outliner .Material { | ||
256 | + color: #eeaaee; | ||
257 | +} | ||
258 | + | ||
259 | +/* */ | ||
260 | + | ||
261 | +#outliner .Script:after { | ||
262 | + content: '◎' | ||
263 | +} | ||
264 | + | ||
265 | +/* */ | ||
266 | + | ||
267 | +button { | ||
268 | + color: #555; | ||
269 | + background-color: #ddd; | ||
270 | + border: 0px; | ||
271 | + margin: 0px; /* GNOME Web */ | ||
272 | + padding: 5px 8px; | ||
273 | + font-size: 12px; | ||
274 | + text-transform: uppercase; | ||
275 | + cursor: pointer; | ||
276 | + outline: none; | ||
277 | +} | ||
278 | + | ||
279 | + button:hover { | ||
280 | + background-color: #fff; | ||
281 | + } | ||
282 | + | ||
283 | + button.selected { | ||
284 | + background-color: #fff; | ||
285 | + } | ||
286 | + | ||
287 | +input, textarea { | ||
288 | + border: 1px solid transparent; | ||
289 | + color: #444; | ||
290 | +} | ||
291 | + | ||
292 | +input.Number { | ||
293 | + color: #08f!important; | ||
294 | + font-size: 12px; | ||
295 | + border: 0px; | ||
296 | + padding: 2px; | ||
297 | +} | ||
298 | + | ||
299 | +select { | ||
300 | + color: #666; | ||
301 | + background-color: #ddd; | ||
302 | + border: 0px; | ||
303 | + text-transform: uppercase; | ||
304 | + cursor: pointer; | ||
305 | + outline: none; | ||
306 | +} | ||
307 | + | ||
308 | + select:hover { | ||
309 | + background-color: #fff; | ||
310 | + } | ||
311 | + | ||
312 | +/* UI */ | ||
313 | + | ||
314 | +#resizer { | ||
315 | + position: absolute; | ||
316 | + z-index: 2; /* Above #sidebar */ | ||
317 | + top: 32px; | ||
318 | + right: 350px; | ||
319 | + width: 5px; | ||
320 | + bottom: 0px; | ||
321 | + transform: translatex(2.5px); | ||
322 | + cursor: col-resize; | ||
323 | +} | ||
324 | + | ||
325 | + #resizer:hover { | ||
326 | + background-color: #08f8; | ||
327 | + transition-property: background-color; | ||
328 | + transition-delay: 0.1s; | ||
329 | + transition-duration: 0.2s; | ||
330 | + } | ||
331 | + | ||
332 | + #resizer:active { | ||
333 | + background-color: #08f; | ||
334 | + } | ||
335 | + | ||
336 | +#viewport { | ||
337 | + position: absolute; | ||
338 | + top: 32px; | ||
339 | + left: 0; | ||
340 | + right: 350px; | ||
341 | + bottom: 0; | ||
342 | +} | ||
343 | + | ||
344 | + #viewport .Text { | ||
345 | + text-shadow: 1px 1px 0 rgba(0,0,0,0.25); | ||
346 | + pointer-events: none; | ||
347 | + } | ||
348 | + | ||
349 | +#script { | ||
350 | + position: absolute; | ||
351 | + top: 32px; | ||
352 | + left: 0; | ||
353 | + right: 350px; | ||
354 | + bottom: 0; | ||
355 | + opacity: 0.9; | ||
356 | +} | ||
357 | + | ||
358 | +#player { | ||
359 | + position: absolute; | ||
360 | + top: 32px; | ||
361 | + left: 0; | ||
362 | + right: 350px; | ||
363 | + bottom: 0; | ||
364 | +} | ||
365 | + | ||
366 | +#menubar { | ||
367 | + position: absolute; | ||
368 | + width: 100%; | ||
369 | + height: 32px; | ||
370 | + background: #eee; | ||
371 | + padding: 0; | ||
372 | + margin: 0; | ||
373 | + right: 0; | ||
374 | + top: 0; | ||
375 | +} | ||
376 | + | ||
377 | + #menubar .menu { | ||
378 | + float: left; | ||
379 | + cursor: pointer; | ||
380 | + padding-right: 8px; | ||
381 | + } | ||
382 | + | ||
383 | + #menubar .menu.right { | ||
384 | + float: right; | ||
385 | + cursor: auto; | ||
386 | + padding-right: 0; | ||
387 | + text-align: right; | ||
388 | + } | ||
389 | + | ||
390 | + #menubar .menu .title { | ||
391 | + display: inline-block; | ||
392 | + color: #888; | ||
393 | + margin: 0; | ||
394 | + padding: 8px; | ||
395 | + line-height: 16px; | ||
396 | + } | ||
397 | + | ||
398 | + #menubar .menu .key { | ||
399 | + position: absolute; | ||
400 | + right: 10px; | ||
401 | + color: #ccc; | ||
402 | + border: 1px solid #ccc; | ||
403 | + border-radius: 4px; | ||
404 | + font-size: 9px; | ||
405 | + padding: 2px 4px; | ||
406 | + right: 10px; | ||
407 | + pointer-events: none; | ||
408 | + } | ||
409 | + | ||
410 | + #menubar .menu .options { | ||
411 | + position: fixed; | ||
412 | + z-index: 1; /* higher than resizer */ | ||
413 | + display: none; | ||
414 | + padding: 5px 0; | ||
415 | + background: #eee; | ||
416 | + min-width: 150px; | ||
417 | + max-height: calc(100vh - 80px); | ||
418 | + overflow: auto; | ||
419 | + } | ||
420 | + | ||
421 | + #menubar .menu:hover .options { | ||
422 | + display: block; | ||
423 | + box-shadow: 0 10px 10px -5px #00000033; | ||
424 | + } | ||
425 | + | ||
426 | + #menubar .menu .options hr { | ||
427 | + border-color: #ddd; | ||
428 | + } | ||
429 | + | ||
430 | + #menubar .menu .options .option { | ||
431 | + color: #666; | ||
432 | + background-color: transparent; | ||
433 | + padding: 5px 10px; | ||
434 | + margin: 0 !important; | ||
435 | + } | ||
436 | + | ||
437 | + #menubar .menu .options .option:hover { | ||
438 | + color: #fff; | ||
439 | + background-color: #08f; | ||
440 | + } | ||
441 | + | ||
442 | + #menubar .menu .options .option:not(.submenu-title):active { | ||
443 | + color: #666; | ||
444 | + background: transparent; | ||
445 | + } | ||
446 | + | ||
447 | + #menubar .menu .options .option.toggle::before { | ||
448 | + | ||
449 | + content: ' '; | ||
450 | + display: inline-block; | ||
451 | + width: 16px; | ||
452 | + | ||
453 | + } | ||
454 | + | ||
455 | + #menubar .menu .options .option.toggle-on::before { | ||
456 | + | ||
457 | + content: '✔'; | ||
458 | + font-size: 12px; | ||
459 | + | ||
460 | + } | ||
461 | + | ||
462 | + #menubar .submenu-title::after { | ||
463 | + content: '⏵'; | ||
464 | + float: right; | ||
465 | + } | ||
466 | + | ||
467 | + #menubar .menu .options .inactive { | ||
468 | + color: #bbb; | ||
469 | + background-color: transparent; | ||
470 | + padding: 5px 10px; | ||
471 | + margin: 0 !important; | ||
472 | + cursor: not-allowed; | ||
473 | + } | ||
474 | + | ||
475 | + | ||
476 | + | ||
477 | +#sidebar { | ||
478 | + position: absolute; | ||
479 | + right: 0; | ||
480 | + top: 32px; | ||
481 | + bottom: 0; | ||
482 | + width: 350px; | ||
483 | + background: #eee; | ||
484 | + overflow: auto; | ||
485 | + overflow-x: hidden; | ||
486 | +} | ||
487 | + | ||
488 | + #sidebar .Panel { | ||
489 | + color: #888; | ||
490 | + padding: 10px; | ||
491 | + border-top: 1px solid #ccc; | ||
492 | + } | ||
493 | + | ||
494 | + #sidebar .Panel.collapsed { | ||
495 | + margin-bottom: 0; | ||
496 | + } | ||
497 | + | ||
498 | + #sidebar .Row { | ||
499 | + display: flex; | ||
500 | + align-items: center; | ||
501 | + min-height: 24px; | ||
502 | + margin-bottom: 10px; | ||
503 | + } | ||
504 | + | ||
505 | + #sidebar .Row .Label { | ||
506 | + | ||
507 | + width: 120px; | ||
508 | + | ||
509 | + } | ||
510 | + | ||
511 | +#tabs { | ||
512 | + background-color: #ddd; | ||
513 | + border-top: 1px solid #ccc; | ||
514 | +} | ||
515 | + | ||
516 | + #tabs span { | ||
517 | + color: #aaa; | ||
518 | + border-right: 1px solid #ccc; | ||
519 | + padding: 10px; | ||
520 | + } | ||
521 | + | ||
522 | + #tabs span.selected { | ||
523 | + color: #888; | ||
524 | + background-color: #eee; | ||
525 | + } | ||
526 | + | ||
527 | +#toolbar { | ||
528 | + position: absolute; | ||
529 | + left: 10px; | ||
530 | + top: 42px; | ||
531 | + width: 32px; | ||
532 | + background: #eee; | ||
533 | + text-align: center; | ||
534 | +} | ||
535 | + | ||
536 | + #toolbar button, #toolbar input { | ||
537 | + height: 32px; | ||
538 | + } | ||
539 | + | ||
540 | + #toolbar button img { | ||
541 | + width: 16px; | ||
542 | + opacity: 0.5; | ||
543 | + } | ||
544 | + | ||
545 | +.Outliner { | ||
546 | + color: #444; | ||
547 | + background-color: #fff; | ||
548 | + padding: 0; | ||
549 | + width: 100%; | ||
550 | + height: 180px; | ||
551 | + font-size: 12px; | ||
552 | + cursor: default; | ||
553 | + overflow: auto; | ||
554 | + resize: vertical; | ||
555 | + outline: none !important; | ||
556 | +} | ||
557 | + | ||
558 | + .Outliner .option { | ||
559 | + padding: 4px; | ||
560 | + color: #666; | ||
561 | + white-space: nowrap; | ||
562 | + } | ||
563 | + | ||
564 | + .Outliner .option:hover { | ||
565 | + background-color: rgba(0,0,0,0.02); | ||
566 | + } | ||
567 | + | ||
568 | + .Outliner .option.active { | ||
569 | + background-color: rgba(0,0,0,0.04); | ||
570 | + } | ||
571 | + | ||
572 | + | ||
573 | +.TabbedPanel .Tabs { | ||
574 | + background-color: #ddd; | ||
575 | + border-top: 1px solid #ccc; | ||
576 | +} | ||
577 | + | ||
578 | + .TabbedPanel .Tab { | ||
579 | + color: #aaa; | ||
580 | + border-right: 1px solid #ccc; | ||
581 | + } | ||
582 | + | ||
583 | + .TabbedPanel .Tab.selected { | ||
584 | + color: #888; | ||
585 | + background-color: #eee; | ||
586 | + } | ||
587 | + | ||
588 | +.Listbox { | ||
589 | + color: #444; | ||
590 | + background-color: #fff; | ||
591 | +} | ||
592 | + | ||
593 | +.Panel { | ||
594 | + color: #888; | ||
595 | +} | ||
596 | + | ||
597 | +/* */ | ||
598 | + | ||
599 | +@media all and ( max-width: 600px ) { | ||
600 | + | ||
601 | + #resizer { | ||
602 | + display: none; | ||
603 | + } | ||
604 | + | ||
605 | + #menubar .menu .options { | ||
606 | + max-height: calc(100% - 80px); | ||
607 | + } | ||
608 | + | ||
609 | + #menubar .menu.right { | ||
610 | + display: none; | ||
611 | + } | ||
612 | + | ||
613 | + #viewport { | ||
614 | + left: 0; | ||
615 | + right: 0; | ||
616 | + top: 32px; | ||
617 | + height: calc(100% - 352px); | ||
618 | + } | ||
619 | + | ||
620 | + #script { | ||
621 | + left: 0; | ||
622 | + right: 0; | ||
623 | + top: 32px; | ||
624 | + height: calc(100% - 352px); | ||
625 | + } | ||
626 | + | ||
627 | + #player { | ||
628 | + left: 0; | ||
629 | + right: 0; | ||
630 | + top: 32px; | ||
631 | + height: calc(100% - 352px); | ||
632 | + } | ||
633 | + | ||
634 | + #sidebar { | ||
635 | + left: 0; | ||
636 | + width: 100%; | ||
637 | + top: calc(100% - 320px); | ||
638 | + bottom: 0; | ||
639 | + } | ||
640 | + | ||
641 | +} | ||
642 | + | ||
643 | +/* DARK MODE */ | ||
644 | + | ||
645 | +@media ( prefers-color-scheme: dark ) { | ||
646 | + | ||
647 | + button { | ||
648 | + color: #aaa; | ||
649 | + background-color: #222; | ||
650 | + } | ||
651 | + | ||
652 | + button:hover { | ||
653 | + color: #ccc; | ||
654 | + background-color: #444; | ||
655 | + } | ||
656 | + | ||
657 | + button.selected { | ||
658 | + color: #fff; | ||
659 | + background-color: #08f; | ||
660 | + } | ||
661 | + | ||
662 | + input, textarea { | ||
663 | + background-color: #222; | ||
664 | + border: 1px solid transparent; | ||
665 | + color: #888; | ||
666 | + } | ||
667 | + | ||
668 | + select { | ||
669 | + color: #aaa; | ||
670 | + background-color: #222; | ||
671 | + } | ||
672 | + | ||
673 | + select:hover { | ||
674 | + color: #ccc; | ||
675 | + background-color: #444; | ||
676 | + } | ||
677 | + | ||
678 | + /* UI */ | ||
679 | + | ||
680 | + #menubar { | ||
681 | + background: #111; | ||
682 | + } | ||
683 | + | ||
684 | + #menubar .menu .key { | ||
685 | + color: #444; | ||
686 | + border-color: #444; | ||
687 | + } | ||
688 | + | ||
689 | + #menubar .menu .options { | ||
690 | + background: #111; | ||
691 | + } | ||
692 | + | ||
693 | + #menubar .menu .options hr { | ||
694 | + border-color: #222; | ||
695 | + } | ||
696 | + | ||
697 | + #menubar .menu .options .option { | ||
698 | + color: #888; | ||
699 | + } | ||
700 | + | ||
701 | + #menubar .menu .options .inactive { | ||
702 | + color: #444; | ||
703 | + } | ||
704 | + | ||
705 | + #sidebar { | ||
706 | + background-color: #111; | ||
707 | + } | ||
708 | + | ||
709 | + #sidebar .Panel { | ||
710 | + border-top: 1px solid #222; | ||
711 | + } | ||
712 | + | ||
713 | + #sidebar .Panel.Material canvas { | ||
714 | + border: solid 1px #5A5A5A; | ||
715 | + } | ||
716 | + | ||
717 | + #tabs { | ||
718 | + background-color: #1b1b1b; | ||
719 | + border-top: 1px solid #222; | ||
720 | + } | ||
721 | + | ||
722 | + #tabs span { | ||
723 | + color: #555; | ||
724 | + border-right: 1px solid #222; | ||
725 | + } | ||
726 | + | ||
727 | + #tabs span.selected { | ||
728 | + background-color: #111; | ||
729 | + } | ||
730 | + | ||
731 | + #toolbar { | ||
732 | + background-color: #111; | ||
733 | + } | ||
734 | + | ||
735 | + #toolbar img { | ||
736 | + filter: invert(1); | ||
737 | + } | ||
738 | + | ||
739 | + .Outliner { | ||
740 | + background: #222; | ||
741 | + } | ||
742 | + | ||
743 | + .Outliner .option { | ||
744 | + color: #999; | ||
745 | + } | ||
746 | + | ||
747 | + .Outliner .option:hover { | ||
748 | + background-color: rgba(21,60,94,0.5); | ||
749 | + } | ||
750 | + | ||
751 | + .Outliner .option.active { | ||
752 | + background-color: rgba(21,60,94,1); | ||
753 | + } | ||
754 | + | ||
755 | + .TabbedPanel .Tabs { | ||
756 | + background-color: #1b1b1b; | ||
757 | + border-top: 1px solid #222; | ||
758 | + } | ||
759 | + | ||
760 | + .TabbedPanel .Tabs::-webkit-scrollbar { | ||
761 | + background: #111; | ||
762 | + } | ||
763 | + | ||
764 | + .TabbedPanel .Tab { | ||
765 | + color: #555; | ||
766 | + border-right: 1px solid #222; | ||
767 | + } | ||
768 | + | ||
769 | + .TabbedPanel .Tab.selected { | ||
770 | + color: #888; | ||
771 | + background-color: #111; | ||
772 | + } | ||
773 | + | ||
774 | + .Listbox { | ||
775 | + color: #888; | ||
776 | + background: #222; | ||
777 | + } | ||
778 | + | ||
779 | + .Listbox .ListboxItem:hover { | ||
780 | + background-color: rgba(21,60,94,0.5); | ||
781 | + } | ||
782 | + | ||
783 | + .Listbox .ListboxItem.active { | ||
784 | + background-color: rgba(21,60,94,1); | ||
785 | + } | ||
786 | + | ||
787 | +} | ||
788 | + | ||
789 | +/* Temporary Chrome fix (#24794) */ | ||
790 | + | ||
791 | +[draggable="true"] { | ||
792 | + transform: translate(0, 0); | ||
793 | + z-index: 0; | ||
794 | +} |
1 | +How to implement additional commands for undo/redo functionality? | ||
2 | +=== | ||
3 | + | ||
4 | +### Basics ### | ||
5 | + | ||
6 | +After evaluating different design patterns for undo/redo we decided to use the [command-pattern](http://en.wikipedia.org/wiki/Command_pattern) for implementing undo/redo functionality in the three.js-editor. | ||
7 | + | ||
8 | +This means that every action is encapsulated in a command-object which contains all the relevant information to restore the previous state. | ||
9 | + | ||
10 | +In our implementation we store the old and the new state separately (we don't store the complete state but rather the attribute and value which has changed). | ||
11 | +It would also be possible to only store the difference between the old and the new state. | ||
12 | + | ||
13 | +**Before implementing your own command you should look if you can't reuse one of the already existing ones.** | ||
14 | + | ||
15 | +For numbers, strings or booleans the Set...ValueCommand-commands can be used. | ||
16 | +Then there are separate commands for: | ||
17 | +- setting a color property (THREE.Color) | ||
18 | +- setting maps (THREE.Texture) | ||
19 | +- setting geometries | ||
20 | +- setting materials | ||
21 | +- setting position, rotation and scale | ||
22 | + | ||
23 | +### Template for new commands ### | ||
24 | + | ||
25 | +Every command needs a constructor. In the constructor | ||
26 | + | ||
27 | +```javascript | ||
28 | + | ||
29 | +function DoSomethingCommand( editor ) { | ||
30 | + | ||
31 | + Command.call( this, editor ); // Required: Call default constructor | ||
32 | + | ||
33 | + this.type = 'DoSomethingCommand'; // Required: has to match the object-name! | ||
34 | + this.name = 'Set/Do/Update Something'; // Required: description of the command, used in Sidebar.History | ||
35 | + | ||
36 | + // TODO: store all the relevant information needed to | ||
37 | + // restore the old and the new state | ||
38 | + | ||
39 | +} | ||
40 | +``` | ||
41 | + | ||
42 | +And as part of the prototype you need to implement four functions | ||
43 | +- **execute:** which is also used for redo | ||
44 | +- **undo:** which reverts the changes made by 'execute' | ||
45 | +- **toJSON:** which serializes the command so that the undo/redo-history can be preserved across a browser refresh | ||
46 | +- **fromJSON:** which deserializes the command | ||
47 | + | ||
48 | +```javascript | ||
49 | +DoSomethingCommand.prototype = { | ||
50 | + | ||
51 | + execute: function () { | ||
52 | + | ||
53 | + // TODO: apply changes to 'object' to reach the new state | ||
54 | + | ||
55 | + }, | ||
56 | + | ||
57 | + undo: function () { | ||
58 | + | ||
59 | + // TODO: restore 'object' to old state | ||
60 | + | ||
61 | + }, | ||
62 | + | ||
63 | + toJSON: function () { | ||
64 | + | ||
65 | + var output = Command.prototype.toJSON.call( this ); // Required: Call 'toJSON'-method of prototype 'Command' | ||
66 | + | ||
67 | + // TODO: serialize all the necessary information as part of 'output' (JSON-format) | ||
68 | + // so that it can be restored in 'fromJSON' | ||
69 | + | ||
70 | + return output; | ||
71 | + | ||
72 | + }, | ||
73 | + | ||
74 | + fromJSON: function ( json ) { | ||
75 | + | ||
76 | + Command.prototype.fromJSON.call( this, json ); // Required: Call 'fromJSON'-method of prototype 'Command' | ||
77 | + | ||
78 | + // TODO: restore command from json | ||
79 | + | ||
80 | + } | ||
81 | + | ||
82 | +}; | ||
83 | + | ||
84 | +``` | ||
85 | + | ||
86 | +### Executing a command ### | ||
87 | + | ||
88 | +To execute a command we need an instance of the main editor-object. The editor-object functions as the only entry point through which all commands have to go to be added as part of the undo/redo-history. | ||
89 | +On **editor** we then call **.execute(...)*** with the new command-object which in turn calls **history.execute(...)** and adds the command to the undo-stack. | ||
90 | + | ||
91 | +```javascript | ||
92 | + | ||
93 | +editor.execute( new DoSomethingCommand() ); | ||
94 | + | ||
95 | +``` | ||
96 | + | ||
97 | +### Updatable commands ### | ||
98 | + | ||
99 | +Some commands are also **updatable**. By default a command is not updatable. Making a command updatable means that you | ||
100 | +have to implement a fifth function 'update' as part of the prototype. In it only the 'new' state gets updated while the old one stays the same. | ||
101 | + | ||
102 | +Here as an example is the update-function of **SetColorCommand**: | ||
103 | + | ||
104 | +```javascript | ||
105 | +update: function ( cmd ) { | ||
106 | + | ||
107 | + this.newValue = cmd.newValue; | ||
108 | + | ||
109 | +}, | ||
110 | + | ||
111 | +``` | ||
112 | + | ||
113 | +#### List of updatable commands | ||
114 | + | ||
115 | +- SetColorCommand | ||
116 | +- SetGeometryCommand | ||
117 | +- SetMaterialColorCommand | ||
118 | +- SetMaterialValueCommand | ||
119 | +- SetPositionCommand | ||
120 | +- SetRotationCommand | ||
121 | +- SetScaleCommand | ||
122 | +- SetValueCommand | ||
123 | +- SetScriptValueCommand | ||
124 | + | ||
125 | +The idea behind 'updatable commands' is that two commands of the same type which occur | ||
126 | +within a short period of time should be merged into one. | ||
127 | +**For example:** Dragging with your mouse over the x-position field in the sidebar | ||
128 | +leads to hundreds of minor changes to the x-position. | ||
129 | +The user expectation is not to undo every single change that happened while they dragged | ||
130 | +the mouse cursor but rather to go back to the position before they started to drag their mouse. | ||
131 | + | ||
132 | +When editing a script the changes are also merged into one undo-step. |
1 | +Writing unit tests for undo-redo commands | ||
2 | +=== | ||
3 | + | ||
4 | +### Overview ### | ||
5 | + | ||
6 | +Writing unit tests for undo/redo commands is easy. | ||
7 | +The main idea to simulate a scene, execute actions and perform undo and redo. | ||
8 | +Following steps are required. | ||
9 | + | ||
10 | +1. Create a new unit test file | ||
11 | +2. Include the new command and the unit test file in the editor's test suite | ||
12 | +3. Write the test | ||
13 | +4. Execute the test | ||
14 | + | ||
15 | +Each of the listed steps will now be described in detail. | ||
16 | + | ||
17 | +### 1. Create a new unit test file ### | ||
18 | + | ||
19 | +Create a new file in path `test/unit/editor/TestDoSomethingCommand.js`. | ||
20 | + | ||
21 | +### 2. Include the new command in the editor test suite ### | ||
22 | + | ||
23 | +Navigate to the editor test suite `test/unit/unittests_editor.html` and open it. | ||
24 | +Within the file, go to the `<!-- command object classes -->` and include the new command: | ||
25 | + | ||
26 | +```html | ||
27 | +// <!-- command object classes --> | ||
28 | +//... | ||
29 | +<script src="../../editor/js/commands/AddScriptCommand.js"></script> | ||
30 | +<script src="../../editor/js/commands/DoSomethingCommand.js"></script> // add this line | ||
31 | +<script src="../../editor/js/commands/MoveObjectCommand.js"></script> | ||
32 | +//... | ||
33 | +``` | ||
34 | + | ||
35 | +It is recommended to keep the script inclusions in alphabetical order, if possible. | ||
36 | + | ||
37 | +Next, in the same file, go to `<!-- Undo-Redo tests -->` and include the test file for the new command: | ||
38 | + | ||
39 | +```html | ||
40 | +// <!-- Undo-Redo tests --> | ||
41 | +//... | ||
42 | +<script src="editor/TestAddScriptCommand.js"></script> | ||
43 | +<script src="editor/TestDoSomethingCommand.js"></script> // add this line | ||
44 | +<script src="editor/TestMoveObjectCommand.js"></script> | ||
45 | +//... | ||
46 | +``` | ||
47 | + | ||
48 | +Again, keeping the alphabetical order is recommended. | ||
49 | + | ||
50 | +### 3. Write the test ### | ||
51 | + | ||
52 | +#### Template #### | ||
53 | + | ||
54 | +Open the unit test file `test/unit/editor/TestDoSomethingCommand.js` and paste following code: | ||
55 | + | ||
56 | +```javascript | ||
57 | +module( "DoSomethingCommand" ); | ||
58 | + | ||
59 | +test("Test DoSomethingCommand (Undo and Redo)", function() { | ||
60 | + | ||
61 | + var editor = new Editor(); | ||
62 | + | ||
63 | + var box = aBox( 'Name your box' ); | ||
64 | + | ||
65 | + // other available objects from "CommonUtilities.js" | ||
66 | + // var sphere = aSphere( 'Name your sphere' ); | ||
67 | + // var pointLight = aPointLight( 'Name your pointLight' ); | ||
68 | + // var perspectiveCamera = aPerspectiveCamera( 'Name your perspectiveCamera' ); | ||
69 | + | ||
70 | + // in most cases you'll need to add the object to work with | ||
71 | + editor.execute( new AddObjectCommand( editor, box ) ); | ||
72 | + | ||
73 | + | ||
74 | + // your test begins here... | ||
75 | + | ||
76 | + | ||
77 | +} ); | ||
78 | +``` | ||
79 | + | ||
80 | +The predefined code is just meant to ease the development, you do not have to stick with it. | ||
81 | +However, the test should cover at least one `editor.execute()`, one `editor.undo()` and one `editor.redo()` call. | ||
82 | + | ||
83 | +Best practice is to call `editor.execute( new DoSomethingCommand( {custom parameters} ) )` **twice**. Since you'll have to do one undo (go one step back), it is recommended to have a custom state for comparison. Try to avoid assertions `ok()` against default values. | ||
84 | + | ||
85 | +#### Assertions #### | ||
86 | +After performing `editor.execute()` twice, you can do your first assertion to check whether the executes are done correctly. | ||
87 | + | ||
88 | +Next, you perform `editor.undo()` and check if the last action was undone. | ||
89 | + | ||
90 | +Finally, perform `editor.redo()` and verify if the values are as expected. | ||
91 | + | ||
92 | +### 4. Execute the test ### | ||
93 | + | ||
94 | +Open the editor's unit test suite `test/unit/unittests_editor.html` in your browser and check the results from the test framework. |
editor/examples/arkanoid.app.json
0 → 100644
1 | +{ | ||
2 | + "metadata": { | ||
3 | + "type": "App" | ||
4 | + }, | ||
5 | + "project": { | ||
6 | + "shadows": true, | ||
7 | + "vr": false | ||
8 | + }, | ||
9 | + "camera": { | ||
10 | + "metadata": { | ||
11 | + "version": 4.5, | ||
12 | + "type": "Object", | ||
13 | + "generator": "Object3D.toJSON" | ||
14 | + }, | ||
15 | + "object": { | ||
16 | + "uuid": "0C0DD0AD-3A7F-4ECD-A9FE-CECD97D5CBD9", | ||
17 | + "type": "PerspectiveCamera", | ||
18 | + "name": "Camera", | ||
19 | + "layers": 1, | ||
20 | + "matrix": [0.939236,0,-0.343272,0,-0.147782,0.902586,-0.404351,0,0.309832,0.430511,0.847741,0,11.713146,19.228675,40.388679,1], | ||
21 | + "fov": 50, | ||
22 | + "zoom": 1, | ||
23 | + "near": 0.1, | ||
24 | + "far": 100000, | ||
25 | + "focus": 10, | ||
26 | + "aspect": 1.428977, | ||
27 | + "filmGauge": 35, | ||
28 | + "filmOffset": 0 | ||
29 | + } | ||
30 | + }, | ||
31 | + "scene": { | ||
32 | + "metadata": { | ||
33 | + "version": 4.5, | ||
34 | + "type": "Object", | ||
35 | + "generator": "Object3D.toJSON" | ||
36 | + }, | ||
37 | + "geometries": [ | ||
38 | + { | ||
39 | + "uuid": "BBEE74D1-E43D-4C32-A9F3-4656E78C26F3", | ||
40 | + "type": "PlaneGeometry", | ||
41 | + "width": 30, | ||
42 | + "height": 40, | ||
43 | + "widthSegments": 1, | ||
44 | + "heightSegments": 1 | ||
45 | + }, | ||
46 | + { | ||
47 | + "uuid": "C1722F5F-89AD-45D8-B78C-D1D34AF2A012", | ||
48 | + "type": "BoxGeometry", | ||
49 | + "width": 2, | ||
50 | + "height": 1, | ||
51 | + "depth": 1, | ||
52 | + "widthSegments": 1, | ||
53 | + "heightSegments": 1, | ||
54 | + "depthSegments": 1 | ||
55 | + }, | ||
56 | + { | ||
57 | + "uuid": "327EFFCF-649C-4EF3-86D4-B422C5A86E89", | ||
58 | + "type": "CylinderGeometry", | ||
59 | + "radiusTop": 0.5, | ||
60 | + "radiusBottom": 0.5, | ||
61 | + "height": 2, | ||
62 | + "radialSegments": 32, | ||
63 | + "heightSegments": 1, | ||
64 | + "openEnded": false | ||
65 | + }, | ||
66 | + { | ||
67 | + "uuid": "0791211B-BB02-4E57-82B5-64C05DE92B39", | ||
68 | + "type": "SphereGeometry", | ||
69 | + "radius": 0.5, | ||
70 | + "widthSegments": 32, | ||
71 | + "heightSegments": 16, | ||
72 | + "phiStart": 0, | ||
73 | + "phiLength": 6.28, | ||
74 | + "thetaStart": 0, | ||
75 | + "thetaLength": 3.14 | ||
76 | + }, | ||
77 | + { | ||
78 | + "uuid": "73F12A47-9EA7-47FD-BCF3-89B8219B2626", | ||
79 | + "type": "BoxGeometry", | ||
80 | + "width": 2, | ||
81 | + "height": 1, | ||
82 | + "depth": 1, | ||
83 | + "widthSegments": 1, | ||
84 | + "heightSegments": 1, | ||
85 | + "depthSegments": 1 | ||
86 | + }, | ||
87 | + { | ||
88 | + "uuid": "3BDEB9FB-BDD4-44AD-8A47-008BED1C8982", | ||
89 | + "type": "CylinderGeometry", | ||
90 | + "radiusTop": 0.5, | ||
91 | + "radiusBottom": 0.5, | ||
92 | + "height": 2, | ||
93 | + "radialSegments": 32, | ||
94 | + "heightSegments": 1, | ||
95 | + "openEnded": false | ||
96 | + }], | ||
97 | + "materials": [ | ||
98 | + { | ||
99 | + "uuid": "2F69AF3A-DDF5-4BBA-87B5-80159F90DDBF", | ||
100 | + "type": "MeshPhongMaterial", | ||
101 | + "color": 86015, | ||
102 | + "emissive": 0, | ||
103 | + "specular": 1118481, | ||
104 | + "shininess": 30, | ||
105 | + "depthFunc": 3, | ||
106 | + "depthTest": true, | ||
107 | + "depthWrite": true | ||
108 | + }, | ||
109 | + { | ||
110 | + "uuid": "D98FC4D1-169E-420A-92EA-20E55009A46D", | ||
111 | + "type": "MeshBasicMaterial", | ||
112 | + "color": 63744, | ||
113 | + "depthFunc": 3, | ||
114 | + "depthTest": true, | ||
115 | + "depthWrite": true, | ||
116 | + "wireframe": true | ||
117 | + }, | ||
118 | + { | ||
119 | + "uuid": "3B9DE64D-E1C8-4C24-9F73-3A9E10E3E655", | ||
120 | + "type": "MeshPhongMaterial", | ||
121 | + "color": 16777215, | ||
122 | + "emissive": 0, | ||
123 | + "specular": 1118481, | ||
124 | + "shininess": 30, | ||
125 | + "depthFunc": 3, | ||
126 | + "depthTest": true, | ||
127 | + "depthWrite": true | ||
128 | + }, | ||
129 | + { | ||
130 | + "uuid": "043B208C-1F83-42C6-802C-E0E35621C27C", | ||
131 | + "type": "MeshPhongMaterial", | ||
132 | + "color": 16777215, | ||
133 | + "emissive": 0, | ||
134 | + "specular": 1118481, | ||
135 | + "shininess": 30, | ||
136 | + "depthFunc": 3, | ||
137 | + "depthTest": true, | ||
138 | + "depthWrite": true | ||
139 | + }, | ||
140 | + { | ||
141 | + "uuid": "40EC9BDA-91C0-4671-937A-2BCB6DA7EEBB", | ||
142 | + "type": "MeshBasicMaterial", | ||
143 | + "color": 63744, | ||
144 | + "depthFunc": 3, | ||
145 | + "depthTest": true, | ||
146 | + "depthWrite": true, | ||
147 | + "wireframe": true | ||
148 | + }], | ||
149 | + "object": { | ||
150 | + "uuid": "31517222-A9A7-4EAF-B5F6-60751C0BABA3", | ||
151 | + "type": "Scene", | ||
152 | + "name": "Scene", | ||
153 | + "layers": 1, | ||
154 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
155 | + "children": [ | ||
156 | + { | ||
157 | + "uuid": "EBBB1E63-6318-4752-AE2E-440A4E0B3EF3", | ||
158 | + "type": "Mesh", | ||
159 | + "name": "Ground", | ||
160 | + "layers": 1, | ||
161 | + "matrix": [1,0,0,0,0,0.000796,-1,0,0,1,0.000796,0,0,0,0,1], | ||
162 | + "geometry": "BBEE74D1-E43D-4C32-A9F3-4656E78C26F3", | ||
163 | + "material": "2F69AF3A-DDF5-4BBA-87B5-80159F90DDBF" | ||
164 | + }, | ||
165 | + { | ||
166 | + "uuid": "6EE2E764-43E0-48E0-85F2-E0C8823C20DC", | ||
167 | + "type": "DirectionalLight", | ||
168 | + "name": "DirectionalLight 1", | ||
169 | + "layers": 1, | ||
170 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,10,20,15,1], | ||
171 | + "color": 16777215, | ||
172 | + "intensity": 1, | ||
173 | + "shadow": { | ||
174 | + "camera": { | ||
175 | + "uuid": "3BC010F7-9766-4087-BA04-1D4FD7721ABA", | ||
176 | + "type": "OrthographicCamera", | ||
177 | + "layers": 1, | ||
178 | + "zoom": 1, | ||
179 | + "left": -5, | ||
180 | + "right": 5, | ||
181 | + "top": 5, | ||
182 | + "bottom": -5, | ||
183 | + "near": 0.5, | ||
184 | + "far": 500 | ||
185 | + } | ||
186 | + } | ||
187 | + }, | ||
188 | + { | ||
189 | + "uuid": "38219749-1E67-45F2-AB15-E64BA0940CAD", | ||
190 | + "type": "Mesh", | ||
191 | + "name": "Brick", | ||
192 | + "layers": 1, | ||
193 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0.5,0,1], | ||
194 | + "geometry": "C1722F5F-89AD-45D8-B78C-D1D34AF2A012", | ||
195 | + "material": "D98FC4D1-169E-420A-92EA-20E55009A46D", | ||
196 | + "children": [ | ||
197 | + { | ||
198 | + "uuid": "711A5955-8F17-4A8B-991A-7604D27E6FA0", | ||
199 | + "type": "Mesh", | ||
200 | + "name": "Cylinder", | ||
201 | + "layers": 1, | ||
202 | + "matrix": [0.000795,0.000795,1,0,-1.000001,-0.000001,0.000795,0,0.000001,-1.000001,0.000795,0,0,0,0,1], | ||
203 | + "geometry": "327EFFCF-649C-4EF3-86D4-B422C5A86E89", | ||
204 | + "material": "3B9DE64D-E1C8-4C24-9F73-3A9E10E3E655" | ||
205 | + }] | ||
206 | + }, | ||
207 | + { | ||
208 | + "uuid": "18FFA67C-F893-4E7A-8A76-8D996DEBE0C6", | ||
209 | + "type": "Mesh", | ||
210 | + "name": "Ball", | ||
211 | + "layers": 1, | ||
212 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0.5,3.55,1], | ||
213 | + "geometry": "0791211B-BB02-4E57-82B5-64C05DE92B39", | ||
214 | + "material": "043B208C-1F83-42C6-802C-E0E35621C27C" | ||
215 | + }, | ||
216 | + { | ||
217 | + "uuid": "6D660D49-39B8-40C3-95F6-E4E007AA8D79", | ||
218 | + "type": "Mesh", | ||
219 | + "name": "Paddle", | ||
220 | + "layers": 1, | ||
221 | + "matrix": [2,0,0,0,0,1,0,0,0,0,1,0,0,0.5,15.95,1], | ||
222 | + "geometry": "73F12A47-9EA7-47FD-BCF3-89B8219B2626", | ||
223 | + "material": "40EC9BDA-91C0-4671-937A-2BCB6DA7EEBB", | ||
224 | + "children": [ | ||
225 | + { | ||
226 | + "uuid": "4F5F884C-9E1B-45E6-8F1E-4D538A46D8CB", | ||
227 | + "type": "Mesh", | ||
228 | + "name": "Cylinder", | ||
229 | + "layers": 1, | ||
230 | + "matrix": [0.000795,0.000795,1,0,-1.000001,-0.000001,0.000795,0,0.000001,-1.000001,0.000795,0,0,0,0,1], | ||
231 | + "geometry": "3BDEB9FB-BDD4-44AD-8A47-008BED1C8982", | ||
232 | + "material": "3B9DE64D-E1C8-4C24-9F73-3A9E10E3E655" | ||
233 | + }] | ||
234 | + }, | ||
235 | + { | ||
236 | + "uuid": "B0BEAF69-8B5D-4D87-ADCA-FDE83A02762D", | ||
237 | + "type": "PointLight", | ||
238 | + "name": "PointLight 2", | ||
239 | + "layers": 1, | ||
240 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-11.65,6.949,-20.682,1], | ||
241 | + "color": 16777215, | ||
242 | + "intensity": 1, | ||
243 | + "distance": 0, | ||
244 | + "decay": 1, | ||
245 | + "shadow": { | ||
246 | + "camera": { | ||
247 | + "uuid": "2F0DA21A-EFB8-4E9A-83C5-A601D6113780", | ||
248 | + "type": "PerspectiveCamera", | ||
249 | + "layers": 1, | ||
250 | + "fov": 90, | ||
251 | + "zoom": 1, | ||
252 | + "near": 0.5, | ||
253 | + "far": 500, | ||
254 | + "focus": 10, | ||
255 | + "aspect": 1, | ||
256 | + "filmGauge": 35, | ||
257 | + "filmOffset": 0 | ||
258 | + } | ||
259 | + } | ||
260 | + }], | ||
261 | + "background": 11184810 | ||
262 | + } | ||
263 | + }, | ||
264 | + "scripts": { | ||
265 | + "6D660D49-39B8-40C3-95F6-E4E007AA8D79": [ | ||
266 | + { | ||
267 | + "name": "User", | ||
268 | + "source": "function pointermove( event ) {\n\n\tthis.position.x = ( event.clientX / player.width ) * 30 - 15;\n\n}\n\n// function update( event ) {}" | ||
269 | + }], | ||
270 | + "31517222-A9A7-4EAF-B5F6-60751C0BABA3": [ | ||
271 | + { | ||
272 | + "name": "Game Logic", | ||
273 | + "source": "var ball = this.getObjectByName( 'Ball' );\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = - 0.5;\ndirection.normalize();\n\nvar speed = new THREE.Vector3();\n\n//\n\nvar group = new THREE.Group();\nthis.add( group );\n\nvar paddle = this.getObjectByName( 'Paddle' );\npaddle.material.visible = false;\ngroup.add( paddle );\n\nvar brick = this.getObjectByName( 'Brick' );\n\nfor ( var j = 0; j < 8; j ++ ) {\n\n\tvar material = new THREE.MeshPhongMaterial( { color: Math.random() * 0xffffff } );\n\n\tfor ( var i = 0; i < 12; i ++ ) {\n\t\t\n\t\tvar object = brick.clone();\n\t\tobject.position.x = i * 2.2 - 12;\n\t\tobject.position.z = j * 1.4 - 12;\n\t\tgroup.add( object );\n\n\t\tvar cylinder = object.getObjectByName( 'Cylinder' );\n\t\tcylinder.material = material;\n\n\t}\n\t\n}\n\nbrick.visible = false;\nbrick.material.visible = false;\n\n//\n\nvar raycaster = new THREE.Raycaster();\n\nfunction update( event ) {\n\t\n\tif ( ball.position.x < - 15 || ball.position.x > 15 ) direction.x = - direction.x;\n\tif ( ball.position.z < - 20 || ball.position.z > 20 ) direction.z = - direction.z;\n\n\tball.position.x = Math.max( - 15, Math.min( 15, ball.position.x ) );\n\tball.position.z = Math.max( - 20, Math.min( 20, ball.position.z ) );\n\t\t\n\traycaster.set( ball.position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( group.children );\n\t\n\tif ( intersections.length > 0 ) {\n\t\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 0.5 ) {\n\t\t\t\n\t\t\tif ( intersection.object !== paddle ) {\n\n\t\t\t\tgroup.remove( intersection.object );\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n\tball.position.add( speed.copy( direction ).multiplyScalar( event.delta / 40 ) );\n\t\n}" | ||
274 | + }] | ||
275 | + } | ||
276 | +} |
editor/examples/camera.app.json
0 → 100644
1 | +{ | ||
2 | + "metadata": { | ||
3 | + "type": "App" | ||
4 | + }, | ||
5 | + "project": { | ||
6 | + "shadows": true, | ||
7 | + "vr": false | ||
8 | + }, | ||
9 | + "camera": { | ||
10 | + "metadata": { | ||
11 | + "version": 4.5, | ||
12 | + "type": "Object", | ||
13 | + "generator": "Object3D.toJSON" | ||
14 | + }, | ||
15 | + "object": { | ||
16 | + "uuid": "60EBAF60-53DA-47B0-A028-8FC031B708F6", | ||
17 | + "type": "PerspectiveCamera", | ||
18 | + "name": "Camera", | ||
19 | + "layers": 1, | ||
20 | + "matrix": [0.970041,0,-0.242943,0,-0.048226,0.980099,-0.192562,0,0.238108,0.198509,0.950736,0,1.548,1.29,6.18,1], | ||
21 | + "fov": 50, | ||
22 | + "zoom": 1, | ||
23 | + "near": 0.1, | ||
24 | + "far": 100000, | ||
25 | + "focus": 10, | ||
26 | + "aspect": 1.428977, | ||
27 | + "filmGauge": 35, | ||
28 | + "filmOffset": 0 | ||
29 | + } | ||
30 | + }, | ||
31 | + "scene": { | ||
32 | + "metadata": { | ||
33 | + "version": 4.5, | ||
34 | + "type": "Object", | ||
35 | + "generator": "Object3D.toJSON" | ||
36 | + }, | ||
37 | + "geometries": [ | ||
38 | + { | ||
39 | + "uuid": "6D90C4BE-EBA6-4E21-8F54-7CFDAA61F30B", | ||
40 | + "type": "PlaneGeometry", | ||
41 | + "width": 10, | ||
42 | + "height": 10, | ||
43 | + "widthSegments": 1, | ||
44 | + "heightSegments": 1 | ||
45 | + }, | ||
46 | + { | ||
47 | + "uuid": "D3008B2A-ACDD-43CC-87F7-4F942607D21A", | ||
48 | + "type": "BoxGeometry", | ||
49 | + "width": 1, | ||
50 | + "height": 1, | ||
51 | + "depth": 1, | ||
52 | + "widthSegments": 1, | ||
53 | + "heightSegments": 1, | ||
54 | + "depthSegments": 1 | ||
55 | + }, | ||
56 | + { | ||
57 | + "uuid": "F482ACD4-013A-49CF-AE0F-C9FF4ADAE409", | ||
58 | + "type": "CylinderGeometry", | ||
59 | + "radiusTop": 0, | ||
60 | + "radiusBottom": 0.4, | ||
61 | + "height": 0.75, | ||
62 | + "radialSegments": 4, | ||
63 | + "heightSegments": 1, | ||
64 | + "openEnded": false | ||
65 | + }, | ||
66 | + { | ||
67 | + "uuid": "51CDDCED-BC71-4B1B-A485-725B6A48204B", | ||
68 | + "type": "IcosahedronGeometry", | ||
69 | + "radius": 0.4, | ||
70 | + "detail": 2 | ||
71 | + }], | ||
72 | + "materials": [ | ||
73 | + { | ||
74 | + "uuid": "4AE8130E-B6A8-47BC-ACCF-060973C74044", | ||
75 | + "type": "MeshPhongMaterial", | ||
76 | + "color": 16777215, | ||
77 | + "emissive": 0, | ||
78 | + "specular": 1118481, | ||
79 | + "shininess": 30, | ||
80 | + "depthFunc": 3, | ||
81 | + "depthTest": true, | ||
82 | + "depthWrite": true | ||
83 | + }, | ||
84 | + { | ||
85 | + "uuid": "B5943856-E404-45D9-A427-4774202C2CD0", | ||
86 | + "type": "MeshPhongMaterial", | ||
87 | + "color": 37119, | ||
88 | + "emissive": 0, | ||
89 | + "specular": 1118481, | ||
90 | + "shininess": 30, | ||
91 | + "depthFunc": 3, | ||
92 | + "depthTest": true, | ||
93 | + "depthWrite": true | ||
94 | + }, | ||
95 | + { | ||
96 | + "uuid": "3F872310-2067-4BE4-9250-5B3F4E43797E", | ||
97 | + "type": "MeshPhongMaterial", | ||
98 | + "color": 15859456, | ||
99 | + "emissive": 0, | ||
100 | + "specular": 1118481, | ||
101 | + "shininess": 30, | ||
102 | + "depthFunc": 3, | ||
103 | + "depthTest": true, | ||
104 | + "depthWrite": true | ||
105 | + }, | ||
106 | + { | ||
107 | + "uuid": "E1826901-7922-4584-A25D-6D487E2C9BBD", | ||
108 | + "type": "MeshPhongMaterial", | ||
109 | + "color": 16711680, | ||
110 | + "emissive": 0, | ||
111 | + "specular": 1118481, | ||
112 | + "shininess": 30, | ||
113 | + "depthFunc": 3, | ||
114 | + "depthTest": true, | ||
115 | + "depthWrite": true | ||
116 | + }], | ||
117 | + "object": { | ||
118 | + "uuid": "3741222A-BD8F-401C-A5D2-5A907E891896", | ||
119 | + "type": "Scene", | ||
120 | + "name": "Scene", | ||
121 | + "layers": 1, | ||
122 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
123 | + "children": [ | ||
124 | + { | ||
125 | + "uuid": "B7CBBC6F-EC26-49B5-8D0D-67D9C535924B", | ||
126 | + "type": "Group", | ||
127 | + "name": "Dummy", | ||
128 | + "layers": 1, | ||
129 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,1,4,1], | ||
130 | + "children": [ | ||
131 | + { | ||
132 | + "uuid": "60B69C58-4201-43FD-815E-AD2EDFBBD0CE", | ||
133 | + "type": "PerspectiveCamera", | ||
134 | + "name": "PerspectiveCamera", | ||
135 | + "layers": 1, | ||
136 | + "matrix": [-1,0,0,0,0,1,0,0,0,0,-1,0,0,0,0,1], | ||
137 | + "fov": 50, | ||
138 | + "zoom": 1, | ||
139 | + "near": 0.1, | ||
140 | + "far": 100, | ||
141 | + "focus": 10, | ||
142 | + "aspect": 1, | ||
143 | + "filmGauge": 35, | ||
144 | + "filmOffset": 0 | ||
145 | + }] | ||
146 | + }, | ||
147 | + { | ||
148 | + "uuid": "A460C230-DC88-4A8F-A3FB-AA0FE735F3ED", | ||
149 | + "type": "Mesh", | ||
150 | + "name": "Plane", | ||
151 | + "layers": 1, | ||
152 | + "matrix": [1,0,0,0,0,0.040785,-0.999168,0,0,0.999168,0.040785,0,0,-0.5,0,1], | ||
153 | + "geometry": "6D90C4BE-EBA6-4E21-8F54-7CFDAA61F30B", | ||
154 | + "material": "4AE8130E-B6A8-47BC-ACCF-060973C74044" | ||
155 | + }, | ||
156 | + { | ||
157 | + "uuid": "26DAAD69-725D-43B7-AF9D-990A99DEF8C5", | ||
158 | + "type": "Mesh", | ||
159 | + "name": "Box", | ||
160 | + "layers": 1, | ||
161 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
162 | + "geometry": "D3008B2A-ACDD-43CC-87F7-4F942607D21A", | ||
163 | + "material": "B5943856-E404-45D9-A427-4774202C2CD0" | ||
164 | + }, | ||
165 | + { | ||
166 | + "uuid": "AAAFF2D6-4725-4AFC-A9FE-26419B11011F", | ||
167 | + "type": "Mesh", | ||
168 | + "name": "Cylinder", | ||
169 | + "layers": 1, | ||
170 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-1.3,-0.15,0,1], | ||
171 | + "geometry": "F482ACD4-013A-49CF-AE0F-C9FF4ADAE409", | ||
172 | + "material": "3F872310-2067-4BE4-9250-5B3F4E43797E" | ||
173 | + }, | ||
174 | + { | ||
175 | + "uuid": "B855E267-A266-4098-ACD6-6A1FDE7B88BA", | ||
176 | + "type": "Mesh", | ||
177 | + "name": "Icosahedron", | ||
178 | + "layers": 1, | ||
179 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,1.3,-0.1,0,1], | ||
180 | + "geometry": "51CDDCED-BC71-4B1B-A485-725B6A48204B", | ||
181 | + "material": "E1826901-7922-4584-A25D-6D487E2C9BBD" | ||
182 | + }, | ||
183 | + { | ||
184 | + "uuid": "E2939A7B-5E40-438A-8C1B-32126FBC6892", | ||
185 | + "type": "PointLight", | ||
186 | + "name": "PointLight 1", | ||
187 | + "layers": 1, | ||
188 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-0.939,1.271,-1.143,1], | ||
189 | + "color": 9474221, | ||
190 | + "intensity": 0.75, | ||
191 | + "distance": 0, | ||
192 | + "decay": 1, | ||
193 | + "shadow": { | ||
194 | + "camera": { | ||
195 | + "uuid": "EFF42F46-1E27-4B36-B9D9-CF7D879D258E", | ||
196 | + "type": "PerspectiveCamera", | ||
197 | + "layers": 1, | ||
198 | + "fov": 90, | ||
199 | + "zoom": 1, | ||
200 | + "near": 0.5, | ||
201 | + "far": 500, | ||
202 | + "focus": 10, | ||
203 | + "aspect": 1, | ||
204 | + "filmGauge": 35, | ||
205 | + "filmOffset": 0 | ||
206 | + } | ||
207 | + } | ||
208 | + }, | ||
209 | + { | ||
210 | + "uuid": "3412781E-27CC-43C3-A5DB-54C0C8E42ED6", | ||
211 | + "type": "PointLight", | ||
212 | + "name": "PointLight 2", | ||
213 | + "layers": 1, | ||
214 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0.881,0.083,1.254,1], | ||
215 | + "color": 12773063, | ||
216 | + "intensity": 1, | ||
217 | + "distance": 0, | ||
218 | + "decay": 1, | ||
219 | + "shadow": { | ||
220 | + "camera": { | ||
221 | + "uuid": "81E800FE-E8A7-4A9E-AFAA-4F04FD56AFE4", | ||
222 | + "type": "PerspectiveCamera", | ||
223 | + "layers": 1, | ||
224 | + "fov": 90, | ||
225 | + "zoom": 1, | ||
226 | + "near": 0.5, | ||
227 | + "far": 500, | ||
228 | + "focus": 10, | ||
229 | + "aspect": 1, | ||
230 | + "filmGauge": 35, | ||
231 | + "filmOffset": 0 | ||
232 | + } | ||
233 | + } | ||
234 | + }], | ||
235 | + "background": 11184810 | ||
236 | + } | ||
237 | + }, | ||
238 | + "scripts": { | ||
239 | + "60B69C58-4201-43FD-815E-AD2EDFBBD0CE": [ | ||
240 | + { | ||
241 | + "name": "Player Camera", | ||
242 | + "source": "player.setCamera( this );" | ||
243 | + }], | ||
244 | + "B7CBBC6F-EC26-49B5-8D0D-67D9C535924B": [ | ||
245 | + { | ||
246 | + "name": "Orbit", | ||
247 | + "source": "function update( event ) {\n\n\tvar time = event.time * 0.001;\n\n\tthis.position.x = Math.sin( time ) * 4;\n\tthis.position.z = Math.cos( time ) * 4;\n\tthis.lookAt( scene.position );\n\n}" | ||
248 | + }] | ||
249 | + } | ||
250 | +} |
editor/examples/particles.app.json
0 → 100644
1 | +{ | ||
2 | + "metadata": { | ||
3 | + "type": "App" | ||
4 | + }, | ||
5 | + "project": { | ||
6 | + "shadows": true, | ||
7 | + "vr": false | ||
8 | + }, | ||
9 | + "camera": { | ||
10 | + "metadata": { | ||
11 | + "version": 4.5, | ||
12 | + "type": "Object", | ||
13 | + "generator": "Object3D.toJSON" | ||
14 | + }, | ||
15 | + "object": { | ||
16 | + "uuid": "056199EB-6985-481B-97CC-A57FB7C87809", | ||
17 | + "type": "PerspectiveCamera", | ||
18 | + "name": "Camera", | ||
19 | + "layers": 1, | ||
20 | + "matrix": [0.707107,0,-0.707107,0,-0.235702,0.942809,-0.235702,0,0.666667,0.333333,0.666667,0,4.182,2.091,4.182,1], | ||
21 | + "fov": 50, | ||
22 | + "zoom": 1, | ||
23 | + "near": 0.1, | ||
24 | + "far": 100000, | ||
25 | + "focus": 10, | ||
26 | + "aspect": 0.666193, | ||
27 | + "filmGauge": 35, | ||
28 | + "filmOffset": 0 | ||
29 | + } | ||
30 | + }, | ||
31 | + "scene": { | ||
32 | + "metadata": { | ||
33 | + "version": 4.5, | ||
34 | + "type": "Object", | ||
35 | + "generator": "Object3D.toJSON" | ||
36 | + }, | ||
37 | + "geometries": [ | ||
38 | + { | ||
39 | + "uuid": "C3C0CE7D-10B8-43FC-8F74-011CC6E57800", | ||
40 | + "type": "PlaneGeometry", | ||
41 | + "width": 100, | ||
42 | + "height": 100, | ||
43 | + "widthSegments": 1, | ||
44 | + "heightSegments": 1 | ||
45 | + }], | ||
46 | + "materials": [ | ||
47 | + { | ||
48 | + "uuid": "3A9449D2-62DB-4BB4-ABBD-6F3F9D46DE1A", | ||
49 | + "type": "MeshStandardMaterial", | ||
50 | + "color": 5465019, | ||
51 | + "roughness": 1, | ||
52 | + "metalness": 0, | ||
53 | + "emissive": 0, | ||
54 | + "depthFunc": 3, | ||
55 | + "depthTest": true, | ||
56 | + "depthWrite": true | ||
57 | + }, | ||
58 | + { | ||
59 | + "uuid": "F5361474-F5F1-412F-8D99-3699B868092D", | ||
60 | + "type": "SpriteMaterial", | ||
61 | + "color": 16777215, | ||
62 | + "transparent": true, | ||
63 | + "depthFunc": 3, | ||
64 | + "depthTest": true, | ||
65 | + "depthWrite": true | ||
66 | + }], | ||
67 | + "object": { | ||
68 | + "uuid": "3741222A-BD8F-401C-A5D2-5A907E891896", | ||
69 | + "type": "Scene", | ||
70 | + "name": "Scene", | ||
71 | + "layers": 1, | ||
72 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
73 | + "children": [ | ||
74 | + { | ||
75 | + "uuid": "05B57416-1BE5-4A96-BB05-9D9CD112D52B", | ||
76 | + "type": "Mesh", | ||
77 | + "name": "Ground", | ||
78 | + "layers": 1, | ||
79 | + "matrix": [1,0,0,0,0,0.000796,-1,0,0,1,0.000796,0,0,-0.5,0,1], | ||
80 | + "geometry": "C3C0CE7D-10B8-43FC-8F74-011CC6E57800", | ||
81 | + "material": "3A9449D2-62DB-4BB4-ABBD-6F3F9D46DE1A" | ||
82 | + }, | ||
83 | + { | ||
84 | + "uuid": "0A3CB873-07E6-4EEB-830B-68192504111B", | ||
85 | + "type": "Sprite", | ||
86 | + "name": "Particle", | ||
87 | + "layers": 1, | ||
88 | + "matrix": [0.04,0,0,0,0,0.04,0,0,0,0,0.04,0,0,0,0,1], | ||
89 | + "material": "F5361474-F5F1-412F-8D99-3699B868092D" | ||
90 | + }, | ||
91 | + { | ||
92 | + "uuid": "40E5CDA4-0E39-4265-9293-3E9EC3207F61", | ||
93 | + "type": "PointLight", | ||
94 | + "name": "PointLight", | ||
95 | + "layers": 1, | ||
96 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,1.183,0,1], | ||
97 | + "color": 16777215, | ||
98 | + "intensity": 1, | ||
99 | + "distance": 0, | ||
100 | + "decay": 1, | ||
101 | + "shadow": { | ||
102 | + "camera": { | ||
103 | + "uuid": "B6D3493E-E5C9-4D65-9E26-BB788D127BE1", | ||
104 | + "type": "PerspectiveCamera", | ||
105 | + "layers": 1, | ||
106 | + "fov": 90, | ||
107 | + "zoom": 1, | ||
108 | + "near": 0.5, | ||
109 | + "far": 500, | ||
110 | + "focus": 10, | ||
111 | + "aspect": 1, | ||
112 | + "filmGauge": 35, | ||
113 | + "filmOffset": 0 | ||
114 | + } | ||
115 | + } | ||
116 | + }], | ||
117 | + "background": 2171689, | ||
118 | + "fog": { | ||
119 | + "type": "Fog", | ||
120 | + "color": 2171688, | ||
121 | + "near": 1, | ||
122 | + "far": 50 | ||
123 | + } | ||
124 | + } | ||
125 | + }, | ||
126 | + "scripts": { | ||
127 | + "3741222A-BD8F-401C-A5D2-5A907E891896": [ | ||
128 | + { | ||
129 | + "name": "Fountain", | ||
130 | + "source": "var original = this.getObjectByName( 'Particle' );\n\nvar particles = [];\n\nfor ( var i = 0; i < 100; i ++ ) {\n\n\tvar particle = original.clone();\n\tparticle.userData.velocity = new THREE.Vector3();\n\tthis.add( particle );\n\n\tparticles.push( particle );\n\n}\n\nfunction update( event ) {\n\t\n\tvar particle = particles.shift();\n\tparticles.push( particle );\n\t\t\n\tvar velocity = particle.userData.velocity;\n\tvelocity.x = Math.random() * 0.1 - 0.05;\n\tvelocity.y = Math.random() * 0.1 + 0.1;\n\tvelocity.z = Math.random() * 0.1 - 0.05;\n\n\tfor ( var i = 0; i < particles.length; i ++ ) {\n\n\t\tvar particle = particles[ i ];\n\n\t\tvar velocity = particle.userData.velocity;\n\n\t\tvelocity.y -= 0.0098;\n\n\t\tparticle.position.add( velocity );\n\n\t\tif ( particle.position.y < 0 ) {\n\n\t\t\tparticle.position.y = 0;\n\n\t\t\tvelocity.y = - velocity.y;\n\t\t\tvelocity.multiplyScalar( 0.6 );\n\n\t\t}\n\n\t}\n\n}" | ||
131 | + }] | ||
132 | + } | ||
133 | +} |
editor/examples/pong.app.json
0 → 100644
1 | +{ | ||
2 | + "metadata": { | ||
3 | + "type": "App" | ||
4 | + }, | ||
5 | + "project": { | ||
6 | + "shadows": true, | ||
7 | + "vr": false | ||
8 | + }, | ||
9 | + "camera": { | ||
10 | + "metadata": { | ||
11 | + "version": 4.5, | ||
12 | + "type": "Object", | ||
13 | + "generator": "Object3D.toJSON" | ||
14 | + }, | ||
15 | + "object": { | ||
16 | + "uuid": "4AC7ADED-CC22-4B16-8218-2E0A0C38C8F8", | ||
17 | + "type": "PerspectiveCamera", | ||
18 | + "name": "Camera", | ||
19 | + "layers": 1, | ||
20 | + "matrix": [0.952212,0,-0.305438,0,-0.17743,0.813973,-0.553142,0,0.248618,0.580902,0.775075,0,1.865,4.357,5.813,1], | ||
21 | + "fov": 50, | ||
22 | + "zoom": 1, | ||
23 | + "near": 0.1, | ||
24 | + "far": 100000, | ||
25 | + "focus": 10, | ||
26 | + "aspect": 1.428977, | ||
27 | + "filmGauge": 35, | ||
28 | + "filmOffset": 0 | ||
29 | + } | ||
30 | + }, | ||
31 | + "scene": { | ||
32 | + "metadata": { | ||
33 | + "version": 4.5, | ||
34 | + "type": "Object", | ||
35 | + "generator": "Object3D.toJSON" | ||
36 | + }, | ||
37 | + "geometries": [ | ||
38 | + { | ||
39 | + "uuid": "490CEBA3-6A25-4BE1-B517-C5FB11A5D18A", | ||
40 | + "type": "PlaneGeometry", | ||
41 | + "width": 6, | ||
42 | + "height": 4, | ||
43 | + "widthSegments": 1, | ||
44 | + "heightSegments": 1 | ||
45 | + }, | ||
46 | + { | ||
47 | + "uuid": "D9A92F2D-2F08-4851-99C7-12D8D1CA13C7", | ||
48 | + "type": "BoxGeometry", | ||
49 | + "width": 0.1, | ||
50 | + "height": 0.1, | ||
51 | + "depth": 0.1, | ||
52 | + "widthSegments": 1, | ||
53 | + "heightSegments": 1, | ||
54 | + "depthSegments": 1 | ||
55 | + }, | ||
56 | + { | ||
57 | + "uuid": "5E63B8CF-E225-4ABC-994A-4D06BD4E21EB", | ||
58 | + "type": "BoxGeometry", | ||
59 | + "width": 0.2, | ||
60 | + "height": 0.2, | ||
61 | + "depth": 1, | ||
62 | + "widthSegments": 1, | ||
63 | + "heightSegments": 1, | ||
64 | + "depthSegments": 1 | ||
65 | + }, | ||
66 | + { | ||
67 | + "uuid": "D61532B4-24C3-4BC4-B56B-7245E8163E09", | ||
68 | + "type": "BoxGeometry", | ||
69 | + "width": 0.2, | ||
70 | + "height": 0.2, | ||
71 | + "depth": 1, | ||
72 | + "widthSegments": 1, | ||
73 | + "heightSegments": 1, | ||
74 | + "depthSegments": 1 | ||
75 | + }], | ||
76 | + "materials": [ | ||
77 | + { | ||
78 | + "uuid": "7EDF7C08-6325-418A-BBAB-89341C694730", | ||
79 | + "type": "MeshPhongMaterial", | ||
80 | + "color": 16777215, | ||
81 | + "emissive": 0, | ||
82 | + "specular": 16777215, | ||
83 | + "shininess": 30, | ||
84 | + "depthFunc": 3, | ||
85 | + "depthTest": true, | ||
86 | + "depthWrite": true | ||
87 | + }, | ||
88 | + { | ||
89 | + "uuid": "B1CAF098-FE36-45E1-BEBE-8D6AC04821CC", | ||
90 | + "type": "MeshPhongMaterial", | ||
91 | + "color": 16711680, | ||
92 | + "emissive": 0, | ||
93 | + "specular": 1118481, | ||
94 | + "shininess": 30, | ||
95 | + "depthFunc": 3, | ||
96 | + "depthTest": true, | ||
97 | + "depthWrite": true | ||
98 | + }, | ||
99 | + { | ||
100 | + "uuid": "FBDBE66D-B613-4741-802D-5AE1DE07DE46", | ||
101 | + "type": "MeshPhongMaterial", | ||
102 | + "color": 2752767, | ||
103 | + "emissive": 0, | ||
104 | + "specular": 1118481, | ||
105 | + "shininess": 30, | ||
106 | + "depthFunc": 3, | ||
107 | + "depthTest": true, | ||
108 | + "depthWrite": true | ||
109 | + }], | ||
110 | + "object": { | ||
111 | + "uuid": "31517222-A9A7-4EAF-B5F6-60751C0BABA3", | ||
112 | + "type": "Scene", | ||
113 | + "name": "Scene", | ||
114 | + "layers": 1, | ||
115 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
116 | + "children": [ | ||
117 | + { | ||
118 | + "uuid": "B47D0BFC-D63A-4CBB-985E-9C4DBDF086E4", | ||
119 | + "type": "Mesh", | ||
120 | + "name": "Ground", | ||
121 | + "layers": 1, | ||
122 | + "matrix": [1,0,0,0,0,0.000796,-1,0,0,1,0.000796,0,0,-0.1,0,1], | ||
123 | + "geometry": "490CEBA3-6A25-4BE1-B517-C5FB11A5D18A", | ||
124 | + "material": "7EDF7C08-6325-418A-BBAB-89341C694730" | ||
125 | + }, | ||
126 | + { | ||
127 | + "uuid": "CE13E58A-4E8B-4F72-9E2E-7DE57C58F989", | ||
128 | + "type": "Mesh", | ||
129 | + "name": "Ball", | ||
130 | + "layers": 1, | ||
131 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
132 | + "geometry": "D9A92F2D-2F08-4851-99C7-12D8D1CA13C7", | ||
133 | + "material": "B1CAF098-FE36-45E1-BEBE-8D6AC04821CC" | ||
134 | + }, | ||
135 | + { | ||
136 | + "uuid": "2AAEA3AA-EC45-492B-B450-10473D1EC6C5", | ||
137 | + "type": "Mesh", | ||
138 | + "name": "Pad 1", | ||
139 | + "layers": 1, | ||
140 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-2.4,0,0,1], | ||
141 | + "geometry": "5E63B8CF-E225-4ABC-994A-4D06BD4E21EB", | ||
142 | + "material": "FBDBE66D-B613-4741-802D-5AE1DE07DE46" | ||
143 | + }, | ||
144 | + { | ||
145 | + "uuid": "F1DD46A7-6584-4A37-BC76-852C3911077E", | ||
146 | + "type": "Mesh", | ||
147 | + "name": "Pad 2", | ||
148 | + "layers": 1, | ||
149 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,2.4,0,0,1], | ||
150 | + "geometry": "D61532B4-24C3-4BC4-B56B-7245E8163E09", | ||
151 | + "material": "FBDBE66D-B613-4741-802D-5AE1DE07DE46" | ||
152 | + }, | ||
153 | + { | ||
154 | + "uuid": "C62AAE9F-9E51-46A5-BD2B-71BA804FC0B3", | ||
155 | + "type": "DirectionalLight", | ||
156 | + "name": "DirectionalLight", | ||
157 | + "layers": 1, | ||
158 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,1,2,1.5,1], | ||
159 | + "color": 16777215, | ||
160 | + "intensity": 1, | ||
161 | + "shadow": { | ||
162 | + "camera": { | ||
163 | + "uuid": "2CF1F42A-8992-4E8D-8D94-7CC20979344C", | ||
164 | + "type": "OrthographicCamera", | ||
165 | + "layers": 1, | ||
166 | + "zoom": 1, | ||
167 | + "left": -5, | ||
168 | + "right": 5, | ||
169 | + "top": 5, | ||
170 | + "bottom": -5, | ||
171 | + "near": 0.5, | ||
172 | + "far": 500 | ||
173 | + } | ||
174 | + } | ||
175 | + }], | ||
176 | + "background": 11184810 | ||
177 | + } | ||
178 | + }, | ||
179 | + "scripts": { | ||
180 | + "31517222-A9A7-4EAF-B5F6-60751C0BABA3": [ | ||
181 | + { | ||
182 | + "name": "Game logic", | ||
183 | + "source": "var ball = this.getObjectByName( 'Ball' );\n\nvar position = ball.position;\n\nvar velocity = new THREE.Vector3();\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = Math.random() - 0.5;\ndirection.normalize().multiplyScalar( 0.1 );\n\nvar pad1 = this.getObjectByName( 'Pad 1' );\nvar pad2 = this.getObjectByName( 'Pad 2' );\n\nvar raycaster = new THREE.Raycaster();\nvar objects = [ pad1, pad2 ];\n\n//\n\nfunction pointermove( event ) {\n\n\tpad1.position.z = ( event.clientX / player.width ) * 3 - 1.5;\n\tpad2.position.z = - pad1.position.z;\n\n}\n\nfunction update( event ) {\n\t\n\tif ( position.x < -3 || position.x > 3 ) direction.x = - direction.x;\n\tif ( position.z < -2 || position.z > 2 ) direction.z = - direction.z;\n\t\n\tposition.x = Math.max( - 3, Math.min( 3, position.x ) );\n\tposition.z = Math.max( - 2, Math.min( 2, position.z ) );\n\t\n\traycaster.set( position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( objects );\n\t\n\tif ( intersections.length > 0 ) {\n\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 0.1 ) {\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n\tposition.add( velocity.copy( direction ).multiplyScalar( event.delta / 20 ) );\n\n}" | ||
184 | + }] | ||
185 | + } | ||
186 | +} |
editor/examples/shaders.app.json
0 → 100755
1 | +{ | ||
2 | + "metadata": { | ||
3 | + "type": "App" | ||
4 | + }, | ||
5 | + "project": { | ||
6 | + "shadows": true, | ||
7 | + "vr": false | ||
8 | + }, | ||
9 | + "camera": { | ||
10 | + "metadata": { | ||
11 | + "version": 4.5, | ||
12 | + "type": "Object", | ||
13 | + "generator": "Object3D.toJSON" | ||
14 | + }, | ||
15 | + "object": { | ||
16 | + "uuid": "4AC7ADED-CC22-4B16-8218-2E0A0C38C8F8", | ||
17 | + "type": "PerspectiveCamera", | ||
18 | + "name": "Camera", | ||
19 | + "layers": 1, | ||
20 | + "matrix": [0.605503,0,-0.795843,0,-0.261526,0.944464,-0.198978,0,0.751645,0.328615,0.571876,0,2.571484,1.124239,1.956469,1], | ||
21 | + "fov": 50, | ||
22 | + "zoom": 1, | ||
23 | + "near": 0.1, | ||
24 | + "far": 10000, | ||
25 | + "focus": 10, | ||
26 | + "aspect": 1.428977, | ||
27 | + "filmGauge": 35, | ||
28 | + "filmOffset": 0 | ||
29 | + } | ||
30 | + }, | ||
31 | + "scene": { | ||
32 | + "metadata": { | ||
33 | + "version": 4.5, | ||
34 | + "type": "Object", | ||
35 | + "generator": "Object3D.toJSON" | ||
36 | + }, | ||
37 | + "geometries": [ | ||
38 | + { | ||
39 | + "uuid": "EA781333-F3AE-470D-9110-A9724FCB42AA", | ||
40 | + "type": "IcosahedronGeometry", | ||
41 | + "radius": 1, | ||
42 | + "detail": 24 | ||
43 | + }], | ||
44 | + "materials": [ | ||
45 | + { | ||
46 | + "uuid": "50ED51F1-DEA4-4B61-8082-BF41609E8C27", | ||
47 | + "type": "ShaderMaterial", | ||
48 | + "depthFunc": 3, | ||
49 | + "depthTest": true, | ||
50 | + "depthWrite": true, | ||
51 | + "wireframe": true, | ||
52 | + "uniforms": { | ||
53 | + "time": { | ||
54 | + "value": 0 | ||
55 | + } | ||
56 | + }, | ||
57 | + "vertexShader": "uniform float time;\nvarying vec3 vPosition;\nvoid main() {\n\tvPosition = position;\n\tvPosition.x += sin( time + vPosition.z * 4.0 ) / 4.0;\n\tvPosition.y += cos( time + vPosition.z * 4.0 ) / 4.0;\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( vPosition, 1.0 );\n}", | ||
58 | + "fragmentShader": "varying vec3 vPosition;\nvoid main() {\n\tgl_FragColor = vec4( vPosition * 2.0, 1.0 );\n}" | ||
59 | + }], | ||
60 | + "object": { | ||
61 | + "uuid": "5FC9ACA9-2A93-474D-AA32-FACC76551914", | ||
62 | + "type": "Scene", | ||
63 | + "name": "Scene", | ||
64 | + "layers": 1, | ||
65 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
66 | + "children": [ | ||
67 | + { | ||
68 | + "uuid": "FC7B6CF2-6386-4F47-9CE6-8ADB9FCA6E1F", | ||
69 | + "type": "Mesh", | ||
70 | + "name": "Icosahedron 1", | ||
71 | + "layers": 1, | ||
72 | + "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], | ||
73 | + "geometry": "EA781333-F3AE-470D-9110-A9724FCB42AA", | ||
74 | + "material": "50ED51F1-DEA4-4B61-8082-BF41609E8C27" | ||
75 | + }], | ||
76 | + "background": 11184810 | ||
77 | + } | ||
78 | + }, | ||
79 | + "scripts": { | ||
80 | + "FC7B6CF2-6386-4F47-9CE6-8ADB9FCA6E1F": [ | ||
81 | + { | ||
82 | + "name": "", | ||
83 | + "source": "function update( event ) {\n\n\tthis.material.uniforms.time.value = event.time / 500.0;\n\n}" | ||
84 | + }] | ||
85 | + } | ||
86 | +} |
editor/images/icon.png
0 → 100644
7.11 KB
editor/images/icon.xcf
0 → 100644
No preview for this file type
editor/images/rotate.svg
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"> | ||
3 | + <path d="m247.8 474.12c61.315 0 112.09-26.692 147.65-59.495l-1.6947 75.58c-0.17697 10.509 8.2063 19.178 18.721 19.349h0.32318c10.364 0 18.855-8.3198 19.033-18.721l2.5382-126.18c0.0827-5.1592-1.9294-10.135-5.5843-13.784-3.6492-3.6492-8.5987-5.6603-13.784-5.5718l-126.73 2.5383c-10.509 0.18371-18.885 8.8469-18.708 19.362 0.17794 10.401 8.6689 18.714 19.032 18.714h0.32991l83.195-1.7957c-29.35 27.815-72.656 51.93-124.33 51.93-105.91 0-177.05-91.548-177.05-177.05-0.31704-10.248-6.2951-19.251-18.532-19.479-12.237-0.22764-19.196 8.1831-19.543 19.486 0 103.89 86.446 215.13 215.13 215.13zm4.3538-435.25c-61.315 0-112.09 26.692-147.65 59.495l1.6947-75.579c0.17698-10.509-8.2063-19.178-18.721-19.349h-0.32317c-10.364 0-18.855 8.3198-19.033 18.721l-2.5383 126.18c-0.08272 5.1592 1.9294 10.135 5.5844 13.784 3.6492 3.6492 8.5987 5.6603 13.784 5.5719l126.73-2.5383c10.509-0.18371 18.885-8.8469 18.708-19.362-0.17794-10.401-8.6689-18.714-19.032-18.714h-0.32991l-83.195 1.7957c29.35-27.815 72.656-51.93 124.33-51.93 105.91 0 177.05 91.548 177.05 177.05 0.31703 10.248 6.2951 19.251 18.532 19.479 12.237 0.22763 19.196-8.1831 19.543-19.486 0-103.89-86.446-215.13-215.13-215.13z"/> | ||
4 | +</svg> |
editor/images/scale.svg
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"> | ||
3 | + <path d="m450.25 44.182-133.13 1.1699c-9.691 0.076-17.482 7.9849-17.406 17.67 0.076 9.638 27.155 17.4 17.535 17.4h0.1289l90.35-0.84375-116.88 116.87c-6.85 6.844-6.85 17.949 0 24.793 3.425 3.425 7.9075 5.1387 12.396 5.1387 4.483 0 8.9734-1.7137 12.398-5.1387l116.87-116.88-0.84179 90.344c-0.07 9.685 7.7193 17.594 17.404 17.67h0.12891c9.62 0 17.459-7.7624 17.535-17.4l1.168-133.13c1.3e-4 -0.01637-2e-3 -0.03246-2e-3 -0.04883 3e-3 -0.51471-0.0239-1.0297-0.0664-1.543-4e-3 -0.05325-9.6e-4 -0.10695-6e-3 -0.16016-2.4e-4 -0.0027-2e-3 -0.0051-2e-3 -0.0078-0.26224-2.8345-1.2132-5.6145-2.8516-8.0742-0.31876-0.47858-0.66235-0.94631-1.0332-1.3984-0.36831-0.45029-0.76079-0.88383-1.1758-1.2988-0.41658-0.41735-0.85218-0.81156-1.3047-1.1816-0.24176-0.19804-0.49656-0.36957-0.7461-0.55273-0.21514-0.15762-0.42293-0.32538-0.64453-0.47266-0.35614-0.23723-0.72494-0.4459-1.0938-0.6543-0.12574-0.07096-0.24567-0.15292-0.37304-0.2207-0.42481-0.22631-0.86059-0.42442-1.2988-0.61328-0.0824-0.03554-0.16117-0.07901-0.24414-0.11328-0.42706-0.17629-0.86181-0.32571-1.2988-0.4668-0.10761-0.03483-0.21201-0.07858-0.32031-0.11133-0.37604-0.11341-0.75724-0.19952-1.1387-0.28711-0.18044-0.04161-0.35715-0.09494-0.53906-0.13086-0.30867-0.06068-0.62071-0.09471-0.93164-0.13867-0.25679-0.03651-0.51062-0.0862-0.76953-0.11133-0.56319-0.0544-1.128-0.08394-1.6934-0.08398zm-387.45 255.62c-9.62 0-17.459 7.7663-17.535 17.404l-1.1699 133.12c-1.22e-4 0.0164 2e-3 0.0325 2e-3 0.0488-0.0025 0.51395 0.02395 1.0285 0.06641 1.541 0.0046 0.0557 0.0026 0.11233 0.0078 0.16797 0.31299 3.3855 1.6059 6.695 3.8809 9.4727 0.3666 0.44761 0.75628 0.88287 1.1738 1.3008h2e-3c0.20731 0.20728 0.42966 0.38925 0.64453 0.58398 0.21795 0.19755 0.42764 0.40626 0.6543 0.5918 0.29789 0.24417 0.61028 0.46004 0.91992 0.68164 0.15919 0.11373 0.31004 0.23952 0.47266 0.34766 0.37454 0.24954 0.76185 0.46983 1.1504 0.6875 0.10897 0.061 0.21208 0.1328 0.32227 0.1914 0.38726 0.20616 0.78506 0.38353 1.1836 0.5586 0.12552 0.0552 0.24628 0.12161 0.37305 0.17382 0.36908 0.15187 0.74642 0.27673 1.123 0.40235 0.16645 0.0556 0.32796 0.12318 0.49609 0.17383 0.46956 0.14111 0.94417 0.25454 1.4219 0.35547 0.07285 0.0154 0.14372 0.0382 0.2168 0.0527 0.4682 0.0927 0.94102 0.15666 1.4141 0.21094 0.09821 0.0113 0.19446 0.0313 0.29297 0.041 0.57076 0.0558 1.6783-0.48578 1.7168 0.0859h0.13477l133.13-1.1699c9.685-0.07 17.476-7.9831 17.4-17.662-0.076-9.69-8.1259-17.387-17.67-17.404l-90.338 0.83996 116.87-116.87c6.844-6.844 6.85-17.943 0-24.793s-17.955-6.85-24.799 0l-116.87 116.86 0.84375-90.344c0.076-9.679-7.7154-17.586-17.4-17.662z"/> | ||
4 | +</svg> |
editor/images/translate.svg
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"> | ||
3 | + <path d="m256 0c-1.2452 0-2.4586 0.12791-3.6387 0.35156-0.0659 0.012567-0.13349 0.018008-0.19922 0.03125-0.57894 0.11595-1.146 0.26357-1.7051 0.42969-0.047 0.01399-0.0957 0.022767-0.14258 0.037109-1.8151 0.55436-3.5159 1.3682-5.0625 2.3965-0.0312 0.020703-0.0646 0.037706-0.0957 0.058594-0.32576 0.21931-0.63201 0.46307-0.94336 0.70117-0.70605 0.53787-1.3906 1.1059-2.0234 1.7422l-75.18 75.828c-7.563 7.628-7.5048 19.937 0.11718 27.5 7.628 7.557 19.941 7.5048 27.498-0.11719l41.932-42.291v169.89h-169.88l42.295-41.926c7.622-7.563 7.6782-19.87 0.11523-27.498s-19.876-7.6802-27.498-0.11719l-75.828 75.18c-0.00297 3e-3 -0.00485 7e-3 -0.00781 0.01-2.2463 2.2302-3.9501 5.0041-4.8984 8.1094-0.004708 0.0154-0.007049 0.0315-0.011719 0.0469-0.17839 0.58935-0.3326 1.1892-0.45508 1.8008-0.013248 0.0657-0.018677 0.13332-0.03125 0.19922-0.10655 0.56223-0.19883 1.1288-0.25586 1.707-0.062671 0.63522-0.095706 1.2794-0.095706 1.9314s0.033035 1.2962 0.095703 1.9316c0.057025 0.57824 0.14931 1.1448 0.25586 1.707 0.012572 0.0659 0.018003 0.13349 0.03125 0.19922 0.11595 0.57894 0.26357 1.146 0.42969 1.7051 0.014761 0.0496 0.023909 0.10093 0.039062 0.15039 0.56503 1.8473 1.396 3.5783 2.4512 5.1465 0.35511 0.52775 0.73293 1.0381 1.1367 1.5274 0.00265 3e-3 0.00516 7e-3 0.00781 0.01 0.067158 0.0815 0.14629 0.15391 0.21484 0.23438 0.33891 0.39681 0.68535 0.78801 1.0547 1.1562 0.01376 0.0137 0.025257 0.0293 0.039063 0.043l75.826 75.18c3.791 3.753 8.7375 5.6328 13.689 5.6328 5.003 0 10.007-1.9189 13.805-5.7559 7.563-7.628 7.5048-19.935-0.11719-27.498l-42.289-41.926h169.89v169.89l-41.926-42.289c-7.557-7.635-19.87-7.667-27.498-0.12305-7.622 7.563-7.6802 19.872-0.11719 27.5l75.18 75.826c3e-3 3e-3 7e-3 5e-3 0.01 8e-3 1.3458 1.3556 2.8885 2.5158 4.584 3.4297 0.0637 0.0343 0.13128 0.0602 0.19532 0.0937 0.50006 0.26239 1.0086 0.50958 1.5332 0.72852 0.0287 0.012 0.0591 0.0194 0.0879 0.0312 0.55148 0.22706 1.1139 0.43292 1.6894 0.60937 0.13059 0.0401 0.26501 0.0662 0.39649 0.10352 0.48459 0.13741 0.97149 0.26916 1.4707 0.36914 0.0657 0.0132 0.13332 0.0187 0.19922 0.0312 1.1798 0.22361 2.3932 0.35152 3.6384 0.35152 1.8194 0 3.572-0.26909 5.2422-0.73633 0.15543-0.0435 0.31454-0.0738 0.46875-0.12109 1.1011-0.33792 2.1539-0.78025 3.1621-1.2988 0.11472-0.0588 0.23407-0.10685 0.34765-0.16797 0.37259-0.20111 0.72848-0.42812 1.0859-0.65234 0.1868-0.11676 0.38143-0.22052 0.56445-0.34375 0.10467-0.0707 0.19953-0.15388 0.30274-0.22657 0.93571-0.65673 1.8271-1.3857 2.6445-2.209l75.18-75.826c7.556-7.628 7.503-19.937-0.125-27.5-7.635-7.557-19.941-7.505-27.498 0.12305l-41.932 42.299v-169.9h169.89l-42.293 41.926c-7.628 7.563-7.68 19.87-0.12305 27.498 3.804 3.837 8.8076 5.7559 13.811 5.7559 4.945 0 9.8984-1.8808 13.689-5.6328l75.826-75.18c0.0141-0.014 0.0269-0.0289 0.041-0.043 0.2515-0.2508 0.48121-0.52292 0.71875-0.78711 0.18447-0.20534 0.38168-0.40105 0.55664-0.61328 0.404-0.48955 0.78339-0.9993 1.1387-1.5274 0.0249-0.0371 0.0437-0.0779 0.0684-0.11523 0.32713-0.4935 0.63802-0.99972 0.91992-1.5234 0.0324-0.0604 0.0561-0.12484 0.0879-0.18555 0.26306-0.50133 0.51109-1.0112 0.73047-1.5371 0.0125-0.0301 0.0208-0.0617 0.0332-0.0918 0.22497-0.54707 0.42834-1.105 0.60352-1.6758 0.0473-0.15419 0.0776-0.31334 0.12109-0.46875 0.13019-0.46537 0.25974-0.93139 0.35547-1.4102 0.0128-0.0637 0.0171-0.12948 0.0293-0.19336 0.22362-1.1801 0.35153-2.3934 0.35153-3.6386 0-1.3125-0.13302-2.5925-0.38086-3.832-4e-4 -2e-3 -2e-3 -4e-3 -2e-3 -6e-3 -0.0955-0.47674-0.22387-0.94085-0.35352-1.4043-0.0435-0.15541-0.0738-0.31456-0.12109-0.46875-0.18478-0.60207-0.39663-1.192-0.63672-1.7676-5.6e-4 -1e-3 -1e-3 -3e-3 -2e-3 -4e-3 -0.24542-0.58801-0.51657-1.1617-0.81641-1.7188-2e-3 -3e-3 -4e-3 -6e-3 -6e-3 -0.01-0.30165-0.55963-0.62921-1.1039-0.98242-1.6289-8e-4 -1e-3 -1e-3 -3e-3 -2e-3 -4e-3 -0.35459-0.52676-0.73364-1.035-1.1367-1.5234-0.17496-0.21223-0.37217-0.40794-0.55664-0.61328-0.23754-0.26419-0.46725-0.53631-0.71875-0.78711-0.0141-0.014-0.0269-0.029-0.041-0.043l-75.826-75.18c-7.635-7.563-19.943-7.5048-27.5 0.11718-7.557 7.628-7.505 19.935 0.12305 27.498l42.295 41.932h-169.89v-169.89l41.926 42.289c3.804 3.837 8.8076 5.7559 13.811 5.7559 4.945 0 9.8955-1.8738 13.688-5.6328 7.628-7.563 7.68-19.872 0.12304-27.5l-75.18-75.826c-0.0137-0.013805-0.0292-0.025303-0.043-0.039063-0.44175-0.44306-0.90413-0.86662-1.3867-1.2656-5e-3 -0.00382-9e-3 -0.0079-0.0137-0.011719-3.3608-2.7735-7.6688-4.4395-12.367-4.4395z"/> | ||
4 | +</svg> |
editor/index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html lang="en"> | ||
3 | + | ||
4 | +<head> | ||
5 | + <title>3D Model Editor</title> | ||
6 | + <meta charset="utf-8"> | ||
7 | + <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | ||
8 | +</head> | ||
9 | + | ||
10 | +<body> | ||
11 | + <link rel="stylesheet" href="css/main.css"> | ||
12 | + <link rel="stylesheet" href="js/libs/codemirror/codemirror.css"> | ||
13 | + <link rel="stylesheet" href="js/libs/codemirror/theme/monokai.css"> | ||
14 | + <link rel="stylesheet" href="js/libs/codemirror/addon/dialog.css"> | ||
15 | + <link rel="stylesheet" href="js/libs/codemirror/addon/show-hint.css"> | ||
16 | + <link rel="stylesheet" href="js/libs/codemirror/addon/tern.css"> | ||
17 | + | ||
18 | + <script type="module" src="./jsm/libs/draco/draco_encoder.js"></script> | ||
19 | + <script type="module" src="./js/libs/codemirror/codemirror.js"></script> | ||
20 | + <script type="module" src="./js/libs/codemirror/mode/javascript.js"></script> | ||
21 | + <script type="module" src="./js/libs/codemirror/mode/glsl.js"></script> | ||
22 | + <script type="module" src="./js/libs/esprima.js"></script> | ||
23 | + <script type="module" src="./js/libs/jsonlint.js"></script> | ||
24 | + <script type="module" src="./js/libs/ffmpeg/ffmpeg.min.js"></script> | ||
25 | + <script type="module" src="./js/libs/codemirror/addon/dialog.js"></script> | ||
26 | + <script type="module" src="./js/libs/codemirror/addon/show-hint.js"></script> | ||
27 | + <script type="module" src="./js/libs/codemirror/addon/tern.js"></script> | ||
28 | + <script type="module" src="./js/libs/acorn/acorn.js"></script> | ||
29 | + <script type="module" src="./js/libs/acorn/acorn_loose.js"></script> | ||
30 | + <script type="module" src="./js/libs/acorn/walk.js"></script> | ||
31 | + <script type="module" src="./js/libs/ternjs/polyfill.js"></script> | ||
32 | + <script type="module" src="./js/libs/ternjs/signal.js"></script> | ||
33 | + <script type="module" src="./js/libs/ternjs/tern.js"></script> | ||
34 | + <script type="module" src="./js/libs/ternjs/def.js"></script> | ||
35 | + <script type="module" src="./js/libs/ternjs/comment.js"></script> | ||
36 | + <script type="module" src="./js/libs/ternjs/infer.js"></script> | ||
37 | + <script type="module" src="./js/libs/ternjs/doc_comment.js"></script> | ||
38 | + <script type="module" src="./js/libs/tern-threejs/threejs.js"></script> | ||
39 | + <script type="module" src="./js/libs/signals.min.js"></script> | ||
40 | + | ||
41 | + <script src="./main.js" type="module"></script> | ||
42 | +</body> | ||
43 | + | ||
44 | +</html> |
editor/js/Command.js
0 → 100644
1 | +/** | ||
2 | + * @param editor pointer to main editor object used to initialize | ||
3 | + * each command object with a reference to the editor | ||
4 | + * @constructor | ||
5 | + */ | ||
6 | + | ||
7 | +class Command { | ||
8 | + | ||
9 | + constructor( editor ) { | ||
10 | + | ||
11 | + this.id = - 1; | ||
12 | + this.inMemory = false; | ||
13 | + this.updatable = false; | ||
14 | + this.type = ''; | ||
15 | + this.name = ''; | ||
16 | + this.editor = editor; | ||
17 | + | ||
18 | + } | ||
19 | + | ||
20 | + toJSON() { | ||
21 | + | ||
22 | + const output = {}; | ||
23 | + output.type = this.type; | ||
24 | + output.id = this.id; | ||
25 | + output.name = this.name; | ||
26 | + return output; | ||
27 | + | ||
28 | + } | ||
29 | + | ||
30 | + fromJSON( json ) { | ||
31 | + | ||
32 | + this.inMemory = true; | ||
33 | + this.type = json.type; | ||
34 | + this.id = json.id; | ||
35 | + this.name = json.name; | ||
36 | + | ||
37 | + } | ||
38 | + | ||
39 | +} | ||
40 | + | ||
41 | +export { Command }; |
editor/js/Config.js
0 → 100644
1 | +function Config() { | ||
2 | + | ||
3 | + const name = 'threejs-editor'; | ||
4 | + | ||
5 | + const userLanguage = navigator.language.split( '-' )[ 0 ]; | ||
6 | + | ||
7 | + const suggestedLanguage = [ 'fr', 'ja', 'zh', 'ko' ].includes( userLanguage ) ? userLanguage : 'en'; | ||
8 | + | ||
9 | + const storage = { | ||
10 | + 'language': suggestedLanguage, | ||
11 | + | ||
12 | + 'autosave': true, | ||
13 | + | ||
14 | + 'project/title': '', | ||
15 | + 'project/editable': false, | ||
16 | + 'project/vr': false, | ||
17 | + | ||
18 | + 'project/renderer/antialias': true, | ||
19 | + 'project/renderer/shadows': true, | ||
20 | + 'project/renderer/shadowType': 1, // PCF | ||
21 | + 'project/renderer/toneMapping': 0, // NoToneMapping | ||
22 | + 'project/renderer/toneMappingExposure': 1, | ||
23 | + | ||
24 | + 'settings/history': false, | ||
25 | + | ||
26 | + 'settings/shortcuts/translate': 'w', | ||
27 | + 'settings/shortcuts/rotate': 'e', | ||
28 | + 'settings/shortcuts/scale': 'r', | ||
29 | + 'settings/shortcuts/undo': 'z', | ||
30 | + 'settings/shortcuts/focus': 'f' | ||
31 | + }; | ||
32 | + | ||
33 | + if ( window.localStorage[ name ] === undefined ) { | ||
34 | + | ||
35 | + window.localStorage[ name ] = JSON.stringify( storage ); | ||
36 | + | ||
37 | + } else { | ||
38 | + | ||
39 | + const data = JSON.parse( window.localStorage[ name ] ); | ||
40 | + | ||
41 | + for ( const key in data ) { | ||
42 | + | ||
43 | + storage[ key ] = data[ key ]; | ||
44 | + | ||
45 | + } | ||
46 | + | ||
47 | + } | ||
48 | + | ||
49 | + return { | ||
50 | + | ||
51 | + getKey: function ( key ) { | ||
52 | + | ||
53 | + return storage[ key ]; | ||
54 | + | ||
55 | + }, | ||
56 | + | ||
57 | + setKey: function () { // key, value, key, value ... | ||
58 | + | ||
59 | + for ( let i = 0, l = arguments.length; i < l; i += 2 ) { | ||
60 | + | ||
61 | + storage[ arguments[ i ] ] = arguments[ i + 1 ]; | ||
62 | + | ||
63 | + } | ||
64 | + | ||
65 | + window.localStorage[ name ] = JSON.stringify( storage ); | ||
66 | + | ||
67 | + console.log( '[' + /\d\d\:\d\d\:\d\d/.exec( new Date() )[ 0 ] + ']', 'Saved config to LocalStorage.' ); | ||
68 | + | ||
69 | + }, | ||
70 | + | ||
71 | + clear: function () { | ||
72 | + | ||
73 | + delete window.localStorage[ name ]; | ||
74 | + | ||
75 | + } | ||
76 | + | ||
77 | + }; | ||
78 | + | ||
79 | +} | ||
80 | + | ||
81 | +export { Config }; |
editor/js/Editor.js
0 → 100644
1 | +import * as THREE from 'three'; | ||
2 | + | ||
3 | +import { Config } from './Config.js'; | ||
4 | +import { Loader } from './Loader.js'; | ||
5 | +import { History as _History } from './History.js'; | ||
6 | +import { Strings } from './Strings.js'; | ||
7 | +import { Storage as _Storage } from './Storage.js'; | ||
8 | +import { Selector } from './Selector.js'; | ||
9 | + | ||
10 | +var _DEFAULT_CAMERA = new THREE.PerspectiveCamera( 50, 1, 0.01, 1000 ); | ||
11 | +_DEFAULT_CAMERA.name = 'Camera'; | ||
12 | +_DEFAULT_CAMERA.position.set( 0, 5, 10 ); | ||
13 | +_DEFAULT_CAMERA.lookAt( new THREE.Vector3() ); | ||
14 | + | ||
15 | +function Editor() { | ||
16 | + | ||
17 | + const Signal = signals.Signal; // eslint-disable-line no-undef | ||
18 | + | ||
19 | + this.signals = { | ||
20 | + | ||
21 | + // script | ||
22 | + | ||
23 | + editScript: new Signal(), | ||
24 | + | ||
25 | + // player | ||
26 | + | ||
27 | + startPlayer: new Signal(), | ||
28 | + stopPlayer: new Signal(), | ||
29 | + | ||
30 | + // xr | ||
31 | + | ||
32 | + enterXR: new Signal(), | ||
33 | + offerXR: new Signal(), | ||
34 | + leaveXR: new Signal(), | ||
35 | + | ||
36 | + // notifications | ||
37 | + | ||
38 | + editorCleared: new Signal(), | ||
39 | + | ||
40 | + savingStarted: new Signal(), | ||
41 | + savingFinished: new Signal(), | ||
42 | + | ||
43 | + transformModeChanged: new Signal(), | ||
44 | + snapChanged: new Signal(), | ||
45 | + spaceChanged: new Signal(), | ||
46 | + rendererCreated: new Signal(), | ||
47 | + rendererUpdated: new Signal(), | ||
48 | + rendererDetectKTX2Support: new Signal(), | ||
49 | + | ||
50 | + sceneBackgroundChanged: new Signal(), | ||
51 | + sceneEnvironmentChanged: new Signal(), | ||
52 | + sceneFogChanged: new Signal(), | ||
53 | + sceneFogSettingsChanged: new Signal(), | ||
54 | + sceneGraphChanged: new Signal(), | ||
55 | + sceneRendered: new Signal(), | ||
56 | + | ||
57 | + cameraChanged: new Signal(), | ||
58 | + cameraResetted: new Signal(), | ||
59 | + | ||
60 | + geometryChanged: new Signal(), | ||
61 | + | ||
62 | + objectSelected: new Signal(), | ||
63 | + objectFocused: new Signal(), | ||
64 | + | ||
65 | + objectAdded: new Signal(), | ||
66 | + objectChanged: new Signal(), | ||
67 | + objectRemoved: new Signal(), | ||
68 | + | ||
69 | + cameraAdded: new Signal(), | ||
70 | + cameraRemoved: new Signal(), | ||
71 | + | ||
72 | + helperAdded: new Signal(), | ||
73 | + helperRemoved: new Signal(), | ||
74 | + | ||
75 | + materialAdded: new Signal(), | ||
76 | + materialChanged: new Signal(), | ||
77 | + materialRemoved: new Signal(), | ||
78 | + | ||
79 | + scriptAdded: new Signal(), | ||
80 | + scriptChanged: new Signal(), | ||
81 | + scriptRemoved: new Signal(), | ||
82 | + | ||
83 | + windowResize: new Signal(), | ||
84 | + | ||
85 | + showHelpersChanged: new Signal(), | ||
86 | + refreshSidebarObject3D: new Signal(), | ||
87 | + refreshSidebarEnvironment: new Signal(), | ||
88 | + historyChanged: new Signal(), | ||
89 | + | ||
90 | + viewportCameraChanged: new Signal(), | ||
91 | + viewportShadingChanged: new Signal(), | ||
92 | + | ||
93 | + intersectionsDetected: new Signal(), | ||
94 | + | ||
95 | + pathTracerUpdated: new Signal(), | ||
96 | + | ||
97 | + }; | ||
98 | + | ||
99 | + this.config = new Config(); | ||
100 | + this.history = new _History( this ); | ||
101 | + this.selector = new Selector( this ); | ||
102 | + this.storage = new _Storage(); | ||
103 | + this.strings = new Strings( this.config ); | ||
104 | + | ||
105 | + this.loader = new Loader( this ); | ||
106 | + | ||
107 | + this.camera = _DEFAULT_CAMERA.clone(); | ||
108 | + | ||
109 | + this.scene = new THREE.Scene(); | ||
110 | + this.scene.name = 'Scene'; | ||
111 | + | ||
112 | + this.sceneHelpers = new THREE.Scene(); | ||
113 | + this.sceneHelpers.add( new THREE.HemisphereLight( 0xffffff, 0x888888, 2 ) ); | ||
114 | + | ||
115 | + this.object = {}; | ||
116 | + this.geometries = {}; | ||
117 | + this.materials = {}; | ||
118 | + this.textures = {}; | ||
119 | + this.scripts = {}; | ||
120 | + | ||
121 | + this.materialsRefCounter = new Map(); // tracks how often is a material used by a 3D object | ||
122 | + | ||
123 | + this.mixer = new THREE.AnimationMixer( this.scene ); | ||
124 | + | ||
125 | + this.selected = null; | ||
126 | + this.helpers = {}; | ||
127 | + | ||
128 | + this.cameras = {}; | ||
129 | + | ||
130 | + this.viewportCamera = this.camera; | ||
131 | + this.viewportShading = 'default'; | ||
132 | + | ||
133 | + this.addCamera( this.camera ); | ||
134 | + | ||
135 | +} | ||
136 | + | ||
137 | +Editor.prototype = { | ||
138 | + | ||
139 | + setScene: function ( scene ) { | ||
140 | + | ||
141 | + this.scene.uuid = scene.uuid; | ||
142 | + this.scene.name = scene.name; | ||
143 | + | ||
144 | + this.scene.background = scene.background; | ||
145 | + this.scene.environment = scene.environment; | ||
146 | + this.scene.fog = scene.fog; | ||
147 | + this.scene.backgroundBlurriness = scene.backgroundBlurriness; | ||
148 | + this.scene.backgroundIntensity = scene.backgroundIntensity; | ||
149 | + | ||
150 | + this.scene.userData = JSON.parse( JSON.stringify( scene.userData ) ); | ||
151 | + | ||
152 | + // avoid render per object | ||
153 | + | ||
154 | + this.signals.sceneGraphChanged.active = false; | ||
155 | + | ||
156 | + while ( scene.children.length > 0 ) { | ||
157 | + | ||
158 | + this.addObject( scene.children[ 0 ] ); | ||
159 | + | ||
160 | + } | ||
161 | + | ||
162 | + this.signals.sceneGraphChanged.active = true; | ||
163 | + this.signals.sceneGraphChanged.dispatch(); | ||
164 | + | ||
165 | + }, | ||
166 | + | ||
167 | + // | ||
168 | + | ||
169 | + addObject: function ( object, parent, index ) { | ||
170 | + | ||
171 | + var scope = this; | ||
172 | + | ||
173 | + object.traverse( function ( child ) { | ||
174 | + | ||
175 | + if ( child.geometry !== undefined ) scope.addGeometry( child.geometry ); | ||
176 | + if ( child.material !== undefined ) scope.addMaterial( child.material ); | ||
177 | + | ||
178 | + scope.addCamera( child ); | ||
179 | + scope.addHelper( child ); | ||
180 | + | ||
181 | + } ); | ||
182 | + | ||
183 | + if ( parent === undefined ) { | ||
184 | + | ||
185 | + this.scene.add( object ); | ||
186 | + | ||
187 | + } else { | ||
188 | + | ||
189 | + parent.children.splice( index, 0, object ); | ||
190 | + object.parent = parent; | ||
191 | + | ||
192 | + } | ||
193 | + | ||
194 | + this.signals.objectAdded.dispatch( object ); | ||
195 | + this.signals.sceneGraphChanged.dispatch(); | ||
196 | + | ||
197 | + }, | ||
198 | + | ||
199 | + nameObject: function ( object, name ) { | ||
200 | + | ||
201 | + object.name = name; | ||
202 | + this.signals.sceneGraphChanged.dispatch(); | ||
203 | + | ||
204 | + }, | ||
205 | + | ||
206 | + removeObject: function ( object ) { | ||
207 | + | ||
208 | + if ( object.parent === null ) return; // avoid deleting the camera or scene | ||
209 | + | ||
210 | + var scope = this; | ||
211 | + | ||
212 | + object.traverse( function ( child ) { | ||
213 | + | ||
214 | + scope.removeCamera( child ); | ||
215 | + scope.removeHelper( child ); | ||
216 | + | ||
217 | + if ( child.material !== undefined ) scope.removeMaterial( child.material ); | ||
218 | + | ||
219 | + } ); | ||
220 | + | ||
221 | + object.parent.remove( object ); | ||
222 | + | ||
223 | + this.signals.objectRemoved.dispatch( object ); | ||
224 | + this.signals.sceneGraphChanged.dispatch(); | ||
225 | + | ||
226 | + }, | ||
227 | + | ||
228 | + addGeometry: function ( geometry ) { | ||
229 | + | ||
230 | + this.geometries[ geometry.uuid ] = geometry; | ||
231 | + | ||
232 | + }, | ||
233 | + | ||
234 | + setGeometryName: function ( geometry, name ) { | ||
235 | + | ||
236 | + geometry.name = name; | ||
237 | + this.signals.sceneGraphChanged.dispatch(); | ||
238 | + | ||
239 | + }, | ||
240 | + | ||
241 | + addMaterial: function ( material ) { | ||
242 | + | ||
243 | + if ( Array.isArray( material ) ) { | ||
244 | + | ||
245 | + for ( var i = 0, l = material.length; i < l; i ++ ) { | ||
246 | + | ||
247 | + this.addMaterialToRefCounter( material[ i ] ); | ||
248 | + | ||
249 | + } | ||
250 | + | ||
251 | + } else { | ||
252 | + | ||
253 | + this.addMaterialToRefCounter( material ); | ||
254 | + | ||
255 | + } | ||
256 | + | ||
257 | + this.signals.materialAdded.dispatch(); | ||
258 | + | ||
259 | + }, | ||
260 | + | ||
261 | + addMaterialToRefCounter: function ( material ) { | ||
262 | + | ||
263 | + var materialsRefCounter = this.materialsRefCounter; | ||
264 | + | ||
265 | + var count = materialsRefCounter.get( material ); | ||
266 | + | ||
267 | + if ( count === undefined ) { | ||
268 | + | ||
269 | + materialsRefCounter.set( material, 1 ); | ||
270 | + this.materials[ material.uuid ] = material; | ||
271 | + | ||
272 | + } else { | ||
273 | + | ||
274 | + count ++; | ||
275 | + materialsRefCounter.set( material, count ); | ||
276 | + | ||
277 | + } | ||
278 | + | ||
279 | + }, | ||
280 | + | ||
281 | + removeMaterial: function ( material ) { | ||
282 | + | ||
283 | + if ( Array.isArray( material ) ) { | ||
284 | + | ||
285 | + for ( var i = 0, l = material.length; i < l; i ++ ) { | ||
286 | + | ||
287 | + this.removeMaterialFromRefCounter( material[ i ] ); | ||
288 | + | ||
289 | + } | ||
290 | + | ||
291 | + } else { | ||
292 | + | ||
293 | + this.removeMaterialFromRefCounter( material ); | ||
294 | + | ||
295 | + } | ||
296 | + | ||
297 | + this.signals.materialRemoved.dispatch(); | ||
298 | + | ||
299 | + }, | ||
300 | + | ||
301 | + removeMaterialFromRefCounter: function ( material ) { | ||
302 | + | ||
303 | + var materialsRefCounter = this.materialsRefCounter; | ||
304 | + | ||
305 | + var count = materialsRefCounter.get( material ); | ||
306 | + count --; | ||
307 | + | ||
308 | + if ( count === 0 ) { | ||
309 | + | ||
310 | + materialsRefCounter.delete( material ); | ||
311 | + delete this.materials[ material.uuid ]; | ||
312 | + | ||
313 | + } else { | ||
314 | + | ||
315 | + materialsRefCounter.set( material, count ); | ||
316 | + | ||
317 | + } | ||
318 | + | ||
319 | + }, | ||
320 | + | ||
321 | + getMaterialById: function ( id ) { | ||
322 | + | ||
323 | + var material; | ||
324 | + var materials = Object.values( this.materials ); | ||
325 | + | ||
326 | + for ( var i = 0; i < materials.length; i ++ ) { | ||
327 | + | ||
328 | + if ( materials[ i ].id === id ) { | ||
329 | + | ||
330 | + material = materials[ i ]; | ||
331 | + break; | ||
332 | + | ||
333 | + } | ||
334 | + | ||
335 | + } | ||
336 | + | ||
337 | + return material; | ||
338 | + | ||
339 | + }, | ||
340 | + | ||
341 | + setMaterialName: function ( material, name ) { | ||
342 | + | ||
343 | + material.name = name; | ||
344 | + this.signals.sceneGraphChanged.dispatch(); | ||
345 | + | ||
346 | + }, | ||
347 | + | ||
348 | + addTexture: function ( texture ) { | ||
349 | + | ||
350 | + this.textures[ texture.uuid ] = texture; | ||
351 | + | ||
352 | + }, | ||
353 | + | ||
354 | + // | ||
355 | + | ||
356 | + addCamera: function ( camera ) { | ||
357 | + | ||
358 | + if ( camera.isCamera ) { | ||
359 | + | ||
360 | + this.cameras[ camera.uuid ] = camera; | ||
361 | + | ||
362 | + this.signals.cameraAdded.dispatch( camera ); | ||
363 | + | ||
364 | + } | ||
365 | + | ||
366 | + }, | ||
367 | + | ||
368 | + removeCamera: function ( camera ) { | ||
369 | + | ||
370 | + if ( this.cameras[ camera.uuid ] !== undefined ) { | ||
371 | + | ||
372 | + delete this.cameras[ camera.uuid ]; | ||
373 | + | ||
374 | + this.signals.cameraRemoved.dispatch( camera ); | ||
375 | + | ||
376 | + } | ||
377 | + | ||
378 | + }, | ||
379 | + | ||
380 | + // | ||
381 | + | ||
382 | + addHelper: function () { | ||
383 | + | ||
384 | + var geometry = new THREE.SphereGeometry( 2, 4, 2 ); | ||
385 | + var material = new THREE.MeshBasicMaterial( { color: 0xff0000, visible: false } ); | ||
386 | + | ||
387 | + return function ( object, helper ) { | ||
388 | + | ||
389 | + if ( helper === undefined ) { | ||
390 | + | ||
391 | + if ( object.isCamera ) { | ||
392 | + | ||
393 | + helper = new THREE.CameraHelper( object ); | ||
394 | + | ||
395 | + } else if ( object.isPointLight ) { | ||
396 | + | ||
397 | + helper = new THREE.PointLightHelper( object, 1 ); | ||
398 | + | ||
399 | + } else if ( object.isDirectionalLight ) { | ||
400 | + | ||
401 | + helper = new THREE.DirectionalLightHelper( object, 1 ); | ||
402 | + | ||
403 | + } else if ( object.isSpotLight ) { | ||
404 | + | ||
405 | + helper = new THREE.SpotLightHelper( object ); | ||
406 | + | ||
407 | + } else if ( object.isHemisphereLight ) { | ||
408 | + | ||
409 | + helper = new THREE.HemisphereLightHelper( object, 1 ); | ||
410 | + | ||
411 | + } else if ( object.isSkinnedMesh ) { | ||
412 | + | ||
413 | + helper = new THREE.SkeletonHelper( object.skeleton.bones[ 0 ] ); | ||
414 | + | ||
415 | + } else if ( object.isBone === true && object.parent && object.parent.isBone !== true ) { | ||
416 | + | ||
417 | + helper = new THREE.SkeletonHelper( object ); | ||
418 | + | ||
419 | + } else { | ||
420 | + | ||
421 | + // no helper for this object type | ||
422 | + return; | ||
423 | + | ||
424 | + } | ||
425 | + | ||
426 | + const picker = new THREE.Mesh( geometry, material ); | ||
427 | + picker.name = 'picker'; | ||
428 | + picker.userData.object = object; | ||
429 | + helper.add( picker ); | ||
430 | + | ||
431 | + } | ||
432 | + | ||
433 | + this.sceneHelpers.add( helper ); | ||
434 | + this.helpers[ object.id ] = helper; | ||
435 | + | ||
436 | + this.signals.helperAdded.dispatch( helper ); | ||
437 | + | ||
438 | + }; | ||
439 | + | ||
440 | + }(), | ||
441 | + | ||
442 | + removeHelper: function ( object ) { | ||
443 | + | ||
444 | + if ( this.helpers[ object.id ] !== undefined ) { | ||
445 | + | ||
446 | + var helper = this.helpers[ object.id ]; | ||
447 | + helper.parent.remove( helper ); | ||
448 | + helper.dispose(); | ||
449 | + | ||
450 | + delete this.helpers[ object.id ]; | ||
451 | + | ||
452 | + this.signals.helperRemoved.dispatch( helper ); | ||
453 | + | ||
454 | + } | ||
455 | + | ||
456 | + }, | ||
457 | + | ||
458 | + // | ||
459 | + | ||
460 | + addScript: function ( object, script ) { | ||
461 | + | ||
462 | + if ( this.scripts[ object.uuid ] === undefined ) { | ||
463 | + | ||
464 | + this.scripts[ object.uuid ] = []; | ||
465 | + | ||
466 | + } | ||
467 | + | ||
468 | + this.scripts[ object.uuid ].push( script ); | ||
469 | + | ||
470 | + this.signals.scriptAdded.dispatch( script ); | ||
471 | + | ||
472 | + }, | ||
473 | + | ||
474 | + removeScript: function ( object, script ) { | ||
475 | + | ||
476 | + if ( this.scripts[ object.uuid ] === undefined ) return; | ||
477 | + | ||
478 | + var index = this.scripts[ object.uuid ].indexOf( script ); | ||
479 | + | ||
480 | + if ( index !== - 1 ) { | ||
481 | + | ||
482 | + this.scripts[ object.uuid ].splice( index, 1 ); | ||
483 | + | ||
484 | + } | ||
485 | + | ||
486 | + this.signals.scriptRemoved.dispatch( script ); | ||
487 | + | ||
488 | + }, | ||
489 | + | ||
490 | + getObjectMaterial: function ( object, slot ) { | ||
491 | + | ||
492 | + var material = object.material; | ||
493 | + | ||
494 | + if ( Array.isArray( material ) && slot !== undefined ) { | ||
495 | + | ||
496 | + material = material[ slot ]; | ||
497 | + | ||
498 | + } | ||
499 | + | ||
500 | + return material; | ||
501 | + | ||
502 | + }, | ||
503 | + | ||
504 | + setObjectMaterial: function ( object, slot, newMaterial ) { | ||
505 | + | ||
506 | + if ( Array.isArray( object.material ) && slot !== undefined ) { | ||
507 | + | ||
508 | + object.material[ slot ] = newMaterial; | ||
509 | + | ||
510 | + } else { | ||
511 | + | ||
512 | + object.material = newMaterial; | ||
513 | + | ||
514 | + } | ||
515 | + | ||
516 | + }, | ||
517 | + | ||
518 | + setViewportCamera: function ( uuid ) { | ||
519 | + | ||
520 | + this.viewportCamera = this.cameras[ uuid ]; | ||
521 | + this.signals.viewportCameraChanged.dispatch(); | ||
522 | + | ||
523 | + }, | ||
524 | + | ||
525 | + setViewportShading: function ( value ) { | ||
526 | + | ||
527 | + this.viewportShading = value; | ||
528 | + this.signals.viewportShadingChanged.dispatch(); | ||
529 | + | ||
530 | + }, | ||
531 | + | ||
532 | + // | ||
533 | + | ||
534 | + select: function ( object ) { | ||
535 | + | ||
536 | + this.selector.select( object ); | ||
537 | + | ||
538 | + }, | ||
539 | + | ||
540 | + selectById: function ( id ) { | ||
541 | + | ||
542 | + if ( id === this.camera.id ) { | ||
543 | + | ||
544 | + this.select( this.camera ); | ||
545 | + return; | ||
546 | + | ||
547 | + } | ||
548 | + | ||
549 | + this.select( this.scene.getObjectById( id ) ); | ||
550 | + | ||
551 | + }, | ||
552 | + | ||
553 | + selectByUuid: function ( uuid ) { | ||
554 | + | ||
555 | + var scope = this; | ||
556 | + | ||
557 | + this.scene.traverse( function ( child ) { | ||
558 | + | ||
559 | + if ( child.uuid === uuid ) { | ||
560 | + | ||
561 | + scope.select( child ); | ||
562 | + | ||
563 | + } | ||
564 | + | ||
565 | + } ); | ||
566 | + | ||
567 | + }, | ||
568 | + | ||
569 | + deselect: function () { | ||
570 | + | ||
571 | + this.selector.deselect(); | ||
572 | + | ||
573 | + }, | ||
574 | + | ||
575 | + focus: function ( object ) { | ||
576 | + | ||
577 | + if ( object !== undefined ) { | ||
578 | + | ||
579 | + this.signals.objectFocused.dispatch( object ); | ||
580 | + | ||
581 | + } | ||
582 | + | ||
583 | + }, | ||
584 | + | ||
585 | + focusById: function ( id ) { | ||
586 | + | ||
587 | + this.focus( this.scene.getObjectById( id ) ); | ||
588 | + | ||
589 | + }, | ||
590 | + | ||
591 | + clear: function () { | ||
592 | + | ||
593 | + this.history.clear(); | ||
594 | + this.storage.clear(); | ||
595 | + | ||
596 | + this.camera.copy( _DEFAULT_CAMERA ); | ||
597 | + this.signals.cameraResetted.dispatch(); | ||
598 | + | ||
599 | + this.scene.name = 'Scene'; | ||
600 | + this.scene.userData = {}; | ||
601 | + this.scene.background = null; | ||
602 | + this.scene.environment = null; | ||
603 | + this.scene.fog = null; | ||
604 | + | ||
605 | + var objects = this.scene.children; | ||
606 | + | ||
607 | + this.signals.sceneGraphChanged.active = false; | ||
608 | + | ||
609 | + while ( objects.length > 0 ) { | ||
610 | + | ||
611 | + this.removeObject( objects[ 0 ] ); | ||
612 | + | ||
613 | + } | ||
614 | + | ||
615 | + this.signals.sceneGraphChanged.active = true; | ||
616 | + | ||
617 | + this.geometries = {}; | ||
618 | + this.materials = {}; | ||
619 | + this.textures = {}; | ||
620 | + this.scripts = {}; | ||
621 | + | ||
622 | + this.materialsRefCounter.clear(); | ||
623 | + | ||
624 | + this.animations = {}; | ||
625 | + this.mixer.stopAllAction(); | ||
626 | + | ||
627 | + this.deselect(); | ||
628 | + | ||
629 | + this.signals.editorCleared.dispatch(); | ||
630 | + | ||
631 | + }, | ||
632 | + | ||
633 | + // | ||
634 | + | ||
635 | + fromJSON: async function ( json ) { | ||
636 | + | ||
637 | + var loader = new THREE.ObjectLoader(); | ||
638 | + var camera = await loader.parseAsync( json.camera ); | ||
639 | + | ||
640 | + const existingUuid = this.camera.uuid; | ||
641 | + const incomingUuid = camera.uuid; | ||
642 | + | ||
643 | + // copy all properties, including uuid | ||
644 | + this.camera.copy( camera ); | ||
645 | + this.camera.uuid = incomingUuid; | ||
646 | + | ||
647 | + delete this.cameras[ existingUuid ]; // remove old entry [existingUuid, this.camera] | ||
648 | + this.cameras[ incomingUuid ] = this.camera; // add new entry [incomingUuid, this.camera] | ||
649 | + | ||
650 | + this.signals.cameraResetted.dispatch(); | ||
651 | + | ||
652 | + this.history.fromJSON( json.history ); | ||
653 | + this.scripts = json.scripts; | ||
654 | + | ||
655 | + this.setScene( await loader.parseAsync( json.scene ) ); | ||
656 | + | ||
657 | + if ( json.environment === 'ModelViewer' ) { | ||
658 | + | ||
659 | + this.signals.sceneEnvironmentChanged.dispatch( json.environment ); | ||
660 | + this.signals.refreshSidebarEnvironment.dispatch(); | ||
661 | + | ||
662 | + } | ||
663 | + | ||
664 | + }, | ||
665 | + | ||
666 | + toJSON: function () { | ||
667 | + | ||
668 | + // scripts clean up | ||
669 | + | ||
670 | + var scene = this.scene; | ||
671 | + var scripts = this.scripts; | ||
672 | + | ||
673 | + for ( var key in scripts ) { | ||
674 | + | ||
675 | + var script = scripts[ key ]; | ||
676 | + | ||
677 | + if ( script.length === 0 || scene.getObjectByProperty( 'uuid', key ) === undefined ) { | ||
678 | + | ||
679 | + delete scripts[ key ]; | ||
680 | + | ||
681 | + } | ||
682 | + | ||
683 | + } | ||
684 | + | ||
685 | + // honor modelviewer environment | ||
686 | + | ||
687 | + let environment = null; | ||
688 | + | ||
689 | + if ( this.scene.environment !== null && this.scene.environment.isRenderTargetTexture === true ) { | ||
690 | + | ||
691 | + environment = 'ModelViewer'; | ||
692 | + | ||
693 | + } | ||
694 | + | ||
695 | + // | ||
696 | + | ||
697 | + return { | ||
698 | + | ||
699 | + metadata: {}, | ||
700 | + project: { | ||
701 | + shadows: this.config.getKey( 'project/renderer/shadows' ), | ||
702 | + shadowType: this.config.getKey( 'project/renderer/shadowType' ), | ||
703 | + toneMapping: this.config.getKey( 'project/renderer/toneMapping' ), | ||
704 | + toneMappingExposure: this.config.getKey( 'project/renderer/toneMappingExposure' ) | ||
705 | + }, | ||
706 | + camera: this.viewportCamera.toJSON(), | ||
707 | + scene: this.scene.toJSON(), | ||
708 | + scripts: this.scripts, | ||
709 | + history: this.history.toJSON(), | ||
710 | + environment: environment | ||
711 | + | ||
712 | + }; | ||
713 | + | ||
714 | + }, | ||
715 | + | ||
716 | + objectByUuid: function ( uuid ) { | ||
717 | + | ||
718 | + return this.scene.getObjectByProperty( 'uuid', uuid, true ); | ||
719 | + | ||
720 | + }, | ||
721 | + | ||
722 | + execute: function ( cmd, optionalName ) { | ||
723 | + | ||
724 | + this.history.execute( cmd, optionalName ); | ||
725 | + | ||
726 | + }, | ||
727 | + | ||
728 | + undo: function () { | ||
729 | + | ||
730 | + this.history.undo(); | ||
731 | + | ||
732 | + }, | ||
733 | + | ||
734 | + redo: function () { | ||
735 | + | ||
736 | + this.history.redo(); | ||
737 | + | ||
738 | + }, | ||
739 | + | ||
740 | + utils: { | ||
741 | + | ||
742 | + save: save, | ||
743 | + saveArrayBuffer: saveArrayBuffer, | ||
744 | + saveString: saveString, | ||
745 | + formatNumber: formatNumber | ||
746 | + | ||
747 | + } | ||
748 | + | ||
749 | +}; | ||
750 | + | ||
751 | +const link = document.createElement( 'a' ); | ||
752 | + | ||
753 | +function save( blob, filename ) { | ||
754 | + | ||
755 | + if ( link.href ) { | ||
756 | + | ||
757 | + URL.revokeObjectURL( link.href ); | ||
758 | + | ||
759 | + } | ||
760 | + | ||
761 | + link.href = URL.createObjectURL( blob ); | ||
762 | + link.download = filename || 'data.json'; | ||
763 | + link.dispatchEvent( new MouseEvent( 'click' ) ); | ||
764 | + | ||
765 | +} | ||
766 | + | ||
767 | +function saveArrayBuffer( buffer, filename ) { | ||
768 | + | ||
769 | + save( new Blob( [ buffer ], { type: 'application/octet-stream' } ), filename ); | ||
770 | + | ||
771 | +} | ||
772 | + | ||
773 | +function saveString( text, filename ) { | ||
774 | + | ||
775 | + save( new Blob( [ text ], { type: 'text/plain' } ), filename ); | ||
776 | + | ||
777 | +} | ||
778 | + | ||
779 | +function formatNumber( number ) { | ||
780 | + | ||
781 | + return new Intl.NumberFormat( 'en-us', { useGrouping: true } ).format( number ); | ||
782 | + | ||
783 | +} | ||
784 | + | ||
785 | +export { Editor }; |
editor/js/EditorControls.js
0 → 100644
1 | +import * as THREE from 'three'; | ||
2 | + | ||
3 | +class EditorControls extends THREE.EventDispatcher { | ||
4 | + | ||
5 | + constructor( object, domElement ) { | ||
6 | + | ||
7 | + super(); | ||
8 | + | ||
9 | + // API | ||
10 | + | ||
11 | + this.enabled = true; | ||
12 | + this.center = new THREE.Vector3(); | ||
13 | + this.panSpeed = 0.002; | ||
14 | + this.zoomSpeed = 0.1; | ||
15 | + this.rotationSpeed = 0.005; | ||
16 | + | ||
17 | + // internals | ||
18 | + | ||
19 | + var scope = this; | ||
20 | + var vector = new THREE.Vector3(); | ||
21 | + var delta = new THREE.Vector3(); | ||
22 | + var box = new THREE.Box3(); | ||
23 | + | ||
24 | + var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 }; | ||
25 | + var state = STATE.NONE; | ||
26 | + | ||
27 | + var center = this.center; | ||
28 | + var normalMatrix = new THREE.Matrix3(); | ||
29 | + var pointer = new THREE.Vector2(); | ||
30 | + var pointerOld = new THREE.Vector2(); | ||
31 | + var spherical = new THREE.Spherical(); | ||
32 | + var sphere = new THREE.Sphere(); | ||
33 | + | ||
34 | + var pointers = []; | ||
35 | + var pointerPositions = {}; | ||
36 | + | ||
37 | + // events | ||
38 | + | ||
39 | + var changeEvent = { type: 'change' }; | ||
40 | + | ||
41 | + this.focus = function ( target ) { | ||
42 | + | ||
43 | + var distance; | ||
44 | + | ||
45 | + box.setFromObject( target ); | ||
46 | + | ||
47 | + if ( box.isEmpty() === false ) { | ||
48 | + | ||
49 | + box.getCenter( center ); | ||
50 | + distance = box.getBoundingSphere( sphere ).radius; | ||
51 | + | ||
52 | + } else { | ||
53 | + | ||
54 | + // Focusing on an Group, AmbientLight, etc | ||
55 | + | ||
56 | + center.setFromMatrixPosition( target.matrixWorld ); | ||
57 | + distance = 0.1; | ||
58 | + | ||
59 | + } | ||
60 | + | ||
61 | + delta.set( 0, 0, 1 ); | ||
62 | + delta.applyQuaternion( object.quaternion ); | ||
63 | + delta.multiplyScalar( distance * 4 ); | ||
64 | + | ||
65 | + object.position.copy( center ).add( delta ); | ||
66 | + | ||
67 | + scope.dispatchEvent( changeEvent ); | ||
68 | + | ||
69 | + }; | ||
70 | + | ||
71 | + this.pan = function ( delta ) { | ||
72 | + | ||
73 | + var distance = object.position.distanceTo( center ); | ||
74 | + | ||
75 | + delta.multiplyScalar( distance * scope.panSpeed ); | ||
76 | + delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) ); | ||
77 | + | ||
78 | + object.position.add( delta ); | ||
79 | + center.add( delta ); | ||
80 | + | ||
81 | + scope.dispatchEvent( changeEvent ); | ||
82 | + | ||
83 | + }; | ||
84 | + | ||
85 | + this.zoom = function ( delta ) { | ||
86 | + | ||
87 | + var distance = object.position.distanceTo( center ); | ||
88 | + | ||
89 | + delta.multiplyScalar( distance * scope.zoomSpeed ); | ||
90 | + | ||
91 | + if ( delta.length() > distance ) return; | ||
92 | + | ||
93 | + delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) ); | ||
94 | + | ||
95 | + object.position.add( delta ); | ||
96 | + | ||
97 | + scope.dispatchEvent( changeEvent ); | ||
98 | + | ||
99 | + }; | ||
100 | + | ||
101 | + this.rotate = function ( delta ) { | ||
102 | + | ||
103 | + vector.copy( object.position ).sub( center ); | ||
104 | + | ||
105 | + spherical.setFromVector3( vector ); | ||
106 | + | ||
107 | + spherical.theta += delta.x * scope.rotationSpeed; | ||
108 | + spherical.phi += delta.y * scope.rotationSpeed; | ||
109 | + | ||
110 | + spherical.makeSafe(); | ||
111 | + | ||
112 | + vector.setFromSpherical( spherical ); | ||
113 | + | ||
114 | + object.position.copy( center ).add( vector ); | ||
115 | + | ||
116 | + object.lookAt( center ); | ||
117 | + | ||
118 | + scope.dispatchEvent( changeEvent ); | ||
119 | + | ||
120 | + }; | ||
121 | + | ||
122 | + // | ||
123 | + | ||
124 | + function onPointerDown( event ) { | ||
125 | + | ||
126 | + if ( scope.enabled === false ) return; | ||
127 | + | ||
128 | + if ( pointers.length === 0 ) { | ||
129 | + | ||
130 | + domElement.setPointerCapture( event.pointerId ); | ||
131 | + | ||
132 | + domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); | ||
133 | + domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); | ||
134 | + | ||
135 | + } | ||
136 | + | ||
137 | + // | ||
138 | + | ||
139 | + if ( isTrackingPointer( event ) ) return; | ||
140 | + | ||
141 | + // | ||
142 | + | ||
143 | + addPointer( event ); | ||
144 | + | ||
145 | + if ( event.pointerType === 'touch' ) { | ||
146 | + | ||
147 | + onTouchStart( event ); | ||
148 | + | ||
149 | + } else { | ||
150 | + | ||
151 | + onMouseDown( event ); | ||
152 | + | ||
153 | + } | ||
154 | + | ||
155 | + } | ||
156 | + | ||
157 | + function onPointerMove( event ) { | ||
158 | + | ||
159 | + if ( scope.enabled === false ) return; | ||
160 | + | ||
161 | + if ( event.pointerType === 'touch' ) { | ||
162 | + | ||
163 | + onTouchMove( event ); | ||
164 | + | ||
165 | + } else { | ||
166 | + | ||
167 | + onMouseMove( event ); | ||
168 | + | ||
169 | + } | ||
170 | + | ||
171 | + } | ||
172 | + | ||
173 | + function onPointerUp( event ) { | ||
174 | + | ||
175 | + removePointer( event ); | ||
176 | + | ||
177 | + switch ( pointers.length ) { | ||
178 | + | ||
179 | + case 0: | ||
180 | + | ||
181 | + domElement.releasePointerCapture( event.pointerId ); | ||
182 | + | ||
183 | + domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); | ||
184 | + domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); | ||
185 | + | ||
186 | + break; | ||
187 | + | ||
188 | + case 1: | ||
189 | + | ||
190 | + var pointerId = pointers[ 0 ]; | ||
191 | + var position = pointerPositions[ pointerId ]; | ||
192 | + | ||
193 | + // minimal placeholder event - allows state correction on pointer-up | ||
194 | + onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); | ||
195 | + | ||
196 | + break; | ||
197 | + | ||
198 | + } | ||
199 | + | ||
200 | + } | ||
201 | + | ||
202 | + // mouse | ||
203 | + | ||
204 | + function onMouseDown( event ) { | ||
205 | + | ||
206 | + if ( event.button === 0 ) { | ||
207 | + | ||
208 | + state = STATE.ROTATE; | ||
209 | + | ||
210 | + } else if ( event.button === 1 ) { | ||
211 | + | ||
212 | + state = STATE.ZOOM; | ||
213 | + | ||
214 | + } else if ( event.button === 2 ) { | ||
215 | + | ||
216 | + state = STATE.PAN; | ||
217 | + | ||
218 | + } | ||
219 | + | ||
220 | + pointerOld.set( event.clientX, event.clientY ); | ||
221 | + | ||
222 | + } | ||
223 | + | ||
224 | + function onMouseMove( event ) { | ||
225 | + | ||
226 | + pointer.set( event.clientX, event.clientY ); | ||
227 | + | ||
228 | + var movementX = pointer.x - pointerOld.x; | ||
229 | + var movementY = pointer.y - pointerOld.y; | ||
230 | + | ||
231 | + if ( state === STATE.ROTATE ) { | ||
232 | + | ||
233 | + scope.rotate( delta.set( - movementX, - movementY, 0 ) ); | ||
234 | + | ||
235 | + } else if ( state === STATE.ZOOM ) { | ||
236 | + | ||
237 | + scope.zoom( delta.set( 0, 0, movementY ) ); | ||
238 | + | ||
239 | + } else if ( state === STATE.PAN ) { | ||
240 | + | ||
241 | + scope.pan( delta.set( - movementX, movementY, 0 ) ); | ||
242 | + | ||
243 | + } | ||
244 | + | ||
245 | + pointerOld.set( event.clientX, event.clientY ); | ||
246 | + | ||
247 | + } | ||
248 | + | ||
249 | + function onMouseUp() { | ||
250 | + | ||
251 | + state = STATE.NONE; | ||
252 | + | ||
253 | + } | ||
254 | + | ||
255 | + function onMouseWheel( event ) { | ||
256 | + | ||
257 | + if ( scope.enabled === false ) return; | ||
258 | + | ||
259 | + event.preventDefault(); | ||
260 | + | ||
261 | + // Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460 | ||
262 | + scope.zoom( delta.set( 0, 0, event.deltaY > 0 ? 1 : - 1 ) ); | ||
263 | + | ||
264 | + } | ||
265 | + | ||
266 | + function contextmenu( event ) { | ||
267 | + | ||
268 | + event.preventDefault(); | ||
269 | + | ||
270 | + } | ||
271 | + | ||
272 | + this.dispose = function () { | ||
273 | + | ||
274 | + domElement.removeEventListener( 'contextmenu', contextmenu ); | ||
275 | + domElement.removeEventListener( 'dblclick', onMouseUp ); | ||
276 | + domElement.removeEventListener( 'wheel', onMouseWheel ); | ||
277 | + | ||
278 | + domElement.removeEventListener( 'pointerdown', onPointerDown ); | ||
279 | + | ||
280 | + }; | ||
281 | + | ||
282 | + domElement.addEventListener( 'contextmenu', contextmenu ); | ||
283 | + domElement.addEventListener( 'dblclick', onMouseUp ); | ||
284 | + domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); | ||
285 | + | ||
286 | + domElement.addEventListener( 'pointerdown', onPointerDown ); | ||
287 | + | ||
288 | + // touch | ||
289 | + | ||
290 | + var touches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]; | ||
291 | + var prevTouches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]; | ||
292 | + | ||
293 | + var prevDistance = null; | ||
294 | + | ||
295 | + function onTouchStart( event ) { | ||
296 | + | ||
297 | + trackPointer( event ); | ||
298 | + | ||
299 | + switch ( pointers.length ) { | ||
300 | + | ||
301 | + case 1: | ||
302 | + touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio ); | ||
303 | + touches[ 1 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio ); | ||
304 | + break; | ||
305 | + | ||
306 | + case 2: | ||
307 | + | ||
308 | + var position = getSecondPointerPosition( event ); | ||
309 | + | ||
310 | + touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio ); | ||
311 | + touches[ 1 ].set( position.x, position.y, 0 ).divideScalar( window.devicePixelRatio ); | ||
312 | + prevDistance = touches[ 0 ].distanceTo( touches[ 1 ] ); | ||
313 | + break; | ||
314 | + | ||
315 | + } | ||
316 | + | ||
317 | + prevTouches[ 0 ].copy( touches[ 0 ] ); | ||
318 | + prevTouches[ 1 ].copy( touches[ 1 ] ); | ||
319 | + | ||
320 | + } | ||
321 | + | ||
322 | + | ||
323 | + function onTouchMove( event ) { | ||
324 | + | ||
325 | + trackPointer( event ); | ||
326 | + | ||
327 | + function getClosest( touch, touches ) { | ||
328 | + | ||
329 | + var closest = touches[ 0 ]; | ||
330 | + | ||
331 | + for ( var touch2 of touches ) { | ||
332 | + | ||
333 | + if ( closest.distanceTo( touch ) > touch2.distanceTo( touch ) ) closest = touch2; | ||
334 | + | ||
335 | + } | ||
336 | + | ||
337 | + return closest; | ||
338 | + | ||
339 | + } | ||
340 | + | ||
341 | + switch ( pointers.length ) { | ||
342 | + | ||
343 | + case 1: | ||
344 | + touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio ); | ||
345 | + touches[ 1 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio ); | ||
346 | + scope.rotate( touches[ 0 ].sub( getClosest( touches[ 0 ], prevTouches ) ).multiplyScalar( - 1 ) ); | ||
347 | + break; | ||
348 | + | ||
349 | + case 2: | ||
350 | + | ||
351 | + var position = getSecondPointerPosition( event ); | ||
352 | + | ||
353 | + touches[ 0 ].set( event.pageX, event.pageY, 0 ).divideScalar( window.devicePixelRatio ); | ||
354 | + touches[ 1 ].set( position.x, position.y, 0 ).divideScalar( window.devicePixelRatio ); | ||
355 | + var distance = touches[ 0 ].distanceTo( touches[ 1 ] ); | ||
356 | + scope.zoom( delta.set( 0, 0, prevDistance - distance ) ); | ||
357 | + prevDistance = distance; | ||
358 | + | ||
359 | + | ||
360 | + var offset0 = touches[ 0 ].clone().sub( getClosest( touches[ 0 ], prevTouches ) ); | ||
361 | + var offset1 = touches[ 1 ].clone().sub( getClosest( touches[ 1 ], prevTouches ) ); | ||
362 | + offset0.x = - offset0.x; | ||
363 | + offset1.x = - offset1.x; | ||
364 | + | ||
365 | + scope.pan( offset0.add( offset1 ) ); | ||
366 | + | ||
367 | + break; | ||
368 | + | ||
369 | + } | ||
370 | + | ||
371 | + prevTouches[ 0 ].copy( touches[ 0 ] ); | ||
372 | + prevTouches[ 1 ].copy( touches[ 1 ] ); | ||
373 | + | ||
374 | + } | ||
375 | + | ||
376 | + function addPointer( event ) { | ||
377 | + | ||
378 | + pointers.push( event.pointerId ); | ||
379 | + | ||
380 | + } | ||
381 | + | ||
382 | + function removePointer( event ) { | ||
383 | + | ||
384 | + delete pointerPositions[ event.pointerId ]; | ||
385 | + | ||
386 | + for ( var i = 0; i < pointers.length; i ++ ) { | ||
387 | + | ||
388 | + if ( pointers[ i ] == event.pointerId ) { | ||
389 | + | ||
390 | + pointers.splice( i, 1 ); | ||
391 | + return; | ||
392 | + | ||
393 | + } | ||
394 | + | ||
395 | + } | ||
396 | + | ||
397 | + } | ||
398 | + | ||
399 | + function isTrackingPointer( event ) { | ||
400 | + | ||
401 | + for ( var i = 0; i < pointers.length; i ++ ) { | ||
402 | + | ||
403 | + if ( pointers[ i ] == event.pointerId ) return true; | ||
404 | + | ||
405 | + } | ||
406 | + | ||
407 | + return false; | ||
408 | + | ||
409 | + } | ||
410 | + | ||
411 | + function trackPointer( event ) { | ||
412 | + | ||
413 | + var position = pointerPositions[ event.pointerId ]; | ||
414 | + | ||
415 | + if ( position === undefined ) { | ||
416 | + | ||
417 | + position = new THREE.Vector2(); | ||
418 | + pointerPositions[ event.pointerId ] = position; | ||
419 | + | ||
420 | + } | ||
421 | + | ||
422 | + position.set( event.pageX, event.pageY ); | ||
423 | + | ||
424 | + } | ||
425 | + | ||
426 | + function getSecondPointerPosition( event ) { | ||
427 | + | ||
428 | + var pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ]; | ||
429 | + | ||
430 | + return pointerPositions[ pointerId ]; | ||
431 | + | ||
432 | + } | ||
433 | + | ||
434 | + } | ||
435 | + | ||
436 | +} | ||
437 | + | ||
438 | +export { EditorControls }; |
editor/js/History.js
0 → 100644
1 | +import * as Commands from './commands/Commands.js'; | ||
2 | + | ||
3 | +class History { | ||
4 | + | ||
5 | + constructor( editor ) { | ||
6 | + | ||
7 | + this.editor = editor; | ||
8 | + this.undos = []; | ||
9 | + this.redos = []; | ||
10 | + this.lastCmdTime = Date.now(); | ||
11 | + this.idCounter = 0; | ||
12 | + | ||
13 | + this.historyDisabled = false; | ||
14 | + this.config = editor.config; | ||
15 | + | ||
16 | + // signals | ||
17 | + | ||
18 | + const scope = this; | ||
19 | + | ||
20 | + this.editor.signals.startPlayer.add( function () { | ||
21 | + | ||
22 | + scope.historyDisabled = true; | ||
23 | + | ||
24 | + } ); | ||
25 | + | ||
26 | + this.editor.signals.stopPlayer.add( function () { | ||
27 | + | ||
28 | + scope.historyDisabled = false; | ||
29 | + | ||
30 | + } ); | ||
31 | + | ||
32 | + } | ||
33 | + | ||
34 | + execute( cmd, optionalName ) { | ||
35 | + | ||
36 | + const lastCmd = this.undos[ this.undos.length - 1 ]; | ||
37 | + const timeDifference = Date.now() - this.lastCmdTime; | ||
38 | + | ||
39 | + const isUpdatableCmd = lastCmd && | ||
40 | + lastCmd.updatable && | ||
41 | + cmd.updatable && | ||
42 | + lastCmd.object === cmd.object && | ||
43 | + lastCmd.type === cmd.type && | ||
44 | + lastCmd.script === cmd.script && | ||
45 | + lastCmd.attributeName === cmd.attributeName; | ||
46 | + | ||
47 | + if ( isUpdatableCmd && cmd.type === 'SetScriptValueCommand' ) { | ||
48 | + | ||
49 | + // When the cmd.type is "SetScriptValueCommand" the timeDifference is ignored | ||
50 | + | ||
51 | + lastCmd.update( cmd ); | ||
52 | + cmd = lastCmd; | ||
53 | + | ||
54 | + } else if ( isUpdatableCmd && timeDifference < 500 ) { | ||
55 | + | ||
56 | + lastCmd.update( cmd ); | ||
57 | + cmd = lastCmd; | ||
58 | + | ||
59 | + } else { | ||
60 | + | ||
61 | + // the command is not updatable and is added as a new part of the history | ||
62 | + | ||
63 | + this.undos.push( cmd ); | ||
64 | + cmd.id = ++ this.idCounter; | ||
65 | + | ||
66 | + } | ||
67 | + | ||
68 | + cmd.name = ( optionalName !== undefined ) ? optionalName : cmd.name; | ||
69 | + cmd.execute(); | ||
70 | + cmd.inMemory = true; | ||
71 | + | ||
72 | + if ( this.config.getKey( 'settings/history' ) ) { | ||
73 | + | ||
74 | + cmd.json = cmd.toJSON(); // serialize the cmd immediately after execution and append the json to the cmd | ||
75 | + | ||
76 | + } | ||
77 | + | ||
78 | + this.lastCmdTime = Date.now(); | ||
79 | + | ||
80 | + // clearing all the redo-commands | ||
81 | + | ||
82 | + this.redos = []; | ||
83 | + this.editor.signals.historyChanged.dispatch( cmd ); | ||
84 | + | ||
85 | + } | ||
86 | + | ||
87 | + undo() { | ||
88 | + | ||
89 | + if ( this.historyDisabled ) { | ||
90 | + | ||
91 | + alert( this.editor.strings.getKey( 'prompt/history/forbid' ) ); | ||
92 | + return; | ||
93 | + | ||
94 | + } | ||
95 | + | ||
96 | + let cmd = undefined; | ||
97 | + | ||
98 | + if ( this.undos.length > 0 ) { | ||
99 | + | ||
100 | + cmd = this.undos.pop(); | ||
101 | + | ||
102 | + if ( cmd.inMemory === false ) { | ||
103 | + | ||
104 | + cmd.fromJSON( cmd.json ); | ||
105 | + | ||
106 | + } | ||
107 | + | ||
108 | + } | ||
109 | + | ||
110 | + if ( cmd !== undefined ) { | ||
111 | + | ||
112 | + cmd.undo(); | ||
113 | + this.redos.push( cmd ); | ||
114 | + this.editor.signals.historyChanged.dispatch( cmd ); | ||
115 | + | ||
116 | + } | ||
117 | + | ||
118 | + return cmd; | ||
119 | + | ||
120 | + } | ||
121 | + | ||
122 | + redo() { | ||
123 | + | ||
124 | + if ( this.historyDisabled ) { | ||
125 | + | ||
126 | + alert( this.editor.strings.getKey( 'prompt/history/forbid' ) ); | ||
127 | + return; | ||
128 | + | ||
129 | + } | ||
130 | + | ||
131 | + let cmd = undefined; | ||
132 | + | ||
133 | + if ( this.redos.length > 0 ) { | ||
134 | + | ||
135 | + cmd = this.redos.pop(); | ||
136 | + | ||
137 | + if ( cmd.inMemory === false ) { | ||
138 | + | ||
139 | + cmd.fromJSON( cmd.json ); | ||
140 | + | ||
141 | + } | ||
142 | + | ||
143 | + } | ||
144 | + | ||
145 | + if ( cmd !== undefined ) { | ||
146 | + | ||
147 | + cmd.execute(); | ||
148 | + this.undos.push( cmd ); | ||
149 | + this.editor.signals.historyChanged.dispatch( cmd ); | ||
150 | + | ||
151 | + } | ||
152 | + | ||
153 | + return cmd; | ||
154 | + | ||
155 | + } | ||
156 | + | ||
157 | + toJSON() { | ||
158 | + | ||
159 | + const history = {}; | ||
160 | + history.undos = []; | ||
161 | + history.redos = []; | ||
162 | + | ||
163 | + if ( ! this.config.getKey( 'settings/history' ) ) { | ||
164 | + | ||
165 | + return history; | ||
166 | + | ||
167 | + } | ||
168 | + | ||
169 | + // Append Undos to History | ||
170 | + | ||
171 | + for ( let i = 0; i < this.undos.length; i ++ ) { | ||
172 | + | ||
173 | + if ( this.undos[ i ].hasOwnProperty( 'json' ) ) { | ||
174 | + | ||
175 | + history.undos.push( this.undos[ i ].json ); | ||
176 | + | ||
177 | + } | ||
178 | + | ||
179 | + } | ||
180 | + | ||
181 | + // Append Redos to History | ||
182 | + | ||
183 | + for ( let i = 0; i < this.redos.length; i ++ ) { | ||
184 | + | ||
185 | + if ( this.redos[ i ].hasOwnProperty( 'json' ) ) { | ||
186 | + | ||
187 | + history.redos.push( this.redos[ i ].json ); | ||
188 | + | ||
189 | + } | ||
190 | + | ||
191 | + } | ||
192 | + | ||
193 | + return history; | ||
194 | + | ||
195 | + } | ||
196 | + | ||
197 | + fromJSON( json ) { | ||
198 | + | ||
199 | + if ( json === undefined ) return; | ||
200 | + | ||
201 | + for ( let i = 0; i < json.undos.length; i ++ ) { | ||
202 | + | ||
203 | + const cmdJSON = json.undos[ i ]; | ||
204 | + const cmd = new Commands[ cmdJSON.type ]( this.editor ); // creates a new object of type "json.type" | ||
205 | + cmd.json = cmdJSON; | ||
206 | + cmd.id = cmdJSON.id; | ||
207 | + cmd.name = cmdJSON.name; | ||
208 | + this.undos.push( cmd ); | ||
209 | + this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter | ||
210 | + | ||
211 | + } | ||
212 | + | ||
213 | + for ( let i = 0; i < json.redos.length; i ++ ) { | ||
214 | + | ||
215 | + const cmdJSON = json.redos[ i ]; | ||
216 | + const cmd = new Commands[ cmdJSON.type ]( this.editor ); // creates a new object of type "json.type" | ||
217 | + cmd.json = cmdJSON; | ||
218 | + cmd.id = cmdJSON.id; | ||
219 | + cmd.name = cmdJSON.name; | ||
220 | + this.redos.push( cmd ); | ||
221 | + this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter | ||
222 | + | ||
223 | + } | ||
224 | + | ||
225 | + // Select the last executed undo-command | ||
226 | + this.editor.signals.historyChanged.dispatch( this.undos[ this.undos.length - 1 ] ); | ||
227 | + | ||
228 | + } | ||
229 | + | ||
230 | + clear() { | ||
231 | + | ||
232 | + this.undos = []; | ||
233 | + this.redos = []; | ||
234 | + this.idCounter = 0; | ||
235 | + | ||
236 | + this.editor.signals.historyChanged.dispatch(); | ||
237 | + | ||
238 | + } | ||
239 | + | ||
240 | + goToState( id ) { | ||
241 | + | ||
242 | + if ( this.historyDisabled ) { | ||
243 | + | ||
244 | + alert( this.editor.strings.getKey( 'prompt/history/forbid' ) ); | ||
245 | + return; | ||
246 | + | ||
247 | + } | ||
248 | + | ||
249 | + this.editor.signals.sceneGraphChanged.active = false; | ||
250 | + this.editor.signals.historyChanged.active = false; | ||
251 | + | ||
252 | + let cmd = this.undos.length > 0 ? this.undos[ this.undos.length - 1 ] : undefined; // next cmd to pop | ||
253 | + | ||
254 | + if ( cmd === undefined || id > cmd.id ) { | ||
255 | + | ||
256 | + cmd = this.redo(); | ||
257 | + while ( cmd !== undefined && id > cmd.id ) { | ||
258 | + | ||
259 | + cmd = this.redo(); | ||
260 | + | ||
261 | + } | ||
262 | + | ||
263 | + } else { | ||
264 | + | ||
265 | + while ( true ) { | ||
266 | + | ||
267 | + cmd = this.undos[ this.undos.length - 1 ]; // next cmd to pop | ||
268 | + | ||
269 | + if ( cmd === undefined || id === cmd.id ) break; | ||
270 | + | ||
271 | + this.undo(); | ||
272 | + | ||
273 | + } | ||
274 | + | ||
275 | + } | ||
276 | + | ||
277 | + this.editor.signals.sceneGraphChanged.active = true; | ||
278 | + this.editor.signals.historyChanged.active = true; | ||
279 | + | ||
280 | + this.editor.signals.sceneGraphChanged.dispatch(); | ||
281 | + this.editor.signals.historyChanged.dispatch( cmd ); | ||
282 | + | ||
283 | + } | ||
284 | + | ||
285 | + enableSerialization( id ) { | ||
286 | + | ||
287 | + /** | ||
288 | + * because there might be commands in this.undos and this.redos | ||
289 | + * which have not been serialized with .toJSON() we go back | ||
290 | + * to the oldest command and redo one command after the other | ||
291 | + * while also calling .toJSON() on them. | ||
292 | + */ | ||
293 | + | ||
294 | + this.goToState( - 1 ); | ||
295 | + | ||
296 | + this.editor.signals.sceneGraphChanged.active = false; | ||
297 | + this.editor.signals.historyChanged.active = false; | ||
298 | + | ||
299 | + let cmd = this.redo(); | ||
300 | + while ( cmd !== undefined ) { | ||
301 | + | ||
302 | + if ( ! cmd.hasOwnProperty( 'json' ) ) { | ||
303 | + | ||
304 | + cmd.json = cmd.toJSON(); | ||
305 | + | ||
306 | + } | ||
307 | + | ||
308 | + cmd = this.redo(); | ||
309 | + | ||
310 | + } | ||
311 | + | ||
312 | + this.editor.signals.sceneGraphChanged.active = true; | ||
313 | + this.editor.signals.historyChanged.active = true; | ||
314 | + | ||
315 | + this.goToState( id ); | ||
316 | + | ||
317 | + } | ||
318 | + | ||
319 | +} | ||
320 | + | ||
321 | +export { History }; |
editor/js/Loader.js
0 → 100644
1 | +import * as THREE from 'three'; | ||
2 | + | ||
3 | +import { TGALoader } from 'three/addons/loaders/TGALoader.js'; | ||
4 | + | ||
5 | +import { AddObjectCommand } from './commands/AddObjectCommand.js'; | ||
6 | + | ||
7 | +import { LoaderUtils } from './LoaderUtils.js'; | ||
8 | + | ||
9 | +import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js'; | ||
10 | + | ||
11 | +function Loader( editor ) { | ||
12 | + | ||
13 | + const scope = this; | ||
14 | + | ||
15 | + this.texturePath = ''; | ||
16 | + | ||
17 | + this.loadItemList = function ( items ) { | ||
18 | + | ||
19 | + LoaderUtils.getFilesFromItemList( items, function ( files, filesMap ) { | ||
20 | + | ||
21 | + scope.loadFiles( files, filesMap ); | ||
22 | + | ||
23 | + } ); | ||
24 | + | ||
25 | + }; | ||
26 | + | ||
27 | + this.loadFiles = function ( files, filesMap ) { | ||
28 | + | ||
29 | + if ( files.length > 0 ) { | ||
30 | + | ||
31 | + filesMap = filesMap || LoaderUtils.createFilesMap( files ); | ||
32 | + | ||
33 | + const manager = new THREE.LoadingManager(); | ||
34 | + manager.setURLModifier( function ( url ) { | ||
35 | + | ||
36 | + url = url.replace( /^(\.?\/)/, '' ); // remove './' | ||
37 | + | ||
38 | + const file = filesMap[ url ]; | ||
39 | + | ||
40 | + if ( file ) { | ||
41 | + | ||
42 | + console.log( 'Loading', url ); | ||
43 | + | ||
44 | + return URL.createObjectURL( file ); | ||
45 | + | ||
46 | + } | ||
47 | + | ||
48 | + return url; | ||
49 | + | ||
50 | + } ); | ||
51 | + | ||
52 | + manager.addHandler( /\.tga$/i, new TGALoader() ); | ||
53 | + | ||
54 | + for ( let i = 0; i < files.length; i ++ ) { | ||
55 | + | ||
56 | + scope.loadFile( files[ i ], manager ); | ||
57 | + | ||
58 | + } | ||
59 | + | ||
60 | + } | ||
61 | + | ||
62 | + }; | ||
63 | + | ||
64 | + this.loadFile = function ( file, manager ) { | ||
65 | + | ||
66 | + const filename = file.name; | ||
67 | + const extension = filename.split( '.' ).pop().toLowerCase(); | ||
68 | + | ||
69 | + const reader = new FileReader(); | ||
70 | + reader.addEventListener( 'progress', function ( event ) { | ||
71 | + | ||
72 | + const size = '(' + editor.utils.formatNumber( Math.floor( event.total / 1000 ) ) + ' KB)'; | ||
73 | + const progress = Math.floor( ( event.loaded / event.total ) * 100 ) + '%'; | ||
74 | + | ||
75 | + console.log( 'Loading', filename, size, progress ); | ||
76 | + | ||
77 | + } ); | ||
78 | + | ||
79 | + switch ( extension ) { | ||
80 | + | ||
81 | + case '3dm': | ||
82 | + | ||
83 | + { | ||
84 | + | ||
85 | + reader.addEventListener( 'load', async function ( event ) { | ||
86 | + | ||
87 | + const contents = event.target.result; | ||
88 | + | ||
89 | + const { Rhino3dmLoader } = await import( 'three/addons/loaders/3DMLoader.js' ); | ||
90 | + | ||
91 | + const loader = new Rhino3dmLoader(); | ||
92 | + loader.setLibraryPath( '../examples/jsm/libs/rhino3dm/' ); | ||
93 | + loader.parse( contents, function ( object ) { | ||
94 | + | ||
95 | + object.name = filename; | ||
96 | + | ||
97 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
98 | + | ||
99 | + }, function ( error ) { | ||
100 | + | ||
101 | + console.error( error ); | ||
102 | + | ||
103 | + } ); | ||
104 | + | ||
105 | + }, false ); | ||
106 | + reader.readAsArrayBuffer( file ); | ||
107 | + | ||
108 | + break; | ||
109 | + | ||
110 | + } | ||
111 | + | ||
112 | + case '3ds': | ||
113 | + | ||
114 | + { | ||
115 | + | ||
116 | + reader.addEventListener( 'load', async function ( event ) { | ||
117 | + | ||
118 | + const { TDSLoader } = await import( 'three/addons/loaders/TDSLoader.js' ); | ||
119 | + | ||
120 | + const loader = new TDSLoader(); | ||
121 | + const object = loader.parse( event.target.result ); | ||
122 | + | ||
123 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
124 | + | ||
125 | + }, false ); | ||
126 | + reader.readAsArrayBuffer( file ); | ||
127 | + | ||
128 | + break; | ||
129 | + | ||
130 | + } | ||
131 | + | ||
132 | + case '3mf': | ||
133 | + | ||
134 | + { | ||
135 | + | ||
136 | + reader.addEventListener( 'load', async function ( event ) { | ||
137 | + | ||
138 | + const { ThreeMFLoader } = await import( 'three/addons/loaders/3MFLoader.js' ); | ||
139 | + | ||
140 | + const loader = new ThreeMFLoader(); | ||
141 | + const object = loader.parse( event.target.result ); | ||
142 | + | ||
143 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
144 | + | ||
145 | + }, false ); | ||
146 | + reader.readAsArrayBuffer( file ); | ||
147 | + | ||
148 | + break; | ||
149 | + | ||
150 | + } | ||
151 | + | ||
152 | + case 'amf': | ||
153 | + | ||
154 | + { | ||
155 | + | ||
156 | + reader.addEventListener( 'load', async function ( event ) { | ||
157 | + | ||
158 | + const { AMFLoader } = await import( 'three/addons/loaders/AMFLoader.js' ); | ||
159 | + | ||
160 | + const loader = new AMFLoader(); | ||
161 | + const amfobject = loader.parse( event.target.result ); | ||
162 | + | ||
163 | + editor.execute( new AddObjectCommand( editor, amfobject ) ); | ||
164 | + | ||
165 | + }, false ); | ||
166 | + reader.readAsArrayBuffer( file ); | ||
167 | + | ||
168 | + break; | ||
169 | + | ||
170 | + } | ||
171 | + | ||
172 | + case 'dae': | ||
173 | + | ||
174 | + { | ||
175 | + | ||
176 | + reader.addEventListener( 'load', async function ( event ) { | ||
177 | + | ||
178 | + const contents = event.target.result; | ||
179 | + | ||
180 | + const { ColladaLoader } = await import( 'three/addons/loaders/ColladaLoader.js' ); | ||
181 | + | ||
182 | + const loader = new ColladaLoader( manager ); | ||
183 | + const collada = loader.parse( contents ); | ||
184 | + | ||
185 | + collada.scene.name = filename; | ||
186 | + | ||
187 | + editor.execute( new AddObjectCommand( editor, collada.scene ) ); | ||
188 | + | ||
189 | + }, false ); | ||
190 | + reader.readAsText( file ); | ||
191 | + | ||
192 | + break; | ||
193 | + | ||
194 | + } | ||
195 | + | ||
196 | + case 'drc': | ||
197 | + | ||
198 | + { | ||
199 | + | ||
200 | + reader.addEventListener( 'load', async function ( event ) { | ||
201 | + | ||
202 | + const contents = event.target.result; | ||
203 | + | ||
204 | + const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' ); | ||
205 | + | ||
206 | + const loader = new DRACOLoader(); | ||
207 | + loader.setDecoderPath( '../examples/jsm/libs/draco/' ); | ||
208 | + loader.parse( contents, function ( geometry ) { | ||
209 | + | ||
210 | + let object; | ||
211 | + | ||
212 | + if ( geometry.index !== null ) { | ||
213 | + | ||
214 | + const material = new THREE.MeshStandardMaterial(); | ||
215 | + | ||
216 | + object = new THREE.Mesh( geometry, material ); | ||
217 | + object.name = filename; | ||
218 | + | ||
219 | + } else { | ||
220 | + | ||
221 | + const material = new THREE.PointsMaterial( { size: 0.01 } ); | ||
222 | + material.vertexColors = geometry.hasAttribute( 'color' ); | ||
223 | + | ||
224 | + object = new THREE.Points( geometry, material ); | ||
225 | + object.name = filename; | ||
226 | + | ||
227 | + } | ||
228 | + | ||
229 | + loader.dispose(); | ||
230 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
231 | + | ||
232 | + } ); | ||
233 | + | ||
234 | + }, false ); | ||
235 | + reader.readAsArrayBuffer( file ); | ||
236 | + | ||
237 | + break; | ||
238 | + | ||
239 | + } | ||
240 | + | ||
241 | + case 'fbx': | ||
242 | + | ||
243 | + { | ||
244 | + | ||
245 | + reader.addEventListener( 'load', async function ( event ) { | ||
246 | + | ||
247 | + const contents = event.target.result; | ||
248 | + | ||
249 | + const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' ); | ||
250 | + | ||
251 | + const loader = new FBXLoader( manager ); | ||
252 | + const object = loader.parse( contents ); | ||
253 | + | ||
254 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
255 | + | ||
256 | + }, false ); | ||
257 | + reader.readAsArrayBuffer( file ); | ||
258 | + | ||
259 | + break; | ||
260 | + | ||
261 | + } | ||
262 | + | ||
263 | + case 'glb': | ||
264 | + | ||
265 | + { | ||
266 | + | ||
267 | + reader.addEventListener( 'load', async function ( event ) { | ||
268 | + | ||
269 | + const contents = event.target.result; | ||
270 | + | ||
271 | + const loader = await createGLTFLoader(); | ||
272 | + | ||
273 | + loader.parse( contents, '', function ( result ) { | ||
274 | + | ||
275 | + const scene = result.scene; | ||
276 | + scene.name = filename; | ||
277 | + | ||
278 | + scene.animations.push( ...result.animations ); | ||
279 | + editor.execute( new AddObjectCommand( editor, scene ) ); | ||
280 | + | ||
281 | + loader.dracoLoader.dispose(); | ||
282 | + loader.ktx2Loader.dispose(); | ||
283 | + | ||
284 | + } ); | ||
285 | + | ||
286 | + }, false ); | ||
287 | + reader.readAsArrayBuffer( file ); | ||
288 | + | ||
289 | + break; | ||
290 | + | ||
291 | + } | ||
292 | + | ||
293 | + case 'gltf': | ||
294 | + | ||
295 | + { | ||
296 | + | ||
297 | + reader.addEventListener( 'load', async function ( event ) { | ||
298 | + | ||
299 | + const contents = event.target.result; | ||
300 | + | ||
301 | + const loader = await createGLTFLoader( manager ); | ||
302 | + | ||
303 | + loader.parse( contents, '', function ( result ) { | ||
304 | + | ||
305 | + const scene = result.scene; | ||
306 | + scene.name = filename; | ||
307 | + | ||
308 | + scene.animations.push( ...result.animations ); | ||
309 | + editor.execute( new AddObjectCommand( editor, scene ) ); | ||
310 | + | ||
311 | + loader.dracoLoader.dispose(); | ||
312 | + loader.ktx2Loader.dispose(); | ||
313 | + | ||
314 | + } ); | ||
315 | + | ||
316 | + }, false ); | ||
317 | + reader.readAsArrayBuffer( file ); | ||
318 | + | ||
319 | + break; | ||
320 | + | ||
321 | + } | ||
322 | + | ||
323 | + case 'js': | ||
324 | + case 'json': | ||
325 | + | ||
326 | + { | ||
327 | + | ||
328 | + reader.addEventListener( 'load', function ( event ) { | ||
329 | + | ||
330 | + const contents = event.target.result; | ||
331 | + | ||
332 | + // 2.0 | ||
333 | + | ||
334 | + if ( contents.indexOf( 'postMessage' ) !== - 1 ) { | ||
335 | + | ||
336 | + const blob = new Blob( [ contents ], { type: 'text/javascript' } ); | ||
337 | + const url = URL.createObjectURL( blob ); | ||
338 | + | ||
339 | + const worker = new Worker( url ); | ||
340 | + | ||
341 | + worker.onmessage = function ( event ) { | ||
342 | + | ||
343 | + event.data.metadata = { version: 2 }; | ||
344 | + handleJSON( event.data ); | ||
345 | + | ||
346 | + }; | ||
347 | + | ||
348 | + worker.postMessage( Date.now() ); | ||
349 | + | ||
350 | + return; | ||
351 | + | ||
352 | + } | ||
353 | + | ||
354 | + // >= 3.0 | ||
355 | + | ||
356 | + let data; | ||
357 | + | ||
358 | + try { | ||
359 | + | ||
360 | + data = JSON.parse( contents ); | ||
361 | + | ||
362 | + } catch ( error ) { | ||
363 | + | ||
364 | + alert( error ); | ||
365 | + return; | ||
366 | + | ||
367 | + } | ||
368 | + | ||
369 | + handleJSON( data ); | ||
370 | + | ||
371 | + }, false ); | ||
372 | + reader.readAsText( file ); | ||
373 | + | ||
374 | + break; | ||
375 | + | ||
376 | + } | ||
377 | + | ||
378 | + case 'kmz': | ||
379 | + | ||
380 | + { | ||
381 | + | ||
382 | + reader.addEventListener( 'load', async function ( event ) { | ||
383 | + | ||
384 | + const { KMZLoader } = await import( 'three/addons/loaders/KMZLoader.js' ); | ||
385 | + | ||
386 | + const loader = new KMZLoader(); | ||
387 | + const collada = loader.parse( event.target.result ); | ||
388 | + | ||
389 | + collada.scene.name = filename; | ||
390 | + | ||
391 | + editor.execute( new AddObjectCommand( editor, collada.scene ) ); | ||
392 | + | ||
393 | + }, false ); | ||
394 | + reader.readAsArrayBuffer( file ); | ||
395 | + | ||
396 | + break; | ||
397 | + | ||
398 | + } | ||
399 | + | ||
400 | + case 'ldr': | ||
401 | + case 'mpd': | ||
402 | + | ||
403 | + { | ||
404 | + | ||
405 | + reader.addEventListener( 'load', async function ( event ) { | ||
406 | + | ||
407 | + const { LDrawLoader } = await import( 'three/addons/loaders/LDrawLoader.js' ); | ||
408 | + | ||
409 | + const loader = new LDrawLoader(); | ||
410 | + loader.setPath( '../../examples/models/ldraw/officialLibrary/' ); | ||
411 | + loader.parse( event.target.result, function ( group ) { | ||
412 | + | ||
413 | + group.name = filename; | ||
414 | + // Convert from LDraw coordinates: rotate 180 degrees around OX | ||
415 | + group.rotation.x = Math.PI; | ||
416 | + | ||
417 | + editor.execute( new AddObjectCommand( editor, group ) ); | ||
418 | + | ||
419 | + } ); | ||
420 | + | ||
421 | + }, false ); | ||
422 | + reader.readAsText( file ); | ||
423 | + | ||
424 | + break; | ||
425 | + | ||
426 | + } | ||
427 | + | ||
428 | + case 'md2': | ||
429 | + | ||
430 | + { | ||
431 | + | ||
432 | + reader.addEventListener( 'load', async function ( event ) { | ||
433 | + | ||
434 | + const contents = event.target.result; | ||
435 | + | ||
436 | + const { MD2Loader } = await import( 'three/addons/loaders/MD2Loader.js' ); | ||
437 | + | ||
438 | + const geometry = new MD2Loader().parse( contents ); | ||
439 | + const material = new THREE.MeshStandardMaterial(); | ||
440 | + | ||
441 | + const mesh = new THREE.Mesh( geometry, material ); | ||
442 | + mesh.mixer = new THREE.AnimationMixer( mesh ); | ||
443 | + mesh.name = filename; | ||
444 | + | ||
445 | + mesh.animations.push( ...geometry.animations ); | ||
446 | + editor.execute( new AddObjectCommand( editor, mesh ) ); | ||
447 | + | ||
448 | + }, false ); | ||
449 | + reader.readAsArrayBuffer( file ); | ||
450 | + | ||
451 | + break; | ||
452 | + | ||
453 | + } | ||
454 | + | ||
455 | + case 'obj': | ||
456 | + | ||
457 | + { | ||
458 | + | ||
459 | + reader.addEventListener( 'load', async function ( event ) { | ||
460 | + | ||
461 | + const contents = event.target.result; | ||
462 | + | ||
463 | + const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' ); | ||
464 | + | ||
465 | + const object = new OBJLoader().parse( contents ); | ||
466 | + object.name = filename; | ||
467 | + | ||
468 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
469 | + | ||
470 | + }, false ); | ||
471 | + reader.readAsText( file ); | ||
472 | + | ||
473 | + break; | ||
474 | + | ||
475 | + } | ||
476 | + | ||
477 | + case 'pcd': | ||
478 | + | ||
479 | + { | ||
480 | + | ||
481 | + reader.addEventListener( 'load', async function ( event ) { | ||
482 | + | ||
483 | + const contents = event.target.result; | ||
484 | + | ||
485 | + const { PCDLoader } = await import( 'three/addons/loaders/PCDLoader.js' ); | ||
486 | + | ||
487 | + const points = new PCDLoader().parse( contents ); | ||
488 | + points.name = filename; | ||
489 | + | ||
490 | + editor.execute( new AddObjectCommand( editor, points ) ); | ||
491 | + | ||
492 | + }, false ); | ||
493 | + reader.readAsArrayBuffer( file ); | ||
494 | + | ||
495 | + break; | ||
496 | + | ||
497 | + } | ||
498 | + | ||
499 | + case 'ply': | ||
500 | + | ||
501 | + { | ||
502 | + | ||
503 | + reader.addEventListener( 'load', async function ( event ) { | ||
504 | + | ||
505 | + const contents = event.target.result; | ||
506 | + | ||
507 | + const { PLYLoader } = await import( 'three/addons/loaders/PLYLoader.js' ); | ||
508 | + | ||
509 | + const geometry = new PLYLoader().parse( contents ); | ||
510 | + let object; | ||
511 | + | ||
512 | + if ( geometry.index !== null ) { | ||
513 | + | ||
514 | + const material = new THREE.MeshStandardMaterial(); | ||
515 | + | ||
516 | + object = new THREE.Mesh( geometry, material ); | ||
517 | + object.name = filename; | ||
518 | + | ||
519 | + } else { | ||
520 | + | ||
521 | + const material = new THREE.PointsMaterial( { size: 0.01 } ); | ||
522 | + material.vertexColors = geometry.hasAttribute( 'color' ); | ||
523 | + | ||
524 | + object = new THREE.Points( geometry, material ); | ||
525 | + object.name = filename; | ||
526 | + | ||
527 | + } | ||
528 | + | ||
529 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
530 | + | ||
531 | + }, false ); | ||
532 | + reader.readAsArrayBuffer( file ); | ||
533 | + | ||
534 | + break; | ||
535 | + | ||
536 | + } | ||
537 | + | ||
538 | + case 'stl': | ||
539 | + | ||
540 | + { | ||
541 | + | ||
542 | + reader.addEventListener( 'load', async function ( event ) { | ||
543 | + | ||
544 | + const contents = event.target.result; | ||
545 | + | ||
546 | + const { STLLoader } = await import( 'three/addons/loaders/STLLoader.js' ); | ||
547 | + | ||
548 | + const geometry = new STLLoader().parse( contents ); | ||
549 | + const material = new THREE.MeshStandardMaterial(); | ||
550 | + | ||
551 | + const mesh = new THREE.Mesh( geometry, material ); | ||
552 | + mesh.name = filename; | ||
553 | + | ||
554 | + editor.execute( new AddObjectCommand( editor, mesh ) ); | ||
555 | + | ||
556 | + }, false ); | ||
557 | + | ||
558 | + if ( reader.readAsBinaryString !== undefined ) { | ||
559 | + | ||
560 | + reader.readAsBinaryString( file ); | ||
561 | + | ||
562 | + } else { | ||
563 | + | ||
564 | + reader.readAsArrayBuffer( file ); | ||
565 | + | ||
566 | + } | ||
567 | + | ||
568 | + break; | ||
569 | + | ||
570 | + } | ||
571 | + | ||
572 | + case 'svg': | ||
573 | + | ||
574 | + { | ||
575 | + | ||
576 | + reader.addEventListener( 'load', async function ( event ) { | ||
577 | + | ||
578 | + const contents = event.target.result; | ||
579 | + | ||
580 | + const { SVGLoader } = await import( 'three/addons/loaders/SVGLoader.js' ); | ||
581 | + | ||
582 | + const loader = new SVGLoader(); | ||
583 | + const paths = loader.parse( contents ).paths; | ||
584 | + | ||
585 | + // | ||
586 | + | ||
587 | + const group = new THREE.Group(); | ||
588 | + group.name = filename; | ||
589 | + group.scale.multiplyScalar( 0.1 ); | ||
590 | + group.scale.y *= - 1; | ||
591 | + | ||
592 | + for ( let i = 0; i < paths.length; i ++ ) { | ||
593 | + | ||
594 | + const path = paths[ i ]; | ||
595 | + | ||
596 | + const material = new THREE.MeshBasicMaterial( { | ||
597 | + color: path.color, | ||
598 | + depthWrite: false | ||
599 | + } ); | ||
600 | + | ||
601 | + const shapes = SVGLoader.createShapes( path ); | ||
602 | + | ||
603 | + for ( let j = 0; j < shapes.length; j ++ ) { | ||
604 | + | ||
605 | + const shape = shapes[ j ]; | ||
606 | + | ||
607 | + const geometry = new THREE.ShapeGeometry( shape ); | ||
608 | + const mesh = new THREE.Mesh( geometry, material ); | ||
609 | + | ||
610 | + group.add( mesh ); | ||
611 | + | ||
612 | + } | ||
613 | + | ||
614 | + } | ||
615 | + | ||
616 | + editor.execute( new AddObjectCommand( editor, group ) ); | ||
617 | + | ||
618 | + }, false ); | ||
619 | + reader.readAsText( file ); | ||
620 | + | ||
621 | + break; | ||
622 | + | ||
623 | + } | ||
624 | + | ||
625 | + case 'usdz': | ||
626 | + | ||
627 | + { | ||
628 | + | ||
629 | + reader.addEventListener( 'load', async function ( event ) { | ||
630 | + | ||
631 | + const contents = event.target.result; | ||
632 | + | ||
633 | + const { USDZLoader } = await import( 'three/addons/loaders/USDZLoader.js' ); | ||
634 | + | ||
635 | + const group = new USDZLoader().parse( contents ); | ||
636 | + group.name = filename; | ||
637 | + | ||
638 | + editor.execute( new AddObjectCommand( editor, group ) ); | ||
639 | + | ||
640 | + }, false ); | ||
641 | + reader.readAsArrayBuffer( file ); | ||
642 | + | ||
643 | + break; | ||
644 | + | ||
645 | + } | ||
646 | + | ||
647 | + case 'vox': | ||
648 | + | ||
649 | + { | ||
650 | + | ||
651 | + reader.addEventListener( 'load', async function ( event ) { | ||
652 | + | ||
653 | + const contents = event.target.result; | ||
654 | + | ||
655 | + const { VOXLoader, VOXMesh } = await import( 'three/addons/loaders/VOXLoader.js' ); | ||
656 | + | ||
657 | + const chunks = new VOXLoader().parse( contents ); | ||
658 | + | ||
659 | + const group = new THREE.Group(); | ||
660 | + group.name = filename; | ||
661 | + | ||
662 | + for ( let i = 0; i < chunks.length; i ++ ) { | ||
663 | + | ||
664 | + const chunk = chunks[ i ]; | ||
665 | + | ||
666 | + const mesh = new VOXMesh( chunk ); | ||
667 | + group.add( mesh ); | ||
668 | + | ||
669 | + } | ||
670 | + | ||
671 | + editor.execute( new AddObjectCommand( editor, group ) ); | ||
672 | + | ||
673 | + }, false ); | ||
674 | + reader.readAsArrayBuffer( file ); | ||
675 | + | ||
676 | + break; | ||
677 | + | ||
678 | + } | ||
679 | + | ||
680 | + case 'vtk': | ||
681 | + case 'vtp': | ||
682 | + | ||
683 | + { | ||
684 | + | ||
685 | + reader.addEventListener( 'load', async function ( event ) { | ||
686 | + | ||
687 | + const contents = event.target.result; | ||
688 | + | ||
689 | + const { VTKLoader } = await import( 'three/addons/loaders/VTKLoader.js' ); | ||
690 | + | ||
691 | + const geometry = new VTKLoader().parse( contents ); | ||
692 | + const material = new THREE.MeshStandardMaterial(); | ||
693 | + | ||
694 | + const mesh = new THREE.Mesh( geometry, material ); | ||
695 | + mesh.name = filename; | ||
696 | + | ||
697 | + editor.execute( new AddObjectCommand( editor, mesh ) ); | ||
698 | + | ||
699 | + }, false ); | ||
700 | + reader.readAsArrayBuffer( file ); | ||
701 | + | ||
702 | + break; | ||
703 | + | ||
704 | + } | ||
705 | + | ||
706 | + case 'wrl': | ||
707 | + | ||
708 | + { | ||
709 | + | ||
710 | + reader.addEventListener( 'load', async function ( event ) { | ||
711 | + | ||
712 | + const contents = event.target.result; | ||
713 | + | ||
714 | + const { VRMLLoader } = await import( 'three/addons/loaders/VRMLLoader.js' ); | ||
715 | + | ||
716 | + const result = new VRMLLoader().parse( contents ); | ||
717 | + | ||
718 | + editor.execute( new AddObjectCommand( editor, result ) ); | ||
719 | + | ||
720 | + }, false ); | ||
721 | + reader.readAsText( file ); | ||
722 | + | ||
723 | + break; | ||
724 | + | ||
725 | + } | ||
726 | + | ||
727 | + case 'xyz': | ||
728 | + | ||
729 | + { | ||
730 | + | ||
731 | + reader.addEventListener( 'load', async function ( event ) { | ||
732 | + | ||
733 | + const contents = event.target.result; | ||
734 | + | ||
735 | + const { XYZLoader } = await import( 'three/addons/loaders/XYZLoader.js' ); | ||
736 | + | ||
737 | + const geometry = new XYZLoader().parse( contents ); | ||
738 | + | ||
739 | + const material = new THREE.PointsMaterial(); | ||
740 | + material.vertexColors = geometry.hasAttribute( 'color' ); | ||
741 | + | ||
742 | + const points = new THREE.Points( geometry, material ); | ||
743 | + points.name = filename; | ||
744 | + | ||
745 | + editor.execute( new AddObjectCommand( editor, points ) ); | ||
746 | + | ||
747 | + }, false ); | ||
748 | + reader.readAsText( file ); | ||
749 | + | ||
750 | + break; | ||
751 | + | ||
752 | + } | ||
753 | + | ||
754 | + case 'zip': | ||
755 | + | ||
756 | + { | ||
757 | + | ||
758 | + reader.addEventListener( 'load', function ( event ) { | ||
759 | + | ||
760 | + handleZIP( event.target.result ); | ||
761 | + | ||
762 | + }, false ); | ||
763 | + reader.readAsArrayBuffer( file ); | ||
764 | + | ||
765 | + break; | ||
766 | + | ||
767 | + } | ||
768 | + | ||
769 | + default: | ||
770 | + | ||
771 | + console.error( 'Unsupported file format (' + extension + ').' ); | ||
772 | + | ||
773 | + break; | ||
774 | + | ||
775 | + } | ||
776 | + | ||
777 | + }; | ||
778 | + | ||
779 | + function handleJSON( data ) { | ||
780 | + | ||
781 | + if ( data.metadata === undefined ) { // 2.0 | ||
782 | + | ||
783 | + data.metadata = { type: 'Geometry' }; | ||
784 | + | ||
785 | + } | ||
786 | + | ||
787 | + if ( data.metadata.type === undefined ) { // 3.0 | ||
788 | + | ||
789 | + data.metadata.type = 'Geometry'; | ||
790 | + | ||
791 | + } | ||
792 | + | ||
793 | + if ( data.metadata.formatVersion !== undefined ) { | ||
794 | + | ||
795 | + data.metadata.version = data.metadata.formatVersion; | ||
796 | + | ||
797 | + } | ||
798 | + | ||
799 | + switch ( data.metadata.type.toLowerCase() ) { | ||
800 | + | ||
801 | + case 'buffergeometry': | ||
802 | + | ||
803 | + { | ||
804 | + | ||
805 | + const loader = new THREE.BufferGeometryLoader(); | ||
806 | + const result = loader.parse( data ); | ||
807 | + | ||
808 | + const mesh = new THREE.Mesh( result ); | ||
809 | + | ||
810 | + editor.execute( new AddObjectCommand( editor, mesh ) ); | ||
811 | + | ||
812 | + break; | ||
813 | + | ||
814 | + } | ||
815 | + | ||
816 | + case 'geometry': | ||
817 | + | ||
818 | + console.error( 'Loader: "Geometry" is no longer supported.' ); | ||
819 | + | ||
820 | + break; | ||
821 | + | ||
822 | + case 'object': | ||
823 | + | ||
824 | + { | ||
825 | + | ||
826 | + const loader = new THREE.ObjectLoader(); | ||
827 | + loader.setResourcePath( scope.texturePath ); | ||
828 | + | ||
829 | + loader.parse( data, function ( result ) { | ||
830 | + | ||
831 | + editor.execute( new AddObjectCommand( editor, result ) ); | ||
832 | + | ||
833 | + } ); | ||
834 | + | ||
835 | + break; | ||
836 | + | ||
837 | + } | ||
838 | + | ||
839 | + case 'app': | ||
840 | + | ||
841 | + editor.fromJSON( data ); | ||
842 | + | ||
843 | + break; | ||
844 | + | ||
845 | + } | ||
846 | + | ||
847 | + } | ||
848 | + | ||
849 | + async function handleZIP( contents ) { | ||
850 | + | ||
851 | + const zip = unzipSync( new Uint8Array( contents ) ); | ||
852 | + | ||
853 | + const manager = new THREE.LoadingManager(); | ||
854 | + manager.setURLModifier( function ( url ) { | ||
855 | + | ||
856 | + const file = zip[ url ]; | ||
857 | + | ||
858 | + if ( file ) { | ||
859 | + | ||
860 | + console.log( 'Loading', url ); | ||
861 | + | ||
862 | + const blob = new Blob( [ file.buffer ], { type: 'application/octet-stream' } ); | ||
863 | + return URL.createObjectURL( blob ); | ||
864 | + | ||
865 | + } | ||
866 | + | ||
867 | + return url; | ||
868 | + | ||
869 | + } ); | ||
870 | + | ||
871 | + // Poly | ||
872 | + | ||
873 | + if ( zip[ 'model.obj' ] && zip[ 'materials.mtl' ] ) { | ||
874 | + | ||
875 | + const { MTLLoader } = await import( 'three/addons/loaders/MTLLoader.js' ); | ||
876 | + const { OBJLoader } = await import( 'three/addons/loaders/OBJLoader.js' ); | ||
877 | + | ||
878 | + const materials = new MTLLoader( manager ).parse( strFromU8( zip[ 'materials.mtl' ] ) ); | ||
879 | + const object = new OBJLoader().setMaterials( materials ).parse( strFromU8( zip[ 'model.obj' ] ) ); | ||
880 | + | ||
881 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
882 | + return; | ||
883 | + | ||
884 | + } | ||
885 | + | ||
886 | + // | ||
887 | + | ||
888 | + for ( const path in zip ) { | ||
889 | + | ||
890 | + const file = zip[ path ]; | ||
891 | + | ||
892 | + const extension = path.split( '.' ).pop().toLowerCase(); | ||
893 | + | ||
894 | + switch ( extension ) { | ||
895 | + | ||
896 | + case 'fbx': | ||
897 | + | ||
898 | + { | ||
899 | + | ||
900 | + const { FBXLoader } = await import( 'three/addons/loaders/FBXLoader.js' ); | ||
901 | + | ||
902 | + const loader = new FBXLoader( manager ); | ||
903 | + const object = loader.parse( file.buffer ); | ||
904 | + | ||
905 | + editor.execute( new AddObjectCommand( editor, object ) ); | ||
906 | + | ||
907 | + break; | ||
908 | + | ||
909 | + } | ||
910 | + | ||
911 | + case 'glb': | ||
912 | + | ||
913 | + { | ||
914 | + | ||
915 | + const loader = await createGLTFLoader(); | ||
916 | + | ||
917 | + loader.parse( file.buffer, '', function ( result ) { | ||
918 | + | ||
919 | + const scene = result.scene; | ||
920 | + | ||
921 | + scene.animations.push( ...result.animations ); | ||
922 | + editor.execute( new AddObjectCommand( editor, scene ) ); | ||
923 | + | ||
924 | + loader.dracoLoader.dispose(); | ||
925 | + loader.ktx2Loader.dispose(); | ||
926 | + | ||
927 | + } ); | ||
928 | + | ||
929 | + break; | ||
930 | + | ||
931 | + } | ||
932 | + | ||
933 | + case 'gltf': | ||
934 | + | ||
935 | + { | ||
936 | + | ||
937 | + const loader = await createGLTFLoader( manager ); | ||
938 | + | ||
939 | + loader.parse( strFromU8( file ), '', function ( result ) { | ||
940 | + | ||
941 | + const scene = result.scene; | ||
942 | + | ||
943 | + scene.animations.push( ...result.animations ); | ||
944 | + editor.execute( new AddObjectCommand( editor, scene ) ); | ||
945 | + | ||
946 | + loader.dracoLoader.dispose(); | ||
947 | + loader.ktx2Loader.dispose(); | ||
948 | + | ||
949 | + } ); | ||
950 | + | ||
951 | + break; | ||
952 | + | ||
953 | + } | ||
954 | + | ||
955 | + } | ||
956 | + | ||
957 | + } | ||
958 | + | ||
959 | + } | ||
960 | + | ||
961 | + async function createGLTFLoader( manager ) { | ||
962 | + | ||
963 | + const { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' ); | ||
964 | + const { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' ); | ||
965 | + const { KTX2Loader } = await import( 'three/addons/loaders/KTX2Loader.js' ); | ||
966 | + const { MeshoptDecoder } = await import( 'three/addons/libs/meshopt_decoder.module.js' ); | ||
967 | + | ||
968 | + const dracoLoader = new DRACOLoader(); | ||
969 | + dracoLoader.setDecoderPath( '../examples/jsm/libs/draco/gltf/' ); | ||
970 | + | ||
971 | + const ktx2Loader = new KTX2Loader( manager ); | ||
972 | + ktx2Loader.setTranscoderPath( '../examples/jsm/libs/basis/' ); | ||
973 | + | ||
974 | + editor.signals.rendererDetectKTX2Support.dispatch( ktx2Loader ); | ||
975 | + | ||
976 | + const loader = new GLTFLoader( manager ); | ||
977 | + loader.setDRACOLoader( dracoLoader ); | ||
978 | + loader.setKTX2Loader( ktx2Loader ); | ||
979 | + loader.setMeshoptDecoder( MeshoptDecoder ); | ||
980 | + | ||
981 | + return loader; | ||
982 | + | ||
983 | + } | ||
984 | + | ||
985 | +} | ||
986 | + | ||
987 | +export { Loader }; |
editor/js/LoaderUtils.js
0 → 100644
1 | +const LoaderUtils = { | ||
2 | + | ||
3 | + createFilesMap: function ( files ) { | ||
4 | + | ||
5 | + const map = {}; | ||
6 | + | ||
7 | + for ( let i = 0; i < files.length; i ++ ) { | ||
8 | + | ||
9 | + const file = files[ i ]; | ||
10 | + map[ file.name ] = file; | ||
11 | + | ||
12 | + } | ||
13 | + | ||
14 | + return map; | ||
15 | + | ||
16 | + }, | ||
17 | + | ||
18 | + getFilesFromItemList: function ( items, onDone ) { | ||
19 | + | ||
20 | + // TOFIX: setURLModifier() breaks when the file being loaded is not in root | ||
21 | + | ||
22 | + let itemsCount = 0; | ||
23 | + let itemsTotal = 0; | ||
24 | + | ||
25 | + const files = []; | ||
26 | + const filesMap = {}; | ||
27 | + | ||
28 | + function onEntryHandled() { | ||
29 | + | ||
30 | + itemsCount ++; | ||
31 | + | ||
32 | + if ( itemsCount === itemsTotal ) { | ||
33 | + | ||
34 | + onDone( files, filesMap ); | ||
35 | + | ||
36 | + } | ||
37 | + | ||
38 | + } | ||
39 | + | ||
40 | + function handleEntry( entry ) { | ||
41 | + | ||
42 | + if ( entry.isDirectory ) { | ||
43 | + | ||
44 | + const reader = entry.createReader(); | ||
45 | + reader.readEntries( function ( entries ) { | ||
46 | + | ||
47 | + for ( let i = 0; i < entries.length; i ++ ) { | ||
48 | + | ||
49 | + handleEntry( entries[ i ] ); | ||
50 | + | ||
51 | + } | ||
52 | + | ||
53 | + onEntryHandled(); | ||
54 | + | ||
55 | + } ); | ||
56 | + | ||
57 | + } else if ( entry.isFile ) { | ||
58 | + | ||
59 | + entry.file( function ( file ) { | ||
60 | + | ||
61 | + files.push( file ); | ||
62 | + | ||
63 | + filesMap[ entry.fullPath.slice( 1 ) ] = file; | ||
64 | + onEntryHandled(); | ||
65 | + | ||
66 | + } ); | ||
67 | + | ||
68 | + } | ||
69 | + | ||
70 | + itemsTotal ++; | ||
71 | + | ||
72 | + } | ||
73 | + | ||
74 | + for ( let i = 0; i < items.length; i ++ ) { | ||
75 | + | ||
76 | + const item = items[ i ]; | ||
77 | + | ||
78 | + if ( item.kind === 'file' ) { | ||
79 | + | ||
80 | + handleEntry( item.webkitGetAsEntry() ); | ||
81 | + | ||
82 | + } | ||
83 | + | ||
84 | + } | ||
85 | + | ||
86 | + } | ||
87 | + | ||
88 | +}; | ||
89 | + | ||
90 | +export { LoaderUtils }; |