Commit c8912c2ba1e131892b650bd7ccf3249603ec1d12

Authored by xp.Huang
2 parents 754942e4 4f6474b8

Merge branch 'main_dev' into 'main'

Main dev

See merge request yunteng/thingskit-view!316

Too many changes to show.

To preserve performance only 27 of 234 files are displayed.

... ... @@ -3,5 +3,6 @@ node_modules
3 3 dist
4 4 dist-ssr
5 5 *.local
  6 +*.zip
6 7 .vscode
7 8 .idea
... ...
  1 +FROM nginx:latest
  2 +
  3 +COPY ./dist/ /usr/share/nginx/html/
  4 +COPY default.conf /etc/nginx/conf.d/default.conf
  5 +
  6 +EXPOSE 8084
... ...
1   -import path from 'path'
  1 +import path, { resolve } from 'path'
2 2 import { BuildOptions } from 'vite'
3 3 export const OUTPUT_DIR = 'dist'
4 4
... ... @@ -13,6 +13,10 @@ export const brotliSize = false
13 13
14 14 // 分包
15 15 export const rollupOptions: BuildOptions['rollupOptions'] = {
  16 + input: {
  17 + main: resolve(__dirname, '../index.html'),
  18 + editor: resolve(__dirname, '../editor/index.html'),
  19 + },
16 20 output: {
17 21 chunkFileNames: 'static/js/[name]-[hash].js',
18 22 entryFileNames: 'static/js/[name]-[hash].js',
... ...
1   -import { createHtmlPlugin } from 'vite-plugin-html'
  1 +import { HtmlTagDescriptor, Plugin } from 'vite'
  2 +// import { createHtmlPlugin } from 'vite-plugin-html'
2 3
3 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 7 const { VITE_GLOB_APP_TITLE, VITE_GLOB_PUBLIC_PATH } = env
7 8 const getAppConfigSrc = () => {
8 9 const path = VITE_GLOB_PUBLIC_PATH
9 10 return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${Date.now()}`
10 11 }
11 12
12   - const htmlPlugin = createHtmlPlugin({
13   - minify: isBuild,
14   - inject: {
15   - data: {
16   - title: VITE_GLOB_APP_TITLE,
17   - },
18   - tags: isBuild ? [
19   - {
20   - tag: 'script',
21   - attrs: { src: getAppConfigSrc() }
22   - }
23   - ] : []
  13 + // const htmlPlugin = createHtmlPlugin({
  14 + // minify: isBuild,
  15 + // inject: {
  16 + // data: {
  17 + // title: VITE_GLOB_APP_TITLE,
  18 + // },
  19 + // tags: isBuild ? [
  20 + // {
  21 + // tag: 'script',
  22 + // attrs: { src: getAppConfigSrc() }
  23 + // }
  24 + // ] : []
  25 + // }
  26 + // })
  27 +
  28 +
  29 +
  30 + const tags:HtmlTagDescriptor[] = [
  31 + {
  32 + tag: 'title',
  33 + children: VITE_GLOB_APP_TITLE
24 34 }
25   - })
  35 + ]
26 36
27   - return htmlPlugin
  37 + if (isBuild) {
  38 + tags.push(
  39 + {
  40 + tag: 'script',
  41 + attrs: { src: getAppConfigSrc() }
  42 + }
  43 + )
  44 + }
  45 + return {
  46 + name: 'html-plugin',
  47 + transformIndexHtml: (code, ctx) => {
  48 +
  49 + return {
  50 + html: code,
  51 + tags
  52 + }
  53 + },
  54 +
  55 + }
28 56 }
... ...
  1 +server {
  2 + listen 80;
  3 + listen [::]:80;
  4 + server_name 192.168.1.48;
  5 + #charset koi8-r;
  6 + #access_log /var/log/nginx/host.access.log main;
  7 +
  8 + root /usr/share/nginx/html;
  9 + index index.html index.htm;
  10 + try_files $uri $uri/ /index.html;
  11 +
  12 +
  13 +
  14 + location /api/ {
  15 + proxy_pass http://192.168.1.48:8080;
  16 + proxy_http_version 1.1;
  17 + proxy_set_header Upgrade $http_upgrade;
  18 + proxy_set_header Connection "Upgrade";
  19 + proxy_set_header Host $host;
  20 + proxy_cache_bypass $http_upgrade;
  21 + }
  22 +
  23 + location /yt/ {
  24 + proxy_pass http://192.168.1.48:8080;
  25 + proxy_http_version 1.1;
  26 + proxy_set_header Upgrade $http_upgrade;
  27 + proxy_set_header Connection "Upgrade";
  28 + proxy_set_header Host $host;
  29 + proxy_cache_bypass $http_upgrade;
  30 + }
  31 +
  32 + location /upload/ {
  33 + proxy_pass http://192.168.1.48:8080;
  34 + proxy_http_version 1.1;
  35 + proxy_set_header Upgrade $http_upgrade;
  36 + proxy_set_header Connection "Upgrade";
  37 + proxy_set_header Host $host;
  38 + proxy_cache_bypass $http_upgrade;
  39 + }
  40 +
  41 + #error_page 404 /404.html;
  42 +
  43 + # redirect server error pages to the static page /50x.html
  44 + #
  45 + error_page 500 502 503 504 /50x.html;
  46 + location = /50x.html {
  47 + root /usr/share/nginx/html;
  48 + }
  49 +}
\ No newline at end of file
... ...
  1 +{
  2 + "extends": [
  3 + "../.eslintrc.json"
  4 + ],
  5 + "parserOptions": {
  6 + "sourceType": "module",
  7 + "ecmaVersion": 2020
  8 + }
  9 +}
\ No newline at end of file
... ...
  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.
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
No preview for this file type
  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>
... ...
  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>
... ...
  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>
... ...
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 + <head>
  4 + <title>ThingsKit 3D Model Editor</title>
  5 + <meta charset="utf-8" />
  6 + <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
  7 + </head>
  8 +
  9 + <body>
  10 + <link rel="stylesheet" href="css/main.css" />
  11 + <link rel="stylesheet" href="./js/libs/spin/spin.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 + <script type="module" src="./jsm/libs/draco/draco_encoder.js"></script>
  18 + <script type="module" src="./js/libs/codemirror/codemirror.js"></script>
  19 + <script type="module" src="./js/libs/codemirror/mode/javascript.js"></script>
  20 + <script type="module" src="./js/libs/codemirror/mode/glsl.js"></script>
  21 + <script type="module" src="./js/libs/esprima.js"></script>
  22 + <script type="module" src="./js/libs/jsonlint.js"></script>
  23 + <script type="module" src="./js/libs/ffmpeg/ffmpeg.min.js"></script>
  24 + <script type="module" src="./js/libs/codemirror/addon/dialog.js"></script>
  25 + <script type="module" src="./js/libs/codemirror/addon/show-hint.js"></script>
  26 + <script type="module" src="./js/libs/codemirror/addon/tern.js"></script>
  27 + <script type="module" src="./js/libs/acorn/acorn.js"></script>
  28 + <script type="module" src="./js/libs/acorn/acorn_loose.js"></script>
  29 + <script type="module" src="./js/libs/acorn/walk.js"></script>
  30 + <script type="module" src="./js/libs/ternjs/polyfill.js"></script>
  31 + <script type="module" src="./js/libs/ternjs/signal.js"></script>
  32 + <script type="module" src="./js/libs/ternjs/tern.js"></script>
  33 + <script type="module" src="./js/libs/ternjs/def.js"></script>
  34 + <script type="module" src="./js/libs/ternjs/comment.js"></script>
  35 + <script type="module" src="./js/libs/ternjs/infer.js"></script>
  36 + <script type="module" src="./js/libs/ternjs/doc_comment.js"></script>
  37 + <script type="module" src="./js/libs/tern-threejs/threejs.js"></script>
  38 + <script type="module" src="./js/libs/signals.min.js"></script>
  39 + <script type="module" src="./main.js"></script>
  40 + </body>
  41 +</html>
... ...
  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 };
... ...
  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 };
... ...
  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 + this.history.execute(cmd, optionalName);
  724 + console.log(this)
  725 + this.saveTooltip?.show?.()
  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 };
... ...
  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 };
... ...
  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 };
... ...
  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 };
\ No newline at end of file
... ...
  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 };
... ...