├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── jsconfig.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── rhino-viewer-2.gif
├── rhino-viewer.gif
├── src
├── App.vue
├── assets
│ └── scss
│ │ ├── loader.scss
│ │ └── main.scss
├── components
│ ├── Footer.vue
│ ├── Loader.vue
│ └── Scene.vue
├── main.js
├── rhinoService.js
└── three
│ ├── colors.js
│ ├── encoders.js
│ ├── environment.js
│ ├── helpers.js
│ ├── three.js
│ └── vendor
│ └── OrbitControls.js
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/three/vendor/OrbitControls.js
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | globals: {
3 | rhino3dm: false,
4 | Rhino3dm: false,
5 | RhinoCompute: false,
6 | },
7 |
8 | root: true,
9 |
10 | env: {
11 | node: true
12 | },
13 |
14 | extends: [
15 | "plugin:vue/essential",
16 | "eslint:recommended",
17 | '@vue/standard'
18 | ],
19 |
20 | rules: {
21 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
23 | 'comma-dangle': ['error', 'only-multiline']
24 | },
25 |
26 | parserOptions: {
27 | parser: 'babel-eslint'
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Gui Talarico
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 | # Vue ThreeJS Rhino Viewer Demo
2 |
3 | This is a demo project to test Rhino3dm & Compute integration with Three JS.
4 | I created this project to test and learn how these new technologies were designed to work,
5 | and to generate boiler plate code for use in future projects.
6 |
7 | It uses [resources and samples provided by McNeel here](https://github.com/mcneel/rhino3dm/blob/master/samples/javascript/rhinologo.html) for the basic setup,
8 | but also build on it with more reusable modules and new JS syntax.
9 |
10 | The application bootstrapping and [loading sequence of dependencies](https://github.com/gtalarico/vue-threejs-rhino-demo/blob/master/src/rhinoService.js#L7) in the [context of a Vue application](https://github.com/gtalarico/vue-threejs-rhino-demo/blob/master/src/components/Scene.vue#L49) was a bit tricky but seems to be working well.
11 |
12 | [**Live Demo**](https://vue-threejs-rhino-viewer.netlify.com/)
13 | (Requires Rhino Compute Token, get yours [here](https://www.rhino3d.com/compute/login))
14 |
15 | ### What's included
16 | * A minimal VueJs project setup using Vue CLI
17 | * My preferred setup for [loading ThreeJS into a Vue project](https://github.com/gtalarico/vue-threejs-rhino-demo/blob/master/src/three) and [setting up a configurable Scene ](https://github.com/gtalarico/vue-threejs-rhino-demo/blob/master/src/three/environment.js)
18 | * A [RhinoService module](https://github.com/gtalarico/vue-threejs-rhino-demo/blob/master/src/rhinoService.js) for making handling auth, Rhino Compute calls, and converting objects to a renderable object (eg. Brep to Meshes)
19 | * Samples [ThreeJs encoders]((https://github.com/gtalarico/vue-threejs-rhino-demo/blob/master/src//three/encoders.js)) to convert renderable Rhino Objects into ThreeJs equivalents (eg. Meshes, NurbsCurve, LineCurve, etc)
20 |
21 | 
22 | 
23 |
24 |
25 | ### Contributing
26 |
27 | Contributions of any kind are welcome, including UI improvement, additional encoders, application setup, etc
28 |
29 | ## Local Development
30 | ```
31 | $ yarn install
32 | $ yarn run serve
33 | ```
34 |
35 | **License MIT**
36 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "allowJs":true,
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["./src/*"]
7 | }
8 | },
9 | "include": ["src/**/*"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-threejs-rhino",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "axios": "^0.19.0",
12 | "core-js": "^2.6.5",
13 | "milligram-scss": "^1.3.0",
14 | "three": "^0.108.0",
15 | "vue": "^2.6.10"
16 | },
17 | "devDependencies": {
18 | "@vue/cli-plugin-babel": "^3.11.0",
19 | "@vue/cli-plugin-eslint": "^3.11.0",
20 | "@vue/cli-service": "^3.11.0",
21 | "@vue/eslint-config-standard": "^4.0.0",
22 | "babel-eslint": "^10.0.1",
23 | "eslint": "^5.16.0",
24 | "eslint-plugin-vue": "^5.0.0",
25 | "node-sass": "^4.12.0",
26 | "sass-loader": "^8.0.0",
27 | "vue-template-compiler": "^2.6.10"
28 | },
29 | "postcss": {
30 | "plugins": {
31 | "autoprefixer": {}
32 | }
33 | },
34 | "browserslist": [
35 | "> 1%",
36 | "last 2 versions"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtalarico/vue-threejs-rhino-demo/c85a1df01fa6cf0e8509eb55e726109bbd78bef9/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | vue-three-scene
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/rhino-viewer-2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtalarico/vue-threejs-rhino-demo/c85a1df01fa6cf0e8509eb55e726109bbd78bef9/rhino-viewer-2.gif
--------------------------------------------------------------------------------
/rhino-viewer.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtalarico/vue-threejs-rhino-demo/c85a1df01fa6cf0e8509eb55e726109bbd78bef9/rhino-viewer.gif
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
41 |
42 |
66 |
--------------------------------------------------------------------------------
/src/assets/scss/loader.scss:
--------------------------------------------------------------------------------
1 | .lds-dual-ring {
2 | display: inline-block;
3 | width: 64px;
4 | height: 64px;
5 | }
6 | .lds-dual-ring:after {
7 | content: " ";
8 | display: block;
9 | width: 46px;
10 | height: 46px;
11 | margin: 1px;
12 | border-radius: 50%;
13 | border: 5px solid #666;
14 | border-color: #666 transparent #666 transparent;
15 | animation: lds-dual-ring 1.2s linear infinite;
16 | }
17 | @keyframes lds-dual-ring {
18 | 0% {
19 | transform: rotate(0deg);
20 | }
21 | 100% {
22 | transform: rotate(360deg);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/assets/scss/main.scss:
--------------------------------------------------------------------------------
1 | @import 'loader.scss';
2 |
3 | body {
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .dark {
9 | color: #aaa;
10 | input {
11 | color: #aaa
12 | }
13 | }
14 |
15 | $color-primary: #f00b42;
16 |
17 |
18 | @import '~milligram-scss';
19 |
--------------------------------------------------------------------------------
/src/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
43 |
44 |
57 |
--------------------------------------------------------------------------------
/src/components/Loader.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ statusMessage }}
5 |
6 |
7 |
8 |
23 |
24 |
40 |
--------------------------------------------------------------------------------
/src/components/Scene.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
94 |
95 |
98 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | Vue.config.productionTip = false
5 |
6 | new Vue({
7 | render: h => h(App),
8 | }).$mount('#app')
9 |
--------------------------------------------------------------------------------
/src/rhinoService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default function RhinoService () {
4 | this.token = this.getToken()
5 | }
6 |
7 | RhinoService.prototype.init = function () {
8 | return new Promise((resolve, reject) => {
9 | const scripts = [
10 | 'https://files.mcneel.com/rhino3dm/js/latest/rhino3dm.js',
11 | 'https://files.mcneel.com/rhino3dm/js/latest/compute.rhino3d.js',
12 | ]
13 | let loaded = 0
14 |
15 | for (let url of scripts) {
16 | let script = document.createElement('script')
17 | script.src = url
18 | document.head.appendChild(script)
19 | script.onerror = (err) => {
20 | return reject(err)
21 | }
22 | script.onload = () => {
23 | loaded++
24 | if (loaded === scripts.length) {
25 | window.RhinoCompute.authToken = `Bearer ${this.getToken()}`
26 | window.rhino3dm()
27 | .then(module => {
28 | window.Rhino3dm = module
29 | resolve()
30 | })
31 | }
32 | }
33 | }
34 | })
35 | }
36 |
37 | RhinoService.prototype.getToken = function () {
38 | let token = localStorage.getItem('computeToken')
39 | if (token) return token
40 | window.open('https://www.rhino3d.com/compute/login')
41 | token = prompt('Rhino Compute Token')
42 | this.setToken(token)
43 | return token
44 | }
45 |
46 | RhinoService.prototype.setToken = function (token) {
47 | return localStorage.setItem('computeToken', token)
48 | }
49 |
50 | RhinoService.prototype.loadFileFromUrl = async function (url) {
51 | let response
52 | try {
53 | response = await axios.get(url, { responseType: 'arraybuffer' })
54 | } catch (e) {
55 | throw Error('could not fetch url')
56 | }
57 | const longInt8View = new Uint8Array(response.data)
58 | const file = Rhino3dm.File3dm.fromByteArray(longInt8View)
59 | return new RhinoDoc(file)
60 | }
61 |
62 | function RhinoDoc (file) {
63 | this.file = file
64 | this.objects = []
65 | const objectsTable = file.objects()
66 | for (let i = 0; i < objectsTable.count; i++) {
67 | this.objects.push(new RhinoObject(objectsTable.get(i)))
68 | }
69 | }
70 |
71 | function RhinoObject (modelObject) {
72 | this.object = modelObject
73 | this.geometry = modelObject.geometry()
74 | this.attributes = modelObject.attributes()
75 | }
76 |
77 | RhinoObject.prototype.toRenderable = async function () {
78 | const rhinoObjecTypeName = this.geometry.constructor.name
79 | const converter = converters[rhinoObjecTypeName]
80 | if (!converter) {
81 | console.warn(`no renderable obj for ${rhinoObjecTypeName}`)
82 | return null
83 | }
84 | let rhinoOBjects
85 | try {
86 | rhinoOBjects = await converter(this.geometry)
87 | } catch (e) {
88 | console.warn('Rhino Compute request failed')
89 | localStorage.removeItem('computeToken')
90 | throw Error('Rhino Compute request failed')
91 | }
92 | return rhinoOBjects
93 | }
94 |
95 | // Convert Rhino Objects into types that can be encoded into ThreeJs Objects
96 | const converters = {
97 | 'Brep': async function (brep) {
98 | const rhinoMeshes = await RhinoCompute.Mesh.createFromBrep(brep)
99 | const rhinoWireframes = await RhinoCompute.Brep.getWireframe(brep, 1)
100 | return rhinoMeshes
101 | .concat(rhinoWireframes)
102 | .map(r => Rhino3dm.CommonObject.decode(r))
103 | },
104 | }
105 |
--------------------------------------------------------------------------------
/src/three/colors.js:
--------------------------------------------------------------------------------
1 | import { THREE } from '@/three/three.js'
2 |
3 | const Colors = {
4 | black: new THREE.Color('#333333'),
5 | offWhite: new THREE.Color('#f0f0f0'),
6 | white: new THREE.Color('#ffffff'),
7 | lightGray: new THREE.Color('#eaeaea'),
8 | gray: new THREE.Color('#cacaca'),
9 | darkGray: new THREE.Color('#aaaaaa'),
10 | }
11 |
12 | const Materials = {
13 | black: new THREE.MeshBasicMaterial({ color: Colors.black }),
14 | white: new THREE.MeshBasicMaterial({ color: Colors.white }),
15 | lightGray: new THREE.MeshBasicMaterial({ color: Colors.lightGray }),
16 | gray: new THREE.MeshBasicMaterial({ color: Colors.gray }),
17 | darkGray: new THREE.MeshBasicMaterial({ color: Colors.darkGray }),
18 | }
19 |
20 | export { Colors, Materials }
21 |
--------------------------------------------------------------------------------
/src/three/encoders.js:
--------------------------------------------------------------------------------
1 | import { THREE } from '@/three/three'
2 |
3 | export default function (rhinoObject) {
4 | const rhinoObjTypeName = rhinoObject.constructor.name
5 | let encoderFunc = encoders[rhinoObjTypeName]
6 | if (!encoderFunc) {
7 | console.warn(`no three js encoder for ${rhinoObjTypeName}`)
8 | return null
9 | }
10 | return encoderFunc(rhinoObject)
11 | }
12 |
13 | const materials = {
14 | line: new THREE.LineBasicMaterial({ color: 0x000000 }),
15 | wire: new THREE.LineBasicMaterial({ color: 0x000000 }),
16 | mesh: new THREE.MeshPhongMaterial({ color: 0xffffff }),
17 | // mesh: new THREE.MeshBasicMaterial({color: 0xe0e0e0}),
18 | }
19 |
20 | const encoders = {
21 | 'LineCurve': (rhinoLineCurve) => {
22 | const pt1 = rhinoLineCurve.pointAtStart
23 | const pt2 = rhinoLineCurve.pointAtEnd
24 | const geometry = new THREE.Geometry()
25 | geometry.vertices.push(new THREE.Vector3(...pt1))
26 | geometry.vertices.push(new THREE.Vector3(...pt2))
27 | return new THREE.Line(geometry, materials.line)
28 | },
29 | 'NurbsCurve': (rhinoCurve) => {
30 | const geometry = new THREE.Geometry()
31 | const domain = rhinoCurve.domain
32 | const start = domain[0]
33 | const range = domain[1] - domain[0]
34 | var interval = range / 50.0
35 | for (var i = 0; i < 51; i++) {
36 | let t = start + i * interval
37 | let pt = rhinoCurve.pointAt(t)
38 | geometry.vertices.push(new THREE.Vector3(pt[0], pt[1], pt[2]))
39 | }
40 | return new THREE.Line(geometry, materials.wire)
41 | },
42 | 'Mesh': (rhinoMesh) => {
43 | const geometry = new THREE.BufferGeometry()
44 | const vertices = rhinoMesh.vertices()
45 | const faces = rhinoMesh.faces()
46 | const normals = rhinoMesh.normals()
47 |
48 | var vertexbuffer = new Float32Array(3 * vertices.count)
49 | for (let i = 0; i < vertices.count; i++) {
50 | let pt = vertices.get(i)
51 | vertexbuffer[i * 3] = pt[0]
52 | vertexbuffer[i * 3 + 1] = pt[1]
53 | vertexbuffer[i * 3 + 2] = pt[2]
54 | }
55 | geometry.addAttribute('position', new THREE.BufferAttribute(vertexbuffer, 3))
56 | const indices = []
57 |
58 | for (let i = 0; i < faces.count; i++) {
59 | const face = faces.get(i)
60 | indices.push(face[0], face[1], face[2])
61 | if (face[2] !== face[3]) {
62 | indices.push(face[2], face[3], face[0])
63 | }
64 | }
65 | geometry.setIndex(indices)
66 |
67 | var normalBuffer = new Float32Array(3 * normals.count)
68 | for (let i = 0; i < normals.count; i++) {
69 | const pt = normals.get(i)
70 | normalBuffer[i * 3] = pt[0]
71 | normalBuffer[i * 3 + 1] = pt[1]
72 | normalBuffer[i * 3 + 2] = pt[1]
73 | }
74 | geometry.addAttribute('normal', new THREE.BufferAttribute(normalBuffer, 3))
75 | return new THREE.Mesh(geometry, materials.mesh)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/three/environment.js:
--------------------------------------------------------------------------------
1 | import { THREE } from '@/three/three'
2 | import { Colors } from '@/three/colors'
3 | import helper from '@/three/helpers'
4 |
5 | export default function Environment (options) {
6 | window.addEventListener('resize', this.handleResize.bind(this), false)
7 |
8 | const defaultOptions = {
9 | background: Colors.offWhite,
10 | axis: true,
11 | sceneSize: 500,
12 | axisSize: 100,
13 | grid: {
14 | show: true,
15 | size: 2000,
16 | cellSize: 10,
17 | major: 5,
18 | }
19 | }
20 | const envOptions = Object.assign(defaultOptions, options)
21 |
22 | this.container = document.getElementById('three-container')
23 | let canvasWidth = this.container.offsetWidth
24 | let canvasHeight = this.container.offsetHeight
25 |
26 | this.renderer = new THREE.WebGLRenderer({ antialias: true })
27 | this.renderer.setPixelRatio(window.devicePixelRatio)
28 | this.renderer.setSize(canvasWidth, canvasHeight)
29 | this.container.appendChild(this.renderer.domElement)
30 |
31 | const cameraDistance = envOptions.sceneSize * 1
32 | this.camera = new THREE.PerspectiveCamera(45, canvasWidth / canvasHeight, 1, envOptions.sceneSize * 10)
33 | this.camera.position.set(cameraDistance, cameraDistance, cameraDistance / 2)
34 | this.camera.up.set(0, 0, 1)
35 | this.camera.lookAt(new THREE.Vector3(0, 0, 0))
36 |
37 | this.controls = new THREE.OrbitControls(this.camera, this.container)
38 | this.controls.enableDamping = true
39 | this.controls.dampingFactor = 0.3
40 | this.controls.minDistance = envOptions.sceneSize / 5
41 | this.controls.maxDistance = envOptions.sceneSize
42 |
43 | this.raycaster = new THREE.Raycaster()
44 | this.mouse = new THREE.Vector2()
45 | this.raycaster.setFromCamera(this.mouse, this.camera)
46 |
47 | this.scene = new THREE.Scene()
48 | this.scene.background = envOptions.background
49 | this.scene.fog = new THREE.Fog(envOptions.background, 1, envOptions.sceneSize * 5)
50 |
51 | // Temporary - Generic Light
52 | let light = new THREE.DirectionalLight(0xffffff)
53 | light.position.set(0, 0, 1)
54 | this.scene.add(light)
55 | var light2 = new THREE.DirectionalLight(0xaaaaaa)
56 | light2.position.set(-1, -1, 0)
57 | this.scene.add(light2)
58 |
59 | if (envOptions.axis) {
60 | this.scene.add(new THREE.AxesHelper(envOptions.axisSize))
61 | }
62 |
63 | if (envOptions.grid.show) {
64 | const grid = helper.makeGrid(envOptions.grid.size, envOptions.grid.cellSize, envOptions.grid.major, Colors.darkGray, Colors.lightGray)
65 | this.scene.add(grid)
66 | }
67 |
68 | // Setup flag native scene objects so we can keep them when wiping scene
69 | this.scene.children.map(o => {
70 | o.userData.isEnvironmentObject = true
71 | })
72 | this.renderForever()
73 | }
74 |
75 | Environment.prototype.renderForever = function () {
76 | requestAnimationFrame(this.renderForever.bind(this))
77 | this.controls.update()
78 | this.renderer.render(this.scene, this.camera)
79 | }
80 |
81 | Environment.prototype.clear = function () {
82 | this.scene.children = this.scene.children.filter(o => o.userData.isEnvironmentObject)
83 | }
84 |
85 | Environment.prototype.handleResize = function () {
86 | const canvasWidth = this.container.offsetWidth
87 | const canvasHeight = this.container.offsetHeight
88 | this.camera.aspect = canvasWidth / canvasHeight
89 | this.camera.updateProjectionMatrix()
90 | this.renderer.setSize(canvasWidth, canvasHeight)
91 | }
92 |
93 | Environment.prototype.tearDownScene = function () {
94 | window.removeEventListener('resize', this.handleResize)
95 | }
96 |
--------------------------------------------------------------------------------
/src/three/helpers.js:
--------------------------------------------------------------------------------
1 | import { THREE } from '@/three/three.js'
2 | // import { Colors } from '@/three/colors.js'
3 |
4 | export default {
5 |
6 | makeGrid (gridSize, cellSize, majorInterval, colorMajor, colorMinor, centered = true) {
7 | let offset = centered ? gridSize / 2 : 0
8 |
9 | var geometry = new THREE.Geometry()
10 | geometry.vertices.push(new THREE.Vector3(0 - offset, 0, 0))
11 | geometry.vertices.push(new THREE.Vector3(gridSize - offset, 0, 0))
12 | let matLineMajor = new THREE.LineBasicMaterial({ color: colorMajor })
13 | let matLineMinor = new THREE.LineBasicMaterial({ color: colorMinor })
14 |
15 | let group = new THREE.Group()
16 | let currentSize = 0
17 |
18 | for (let i = 0; currentSize <= gridSize; i++) {
19 | currentSize = i * cellSize
20 | let color = i % majorInterval === 0 ? matLineMajor : matLineMinor
21 | let line = new THREE.Line(geometry, color)
22 |
23 | line.position.y = currentSize - offset
24 | group.add(line)
25 |
26 | let line2 = new THREE.Line(geometry, color)
27 | line2.rotation.z = Math.PI / 2
28 | line2.position.x = currentSize - offset
29 | group.add(line2)
30 | }
31 | return group
32 | },
33 | }
34 |
--------------------------------------------------------------------------------
/src/three/three.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | window.THREE = THREE
3 | require('@/three/vendor/OrbitControls.js')
4 | export { THREE }
5 |
--------------------------------------------------------------------------------
/src/three/vendor/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | */
8 |
9 | // This set of controls performs orbiting, dollying (zooming), and panning.
10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
11 | //
12 | // Orbit - left mouse / touch: one-finger move
13 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
14 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
15 |
16 | THREE.OrbitControls = function ( object, domElement ) {
17 |
18 | this.object = object;
19 |
20 | this.domElement = ( domElement !== undefined ) ? domElement : document;
21 |
22 | // Set to false to disable this control
23 | this.enabled = true;
24 |
25 | // "target" sets the location of focus, where the object orbits around
26 | this.target = new THREE.Vector3();
27 |
28 | // How far you can dolly in and out ( PerspectiveCamera only )
29 | this.minDistance = 0;
30 | this.maxDistance = Infinity;
31 |
32 | // How far you can zoom in and out ( OrthographicCamera only )
33 | this.minZoom = 0;
34 | this.maxZoom = Infinity;
35 |
36 | // How far you can orbit vertically, upper and lower limits.
37 | // Range is 0 to Math.PI radians.
38 | this.minPolarAngle = 0; // radians
39 | this.maxPolarAngle = Math.PI; // radians
40 |
41 | // How far you can orbit horizontally, upper and lower limits.
42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
43 | this.minAzimuthAngle = - Infinity; // radians
44 | this.maxAzimuthAngle = Infinity; // radians
45 |
46 | // Set to true to enable damping (inertia)
47 | // If damping is enabled, you must call controls.update() in your animation loop
48 | this.enableDamping = false;
49 | this.dampingFactor = 0.25;
50 |
51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
52 | // Set to false to disable zooming
53 | this.enableZoom = true;
54 | this.zoomSpeed = 1.0;
55 |
56 | // Set to false to disable rotating
57 | this.enableRotate = true;
58 | this.rotateSpeed = 1.0;
59 |
60 | // Set to false to disable panning
61 | this.enablePan = true;
62 | this.panSpeed = 1.0;
63 | this.screenSpacePanning = false; // if true, pan in screen-space
64 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
65 |
66 | // Set to true to automatically rotate around the target
67 | // If auto-rotate is enabled, you must call controls.update() in your animation loop
68 | this.autoRotate = false;
69 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
70 |
71 | // Set to false to disable use of the keys
72 | this.enableKeys = true;
73 |
74 | // The four arrow keys
75 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
76 |
77 | // Mouse buttons
78 | this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT };
79 |
80 | // for reset
81 | this.target0 = this.target.clone();
82 | this.position0 = this.object.position.clone();
83 | this.zoom0 = this.object.zoom;
84 |
85 | //
86 | // public methods
87 | //
88 |
89 | this.getPolarAngle = function () {
90 |
91 | return spherical.phi;
92 |
93 | };
94 |
95 | this.getAzimuthalAngle = function () {
96 |
97 | return spherical.theta;
98 |
99 | };
100 |
101 | this.saveState = function () {
102 |
103 | scope.target0.copy( scope.target );
104 | scope.position0.copy( scope.object.position );
105 | scope.zoom0 = scope.object.zoom;
106 |
107 | };
108 |
109 | this.reset = function () {
110 |
111 | scope.target.copy( scope.target0 );
112 | scope.object.position.copy( scope.position0 );
113 | scope.object.zoom = scope.zoom0;
114 |
115 | scope.object.updateProjectionMatrix();
116 | scope.dispatchEvent( changeEvent );
117 |
118 | scope.update();
119 |
120 | state = STATE.NONE;
121 |
122 | };
123 |
124 | // this method is exposed, but perhaps it would be better if we can make it private...
125 | this.update = function () {
126 |
127 | var offset = new THREE.Vector3();
128 |
129 | // so camera.up is the orbit axis
130 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
131 | var quatInverse = quat.clone().inverse();
132 |
133 | var lastPosition = new THREE.Vector3();
134 | var lastQuaternion = new THREE.Quaternion();
135 |
136 | return function update() {
137 |
138 | var position = scope.object.position;
139 |
140 | offset.copy( position ).sub( scope.target );
141 |
142 | // rotate offset to "y-axis-is-up" space
143 | offset.applyQuaternion( quat );
144 |
145 | // angle from z-axis around y-axis
146 | spherical.setFromVector3( offset );
147 |
148 | if ( scope.autoRotate && state === STATE.NONE ) {
149 |
150 | rotateLeft( getAutoRotationAngle() );
151 |
152 | }
153 |
154 | spherical.theta += sphericalDelta.theta;
155 | spherical.phi += sphericalDelta.phi;
156 |
157 | // restrict theta to be between desired limits
158 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
159 |
160 | // restrict phi to be between desired limits
161 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
162 |
163 | spherical.makeSafe();
164 |
165 |
166 | spherical.radius *= scale;
167 |
168 | // restrict radius to be between desired limits
169 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
170 |
171 | // move target to panned location
172 | scope.target.add( panOffset );
173 |
174 | offset.setFromSpherical( spherical );
175 |
176 | // rotate offset back to "camera-up-vector-is-up" space
177 | offset.applyQuaternion( quatInverse );
178 |
179 | position.copy( scope.target ).add( offset );
180 |
181 | scope.object.lookAt( scope.target );
182 |
183 | if ( scope.enableDamping === true ) {
184 |
185 | sphericalDelta.theta *= ( 1 - scope.dampingFactor );
186 | sphericalDelta.phi *= ( 1 - scope.dampingFactor );
187 |
188 | panOffset.multiplyScalar( 1 - scope.dampingFactor );
189 |
190 | } else {
191 |
192 | sphericalDelta.set( 0, 0, 0 );
193 |
194 | panOffset.set( 0, 0, 0 );
195 |
196 | }
197 |
198 | scale = 1;
199 |
200 | // update condition is:
201 | // min(camera displacement, camera rotation in radians)^2 > EPS
202 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
203 |
204 | if ( zoomChanged ||
205 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
206 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
207 |
208 | scope.dispatchEvent( changeEvent );
209 |
210 | lastPosition.copy( scope.object.position );
211 | lastQuaternion.copy( scope.object.quaternion );
212 | zoomChanged = false;
213 |
214 | return true;
215 |
216 | }
217 |
218 | return false;
219 |
220 | };
221 |
222 | }();
223 |
224 | this.dispose = function () {
225 |
226 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
227 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
228 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
229 |
230 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
231 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
232 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
233 |
234 | document.removeEventListener( 'mousemove', onMouseMove, false );
235 | document.removeEventListener( 'mouseup', onMouseUp, false );
236 |
237 | window.removeEventListener( 'keydown', onKeyDown, false );
238 |
239 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
240 |
241 | };
242 |
243 | //
244 | // internals
245 | //
246 |
247 | var scope = this;
248 |
249 | var changeEvent = { type: 'change' };
250 | var startEvent = { type: 'start' };
251 | var endEvent = { type: 'end' };
252 |
253 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 };
254 |
255 | var state = STATE.NONE;
256 |
257 | var EPS = 0.000001;
258 |
259 | // current position in spherical coordinates
260 | var spherical = new THREE.Spherical();
261 | var sphericalDelta = new THREE.Spherical();
262 |
263 | var scale = 1;
264 | var panOffset = new THREE.Vector3();
265 | var zoomChanged = false;
266 |
267 | var rotateStart = new THREE.Vector2();
268 | var rotateEnd = new THREE.Vector2();
269 | var rotateDelta = new THREE.Vector2();
270 |
271 | var panStart = new THREE.Vector2();
272 | var panEnd = new THREE.Vector2();
273 | var panDelta = new THREE.Vector2();
274 |
275 | var dollyStart = new THREE.Vector2();
276 | var dollyEnd = new THREE.Vector2();
277 | var dollyDelta = new THREE.Vector2();
278 |
279 | function getAutoRotationAngle() {
280 |
281 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
282 |
283 | }
284 |
285 | function getZoomScale() {
286 |
287 | return Math.pow( 0.95, scope.zoomSpeed );
288 |
289 | }
290 |
291 | function rotateLeft( angle ) {
292 |
293 | sphericalDelta.theta -= angle;
294 |
295 | }
296 |
297 | function rotateUp( angle ) {
298 |
299 | sphericalDelta.phi -= angle;
300 |
301 | }
302 |
303 | var panLeft = function () {
304 |
305 | var v = new THREE.Vector3();
306 |
307 | return function panLeft( distance, objectMatrix ) {
308 |
309 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
310 | v.multiplyScalar( - distance );
311 |
312 | panOffset.add( v );
313 |
314 | };
315 |
316 | }();
317 |
318 | var panUp = function () {
319 |
320 | var v = new THREE.Vector3();
321 |
322 | return function panUp( distance, objectMatrix ) {
323 |
324 | if ( scope.screenSpacePanning === true ) {
325 |
326 | v.setFromMatrixColumn( objectMatrix, 1 );
327 |
328 | } else {
329 |
330 | v.setFromMatrixColumn( objectMatrix, 0 );
331 | v.crossVectors( scope.object.up, v );
332 |
333 | }
334 |
335 | v.multiplyScalar( distance );
336 |
337 | panOffset.add( v );
338 |
339 | };
340 |
341 | }();
342 |
343 | // deltaX and deltaY are in pixels; right and down are positive
344 | var pan = function () {
345 |
346 | var offset = new THREE.Vector3();
347 |
348 | return function pan( deltaX, deltaY ) {
349 |
350 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
351 |
352 | if ( scope.object.isPerspectiveCamera ) {
353 |
354 | // perspective
355 | var position = scope.object.position;
356 | offset.copy( position ).sub( scope.target );
357 | var targetDistance = offset.length();
358 |
359 | // half of the fov is center to top of screen
360 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
361 |
362 | // we use only clientHeight here so aspect ratio does not distort speed
363 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
364 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
365 |
366 | } else if ( scope.object.isOrthographicCamera ) {
367 |
368 | // orthographic
369 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
370 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
371 |
372 | } else {
373 |
374 | // camera neither orthographic nor perspective
375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
376 | scope.enablePan = false;
377 |
378 | }
379 |
380 | };
381 |
382 | }();
383 |
384 | function dollyIn( dollyScale ) {
385 |
386 | if ( scope.object.isPerspectiveCamera ) {
387 |
388 | scale /= dollyScale;
389 |
390 | } else if ( scope.object.isOrthographicCamera ) {
391 |
392 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
393 | scope.object.updateProjectionMatrix();
394 | zoomChanged = true;
395 |
396 | } else {
397 |
398 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
399 | scope.enableZoom = false;
400 |
401 | }
402 |
403 | }
404 |
405 | function dollyOut( dollyScale ) {
406 |
407 | if ( scope.object.isPerspectiveCamera ) {
408 |
409 | scale *= dollyScale;
410 |
411 | } else if ( scope.object.isOrthographicCamera ) {
412 |
413 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
414 | scope.object.updateProjectionMatrix();
415 | zoomChanged = true;
416 |
417 | } else {
418 |
419 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
420 | scope.enableZoom = false;
421 |
422 | }
423 |
424 | }
425 |
426 | //
427 | // event callbacks - update the object state
428 | //
429 |
430 | function handleMouseDownRotate( event ) {
431 |
432 | //console.log( 'handleMouseDownRotate' );
433 |
434 | rotateStart.set( event.clientX, event.clientY );
435 |
436 | }
437 |
438 | function handleMouseDownDolly( event ) {
439 |
440 | //console.log( 'handleMouseDownDolly' );
441 |
442 | dollyStart.set( event.clientX, event.clientY );
443 |
444 | }
445 |
446 | function handleMouseDownPan( event ) {
447 |
448 | //console.log( 'handleMouseDownPan' );
449 |
450 | panStart.set( event.clientX, event.clientY );
451 |
452 | }
453 |
454 | function handleMouseMoveRotate( event ) {
455 |
456 | //console.log( 'handleMouseMoveRotate' );
457 |
458 | rotateEnd.set( event.clientX, event.clientY );
459 |
460 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
461 |
462 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
463 |
464 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
465 |
466 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
467 |
468 | rotateStart.copy( rotateEnd );
469 |
470 | scope.update();
471 |
472 | }
473 |
474 | function handleMouseMoveDolly( event ) {
475 |
476 | //console.log( 'handleMouseMoveDolly' );
477 |
478 | dollyEnd.set( event.clientX, event.clientY );
479 |
480 | dollyDelta.subVectors( dollyEnd, dollyStart );
481 |
482 | if ( dollyDelta.y > 0 ) {
483 |
484 | dollyIn( getZoomScale() );
485 |
486 | } else if ( dollyDelta.y < 0 ) {
487 |
488 | dollyOut( getZoomScale() );
489 |
490 | }
491 |
492 | dollyStart.copy( dollyEnd );
493 |
494 | scope.update();
495 |
496 | }
497 |
498 | function handleMouseMovePan( event ) {
499 |
500 | //console.log( 'handleMouseMovePan' );
501 |
502 | panEnd.set( event.clientX, event.clientY );
503 |
504 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
505 |
506 | pan( panDelta.x, panDelta.y );
507 |
508 | panStart.copy( panEnd );
509 |
510 | scope.update();
511 |
512 | }
513 |
514 | function handleMouseUp( event ) {
515 |
516 | // console.log( 'handleMouseUp' );
517 |
518 | }
519 |
520 | function handleMouseWheel( event ) {
521 |
522 | // console.log( 'handleMouseWheel' );
523 |
524 | if ( event.deltaY < 0 ) {
525 |
526 | dollyOut( getZoomScale() );
527 |
528 | } else if ( event.deltaY > 0 ) {
529 |
530 | dollyIn( getZoomScale() );
531 |
532 | }
533 |
534 | scope.update();
535 |
536 | }
537 |
538 | function handleKeyDown( event ) {
539 |
540 | // console.log( 'handleKeyDown' );
541 |
542 | var needsUpdate = false;
543 |
544 | switch ( event.keyCode ) {
545 |
546 | case scope.keys.UP:
547 | pan( 0, scope.keyPanSpeed );
548 | needsUpdate = true;
549 | break;
550 |
551 | case scope.keys.BOTTOM:
552 | pan( 0, - scope.keyPanSpeed );
553 | needsUpdate = true;
554 | break;
555 |
556 | case scope.keys.LEFT:
557 | pan( scope.keyPanSpeed, 0 );
558 | needsUpdate = true;
559 | break;
560 |
561 | case scope.keys.RIGHT:
562 | pan( - scope.keyPanSpeed, 0 );
563 | needsUpdate = true;
564 | break;
565 |
566 | }
567 |
568 | if ( needsUpdate ) {
569 |
570 | // prevent the browser from scrolling on cursor keys
571 | event.preventDefault();
572 |
573 | scope.update();
574 |
575 | }
576 |
577 |
578 | }
579 |
580 | function handleTouchStartRotate( event ) {
581 |
582 | //console.log( 'handleTouchStartRotate' );
583 |
584 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
585 |
586 | }
587 |
588 | function handleTouchStartDollyPan( event ) {
589 |
590 | //console.log( 'handleTouchStartDollyPan' );
591 |
592 | if ( scope.enableZoom ) {
593 |
594 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
595 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
596 |
597 | var distance = Math.sqrt( dx * dx + dy * dy );
598 |
599 | dollyStart.set( 0, distance );
600 |
601 | }
602 |
603 | if ( scope.enablePan ) {
604 |
605 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
606 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
607 |
608 | panStart.set( x, y );
609 |
610 | }
611 |
612 | }
613 |
614 | function handleTouchMoveRotate( event ) {
615 |
616 | //console.log( 'handleTouchMoveRotate' );
617 |
618 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
619 |
620 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
621 |
622 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
623 |
624 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
625 |
626 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
627 |
628 | rotateStart.copy( rotateEnd );
629 |
630 | scope.update();
631 |
632 | }
633 |
634 | function handleTouchMoveDollyPan( event ) {
635 |
636 | //console.log( 'handleTouchMoveDollyPan' );
637 |
638 | if ( scope.enableZoom ) {
639 |
640 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
641 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
642 |
643 | var distance = Math.sqrt( dx * dx + dy * dy );
644 |
645 | dollyEnd.set( 0, distance );
646 |
647 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
648 |
649 | dollyIn( dollyDelta.y );
650 |
651 | dollyStart.copy( dollyEnd );
652 |
653 | }
654 |
655 | if ( scope.enablePan ) {
656 |
657 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
658 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
659 |
660 | panEnd.set( x, y );
661 |
662 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
663 |
664 | pan( panDelta.x, panDelta.y );
665 |
666 | panStart.copy( panEnd );
667 |
668 | }
669 |
670 | scope.update();
671 |
672 | }
673 |
674 | function handleTouchEnd( event ) {
675 |
676 | //console.log( 'handleTouchEnd' );
677 |
678 | }
679 |
680 | //
681 | // event handlers - FSM: listen for events and reset state
682 | //
683 |
684 | function onMouseDown( event ) {
685 |
686 | if ( scope.enabled === false ) return;
687 |
688 | // Prevent the browser from scrolling.
689 |
690 | event.preventDefault();
691 |
692 | // Manually set the focus since calling preventDefault above
693 | // prevents the browser from setting it automatically.
694 |
695 | scope.domElement.focus ? scope.domElement.focus() : window.focus();
696 |
697 | switch ( event.button ) {
698 |
699 | case scope.mouseButtons.LEFT:
700 |
701 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
702 |
703 | if ( scope.enablePan === false ) return;
704 |
705 | handleMouseDownPan( event );
706 |
707 | state = STATE.PAN;
708 |
709 | } else {
710 |
711 | if ( scope.enableRotate === false ) return;
712 |
713 | handleMouseDownRotate( event );
714 |
715 | state = STATE.ROTATE;
716 |
717 | }
718 |
719 | break;
720 |
721 | case scope.mouseButtons.MIDDLE:
722 |
723 | if ( scope.enableZoom === false ) return;
724 |
725 | handleMouseDownDolly( event );
726 |
727 | state = STATE.DOLLY;
728 |
729 | break;
730 |
731 | case scope.mouseButtons.RIGHT:
732 |
733 | if ( scope.enablePan === false ) return;
734 |
735 | handleMouseDownPan( event );
736 |
737 | state = STATE.PAN;
738 |
739 | break;
740 |
741 | }
742 |
743 | if ( state !== STATE.NONE ) {
744 |
745 | document.addEventListener( 'mousemove', onMouseMove, false );
746 | document.addEventListener( 'mouseup', onMouseUp, false );
747 |
748 | scope.dispatchEvent( startEvent );
749 |
750 | }
751 |
752 | }
753 |
754 | function onMouseMove( event ) {
755 |
756 | if ( scope.enabled === false ) return;
757 |
758 | event.preventDefault();
759 |
760 | switch ( state ) {
761 |
762 | case STATE.ROTATE:
763 |
764 | if ( scope.enableRotate === false ) return;
765 |
766 | handleMouseMoveRotate( event );
767 |
768 | break;
769 |
770 | case STATE.DOLLY:
771 |
772 | if ( scope.enableZoom === false ) return;
773 |
774 | handleMouseMoveDolly( event );
775 |
776 | break;
777 |
778 | case STATE.PAN:
779 |
780 | if ( scope.enablePan === false ) return;
781 |
782 | handleMouseMovePan( event );
783 |
784 | break;
785 |
786 | }
787 |
788 | }
789 |
790 | function onMouseUp( event ) {
791 |
792 | if ( scope.enabled === false ) return;
793 |
794 | handleMouseUp( event );
795 |
796 | document.removeEventListener( 'mousemove', onMouseMove, false );
797 | document.removeEventListener( 'mouseup', onMouseUp, false );
798 |
799 | scope.dispatchEvent( endEvent );
800 |
801 | state = STATE.NONE;
802 |
803 | }
804 |
805 | function onMouseWheel( event ) {
806 |
807 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
808 |
809 | event.preventDefault();
810 | event.stopPropagation();
811 |
812 | scope.dispatchEvent( startEvent );
813 |
814 | handleMouseWheel( event );
815 |
816 | scope.dispatchEvent( endEvent );
817 |
818 | }
819 |
820 | function onKeyDown( event ) {
821 |
822 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
823 |
824 | handleKeyDown( event );
825 |
826 | }
827 |
828 | function onTouchStart( event ) {
829 |
830 | if ( scope.enabled === false ) return;
831 |
832 | event.preventDefault();
833 |
834 | switch ( event.touches.length ) {
835 |
836 | case 1: // one-fingered touch: rotate
837 |
838 | if ( scope.enableRotate === false ) return;
839 |
840 | handleTouchStartRotate( event );
841 |
842 | state = STATE.TOUCH_ROTATE;
843 |
844 | break;
845 |
846 | case 2: // two-fingered touch: dolly-pan
847 |
848 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
849 |
850 | handleTouchStartDollyPan( event );
851 |
852 | state = STATE.TOUCH_DOLLY_PAN;
853 |
854 | break;
855 |
856 | default:
857 |
858 | state = STATE.NONE;
859 |
860 | }
861 |
862 | if ( state !== STATE.NONE ) {
863 |
864 | scope.dispatchEvent( startEvent );
865 |
866 | }
867 |
868 | }
869 |
870 | function onTouchMove( event ) {
871 |
872 | if ( scope.enabled === false ) return;
873 |
874 | event.preventDefault();
875 | event.stopPropagation();
876 |
877 | switch ( event.touches.length ) {
878 |
879 | case 1: // one-fingered touch: rotate
880 |
881 | if ( scope.enableRotate === false ) return;
882 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?
883 |
884 | handleTouchMoveRotate( event );
885 |
886 | break;
887 |
888 | case 2: // two-fingered touch: dolly-pan
889 |
890 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
891 | if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed?
892 |
893 | handleTouchMoveDollyPan( event );
894 |
895 | break;
896 |
897 | default:
898 |
899 | state = STATE.NONE;
900 |
901 | }
902 |
903 | }
904 |
905 | function onTouchEnd( event ) {
906 |
907 | if ( scope.enabled === false ) return;
908 |
909 | handleTouchEnd( event );
910 |
911 | scope.dispatchEvent( endEvent );
912 |
913 | state = STATE.NONE;
914 |
915 | }
916 |
917 | function onContextMenu( event ) {
918 |
919 | if ( scope.enabled === false ) return;
920 |
921 | event.preventDefault();
922 |
923 | }
924 |
925 | //
926 |
927 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
928 |
929 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
930 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
931 |
932 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
933 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
934 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
935 |
936 | window.addEventListener( 'keydown', onKeyDown, false );
937 |
938 | // force an update at start
939 |
940 | this.update();
941 |
942 | };
943 |
944 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
945 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
946 |
947 | Object.defineProperties( THREE.OrbitControls.prototype, {
948 |
949 | center: {
950 |
951 | get: function () {
952 |
953 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
954 | return this.target;
955 |
956 | }
957 |
958 | },
959 |
960 | // backward compatibility
961 |
962 | noZoom: {
963 |
964 | get: function () {
965 |
966 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
967 | return ! this.enableZoom;
968 |
969 | },
970 |
971 | set: function ( value ) {
972 |
973 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
974 | this.enableZoom = ! value;
975 |
976 | }
977 |
978 | },
979 |
980 | noRotate: {
981 |
982 | get: function () {
983 |
984 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
985 | return ! this.enableRotate;
986 |
987 | },
988 |
989 | set: function ( value ) {
990 |
991 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
992 | this.enableRotate = ! value;
993 |
994 | }
995 |
996 | },
997 |
998 | noPan: {
999 |
1000 | get: function () {
1001 |
1002 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
1003 | return ! this.enablePan;
1004 |
1005 | },
1006 |
1007 | set: function ( value ) {
1008 |
1009 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
1010 | this.enablePan = ! value;
1011 |
1012 | }
1013 |
1014 | },
1015 |
1016 | noKeys: {
1017 |
1018 | get: function () {
1019 |
1020 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
1021 | return ! this.enableKeys;
1022 |
1023 | },
1024 |
1025 | set: function ( value ) {
1026 |
1027 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
1028 | this.enableKeys = ! value;
1029 |
1030 | }
1031 |
1032 | },
1033 |
1034 | staticMoving: {
1035 |
1036 | get: function () {
1037 |
1038 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
1039 | return ! this.enableDamping;
1040 |
1041 | },
1042 |
1043 | set: function ( value ) {
1044 |
1045 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
1046 | this.enableDamping = ! value;
1047 |
1048 | }
1049 |
1050 | },
1051 |
1052 | dynamicDampingFactor: {
1053 |
1054 | get: function () {
1055 |
1056 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1057 | return this.dampingFactor;
1058 |
1059 | },
1060 |
1061 | set: function ( value ) {
1062 |
1063 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1064 | this.dampingFactor = value;
1065 |
1066 | }
1067 |
1068 | }
1069 |
1070 | } );
1071 |
--------------------------------------------------------------------------------