├── .gitattributes
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .nojekyll
├── LICENSE
├── README.md
├── assets
├── normalize.css
└── skeleton.css
├── index.html
├── jsconfig.json
├── node_modules
└── three
│ └── examples
│ └── jsm
│ └── controls
│ ├── ArcballControls.js
│ ├── DragControls.js
│ ├── FirstPersonControls.js
│ ├── FlyControls.js
│ ├── MapControls.js
│ ├── OrbitControls.js
│ ├── PointerLockControls.js
│ ├── TrackballControls.js
│ └── TransformControls.js
├── package-lock.json
├── package.json
└── src
├── TimeResampler.js
├── World.js
└── main.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | # Ideally this makes the next step run faster
35 | # But I haven't noticed a speed difference
36 | # May want to cache the ./node_modules directory directly...
37 | - name: Cache Node Modules
38 | uses: actions/cache@v2
39 | env:
40 | cache-name: cache-node-modules
41 | with:
42 | # npm cache files are stored in `~/.npm` on Linux/macOS
43 | path: ~/.npm
44 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
45 | restore-keys: |
46 | ${{ runner.os }}-build-${{ env.cache-name }}-
47 | ${{ runner.os }}-build-
48 | ${{ runner.os }}-
49 | - name: Install Node Modules
50 | run: npm install
51 | # Switching the HTML entrypoint over to the build
52 | - name: Pre-Process Site
53 | run: |
54 | sed -e 's/\/src\//\/build\//g' index.html > index.html.tmp
55 | mv index.html.tmp index.html
56 | - name: Run esbuild
57 | run: npm run build
58 | - name: Setup Pages
59 | uses: actions/configure-pages@v4
60 | - name: Upload artifact
61 | uses: actions/upload-pages-artifact@v3
62 | with:
63 | # Upload entire repository
64 | path: '.'
65 | - name: Deploy to GitHub Pages
66 | id: deployment
67 | uses: actions/deploy-pages@v4
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/**/*
37 | jspm_packages/
38 |
39 | # Three.js Build
40 | !node_modules/three/
41 | !node_modules/three/*/
42 | !node_modules/three/build/*
43 | # Three.js Controls
44 | !node_modules/three/examples/*/
45 | !node_modules/three/examples/jsm/
46 | !node_modules/three/examples/jsm/*/
47 | !node_modules/three/examples/jsm/controls/
48 | !node_modules/three/examples/jsm/controls/*
49 |
50 | # TypeScript v1 declaration files
51 | typings/
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Optional REPL history
60 | .node_repl_history
61 |
62 | # Output of 'npm pack'
63 | *.tgz
64 |
65 | # Yarn Integrity file
66 | .yarn-integrity
67 |
68 | # dotenv environment variables file
69 | .env
70 |
71 | # parcel-bundler cache (https://parceljs.org/)
72 | .cache
73 |
74 | # next.js build output
75 | .next
76 |
77 | # nuxt.js build output
78 | .nuxt
79 |
80 | # vuepress build output
81 | .vuepress/dist
82 |
83 | # Serverless directories
84 | .serverless
85 |
86 | # FuseBox cache
87 | .fusebox/
88 |
89 | [Bb]uild
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zalo/ThreeOverlay/de56e643e1ec9da7e3b8b418c8f6f8d5e4e729bc/.nojekyll
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Johnathon Selstad
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [ThreeOverlay - WIP](https://zalo.github.io/ThreeOverlay/)
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 | Framework for Rendering 3D Content Inline with Normal Text Content for a more whimsical internet.
13 |
14 | # Building
15 |
16 | This demo can either be run without building (in Chrome/Edge/Opera since raw three.js examples need [Import Maps](https://caniuse.com/import-maps)), or built with:
17 | ```
18 | npm install
19 | npm run build
20 | ```
21 | After building, make sure to edit the index .html to point from `"./src/main.js"` to `"./build/main.js"`.
22 |
23 | # Dependencies
24 | - [three.js](https://github.com/mrdoob/three.js/) (3D Rendering Engine)
25 | - [esbuild](https://github.com/evanw/esbuild/) (Bundler)
26 |
--------------------------------------------------------------------------------
/assets/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | -moz-box-sizing: content-box;
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
354 | * (include `-moz` to future-proof).
355 | */
356 |
357 | input[type="search"] {
358 | -webkit-appearance: textfield; /* 1 */
359 | -moz-box-sizing: content-box;
360 | -webkit-box-sizing: content-box; /* 2 */
361 | box-sizing: content-box;
362 | }
363 |
364 | /**
365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
366 | * Safari (but not Chrome) clips the cancel button when the search input has
367 | * padding (and `textfield` appearance).
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Define consistent border, margin, and padding.
377 | */
378 |
379 | fieldset {
380 | border: 1px solid #c0c0c0;
381 | margin: 0 2px;
382 | padding: 0.35em 0.625em 0.75em;
383 | }
384 |
385 | /**
386 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
388 | */
389 |
390 | legend {
391 | border: 0; /* 1 */
392 | padding: 0; /* 2 */
393 | }
394 |
395 | /**
396 | * Remove default vertical scrollbar in IE 8/9/10/11.
397 | */
398 |
399 | textarea {
400 | overflow: auto;
401 | }
402 |
403 | /**
404 | * Don't inherit the `font-weight` (applied by a rule above).
405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
406 | */
407 |
408 | optgroup {
409 | font-weight: bold;
410 | }
411 |
412 | /* Tables
413 | ========================================================================== */
414 |
415 | /**
416 | * Remove most spacing between table cells.
417 | */
418 |
419 | table {
420 | border-collapse: collapse;
421 | border-spacing: 0;
422 | }
423 |
424 | td,
425 | th {
426 | padding: 0;
427 | }
--------------------------------------------------------------------------------
/assets/skeleton.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Skeleton V2.0.4
3 | * Copyright 2014, Dave Gamache
4 | * www.getskeleton.com
5 | * Free to use under the MIT license.
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * 12/29/2014
8 | */
9 |
10 |
11 | /* Table of contents
12 | ––––––––––––––––––––––––––––––––––––––––––––––––––
13 | - Grid
14 | - Base Styles
15 | - Typography
16 | - Links
17 | - Buttons
18 | - Forms
19 | - Lists
20 | - Code
21 | - Tables
22 | - Spacing
23 | - Utilities
24 | - Clearing
25 | - Media Queries
26 | */
27 |
28 |
29 | /* Grid
30 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
31 | .container {
32 | position: relative;
33 | width: 100%;
34 | max-width: 960px;
35 | margin: 0 auto;
36 | padding: 0 20px;
37 | box-sizing: border-box; }
38 | .column,
39 | .columns {
40 | width: 100%;
41 | float: left;
42 | box-sizing: border-box; }
43 |
44 | /* For devices larger than 400px */
45 | @media (min-width: 400px) {
46 | .container {
47 | width: 85%;
48 | padding: 0; }
49 | }
50 |
51 | /* For devices larger than 550px */
52 | @media (min-width: 550px) {
53 | .container {
54 | width: 80%; }
55 | .column,
56 | .columns {
57 | margin-left: 4%; }
58 | .column:first-child,
59 | .columns:first-child {
60 | margin-left: 0; }
61 |
62 | .one.column,
63 | .one.columns { width: 4.66666666667%; }
64 | .two.columns { width: 13.3333333333%; }
65 | .three.columns { width: 22%; }
66 | .four.columns { width: 30.6666666667%; }
67 | .five.columns { width: 39.3333333333%; }
68 | .six.columns { width: 48%; }
69 | .seven.columns { width: 56.6666666667%; }
70 | .eight.columns { width: 65.3333333333%; }
71 | .nine.columns { width: 74.0%; }
72 | .ten.columns { width: 82.6666666667%; }
73 | .eleven.columns { width: 91.3333333333%; }
74 | .twelve.columns { width: 100%; margin-left: 0; }
75 |
76 | .one-third.column { width: 30.6666666667%; }
77 | .two-thirds.column { width: 65.3333333333%; }
78 |
79 | .one-half.column { width: 48%; }
80 |
81 | /* Offsets */
82 | .offset-by-one.column,
83 | .offset-by-one.columns { margin-left: 8.66666666667%; }
84 | .offset-by-two.column,
85 | .offset-by-two.columns { margin-left: 17.3333333333%; }
86 | .offset-by-three.column,
87 | .offset-by-three.columns { margin-left: 26%; }
88 | .offset-by-four.column,
89 | .offset-by-four.columns { margin-left: 34.6666666667%; }
90 | .offset-by-five.column,
91 | .offset-by-five.columns { margin-left: 43.3333333333%; }
92 | .offset-by-six.column,
93 | .offset-by-six.columns { margin-left: 52%; }
94 | .offset-by-seven.column,
95 | .offset-by-seven.columns { margin-left: 60.6666666667%; }
96 | .offset-by-eight.column,
97 | .offset-by-eight.columns { margin-left: 69.3333333333%; }
98 | .offset-by-nine.column,
99 | .offset-by-nine.columns { margin-left: 78.0%; }
100 | .offset-by-ten.column,
101 | .offset-by-ten.columns { margin-left: 86.6666666667%; }
102 | .offset-by-eleven.column,
103 | .offset-by-eleven.columns { margin-left: 95.3333333333%; }
104 |
105 | .offset-by-one-third.column,
106 | .offset-by-one-third.columns { margin-left: 34.6666666667%; }
107 | .offset-by-two-thirds.column,
108 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
109 |
110 | .offset-by-one-half.column,
111 | .offset-by-one-half.columns { margin-left: 52%; }
112 |
113 | }
114 |
115 |
116 | /* Base Styles
117 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
118 | /* NOTE
119 | html is set to 62.5% so that all the REM measurements throughout Skeleton
120 | are based on 10px sizing. So basically 1.5rem = 15px :) */
121 | html {
122 | font-size: 62.5%; }
123 | body {
124 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
125 | line-height: 1.6;
126 | font-weight: 400;
127 | font-display: swap;
128 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
129 | color: #222; }
130 |
131 |
132 | /* Typography
133 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
134 | h1, h2, h3, h4, h5, h6 {
135 | margin-top: 0;
136 | margin-bottom: 2rem;
137 | font-weight: 300; }
138 | h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
139 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
140 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
141 | h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
142 | h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
143 | h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
144 |
145 | /* Larger than phablet */
146 | @media (min-width: 550px) {
147 | h1 { font-size: 5.0rem; }
148 | h2 { font-size: 4.2rem; }
149 | h3 { font-size: 3.6rem; }
150 | h4 { font-size: 3.0rem; }
151 | h5 { font-size: 2.4rem; }
152 | h6 { font-size: 1.5rem; }
153 | }
154 |
155 | p {
156 | margin-top: 0; }
157 |
158 |
159 | /* Links
160 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
161 | a {
162 | color: #1EAEDB; }
163 | a:hover {
164 | color: #0FA0CE; }
165 |
166 |
167 | /* Buttons
168 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
169 | .button,
170 | button,
171 | input[type="submit"],
172 | input[type="reset"],
173 | input[type="button"] {
174 | display: inline-block;
175 | height: 38px;
176 | padding: 0 30px;
177 | color: #555;
178 | text-align: center;
179 | font-size: 11px;
180 | font-weight: 600;
181 | line-height: 38px;
182 | letter-spacing: .1rem;
183 | text-transform: uppercase;
184 | text-decoration: none;
185 | white-space: nowrap;
186 | background-color: transparent;
187 | border-radius: 4px;
188 | border: 1px solid #bbb;
189 | cursor: pointer;
190 | box-sizing: border-box; }
191 | .button:hover,
192 | button:hover,
193 | input[type="submit"]:hover,
194 | input[type="reset"]:hover,
195 | input[type="button"]:hover,
196 | .button:focus,
197 | button:focus,
198 | input[type="submit"]:focus,
199 | input[type="reset"]:focus,
200 | input[type="button"]:focus {
201 | color: #333;
202 | border-color: #888;
203 | outline: 0; }
204 | .button.button-primary,
205 | button.button-primary,
206 | input[type="submit"].button-primary,
207 | input[type="reset"].button-primary,
208 | input[type="button"].button-primary {
209 | color: #FFF;
210 | background-color: #33C3F0;
211 | border-color: #33C3F0; }
212 | .button.button-primary:hover,
213 | button.button-primary:hover,
214 | input[type="submit"].button-primary:hover,
215 | input[type="reset"].button-primary:hover,
216 | input[type="button"].button-primary:hover,
217 | .button.button-primary:focus,
218 | button.button-primary:focus,
219 | input[type="submit"].button-primary:focus,
220 | input[type="reset"].button-primary:focus,
221 | input[type="button"].button-primary:focus {
222 | color: #FFF;
223 | background-color: #1EAEDB;
224 | border-color: #1EAEDB; }
225 |
226 |
227 | /* Forms
228 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
229 | input[type="email"],
230 | input[type="number"],
231 | input[type="search"],
232 | input[type="text"],
233 | input[type="tel"],
234 | input[type="url"],
235 | input[type="password"],
236 | textarea,
237 | select {
238 | height: 38px;
239 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
240 | background-color: #fff;
241 | border: 1px solid #D1D1D1;
242 | border-radius: 4px;
243 | box-shadow: none;
244 | box-sizing: border-box; }
245 | /* Removes awkward default styles on some inputs for iOS */
246 | input[type="email"],
247 | input[type="number"],
248 | input[type="search"],
249 | input[type="text"],
250 | input[type="tel"],
251 | input[type="url"],
252 | input[type="password"],
253 | textarea {
254 | -webkit-appearance: none;
255 | -moz-appearance: none;
256 | appearance: none; }
257 | textarea {
258 | min-height: 65px;
259 | padding-top: 6px;
260 | padding-bottom: 6px; }
261 | input[type="email"]:focus,
262 | input[type="number"]:focus,
263 | input[type="search"]:focus,
264 | input[type="text"]:focus,
265 | input[type="tel"]:focus,
266 | input[type="url"]:focus,
267 | input[type="password"]:focus,
268 | textarea:focus,
269 | select:focus {
270 | border: 1px solid #33C3F0;
271 | outline: 0; }
272 | label,
273 | legend {
274 | display: block;
275 | margin-bottom: .5rem;
276 | font-weight: 600; }
277 | fieldset {
278 | padding: 0;
279 | border-width: 0; }
280 | input[type="checkbox"],
281 | input[type="radio"] {
282 | display: inline; }
283 | label > .label-body {
284 | display: inline-block;
285 | margin-left: .5rem;
286 | font-weight: normal; }
287 |
288 |
289 | /* Lists
290 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
291 | ul {
292 | list-style: circle inside; }
293 | ol {
294 | list-style: decimal inside; }
295 | ol, ul {
296 | padding-left: 0;
297 | margin-top: 0; }
298 | ul ul,
299 | ul ol,
300 | ol ol,
301 | ol ul {
302 | margin: 1.5rem 0 1.5rem 3rem;
303 | font-size: 90%; }
304 | li {
305 | margin-bottom: 1rem; }
306 |
307 |
308 | /* Code
309 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
310 | code {
311 | padding: .2rem .5rem;
312 | margin: 0 .2rem;
313 | font-size: 90%;
314 | white-space: nowrap;
315 | background: #F1F1F1;
316 | border: 1px solid #E1E1E1;
317 | border-radius: 4px; }
318 | pre > code {
319 | display: block;
320 | padding: 1rem 1.5rem;
321 | white-space: pre; }
322 |
323 |
324 | /* Tables
325 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
326 | th,
327 | td {
328 | padding: 12px 15px;
329 | text-align: left;
330 | border-bottom: 1px solid #E1E1E1; }
331 | th:first-child,
332 | td:first-child {
333 | padding-left: 0; }
334 | th:last-child,
335 | td:last-child {
336 | padding-right: 0; }
337 |
338 |
339 | /* Spacing
340 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
341 | button,
342 | .button {
343 | margin-bottom: 1rem; }
344 | input,
345 | textarea,
346 | select,
347 | fieldset {
348 | margin-bottom: 1.5rem; }
349 | pre,
350 | blockquote,
351 | dl,
352 | figure,
353 | table,
354 | p,
355 | ul,
356 | ol,
357 | form {
358 | margin-bottom: 2.5rem; }
359 |
360 |
361 | /* Utilities
362 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
363 | .u-full-width {
364 | width: 100%;
365 | box-sizing: border-box; }
366 | .u-max-full-width {
367 | max-width: 100%;
368 | box-sizing: border-box; }
369 | .u-pull-right {
370 | float: right; }
371 | .u-pull-left {
372 | float: left; }
373 |
374 |
375 | /* Misc
376 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
377 | hr {
378 | margin-top: 3rem;
379 | margin-bottom: 3.5rem;
380 | border-width: 0;
381 | border-top: 1px solid #E1E1E1; }
382 |
383 |
384 | /* Clearing
385 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
386 |
387 | /* Self Clearing Goodness */
388 | .container:after,
389 | .row:after,
390 | .u-cf {
391 | content: "";
392 | display: table;
393 | clear: both; }
394 |
395 |
396 | /* Media Queries
397 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
398 | /*
399 | Note: The best way to structure the use of media queries is to create the queries
400 | near the relevant code. For example, if you wanted to change the styles for buttons
401 | on small devices, paste the mobile query code up in the buttons section and style it
402 | there.
403 | */
404 |
405 |
406 | /* Larger than mobile */
407 | @media (min-width: 400px) {}
408 |
409 | /* Larger than phablet (also point when grid becomes active) */
410 | @media (min-width: 550px) {}
411 |
412 | /* Larger than tablet */
413 | @media (min-width: 750px) {}
414 |
415 | /* Larger than desktop */
416 | @media (min-width: 1000px) {}
417 |
418 | /* Larger than Desktop HD */
419 | @media (min-width: 1200px) {}
420 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 | ThreeOverlay Demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
49 |
50 |
51 |
ThreeOverlay Demo
52 |
ThreeOverlay is a system that overlays a 3D rendering canvas onto web pages.
53 |
Instructions
54 |
Download the template and modify it to create your ideal webpage ornaments and buddies.
55 |
How it works
56 |
ThreeOverlay creates a transparent canvas that spans your webpage, allowing you to render dynamic 3D elements inline with your text.
57 |
Specific locations and features of your page will be injected into the 3D space as interactable elements via a bidirectional mapping.
58 |
Keymappings
59 |
60 |
61 |
62 | Controller Button |
63 | Keypress |
64 |
65 |
66 |
67 |
68 | Up |
69 | w |
70 |
71 |
72 | Left |
73 | a |
74 |
75 |
76 | Down |
77 | s |
78 |
79 |
80 | Right |
81 | d |
82 |
83 |
84 | A |
85 | Space |
86 |
87 |
88 | B |
89 | Shift |
90 |
91 |
92 | Start |
93 | Enter |
94 |
95 |
96 |
97 |
These keymappings aren't used, but provide extra flavor to this page.
98 |
Open Source
99 |
The source code to ThreeOverlay is available under the MIT License at Github.
100 |
Credits
101 |
ThreeOverlay is based on three.js, and Skeleton.css.
102 |
Raw Events
103 |
ThreeOverlay Events will appear here upon connection.
104 |
105 |
106 |
107 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "es6",
4 | "target": "es6"
5 | },
6 | "include": ["src/**/*"],
7 | "typeAcquisition": {
8 | "include": [
9 | "three"
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/DragControls.js:
--------------------------------------------------------------------------------
1 | import {
2 | EventDispatcher,
3 | Matrix4,
4 | Plane,
5 | Raycaster,
6 | Vector2,
7 | Vector3
8 | } from 'three';
9 |
10 | const _plane = new Plane();
11 | const _raycaster = new Raycaster();
12 |
13 | const _pointer = new Vector2();
14 | const _offset = new Vector3();
15 | const _diff = new Vector2();
16 | const _previousPointer = new Vector2();
17 | const _intersection = new Vector3();
18 | const _worldPosition = new Vector3();
19 | const _inverseMatrix = new Matrix4();
20 |
21 | const _up = new Vector3();
22 | const _right = new Vector3();
23 |
24 | class DragControls extends EventDispatcher {
25 |
26 | constructor( _objects, _camera, _domElement ) {
27 |
28 | super();
29 |
30 | _domElement.style.touchAction = 'none'; // disable touch scroll
31 |
32 | let _selected = null, _hovered = null;
33 |
34 | const _intersections = [];
35 |
36 | this.mode = 'translate';
37 |
38 | this.rotateSpeed = 1;
39 |
40 | //
41 |
42 | const scope = this;
43 |
44 | function activate() {
45 |
46 | _domElement.addEventListener( 'pointermove', onPointerMove );
47 | _domElement.addEventListener( 'pointerdown', onPointerDown );
48 | _domElement.addEventListener( 'pointerup', onPointerCancel );
49 | _domElement.addEventListener( 'pointerleave', onPointerCancel );
50 |
51 | }
52 |
53 | function deactivate() {
54 |
55 | _domElement.removeEventListener( 'pointermove', onPointerMove );
56 | _domElement.removeEventListener( 'pointerdown', onPointerDown );
57 | _domElement.removeEventListener( 'pointerup', onPointerCancel );
58 | _domElement.removeEventListener( 'pointerleave', onPointerCancel );
59 |
60 | _domElement.style.cursor = '';
61 |
62 | }
63 |
64 | function dispose() {
65 |
66 | deactivate();
67 |
68 | }
69 |
70 | function getObjects() {
71 |
72 | return _objects;
73 |
74 | }
75 |
76 | function setObjects( objects ) {
77 |
78 | _objects = objects;
79 |
80 | }
81 |
82 | function getRaycaster() {
83 |
84 | return _raycaster;
85 |
86 | }
87 |
88 | function onPointerMove( event ) {
89 |
90 | if ( scope.enabled === false ) return;
91 |
92 | updatePointer( event );
93 |
94 | _raycaster.setFromCamera( _pointer, _camera );
95 |
96 | if ( _selected ) {
97 |
98 | if ( scope.mode === 'translate' ) {
99 |
100 | if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
101 |
102 | _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
103 |
104 | }
105 |
106 | } else if ( scope.mode === 'rotate' ) {
107 |
108 | _diff.subVectors( _pointer, _previousPointer ).multiplyScalar( scope.rotateSpeed );
109 | _selected.rotateOnWorldAxis( _up, _diff.x );
110 | _selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );
111 |
112 | }
113 |
114 | scope.dispatchEvent( { type: 'drag', object: _selected } );
115 |
116 | _previousPointer.copy( _pointer );
117 |
118 | } else {
119 |
120 | // hover support
121 |
122 | if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
123 |
124 | _intersections.length = 0;
125 |
126 | _raycaster.setFromCamera( _pointer, _camera );
127 | _raycaster.intersectObjects( _objects, scope.recursive, _intersections );
128 |
129 | if ( _intersections.length > 0 ) {
130 |
131 | const object = _intersections[ 0 ].object;
132 |
133 | _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
134 |
135 | if ( _hovered !== object && _hovered !== null ) {
136 |
137 | scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
138 |
139 | _domElement.style.cursor = 'auto';
140 | _hovered = null;
141 |
142 | }
143 |
144 | if ( _hovered !== object ) {
145 |
146 | scope.dispatchEvent( { type: 'hoveron', object: object } );
147 |
148 | _domElement.style.cursor = 'pointer';
149 | _hovered = object;
150 |
151 | }
152 |
153 | } else {
154 |
155 | if ( _hovered !== null ) {
156 |
157 | scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
158 |
159 | _domElement.style.cursor = 'auto';
160 | _hovered = null;
161 |
162 | }
163 |
164 | }
165 |
166 | }
167 |
168 | }
169 |
170 | _previousPointer.copy( _pointer );
171 |
172 | }
173 |
174 | function onPointerDown( event ) {
175 |
176 | if ( scope.enabled === false ) return;
177 |
178 | updatePointer( event );
179 |
180 | _intersections.length = 0;
181 |
182 | _raycaster.setFromCamera( _pointer, _camera );
183 | _raycaster.intersectObjects( _objects, scope.recursive, _intersections );
184 |
185 | if ( _intersections.length > 0 ) {
186 |
187 | if ( scope.transformGroup === true ) {
188 |
189 | // look for the outermost group in the object's upper hierarchy
190 |
191 | _selected = findGroup( _intersections[ 0 ].object );
192 |
193 | } else {
194 |
195 | _selected = _intersections[ 0 ].object;
196 |
197 | }
198 |
199 | _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
200 |
201 | if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
202 |
203 | if ( scope.mode === 'translate' ) {
204 |
205 | _inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
206 | _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
207 |
208 | } else if ( scope.mode === 'rotate' ) {
209 |
210 | // the controls only support Y+ up
211 | _up.set( 0, 1, 0 ).applyQuaternion( _camera.quaternion ).normalize();
212 | _right.set( 1, 0, 0 ).applyQuaternion( _camera.quaternion ).normalize();
213 |
214 | }
215 |
216 | }
217 |
218 | _domElement.style.cursor = 'move';
219 |
220 | scope.dispatchEvent( { type: 'dragstart', object: _selected } );
221 |
222 | }
223 |
224 | _previousPointer.copy( _pointer );
225 |
226 | }
227 |
228 | function onPointerCancel() {
229 |
230 | if ( scope.enabled === false ) return;
231 |
232 | if ( _selected ) {
233 |
234 | scope.dispatchEvent( { type: 'dragend', object: _selected } );
235 |
236 | _selected = null;
237 |
238 | }
239 |
240 | _domElement.style.cursor = _hovered ? 'pointer' : 'auto';
241 |
242 | }
243 |
244 | function updatePointer( event ) {
245 |
246 | const rect = _domElement.getBoundingClientRect();
247 |
248 | _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
249 | _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
250 |
251 | }
252 |
253 | function findGroup( obj, group = null ) {
254 |
255 | if ( obj.isGroup ) group = obj;
256 |
257 | if ( obj.parent === null ) return group;
258 |
259 | return findGroup( obj.parent, group );
260 |
261 | }
262 |
263 | activate();
264 |
265 | // API
266 |
267 | this.enabled = true;
268 | this.recursive = true;
269 | this.transformGroup = false;
270 |
271 | this.activate = activate;
272 | this.deactivate = deactivate;
273 | this.dispose = dispose;
274 | this.getObjects = getObjects;
275 | this.getRaycaster = getRaycaster;
276 | this.setObjects = setObjects;
277 |
278 | }
279 |
280 | }
281 |
282 | export { DragControls };
283 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/FirstPersonControls.js:
--------------------------------------------------------------------------------
1 | import {
2 | MathUtils,
3 | Spherical,
4 | Vector3
5 | } from 'three';
6 |
7 | const _lookDirection = new Vector3();
8 | const _spherical = new Spherical();
9 | const _target = new Vector3();
10 |
11 | class FirstPersonControls {
12 |
13 | constructor( object, domElement ) {
14 |
15 | this.object = object;
16 | this.domElement = domElement;
17 |
18 | // API
19 |
20 | this.enabled = true;
21 |
22 | this.movementSpeed = 1.0;
23 | this.lookSpeed = 0.005;
24 |
25 | this.lookVertical = true;
26 | this.autoForward = false;
27 |
28 | this.activeLook = true;
29 |
30 | this.heightSpeed = false;
31 | this.heightCoef = 1.0;
32 | this.heightMin = 0.0;
33 | this.heightMax = 1.0;
34 |
35 | this.constrainVertical = false;
36 | this.verticalMin = 0;
37 | this.verticalMax = Math.PI;
38 |
39 | this.mouseDragOn = false;
40 |
41 | // internals
42 |
43 | this.autoSpeedFactor = 0.0;
44 |
45 | this.pointerX = 0;
46 | this.pointerY = 0;
47 |
48 | this.moveForward = false;
49 | this.moveBackward = false;
50 | this.moveLeft = false;
51 | this.moveRight = false;
52 |
53 | this.viewHalfX = 0;
54 | this.viewHalfY = 0;
55 |
56 | // private variables
57 |
58 | let lat = 0;
59 | let lon = 0;
60 |
61 | //
62 |
63 | this.handleResize = function () {
64 |
65 | if ( this.domElement === document ) {
66 |
67 | this.viewHalfX = window.innerWidth / 2;
68 | this.viewHalfY = window.innerHeight / 2;
69 |
70 | } else {
71 |
72 | this.viewHalfX = this.domElement.offsetWidth / 2;
73 | this.viewHalfY = this.domElement.offsetHeight / 2;
74 |
75 | }
76 |
77 | };
78 |
79 | this.onPointerDown = function ( event ) {
80 |
81 | if ( this.domElement !== document ) {
82 |
83 | this.domElement.focus();
84 |
85 | }
86 |
87 | if ( this.activeLook ) {
88 |
89 | switch ( event.button ) {
90 |
91 | case 0: this.moveForward = true; break;
92 | case 2: this.moveBackward = true; break;
93 |
94 | }
95 |
96 | }
97 |
98 | this.mouseDragOn = true;
99 |
100 | };
101 |
102 | this.onPointerUp = function ( event ) {
103 |
104 | if ( this.activeLook ) {
105 |
106 | switch ( event.button ) {
107 |
108 | case 0: this.moveForward = false; break;
109 | case 2: this.moveBackward = false; break;
110 |
111 | }
112 |
113 | }
114 |
115 | this.mouseDragOn = false;
116 |
117 | };
118 |
119 | this.onPointerMove = function ( event ) {
120 |
121 | if ( this.domElement === document ) {
122 |
123 | this.pointerX = event.pageX - this.viewHalfX;
124 | this.pointerY = event.pageY - this.viewHalfY;
125 |
126 | } else {
127 |
128 | this.pointerX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
129 | this.pointerY = event.pageY - this.domElement.offsetTop - this.viewHalfY;
130 |
131 | }
132 |
133 | };
134 |
135 | this.onKeyDown = function ( event ) {
136 |
137 | switch ( event.code ) {
138 |
139 | case 'ArrowUp':
140 | case 'KeyW': this.moveForward = true; break;
141 |
142 | case 'ArrowLeft':
143 | case 'KeyA': this.moveLeft = true; break;
144 |
145 | case 'ArrowDown':
146 | case 'KeyS': this.moveBackward = true; break;
147 |
148 | case 'ArrowRight':
149 | case 'KeyD': this.moveRight = true; break;
150 |
151 | case 'KeyR': this.moveUp = true; break;
152 | case 'KeyF': this.moveDown = true; break;
153 |
154 | }
155 |
156 | };
157 |
158 | this.onKeyUp = function ( event ) {
159 |
160 | switch ( event.code ) {
161 |
162 | case 'ArrowUp':
163 | case 'KeyW': this.moveForward = false; break;
164 |
165 | case 'ArrowLeft':
166 | case 'KeyA': this.moveLeft = false; break;
167 |
168 | case 'ArrowDown':
169 | case 'KeyS': this.moveBackward = false; break;
170 |
171 | case 'ArrowRight':
172 | case 'KeyD': this.moveRight = false; break;
173 |
174 | case 'KeyR': this.moveUp = false; break;
175 | case 'KeyF': this.moveDown = false; break;
176 |
177 | }
178 |
179 | };
180 |
181 | this.lookAt = function ( x, y, z ) {
182 |
183 | if ( x.isVector3 ) {
184 |
185 | _target.copy( x );
186 |
187 | } else {
188 |
189 | _target.set( x, y, z );
190 |
191 | }
192 |
193 | this.object.lookAt( _target );
194 |
195 | setOrientation( this );
196 |
197 | return this;
198 |
199 | };
200 |
201 | this.update = function () {
202 |
203 | const targetPosition = new Vector3();
204 |
205 | return function update( delta ) {
206 |
207 | if ( this.enabled === false ) return;
208 |
209 | if ( this.heightSpeed ) {
210 |
211 | const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
212 | const heightDelta = y - this.heightMin;
213 |
214 | this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
215 |
216 | } else {
217 |
218 | this.autoSpeedFactor = 0.0;
219 |
220 | }
221 |
222 | const actualMoveSpeed = delta * this.movementSpeed;
223 |
224 | if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) );
225 | if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed );
226 |
227 | if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed );
228 | if ( this.moveRight ) this.object.translateX( actualMoveSpeed );
229 |
230 | if ( this.moveUp ) this.object.translateY( actualMoveSpeed );
231 | if ( this.moveDown ) this.object.translateY( - actualMoveSpeed );
232 |
233 | let actualLookSpeed = delta * this.lookSpeed;
234 |
235 | if ( ! this.activeLook ) {
236 |
237 | actualLookSpeed = 0;
238 |
239 | }
240 |
241 | let verticalLookRatio = 1;
242 |
243 | if ( this.constrainVertical ) {
244 |
245 | verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
246 |
247 | }
248 |
249 | lon -= this.pointerX * actualLookSpeed;
250 | if ( this.lookVertical ) lat -= this.pointerY * actualLookSpeed * verticalLookRatio;
251 |
252 | lat = Math.max( - 85, Math.min( 85, lat ) );
253 |
254 | let phi = MathUtils.degToRad( 90 - lat );
255 | const theta = MathUtils.degToRad( lon );
256 |
257 | if ( this.constrainVertical ) {
258 |
259 | phi = MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
260 |
261 | }
262 |
263 | const position = this.object.position;
264 |
265 | targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );
266 |
267 | this.object.lookAt( targetPosition );
268 |
269 | };
270 |
271 | }();
272 |
273 | this.dispose = function () {
274 |
275 | this.domElement.removeEventListener( 'contextmenu', contextmenu );
276 | this.domElement.removeEventListener( 'pointerdown', _onPointerDown );
277 | this.domElement.removeEventListener( 'pointermove', _onPointerMove );
278 | this.domElement.removeEventListener( 'pointerup', _onPointerUp );
279 |
280 | window.removeEventListener( 'keydown', _onKeyDown );
281 | window.removeEventListener( 'keyup', _onKeyUp );
282 |
283 | };
284 |
285 | const _onPointerMove = this.onPointerMove.bind( this );
286 | const _onPointerDown = this.onPointerDown.bind( this );
287 | const _onPointerUp = this.onPointerUp.bind( this );
288 | const _onKeyDown = this.onKeyDown.bind( this );
289 | const _onKeyUp = this.onKeyUp.bind( this );
290 |
291 | this.domElement.addEventListener( 'contextmenu', contextmenu );
292 | this.domElement.addEventListener( 'pointerdown', _onPointerDown );
293 | this.domElement.addEventListener( 'pointermove', _onPointerMove );
294 | this.domElement.addEventListener( 'pointerup', _onPointerUp );
295 |
296 | window.addEventListener( 'keydown', _onKeyDown );
297 | window.addEventListener( 'keyup', _onKeyUp );
298 |
299 | function setOrientation( controls ) {
300 |
301 | const quaternion = controls.object.quaternion;
302 |
303 | _lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion );
304 | _spherical.setFromVector3( _lookDirection );
305 |
306 | lat = 90 - MathUtils.radToDeg( _spherical.phi );
307 | lon = MathUtils.radToDeg( _spherical.theta );
308 |
309 | }
310 |
311 | this.handleResize();
312 |
313 | setOrientation( this );
314 |
315 | }
316 |
317 | }
318 |
319 | function contextmenu( event ) {
320 |
321 | event.preventDefault();
322 |
323 | }
324 |
325 | export { FirstPersonControls };
326 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/FlyControls.js:
--------------------------------------------------------------------------------
1 | import {
2 | EventDispatcher,
3 | Quaternion,
4 | Vector3
5 | } from 'three';
6 |
7 | const _changeEvent = { type: 'change' };
8 |
9 | class FlyControls extends EventDispatcher {
10 |
11 | constructor( object, domElement ) {
12 |
13 | super();
14 |
15 | this.object = object;
16 | this.domElement = domElement;
17 |
18 | // API
19 |
20 | // Set to false to disable this control
21 | this.enabled = true;
22 |
23 | this.movementSpeed = 1.0;
24 | this.rollSpeed = 0.005;
25 |
26 | this.dragToLook = false;
27 | this.autoForward = false;
28 |
29 | // disable default target object behavior
30 |
31 | // internals
32 |
33 | const scope = this;
34 |
35 | const EPS = 0.000001;
36 |
37 | const lastQuaternion = new Quaternion();
38 | const lastPosition = new Vector3();
39 |
40 | this.tmpQuaternion = new Quaternion();
41 |
42 | this.status = 0;
43 |
44 | this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
45 | this.moveVector = new Vector3( 0, 0, 0 );
46 | this.rotationVector = new Vector3( 0, 0, 0 );
47 |
48 | this.keydown = function ( event ) {
49 |
50 | if ( event.altKey || this.enabled === false ) {
51 |
52 | return;
53 |
54 | }
55 |
56 | switch ( event.code ) {
57 |
58 | case 'ShiftLeft':
59 | case 'ShiftRight': this.movementSpeedMultiplier = .1; break;
60 |
61 | case 'KeyW': this.moveState.forward = 1; break;
62 | case 'KeyS': this.moveState.back = 1; break;
63 |
64 | case 'KeyA': this.moveState.left = 1; break;
65 | case 'KeyD': this.moveState.right = 1; break;
66 |
67 | case 'KeyR': this.moveState.up = 1; break;
68 | case 'KeyF': this.moveState.down = 1; break;
69 |
70 | case 'ArrowUp': this.moveState.pitchUp = 1; break;
71 | case 'ArrowDown': this.moveState.pitchDown = 1; break;
72 |
73 | case 'ArrowLeft': this.moveState.yawLeft = 1; break;
74 | case 'ArrowRight': this.moveState.yawRight = 1; break;
75 |
76 | case 'KeyQ': this.moveState.rollLeft = 1; break;
77 | case 'KeyE': this.moveState.rollRight = 1; break;
78 |
79 | }
80 |
81 | this.updateMovementVector();
82 | this.updateRotationVector();
83 |
84 | };
85 |
86 | this.keyup = function ( event ) {
87 |
88 | if ( this.enabled === false ) return;
89 |
90 | switch ( event.code ) {
91 |
92 | case 'ShiftLeft':
93 | case 'ShiftRight': this.movementSpeedMultiplier = 1; break;
94 |
95 | case 'KeyW': this.moveState.forward = 0; break;
96 | case 'KeyS': this.moveState.back = 0; break;
97 |
98 | case 'KeyA': this.moveState.left = 0; break;
99 | case 'KeyD': this.moveState.right = 0; break;
100 |
101 | case 'KeyR': this.moveState.up = 0; break;
102 | case 'KeyF': this.moveState.down = 0; break;
103 |
104 | case 'ArrowUp': this.moveState.pitchUp = 0; break;
105 | case 'ArrowDown': this.moveState.pitchDown = 0; break;
106 |
107 | case 'ArrowLeft': this.moveState.yawLeft = 0; break;
108 | case 'ArrowRight': this.moveState.yawRight = 0; break;
109 |
110 | case 'KeyQ': this.moveState.rollLeft = 0; break;
111 | case 'KeyE': this.moveState.rollRight = 0; break;
112 |
113 | }
114 |
115 | this.updateMovementVector();
116 | this.updateRotationVector();
117 |
118 | };
119 |
120 | this.pointerdown = function ( event ) {
121 |
122 | if ( this.enabled === false ) return;
123 |
124 | if ( this.dragToLook ) {
125 |
126 | this.status ++;
127 |
128 | } else {
129 |
130 | switch ( event.button ) {
131 |
132 | case 0: this.moveState.forward = 1; break;
133 | case 2: this.moveState.back = 1; break;
134 |
135 | }
136 |
137 | this.updateMovementVector();
138 |
139 | }
140 |
141 | };
142 |
143 | this.pointermove = function ( event ) {
144 |
145 | if ( this.enabled === false ) return;
146 |
147 | if ( ! this.dragToLook || this.status > 0 ) {
148 |
149 | const container = this.getContainerDimensions();
150 | const halfWidth = container.size[ 0 ] / 2;
151 | const halfHeight = container.size[ 1 ] / 2;
152 |
153 | this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth;
154 | this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight;
155 |
156 | this.updateRotationVector();
157 |
158 | }
159 |
160 | };
161 |
162 | this.pointerup = function ( event ) {
163 |
164 | if ( this.enabled === false ) return;
165 |
166 | if ( this.dragToLook ) {
167 |
168 | this.status --;
169 |
170 | this.moveState.yawLeft = this.moveState.pitchDown = 0;
171 |
172 | } else {
173 |
174 | switch ( event.button ) {
175 |
176 | case 0: this.moveState.forward = 0; break;
177 | case 2: this.moveState.back = 0; break;
178 |
179 | }
180 |
181 | this.updateMovementVector();
182 |
183 | }
184 |
185 | this.updateRotationVector();
186 |
187 | };
188 |
189 | this.pointercancel = function () {
190 |
191 | if ( this.enabled === false ) return;
192 |
193 | if ( this.dragToLook ) {
194 |
195 | this.status = 0;
196 |
197 | this.moveState.yawLeft = this.moveState.pitchDown = 0;
198 |
199 | } else {
200 |
201 | this.moveState.forward = 0;
202 | this.moveState.back = 0;
203 |
204 | this.updateMovementVector();
205 |
206 | }
207 |
208 | this.updateRotationVector();
209 |
210 | };
211 |
212 | this.contextMenu = function ( event ) {
213 |
214 | if ( this.enabled === false ) return;
215 |
216 | event.preventDefault();
217 |
218 | };
219 |
220 | this.update = function ( delta ) {
221 |
222 | if ( this.enabled === false ) return;
223 |
224 | const moveMult = delta * scope.movementSpeed;
225 | const rotMult = delta * scope.rollSpeed;
226 |
227 | scope.object.translateX( scope.moveVector.x * moveMult );
228 | scope.object.translateY( scope.moveVector.y * moveMult );
229 | scope.object.translateZ( scope.moveVector.z * moveMult );
230 |
231 | scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize();
232 | scope.object.quaternion.multiply( scope.tmpQuaternion );
233 |
234 | if (
235 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
236 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS
237 | ) {
238 |
239 | scope.dispatchEvent( _changeEvent );
240 | lastQuaternion.copy( scope.object.quaternion );
241 | lastPosition.copy( scope.object.position );
242 |
243 | }
244 |
245 | };
246 |
247 | this.updateMovementVector = function () {
248 |
249 | const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0;
250 |
251 | this.moveVector.x = ( - this.moveState.left + this.moveState.right );
252 | this.moveVector.y = ( - this.moveState.down + this.moveState.up );
253 | this.moveVector.z = ( - forward + this.moveState.back );
254 |
255 | //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
256 |
257 | };
258 |
259 | this.updateRotationVector = function () {
260 |
261 | this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp );
262 | this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft );
263 | this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft );
264 |
265 | //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
266 |
267 | };
268 |
269 | this.getContainerDimensions = function () {
270 |
271 | if ( this.domElement != document ) {
272 |
273 | return {
274 | size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
275 | offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
276 | };
277 |
278 | } else {
279 |
280 | return {
281 | size: [ window.innerWidth, window.innerHeight ],
282 | offset: [ 0, 0 ]
283 | };
284 |
285 | }
286 |
287 | };
288 |
289 | this.dispose = function () {
290 |
291 | this.domElement.removeEventListener( 'contextmenu', _contextmenu );
292 | this.domElement.removeEventListener( 'pointerdown', _pointerdown );
293 | this.domElement.removeEventListener( 'pointermove', _pointermove );
294 | this.domElement.removeEventListener( 'pointerup', _pointerup );
295 | this.domElement.removeEventListener( 'pointercancel', _pointercancel );
296 |
297 | window.removeEventListener( 'keydown', _keydown );
298 | window.removeEventListener( 'keyup', _keyup );
299 |
300 | };
301 |
302 | const _contextmenu = this.contextMenu.bind( this );
303 | const _pointermove = this.pointermove.bind( this );
304 | const _pointerdown = this.pointerdown.bind( this );
305 | const _pointerup = this.pointerup.bind( this );
306 | const _pointercancel = this.pointercancel.bind( this );
307 | const _keydown = this.keydown.bind( this );
308 | const _keyup = this.keyup.bind( this );
309 |
310 | this.domElement.addEventListener( 'contextmenu', _contextmenu );
311 | this.domElement.addEventListener( 'pointerdown', _pointerdown );
312 | this.domElement.addEventListener( 'pointermove', _pointermove );
313 | this.domElement.addEventListener( 'pointerup', _pointerup );
314 | this.domElement.addEventListener( 'pointercancel', _pointercancel );
315 |
316 | window.addEventListener( 'keydown', _keydown );
317 | window.addEventListener( 'keyup', _keyup );
318 |
319 | this.updateMovementVector();
320 | this.updateRotationVector();
321 |
322 | }
323 |
324 | }
325 |
326 | export { FlyControls };
327 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/MapControls.js:
--------------------------------------------------------------------------------
1 | import { MOUSE, TOUCH } from 'three';
2 |
3 | import { OrbitControls } from './OrbitControls.js';
4 |
5 | // MapControls performs orbiting, dollying (zooming), and panning.
6 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
7 | //
8 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
9 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
10 | // Pan - left mouse, or arrow keys / touch: one-finger move
11 |
12 | class MapControls extends OrbitControls {
13 |
14 | constructor( object, domElement ) {
15 |
16 | super( object, domElement );
17 |
18 | this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
19 |
20 | this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.ROTATE };
21 |
22 | this.touches = { ONE: TOUCH.PAN, TWO: TOUCH.DOLLY_ROTATE };
23 |
24 | }
25 |
26 | }
27 |
28 | export { MapControls };
29 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/OrbitControls.js:
--------------------------------------------------------------------------------
1 | import {
2 | EventDispatcher,
3 | MOUSE,
4 | Quaternion,
5 | Spherical,
6 | TOUCH,
7 | Vector2,
8 | Vector3,
9 | Plane,
10 | Ray,
11 | MathUtils
12 | } from 'three';
13 |
14 | // OrbitControls performs orbiting, dollying (zooming), and panning.
15 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
16 | //
17 | // Orbit - left mouse / touch: one-finger move
18 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
19 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
20 |
21 | const _changeEvent = { type: 'change' };
22 | const _startEvent = { type: 'start' };
23 | const _endEvent = { type: 'end' };
24 | const _ray = new Ray();
25 | const _plane = new Plane();
26 | const TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
27 |
28 | class OrbitControls extends EventDispatcher {
29 |
30 | constructor( object, domElement ) {
31 |
32 | super();
33 |
34 | this.object = object;
35 | this.domElement = domElement;
36 | this.domElement.style.touchAction = 'none'; // disable touch scroll
37 |
38 | // Set to false to disable this control
39 | this.enabled = true;
40 |
41 | // "target" sets the location of focus, where the object orbits around
42 | this.target = new Vector3();
43 |
44 | // Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect
45 | this.cursor = new Vector3();
46 |
47 | // How far you can dolly in and out ( PerspectiveCamera only )
48 | this.minDistance = 0;
49 | this.maxDistance = Infinity;
50 |
51 | // How far you can zoom in and out ( OrthographicCamera only )
52 | this.minZoom = 0;
53 | this.maxZoom = Infinity;
54 |
55 | // Limit camera target within a spherical area around the cursor
56 | this.minTargetRadius = 0;
57 | this.maxTargetRadius = Infinity;
58 |
59 | // How far you can orbit vertically, upper and lower limits.
60 | // Range is 0 to Math.PI radians.
61 | this.minPolarAngle = 0; // radians
62 | this.maxPolarAngle = Math.PI; // radians
63 |
64 | // How far you can orbit horizontally, upper and lower limits.
65 | // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
66 | this.minAzimuthAngle = - Infinity; // radians
67 | this.maxAzimuthAngle = Infinity; // radians
68 |
69 | // Set to true to enable damping (inertia)
70 | // If damping is enabled, you must call controls.update() in your animation loop
71 | this.enableDamping = false;
72 | this.dampingFactor = 0.05;
73 |
74 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
75 | // Set to false to disable zooming
76 | this.enableZoom = true;
77 | this.zoomSpeed = 1.0;
78 |
79 | // Set to false to disable rotating
80 | this.enableRotate = true;
81 | this.rotateSpeed = 1.0;
82 |
83 | // Set to false to disable panning
84 | this.enablePan = true;
85 | this.panSpeed = 1.0;
86 | this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
87 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
88 | this.zoomToCursor = false;
89 |
90 | // Set to true to automatically rotate around the target
91 | // If auto-rotate is enabled, you must call controls.update() in your animation loop
92 | this.autoRotate = false;
93 | this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
94 |
95 | // The four arrow keys
96 | this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
97 |
98 | // Mouse buttons
99 | this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
100 |
101 | // Touch fingers
102 | this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
103 |
104 | // for reset
105 | this.target0 = this.target.clone();
106 | this.position0 = this.object.position.clone();
107 | this.zoom0 = this.object.zoom;
108 |
109 | // the target DOM element for key events
110 | this._domElementKeyEvents = null;
111 |
112 | //
113 | // public methods
114 | //
115 |
116 | this.getPolarAngle = function () {
117 |
118 | return spherical.phi;
119 |
120 | };
121 |
122 | this.getAzimuthalAngle = function () {
123 |
124 | return spherical.theta;
125 |
126 | };
127 |
128 | this.getDistance = function () {
129 |
130 | return this.object.position.distanceTo( this.target );
131 |
132 | };
133 |
134 | this.listenToKeyEvents = function ( domElement ) {
135 |
136 | domElement.addEventListener( 'keydown', onKeyDown );
137 | this._domElementKeyEvents = domElement;
138 |
139 | };
140 |
141 | this.stopListenToKeyEvents = function () {
142 |
143 | this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
144 | this._domElementKeyEvents = null;
145 |
146 | };
147 |
148 | this.saveState = function () {
149 |
150 | scope.target0.copy( scope.target );
151 | scope.position0.copy( scope.object.position );
152 | scope.zoom0 = scope.object.zoom;
153 |
154 | };
155 |
156 | this.reset = function () {
157 |
158 | scope.target.copy( scope.target0 );
159 | scope.object.position.copy( scope.position0 );
160 | scope.object.zoom = scope.zoom0;
161 |
162 | scope.object.updateProjectionMatrix();
163 | scope.dispatchEvent( _changeEvent );
164 |
165 | scope.update();
166 |
167 | state = STATE.NONE;
168 |
169 | };
170 |
171 | // this method is exposed, but perhaps it would be better if we can make it private...
172 | this.update = function () {
173 |
174 | const offset = new Vector3();
175 |
176 | // so camera.up is the orbit axis
177 | const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
178 | const quatInverse = quat.clone().invert();
179 |
180 | const lastPosition = new Vector3();
181 | const lastQuaternion = new Quaternion();
182 | const lastTargetPosition = new Vector3();
183 |
184 | const twoPI = 2 * Math.PI;
185 |
186 | return function update( deltaTime = null ) {
187 |
188 | const position = scope.object.position;
189 |
190 | offset.copy( position ).sub( scope.target );
191 |
192 | // rotate offset to "y-axis-is-up" space
193 | offset.applyQuaternion( quat );
194 |
195 | // angle from z-axis around y-axis
196 | spherical.setFromVector3( offset );
197 |
198 | if ( scope.autoRotate && state === STATE.NONE ) {
199 |
200 | rotateLeft( getAutoRotationAngle( deltaTime ) );
201 |
202 | }
203 |
204 | if ( scope.enableDamping ) {
205 |
206 | spherical.theta += sphericalDelta.theta * scope.dampingFactor;
207 | spherical.phi += sphericalDelta.phi * scope.dampingFactor;
208 |
209 | } else {
210 |
211 | spherical.theta += sphericalDelta.theta;
212 | spherical.phi += sphericalDelta.phi;
213 |
214 | }
215 |
216 | // restrict theta to be between desired limits
217 |
218 | let min = scope.minAzimuthAngle;
219 | let max = scope.maxAzimuthAngle;
220 |
221 | if ( isFinite( min ) && isFinite( max ) ) {
222 |
223 | if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
224 |
225 | if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
226 |
227 | if ( min <= max ) {
228 |
229 | spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
230 |
231 | } else {
232 |
233 | spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
234 | Math.max( min, spherical.theta ) :
235 | Math.min( max, spherical.theta );
236 |
237 | }
238 |
239 | }
240 |
241 | // restrict phi to be between desired limits
242 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
243 |
244 | spherical.makeSafe();
245 |
246 |
247 | // move target to panned location
248 |
249 | if ( scope.enableDamping === true ) {
250 |
251 | scope.target.addScaledVector( panOffset, scope.dampingFactor );
252 |
253 | } else {
254 |
255 | scope.target.add( panOffset );
256 |
257 | }
258 |
259 | // Limit the target distance from the cursor to create a sphere around the center of interest
260 | scope.target.sub( scope.cursor );
261 | scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius );
262 | scope.target.add( scope.cursor );
263 |
264 | let zoomChanged = false;
265 | // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
266 | // we adjust zoom later in these cases
267 | if ( scope.zoomToCursor && performCursorZoom || scope.object.isOrthographicCamera ) {
268 |
269 | spherical.radius = clampDistance( spherical.radius );
270 |
271 | } else {
272 |
273 | const prevRadius = spherical.radius;
274 | spherical.radius = clampDistance( spherical.radius * scale );
275 | zoomChanged = prevRadius != spherical.radius;
276 |
277 | }
278 |
279 | offset.setFromSpherical( spherical );
280 |
281 | // rotate offset back to "camera-up-vector-is-up" space
282 | offset.applyQuaternion( quatInverse );
283 |
284 | position.copy( scope.target ).add( offset );
285 |
286 | scope.object.lookAt( scope.target );
287 |
288 | if ( scope.enableDamping === true ) {
289 |
290 | sphericalDelta.theta *= ( 1 - scope.dampingFactor );
291 | sphericalDelta.phi *= ( 1 - scope.dampingFactor );
292 |
293 | panOffset.multiplyScalar( 1 - scope.dampingFactor );
294 |
295 | } else {
296 |
297 | sphericalDelta.set( 0, 0, 0 );
298 |
299 | panOffset.set( 0, 0, 0 );
300 |
301 | }
302 |
303 | // adjust camera position
304 | if ( scope.zoomToCursor && performCursorZoom ) {
305 |
306 | let newRadius = null;
307 | if ( scope.object.isPerspectiveCamera ) {
308 |
309 | // move the camera down the pointer ray
310 | // this method avoids floating point error
311 | const prevRadius = offset.length();
312 | newRadius = clampDistance( prevRadius * scale );
313 |
314 | const radiusDelta = prevRadius - newRadius;
315 | scope.object.position.addScaledVector( dollyDirection, radiusDelta );
316 | scope.object.updateMatrixWorld();
317 |
318 | zoomChanged = !! radiusDelta;
319 |
320 | } else if ( scope.object.isOrthographicCamera ) {
321 |
322 | // adjust the ortho camera position based on zoom changes
323 | const mouseBefore = new Vector3( mouse.x, mouse.y, 0 );
324 | mouseBefore.unproject( scope.object );
325 |
326 | const prevZoom = scope.object.zoom;
327 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
328 | scope.object.updateProjectionMatrix();
329 |
330 | zoomChanged = prevZoom !== scope.object.zoom;
331 |
332 | const mouseAfter = new Vector3( mouse.x, mouse.y, 0 );
333 | mouseAfter.unproject( scope.object );
334 |
335 | scope.object.position.sub( mouseAfter ).add( mouseBefore );
336 | scope.object.updateMatrixWorld();
337 |
338 | newRadius = offset.length();
339 |
340 | } else {
341 |
342 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
343 | scope.zoomToCursor = false;
344 |
345 | }
346 |
347 | // handle the placement of the target
348 | if ( newRadius !== null ) {
349 |
350 | if ( this.screenSpacePanning ) {
351 |
352 | // position the orbit target in front of the new camera position
353 | scope.target.set( 0, 0, - 1 )
354 | .transformDirection( scope.object.matrix )
355 | .multiplyScalar( newRadius )
356 | .add( scope.object.position );
357 |
358 | } else {
359 |
360 | // get the ray and translation plane to compute target
361 | _ray.origin.copy( scope.object.position );
362 | _ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix );
363 |
364 | // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
365 | // extremely large values
366 | if ( Math.abs( scope.object.up.dot( _ray.direction ) ) < TILT_LIMIT ) {
367 |
368 | object.lookAt( scope.target );
369 |
370 | } else {
371 |
372 | _plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target );
373 | _ray.intersectPlane( _plane, scope.target );
374 |
375 | }
376 |
377 | }
378 |
379 | }
380 |
381 | } else if ( scope.object.isOrthographicCamera ) {
382 |
383 | const prevZoom = scope.object.zoom;
384 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );
385 |
386 | if ( prevZoom !== scope.object.zoom ) {
387 |
388 | scope.object.updateProjectionMatrix();
389 | zoomChanged = true;
390 |
391 | }
392 |
393 | }
394 |
395 | scale = 1;
396 | performCursorZoom = false;
397 |
398 | // update condition is:
399 | // min(camera displacement, camera rotation in radians)^2 > EPS
400 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
401 |
402 | if ( zoomChanged ||
403 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
404 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ||
405 | lastTargetPosition.distanceToSquared( scope.target ) > EPS ) {
406 |
407 | scope.dispatchEvent( _changeEvent );
408 |
409 | lastPosition.copy( scope.object.position );
410 | lastQuaternion.copy( scope.object.quaternion );
411 | lastTargetPosition.copy( scope.target );
412 |
413 | return true;
414 |
415 | }
416 |
417 | return false;
418 |
419 | };
420 |
421 | }();
422 |
423 | this.dispose = function () {
424 |
425 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
426 |
427 | scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
428 | scope.domElement.removeEventListener( 'pointercancel', onPointerUp );
429 | scope.domElement.removeEventListener( 'wheel', onMouseWheel );
430 |
431 | scope.domElement.removeEventListener( 'pointermove', onPointerMove );
432 | scope.domElement.removeEventListener( 'pointerup', onPointerUp );
433 |
434 | const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
435 |
436 | document.removeEventListener( 'keydown', interceptControlDown, { capture: true } );
437 |
438 | if ( scope._domElementKeyEvents !== null ) {
439 |
440 | scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
441 | scope._domElementKeyEvents = null;
442 |
443 | }
444 |
445 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
446 |
447 | };
448 |
449 | //
450 | // internals
451 | //
452 |
453 | const scope = this;
454 |
455 | const STATE = {
456 | NONE: - 1,
457 | ROTATE: 0,
458 | DOLLY: 1,
459 | PAN: 2,
460 | TOUCH_ROTATE: 3,
461 | TOUCH_PAN: 4,
462 | TOUCH_DOLLY_PAN: 5,
463 | TOUCH_DOLLY_ROTATE: 6
464 | };
465 |
466 | let state = STATE.NONE;
467 |
468 | const EPS = 0.000001;
469 |
470 | // current position in spherical coordinates
471 | const spherical = new Spherical();
472 | const sphericalDelta = new Spherical();
473 |
474 | let scale = 1;
475 | const panOffset = new Vector3();
476 |
477 | const rotateStart = new Vector2();
478 | const rotateEnd = new Vector2();
479 | const rotateDelta = new Vector2();
480 |
481 | const panStart = new Vector2();
482 | const panEnd = new Vector2();
483 | const panDelta = new Vector2();
484 |
485 | const dollyStart = new Vector2();
486 | const dollyEnd = new Vector2();
487 | const dollyDelta = new Vector2();
488 |
489 | const dollyDirection = new Vector3();
490 | const mouse = new Vector2();
491 | let performCursorZoom = false;
492 |
493 | const pointers = [];
494 | const pointerPositions = {};
495 |
496 | let controlActive = false;
497 |
498 | function getAutoRotationAngle( deltaTime ) {
499 |
500 | if ( deltaTime !== null ) {
501 |
502 | return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime;
503 |
504 | } else {
505 |
506 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
507 |
508 | }
509 |
510 | }
511 |
512 | function getZoomScale( delta ) {
513 |
514 | const normalizedDelta = Math.abs( delta * 0.01 );
515 | return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta );
516 |
517 | }
518 |
519 | function rotateLeft( angle ) {
520 |
521 | sphericalDelta.theta -= angle;
522 |
523 | }
524 |
525 | function rotateUp( angle ) {
526 |
527 | sphericalDelta.phi -= angle;
528 |
529 | }
530 |
531 | const panLeft = function () {
532 |
533 | const v = new Vector3();
534 |
535 | return function panLeft( distance, objectMatrix ) {
536 |
537 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
538 | v.multiplyScalar( - distance );
539 |
540 | panOffset.add( v );
541 |
542 | };
543 |
544 | }();
545 |
546 | const panUp = function () {
547 |
548 | const v = new Vector3();
549 |
550 | return function panUp( distance, objectMatrix ) {
551 |
552 | if ( scope.screenSpacePanning === true ) {
553 |
554 | v.setFromMatrixColumn( objectMatrix, 1 );
555 |
556 | } else {
557 |
558 | v.setFromMatrixColumn( objectMatrix, 0 );
559 | v.crossVectors( scope.object.up, v );
560 |
561 | }
562 |
563 | v.multiplyScalar( distance );
564 |
565 | panOffset.add( v );
566 |
567 | };
568 |
569 | }();
570 |
571 | // deltaX and deltaY are in pixels; right and down are positive
572 | const pan = function () {
573 |
574 | const offset = new Vector3();
575 |
576 | return function pan( deltaX, deltaY ) {
577 |
578 | const element = scope.domElement;
579 |
580 | if ( scope.object.isPerspectiveCamera ) {
581 |
582 | // perspective
583 | const position = scope.object.position;
584 | offset.copy( position ).sub( scope.target );
585 | let targetDistance = offset.length();
586 |
587 | // half of the fov is center to top of screen
588 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
589 |
590 | // we use only clientHeight here so aspect ratio does not distort speed
591 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
592 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
593 |
594 | } else if ( scope.object.isOrthographicCamera ) {
595 |
596 | // orthographic
597 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
598 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
599 |
600 | } else {
601 |
602 | // camera neither orthographic nor perspective
603 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
604 | scope.enablePan = false;
605 |
606 | }
607 |
608 | };
609 |
610 | }();
611 |
612 | function dollyOut( dollyScale ) {
613 |
614 | if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
615 |
616 | scale /= dollyScale;
617 |
618 | } else {
619 |
620 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
621 | scope.enableZoom = false;
622 |
623 | }
624 |
625 | }
626 |
627 | function dollyIn( dollyScale ) {
628 |
629 | if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {
630 |
631 | scale *= dollyScale;
632 |
633 | } else {
634 |
635 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
636 | scope.enableZoom = false;
637 |
638 | }
639 |
640 | }
641 |
642 | function updateZoomParameters( x, y ) {
643 |
644 | if ( ! scope.zoomToCursor ) {
645 |
646 | return;
647 |
648 | }
649 |
650 | performCursorZoom = true;
651 |
652 | const rect = scope.domElement.getBoundingClientRect();
653 | const dx = x - rect.left;
654 | const dy = y - rect.top;
655 | const w = rect.width;
656 | const h = rect.height;
657 |
658 | mouse.x = ( dx / w ) * 2 - 1;
659 | mouse.y = - ( dy / h ) * 2 + 1;
660 |
661 | dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize();
662 |
663 | }
664 |
665 | function clampDistance( dist ) {
666 |
667 | return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) );
668 |
669 | }
670 |
671 | //
672 | // event callbacks - update the object state
673 | //
674 |
675 | function handleMouseDownRotate( event ) {
676 |
677 | rotateStart.set( event.clientX, event.clientY );
678 |
679 | }
680 |
681 | function handleMouseDownDolly( event ) {
682 |
683 | updateZoomParameters( event.clientX, event.clientX );
684 | dollyStart.set( event.clientX, event.clientY );
685 |
686 | }
687 |
688 | function handleMouseDownPan( event ) {
689 |
690 | panStart.set( event.clientX, event.clientY );
691 |
692 | }
693 |
694 | function handleMouseMoveRotate( event ) {
695 |
696 | rotateEnd.set( event.clientX, event.clientY );
697 |
698 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
699 |
700 | const element = scope.domElement;
701 |
702 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
703 |
704 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
705 |
706 | rotateStart.copy( rotateEnd );
707 |
708 | scope.update();
709 |
710 | }
711 |
712 | function handleMouseMoveDolly( event ) {
713 |
714 | dollyEnd.set( event.clientX, event.clientY );
715 |
716 | dollyDelta.subVectors( dollyEnd, dollyStart );
717 |
718 | if ( dollyDelta.y > 0 ) {
719 |
720 | dollyOut( getZoomScale( dollyDelta.y ) );
721 |
722 | } else if ( dollyDelta.y < 0 ) {
723 |
724 | dollyIn( getZoomScale( dollyDelta.y ) );
725 |
726 | }
727 |
728 | dollyStart.copy( dollyEnd );
729 |
730 | scope.update();
731 |
732 | }
733 |
734 | function handleMouseMovePan( event ) {
735 |
736 | panEnd.set( event.clientX, event.clientY );
737 |
738 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
739 |
740 | pan( panDelta.x, panDelta.y );
741 |
742 | panStart.copy( panEnd );
743 |
744 | scope.update();
745 |
746 | }
747 |
748 | function handleMouseWheel( event ) {
749 |
750 | updateZoomParameters( event.clientX, event.clientY );
751 |
752 | if ( event.deltaY < 0 ) {
753 |
754 | dollyIn( getZoomScale( event.deltaY ) );
755 |
756 | } else if ( event.deltaY > 0 ) {
757 |
758 | dollyOut( getZoomScale( event.deltaY ) );
759 |
760 | }
761 |
762 | scope.update();
763 |
764 | }
765 |
766 | function handleKeyDown( event ) {
767 |
768 | let needsUpdate = false;
769 |
770 | switch ( event.code ) {
771 |
772 | case scope.keys.UP:
773 |
774 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
775 |
776 | rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
777 |
778 | } else {
779 |
780 | pan( 0, scope.keyPanSpeed );
781 |
782 | }
783 |
784 | needsUpdate = true;
785 | break;
786 |
787 | case scope.keys.BOTTOM:
788 |
789 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
790 |
791 | rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
792 |
793 | } else {
794 |
795 | pan( 0, - scope.keyPanSpeed );
796 |
797 | }
798 |
799 | needsUpdate = true;
800 | break;
801 |
802 | case scope.keys.LEFT:
803 |
804 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
805 |
806 | rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
807 |
808 | } else {
809 |
810 | pan( scope.keyPanSpeed, 0 );
811 |
812 | }
813 |
814 | needsUpdate = true;
815 | break;
816 |
817 | case scope.keys.RIGHT:
818 |
819 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
820 |
821 | rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );
822 |
823 | } else {
824 |
825 | pan( - scope.keyPanSpeed, 0 );
826 |
827 | }
828 |
829 | needsUpdate = true;
830 | break;
831 |
832 | }
833 |
834 | if ( needsUpdate ) {
835 |
836 | // prevent the browser from scrolling on cursor keys
837 | event.preventDefault();
838 |
839 | scope.update();
840 |
841 | }
842 |
843 |
844 | }
845 |
846 | function handleTouchStartRotate( event ) {
847 |
848 | if ( pointers.length === 1 ) {
849 |
850 | rotateStart.set( event.pageX, event.pageY );
851 |
852 | } else {
853 |
854 | const position = getSecondPointerPosition( event );
855 |
856 | const x = 0.5 * ( event.pageX + position.x );
857 | const y = 0.5 * ( event.pageY + position.y );
858 |
859 | rotateStart.set( x, y );
860 |
861 | }
862 |
863 | }
864 |
865 | function handleTouchStartPan( event ) {
866 |
867 | if ( pointers.length === 1 ) {
868 |
869 | panStart.set( event.pageX, event.pageY );
870 |
871 | } else {
872 |
873 | const position = getSecondPointerPosition( event );
874 |
875 | const x = 0.5 * ( event.pageX + position.x );
876 | const y = 0.5 * ( event.pageY + position.y );
877 |
878 | panStart.set( x, y );
879 |
880 | }
881 |
882 | }
883 |
884 | function handleTouchStartDolly( event ) {
885 |
886 | const position = getSecondPointerPosition( event );
887 |
888 | const dx = event.pageX - position.x;
889 | const dy = event.pageY - position.y;
890 |
891 | const distance = Math.sqrt( dx * dx + dy * dy );
892 |
893 | dollyStart.set( 0, distance );
894 |
895 | }
896 |
897 | function handleTouchStartDollyPan( event ) {
898 |
899 | if ( scope.enableZoom ) handleTouchStartDolly( event );
900 |
901 | if ( scope.enablePan ) handleTouchStartPan( event );
902 |
903 | }
904 |
905 | function handleTouchStartDollyRotate( event ) {
906 |
907 | if ( scope.enableZoom ) handleTouchStartDolly( event );
908 |
909 | if ( scope.enableRotate ) handleTouchStartRotate( event );
910 |
911 | }
912 |
913 | function handleTouchMoveRotate( event ) {
914 |
915 | if ( pointers.length == 1 ) {
916 |
917 | rotateEnd.set( event.pageX, event.pageY );
918 |
919 | } else {
920 |
921 | const position = getSecondPointerPosition( event );
922 |
923 | const x = 0.5 * ( event.pageX + position.x );
924 | const y = 0.5 * ( event.pageY + position.y );
925 |
926 | rotateEnd.set( x, y );
927 |
928 | }
929 |
930 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
931 |
932 | const element = scope.domElement;
933 |
934 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
935 |
936 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
937 |
938 | rotateStart.copy( rotateEnd );
939 |
940 | }
941 |
942 | function handleTouchMovePan( event ) {
943 |
944 | if ( pointers.length === 1 ) {
945 |
946 | panEnd.set( event.pageX, event.pageY );
947 |
948 | } else {
949 |
950 | const position = getSecondPointerPosition( event );
951 |
952 | const x = 0.5 * ( event.pageX + position.x );
953 | const y = 0.5 * ( event.pageY + position.y );
954 |
955 | panEnd.set( x, y );
956 |
957 | }
958 |
959 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
960 |
961 | pan( panDelta.x, panDelta.y );
962 |
963 | panStart.copy( panEnd );
964 |
965 | }
966 |
967 | function handleTouchMoveDolly( event ) {
968 |
969 | const position = getSecondPointerPosition( event );
970 |
971 | const dx = event.pageX - position.x;
972 | const dy = event.pageY - position.y;
973 |
974 | const distance = Math.sqrt( dx * dx + dy * dy );
975 |
976 | dollyEnd.set( 0, distance );
977 |
978 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
979 |
980 | dollyOut( dollyDelta.y );
981 |
982 | dollyStart.copy( dollyEnd );
983 |
984 | const centerX = ( event.pageX + position.x ) * 0.5;
985 | const centerY = ( event.pageY + position.y ) * 0.5;
986 |
987 | updateZoomParameters( centerX, centerY );
988 |
989 | }
990 |
991 | function handleTouchMoveDollyPan( event ) {
992 |
993 | if ( scope.enableZoom ) handleTouchMoveDolly( event );
994 |
995 | if ( scope.enablePan ) handleTouchMovePan( event );
996 |
997 | }
998 |
999 | function handleTouchMoveDollyRotate( event ) {
1000 |
1001 | if ( scope.enableZoom ) handleTouchMoveDolly( event );
1002 |
1003 | if ( scope.enableRotate ) handleTouchMoveRotate( event );
1004 |
1005 | }
1006 |
1007 | //
1008 | // event handlers - FSM: listen for events and reset state
1009 | //
1010 |
1011 | function onPointerDown( event ) {
1012 |
1013 | if ( scope.enabled === false ) return;
1014 |
1015 | if ( pointers.length === 0 ) {
1016 |
1017 | scope.domElement.setPointerCapture( event.pointerId );
1018 |
1019 | scope.domElement.addEventListener( 'pointermove', onPointerMove );
1020 | scope.domElement.addEventListener( 'pointerup', onPointerUp );
1021 |
1022 | }
1023 |
1024 | //
1025 |
1026 | if ( isTrackingPointer( event ) ) return;
1027 |
1028 | //
1029 |
1030 | addPointer( event );
1031 |
1032 | if ( event.pointerType === 'touch' ) {
1033 |
1034 | onTouchStart( event );
1035 |
1036 | } else {
1037 |
1038 | onMouseDown( event );
1039 |
1040 | }
1041 |
1042 | }
1043 |
1044 | function onPointerMove( event ) {
1045 |
1046 | if ( scope.enabled === false ) return;
1047 |
1048 | if ( event.pointerType === 'touch' ) {
1049 |
1050 | onTouchMove( event );
1051 |
1052 | } else {
1053 |
1054 | onMouseMove( event );
1055 |
1056 | }
1057 |
1058 | }
1059 |
1060 | function onPointerUp( event ) {
1061 |
1062 | removePointer( event );
1063 |
1064 | switch ( pointers.length ) {
1065 |
1066 | case 0:
1067 |
1068 | scope.domElement.releasePointerCapture( event.pointerId );
1069 |
1070 | scope.domElement.removeEventListener( 'pointermove', onPointerMove );
1071 | scope.domElement.removeEventListener( 'pointerup', onPointerUp );
1072 |
1073 | scope.dispatchEvent( _endEvent );
1074 |
1075 | state = STATE.NONE;
1076 |
1077 | break;
1078 |
1079 | case 1:
1080 |
1081 | const pointerId = pointers[ 0 ];
1082 | const position = pointerPositions[ pointerId ];
1083 |
1084 | // minimal placeholder event - allows state correction on pointer-up
1085 | onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );
1086 |
1087 | break;
1088 |
1089 | }
1090 |
1091 | }
1092 |
1093 | function onMouseDown( event ) {
1094 |
1095 | let mouseAction;
1096 |
1097 | switch ( event.button ) {
1098 |
1099 | case 0:
1100 |
1101 | mouseAction = scope.mouseButtons.LEFT;
1102 | break;
1103 |
1104 | case 1:
1105 |
1106 | mouseAction = scope.mouseButtons.MIDDLE;
1107 | break;
1108 |
1109 | case 2:
1110 |
1111 | mouseAction = scope.mouseButtons.RIGHT;
1112 | break;
1113 |
1114 | default:
1115 |
1116 | mouseAction = - 1;
1117 |
1118 | }
1119 |
1120 | switch ( mouseAction ) {
1121 |
1122 | case MOUSE.DOLLY:
1123 |
1124 | if ( scope.enableZoom === false ) return;
1125 |
1126 | handleMouseDownDolly( event );
1127 |
1128 | state = STATE.DOLLY;
1129 |
1130 | break;
1131 |
1132 | case MOUSE.ROTATE:
1133 |
1134 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1135 |
1136 | if ( scope.enablePan === false ) return;
1137 |
1138 | handleMouseDownPan( event );
1139 |
1140 | state = STATE.PAN;
1141 |
1142 | } else {
1143 |
1144 | if ( scope.enableRotate === false ) return;
1145 |
1146 | handleMouseDownRotate( event );
1147 |
1148 | state = STATE.ROTATE;
1149 |
1150 | }
1151 |
1152 | break;
1153 |
1154 | case MOUSE.PAN:
1155 |
1156 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
1157 |
1158 | if ( scope.enableRotate === false ) return;
1159 |
1160 | handleMouseDownRotate( event );
1161 |
1162 | state = STATE.ROTATE;
1163 |
1164 | } else {
1165 |
1166 | if ( scope.enablePan === false ) return;
1167 |
1168 | handleMouseDownPan( event );
1169 |
1170 | state = STATE.PAN;
1171 |
1172 | }
1173 |
1174 | break;
1175 |
1176 | default:
1177 |
1178 | state = STATE.NONE;
1179 |
1180 | }
1181 |
1182 | if ( state !== STATE.NONE ) {
1183 |
1184 | scope.dispatchEvent( _startEvent );
1185 |
1186 | }
1187 |
1188 | }
1189 |
1190 | function onMouseMove( event ) {
1191 |
1192 | switch ( state ) {
1193 |
1194 | case STATE.ROTATE:
1195 |
1196 | if ( scope.enableRotate === false ) return;
1197 |
1198 | handleMouseMoveRotate( event );
1199 |
1200 | break;
1201 |
1202 | case STATE.DOLLY:
1203 |
1204 | if ( scope.enableZoom === false ) return;
1205 |
1206 | handleMouseMoveDolly( event );
1207 |
1208 | break;
1209 |
1210 | case STATE.PAN:
1211 |
1212 | if ( scope.enablePan === false ) return;
1213 |
1214 | handleMouseMovePan( event );
1215 |
1216 | break;
1217 |
1218 | }
1219 |
1220 | }
1221 |
1222 | function onMouseWheel( event ) {
1223 |
1224 | if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
1225 |
1226 | event.preventDefault();
1227 |
1228 | scope.dispatchEvent( _startEvent );
1229 |
1230 | handleMouseWheel( customWheelEvent( event ) );
1231 |
1232 | scope.dispatchEvent( _endEvent );
1233 |
1234 | }
1235 |
1236 | function customWheelEvent( event ) {
1237 |
1238 | const mode = event.deltaMode;
1239 |
1240 | // minimal wheel event altered to meet delta-zoom demand
1241 | const newEvent = {
1242 | clientX: event.clientX,
1243 | clientY: event.clientY,
1244 | deltaY: event.deltaY,
1245 | };
1246 |
1247 | switch ( mode ) {
1248 |
1249 | case 1: // LINE_MODE
1250 | newEvent.deltaY *= 16;
1251 | break;
1252 |
1253 | case 2: // PAGE_MODE
1254 | newEvent.deltaY *= 100;
1255 | break;
1256 |
1257 | }
1258 |
1259 | // detect if event was triggered by pinching
1260 | if ( event.ctrlKey && ! controlActive ) {
1261 |
1262 | newEvent.deltaY *= 10;
1263 |
1264 | }
1265 |
1266 | return newEvent;
1267 |
1268 | }
1269 |
1270 | function interceptControlDown( event ) {
1271 |
1272 | if ( event.key === 'Control' ) {
1273 |
1274 | controlActive = true;
1275 |
1276 |
1277 | const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
1278 |
1279 | document.addEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } );
1280 |
1281 | }
1282 |
1283 | }
1284 |
1285 | function interceptControlUp( event ) {
1286 |
1287 | if ( event.key === 'Control' ) {
1288 |
1289 | controlActive = false;
1290 |
1291 |
1292 | const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
1293 |
1294 | document.removeEventListener( 'keyup', interceptControlUp, { passive: true, capture: true } );
1295 |
1296 | }
1297 |
1298 | }
1299 |
1300 | function onKeyDown( event ) {
1301 |
1302 | if ( scope.enabled === false || scope.enablePan === false ) return;
1303 |
1304 | handleKeyDown( event );
1305 |
1306 | }
1307 |
1308 | function onTouchStart( event ) {
1309 |
1310 | trackPointer( event );
1311 |
1312 | switch ( pointers.length ) {
1313 |
1314 | case 1:
1315 |
1316 | switch ( scope.touches.ONE ) {
1317 |
1318 | case TOUCH.ROTATE:
1319 |
1320 | if ( scope.enableRotate === false ) return;
1321 |
1322 | handleTouchStartRotate( event );
1323 |
1324 | state = STATE.TOUCH_ROTATE;
1325 |
1326 | break;
1327 |
1328 | case TOUCH.PAN:
1329 |
1330 | if ( scope.enablePan === false ) return;
1331 |
1332 | handleTouchStartPan( event );
1333 |
1334 | state = STATE.TOUCH_PAN;
1335 |
1336 | break;
1337 |
1338 | default:
1339 |
1340 | state = STATE.NONE;
1341 |
1342 | }
1343 |
1344 | break;
1345 |
1346 | case 2:
1347 |
1348 | switch ( scope.touches.TWO ) {
1349 |
1350 | case TOUCH.DOLLY_PAN:
1351 |
1352 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
1353 |
1354 | handleTouchStartDollyPan( event );
1355 |
1356 | state = STATE.TOUCH_DOLLY_PAN;
1357 |
1358 | break;
1359 |
1360 | case TOUCH.DOLLY_ROTATE:
1361 |
1362 | if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1363 |
1364 | handleTouchStartDollyRotate( event );
1365 |
1366 | state = STATE.TOUCH_DOLLY_ROTATE;
1367 |
1368 | break;
1369 |
1370 | default:
1371 |
1372 | state = STATE.NONE;
1373 |
1374 | }
1375 |
1376 | break;
1377 |
1378 | default:
1379 |
1380 | state = STATE.NONE;
1381 |
1382 | }
1383 |
1384 | if ( state !== STATE.NONE ) {
1385 |
1386 | scope.dispatchEvent( _startEvent );
1387 |
1388 | }
1389 |
1390 | }
1391 |
1392 | function onTouchMove( event ) {
1393 |
1394 | trackPointer( event );
1395 |
1396 | switch ( state ) {
1397 |
1398 | case STATE.TOUCH_ROTATE:
1399 |
1400 | if ( scope.enableRotate === false ) return;
1401 |
1402 | handleTouchMoveRotate( event );
1403 |
1404 | scope.update();
1405 |
1406 | break;
1407 |
1408 | case STATE.TOUCH_PAN:
1409 |
1410 | if ( scope.enablePan === false ) return;
1411 |
1412 | handleTouchMovePan( event );
1413 |
1414 | scope.update();
1415 |
1416 | break;
1417 |
1418 | case STATE.TOUCH_DOLLY_PAN:
1419 |
1420 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
1421 |
1422 | handleTouchMoveDollyPan( event );
1423 |
1424 | scope.update();
1425 |
1426 | break;
1427 |
1428 | case STATE.TOUCH_DOLLY_ROTATE:
1429 |
1430 | if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1431 |
1432 | handleTouchMoveDollyRotate( event );
1433 |
1434 | scope.update();
1435 |
1436 | break;
1437 |
1438 | default:
1439 |
1440 | state = STATE.NONE;
1441 |
1442 | }
1443 |
1444 | }
1445 |
1446 | function onContextMenu( event ) {
1447 |
1448 | if ( scope.enabled === false ) return;
1449 |
1450 | event.preventDefault();
1451 |
1452 | }
1453 |
1454 | function addPointer( event ) {
1455 |
1456 | pointers.push( event.pointerId );
1457 |
1458 | }
1459 |
1460 | function removePointer( event ) {
1461 |
1462 | delete pointerPositions[ event.pointerId ];
1463 |
1464 | for ( let i = 0; i < pointers.length; i ++ ) {
1465 |
1466 | if ( pointers[ i ] == event.pointerId ) {
1467 |
1468 | pointers.splice( i, 1 );
1469 | return;
1470 |
1471 | }
1472 |
1473 | }
1474 |
1475 | }
1476 |
1477 | function isTrackingPointer( event ) {
1478 |
1479 | for ( let i = 0; i < pointers.length; i ++ ) {
1480 |
1481 | if ( pointers[ i ] == event.pointerId ) return true;
1482 |
1483 | }
1484 |
1485 | return false;
1486 |
1487 | }
1488 |
1489 | function trackPointer( event ) {
1490 |
1491 | let position = pointerPositions[ event.pointerId ];
1492 |
1493 | if ( position === undefined ) {
1494 |
1495 | position = new Vector2();
1496 | pointerPositions[ event.pointerId ] = position;
1497 |
1498 | }
1499 |
1500 | position.set( event.pageX, event.pageY );
1501 |
1502 | }
1503 |
1504 | function getSecondPointerPosition( event ) {
1505 |
1506 | const pointerId = ( event.pointerId === pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ];
1507 |
1508 | return pointerPositions[ pointerId ];
1509 |
1510 | }
1511 |
1512 | //
1513 |
1514 | scope.domElement.addEventListener( 'contextmenu', onContextMenu );
1515 |
1516 | scope.domElement.addEventListener( 'pointerdown', onPointerDown );
1517 | scope.domElement.addEventListener( 'pointercancel', onPointerUp );
1518 | scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
1519 |
1520 | const document = scope.domElement.getRootNode(); // offscreen canvas compatibility
1521 |
1522 | document.addEventListener( 'keydown', interceptControlDown, { passive: true, capture: true } );
1523 |
1524 | // force an update at start
1525 |
1526 | this.update();
1527 |
1528 | }
1529 |
1530 | }
1531 |
1532 | export { OrbitControls };
1533 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/PointerLockControls.js:
--------------------------------------------------------------------------------
1 | import {
2 | Euler,
3 | EventDispatcher,
4 | Vector3
5 | } from 'three';
6 |
7 | const _euler = new Euler( 0, 0, 0, 'YXZ' );
8 | const _vector = new Vector3();
9 |
10 | const _changeEvent = { type: 'change' };
11 | const _lockEvent = { type: 'lock' };
12 | const _unlockEvent = { type: 'unlock' };
13 |
14 | const _PI_2 = Math.PI / 2;
15 |
16 | class PointerLockControls extends EventDispatcher {
17 |
18 | constructor( camera, domElement ) {
19 |
20 | super();
21 |
22 | this.camera = camera;
23 | this.domElement = domElement;
24 |
25 | this.isLocked = false;
26 |
27 | // Set to constrain the pitch of the camera
28 | // Range is 0 to Math.PI radians
29 | this.minPolarAngle = 0; // radians
30 | this.maxPolarAngle = Math.PI; // radians
31 |
32 | this.pointerSpeed = 1.0;
33 |
34 | this._onMouseMove = onMouseMove.bind( this );
35 | this._onPointerlockChange = onPointerlockChange.bind( this );
36 | this._onPointerlockError = onPointerlockError.bind( this );
37 |
38 | this.connect();
39 |
40 | }
41 |
42 | connect() {
43 |
44 | this.domElement.ownerDocument.addEventListener( 'mousemove', this._onMouseMove );
45 | this.domElement.ownerDocument.addEventListener( 'pointerlockchange', this._onPointerlockChange );
46 | this.domElement.ownerDocument.addEventListener( 'pointerlockerror', this._onPointerlockError );
47 |
48 | }
49 |
50 | disconnect() {
51 |
52 | this.domElement.ownerDocument.removeEventListener( 'mousemove', this._onMouseMove );
53 | this.domElement.ownerDocument.removeEventListener( 'pointerlockchange', this._onPointerlockChange );
54 | this.domElement.ownerDocument.removeEventListener( 'pointerlockerror', this._onPointerlockError );
55 |
56 | }
57 |
58 | dispose() {
59 |
60 | this.disconnect();
61 |
62 | }
63 |
64 | getObject() { // retaining this method for backward compatibility
65 |
66 | return this.camera;
67 |
68 | }
69 |
70 | getDirection( v ) {
71 |
72 | return v.set( 0, 0, - 1 ).applyQuaternion( this.camera.quaternion );
73 |
74 | }
75 |
76 | moveForward( distance ) {
77 |
78 | // move forward parallel to the xz-plane
79 | // assumes camera.up is y-up
80 |
81 | const camera = this.camera;
82 |
83 | _vector.setFromMatrixColumn( camera.matrix, 0 );
84 |
85 | _vector.crossVectors( camera.up, _vector );
86 |
87 | camera.position.addScaledVector( _vector, distance );
88 |
89 | }
90 |
91 | moveRight( distance ) {
92 |
93 | const camera = this.camera;
94 |
95 | _vector.setFromMatrixColumn( camera.matrix, 0 );
96 |
97 | camera.position.addScaledVector( _vector, distance );
98 |
99 | }
100 |
101 | lock() {
102 |
103 | this.domElement.requestPointerLock();
104 |
105 | }
106 |
107 | unlock() {
108 |
109 | this.domElement.ownerDocument.exitPointerLock();
110 |
111 | }
112 |
113 | }
114 |
115 | // event listeners
116 |
117 | function onMouseMove( event ) {
118 |
119 | if ( this.isLocked === false ) return;
120 |
121 | const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
122 | const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
123 |
124 | const camera = this.camera;
125 | _euler.setFromQuaternion( camera.quaternion );
126 |
127 | _euler.y -= movementX * 0.002 * this.pointerSpeed;
128 | _euler.x -= movementY * 0.002 * this.pointerSpeed;
129 |
130 | _euler.x = Math.max( _PI_2 - this.maxPolarAngle, Math.min( _PI_2 - this.minPolarAngle, _euler.x ) );
131 |
132 | camera.quaternion.setFromEuler( _euler );
133 |
134 | this.dispatchEvent( _changeEvent );
135 |
136 | }
137 |
138 | function onPointerlockChange() {
139 |
140 | if ( this.domElement.ownerDocument.pointerLockElement === this.domElement ) {
141 |
142 | this.dispatchEvent( _lockEvent );
143 |
144 | this.isLocked = true;
145 |
146 | } else {
147 |
148 | this.dispatchEvent( _unlockEvent );
149 |
150 | this.isLocked = false;
151 |
152 | }
153 |
154 | }
155 |
156 | function onPointerlockError() {
157 |
158 | console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
159 |
160 | }
161 |
162 | export { PointerLockControls };
163 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/TrackballControls.js:
--------------------------------------------------------------------------------
1 | import {
2 | EventDispatcher,
3 | MathUtils,
4 | MOUSE,
5 | Quaternion,
6 | Vector2,
7 | Vector3
8 | } from 'three';
9 |
10 | const _changeEvent = { type: 'change' };
11 | const _startEvent = { type: 'start' };
12 | const _endEvent = { type: 'end' };
13 |
14 | class TrackballControls extends EventDispatcher {
15 |
16 | constructor( object, domElement ) {
17 |
18 | super();
19 |
20 | const scope = this;
21 | const STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
22 |
23 | this.object = object;
24 | this.domElement = domElement;
25 | this.domElement.style.touchAction = 'none'; // disable touch scroll
26 |
27 | // API
28 |
29 | this.enabled = true;
30 |
31 | this.screen = { left: 0, top: 0, width: 0, height: 0 };
32 |
33 | this.rotateSpeed = 1.0;
34 | this.zoomSpeed = 1.2;
35 | this.panSpeed = 0.3;
36 |
37 | this.noRotate = false;
38 | this.noZoom = false;
39 | this.noPan = false;
40 |
41 | this.staticMoving = false;
42 | this.dynamicDampingFactor = 0.2;
43 |
44 | this.minDistance = 0;
45 | this.maxDistance = Infinity;
46 |
47 | this.minZoom = 0;
48 | this.maxZoom = Infinity;
49 |
50 | this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ];
51 |
52 | this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
53 |
54 | // internals
55 |
56 | this.target = new Vector3();
57 |
58 | const EPS = 0.000001;
59 |
60 | const lastPosition = new Vector3();
61 | let lastZoom = 1;
62 |
63 | let _state = STATE.NONE,
64 | _keyState = STATE.NONE,
65 |
66 | _touchZoomDistanceStart = 0,
67 | _touchZoomDistanceEnd = 0,
68 |
69 | _lastAngle = 0;
70 |
71 | const _eye = new Vector3(),
72 |
73 | _movePrev = new Vector2(),
74 | _moveCurr = new Vector2(),
75 |
76 | _lastAxis = new Vector3(),
77 |
78 | _zoomStart = new Vector2(),
79 | _zoomEnd = new Vector2(),
80 |
81 | _panStart = new Vector2(),
82 | _panEnd = new Vector2(),
83 |
84 | _pointers = [],
85 | _pointerPositions = {};
86 |
87 | // for reset
88 |
89 | this.target0 = this.target.clone();
90 | this.position0 = this.object.position.clone();
91 | this.up0 = this.object.up.clone();
92 | this.zoom0 = this.object.zoom;
93 |
94 | // methods
95 |
96 | this.handleResize = function () {
97 |
98 | const box = scope.domElement.getBoundingClientRect();
99 | // adjustments come from similar code in the jquery offset() function
100 | const d = scope.domElement.ownerDocument.documentElement;
101 | scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
102 | scope.screen.top = box.top + window.pageYOffset - d.clientTop;
103 | scope.screen.width = box.width;
104 | scope.screen.height = box.height;
105 |
106 | };
107 |
108 | const getMouseOnScreen = ( function () {
109 |
110 | const vector = new Vector2();
111 |
112 | return function getMouseOnScreen( pageX, pageY ) {
113 |
114 | vector.set(
115 | ( pageX - scope.screen.left ) / scope.screen.width,
116 | ( pageY - scope.screen.top ) / scope.screen.height
117 | );
118 |
119 | return vector;
120 |
121 | };
122 |
123 | }() );
124 |
125 | const getMouseOnCircle = ( function () {
126 |
127 | const vector = new Vector2();
128 |
129 | return function getMouseOnCircle( pageX, pageY ) {
130 |
131 | vector.set(
132 | ( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ),
133 | ( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional
134 | );
135 |
136 | return vector;
137 |
138 | };
139 |
140 | }() );
141 |
142 | this.rotateCamera = ( function () {
143 |
144 | const axis = new Vector3(),
145 | quaternion = new Quaternion(),
146 | eyeDirection = new Vector3(),
147 | objectUpDirection = new Vector3(),
148 | objectSidewaysDirection = new Vector3(),
149 | moveDirection = new Vector3();
150 |
151 | return function rotateCamera() {
152 |
153 | moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
154 | let angle = moveDirection.length();
155 |
156 | if ( angle ) {
157 |
158 | _eye.copy( scope.object.position ).sub( scope.target );
159 |
160 | eyeDirection.copy( _eye ).normalize();
161 | objectUpDirection.copy( scope.object.up ).normalize();
162 | objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
163 |
164 | objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
165 | objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
166 |
167 | moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
168 |
169 | axis.crossVectors( moveDirection, _eye ).normalize();
170 |
171 | angle *= scope.rotateSpeed;
172 | quaternion.setFromAxisAngle( axis, angle );
173 |
174 | _eye.applyQuaternion( quaternion );
175 | scope.object.up.applyQuaternion( quaternion );
176 |
177 | _lastAxis.copy( axis );
178 | _lastAngle = angle;
179 |
180 | } else if ( ! scope.staticMoving && _lastAngle ) {
181 |
182 | _lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
183 | _eye.copy( scope.object.position ).sub( scope.target );
184 | quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
185 | _eye.applyQuaternion( quaternion );
186 | scope.object.up.applyQuaternion( quaternion );
187 |
188 | }
189 |
190 | _movePrev.copy( _moveCurr );
191 |
192 | };
193 |
194 | }() );
195 |
196 |
197 | this.zoomCamera = function () {
198 |
199 | let factor;
200 |
201 | if ( _state === STATE.TOUCH_ZOOM_PAN ) {
202 |
203 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
204 | _touchZoomDistanceStart = _touchZoomDistanceEnd;
205 |
206 | if ( scope.object.isPerspectiveCamera ) {
207 |
208 | _eye.multiplyScalar( factor );
209 |
210 | } else if ( scope.object.isOrthographicCamera ) {
211 |
212 | scope.object.zoom = MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom );
213 |
214 | if ( lastZoom !== scope.object.zoom ) {
215 |
216 | scope.object.updateProjectionMatrix();
217 |
218 | }
219 |
220 | } else {
221 |
222 | console.warn( 'THREE.TrackballControls: Unsupported camera type' );
223 |
224 | }
225 |
226 | } else {
227 |
228 | factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
229 |
230 | if ( factor !== 1.0 && factor > 0.0 ) {
231 |
232 | if ( scope.object.isPerspectiveCamera ) {
233 |
234 | _eye.multiplyScalar( factor );
235 |
236 | } else if ( scope.object.isOrthographicCamera ) {
237 |
238 | scope.object.zoom = MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom );
239 |
240 | if ( lastZoom !== scope.object.zoom ) {
241 |
242 | scope.object.updateProjectionMatrix();
243 |
244 | }
245 |
246 | } else {
247 |
248 | console.warn( 'THREE.TrackballControls: Unsupported camera type' );
249 |
250 | }
251 |
252 | }
253 |
254 | if ( scope.staticMoving ) {
255 |
256 | _zoomStart.copy( _zoomEnd );
257 |
258 | } else {
259 |
260 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
261 |
262 | }
263 |
264 | }
265 |
266 | };
267 |
268 | this.panCamera = ( function () {
269 |
270 | const mouseChange = new Vector2(),
271 | objectUp = new Vector3(),
272 | pan = new Vector3();
273 |
274 | return function panCamera() {
275 |
276 | mouseChange.copy( _panEnd ).sub( _panStart );
277 |
278 | if ( mouseChange.lengthSq() ) {
279 |
280 | if ( scope.object.isOrthographicCamera ) {
281 |
282 | const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
283 | const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
284 |
285 | mouseChange.x *= scale_x;
286 | mouseChange.y *= scale_y;
287 |
288 | }
289 |
290 | mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
291 |
292 | pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
293 | pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
294 |
295 | scope.object.position.add( pan );
296 | scope.target.add( pan );
297 |
298 | if ( scope.staticMoving ) {
299 |
300 | _panStart.copy( _panEnd );
301 |
302 | } else {
303 |
304 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
305 |
306 | }
307 |
308 | }
309 |
310 | };
311 |
312 | }() );
313 |
314 | this.checkDistances = function () {
315 |
316 | if ( ! scope.noZoom || ! scope.noPan ) {
317 |
318 | if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
319 |
320 | scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
321 | _zoomStart.copy( _zoomEnd );
322 |
323 | }
324 |
325 | if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
326 |
327 | scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
328 | _zoomStart.copy( _zoomEnd );
329 |
330 | }
331 |
332 | }
333 |
334 | };
335 |
336 | this.update = function () {
337 |
338 | _eye.subVectors( scope.object.position, scope.target );
339 |
340 | if ( ! scope.noRotate ) {
341 |
342 | scope.rotateCamera();
343 |
344 | }
345 |
346 | if ( ! scope.noZoom ) {
347 |
348 | scope.zoomCamera();
349 |
350 | }
351 |
352 | if ( ! scope.noPan ) {
353 |
354 | scope.panCamera();
355 |
356 | }
357 |
358 | scope.object.position.addVectors( scope.target, _eye );
359 |
360 | if ( scope.object.isPerspectiveCamera ) {
361 |
362 | scope.checkDistances();
363 |
364 | scope.object.lookAt( scope.target );
365 |
366 | if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
367 |
368 | scope.dispatchEvent( _changeEvent );
369 |
370 | lastPosition.copy( scope.object.position );
371 |
372 | }
373 |
374 | } else if ( scope.object.isOrthographicCamera ) {
375 |
376 | scope.object.lookAt( scope.target );
377 |
378 | if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
379 |
380 | scope.dispatchEvent( _changeEvent );
381 |
382 | lastPosition.copy( scope.object.position );
383 | lastZoom = scope.object.zoom;
384 |
385 | }
386 |
387 | } else {
388 |
389 | console.warn( 'THREE.TrackballControls: Unsupported camera type' );
390 |
391 | }
392 |
393 | };
394 |
395 | this.reset = function () {
396 |
397 | _state = STATE.NONE;
398 | _keyState = STATE.NONE;
399 |
400 | scope.target.copy( scope.target0 );
401 | scope.object.position.copy( scope.position0 );
402 | scope.object.up.copy( scope.up0 );
403 | scope.object.zoom = scope.zoom0;
404 |
405 | scope.object.updateProjectionMatrix();
406 |
407 | _eye.subVectors( scope.object.position, scope.target );
408 |
409 | scope.object.lookAt( scope.target );
410 |
411 | scope.dispatchEvent( _changeEvent );
412 |
413 | lastPosition.copy( scope.object.position );
414 | lastZoom = scope.object.zoom;
415 |
416 | };
417 |
418 | // listeners
419 |
420 | function onPointerDown( event ) {
421 |
422 | if ( scope.enabled === false ) return;
423 |
424 | if ( _pointers.length === 0 ) {
425 |
426 | scope.domElement.setPointerCapture( event.pointerId );
427 |
428 | scope.domElement.addEventListener( 'pointermove', onPointerMove );
429 | scope.domElement.addEventListener( 'pointerup', onPointerUp );
430 |
431 | }
432 |
433 | //
434 |
435 | addPointer( event );
436 |
437 | if ( event.pointerType === 'touch' ) {
438 |
439 | onTouchStart( event );
440 |
441 | } else {
442 |
443 | onMouseDown( event );
444 |
445 | }
446 |
447 | }
448 |
449 | function onPointerMove( event ) {
450 |
451 | if ( scope.enabled === false ) return;
452 |
453 | if ( event.pointerType === 'touch' ) {
454 |
455 | onTouchMove( event );
456 |
457 | } else {
458 |
459 | onMouseMove( event );
460 |
461 | }
462 |
463 | }
464 |
465 | function onPointerUp( event ) {
466 |
467 | if ( scope.enabled === false ) return;
468 |
469 | if ( event.pointerType === 'touch' ) {
470 |
471 | onTouchEnd( event );
472 |
473 | } else {
474 |
475 | onMouseUp();
476 |
477 | }
478 |
479 | //
480 |
481 | removePointer( event );
482 |
483 | if ( _pointers.length === 0 ) {
484 |
485 | scope.domElement.releasePointerCapture( event.pointerId );
486 |
487 | scope.domElement.removeEventListener( 'pointermove', onPointerMove );
488 | scope.domElement.removeEventListener( 'pointerup', onPointerUp );
489 |
490 | }
491 |
492 |
493 | }
494 |
495 | function onPointerCancel( event ) {
496 |
497 | removePointer( event );
498 |
499 | }
500 |
501 | function keydown( event ) {
502 |
503 | if ( scope.enabled === false ) return;
504 |
505 | window.removeEventListener( 'keydown', keydown );
506 |
507 | if ( _keyState !== STATE.NONE ) {
508 |
509 | return;
510 |
511 | } else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
512 |
513 | _keyState = STATE.ROTATE;
514 |
515 | } else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
516 |
517 | _keyState = STATE.ZOOM;
518 |
519 | } else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
520 |
521 | _keyState = STATE.PAN;
522 |
523 | }
524 |
525 | }
526 |
527 | function keyup() {
528 |
529 | if ( scope.enabled === false ) return;
530 |
531 | _keyState = STATE.NONE;
532 |
533 | window.addEventListener( 'keydown', keydown );
534 |
535 | }
536 |
537 | function onMouseDown( event ) {
538 |
539 | if ( _state === STATE.NONE ) {
540 |
541 | switch ( event.button ) {
542 |
543 | case scope.mouseButtons.LEFT:
544 | _state = STATE.ROTATE;
545 | break;
546 |
547 | case scope.mouseButtons.MIDDLE:
548 | _state = STATE.ZOOM;
549 | break;
550 |
551 | case scope.mouseButtons.RIGHT:
552 | _state = STATE.PAN;
553 | break;
554 |
555 | }
556 |
557 | }
558 |
559 | const state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
560 |
561 | if ( state === STATE.ROTATE && ! scope.noRotate ) {
562 |
563 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
564 | _movePrev.copy( _moveCurr );
565 |
566 | } else if ( state === STATE.ZOOM && ! scope.noZoom ) {
567 |
568 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
569 | _zoomEnd.copy( _zoomStart );
570 |
571 | } else if ( state === STATE.PAN && ! scope.noPan ) {
572 |
573 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
574 | _panEnd.copy( _panStart );
575 |
576 | }
577 |
578 | scope.dispatchEvent( _startEvent );
579 |
580 | }
581 |
582 | function onMouseMove( event ) {
583 |
584 | const state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
585 |
586 | if ( state === STATE.ROTATE && ! scope.noRotate ) {
587 |
588 | _movePrev.copy( _moveCurr );
589 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
590 |
591 | } else if ( state === STATE.ZOOM && ! scope.noZoom ) {
592 |
593 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
594 |
595 | } else if ( state === STATE.PAN && ! scope.noPan ) {
596 |
597 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
598 |
599 | }
600 |
601 | }
602 |
603 | function onMouseUp() {
604 |
605 | _state = STATE.NONE;
606 |
607 | scope.dispatchEvent( _endEvent );
608 |
609 | }
610 |
611 | function onMouseWheel( event ) {
612 |
613 | if ( scope.enabled === false ) return;
614 |
615 | if ( scope.noZoom === true ) return;
616 |
617 | event.preventDefault();
618 |
619 | switch ( event.deltaMode ) {
620 |
621 | case 2:
622 | // Zoom in pages
623 | _zoomStart.y -= event.deltaY * 0.025;
624 | break;
625 |
626 | case 1:
627 | // Zoom in lines
628 | _zoomStart.y -= event.deltaY * 0.01;
629 | break;
630 |
631 | default:
632 | // undefined, 0, assume pixels
633 | _zoomStart.y -= event.deltaY * 0.00025;
634 | break;
635 |
636 | }
637 |
638 | scope.dispatchEvent( _startEvent );
639 | scope.dispatchEvent( _endEvent );
640 |
641 | }
642 |
643 | function onTouchStart( event ) {
644 |
645 | trackPointer( event );
646 |
647 | switch ( _pointers.length ) {
648 |
649 | case 1:
650 | _state = STATE.TOUCH_ROTATE;
651 | _moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) );
652 | _movePrev.copy( _moveCurr );
653 | break;
654 |
655 | default: // 2 or more
656 | _state = STATE.TOUCH_ZOOM_PAN;
657 | const dx = _pointers[ 0 ].pageX - _pointers[ 1 ].pageX;
658 | const dy = _pointers[ 0 ].pageY - _pointers[ 1 ].pageY;
659 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
660 |
661 | const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2;
662 | const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2;
663 | _panStart.copy( getMouseOnScreen( x, y ) );
664 | _panEnd.copy( _panStart );
665 | break;
666 |
667 | }
668 |
669 | scope.dispatchEvent( _startEvent );
670 |
671 | }
672 |
673 | function onTouchMove( event ) {
674 |
675 | trackPointer( event );
676 |
677 | switch ( _pointers.length ) {
678 |
679 | case 1:
680 | _movePrev.copy( _moveCurr );
681 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
682 | break;
683 |
684 | default: // 2 or more
685 |
686 | const position = getSecondPointerPosition( event );
687 |
688 | const dx = event.pageX - position.x;
689 | const dy = event.pageY - position.y;
690 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
691 |
692 | const x = ( event.pageX + position.x ) / 2;
693 | const y = ( event.pageY + position.y ) / 2;
694 | _panEnd.copy( getMouseOnScreen( x, y ) );
695 | break;
696 |
697 | }
698 |
699 | }
700 |
701 | function onTouchEnd( event ) {
702 |
703 | switch ( _pointers.length ) {
704 |
705 | case 0:
706 | _state = STATE.NONE;
707 | break;
708 |
709 | case 1:
710 | _state = STATE.TOUCH_ROTATE;
711 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
712 | _movePrev.copy( _moveCurr );
713 | break;
714 |
715 | case 2:
716 | _state = STATE.TOUCH_ZOOM_PAN;
717 |
718 | for ( let i = 0; i < _pointers.length; i ++ ) {
719 |
720 | if ( _pointers[ i ].pointerId !== event.pointerId ) {
721 |
722 | const position = _pointerPositions[ _pointers[ i ].pointerId ];
723 | _moveCurr.copy( getMouseOnCircle( position.x, position.y ) );
724 | _movePrev.copy( _moveCurr );
725 | break;
726 |
727 | }
728 |
729 | }
730 |
731 | break;
732 |
733 | }
734 |
735 | scope.dispatchEvent( _endEvent );
736 |
737 | }
738 |
739 | function contextmenu( event ) {
740 |
741 | if ( scope.enabled === false ) return;
742 |
743 | event.preventDefault();
744 |
745 | }
746 |
747 | function addPointer( event ) {
748 |
749 | _pointers.push( event );
750 |
751 | }
752 |
753 | function removePointer( event ) {
754 |
755 | delete _pointerPositions[ event.pointerId ];
756 |
757 | for ( let i = 0; i < _pointers.length; i ++ ) {
758 |
759 | if ( _pointers[ i ].pointerId == event.pointerId ) {
760 |
761 | _pointers.splice( i, 1 );
762 | return;
763 |
764 | }
765 |
766 | }
767 |
768 | }
769 |
770 | function trackPointer( event ) {
771 |
772 | let position = _pointerPositions[ event.pointerId ];
773 |
774 | if ( position === undefined ) {
775 |
776 | position = new Vector2();
777 | _pointerPositions[ event.pointerId ] = position;
778 |
779 | }
780 |
781 | position.set( event.pageX, event.pageY );
782 |
783 | }
784 |
785 | function getSecondPointerPosition( event ) {
786 |
787 | const pointer = ( event.pointerId === _pointers[ 0 ].pointerId ) ? _pointers[ 1 ] : _pointers[ 0 ];
788 |
789 | return _pointerPositions[ pointer.pointerId ];
790 |
791 | }
792 |
793 | this.dispose = function () {
794 |
795 | scope.domElement.removeEventListener( 'contextmenu', contextmenu );
796 |
797 | scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
798 | scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
799 | scope.domElement.removeEventListener( 'wheel', onMouseWheel );
800 |
801 | scope.domElement.removeEventListener( 'pointermove', onPointerMove );
802 | scope.domElement.removeEventListener( 'pointerup', onPointerUp );
803 |
804 | window.removeEventListener( 'keydown', keydown );
805 | window.removeEventListener( 'keyup', keyup );
806 |
807 | };
808 |
809 | this.domElement.addEventListener( 'contextmenu', contextmenu );
810 |
811 | this.domElement.addEventListener( 'pointerdown', onPointerDown );
812 | this.domElement.addEventListener( 'pointercancel', onPointerCancel );
813 | this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } );
814 |
815 |
816 | window.addEventListener( 'keydown', keydown );
817 | window.addEventListener( 'keyup', keyup );
818 |
819 | this.handleResize();
820 |
821 | // force an update at start
822 | this.update();
823 |
824 | }
825 |
826 | }
827 |
828 | export { TrackballControls };
829 |
--------------------------------------------------------------------------------
/node_modules/three/examples/jsm/controls/TransformControls.js:
--------------------------------------------------------------------------------
1 | import {
2 | BoxGeometry,
3 | BufferGeometry,
4 | CylinderGeometry,
5 | DoubleSide,
6 | Euler,
7 | Float32BufferAttribute,
8 | Line,
9 | LineBasicMaterial,
10 | Matrix4,
11 | Mesh,
12 | MeshBasicMaterial,
13 | Object3D,
14 | OctahedronGeometry,
15 | PlaneGeometry,
16 | Quaternion,
17 | Raycaster,
18 | SphereGeometry,
19 | TorusGeometry,
20 | Vector3
21 | } from 'three';
22 |
23 | const _raycaster = new Raycaster();
24 |
25 | const _tempVector = new Vector3();
26 | const _tempVector2 = new Vector3();
27 | const _tempQuaternion = new Quaternion();
28 | const _unit = {
29 | X: new Vector3( 1, 0, 0 ),
30 | Y: new Vector3( 0, 1, 0 ),
31 | Z: new Vector3( 0, 0, 1 )
32 | };
33 |
34 | const _changeEvent = { type: 'change' };
35 | const _mouseDownEvent = { type: 'mouseDown' };
36 | const _mouseUpEvent = { type: 'mouseUp', mode: null };
37 | const _objectChangeEvent = { type: 'objectChange' };
38 |
39 | class TransformControls extends Object3D {
40 |
41 | constructor( camera, domElement ) {
42 |
43 | super();
44 |
45 | if ( domElement === undefined ) {
46 |
47 | console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' );
48 | domElement = document;
49 |
50 | }
51 |
52 | this.isTransformControls = true;
53 |
54 | this.visible = false;
55 | this.domElement = domElement;
56 | this.domElement.style.touchAction = 'none'; // disable touch scroll
57 |
58 | const _gizmo = new TransformControlsGizmo();
59 | this._gizmo = _gizmo;
60 | this.add( _gizmo );
61 |
62 | const _plane = new TransformControlsPlane();
63 | this._plane = _plane;
64 | this.add( _plane );
65 |
66 | const scope = this;
67 |
68 | // Defined getter, setter and store for a property
69 | function defineProperty( propName, defaultValue ) {
70 |
71 | let propValue = defaultValue;
72 |
73 | Object.defineProperty( scope, propName, {
74 |
75 | get: function () {
76 |
77 | return propValue !== undefined ? propValue : defaultValue;
78 |
79 | },
80 |
81 | set: function ( value ) {
82 |
83 | if ( propValue !== value ) {
84 |
85 | propValue = value;
86 | _plane[ propName ] = value;
87 | _gizmo[ propName ] = value;
88 |
89 | scope.dispatchEvent( { type: propName + '-changed', value: value } );
90 | scope.dispatchEvent( _changeEvent );
91 |
92 | }
93 |
94 | }
95 |
96 | } );
97 |
98 | scope[ propName ] = defaultValue;
99 | _plane[ propName ] = defaultValue;
100 | _gizmo[ propName ] = defaultValue;
101 |
102 | }
103 |
104 | // Define properties with getters/setter
105 | // Setting the defined property will automatically trigger change event
106 | // Defined properties are passed down to gizmo and plane
107 |
108 | defineProperty( 'camera', camera );
109 | defineProperty( 'object', undefined );
110 | defineProperty( 'enabled', true );
111 | defineProperty( 'axis', null );
112 | defineProperty( 'mode', 'translate' );
113 | defineProperty( 'translationSnap', null );
114 | defineProperty( 'rotationSnap', null );
115 | defineProperty( 'scaleSnap', null );
116 | defineProperty( 'space', 'world' );
117 | defineProperty( 'size', 1 );
118 | defineProperty( 'dragging', false );
119 | defineProperty( 'showX', true );
120 | defineProperty( 'showY', true );
121 | defineProperty( 'showZ', true );
122 |
123 | // Reusable utility variables
124 |
125 | const worldPosition = new Vector3();
126 | const worldPositionStart = new Vector3();
127 | const worldQuaternion = new Quaternion();
128 | const worldQuaternionStart = new Quaternion();
129 | const cameraPosition = new Vector3();
130 | const cameraQuaternion = new Quaternion();
131 | const pointStart = new Vector3();
132 | const pointEnd = new Vector3();
133 | const rotationAxis = new Vector3();
134 | const rotationAngle = 0;
135 | const eye = new Vector3();
136 |
137 | // TODO: remove properties unused in plane and gizmo
138 |
139 | defineProperty( 'worldPosition', worldPosition );
140 | defineProperty( 'worldPositionStart', worldPositionStart );
141 | defineProperty( 'worldQuaternion', worldQuaternion );
142 | defineProperty( 'worldQuaternionStart', worldQuaternionStart );
143 | defineProperty( 'cameraPosition', cameraPosition );
144 | defineProperty( 'cameraQuaternion', cameraQuaternion );
145 | defineProperty( 'pointStart', pointStart );
146 | defineProperty( 'pointEnd', pointEnd );
147 | defineProperty( 'rotationAxis', rotationAxis );
148 | defineProperty( 'rotationAngle', rotationAngle );
149 | defineProperty( 'eye', eye );
150 |
151 | this._offset = new Vector3();
152 | this._startNorm = new Vector3();
153 | this._endNorm = new Vector3();
154 | this._cameraScale = new Vector3();
155 |
156 | this._parentPosition = new Vector3();
157 | this._parentQuaternion = new Quaternion();
158 | this._parentQuaternionInv = new Quaternion();
159 | this._parentScale = new Vector3();
160 |
161 | this._worldScaleStart = new Vector3();
162 | this._worldQuaternionInv = new Quaternion();
163 | this._worldScale = new Vector3();
164 |
165 | this._positionStart = new Vector3();
166 | this._quaternionStart = new Quaternion();
167 | this._scaleStart = new Vector3();
168 |
169 | this._getPointer = getPointer.bind( this );
170 | this._onPointerDown = onPointerDown.bind( this );
171 | this._onPointerHover = onPointerHover.bind( this );
172 | this._onPointerMove = onPointerMove.bind( this );
173 | this._onPointerUp = onPointerUp.bind( this );
174 |
175 | this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
176 | this.domElement.addEventListener( 'pointermove', this._onPointerHover );
177 | this.domElement.addEventListener( 'pointerup', this._onPointerUp );
178 |
179 | }
180 |
181 | // updateMatrixWorld updates key transformation variables
182 | updateMatrixWorld( force ) {
183 |
184 | if ( this.object !== undefined ) {
185 |
186 | this.object.updateMatrixWorld();
187 |
188 | if ( this.object.parent === null ) {
189 |
190 | console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' );
191 |
192 | } else {
193 |
194 | this.object.parent.matrixWorld.decompose( this._parentPosition, this._parentQuaternion, this._parentScale );
195 |
196 | }
197 |
198 | this.object.matrixWorld.decompose( this.worldPosition, this.worldQuaternion, this._worldScale );
199 |
200 | this._parentQuaternionInv.copy( this._parentQuaternion ).invert();
201 | this._worldQuaternionInv.copy( this.worldQuaternion ).invert();
202 |
203 | }
204 |
205 | this.camera.updateMatrixWorld();
206 | this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale );
207 |
208 | if ( this.camera.isOrthographicCamera ) {
209 |
210 | this.camera.getWorldDirection( this.eye ).negate();
211 |
212 | } else {
213 |
214 | this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize();
215 |
216 | }
217 |
218 | super.updateMatrixWorld( force );
219 |
220 | }
221 |
222 | pointerHover( pointer ) {
223 |
224 | if ( this.object === undefined || this.dragging === true ) return;
225 |
226 | if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
227 |
228 | const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster );
229 |
230 | if ( intersect ) {
231 |
232 | this.axis = intersect.object.name;
233 |
234 | } else {
235 |
236 | this.axis = null;
237 |
238 | }
239 |
240 | }
241 |
242 | pointerDown( pointer ) {
243 |
244 | if ( this.object === undefined || this.dragging === true || ( pointer != null && pointer.button !== 0 ) ) return;
245 |
246 | if ( this.axis !== null ) {
247 |
248 | if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
249 |
250 | const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
251 |
252 | if ( planeIntersect ) {
253 |
254 | this.object.updateMatrixWorld();
255 | this.object.parent.updateMatrixWorld();
256 |
257 | this._positionStart.copy( this.object.position );
258 | this._quaternionStart.copy( this.object.quaternion );
259 | this._scaleStart.copy( this.object.scale );
260 |
261 | this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart );
262 |
263 | this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart );
264 |
265 | }
266 |
267 | this.dragging = true;
268 | _mouseDownEvent.mode = this.mode;
269 | this.dispatchEvent( _mouseDownEvent );
270 |
271 | }
272 |
273 | }
274 |
275 | pointerMove( pointer ) {
276 |
277 | const axis = this.axis;
278 | const mode = this.mode;
279 | const object = this.object;
280 | let space = this.space;
281 |
282 | if ( mode === 'scale' ) {
283 |
284 | space = 'local';
285 |
286 | } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) {
287 |
288 | space = 'world';
289 |
290 | }
291 |
292 | if ( object === undefined || axis === null || this.dragging === false || ( pointer !== null && pointer.button !== - 1 ) ) return;
293 |
294 | if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
295 |
296 | const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
297 |
298 | if ( ! planeIntersect ) return;
299 |
300 | this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart );
301 |
302 | if ( mode === 'translate' ) {
303 |
304 | // Apply translate
305 |
306 | this._offset.copy( this.pointEnd ).sub( this.pointStart );
307 |
308 | if ( space === 'local' && axis !== 'XYZ' ) {
309 |
310 | this._offset.applyQuaternion( this._worldQuaternionInv );
311 |
312 | }
313 |
314 | if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0;
315 | if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0;
316 | if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0;
317 |
318 | if ( space === 'local' && axis !== 'XYZ' ) {
319 |
320 | this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale );
321 |
322 | } else {
323 |
324 | this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale );
325 |
326 | }
327 |
328 | object.position.copy( this._offset ).add( this._positionStart );
329 |
330 | // Apply translation snap
331 |
332 | if ( this.translationSnap ) {
333 |
334 | if ( space === 'local' ) {
335 |
336 | object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() );
337 |
338 | if ( axis.search( 'X' ) !== - 1 ) {
339 |
340 | object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
341 |
342 | }
343 |
344 | if ( axis.search( 'Y' ) !== - 1 ) {
345 |
346 | object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
347 |
348 | }
349 |
350 | if ( axis.search( 'Z' ) !== - 1 ) {
351 |
352 | object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
353 |
354 | }
355 |
356 | object.position.applyQuaternion( this._quaternionStart );
357 |
358 | }
359 |
360 | if ( space === 'world' ) {
361 |
362 | if ( object.parent ) {
363 |
364 | object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
365 |
366 | }
367 |
368 | if ( axis.search( 'X' ) !== - 1 ) {
369 |
370 | object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
371 |
372 | }
373 |
374 | if ( axis.search( 'Y' ) !== - 1 ) {
375 |
376 | object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
377 |
378 | }
379 |
380 | if ( axis.search( 'Z' ) !== - 1 ) {
381 |
382 | object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
383 |
384 | }
385 |
386 | if ( object.parent ) {
387 |
388 | object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
389 |
390 | }
391 |
392 | }
393 |
394 | }
395 |
396 | } else if ( mode === 'scale' ) {
397 |
398 | if ( axis.search( 'XYZ' ) !== - 1 ) {
399 |
400 | let d = this.pointEnd.length() / this.pointStart.length();
401 |
402 | if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1;
403 |
404 | _tempVector2.set( d, d, d );
405 |
406 | } else {
407 |
408 | _tempVector.copy( this.pointStart );
409 | _tempVector2.copy( this.pointEnd );
410 |
411 | _tempVector.applyQuaternion( this._worldQuaternionInv );
412 | _tempVector2.applyQuaternion( this._worldQuaternionInv );
413 |
414 | _tempVector2.divide( _tempVector );
415 |
416 | if ( axis.search( 'X' ) === - 1 ) {
417 |
418 | _tempVector2.x = 1;
419 |
420 | }
421 |
422 | if ( axis.search( 'Y' ) === - 1 ) {
423 |
424 | _tempVector2.y = 1;
425 |
426 | }
427 |
428 | if ( axis.search( 'Z' ) === - 1 ) {
429 |
430 | _tempVector2.z = 1;
431 |
432 | }
433 |
434 | }
435 |
436 | // Apply scale
437 |
438 | object.scale.copy( this._scaleStart ).multiply( _tempVector2 );
439 |
440 | if ( this.scaleSnap ) {
441 |
442 | if ( axis.search( 'X' ) !== - 1 ) {
443 |
444 | object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
445 |
446 | }
447 |
448 | if ( axis.search( 'Y' ) !== - 1 ) {
449 |
450 | object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
451 |
452 | }
453 |
454 | if ( axis.search( 'Z' ) !== - 1 ) {
455 |
456 | object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
457 |
458 | }
459 |
460 | }
461 |
462 | } else if ( mode === 'rotate' ) {
463 |
464 | this._offset.copy( this.pointEnd ).sub( this.pointStart );
465 |
466 | const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
467 |
468 | let _inPlaneRotation = false;
469 |
470 | if ( axis === 'XYZE' ) {
471 |
472 | this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize();
473 | this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
474 |
475 | } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
476 |
477 | this.rotationAxis.copy( _unit[ axis ] );
478 |
479 | _tempVector.copy( _unit[ axis ] );
480 |
481 | if ( space === 'local' ) {
482 |
483 | _tempVector.applyQuaternion( this.worldQuaternion );
484 |
485 | }
486 |
487 | _tempVector.cross( this.eye );
488 |
489 | // When _tempVector is 0 after cross with this.eye the vectors are parallel and should use in-plane rotation logic.
490 | if ( _tempVector.length() === 0 ) {
491 |
492 | _inPlaneRotation = true;
493 |
494 | } else {
495 |
496 | this.rotationAngle = this._offset.dot( _tempVector.normalize() ) * ROTATION_SPEED;
497 |
498 | }
499 |
500 |
501 | }
502 |
503 | if ( axis === 'E' || _inPlaneRotation ) {
504 |
505 | this.rotationAxis.copy( this.eye );
506 | this.rotationAngle = this.pointEnd.angleTo( this.pointStart );
507 |
508 | this._startNorm.copy( this.pointStart ).normalize();
509 | this._endNorm.copy( this.pointEnd ).normalize();
510 |
511 | this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 );
512 |
513 | }
514 |
515 | // Apply rotation snap
516 |
517 | if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap;
518 |
519 | // Apply rotate
520 | if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) {
521 |
522 | object.quaternion.copy( this._quaternionStart );
523 | object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize();
524 |
525 | } else {
526 |
527 | this.rotationAxis.applyQuaternion( this._parentQuaternionInv );
528 | object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) );
529 | object.quaternion.multiply( this._quaternionStart ).normalize();
530 |
531 | }
532 |
533 | }
534 |
535 | this.dispatchEvent( _changeEvent );
536 | this.dispatchEvent( _objectChangeEvent );
537 |
538 | }
539 |
540 | pointerUp( pointer ) {
541 |
542 | if ( pointer !== null && pointer.button !== 0 ) return;
543 |
544 | if ( this.dragging && ( this.axis !== null ) ) {
545 |
546 | _mouseUpEvent.mode = this.mode;
547 | this.dispatchEvent( _mouseUpEvent );
548 |
549 | }
550 |
551 | this.dragging = false;
552 | this.axis = null;
553 |
554 | }
555 |
556 | dispose() {
557 |
558 | this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
559 | this.domElement.removeEventListener( 'pointermove', this._onPointerHover );
560 | this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
561 | this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
562 |
563 | this.traverse( function ( child ) {
564 |
565 | if ( child.geometry ) child.geometry.dispose();
566 | if ( child.material ) child.material.dispose();
567 |
568 | } );
569 |
570 | }
571 |
572 | // Set current object
573 | attach( object ) {
574 |
575 | this.object = object;
576 | this.visible = true;
577 |
578 | return this;
579 |
580 | }
581 |
582 | // Detach from object
583 | detach() {
584 |
585 | this.object = undefined;
586 | this.visible = false;
587 | this.axis = null;
588 |
589 | return this;
590 |
591 | }
592 |
593 | reset() {
594 |
595 | if ( ! this.enabled ) return;
596 |
597 | if ( this.dragging ) {
598 |
599 | this.object.position.copy( this._positionStart );
600 | this.object.quaternion.copy( this._quaternionStart );
601 | this.object.scale.copy( this._scaleStart );
602 |
603 | this.dispatchEvent( _changeEvent );
604 | this.dispatchEvent( _objectChangeEvent );
605 |
606 | this.pointStart.copy( this.pointEnd );
607 |
608 | }
609 |
610 | }
611 |
612 | getRaycaster() {
613 |
614 | return _raycaster;
615 |
616 | }
617 |
618 | // TODO: deprecate
619 |
620 | getMode() {
621 |
622 | return this.mode;
623 |
624 | }
625 |
626 | setMode( mode ) {
627 |
628 | this.mode = mode;
629 |
630 | }
631 |
632 | setTranslationSnap( translationSnap ) {
633 |
634 | this.translationSnap = translationSnap;
635 |
636 | }
637 |
638 | setRotationSnap( rotationSnap ) {
639 |
640 | this.rotationSnap = rotationSnap;
641 |
642 | }
643 |
644 | setScaleSnap( scaleSnap ) {
645 |
646 | this.scaleSnap = scaleSnap;
647 |
648 | }
649 |
650 | setSize( size ) {
651 |
652 | this.size = size;
653 |
654 | }
655 |
656 | setSpace( space ) {
657 |
658 | this.space = space;
659 |
660 | }
661 |
662 | }
663 |
664 | // mouse / touch event handlers
665 |
666 | function getPointer( event ) {
667 |
668 | if ( this.domElement.ownerDocument.pointerLockElement ) {
669 |
670 | return {
671 | x: 0,
672 | y: 0,
673 | button: event.button
674 | };
675 |
676 | } else {
677 |
678 | const rect = this.domElement.getBoundingClientRect();
679 |
680 | return {
681 | x: ( event.clientX - rect.left ) / rect.width * 2 - 1,
682 | y: - ( event.clientY - rect.top ) / rect.height * 2 + 1,
683 | button: event.button
684 | };
685 |
686 | }
687 |
688 | }
689 |
690 | function onPointerHover( event ) {
691 |
692 | if ( ! this.enabled ) return;
693 |
694 | switch ( event.pointerType ) {
695 |
696 | case 'mouse':
697 | case 'pen':
698 | this.pointerHover( this._getPointer( event ) );
699 | break;
700 |
701 | }
702 |
703 | }
704 |
705 | function onPointerDown( event ) {
706 |
707 | if ( ! this.enabled ) return;
708 |
709 | if ( ! document.pointerLockElement ) {
710 |
711 | this.domElement.setPointerCapture( event.pointerId );
712 |
713 | }
714 |
715 | this.domElement.addEventListener( 'pointermove', this._onPointerMove );
716 |
717 | this.pointerHover( this._getPointer( event ) );
718 | this.pointerDown( this._getPointer( event ) );
719 |
720 | }
721 |
722 | function onPointerMove( event ) {
723 |
724 | if ( ! this.enabled ) return;
725 |
726 | this.pointerMove( this._getPointer( event ) );
727 |
728 | }
729 |
730 | function onPointerUp( event ) {
731 |
732 | if ( ! this.enabled ) return;
733 |
734 | this.domElement.releasePointerCapture( event.pointerId );
735 |
736 | this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
737 |
738 | this.pointerUp( this._getPointer( event ) );
739 |
740 | }
741 |
742 | function intersectObjectWithRay( object, raycaster, includeInvisible ) {
743 |
744 | const allIntersections = raycaster.intersectObject( object, true );
745 |
746 | for ( let i = 0; i < allIntersections.length; i ++ ) {
747 |
748 | if ( allIntersections[ i ].object.visible || includeInvisible ) {
749 |
750 | return allIntersections[ i ];
751 |
752 | }
753 |
754 | }
755 |
756 | return false;
757 |
758 | }
759 |
760 | //
761 |
762 | // Reusable utility variables
763 |
764 | const _tempEuler = new Euler();
765 | const _alignVector = new Vector3( 0, 1, 0 );
766 | const _zeroVector = new Vector3( 0, 0, 0 );
767 | const _lookAtMatrix = new Matrix4();
768 | const _tempQuaternion2 = new Quaternion();
769 | const _identityQuaternion = new Quaternion();
770 | const _dirVector = new Vector3();
771 | const _tempMatrix = new Matrix4();
772 |
773 | const _unitX = new Vector3( 1, 0, 0 );
774 | const _unitY = new Vector3( 0, 1, 0 );
775 | const _unitZ = new Vector3( 0, 0, 1 );
776 |
777 | const _v1 = new Vector3();
778 | const _v2 = new Vector3();
779 | const _v3 = new Vector3();
780 |
781 | class TransformControlsGizmo extends Object3D {
782 |
783 | constructor() {
784 |
785 | super();
786 |
787 | this.isTransformControlsGizmo = true;
788 |
789 | this.type = 'TransformControlsGizmo';
790 |
791 | // shared materials
792 |
793 | const gizmoMaterial = new MeshBasicMaterial( {
794 | depthTest: false,
795 | depthWrite: false,
796 | fog: false,
797 | toneMapped: false,
798 | transparent: true
799 | } );
800 |
801 | const gizmoLineMaterial = new LineBasicMaterial( {
802 | depthTest: false,
803 | depthWrite: false,
804 | fog: false,
805 | toneMapped: false,
806 | transparent: true
807 | } );
808 |
809 | // Make unique material for each axis/color
810 |
811 | const matInvisible = gizmoMaterial.clone();
812 | matInvisible.opacity = 0.15;
813 |
814 | const matHelper = gizmoLineMaterial.clone();
815 | matHelper.opacity = 0.5;
816 |
817 | const matRed = gizmoMaterial.clone();
818 | matRed.color.setHex( 0xff0000 );
819 |
820 | const matGreen = gizmoMaterial.clone();
821 | matGreen.color.setHex( 0x00ff00 );
822 |
823 | const matBlue = gizmoMaterial.clone();
824 | matBlue.color.setHex( 0x0000ff );
825 |
826 | const matRedTransparent = gizmoMaterial.clone();
827 | matRedTransparent.color.setHex( 0xff0000 );
828 | matRedTransparent.opacity = 0.5;
829 |
830 | const matGreenTransparent = gizmoMaterial.clone();
831 | matGreenTransparent.color.setHex( 0x00ff00 );
832 | matGreenTransparent.opacity = 0.5;
833 |
834 | const matBlueTransparent = gizmoMaterial.clone();
835 | matBlueTransparent.color.setHex( 0x0000ff );
836 | matBlueTransparent.opacity = 0.5;
837 |
838 | const matWhiteTransparent = gizmoMaterial.clone();
839 | matWhiteTransparent.opacity = 0.25;
840 |
841 | const matYellowTransparent = gizmoMaterial.clone();
842 | matYellowTransparent.color.setHex( 0xffff00 );
843 | matYellowTransparent.opacity = 0.25;
844 |
845 | const matYellow = gizmoMaterial.clone();
846 | matYellow.color.setHex( 0xffff00 );
847 |
848 | const matGray = gizmoMaterial.clone();
849 | matGray.color.setHex( 0x787878 );
850 |
851 | // reusable geometry
852 |
853 | const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 );
854 | arrowGeometry.translate( 0, 0.05, 0 );
855 |
856 | const scaleHandleGeometry = new BoxGeometry( 0.08, 0.08, 0.08 );
857 | scaleHandleGeometry.translate( 0, 0.04, 0 );
858 |
859 | const lineGeometry = new BufferGeometry();
860 | lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) );
861 |
862 | const lineGeometry2 = new CylinderGeometry( 0.0075, 0.0075, 0.5, 3 );
863 | lineGeometry2.translate( 0, 0.25, 0 );
864 |
865 | function CircleGeometry( radius, arc ) {
866 |
867 | const geometry = new TorusGeometry( radius, 0.0075, 3, 64, arc * Math.PI * 2 );
868 | geometry.rotateY( Math.PI / 2 );
869 | geometry.rotateX( Math.PI / 2 );
870 | return geometry;
871 |
872 | }
873 |
874 | // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position
875 |
876 | function TranslateHelperGeometry() {
877 |
878 | const geometry = new BufferGeometry();
879 |
880 | geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
881 |
882 | return geometry;
883 |
884 | }
885 |
886 | // Gizmo definitions - custom hierarchy definitions for setupGizmo() function
887 |
888 | const gizmoTranslate = {
889 | X: [
890 | [ new Mesh( arrowGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
891 | [ new Mesh( arrowGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]],
892 | [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]
893 | ],
894 | Y: [
895 | [ new Mesh( arrowGeometry, matGreen ), [ 0, 0.5, 0 ]],
896 | [ new Mesh( arrowGeometry, matGreen ), [ 0, - 0.5, 0 ], [ Math.PI, 0, 0 ]],
897 | [ new Mesh( lineGeometry2, matGreen ) ]
898 | ],
899 | Z: [
900 | [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]],
901 | [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]],
902 | [ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]]
903 | ],
904 | XYZ: [
905 | [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]]
906 | ],
907 | XY: [
908 | [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]]
909 | ],
910 | YZ: [
911 | [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
912 | ],
913 | XZ: [
914 | [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
915 | ]
916 | };
917 |
918 | const pickerTranslate = {
919 | X: [
920 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
921 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]]
922 | ],
923 | Y: [
924 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]],
925 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]]
926 | ],
927 | Z: [
928 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]],
929 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]]
930 | ],
931 | XYZ: [
932 | [ new Mesh( new OctahedronGeometry( 0.2, 0 ), matInvisible ) ]
933 | ],
934 | XY: [
935 | [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]]
936 | ],
937 | YZ: [
938 | [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
939 | ],
940 | XZ: [
941 | [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
942 | ]
943 | };
944 |
945 | const helperTranslate = {
946 | START: [
947 | [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
948 | ],
949 | END: [
950 | [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
951 | ],
952 | DELTA: [
953 | [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]
954 | ],
955 | X: [
956 | [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
957 | ],
958 | Y: [
959 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
960 | ],
961 | Z: [
962 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
963 | ]
964 | };
965 |
966 | const gizmoRotate = {
967 | XYZE: [
968 | [ new Mesh( CircleGeometry( 0.5, 1 ), matGray ), null, [ 0, Math.PI / 2, 0 ]]
969 | ],
970 | X: [
971 | [ new Mesh( CircleGeometry( 0.5, 0.5 ), matRed ) ]
972 | ],
973 | Y: [
974 | [ new Mesh( CircleGeometry( 0.5, 0.5 ), matGreen ), null, [ 0, 0, - Math.PI / 2 ]]
975 | ],
976 | Z: [
977 | [ new Mesh( CircleGeometry( 0.5, 0.5 ), matBlue ), null, [ 0, Math.PI / 2, 0 ]]
978 | ],
979 | E: [
980 | [ new Mesh( CircleGeometry( 0.75, 1 ), matYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]]
981 | ]
982 | };
983 |
984 | const helperRotate = {
985 | AXIS: [
986 | [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
987 | ]
988 | };
989 |
990 | const pickerRotate = {
991 | XYZE: [
992 | [ new Mesh( new SphereGeometry( 0.25, 10, 8 ), matInvisible ) ]
993 | ],
994 | X: [
995 | [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]],
996 | ],
997 | Y: [
998 | [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
999 | ],
1000 | Z: [
1001 | [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
1002 | ],
1003 | E: [
1004 | [ new Mesh( new TorusGeometry( 0.75, 0.1, 2, 24 ), matInvisible ) ]
1005 | ]
1006 | };
1007 |
1008 | const gizmoScale = {
1009 | X: [
1010 | [ new Mesh( scaleHandleGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
1011 | [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
1012 | [ new Mesh( scaleHandleGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]],
1013 | ],
1014 | Y: [
1015 | [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.5, 0 ]],
1016 | [ new Mesh( lineGeometry2, matGreen ) ],
1017 | [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, - 0.5, 0 ], [ 0, 0, Math.PI ]],
1018 | ],
1019 | Z: [
1020 | [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]],
1021 | [ new Mesh( lineGeometry2, matBlue ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
1022 | [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]]
1023 | ],
1024 | XY: [
1025 | [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]]
1026 | ],
1027 | YZ: [
1028 | [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
1029 | ],
1030 | XZ: [
1031 | [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
1032 | ],
1033 | XYZ: [
1034 | [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ],
1035 | ]
1036 | };
1037 |
1038 | const pickerScale = {
1039 | X: [
1040 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
1041 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]]
1042 | ],
1043 | Y: [
1044 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]],
1045 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]]
1046 | ],
1047 | Z: [
1048 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]],
1049 | [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]]
1050 | ],
1051 | XY: [
1052 | [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]],
1053 | ],
1054 | YZ: [
1055 | [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]],
1056 | ],
1057 | XZ: [
1058 | [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]],
1059 | ],
1060 | XYZ: [
1061 | [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 0 ]],
1062 | ]
1063 | };
1064 |
1065 | const helperScale = {
1066 | X: [
1067 | [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
1068 | ],
1069 | Y: [
1070 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
1071 | ],
1072 | Z: [
1073 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
1074 | ]
1075 | };
1076 |
1077 | // Creates an Object3D with gizmos described in custom hierarchy definition.
1078 |
1079 | function setupGizmo( gizmoMap ) {
1080 |
1081 | const gizmo = new Object3D();
1082 |
1083 | for ( const name in gizmoMap ) {
1084 |
1085 | for ( let i = gizmoMap[ name ].length; i --; ) {
1086 |
1087 | const object = gizmoMap[ name ][ i ][ 0 ].clone();
1088 | const position = gizmoMap[ name ][ i ][ 1 ];
1089 | const rotation = gizmoMap[ name ][ i ][ 2 ];
1090 | const scale = gizmoMap[ name ][ i ][ 3 ];
1091 | const tag = gizmoMap[ name ][ i ][ 4 ];
1092 |
1093 | // name and tag properties are essential for picking and updating logic.
1094 | object.name = name;
1095 | object.tag = tag;
1096 |
1097 | if ( position ) {
1098 |
1099 | object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] );
1100 |
1101 | }
1102 |
1103 | if ( rotation ) {
1104 |
1105 | object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] );
1106 |
1107 | }
1108 |
1109 | if ( scale ) {
1110 |
1111 | object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] );
1112 |
1113 | }
1114 |
1115 | object.updateMatrix();
1116 |
1117 | const tempGeometry = object.geometry.clone();
1118 | tempGeometry.applyMatrix4( object.matrix );
1119 | object.geometry = tempGeometry;
1120 | object.renderOrder = Infinity;
1121 |
1122 | object.position.set( 0, 0, 0 );
1123 | object.rotation.set( 0, 0, 0 );
1124 | object.scale.set( 1, 1, 1 );
1125 |
1126 | gizmo.add( object );
1127 |
1128 | }
1129 |
1130 | }
1131 |
1132 | return gizmo;
1133 |
1134 | }
1135 |
1136 | // Gizmo creation
1137 |
1138 | this.gizmo = {};
1139 | this.picker = {};
1140 | this.helper = {};
1141 |
1142 | this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) );
1143 | this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) );
1144 | this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) );
1145 | this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) );
1146 | this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) );
1147 | this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) );
1148 | this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) );
1149 | this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) );
1150 | this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) );
1151 |
1152 | // Pickers should be hidden always
1153 |
1154 | this.picker[ 'translate' ].visible = false;
1155 | this.picker[ 'rotate' ].visible = false;
1156 | this.picker[ 'scale' ].visible = false;
1157 |
1158 | }
1159 |
1160 | // updateMatrixWorld will update transformations and appearance of individual handles
1161 |
1162 | updateMatrixWorld( force ) {
1163 |
1164 | const space = ( this.mode === 'scale' ) ? 'local' : this.space; // scale always oriented to local rotation
1165 |
1166 | const quaternion = ( space === 'local' ) ? this.worldQuaternion : _identityQuaternion;
1167 |
1168 | // Show only gizmos for current transform mode
1169 |
1170 | this.gizmo[ 'translate' ].visible = this.mode === 'translate';
1171 | this.gizmo[ 'rotate' ].visible = this.mode === 'rotate';
1172 | this.gizmo[ 'scale' ].visible = this.mode === 'scale';
1173 |
1174 | this.helper[ 'translate' ].visible = this.mode === 'translate';
1175 | this.helper[ 'rotate' ].visible = this.mode === 'rotate';
1176 | this.helper[ 'scale' ].visible = this.mode === 'scale';
1177 |
1178 |
1179 | let handles = [];
1180 | handles = handles.concat( this.picker[ this.mode ].children );
1181 | handles = handles.concat( this.gizmo[ this.mode ].children );
1182 | handles = handles.concat( this.helper[ this.mode ].children );
1183 |
1184 | for ( let i = 0; i < handles.length; i ++ ) {
1185 |
1186 | const handle = handles[ i ];
1187 |
1188 | // hide aligned to camera
1189 |
1190 | handle.visible = true;
1191 | handle.rotation.set( 0, 0, 0 );
1192 | handle.position.copy( this.worldPosition );
1193 |
1194 | let factor;
1195 |
1196 | if ( this.camera.isOrthographicCamera ) {
1197 |
1198 | factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
1199 |
1200 | } else {
1201 |
1202 | factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
1203 |
1204 | }
1205 |
1206 | handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 4 );
1207 |
1208 | // TODO: simplify helpers and consider decoupling from gizmo
1209 |
1210 | if ( handle.tag === 'helper' ) {
1211 |
1212 | handle.visible = false;
1213 |
1214 | if ( handle.name === 'AXIS' ) {
1215 |
1216 | handle.visible = !! this.axis;
1217 |
1218 | if ( this.axis === 'X' ) {
1219 |
1220 | _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) );
1221 | handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
1222 |
1223 | if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
1224 |
1225 | handle.visible = false;
1226 |
1227 | }
1228 |
1229 | }
1230 |
1231 | if ( this.axis === 'Y' ) {
1232 |
1233 | _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, Math.PI / 2 ) );
1234 | handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
1235 |
1236 | if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
1237 |
1238 | handle.visible = false;
1239 |
1240 | }
1241 |
1242 | }
1243 |
1244 | if ( this.axis === 'Z' ) {
1245 |
1246 | _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) );
1247 | handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
1248 |
1249 | if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
1250 |
1251 | handle.visible = false;
1252 |
1253 | }
1254 |
1255 | }
1256 |
1257 | if ( this.axis === 'XYZE' ) {
1258 |
1259 | _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) );
1260 | _alignVector.copy( this.rotationAxis );
1261 | handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) );
1262 | handle.quaternion.multiply( _tempQuaternion );
1263 | handle.visible = this.dragging;
1264 |
1265 | }
1266 |
1267 | if ( this.axis === 'E' ) {
1268 |
1269 | handle.visible = false;
1270 |
1271 | }
1272 |
1273 |
1274 | } else if ( handle.name === 'START' ) {
1275 |
1276 | handle.position.copy( this.worldPositionStart );
1277 | handle.visible = this.dragging;
1278 |
1279 | } else if ( handle.name === 'END' ) {
1280 |
1281 | handle.position.copy( this.worldPosition );
1282 | handle.visible = this.dragging;
1283 |
1284 | } else if ( handle.name === 'DELTA' ) {
1285 |
1286 | handle.position.copy( this.worldPositionStart );
1287 | handle.quaternion.copy( this.worldQuaternionStart );
1288 | _tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 );
1289 | _tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() );
1290 | handle.scale.copy( _tempVector );
1291 | handle.visible = this.dragging;
1292 |
1293 | } else {
1294 |
1295 | handle.quaternion.copy( quaternion );
1296 |
1297 | if ( this.dragging ) {
1298 |
1299 | handle.position.copy( this.worldPositionStart );
1300 |
1301 | } else {
1302 |
1303 | handle.position.copy( this.worldPosition );
1304 |
1305 | }
1306 |
1307 | if ( this.axis ) {
1308 |
1309 | handle.visible = this.axis.search( handle.name ) !== - 1;
1310 |
1311 | }
1312 |
1313 | }
1314 |
1315 | // If updating helper, skip rest of the loop
1316 | continue;
1317 |
1318 | }
1319 |
1320 | // Align handles to current local or world rotation
1321 |
1322 | handle.quaternion.copy( quaternion );
1323 |
1324 | if ( this.mode === 'translate' || this.mode === 'scale' ) {
1325 |
1326 | // Hide translate and scale axis facing the camera
1327 |
1328 | const AXIS_HIDE_THRESHOLD = 0.99;
1329 | const PLANE_HIDE_THRESHOLD = 0.2;
1330 |
1331 | if ( handle.name === 'X' ) {
1332 |
1333 | if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) {
1334 |
1335 | handle.scale.set( 1e-10, 1e-10, 1e-10 );
1336 | handle.visible = false;
1337 |
1338 | }
1339 |
1340 | }
1341 |
1342 | if ( handle.name === 'Y' ) {
1343 |
1344 | if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) {
1345 |
1346 | handle.scale.set( 1e-10, 1e-10, 1e-10 );
1347 | handle.visible = false;
1348 |
1349 | }
1350 |
1351 | }
1352 |
1353 | if ( handle.name === 'Z' ) {
1354 |
1355 | if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) {
1356 |
1357 | handle.scale.set( 1e-10, 1e-10, 1e-10 );
1358 | handle.visible = false;
1359 |
1360 | }
1361 |
1362 | }
1363 |
1364 | if ( handle.name === 'XY' ) {
1365 |
1366 | if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) {
1367 |
1368 | handle.scale.set( 1e-10, 1e-10, 1e-10 );
1369 | handle.visible = false;
1370 |
1371 | }
1372 |
1373 | }
1374 |
1375 | if ( handle.name === 'YZ' ) {
1376 |
1377 | if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) {
1378 |
1379 | handle.scale.set( 1e-10, 1e-10, 1e-10 );
1380 | handle.visible = false;
1381 |
1382 | }
1383 |
1384 | }
1385 |
1386 | if ( handle.name === 'XZ' ) {
1387 |
1388 | if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) {
1389 |
1390 | handle.scale.set( 1e-10, 1e-10, 1e-10 );
1391 | handle.visible = false;
1392 |
1393 | }
1394 |
1395 | }
1396 |
1397 | } else if ( this.mode === 'rotate' ) {
1398 |
1399 | // Align handles to current local or world rotation
1400 |
1401 | _tempQuaternion2.copy( quaternion );
1402 | _alignVector.copy( this.eye ).applyQuaternion( _tempQuaternion.copy( quaternion ).invert() );
1403 |
1404 | if ( handle.name.search( 'E' ) !== - 1 ) {
1405 |
1406 | handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) );
1407 |
1408 | }
1409 |
1410 | if ( handle.name === 'X' ) {
1411 |
1412 | _tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) );
1413 | _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
1414 | handle.quaternion.copy( _tempQuaternion );
1415 |
1416 | }
1417 |
1418 | if ( handle.name === 'Y' ) {
1419 |
1420 | _tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) );
1421 | _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
1422 | handle.quaternion.copy( _tempQuaternion );
1423 |
1424 | }
1425 |
1426 | if ( handle.name === 'Z' ) {
1427 |
1428 | _tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) );
1429 | _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
1430 | handle.quaternion.copy( _tempQuaternion );
1431 |
1432 | }
1433 |
1434 | }
1435 |
1436 | // Hide disabled axes
1437 | handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX );
1438 | handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY );
1439 | handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ );
1440 | handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) );
1441 |
1442 | // highlight selected axis
1443 |
1444 | handle.material._color = handle.material._color || handle.material.color.clone();
1445 | handle.material._opacity = handle.material._opacity || handle.material.opacity;
1446 |
1447 | handle.material.color.copy( handle.material._color );
1448 | handle.material.opacity = handle.material._opacity;
1449 |
1450 | if ( this.enabled && this.axis ) {
1451 |
1452 | if ( handle.name === this.axis ) {
1453 |
1454 | handle.material.color.setHex( 0xffff00 );
1455 | handle.material.opacity = 1.0;
1456 |
1457 | } else if ( this.axis.split( '' ).some( function ( a ) {
1458 |
1459 | return handle.name === a;
1460 |
1461 | } ) ) {
1462 |
1463 | handle.material.color.setHex( 0xffff00 );
1464 | handle.material.opacity = 1.0;
1465 |
1466 | }
1467 |
1468 | }
1469 |
1470 | }
1471 |
1472 | super.updateMatrixWorld( force );
1473 |
1474 | }
1475 |
1476 | }
1477 |
1478 | //
1479 |
1480 | class TransformControlsPlane extends Mesh {
1481 |
1482 | constructor() {
1483 |
1484 | super(
1485 | new PlaneGeometry( 100000, 100000, 2, 2 ),
1486 | new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } )
1487 | );
1488 |
1489 | this.isTransformControlsPlane = true;
1490 |
1491 | this.type = 'TransformControlsPlane';
1492 |
1493 | }
1494 |
1495 | updateMatrixWorld( force ) {
1496 |
1497 | let space = this.space;
1498 |
1499 | this.position.copy( this.worldPosition );
1500 |
1501 | if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
1502 |
1503 | _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
1504 | _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
1505 | _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
1506 |
1507 | // Align the plane for current transform mode, axis and space.
1508 |
1509 | _alignVector.copy( _v2 );
1510 |
1511 | switch ( this.mode ) {
1512 |
1513 | case 'translate':
1514 | case 'scale':
1515 | switch ( this.axis ) {
1516 |
1517 | case 'X':
1518 | _alignVector.copy( this.eye ).cross( _v1 );
1519 | _dirVector.copy( _v1 ).cross( _alignVector );
1520 | break;
1521 | case 'Y':
1522 | _alignVector.copy( this.eye ).cross( _v2 );
1523 | _dirVector.copy( _v2 ).cross( _alignVector );
1524 | break;
1525 | case 'Z':
1526 | _alignVector.copy( this.eye ).cross( _v3 );
1527 | _dirVector.copy( _v3 ).cross( _alignVector );
1528 | break;
1529 | case 'XY':
1530 | _dirVector.copy( _v3 );
1531 | break;
1532 | case 'YZ':
1533 | _dirVector.copy( _v1 );
1534 | break;
1535 | case 'XZ':
1536 | _alignVector.copy( _v3 );
1537 | _dirVector.copy( _v2 );
1538 | break;
1539 | case 'XYZ':
1540 | case 'E':
1541 | _dirVector.set( 0, 0, 0 );
1542 | break;
1543 |
1544 | }
1545 |
1546 | break;
1547 | case 'rotate':
1548 | default:
1549 | // special case for rotate
1550 | _dirVector.set( 0, 0, 0 );
1551 |
1552 | }
1553 |
1554 | if ( _dirVector.length() === 0 ) {
1555 |
1556 | // If in rotate mode, make the plane parallel to camera
1557 | this.quaternion.copy( this.cameraQuaternion );
1558 |
1559 | } else {
1560 |
1561 | _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector );
1562 |
1563 | this.quaternion.setFromRotationMatrix( _tempMatrix );
1564 |
1565 | }
1566 |
1567 | super.updateMatrixWorld( force );
1568 |
1569 | }
1570 |
1571 | }
1572 |
1573 | export { TransformControls, TransformControlsGizmo, TransformControlsPlane };
1574 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ThreeOverlay",
3 | "lockfileVersion": 2,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "dependencies": {
8 | "esbuild": "^0.21.4",
9 | "three": "^0.164.1"
10 | }
11 | },
12 | "node_modules/@esbuild/aix-ppc64": {
13 | "version": "0.21.4",
14 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz",
15 | "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==",
16 | "cpu": [
17 | "ppc64"
18 | ],
19 | "optional": true,
20 | "os": [
21 | "aix"
22 | ],
23 | "engines": {
24 | "node": ">=12"
25 | }
26 | },
27 | "node_modules/@esbuild/android-arm": {
28 | "version": "0.21.4",
29 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz",
30 | "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==",
31 | "cpu": [
32 | "arm"
33 | ],
34 | "optional": true,
35 | "os": [
36 | "android"
37 | ],
38 | "engines": {
39 | "node": ">=12"
40 | }
41 | },
42 | "node_modules/@esbuild/android-arm64": {
43 | "version": "0.21.4",
44 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz",
45 | "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==",
46 | "cpu": [
47 | "arm64"
48 | ],
49 | "optional": true,
50 | "os": [
51 | "android"
52 | ],
53 | "engines": {
54 | "node": ">=12"
55 | }
56 | },
57 | "node_modules/@esbuild/android-x64": {
58 | "version": "0.21.4",
59 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz",
60 | "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==",
61 | "cpu": [
62 | "x64"
63 | ],
64 | "optional": true,
65 | "os": [
66 | "android"
67 | ],
68 | "engines": {
69 | "node": ">=12"
70 | }
71 | },
72 | "node_modules/@esbuild/darwin-arm64": {
73 | "version": "0.21.4",
74 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz",
75 | "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==",
76 | "cpu": [
77 | "arm64"
78 | ],
79 | "optional": true,
80 | "os": [
81 | "darwin"
82 | ],
83 | "engines": {
84 | "node": ">=12"
85 | }
86 | },
87 | "node_modules/@esbuild/darwin-x64": {
88 | "version": "0.21.4",
89 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz",
90 | "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==",
91 | "cpu": [
92 | "x64"
93 | ],
94 | "optional": true,
95 | "os": [
96 | "darwin"
97 | ],
98 | "engines": {
99 | "node": ">=12"
100 | }
101 | },
102 | "node_modules/@esbuild/freebsd-arm64": {
103 | "version": "0.21.4",
104 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz",
105 | "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==",
106 | "cpu": [
107 | "arm64"
108 | ],
109 | "optional": true,
110 | "os": [
111 | "freebsd"
112 | ],
113 | "engines": {
114 | "node": ">=12"
115 | }
116 | },
117 | "node_modules/@esbuild/freebsd-x64": {
118 | "version": "0.21.4",
119 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz",
120 | "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==",
121 | "cpu": [
122 | "x64"
123 | ],
124 | "optional": true,
125 | "os": [
126 | "freebsd"
127 | ],
128 | "engines": {
129 | "node": ">=12"
130 | }
131 | },
132 | "node_modules/@esbuild/linux-arm": {
133 | "version": "0.21.4",
134 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz",
135 | "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==",
136 | "cpu": [
137 | "arm"
138 | ],
139 | "optional": true,
140 | "os": [
141 | "linux"
142 | ],
143 | "engines": {
144 | "node": ">=12"
145 | }
146 | },
147 | "node_modules/@esbuild/linux-arm64": {
148 | "version": "0.21.4",
149 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz",
150 | "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==",
151 | "cpu": [
152 | "arm64"
153 | ],
154 | "optional": true,
155 | "os": [
156 | "linux"
157 | ],
158 | "engines": {
159 | "node": ">=12"
160 | }
161 | },
162 | "node_modules/@esbuild/linux-ia32": {
163 | "version": "0.21.4",
164 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz",
165 | "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==",
166 | "cpu": [
167 | "ia32"
168 | ],
169 | "optional": true,
170 | "os": [
171 | "linux"
172 | ],
173 | "engines": {
174 | "node": ">=12"
175 | }
176 | },
177 | "node_modules/@esbuild/linux-loong64": {
178 | "version": "0.21.4",
179 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz",
180 | "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==",
181 | "cpu": [
182 | "loong64"
183 | ],
184 | "optional": true,
185 | "os": [
186 | "linux"
187 | ],
188 | "engines": {
189 | "node": ">=12"
190 | }
191 | },
192 | "node_modules/@esbuild/linux-mips64el": {
193 | "version": "0.21.4",
194 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz",
195 | "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==",
196 | "cpu": [
197 | "mips64el"
198 | ],
199 | "optional": true,
200 | "os": [
201 | "linux"
202 | ],
203 | "engines": {
204 | "node": ">=12"
205 | }
206 | },
207 | "node_modules/@esbuild/linux-ppc64": {
208 | "version": "0.21.4",
209 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz",
210 | "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==",
211 | "cpu": [
212 | "ppc64"
213 | ],
214 | "optional": true,
215 | "os": [
216 | "linux"
217 | ],
218 | "engines": {
219 | "node": ">=12"
220 | }
221 | },
222 | "node_modules/@esbuild/linux-riscv64": {
223 | "version": "0.21.4",
224 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz",
225 | "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==",
226 | "cpu": [
227 | "riscv64"
228 | ],
229 | "optional": true,
230 | "os": [
231 | "linux"
232 | ],
233 | "engines": {
234 | "node": ">=12"
235 | }
236 | },
237 | "node_modules/@esbuild/linux-s390x": {
238 | "version": "0.21.4",
239 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz",
240 | "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==",
241 | "cpu": [
242 | "s390x"
243 | ],
244 | "optional": true,
245 | "os": [
246 | "linux"
247 | ],
248 | "engines": {
249 | "node": ">=12"
250 | }
251 | },
252 | "node_modules/@esbuild/linux-x64": {
253 | "version": "0.21.4",
254 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz",
255 | "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==",
256 | "cpu": [
257 | "x64"
258 | ],
259 | "optional": true,
260 | "os": [
261 | "linux"
262 | ],
263 | "engines": {
264 | "node": ">=12"
265 | }
266 | },
267 | "node_modules/@esbuild/netbsd-x64": {
268 | "version": "0.21.4",
269 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz",
270 | "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==",
271 | "cpu": [
272 | "x64"
273 | ],
274 | "optional": true,
275 | "os": [
276 | "netbsd"
277 | ],
278 | "engines": {
279 | "node": ">=12"
280 | }
281 | },
282 | "node_modules/@esbuild/openbsd-x64": {
283 | "version": "0.21.4",
284 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz",
285 | "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==",
286 | "cpu": [
287 | "x64"
288 | ],
289 | "optional": true,
290 | "os": [
291 | "openbsd"
292 | ],
293 | "engines": {
294 | "node": ">=12"
295 | }
296 | },
297 | "node_modules/@esbuild/sunos-x64": {
298 | "version": "0.21.4",
299 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz",
300 | "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==",
301 | "cpu": [
302 | "x64"
303 | ],
304 | "optional": true,
305 | "os": [
306 | "sunos"
307 | ],
308 | "engines": {
309 | "node": ">=12"
310 | }
311 | },
312 | "node_modules/@esbuild/win32-arm64": {
313 | "version": "0.21.4",
314 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz",
315 | "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==",
316 | "cpu": [
317 | "arm64"
318 | ],
319 | "optional": true,
320 | "os": [
321 | "win32"
322 | ],
323 | "engines": {
324 | "node": ">=12"
325 | }
326 | },
327 | "node_modules/@esbuild/win32-ia32": {
328 | "version": "0.21.4",
329 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz",
330 | "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==",
331 | "cpu": [
332 | "ia32"
333 | ],
334 | "optional": true,
335 | "os": [
336 | "win32"
337 | ],
338 | "engines": {
339 | "node": ">=12"
340 | }
341 | },
342 | "node_modules/@esbuild/win32-x64": {
343 | "version": "0.21.4",
344 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz",
345 | "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==",
346 | "cpu": [
347 | "x64"
348 | ],
349 | "optional": true,
350 | "os": [
351 | "win32"
352 | ],
353 | "engines": {
354 | "node": ">=12"
355 | }
356 | },
357 | "node_modules/esbuild": {
358 | "version": "0.21.4",
359 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz",
360 | "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==",
361 | "hasInstallScript": true,
362 | "bin": {
363 | "esbuild": "bin/esbuild"
364 | },
365 | "engines": {
366 | "node": ">=12"
367 | },
368 | "optionalDependencies": {
369 | "@esbuild/aix-ppc64": "0.21.4",
370 | "@esbuild/android-arm": "0.21.4",
371 | "@esbuild/android-arm64": "0.21.4",
372 | "@esbuild/android-x64": "0.21.4",
373 | "@esbuild/darwin-arm64": "0.21.4",
374 | "@esbuild/darwin-x64": "0.21.4",
375 | "@esbuild/freebsd-arm64": "0.21.4",
376 | "@esbuild/freebsd-x64": "0.21.4",
377 | "@esbuild/linux-arm": "0.21.4",
378 | "@esbuild/linux-arm64": "0.21.4",
379 | "@esbuild/linux-ia32": "0.21.4",
380 | "@esbuild/linux-loong64": "0.21.4",
381 | "@esbuild/linux-mips64el": "0.21.4",
382 | "@esbuild/linux-ppc64": "0.21.4",
383 | "@esbuild/linux-riscv64": "0.21.4",
384 | "@esbuild/linux-s390x": "0.21.4",
385 | "@esbuild/linux-x64": "0.21.4",
386 | "@esbuild/netbsd-x64": "0.21.4",
387 | "@esbuild/openbsd-x64": "0.21.4",
388 | "@esbuild/sunos-x64": "0.21.4",
389 | "@esbuild/win32-arm64": "0.21.4",
390 | "@esbuild/win32-ia32": "0.21.4",
391 | "@esbuild/win32-x64": "0.21.4"
392 | }
393 | },
394 | "node_modules/three": {
395 | "version": "0.164.1",
396 | "resolved": "https://registry.npmjs.org/three/-/three-0.164.1.tgz",
397 | "integrity": "sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w=="
398 | }
399 | },
400 | "dependencies": {
401 | "@esbuild/aix-ppc64": {
402 | "version": "0.21.4",
403 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz",
404 | "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==",
405 | "optional": true
406 | },
407 | "@esbuild/android-arm": {
408 | "version": "0.21.4",
409 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz",
410 | "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==",
411 | "optional": true
412 | },
413 | "@esbuild/android-arm64": {
414 | "version": "0.21.4",
415 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz",
416 | "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==",
417 | "optional": true
418 | },
419 | "@esbuild/android-x64": {
420 | "version": "0.21.4",
421 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz",
422 | "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==",
423 | "optional": true
424 | },
425 | "@esbuild/darwin-arm64": {
426 | "version": "0.21.4",
427 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz",
428 | "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==",
429 | "optional": true
430 | },
431 | "@esbuild/darwin-x64": {
432 | "version": "0.21.4",
433 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz",
434 | "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==",
435 | "optional": true
436 | },
437 | "@esbuild/freebsd-arm64": {
438 | "version": "0.21.4",
439 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz",
440 | "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==",
441 | "optional": true
442 | },
443 | "@esbuild/freebsd-x64": {
444 | "version": "0.21.4",
445 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz",
446 | "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==",
447 | "optional": true
448 | },
449 | "@esbuild/linux-arm": {
450 | "version": "0.21.4",
451 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz",
452 | "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==",
453 | "optional": true
454 | },
455 | "@esbuild/linux-arm64": {
456 | "version": "0.21.4",
457 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz",
458 | "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==",
459 | "optional": true
460 | },
461 | "@esbuild/linux-ia32": {
462 | "version": "0.21.4",
463 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz",
464 | "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==",
465 | "optional": true
466 | },
467 | "@esbuild/linux-loong64": {
468 | "version": "0.21.4",
469 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz",
470 | "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==",
471 | "optional": true
472 | },
473 | "@esbuild/linux-mips64el": {
474 | "version": "0.21.4",
475 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz",
476 | "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==",
477 | "optional": true
478 | },
479 | "@esbuild/linux-ppc64": {
480 | "version": "0.21.4",
481 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz",
482 | "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==",
483 | "optional": true
484 | },
485 | "@esbuild/linux-riscv64": {
486 | "version": "0.21.4",
487 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz",
488 | "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==",
489 | "optional": true
490 | },
491 | "@esbuild/linux-s390x": {
492 | "version": "0.21.4",
493 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz",
494 | "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==",
495 | "optional": true
496 | },
497 | "@esbuild/linux-x64": {
498 | "version": "0.21.4",
499 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz",
500 | "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==",
501 | "optional": true
502 | },
503 | "@esbuild/netbsd-x64": {
504 | "version": "0.21.4",
505 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz",
506 | "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==",
507 | "optional": true
508 | },
509 | "@esbuild/openbsd-x64": {
510 | "version": "0.21.4",
511 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz",
512 | "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==",
513 | "optional": true
514 | },
515 | "@esbuild/sunos-x64": {
516 | "version": "0.21.4",
517 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz",
518 | "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==",
519 | "optional": true
520 | },
521 | "@esbuild/win32-arm64": {
522 | "version": "0.21.4",
523 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz",
524 | "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==",
525 | "optional": true
526 | },
527 | "@esbuild/win32-ia32": {
528 | "version": "0.21.4",
529 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz",
530 | "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==",
531 | "optional": true
532 | },
533 | "@esbuild/win32-x64": {
534 | "version": "0.21.4",
535 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz",
536 | "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==",
537 | "optional": true
538 | },
539 | "esbuild": {
540 | "version": "0.21.4",
541 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz",
542 | "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==",
543 | "requires": {
544 | "@esbuild/aix-ppc64": "0.21.4",
545 | "@esbuild/android-arm": "0.21.4",
546 | "@esbuild/android-arm64": "0.21.4",
547 | "@esbuild/android-x64": "0.21.4",
548 | "@esbuild/darwin-arm64": "0.21.4",
549 | "@esbuild/darwin-x64": "0.21.4",
550 | "@esbuild/freebsd-arm64": "0.21.4",
551 | "@esbuild/freebsd-x64": "0.21.4",
552 | "@esbuild/linux-arm": "0.21.4",
553 | "@esbuild/linux-arm64": "0.21.4",
554 | "@esbuild/linux-ia32": "0.21.4",
555 | "@esbuild/linux-loong64": "0.21.4",
556 | "@esbuild/linux-mips64el": "0.21.4",
557 | "@esbuild/linux-ppc64": "0.21.4",
558 | "@esbuild/linux-riscv64": "0.21.4",
559 | "@esbuild/linux-s390x": "0.21.4",
560 | "@esbuild/linux-x64": "0.21.4",
561 | "@esbuild/netbsd-x64": "0.21.4",
562 | "@esbuild/openbsd-x64": "0.21.4",
563 | "@esbuild/sunos-x64": "0.21.4",
564 | "@esbuild/win32-arm64": "0.21.4",
565 | "@esbuild/win32-ia32": "0.21.4",
566 | "@esbuild/win32-x64": "0.21.4"
567 | }
568 | },
569 | "three": {
570 | "version": "0.164.1",
571 | "resolved": "https://registry.npmjs.org/three/-/three-0.164.1.tgz",
572 | "integrity": "sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w=="
573 | }
574 | }
575 | }
576 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "build": "esbuild ./src/main.js --bundle --minify --sourcemap --format=esm --outdir=./build"
4 | },
5 | "dependencies": {
6 | "esbuild": "^0.21.4",
7 | "three": "^0.164.1"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/TimeResampler.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../node_modules/three/build/three.module.js';
2 | import Stats from '../node_modules/three/examples/jsm/libs/stats.module.js';
3 |
4 | /** Dequeue and resample time series values with linear interpolation */
5 | export default class TimeResampler {
6 |
7 | /** Construct a new TimeResampler with numSamples in the circular buffer */
8 | constructor(numSamples) {
9 | this.numSamples = numSamples;
10 | this.values = new Array(this.numSamples).fill(0);
11 | this.times = new Array(this.numSamples).fill(10000);
12 | this.head = 0;
13 | this.tail = 1;
14 | }
15 |
16 | /** Enqueue a new value */
17 | enqueue(timeMS, value){
18 | this.values[this.tail] = value;
19 | this.times[this.tail] = timeMS;
20 | this.tail = (this.tail + 1) % this.numSamples;
21 | }
22 |
23 | /** Resample the time series - BROKEN */
24 | interpolate(timeMS){
25 | let nextIdx = (this.head + 1) % this.numSamples;
26 | while(true){
27 | if((this.times[this.head] > timeMS && this.times[nextIdx] < timeMS) || nextIdx == this.tail){
28 | break;
29 | } else {
30 | this.head = nextIdx;
31 | nextIdx = (this.head + 1) % this.numSamples;
32 | }
33 | }
34 | if(timeMS > this.times[nextIdx] + 16.0){ return this.values[nextIdx]; }
35 |
36 | let alpha = (timeMS - this.times[this.head]) / (this.times[nextIdx] - this.times[this.head]);
37 | return this.values[this.head] + alpha * (this.values[nextIdx] - this.values[this.head]);
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/src/World.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../node_modules/three/build/three.module.js';
2 |
3 | /** The fundamental set up and animation structures for 3D Visualization */
4 | export default class World {
5 |
6 | constructor(mainObject) {
7 | this.container_bg = document.getElementById('appbody-bg');
8 | this.container_fg = document.getElementById('appbody-fg');
9 |
10 | // camera and world
11 | this.scene = new THREE.Scene();
12 |
13 | this.forcedPixelsPerMeter = 100.0;
14 | this.cameraDepth = 5.0;
15 | this.cameraFoV = 60.0;
16 |
17 | this.camera = new THREE.PerspectiveCamera( this.cameraFoV, window.innerWidth / window.innerHeight, 2.0, 1000 );
18 | this.camera.position.set( 0.0, 0.0, this.cameraDepth );
19 | this.camera.layers.enableAll();
20 | this.scene.add(this.camera);
21 |
22 | this._forcePixelsPerMeter();
23 |
24 | this.spotLight = new THREE.SpotLight( 0xffffff, Math.PI * 10.0 );
25 | this.spotLight.angle = Math.PI / 5;
26 | this.spotLight.penumbra = 0.2;
27 | this.spotLight.position.set( -2, 3, 3 );
28 | this.spotLight.castShadow = true;
29 | this.spotLight.shadow.camera.near = 1;
30 | this.spotLight.shadow.camera.far = 20;
31 | this.spotLight.shadow.mapSize.width = 1024;
32 | this.spotLight.shadow.mapSize.height = 1024;
33 | this.scene.add( this.spotLight );
34 |
35 | this.dirLight = new THREE.DirectionalLight( 0x55505a, Math.PI * 10.0 );
36 | this.dirLight.position.set( 0, 3, 0 );
37 | this.dirLight.castShadow = true;
38 | this.dirLight.shadow.camera.near = -10;
39 | this.dirLight.shadow.camera.far = 10;
40 |
41 | this.dirLight.shadow.camera.right = 3;
42 | this.dirLight.shadow.camera.left = - 3;
43 | this.dirLight.shadow.camera.top = 3;
44 | this.dirLight.shadow.camera.bottom = - 3;
45 |
46 | this.dirLight.shadow.mapSize.width = 1024;
47 | this.dirLight.shadow.mapSize.height = 1024;
48 | this.scene.add( this.dirLight );
49 |
50 | // Geometry
51 |
52 | for(let i = 0; i < 10; i++){
53 | this.helper0 = new THREE.GridHelper( 20, 20 );
54 | this.helper0.material.opacity = 0.2;
55 | this.helper0.material.transparent = true;
56 | this.helper0.position.set((window.innerWidth * 0.5) / this.pixelsPerMeter, i * -5.0, 0);
57 | this.scene.add( this.helper0 );
58 | }
59 |
60 | this.renderingMode = 2;
61 |
62 | // renderer
63 | this.renderer_bg = new THREE.WebGLRenderer( { antialias: true } ); //, alpha: true
64 | this.renderer_bg.setPixelRatio(1.0);//window.devicePixelRatio > 1.5 ? 1.0 : 1.0);
65 | this.renderer_bg.shadowMap.enabled = true;
66 | this.renderer_bg.setAnimationLoop(mainObject.update.bind(mainObject));
67 | this.renderer_bg.setClearColor( 0x000000, 0 ); // the default
68 |
69 | this.renderer_fg = new THREE.WebGLRenderer( { antialias: true } ); //, alpha: true
70 | this.renderer_fg.setPixelRatio(1.0);//window.devicePixelRatio > 1.5 ? 1.0 : 1.0);
71 | this.renderer_fg.shadowMap.enabled = true;
72 | //this.renderer_fg.setAnimationLoop(mainObject.update.bind(mainObject));
73 | this.renderer_fg.setClearColor( 0x000000, 0 ); // the default
74 |
75 | window.addEventListener('resize', this._onWindowResize.bind(this), false);
76 | window.addEventListener('orientationchange', this._onWindowResize.bind(this), false);
77 | this._onWindowResize();
78 |
79 | this.container_bg.appendChild(this.renderer_bg.domElement);
80 | this.container_fg.appendChild(this.renderer_fg.domElement);
81 |
82 | // raycaster
83 | this.raycaster = new THREE.Raycaster();
84 | this.raycaster.layers.set(0);
85 |
86 | // Enqueue Scroll Events, since sometimes multiple scroll events are fired in a single frame
87 | this.scrollQueue = [];
88 | this.curScrollY = window.scrollY;
89 | this.positioningMode = 1; // 0 is Fixed, 1 is Absolute
90 |
91 | this.boxGeometry = new THREE.BoxGeometry(1, 1, 1)
92 | this.elementMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, wireframe: true, opacity: 0.05, transparent: true});
93 |
94 | this.elementBoxes = [];
95 | this._recomputeElementBoxes();
96 | this._setScroll();
97 |
98 | this.cube = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhysicalMaterial({ color: 0x00ff00, wireframe: false }));
99 | this.cube.position.set((window.innerWidth * 0.5) / this.pixelsPerMeter, -5.0, 0.0);
100 | this.scene.add(this.cube);
101 | }
102 |
103 |
104 | _render() {
105 | // Trigger a resize event if the window size has changed
106 | // iOS Pinch to Zoom does not trigger a resize event
107 | if( window.innerWidth != this.lastWidth ||
108 | window.innerHeight != this.lastHeight){
109 | this._onWindowResize();
110 | }
111 |
112 | if(this.renderingMode == 2){
113 | this.camera.near = this.cameraDepth;
114 | this.camera.far = 1000;
115 | this.camera.updateProjectionMatrix();
116 | this.renderer_bg.render(this.scene, this.camera);
117 |
118 | this.camera.near = 2.0;
119 | this.camera.far = this.cameraDepth + 0.01;
120 | this.camera.updateProjectionMatrix();
121 | this.renderer_fg.render(this.scene, this.camera);
122 | }else{
123 | this.camera.near = 2.0;
124 | this.camera.far = 1000;
125 | this.camera.updateProjectionMatrix();
126 | if(this.renderingMode == 0){
127 | this.renderer_bg.render(this.scene, this.camera);
128 | this.renderer_fg.clear();
129 | }else{
130 | this.renderer_bg.clear();
131 | this.renderer_fg.render(this.scene, this.camera);
132 | }
133 | }
134 | }
135 |
136 | _setScroll(){
137 | this.curScrollX = window.scrollX;
138 | this.curScrollY = window.scrollY;
139 | this.camera.position.set( (this.curScrollX + (window.innerWidth * 0.5)) / this.pixelsPerMeter,
140 | -(this.curScrollY + (window.innerHeight * 0.5)) / this.pixelsPerMeter, this.cameraDepth); // - (window.innerWidth * 0.5)
141 |
142 | this._render(this.scene, this.camera);
143 | if(this.positioningMode == 1){
144 | this.container_bg.style.position = "absolute";
145 | this.container_fg.style.position = "absolute";
146 | this.container_bg.style.transform = "translate("+this.curScrollX+"px, "+this.curScrollY+"px)";
147 | this.container_fg.style.transform = "translate("+this.curScrollX+"px, "+this.curScrollY+"px)";
148 | }else{
149 | this.container_bg.style.position = "fixed";
150 | this.container_fg.style.position = "fixed";
151 | this.container_bg.style.transform = "translate(0px, 0px)";
152 | this.container_fg.style.transform = "translate(0px, 0px)";
153 | }
154 | }
155 |
156 | _recomputeElementBoxes(){
157 | if(!this.elementBoxes){ this.elementBoxes = []; }
158 |
159 | let elements = document.getElementsByTagName('p');
160 | for(let i = 0; i < elements.length; i++){
161 | if(i >= this.elementBoxes.length){
162 | let cube = new THREE.Mesh(this.boxGeometry, this.elementMaterial);
163 | this.scene.add(cube);
164 | this.elementBoxes.push(cube);
165 | }
166 |
167 | let rect = elements[i].getBoundingClientRect();
168 | this.elementBoxes[i].position.set(
169 | (rect.left + window.scrollX + (rect.width * 0.5)) / this.pixelsPerMeter,
170 | (rect.top + window.scrollY + (rect.height * 0.5)) / -this.pixelsPerMeter, 0.0);
171 | this.elementBoxes[i].scale.set(rect.width / this.pixelsPerMeter, rect.height / this.pixelsPerMeter, 0.5);
172 | this.elementBoxes[i].element = elements[i];
173 | }
174 | }
175 |
176 | _recomputePixelsPerMeter(){
177 | // Calculate the camera movement required to follow the scroll
178 | let oldPosition = this.camera.position.clone();
179 | this.camera.position.set(0.0, 0.0, this.cameraDepth);
180 | this.camera.updateMatrixWorld();
181 | this.camera.updateProjectionMatrix();
182 | this.derp = new THREE.Vector3(0.0, 0, 0.0);
183 | this.derp.project(this.camera);
184 | this.derp.y = 1.0/window.innerHeight;
185 | this.derp.unproject(this.camera);
186 | this.camera.position.copy(oldPosition);
187 | this.camera.updateMatrixWorld();
188 | this.camera.updateProjectionMatrix();
189 | this.pixelsPerMeter = 1.0 / (this.derp.y * 2.0);
190 | return this.pixelsPerMeter;
191 | }
192 |
193 | _forcePixelsPerMeter(){
194 | for(let i = 0; i < 10; i++){
195 | let curPixelsPerMeter = this._recomputePixelsPerMeter();
196 | // This is more compelling, but breaks the near clipping plane when zoomed in...
197 | //this.cameraDepth *= curPixelsPerMeter / this.forcedPixelsPerMeter;
198 | this.camera.fov *= curPixelsPerMeter / this.forcedPixelsPerMeter;
199 | this.camera.updateProjectionMatrix();
200 | }
201 | }
202 |
203 | /** **INTERNAL**: This function recalculates the viewport based on the new window size. */
204 | _onWindowResize() {
205 | let width = window.innerWidth, height = window.innerHeight;
206 | if(this.lastWidth != width || this.lastHeight != height){
207 | this.camera.aspect = width / height;
208 | this.camera.updateProjectionMatrix();
209 | this.renderer_bg.setSize(width, height);
210 | this.renderer_fg.setSize(width, height);
211 | this.lastWidth = width;
212 | this.lastHeight = height;
213 | this._forcePixelsPerMeter();
214 | this._recomputeElementBoxes();
215 | }
216 | this._setScroll();
217 | }
218 |
219 | }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../node_modules/three/build/three.module.js';
2 | import { GUI } from '../node_modules/three/examples/jsm/libs/lil-gui.module.min.js';
3 | import World from './World.js';
4 | import { OBJLoader } from '../node_modules/three/examples/jsm/loaders/OBJLoader.js';
5 |
6 | /** The fundamental set up and animation structures for 3D Visualization */
7 | export default class Main {
8 |
9 | constructor() {
10 | // Intercept Main Window Errors
11 | window.realConsoleError = console.error;
12 | window.addEventListener('error', (event) => {
13 | let path = event.filename.split("/");
14 | this.display((path[path.length - 1] + ":" + event.lineno + " - " + event.message));
15 | });
16 | console.error = this.fakeError.bind(this);
17 | this.physicsScene = { softBodies: [] };
18 | this.deferredConstructor();
19 | }
20 | async deferredConstructor() {
21 | // Construct the render world
22 | this.world = new World(this);
23 |
24 | // Configure Settings
25 | this.overlayParams = {
26 | renderingMode: this.world.renderingMode,
27 | positioningMode: this.world.positioningMode,
28 | };
29 | //this.gui = new GUI();
30 | //this.gui.add( this.overlayParams, 'renderingMode', { Background: 0, Foreground: 1, ForegroundAndBackground: 2 } )
31 | // .onFinishChange((value) => { this.world.renderingMode = value; });
32 | //this.gui.add( this.overlayParams, 'positioningMode', { Fixed: 0, Absolute: 1 } )
33 | // .onFinishChange((value) => { this.world.positioningMode = value; });
34 |
35 | //this.gui.add(this.overlayParams, 'RemeshResolution', 0, 50, 1).onFinishChange((value) => {
36 | // if(this.mesh){ this.generateTetMesh(this.mesh); }});
37 | //this.gui.add(this.overlayParams, 'TargetTriangles', 100, 5000, 100).onFinishChange((value) => {
38 | // if(this.mesh){ this.generateTetMesh(this.mesh); }});
39 | //this.gui.add(this.overlayParams, 'MaxTriangleEdgeLength').onFinishChange((value) => {
40 | // if(this.mesh){ this.generateTetMesh(this.mesh); }});
41 | //this.gui.add(this.overlayParams, 'MinTetVolume').onFinishChange((value) => {
42 | // if(this.mesh){ this.generateTetMesh(this.mesh); }});
43 |
44 | }
45 |
46 | /** Update the simulation */
47 | update(timeMS) {
48 | this.world._setScroll(timeMS);
49 | //this.world.stats.update();
50 | }
51 |
52 | // Log Errors as s over the main viewport
53 | fakeError(...args) {
54 | if (args.length > 0 && args[0]) { this.display(JSON.stringify(args[0])); }
55 | window.realConsoleError.apply(console, arguments);
56 | }
57 |
58 | display(text) {
59 | let errorNode = window.document.createElement("div");
60 | errorNode.innerHTML = text.fontcolor("red");
61 | window.document.getElementById("info").appendChild(errorNode);
62 | }
63 | }
64 |
65 | var main = new Main();
66 |
--------------------------------------------------------------------------------