├── A4.png ├── Greenlab Data.xlsx ├── LICENSE ├── README.md ├── background.A4.jpg ├── package-lock.json ├── package.json ├── public ├── assets │ ├── clear.png │ ├── dark.png │ ├── day-view.png │ ├── delete.png │ ├── download--v1.png │ ├── download.png │ ├── email.png │ ├── engineering.png │ ├── help.png │ ├── icon.png │ ├── light.png │ ├── logo.png │ ├── logoBlack.png │ ├── menu.png │ ├── refresh.png │ ├── settings.png │ ├── xls-import.png │ ├── ziko.PNG │ └── ziko.png ├── index.html ├── index.js ├── js │ ├── db.js │ ├── footer.js │ ├── form.js │ ├── header.js │ ├── preview.js │ ├── scene.js │ └── theme.js └── lib │ ├── 0 │ ├── CSS3DRenderer.js │ ├── OrbitControls.js │ ├── TransformControls.js │ ├── htmltoimage.js │ ├── jspdf.js │ ├── socket.io.js │ ├── three.js │ ├── three.min.js │ ├── three.module.js │ └── xlsx.js ├── server.js └── video.mp4 /A4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/A4.png -------------------------------------------------------------------------------- /Greenlab Data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/Greenlab Data.xlsx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ZAKARIA ELALAOUI 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 | # Greenlab-Challenge 2 | ## Preview 3 | [Demo ](https://drive.google.com/file/d/11SMlCCuraowYi7QtERldPtLFl3TP0B9Y/view?usp=sharing) 4 |
5 | [Want To Try!](https://greenlab-challenge.vercel.app/) 6 | ## Libraries 7 | - [zikojs](https://github.com/zakarialaoui10/ziko.js) 8 | - [threejs](https://threejs.org/) 9 | - [xlsxjs](https://www.npmjs.com/package/xlsx) 10 | - [htmltoimage](https://www.npmjs.com/package/html-to-image) 11 | - [jsPdf](https://www.npmjs.com/package/jspdf) 12 | - [express](https://expressjs.com/fr/) 13 | - [socket.io](https://socket.io/fr/) 14 | - [nodemailer](https://nodemailer.com/about/) 15 | 16 | 17 | ## Features 18 | - [x] Change font family & size and color 19 | - [X] Toggle Dark/Light Mode 20 | - [x] store settings in Local storage (styles & positions) 21 | - [x] Upload Background 22 | - [x] Export Data from Excel File 23 | - [x] Orbit and transform control 24 | - [x] Preview of generated Attestations 25 | - [x] Download Attestations as pdf file 26 | - [x] Send Attestations via Email 27 | 28 | ## Index 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Refresh
Reset settings
help
Toggle Dark/Light Mode
47 | 48 | ## Limitations 49 | To send attestaions via Email we need real-time communication between the client and the server,I use socket.io library to reach this communication ,on the other hand Serverless Functions on Vercel are stateless and have a maximum execution duration. 50 | As a result, it is not possible to maintain a WebSocket connection to a Serverless Function. 51 | 52 | ## Want to try localy 53 | 54 | 55 |
    56 |
  1. Install any IDE (I Recommand Vs Code)
  2. 57 |
  3. Install Node.js
  4. 58 |
  5. Clone this repository
  6. 59 |
  7. 60 |
61 | 62 | 63 | ``` 64 | gh repo clone zakarialaoui10/Greenlab-Challenge 65 | ``` 66 | 67 | 68 | ## License 69 | [MIT](https://choosealicense.com/licenses/mit/) 70 | 71 | 72 | 75 | -------------------------------------------------------------------------------- /background.A4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/background.A4.jpg -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greenlab-app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/component-emitter": { 8 | "version": "1.2.11", 9 | "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", 10 | "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==" 11 | }, 12 | "@types/cookie": { 13 | "version": "0.4.1", 14 | "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", 15 | "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" 16 | }, 17 | "@types/cors": { 18 | "version": "2.8.12", 19 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", 20 | "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" 21 | }, 22 | "@types/node": { 23 | "version": "17.0.33", 24 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.33.tgz", 25 | "integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==" 26 | }, 27 | "accepts": { 28 | "version": "1.3.8", 29 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 30 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 31 | "requires": { 32 | "mime-types": "~2.1.34", 33 | "negotiator": "0.6.3" 34 | } 35 | }, 36 | "array-flatten": { 37 | "version": "1.1.1", 38 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 39 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 40 | }, 41 | "base64id": { 42 | "version": "2.0.0", 43 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 44 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" 45 | }, 46 | "body-parser": { 47 | "version": "1.20.0", 48 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", 49 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", 50 | "requires": { 51 | "bytes": "3.1.2", 52 | "content-type": "~1.0.4", 53 | "debug": "2.6.9", 54 | "depd": "2.0.0", 55 | "destroy": "1.2.0", 56 | "http-errors": "2.0.0", 57 | "iconv-lite": "0.4.24", 58 | "on-finished": "2.4.1", 59 | "qs": "6.10.3", 60 | "raw-body": "2.5.1", 61 | "type-is": "~1.6.18", 62 | "unpipe": "1.0.0" 63 | } 64 | }, 65 | "bytes": { 66 | "version": "3.1.2", 67 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 68 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 69 | }, 70 | "call-bind": { 71 | "version": "1.0.2", 72 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 73 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 74 | "requires": { 75 | "function-bind": "^1.1.1", 76 | "get-intrinsic": "^1.0.2" 77 | } 78 | }, 79 | "component-emitter": { 80 | "version": "1.3.0", 81 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 82 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" 83 | }, 84 | "content-disposition": { 85 | "version": "0.5.4", 86 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 87 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 88 | "requires": { 89 | "safe-buffer": "5.2.1" 90 | } 91 | }, 92 | "content-type": { 93 | "version": "1.0.4", 94 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 95 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 96 | }, 97 | "cookie": { 98 | "version": "0.5.0", 99 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 100 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" 101 | }, 102 | "cookie-signature": { 103 | "version": "1.0.6", 104 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 105 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 106 | }, 107 | "cors": { 108 | "version": "2.8.5", 109 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 110 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 111 | "requires": { 112 | "object-assign": "^4", 113 | "vary": "^1" 114 | } 115 | }, 116 | "debug": { 117 | "version": "2.6.9", 118 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 119 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 120 | "requires": { 121 | "ms": "2.0.0" 122 | } 123 | }, 124 | "depd": { 125 | "version": "2.0.0", 126 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 127 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 128 | }, 129 | "destroy": { 130 | "version": "1.2.0", 131 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 132 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 133 | }, 134 | "dotenv": { 135 | "version": "16.0.1", 136 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", 137 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" 138 | }, 139 | "ee-first": { 140 | "version": "1.1.1", 141 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 142 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 143 | }, 144 | "encodeurl": { 145 | "version": "1.0.2", 146 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 147 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 148 | }, 149 | "engine.io": { 150 | "version": "6.2.0", 151 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", 152 | "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", 153 | "requires": { 154 | "@types/cookie": "^0.4.1", 155 | "@types/cors": "^2.8.12", 156 | "@types/node": ">=10.0.0", 157 | "accepts": "~1.3.4", 158 | "base64id": "2.0.0", 159 | "cookie": "~0.4.1", 160 | "cors": "~2.8.5", 161 | "debug": "~4.3.1", 162 | "engine.io-parser": "~5.0.3", 163 | "ws": "~8.2.3" 164 | }, 165 | "dependencies": { 166 | "cookie": { 167 | "version": "0.4.2", 168 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", 169 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" 170 | }, 171 | "debug": { 172 | "version": "4.3.4", 173 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 174 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 175 | "requires": { 176 | "ms": "2.1.2" 177 | } 178 | }, 179 | "ms": { 180 | "version": "2.1.2", 181 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 182 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 183 | } 184 | } 185 | }, 186 | "engine.io-parser": { 187 | "version": "5.0.4", 188 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", 189 | "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==" 190 | }, 191 | "escape-html": { 192 | "version": "1.0.3", 193 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 194 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 195 | }, 196 | "etag": { 197 | "version": "1.8.1", 198 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 199 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 200 | }, 201 | "express": { 202 | "version": "4.18.1", 203 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", 204 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", 205 | "requires": { 206 | "accepts": "~1.3.8", 207 | "array-flatten": "1.1.1", 208 | "body-parser": "1.20.0", 209 | "content-disposition": "0.5.4", 210 | "content-type": "~1.0.4", 211 | "cookie": "0.5.0", 212 | "cookie-signature": "1.0.6", 213 | "debug": "2.6.9", 214 | "depd": "2.0.0", 215 | "encodeurl": "~1.0.2", 216 | "escape-html": "~1.0.3", 217 | "etag": "~1.8.1", 218 | "finalhandler": "1.2.0", 219 | "fresh": "0.5.2", 220 | "http-errors": "2.0.0", 221 | "merge-descriptors": "1.0.1", 222 | "methods": "~1.1.2", 223 | "on-finished": "2.4.1", 224 | "parseurl": "~1.3.3", 225 | "path-to-regexp": "0.1.7", 226 | "proxy-addr": "~2.0.7", 227 | "qs": "6.10.3", 228 | "range-parser": "~1.2.1", 229 | "safe-buffer": "5.2.1", 230 | "send": "0.18.0", 231 | "serve-static": "1.15.0", 232 | "setprototypeof": "1.2.0", 233 | "statuses": "2.0.1", 234 | "type-is": "~1.6.18", 235 | "utils-merge": "1.0.1", 236 | "vary": "~1.1.2" 237 | } 238 | }, 239 | "finalhandler": { 240 | "version": "1.2.0", 241 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 242 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 243 | "requires": { 244 | "debug": "2.6.9", 245 | "encodeurl": "~1.0.2", 246 | "escape-html": "~1.0.3", 247 | "on-finished": "2.4.1", 248 | "parseurl": "~1.3.3", 249 | "statuses": "2.0.1", 250 | "unpipe": "~1.0.0" 251 | } 252 | }, 253 | "forwarded": { 254 | "version": "0.2.0", 255 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 256 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 257 | }, 258 | "fresh": { 259 | "version": "0.5.2", 260 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 261 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 262 | }, 263 | "function-bind": { 264 | "version": "1.1.1", 265 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 266 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 267 | }, 268 | "get-intrinsic": { 269 | "version": "1.1.1", 270 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 271 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 272 | "requires": { 273 | "function-bind": "^1.1.1", 274 | "has": "^1.0.3", 275 | "has-symbols": "^1.0.1" 276 | } 277 | }, 278 | "has": { 279 | "version": "1.0.3", 280 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 281 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 282 | "requires": { 283 | "function-bind": "^1.1.1" 284 | } 285 | }, 286 | "has-symbols": { 287 | "version": "1.0.3", 288 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 289 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 290 | }, 291 | "http-errors": { 292 | "version": "2.0.0", 293 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 294 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 295 | "requires": { 296 | "depd": "2.0.0", 297 | "inherits": "2.0.4", 298 | "setprototypeof": "1.2.0", 299 | "statuses": "2.0.1", 300 | "toidentifier": "1.0.1" 301 | } 302 | }, 303 | "iconv-lite": { 304 | "version": "0.4.24", 305 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 306 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 307 | "requires": { 308 | "safer-buffer": ">= 2.1.2 < 3" 309 | } 310 | }, 311 | "inherits": { 312 | "version": "2.0.4", 313 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 314 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 315 | }, 316 | "ipaddr.js": { 317 | "version": "1.9.1", 318 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 319 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 320 | }, 321 | "media-typer": { 322 | "version": "0.3.0", 323 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 324 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 325 | }, 326 | "merge-descriptors": { 327 | "version": "1.0.1", 328 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 329 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 330 | }, 331 | "methods": { 332 | "version": "1.1.2", 333 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 334 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 335 | }, 336 | "mime": { 337 | "version": "1.6.0", 338 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 339 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 340 | }, 341 | "mime-db": { 342 | "version": "1.52.0", 343 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 344 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 345 | }, 346 | "mime-types": { 347 | "version": "2.1.35", 348 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 349 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 350 | "requires": { 351 | "mime-db": "1.52.0" 352 | } 353 | }, 354 | "ms": { 355 | "version": "2.0.0", 356 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 357 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 358 | }, 359 | "negotiator": { 360 | "version": "0.6.3", 361 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 362 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 363 | }, 364 | "nodemailer": { 365 | "version": "6.7.5", 366 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.5.tgz", 367 | "integrity": "sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==" 368 | }, 369 | "object-assign": { 370 | "version": "4.1.1", 371 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 372 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 373 | }, 374 | "object-inspect": { 375 | "version": "1.12.0", 376 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", 377 | "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" 378 | }, 379 | "on-finished": { 380 | "version": "2.4.1", 381 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 382 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 383 | "requires": { 384 | "ee-first": "1.1.1" 385 | } 386 | }, 387 | "parseurl": { 388 | "version": "1.3.3", 389 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 390 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 391 | }, 392 | "path-to-regexp": { 393 | "version": "0.1.7", 394 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 395 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 396 | }, 397 | "proxy-addr": { 398 | "version": "2.0.7", 399 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 400 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 401 | "requires": { 402 | "forwarded": "0.2.0", 403 | "ipaddr.js": "1.9.1" 404 | } 405 | }, 406 | "qs": { 407 | "version": "6.10.3", 408 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 409 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 410 | "requires": { 411 | "side-channel": "^1.0.4" 412 | } 413 | }, 414 | "range-parser": { 415 | "version": "1.2.1", 416 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 417 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 418 | }, 419 | "raw-body": { 420 | "version": "2.5.1", 421 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 422 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 423 | "requires": { 424 | "bytes": "3.1.2", 425 | "http-errors": "2.0.0", 426 | "iconv-lite": "0.4.24", 427 | "unpipe": "1.0.0" 428 | } 429 | }, 430 | "safe-buffer": { 431 | "version": "5.2.1", 432 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 433 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 434 | }, 435 | "safer-buffer": { 436 | "version": "2.1.2", 437 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 438 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 439 | }, 440 | "send": { 441 | "version": "0.18.0", 442 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 443 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 444 | "requires": { 445 | "debug": "2.6.9", 446 | "depd": "2.0.0", 447 | "destroy": "1.2.0", 448 | "encodeurl": "~1.0.2", 449 | "escape-html": "~1.0.3", 450 | "etag": "~1.8.1", 451 | "fresh": "0.5.2", 452 | "http-errors": "2.0.0", 453 | "mime": "1.6.0", 454 | "ms": "2.1.3", 455 | "on-finished": "2.4.1", 456 | "range-parser": "~1.2.1", 457 | "statuses": "2.0.1" 458 | }, 459 | "dependencies": { 460 | "ms": { 461 | "version": "2.1.3", 462 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 463 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 464 | } 465 | } 466 | }, 467 | "serve-static": { 468 | "version": "1.15.0", 469 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 470 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 471 | "requires": { 472 | "encodeurl": "~1.0.2", 473 | "escape-html": "~1.0.3", 474 | "parseurl": "~1.3.3", 475 | "send": "0.18.0" 476 | } 477 | }, 478 | "setprototypeof": { 479 | "version": "1.2.0", 480 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 481 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 482 | }, 483 | "side-channel": { 484 | "version": "1.0.4", 485 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 486 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 487 | "requires": { 488 | "call-bind": "^1.0.0", 489 | "get-intrinsic": "^1.0.2", 490 | "object-inspect": "^1.9.0" 491 | } 492 | }, 493 | "socket.io": { 494 | "version": "4.5.0", 495 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.0.tgz", 496 | "integrity": "sha512-slTYqU2jCgMjXwresG8grhUi/cC6GjzmcfqArzaH3BN/9I/42eZk9yamNvZJdBfTubkjEdKAKs12NEztId+bUA==", 497 | "requires": { 498 | "accepts": "~1.3.4", 499 | "base64id": "~2.0.0", 500 | "debug": "~4.3.2", 501 | "engine.io": "~6.2.0", 502 | "socket.io-adapter": "~2.4.0", 503 | "socket.io-parser": "~4.0.4" 504 | }, 505 | "dependencies": { 506 | "debug": { 507 | "version": "4.3.4", 508 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 509 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 510 | "requires": { 511 | "ms": "2.1.2" 512 | } 513 | }, 514 | "ms": { 515 | "version": "2.1.2", 516 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 517 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 518 | } 519 | } 520 | }, 521 | "socket.io-adapter": { 522 | "version": "2.4.0", 523 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", 524 | "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" 525 | }, 526 | "socket.io-parser": { 527 | "version": "4.0.4", 528 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", 529 | "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", 530 | "requires": { 531 | "@types/component-emitter": "^1.2.10", 532 | "component-emitter": "~1.3.0", 533 | "debug": "~4.3.1" 534 | }, 535 | "dependencies": { 536 | "debug": { 537 | "version": "4.3.4", 538 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 539 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 540 | "requires": { 541 | "ms": "2.1.2" 542 | } 543 | }, 544 | "ms": { 545 | "version": "2.1.2", 546 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 547 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 548 | } 549 | } 550 | }, 551 | "statuses": { 552 | "version": "2.0.1", 553 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 554 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 555 | }, 556 | "toidentifier": { 557 | "version": "1.0.1", 558 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 559 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 560 | }, 561 | "type-is": { 562 | "version": "1.6.18", 563 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 564 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 565 | "requires": { 566 | "media-typer": "0.3.0", 567 | "mime-types": "~2.1.24" 568 | } 569 | }, 570 | "unpipe": { 571 | "version": "1.0.0", 572 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 573 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 574 | }, 575 | "utils-merge": { 576 | "version": "1.0.1", 577 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 578 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 579 | }, 580 | "vary": { 581 | "version": "1.1.2", 582 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 583 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 584 | }, 585 | "ws": { 586 | "version": "8.2.3", 587 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", 588 | "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==" 589 | } 590 | } 591 | } 592 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "greenlab-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "author": "zakaria elalaoui", 11 | "license": "MIT", 12 | "dependencies": { 13 | "dotenv": "^16.0.1", 14 | "express": "^4.18.1", 15 | "nodemailer": "^6.7.5", 16 | "socket.io": "^4.5.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /public/assets/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/clear.png -------------------------------------------------------------------------------- /public/assets/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/dark.png -------------------------------------------------------------------------------- /public/assets/day-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/day-view.png -------------------------------------------------------------------------------- /public/assets/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/delete.png -------------------------------------------------------------------------------- /public/assets/download--v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/download--v1.png -------------------------------------------------------------------------------- /public/assets/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/download.png -------------------------------------------------------------------------------- /public/assets/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/email.png -------------------------------------------------------------------------------- /public/assets/engineering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/engineering.png -------------------------------------------------------------------------------- /public/assets/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/help.png -------------------------------------------------------------------------------- /public/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/icon.png -------------------------------------------------------------------------------- /public/assets/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/light.png -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/logo.png -------------------------------------------------------------------------------- /public/assets/logoBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/logoBlack.png -------------------------------------------------------------------------------- /public/assets/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/menu.png -------------------------------------------------------------------------------- /public/assets/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/refresh.png -------------------------------------------------------------------------------- /public/assets/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/settings.png -------------------------------------------------------------------------------- /public/assets/xls-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/xls-import.png -------------------------------------------------------------------------------- /public/assets/ziko.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/ziko.PNG -------------------------------------------------------------------------------- /public/assets/ziko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/public/assets/ziko.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | GreenLab 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | Ziko.UI.ExtractAll() 2 | Ziko.Math.ExtractAll() 3 | Ziko.THREE.ExtractAll() 4 | import {header, logo, modeImage} from "./js/header.js"; 5 | import {form} from "./js/form.js"; 6 | import {scene,resetCamera,saveTransform} from "./js/scene.js"; 7 | import {Galerie,GridPreview} from "./js/preview.js" 8 | import {theme,setTheme,getTheme} from "./js/theme.js" 9 | import { footer } from "./js/footer.js"; 10 | header.render() 11 | form.render() 12 | scene.render() 13 | Galerie.render().hide() 14 | GridPreview.render().hide() 15 | 16 | var App=Flex( 17 | header, 18 | form, 19 | scene, 20 | Galerie, 21 | GridPreview, 22 | footer, 23 | ).size("100%","auto").border("none").vertical(0,0) 24 | App.style(theme[getTheme()]) 25 | modeImage.updateSrc(theme[getTheme()].src) 26 | logo.updateSrc(theme[getTheme()].logosrc) 27 | modeImage.click(()=>{ 28 | if(getTheme()==="dark")setTheme("light"); 29 | else setTheme("dark") 30 | modeImage.updateSrc(theme[getTheme()].src); 31 | logo.updateSrc(theme[getTheme()].logosrc); 32 | App.style(theme[getTheme()]) 33 | }) 34 | function resize(){ 35 | if(window.innerWidth<700){ 36 | scene.hide(); 37 | footer.margin("30px auto") 38 | } 39 | else{ 40 | scene.show(); 41 | footer.margin("10px auto") 42 | } 43 | } 44 | App.resizeObserver(resize) 45 | resize() 46 | -------------------------------------------------------------------------------- /public/js/db.js: -------------------------------------------------------------------------------- 1 | export var styles=new Array(5).fill(null).map(n=>Object.assign({}, 2 | { 3 | color:"black", 4 | fontSize:"14", 5 | fontFamily:"Arial" 6 | } 7 | )) 8 | export var position=[ 9 | [0,150,0], 10 | [0,100,0], 11 | [0,-50,0], 12 | [100,-180,0], 13 | [0,0,0], 14 | [0,200,0], 15 | [0,-120,0], 16 | ] 17 | if(!localStorage.getItem("styles"))localStorage.setItem("styles",JSON.stringify(styles)); 18 | else styles=JSON.parse(localStorage.getItem("styles")) 19 | if(!localStorage.getItem("position"))localStorage.setItem("position",JSON.stringify(position)); 20 | else position=JSON.parse(localStorage.getItem("position")) 21 | export var setStyle=(i,newConfig)=>{ 22 | styles[i]=Object.assign(getStyle(i),newConfig) 23 | localStorage.setItem("styles",JSON.stringify(styles)) 24 | } 25 | export var getStyle=(i)=>{ 26 | return JSON.parse(localStorage.getItem("styles"))[i] 27 | } 28 | export var setPosition=(i,newPosition)=>{ 29 | position[i]=newPosition; 30 | localStorage.setItem("position",JSON.stringify(position)) 31 | } 32 | export var getPosition=(i)=>{ 33 | return JSON.parse(localStorage.getItem("position"))[i] 34 | } 35 | export var resetAll=()=>{ 36 | localStorage.removeItem("position"); 37 | localStorage.removeItem("styles"); 38 | location.reload() 39 | } -------------------------------------------------------------------------------- /public/js/footer.js: -------------------------------------------------------------------------------- 1 | Ziko.UI.ExtractAll() 2 | export var footer=Footer( 3 | Flex( 4 | h2("Powered by "), 5 | image("./assets/ziko.png","150px","auto").link("https://github.com/zakarialaoui10/ziko.js","_blank") 6 | ).size("100%","auto").horizontal("space-around",0).border("none") 7 | ).size("70%","auto").margin("10px auto") -------------------------------------------------------------------------------- /public/js/form.js: -------------------------------------------------------------------------------- 1 | import {getStyle,setStyle,styles} from "./db.js" 2 | var dataForm = { 3 | "Type de Certificat": "", 4 | "Titre de formation": "", 5 | "Description de formation": "", 6 | "Date": "", 7 | "Nom et Prénoms":null, 8 | "Image d'arriére plan": null 9 | } 10 | var labels = Object.keys(dataForm).map(n => Flex(text(n).margin("3px 0")).border("none").margin("10px 0").size("300px","auto").vertical(0,0)) 11 | var inputs = []; 12 | inputs[0] = input().size("200px","15px") 13 | inputs[0].oninput(() => {Object.assign(dataForm, { 14 | "Type de Certificat": inputs[0].value 15 | }) 16 | txt[0].setValue(inputs[0].value) 17 | }) 18 | inputs[1] = input().size("200px","15px") 19 | inputs[1].oninput(() => {Object.assign(dataForm, { 20 | "Titre de formation": inputs[1].value 21 | }) 22 | txt[1].setValue(inputs[1].value) 23 | }) 24 | inputs[2] = input().size("200px","15px") 25 | inputs[2].oninput(() => { Object.assign(dataForm, { 26 | "Description de formation": inputs[2].value 27 | }) 28 | txt[2].setValue(inputs[2].value) 29 | }) 30 | inputs[3] = inputDate().size("200px","15px") 31 | inputs[3].oninput(() => {Object.assign(dataForm, { 32 | "Date": inputs[3].value 33 | }) 34 | txt[3].setValue(inputs[3].value) 35 | }) 36 | inputs[4] = input().setType("file").accept(".xls,.xlsx").size("200px","20px").hide(); 37 | var importExcel=Grid(text("import excel file").center,image("./assets/xls-import.png","50px","50px").border("none")).columns(2).size("200px","50px").margin("auto 0").cursor("pointer") 38 | importExcel.click(()=>inputs[4].element.click()).border("none") 39 | var ExcelHandler=inputs[4] 40 | inputs[5] = input().setType("file").cursor("pointer").size("200px","20px") 41 | var backgroundInput=inputs[5]; 42 | inputs.map(n=>n.padding("5px")) 43 | var stylesConfig=new Array(5).fill(null).map((n,i)=> 44 | Grid( 45 | text("color"), 46 | text("font size"), 47 | text("font family"), 48 | inputColor().width("40px").setValue(getStyle(i).color), 49 | inputNumber().width("80px").setValue(getStyle(i).fontSize), 50 | select("Arial","Verdana","Helvetica","Tahoma","Trebuchet MS","Times New Roman","Georgia","Garamond","Courier New","Brush Swcript MT").setSelectedValue(getStyle(i).fontFamily).width("80px") 51 | 52 | ).columns(3).size("250px","auto").spaceAround(1,1).padding("5px").fontSize("1rem").border("none")) 53 | stylesConfig.map((n,i)=>{ 54 | n.items[3].oninput(()=>{ 55 | setStyle(i,{color:n.items[3].value}); 56 | txt[i].color(n.items[3].value) 57 | }); 58 | n.items[4].oninput(()=>{ 59 | setStyle(i,{fontSize:n.items[4].value+""}) 60 | txt[i].style({fontSize:n.items[4].value+"px"}) 61 | }); 62 | n.items[5].onchange(()=>{ 63 | setStyle(i,{fontFamily:n.items[5].value}); 64 | txt[i].style({fontFamily:n.items[5].value}); 65 | }); 66 | }) 67 | labels.map((n,i)=>n.append(inputs[i])) 68 | labels.slice(0,5).map((n,i)=>n.append(stylesConfig[i])) 69 | var form=Flex(...labels).resp("300px").style({ 70 | width:"95vw", 71 | height:"auto", 72 | margin:"5px auto", 73 | fontFamily:"verdana", 74 | justifyContent:"space-around", 75 | alignItems:"stretch", 76 | border:"none" 77 | }) 78 | form.append(importExcel) 79 | var txt=["Type de Certificat","Titre de formation","Description de formation","Date","Nom Et Prénom"].map((n,i)=>text(n).style({maxWidth:"200px",fontFamily:"arial"}).style({ 80 | color:getStyle(i).color, 81 | fontSize:getStyle(i).fontSize+"px", 82 | fontFamily:getStyle(i), 83 | })) 84 | var nameText=txt[4] 85 | export{form,txt,backgroundInput,ExcelHandler,nameText} -------------------------------------------------------------------------------- /public/js/header.js: -------------------------------------------------------------------------------- 1 | import { resetAll } from "./db.js"; 2 | 3 | Ziko.UI.ExtractAll() 4 | Ziko.Math.ExtractAll() 5 | Ziko.THREE.ExtractAll() 6 | var modeImage=image("./assets/dark.png","30px","auto").border("none").cursor("pointer"); 7 | var logo=image("./assets/logo.png","auto","100%").border("none") 8 | var refresh=image("./assets/refresh.png","30px","auto").border("none").cursor("pointer").click(()=>location.reload()) 9 | var refreshSetting=image("./assets/clear.png","30px","auto").border("none").cursor("pointer").click(()=>resetAll()) 10 | var help=image("./assets/help.png","30px","auto").border("none").link("https://github.com/zakarialaoui10/Greenlab-Challenge","_blank") 11 | var header=Header( 12 | Flex( 13 | logo, 14 | refresh, 15 | refreshSetting, 16 | help, 17 | modeImage 18 | ).size("100%","100%").horizontal("space-around",0).border("none") 19 | ).style({ 20 | width:"90vw", 21 | height:"50px", 22 | margin:"10px auto", 23 | padding:"10px auto", 24 | top:0, 25 | zIndex:3 26 | }) 27 | if(window.innerWidth<700){ 28 | help.hide() 29 | refresh.hide() 30 | refreshSetting.hide() 31 | } 32 | export{header,modeImage,logo} -------------------------------------------------------------------------------- /public/js/preview.js: -------------------------------------------------------------------------------- 1 | export var Galerie=Carousel().style({ 2 | width:"90%", 3 | height:"500px", 4 | margin:"5px auto", 5 | background:"#eee" 6 | }).cursor("move").style({padding:"10px",width:"90%"}) 7 | export var GridPreview=Grid().columns(4).spaceAround(1,0).style({ 8 | width:"80%", 9 | height:"auto", 10 | margin:"5px auto", 11 | padding:"10px", 12 | fontSize:"1.5rem", 13 | fontFamily:"Cursive" 14 | }) 15 | -------------------------------------------------------------------------------- /public/js/scene.js: -------------------------------------------------------------------------------- 1 | Ziko.UI.ExtractAll() 2 | Ziko.Math.ExtractAll() 3 | Ziko.THREE.ExtractAll() 4 | var socket = io(); 5 | import {backgroundInput, ExcelHandler, nameText, txt} from "./form.js" 6 | import {setStyle,getStyle,setPosition,getPosition} from "./db.js" 7 | import {Galerie,GridPreview} from "./preview.js" 8 | txt[5]=image("./assets/icon.png","50px").border("none"); 9 | txt[6]=image("./assets/logoBlack.png","100px").border("none") 10 | var scene=Css3D("1170px","827px").style({minHeight:"70vh",zIndex:2}).margin("10px auto").orbitOn().background("#ddd").border("1px solid black").style({ 11 | boxShadow:"1px 1px 5px white,-1px -1px 10px black" 12 | }) 13 | scene.camera.translateZ(200); 14 | var backgroundImage=image(""); 15 | scene.addCss(backgroundImage).position(0,0,-2); 16 | var t=new Array(5).fill(null); 17 | t=t.map((n,i)=>scene.addCss(txt[i].style(setStyle(i))).position(...getPosition(i))) 18 | t[5]=scene.addCss(txt[5]).position(...getPosition(5)) 19 | t[6]=scene.addCss(txt[6]).position(...getPosition(6)) 20 | 21 | txt.map((n,i)=>n.mousedown(()=>{ 22 | scene.setTransform(t[i]); 23 | })) 24 | txt.map((n,i)=>n.mouseup(()=>{ 25 | saveTransform() 26 | })) 27 | scene.addGl(gridH(1000,100).rotX(PI/2)) 28 | var saveTransform=()=>t.map((n,i)=>setPosition(i,Object.values(n.mesh.position))) 29 | var resetCamera=()=>{ 30 | saveTransform() 31 | scene.camera.rotation.x=-6.123233995736766e-17; 32 | scene.camera.rotation.y=0; 33 | scene.camera.rotation.z=0; 34 | scene.camera.position.x=0; 35 | scene.camera.position.y=1.8369701987210297e-14; 36 | scene.camera.position.z=300 37 | scene.renderGl().renderCss() 38 | } 39 | async function handleImageAsync(e) { 40 | const reader = new FileReader(); 41 | const img = new Image(); 42 | reader.onload = function (event) { 43 | img.src = event.target.result; 44 | backgroundImage.updateSrc(img.src) 45 | }; 46 | reader.readAsDataURL(e.target.files[0]); 47 | this.img = img; 48 | } 49 | 50 | var images=[],pdfs=[] 51 | var toImage=(i=0)=>{ 52 | resetCamera() 53 | var node = scene.element.children[1]; 54 | Galerie.show() 55 | htmlToImage.toPng(node) 56 | .then(function (dataUrl) { 57 | var im=new Image() 58 | im.src=dataUrl; 59 | images[i]=image(im.src).render(true).background("white"); 60 | Galerie.addTrack(images[i].size("500px","auto")); 61 | var doc = new jspdf.jsPDF('l', 'mm', 'a4'); 62 | doc.addImage(im.src, 'JPEG', 0, 0); 63 | pdfs[i]=doc; 64 | url[i] = "data:application/pdf;base64," + btoa(pdfs[i].output()); 65 | //console.log(url[i]) 66 | const pdfo = new File([doc.output("blob")],names[i]+".pdf", { 67 | type: "application/pdf", 68 | }); 69 | }) 70 | .catch(function (error) { 71 | console.error('oops, something went wrong!', error); 72 | }); 73 | } 74 | Galerie.pointermove(()=>{ 75 | images.map(n=>{ 76 | n.intersectRatio(e=>n.scale(e).opacity(e)) 77 | }) 78 | }) 79 | var names=[],id=[],save=[],emails=[],url=[] 80 | async function handleFileAsync(e) { 81 | const file = e.target.files[0]; 82 | const data = await file.arrayBuffer(); 83 | GridPreview.show() 84 | const workbook = XLSX.read(data); 85 | workbook.SheetNames.forEach(sheet => { 86 | let rowObject = XLSX.utils.sheet_to_row_object_array(workbook.Sheets[sheet]); 87 | var arr = rowObject.map(n => Object.values(n)); 88 | id=arange(1,arr.length+1,1); 89 | save=new Array(arr.length).fill("download"); 90 | arr.map((n,i)=>{ 91 | names[i]=arr[i][1]; 92 | emails[i]=arr[i][3] 93 | }); 94 | var GridData=matrix([id,names,save,emails]).T.arr; 95 | var Emails=emails 96 | for(let i=0;ipdfs[i].save(names[i])) 100 | GridData[i][3]=image("./assets/email.png","50px","50px").cursor("pointer").border("none").click(()=>{ 101 | var data=Object.assign({},{ 102 | data:url[i], 103 | email:Emails[i], 104 | name:names[i] 105 | }) 106 | socket.emit('email',JSON.stringify(data)); 107 | }) 108 | } 109 | GridPreview.append(...GridData.flat(1)) 110 | }) 111 | names.map((n,i)=>{ 112 | setTimeout(() => { 113 | nameText.setValue(names[i]); 114 | toImage(i) 115 | }, 100); 116 | }); 117 | } 118 | 119 | ExcelHandler.onchange((e)=>handleFileAsync(e).then(()=>{ 120 | saveTransform() 121 | id=[]; 122 | save=[]; 123 | pdfs=[]; 124 | images=[]; 125 | emails=[]; 126 | })); 127 | backgroundInput.onchange(handleImageAsync) 128 | socket.on('succed',(data)=>alert(data)) 129 | socket.on('fail',(data)=>alert(data)) 130 | export{scene,backgroundImage,resetCamera,saveTransform,toImage} 131 | -------------------------------------------------------------------------------- /public/js/theme.js: -------------------------------------------------------------------------------- 1 | var colors = ["#e7f5fe", "#88bdff", "#4680c2", "#1778f2", "#1778f2", "#0d66c2"]; 2 | var theme={ 3 | dark:{ 4 | background:"#4680c2", 5 | color:"#eee", 6 | src:"./assets/light.png", 7 | logosrc:"./assets/logoBlack.png" 8 | }, 9 | light:{ 10 | background:"#e7f5fe", 11 | color:"#333", 12 | src:"./assets/dark.png", 13 | logosrc:"./assets/logo.png" 14 | } 15 | } 16 | if(!localStorage.getItem("theme"))localStorage.setItem("theme","dark"); 17 | var setTheme=(mode)=>localStorage.setItem("theme",mode); 18 | var getTheme=()=>localStorage.getItem("theme"); 19 | export{setTheme,theme,getTheme} -------------------------------------------------------------------------------- /public/lib/0: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/lib/CSS3DRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs 3 | * @author mrdoob / http://mrdoob.com/ 4 | */ 5 | 6 | THREE.CSS3DObject = function ( element ) { 7 | 8 | THREE.Object3D.call( this ); 9 | 10 | this.element = element; 11 | this.element.style.position = "absolute"; 12 | this.element.style.WebkitTransformStyle = 'preserve-3d'; 13 | this.element.style.MozTransformStyle = 'preserve-3d'; 14 | this.element.style.oTransformStyle = 'preserve-3d'; 15 | this.element.style.transformStyle = 'preserve-3d'; 16 | 17 | }; 18 | 19 | THREE.CSS3DObject.prototype = Object.create( THREE.Object3D.prototype ); 20 | 21 | THREE.CSS3DSprite = function ( element ) { 22 | 23 | THREE.CSS3DObject.call( this, element ); 24 | 25 | }; 26 | 27 | THREE.CSS3DSprite.prototype = Object.create( THREE.CSS3DObject.prototype ); 28 | 29 | // 30 | 31 | THREE.CSS3DRenderer = function () { 32 | 33 | console.log( 'THREE.CSS3DRenderer', THREE.REVISION ); 34 | 35 | var _width, _height; 36 | var _widthHalf, _heightHalf; 37 | 38 | var matrix = new THREE.Matrix4(); 39 | 40 | var domElement = document.createElement( 'div' ); 41 | domElement.style.overflow = 'hidden'; 42 | 43 | domElement.style.WebkitTransformStyle = 'preserve-3d'; 44 | domElement.style.WebkitPerspectiveOrigin = '50% 50%'; 45 | 46 | domElement.style.MozTransformStyle = 'preserve-3d'; 47 | domElement.style.MozPerspectiveOrigin = '50% 50%'; 48 | 49 | domElement.style.oTransformStyle = 'preserve-3d'; 50 | domElement.style.oPerspectiveOrigin = '50% 50%'; 51 | 52 | domElement.style.transformStyle = 'preserve-3d'; 53 | domElement.style.perspectiveOrigin = '50% 50%'; 54 | 55 | this.domElement = domElement; 56 | 57 | var cameraElement = document.createElement( 'div' ); 58 | 59 | cameraElement.style.WebkitTransformStyle = 'preserve-3d'; 60 | cameraElement.style.MozTransformStyle = 'preserve-3d'; 61 | cameraElement.style.oTransformStyle = 'preserve-3d'; 62 | cameraElement.style.transformStyle = 'preserve-3d'; 63 | 64 | domElement.appendChild( cameraElement ); 65 | 66 | this.cameraElement = cameraElement; 67 | 68 | this.setSize = function ( width, height ) { 69 | 70 | _width = width; 71 | _height = height; 72 | 73 | _widthHalf = _width / 2; 74 | _heightHalf = _height / 2; 75 | 76 | domElement.style.width = width + 'px'; 77 | domElement.style.height = height + 'px'; 78 | 79 | cameraElement.style.width = width + 'px'; 80 | cameraElement.style.height = height + 'px'; 81 | 82 | }; 83 | 84 | var epsilon = function ( value ) { 85 | 86 | return Math.abs( value ) < 0.000001 ? 0 : value; 87 | 88 | }; 89 | 90 | var getCameraCSSMatrix = function ( matrix ) { 91 | 92 | var elements = matrix.elements; 93 | 94 | return 'matrix3d(' + 95 | epsilon( elements[ 0 ] ) + ',' + 96 | epsilon( - elements[ 1 ] ) + ',' + 97 | epsilon( elements[ 2 ] ) + ',' + 98 | epsilon( elements[ 3 ] ) + ',' + 99 | epsilon( elements[ 4 ] ) + ',' + 100 | epsilon( - elements[ 5 ] ) + ',' + 101 | epsilon( elements[ 6 ] ) + ',' + 102 | epsilon( elements[ 7 ] ) + ',' + 103 | epsilon( elements[ 8 ] ) + ',' + 104 | epsilon( - elements[ 9 ] ) + ',' + 105 | epsilon( elements[ 10 ] ) + ',' + 106 | epsilon( elements[ 11 ] ) + ',' + 107 | epsilon( elements[ 12 ] ) + ',' + 108 | epsilon( - elements[ 13 ] ) + ',' + 109 | epsilon( elements[ 14 ] ) + ',' + 110 | epsilon( elements[ 15 ] ) + 111 | ')'; 112 | 113 | }; 114 | 115 | var getObjectCSSMatrix = function ( matrix ) { 116 | 117 | var elements = matrix.elements; 118 | 119 | return 'translate3d(-50%,-50%,0) matrix3d(' + 120 | epsilon( elements[ 0 ] ) + ',' + 121 | epsilon( elements[ 1 ] ) + ',' + 122 | epsilon( elements[ 2 ] ) + ',' + 123 | epsilon( elements[ 3 ] ) + ',' + 124 | epsilon( - elements[ 4 ] ) + ',' + 125 | epsilon( - elements[ 5 ] ) + ',' + 126 | epsilon( - elements[ 6 ] ) + ',' + 127 | epsilon( - elements[ 7 ] ) + ',' + 128 | epsilon( elements[ 8 ] ) + ',' + 129 | epsilon( elements[ 9 ] ) + ',' + 130 | epsilon( elements[ 10 ] ) + ',' + 131 | epsilon( elements[ 11 ] ) + ',' + 132 | epsilon( elements[ 12 ] ) + ',' + 133 | epsilon( elements[ 13 ] ) + ',' + 134 | epsilon( elements[ 14 ] ) + ',' + 135 | epsilon( elements[ 15 ] ) + 136 | ')'; 137 | 138 | }; 139 | 140 | var renderObject = function ( object, camera ) { 141 | 142 | if ( object instanceof THREE.CSS3DObject ) { 143 | 144 | var style; 145 | 146 | if ( object instanceof THREE.CSS3DSprite ) { 147 | 148 | // http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/ 149 | 150 | matrix.copy( camera.matrixWorldInverse ); 151 | matrix.transpose(); 152 | matrix.copyPosition( object.matrixWorld ); 153 | matrix.scale( object.scale ); 154 | 155 | matrix.elements[ 3 ] = 0; 156 | matrix.elements[ 7 ] = 0; 157 | matrix.elements[ 11 ] = 0; 158 | matrix.elements[ 15 ] = 1; 159 | 160 | style = getObjectCSSMatrix( matrix ); 161 | 162 | } else { 163 | 164 | style = getObjectCSSMatrix( object.matrixWorld ); 165 | 166 | } 167 | 168 | var element = object.element; 169 | 170 | element.style.WebkitTransform = style; 171 | element.style.MozTransform = style; 172 | element.style.oTransform = style; 173 | element.style.transform = style; 174 | 175 | if ( element.parentNode !== cameraElement ) { 176 | 177 | cameraElement.appendChild( element ); 178 | 179 | } 180 | 181 | } 182 | 183 | for ( var i = 0, l = object.children.length; i < l; i ++ ) { 184 | 185 | renderObject( object.children[ i ], camera ); 186 | 187 | } 188 | 189 | }; 190 | 191 | this.render = function ( scene, camera ) { 192 | 193 | var fov = 0.5 / Math.tan( THREE.Math.degToRad( camera.fov * 0.5 ) ) * _height; 194 | 195 | domElement.style.WebkitPerspective = fov + "px"; 196 | domElement.style.MozPerspective = fov + "px"; 197 | domElement.style.oPerspective = fov + "px"; 198 | domElement.style.perspective = fov + "px"; 199 | 200 | scene.updateMatrixWorld(); 201 | 202 | if ( camera.parent === undefined ) camera.updateMatrixWorld(); 203 | 204 | camera.matrixWorldInverse.getInverse( camera.matrixWorld ); 205 | 206 | var style = "translate3d(0,0," + fov + "px)" + getCameraCSSMatrix( camera.matrixWorldInverse ) + 207 | " translate3d(" + _widthHalf + "px," + _heightHalf + "px, 0)"; 208 | 209 | cameraElement.style.WebkitTransform = style; 210 | cameraElement.style.MozTransform = style; 211 | cameraElement.style.oTransform = style; 212 | cameraElement.style.transform = style; 213 | 214 | renderObject( scene, camera ); 215 | 216 | }; 217 | 218 | }; 219 | -------------------------------------------------------------------------------- /public/lib/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 | * @author ScieCode / http://github.com/sciecode 8 | */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. 11 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 12 | // 13 | // Orbit - left mouse / touch: one-finger move 14 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 15 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 16 | 17 | THREE.OrbitControls = function ( object, domElement ) { 18 | 19 | this.object = object; 20 | 21 | this.domElement = ( domElement !== undefined ) ? domElement : document; 22 | 23 | // Set to false to disable this control 24 | this.enabled = true; 25 | 26 | // "target" sets the location of focus, where the object orbits around 27 | this.target = new THREE.Vector3(); 28 | 29 | // How far you can dolly in and out ( PerspectiveCamera only ) 30 | this.minDistance = 0; 31 | this.maxDistance = Infinity; 32 | 33 | // How far you can zoom in and out ( OrthographicCamera only ) 34 | this.minZoom = 0; 35 | this.maxZoom = Infinity; 36 | 37 | // How far you can orbit vertically, upper and lower limits. 38 | // Range is 0 to Math.PI radians. 39 | this.minPolarAngle = 0; // radians 40 | this.maxPolarAngle = Math.PI; // radians 41 | 42 | // How far you can orbit horizontally, upper and lower limits. 43 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 44 | this.minAzimuthAngle = - Infinity; // radians 45 | this.maxAzimuthAngle = Infinity; // radians 46 | 47 | // Set to true to enable damping (inertia) 48 | // If damping is enabled, you must call controls.update() in your animation loop 49 | this.enableDamping = false; 50 | this.dampingFactor = 0.05; 51 | 52 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 53 | // Set to false to disable zooming 54 | this.enableZoom = true; 55 | this.zoomSpeed = 1.0; 56 | 57 | // Set to false to disable rotating 58 | this.enableRotate = true; 59 | this.rotateSpeed = 1.0; 60 | 61 | // Set to false to disable panning 62 | this.enablePan = true; 63 | this.panSpeed = 1.0; 64 | this.screenSpacePanning = false; // if true, pan in screen-space 65 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 66 | 67 | // Set to true to automatically rotate around the target 68 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 69 | this.autoRotate = false; 70 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 71 | 72 | // Set to false to disable use of the keys 73 | this.enableKeys = true; 74 | 75 | // The four arrow keys 76 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 77 | 78 | // Mouse buttons 79 | this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; 80 | 81 | // Touch fingers 82 | this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; 83 | 84 | // for reset 85 | this.target0 = this.target.clone(); 86 | this.position0 = this.object.position.clone(); 87 | this.zoom0 = this.object.zoom; 88 | 89 | // 90 | // public methods 91 | // 92 | 93 | this.getPolarAngle = function () { 94 | 95 | return spherical.phi; 96 | 97 | }; 98 | 99 | this.getAzimuthalAngle = function () { 100 | 101 | return spherical.theta; 102 | 103 | }; 104 | 105 | this.saveState = function () { 106 | 107 | scope.target0.copy( scope.target ); 108 | scope.position0.copy( scope.object.position ); 109 | scope.zoom0 = scope.object.zoom; 110 | 111 | }; 112 | 113 | this.reset = function () { 114 | 115 | scope.target.copy( scope.target0 ); 116 | scope.object.position.copy( scope.position0 ); 117 | scope.object.zoom = scope.zoom0; 118 | 119 | scope.object.updateProjectionMatrix(); 120 | scope.dispatchEvent( changeEvent ); 121 | 122 | scope.update(); 123 | 124 | state = STATE.NONE; 125 | 126 | }; 127 | 128 | // this method is exposed, but perhaps it would be better if we can make it private... 129 | this.update = function () { 130 | 131 | var offset = new THREE.Vector3(); 132 | 133 | // so camera.up is the orbit axis 134 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 135 | var quatInverse = quat.clone().inverse(); 136 | 137 | var lastPosition = new THREE.Vector3(); 138 | var lastQuaternion = new THREE.Quaternion(); 139 | 140 | return function update() { 141 | 142 | var position = scope.object.position; 143 | 144 | offset.copy( position ).sub( scope.target ); 145 | 146 | // rotate offset to "y-axis-is-up" space 147 | offset.applyQuaternion( quat ); 148 | 149 | // angle from z-axis around y-axis 150 | spherical.setFromVector3( offset ); 151 | 152 | if ( scope.autoRotate && state === STATE.NONE ) { 153 | 154 | rotateLeft( getAutoRotationAngle() ); 155 | 156 | } 157 | 158 | if ( scope.enableDamping ) { 159 | 160 | spherical.theta += sphericalDelta.theta * scope.dampingFactor; 161 | spherical.phi += sphericalDelta.phi * scope.dampingFactor; 162 | 163 | } else { 164 | 165 | spherical.theta += sphericalDelta.theta; 166 | spherical.phi += sphericalDelta.phi; 167 | 168 | } 169 | 170 | // restrict theta to be between desired limits 171 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 172 | 173 | // restrict phi to be between desired limits 174 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 175 | 176 | spherical.makeSafe(); 177 | 178 | 179 | spherical.radius *= scale; 180 | 181 | // restrict radius to be between desired limits 182 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 183 | 184 | // move target to panned location 185 | 186 | if ( scope.enableDamping === true ) { 187 | 188 | scope.target.addScaledVector( panOffset, scope.dampingFactor ); 189 | 190 | } else { 191 | 192 | scope.target.add( panOffset ); 193 | 194 | } 195 | 196 | offset.setFromSpherical( spherical ); 197 | 198 | // rotate offset back to "camera-up-vector-is-up" space 199 | offset.applyQuaternion( quatInverse ); 200 | 201 | position.copy( scope.target ).add( offset ); 202 | 203 | scope.object.lookAt( scope.target ); 204 | 205 | if ( scope.enableDamping === true ) { 206 | 207 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 208 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 209 | 210 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 211 | 212 | } else { 213 | 214 | sphericalDelta.set( 0, 0, 0 ); 215 | 216 | panOffset.set( 0, 0, 0 ); 217 | 218 | } 219 | 220 | scale = 1; 221 | 222 | // update condition is: 223 | // min(camera displacement, camera rotation in radians)^2 > EPS 224 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 225 | 226 | if ( zoomChanged || 227 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 228 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 229 | 230 | scope.dispatchEvent( changeEvent ); 231 | 232 | lastPosition.copy( scope.object.position ); 233 | lastQuaternion.copy( scope.object.quaternion ); 234 | zoomChanged = false; 235 | 236 | return true; 237 | 238 | } 239 | 240 | return false; 241 | 242 | }; 243 | 244 | }(); 245 | 246 | this.dispose = function () { 247 | 248 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 249 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 250 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 251 | 252 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 253 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 254 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 255 | 256 | document.removeEventListener( 'mousemove', onMouseMove, false ); 257 | document.removeEventListener( 'mouseup', onMouseUp, false ); 258 | 259 | window.removeEventListener( 'keydown', onKeyDown, false ); 260 | 261 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 262 | 263 | }; 264 | 265 | // 266 | // internals 267 | // 268 | 269 | var scope = this; 270 | 271 | var changeEvent = { type: 'change' }; 272 | var startEvent = { type: 'start' }; 273 | var endEvent = { type: 'end' }; 274 | 275 | var STATE = { 276 | NONE: - 1, 277 | ROTATE: 0, 278 | DOLLY: 1, 279 | PAN: 2, 280 | TOUCH_ROTATE: 3, 281 | TOUCH_PAN: 4, 282 | TOUCH_DOLLY_PAN: 5, 283 | TOUCH_DOLLY_ROTATE: 6 284 | }; 285 | 286 | var state = STATE.NONE; 287 | 288 | var EPS = 0.000001; 289 | 290 | // current position in spherical coordinates 291 | var spherical = new THREE.Spherical(); 292 | var sphericalDelta = new THREE.Spherical(); 293 | 294 | var scale = 1; 295 | var panOffset = new THREE.Vector3(); 296 | var zoomChanged = false; 297 | 298 | var rotateStart = new THREE.Vector2(); 299 | var rotateEnd = new THREE.Vector2(); 300 | var rotateDelta = new THREE.Vector2(); 301 | 302 | var panStart = new THREE.Vector2(); 303 | var panEnd = new THREE.Vector2(); 304 | var panDelta = new THREE.Vector2(); 305 | 306 | var dollyStart = new THREE.Vector2(); 307 | var dollyEnd = new THREE.Vector2(); 308 | var dollyDelta = new THREE.Vector2(); 309 | 310 | function getAutoRotationAngle() { 311 | 312 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 313 | 314 | } 315 | 316 | function getZoomScale() { 317 | 318 | return Math.pow( 0.95, scope.zoomSpeed ); 319 | 320 | } 321 | 322 | function rotateLeft( angle ) { 323 | 324 | sphericalDelta.theta -= angle; 325 | 326 | } 327 | 328 | function rotateUp( angle ) { 329 | 330 | sphericalDelta.phi -= angle; 331 | 332 | } 333 | 334 | var panLeft = function () { 335 | 336 | var v = new THREE.Vector3(); 337 | 338 | return function panLeft( distance, objectMatrix ) { 339 | 340 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 341 | v.multiplyScalar( - distance ); 342 | 343 | panOffset.add( v ); 344 | 345 | }; 346 | 347 | }(); 348 | 349 | var panUp = function () { 350 | 351 | var v = new THREE.Vector3(); 352 | 353 | return function panUp( distance, objectMatrix ) { 354 | 355 | if ( scope.screenSpacePanning === true ) { 356 | 357 | v.setFromMatrixColumn( objectMatrix, 1 ); 358 | 359 | } else { 360 | 361 | v.setFromMatrixColumn( objectMatrix, 0 ); 362 | v.crossVectors( scope.object.up, v ); 363 | 364 | } 365 | 366 | v.multiplyScalar( distance ); 367 | 368 | panOffset.add( v ); 369 | 370 | }; 371 | 372 | }(); 373 | 374 | // deltaX and deltaY are in pixels; right and down are positive 375 | var pan = function () { 376 | 377 | var offset = new THREE.Vector3(); 378 | 379 | return function pan( deltaX, deltaY ) { 380 | 381 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 382 | 383 | if ( scope.object.isPerspectiveCamera ) { 384 | 385 | // perspective 386 | var position = scope.object.position; 387 | offset.copy( position ).sub( scope.target ); 388 | var targetDistance = offset.length(); 389 | 390 | // half of the fov is center to top of screen 391 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 392 | 393 | // we use only clientHeight here so aspect ratio does not distort speed 394 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 395 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 396 | 397 | } else if ( scope.object.isOrthographicCamera ) { 398 | 399 | // orthographic 400 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 401 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 402 | 403 | } else { 404 | 405 | // camera neither orthographic nor perspective 406 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 407 | scope.enablePan = false; 408 | 409 | } 410 | 411 | }; 412 | 413 | }(); 414 | 415 | function dollyIn( dollyScale ) { 416 | 417 | if ( scope.object.isPerspectiveCamera ) { 418 | 419 | scale /= dollyScale; 420 | 421 | } else if ( scope.object.isOrthographicCamera ) { 422 | 423 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 424 | scope.object.updateProjectionMatrix(); 425 | zoomChanged = true; 426 | 427 | } else { 428 | 429 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 430 | scope.enableZoom = false; 431 | 432 | } 433 | 434 | } 435 | 436 | function dollyOut( dollyScale ) { 437 | 438 | if ( scope.object.isPerspectiveCamera ) { 439 | 440 | scale *= dollyScale; 441 | 442 | } else if ( scope.object.isOrthographicCamera ) { 443 | 444 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 445 | scope.object.updateProjectionMatrix(); 446 | zoomChanged = true; 447 | 448 | } else { 449 | 450 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 451 | scope.enableZoom = false; 452 | 453 | } 454 | 455 | } 456 | 457 | // 458 | // event callbacks - update the object state 459 | // 460 | 461 | function handleMouseDownRotate( event ) { 462 | 463 | rotateStart.set( event.clientX, event.clientY ); 464 | 465 | } 466 | 467 | function handleMouseDownDolly( event ) { 468 | 469 | dollyStart.set( event.clientX, event.clientY ); 470 | 471 | } 472 | 473 | function handleMouseDownPan( event ) { 474 | 475 | panStart.set( event.clientX, event.clientY ); 476 | 477 | } 478 | 479 | function handleMouseMoveRotate( event ) { 480 | 481 | rotateEnd.set( event.clientX, event.clientY ); 482 | 483 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 484 | 485 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 486 | 487 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 488 | 489 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 490 | 491 | rotateStart.copy( rotateEnd ); 492 | 493 | scope.update(); 494 | 495 | } 496 | 497 | function handleMouseMoveDolly( event ) { 498 | 499 | dollyEnd.set( event.clientX, event.clientY ); 500 | 501 | dollyDelta.subVectors( dollyEnd, dollyStart ); 502 | 503 | if ( dollyDelta.y > 0 ) { 504 | 505 | dollyIn( getZoomScale() ); 506 | 507 | } else if ( dollyDelta.y < 0 ) { 508 | 509 | dollyOut( getZoomScale() ); 510 | 511 | } 512 | 513 | dollyStart.copy( dollyEnd ); 514 | 515 | scope.update(); 516 | 517 | } 518 | 519 | function handleMouseMovePan( event ) { 520 | 521 | panEnd.set( event.clientX, event.clientY ); 522 | 523 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 524 | 525 | pan( panDelta.x, panDelta.y ); 526 | 527 | panStart.copy( panEnd ); 528 | 529 | scope.update(); 530 | 531 | } 532 | 533 | function handleMouseUp( /*event*/ ) { 534 | 535 | // no-op 536 | 537 | } 538 | 539 | function handleMouseWheel( event ) { 540 | 541 | if ( event.deltaY < 0 ) { 542 | 543 | dollyOut( getZoomScale() ); 544 | 545 | } else if ( event.deltaY > 0 ) { 546 | 547 | dollyIn( getZoomScale() ); 548 | 549 | } 550 | 551 | scope.update(); 552 | 553 | } 554 | 555 | function handleKeyDown( event ) { 556 | 557 | var needsUpdate = false; 558 | 559 | switch ( event.keyCode ) { 560 | 561 | case scope.keys.UP: 562 | pan( 0, scope.keyPanSpeed ); 563 | needsUpdate = true; 564 | break; 565 | 566 | case scope.keys.BOTTOM: 567 | pan( 0, - scope.keyPanSpeed ); 568 | needsUpdate = true; 569 | break; 570 | 571 | case scope.keys.LEFT: 572 | pan( scope.keyPanSpeed, 0 ); 573 | needsUpdate = true; 574 | break; 575 | 576 | case scope.keys.RIGHT: 577 | pan( - scope.keyPanSpeed, 0 ); 578 | needsUpdate = true; 579 | break; 580 | 581 | } 582 | 583 | if ( needsUpdate ) { 584 | 585 | // prevent the browser from scrolling on cursor keys 586 | event.preventDefault(); 587 | 588 | scope.update(); 589 | 590 | } 591 | 592 | 593 | } 594 | 595 | function handleTouchStartRotate( event ) { 596 | 597 | if ( event.touches.length == 1 ) { 598 | 599 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 600 | 601 | } else { 602 | 603 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 604 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 605 | 606 | rotateStart.set( x, y ); 607 | 608 | } 609 | 610 | } 611 | 612 | function handleTouchStartPan( event ) { 613 | 614 | if ( event.touches.length == 1 ) { 615 | 616 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 617 | 618 | } else { 619 | 620 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 621 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 622 | 623 | panStart.set( x, y ); 624 | 625 | } 626 | 627 | } 628 | 629 | function handleTouchStartDolly( event ) { 630 | 631 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 632 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 633 | 634 | var distance = Math.sqrt( dx * dx + dy * dy ); 635 | 636 | dollyStart.set( 0, distance ); 637 | 638 | } 639 | 640 | function handleTouchStartDollyPan( event ) { 641 | 642 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 643 | 644 | if ( scope.enablePan ) handleTouchStartPan( event ); 645 | 646 | } 647 | 648 | function handleTouchStartDollyRotate( event ) { 649 | 650 | if ( scope.enableZoom ) handleTouchStartDolly( event ); 651 | 652 | if ( scope.enableRotate ) handleTouchStartRotate( event ); 653 | 654 | } 655 | 656 | function handleTouchMoveRotate( event ) { 657 | 658 | if ( event.touches.length == 1 ) { 659 | 660 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 661 | 662 | } else { 663 | 664 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 665 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 666 | 667 | rotateEnd.set( x, y ); 668 | 669 | } 670 | 671 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 672 | 673 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 674 | 675 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 676 | 677 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 678 | 679 | rotateStart.copy( rotateEnd ); 680 | 681 | } 682 | 683 | function handleTouchMovePan( event ) { 684 | 685 | if ( event.touches.length == 1 ) { 686 | 687 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 688 | 689 | } else { 690 | 691 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 692 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 693 | 694 | panEnd.set( x, y ); 695 | 696 | } 697 | 698 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 699 | 700 | pan( panDelta.x, panDelta.y ); 701 | 702 | panStart.copy( panEnd ); 703 | 704 | } 705 | 706 | function handleTouchMoveDolly( event ) { 707 | 708 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 709 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 710 | 711 | var distance = Math.sqrt( dx * dx + dy * dy ); 712 | 713 | dollyEnd.set( 0, distance ); 714 | 715 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 716 | 717 | dollyIn( dollyDelta.y ); 718 | 719 | dollyStart.copy( dollyEnd ); 720 | 721 | } 722 | 723 | function handleTouchMoveDollyPan( event ) { 724 | 725 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 726 | 727 | if ( scope.enablePan ) handleTouchMovePan( event ); 728 | 729 | } 730 | 731 | function handleTouchMoveDollyRotate( event ) { 732 | 733 | if ( scope.enableZoom ) handleTouchMoveDolly( event ); 734 | 735 | if ( scope.enableRotate ) handleTouchMoveRotate( event ); 736 | 737 | } 738 | 739 | function handleTouchEnd( /*event*/ ) { 740 | 741 | // no-op 742 | 743 | } 744 | 745 | // 746 | // event handlers - FSM: listen for events and reset state 747 | // 748 | 749 | function onMouseDown( event ) { 750 | 751 | if ( scope.enabled === false ) return; 752 | 753 | // Prevent the browser from scrolling. 754 | 755 | event.preventDefault(); 756 | 757 | // Manually set the focus since calling preventDefault above 758 | // prevents the browser from setting it automatically. 759 | 760 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 761 | 762 | switch ( event.button ) { 763 | 764 | case 0: 765 | 766 | switch ( scope.mouseButtons.LEFT ) { 767 | 768 | case THREE.MOUSE.ROTATE: 769 | 770 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 771 | 772 | if ( scope.enablePan === false ) return; 773 | 774 | handleMouseDownPan( event ); 775 | 776 | state = STATE.PAN; 777 | 778 | } else { 779 | 780 | if ( scope.enableRotate === false ) return; 781 | 782 | handleMouseDownRotate( event ); 783 | 784 | state = STATE.ROTATE; 785 | 786 | } 787 | 788 | break; 789 | 790 | case THREE.MOUSE.PAN: 791 | 792 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 793 | 794 | if ( scope.enableRotate === false ) return; 795 | 796 | handleMouseDownRotate( event ); 797 | 798 | state = STATE.ROTATE; 799 | 800 | } else { 801 | 802 | if ( scope.enablePan === false ) return; 803 | 804 | handleMouseDownPan( event ); 805 | 806 | state = STATE.PAN; 807 | 808 | } 809 | 810 | break; 811 | 812 | default: 813 | 814 | state = STATE.NONE; 815 | 816 | } 817 | 818 | break; 819 | 820 | 821 | case 1: 822 | 823 | switch ( scope.mouseButtons.MIDDLE ) { 824 | 825 | case THREE.MOUSE.DOLLY: 826 | 827 | if ( scope.enableZoom === false ) return; 828 | 829 | handleMouseDownDolly( event ); 830 | 831 | state = STATE.DOLLY; 832 | 833 | break; 834 | 835 | 836 | default: 837 | 838 | state = STATE.NONE; 839 | 840 | } 841 | 842 | break; 843 | 844 | case 2: 845 | 846 | switch ( scope.mouseButtons.RIGHT ) { 847 | 848 | case THREE.MOUSE.ROTATE: 849 | 850 | if ( scope.enableRotate === false ) return; 851 | 852 | handleMouseDownRotate( event ); 853 | 854 | state = STATE.ROTATE; 855 | 856 | break; 857 | 858 | case THREE.MOUSE.PAN: 859 | 860 | if ( scope.enablePan === false ) return; 861 | 862 | handleMouseDownPan( event ); 863 | 864 | state = STATE.PAN; 865 | 866 | break; 867 | 868 | default: 869 | 870 | state = STATE.NONE; 871 | 872 | } 873 | 874 | break; 875 | 876 | } 877 | 878 | if ( state !== STATE.NONE ) { 879 | 880 | document.addEventListener( 'mousemove', onMouseMove, false ); 881 | document.addEventListener( 'mouseup', onMouseUp, false ); 882 | 883 | scope.dispatchEvent( startEvent ); 884 | 885 | } 886 | 887 | } 888 | 889 | function onMouseMove( event ) { 890 | 891 | if ( scope.enabled === false ) return; 892 | 893 | event.preventDefault(); 894 | 895 | switch ( state ) { 896 | 897 | case STATE.ROTATE: 898 | 899 | if ( scope.enableRotate === false ) return; 900 | 901 | handleMouseMoveRotate( event ); 902 | 903 | break; 904 | 905 | case STATE.DOLLY: 906 | 907 | if ( scope.enableZoom === false ) return; 908 | 909 | handleMouseMoveDolly( event ); 910 | 911 | break; 912 | 913 | case STATE.PAN: 914 | 915 | if ( scope.enablePan === false ) return; 916 | 917 | handleMouseMovePan( event ); 918 | 919 | break; 920 | 921 | } 922 | 923 | } 924 | 925 | function onMouseUp( event ) { 926 | 927 | if ( scope.enabled === false ) return; 928 | 929 | handleMouseUp( event ); 930 | 931 | document.removeEventListener( 'mousemove', onMouseMove, false ); 932 | document.removeEventListener( 'mouseup', onMouseUp, false ); 933 | 934 | scope.dispatchEvent( endEvent ); 935 | 936 | state = STATE.NONE; 937 | 938 | } 939 | 940 | function onMouseWheel( event ) { 941 | 942 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 943 | 944 | event.preventDefault(); 945 | event.stopPropagation(); 946 | 947 | scope.dispatchEvent( startEvent ); 948 | 949 | handleMouseWheel( event ); 950 | 951 | scope.dispatchEvent( endEvent ); 952 | 953 | } 954 | 955 | function onKeyDown( event ) { 956 | 957 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 958 | 959 | handleKeyDown( event ); 960 | 961 | } 962 | 963 | function onTouchStart( event ) { 964 | 965 | if ( scope.enabled === false ) return; 966 | 967 | event.preventDefault(); 968 | 969 | switch ( event.touches.length ) { 970 | 971 | case 1: 972 | 973 | switch ( scope.touches.ONE ) { 974 | 975 | case THREE.TOUCH.ROTATE: 976 | 977 | if ( scope.enableRotate === false ) return; 978 | 979 | handleTouchStartRotate( event ); 980 | 981 | state = STATE.TOUCH_ROTATE; 982 | 983 | break; 984 | 985 | case THREE.TOUCH.PAN: 986 | 987 | if ( scope.enablePan === false ) return; 988 | 989 | handleTouchStartPan( event ); 990 | 991 | state = STATE.TOUCH_PAN; 992 | 993 | break; 994 | 995 | default: 996 | 997 | state = STATE.NONE; 998 | 999 | } 1000 | 1001 | break; 1002 | 1003 | case 2: 1004 | 1005 | switch ( scope.touches.TWO ) { 1006 | 1007 | case THREE.TOUCH.DOLLY_PAN: 1008 | 1009 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1010 | 1011 | handleTouchStartDollyPan( event ); 1012 | 1013 | state = STATE.TOUCH_DOLLY_PAN; 1014 | 1015 | break; 1016 | 1017 | case THREE.TOUCH.DOLLY_ROTATE: 1018 | 1019 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1020 | 1021 | handleTouchStartDollyRotate( event ); 1022 | 1023 | state = STATE.TOUCH_DOLLY_ROTATE; 1024 | 1025 | break; 1026 | 1027 | default: 1028 | 1029 | state = STATE.NONE; 1030 | 1031 | } 1032 | 1033 | break; 1034 | 1035 | default: 1036 | 1037 | state = STATE.NONE; 1038 | 1039 | } 1040 | 1041 | if ( state !== STATE.NONE ) { 1042 | 1043 | scope.dispatchEvent( startEvent ); 1044 | 1045 | } 1046 | 1047 | } 1048 | 1049 | function onTouchMove( event ) { 1050 | 1051 | if ( scope.enabled === false ) return; 1052 | 1053 | event.preventDefault(); 1054 | event.stopPropagation(); 1055 | 1056 | switch ( state ) { 1057 | 1058 | case STATE.TOUCH_ROTATE: 1059 | 1060 | if ( scope.enableRotate === false ) return; 1061 | 1062 | handleTouchMoveRotate( event ); 1063 | 1064 | scope.update(); 1065 | 1066 | break; 1067 | 1068 | case STATE.TOUCH_PAN: 1069 | 1070 | if ( scope.enablePan === false ) return; 1071 | 1072 | handleTouchMovePan( event ); 1073 | 1074 | scope.update(); 1075 | 1076 | break; 1077 | 1078 | case STATE.TOUCH_DOLLY_PAN: 1079 | 1080 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 1081 | 1082 | handleTouchMoveDollyPan( event ); 1083 | 1084 | scope.update(); 1085 | 1086 | break; 1087 | 1088 | case STATE.TOUCH_DOLLY_ROTATE: 1089 | 1090 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 1091 | 1092 | handleTouchMoveDollyRotate( event ); 1093 | 1094 | scope.update(); 1095 | 1096 | break; 1097 | 1098 | default: 1099 | 1100 | state = STATE.NONE; 1101 | 1102 | } 1103 | 1104 | } 1105 | 1106 | function onTouchEnd( event ) { 1107 | 1108 | if ( scope.enabled === false ) return; 1109 | 1110 | handleTouchEnd( event ); 1111 | 1112 | scope.dispatchEvent( endEvent ); 1113 | 1114 | state = STATE.NONE; 1115 | 1116 | } 1117 | 1118 | function onContextMenu( event ) { 1119 | 1120 | if ( scope.enabled === false ) return; 1121 | 1122 | event.preventDefault(); 1123 | 1124 | } 1125 | 1126 | // 1127 | 1128 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1129 | 1130 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 1131 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1132 | 1133 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1134 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1135 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1136 | 1137 | window.addEventListener( 'keydown', onKeyDown, false ); 1138 | 1139 | // force an update at start 1140 | 1141 | this.update(); 1142 | 1143 | }; 1144 | 1145 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1146 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 1147 | 1148 | 1149 | // This set of controls performs orbiting, dollying (zooming), and panning. 1150 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 1151 | // This is very similar to OrbitControls, another set of touch behavior 1152 | // 1153 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate 1154 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 1155 | // Pan - left mouse, or arrow keys / touch: one-finger move 1156 | 1157 | THREE.MapControls = function ( object, domElement ) { 1158 | 1159 | THREE.OrbitControls.call( this, object, domElement ); 1160 | 1161 | this.mouseButtons.LEFT = THREE.MOUSE.PAN; 1162 | this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; 1163 | 1164 | this.touches.ONE = THREE.TOUCH.PAN; 1165 | this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; 1166 | 1167 | }; 1168 | 1169 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1170 | THREE.MapControls.prototype.constructor = THREE.MapControls; -------------------------------------------------------------------------------- /public/lib/TransformControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author arodic / https://github.com/arodic 3 | */ 4 | 5 | var TransformControls = function ( camera, domElement ) { 6 | 7 | if ( domElement === undefined ) { 8 | 9 | console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' ); 10 | domElement = document; 11 | 12 | } 13 | 14 | Object3D.call( this ); 15 | 16 | this.visible = false; 17 | this.domElement = domElement; 18 | 19 | var _gizmo = new TransformControlsGizmo(); 20 | this.add( _gizmo ); 21 | 22 | var _plane = new TransformControlsPlane(); 23 | this.add( _plane ); 24 | 25 | var scope = this; 26 | 27 | // Define properties with getters/setter 28 | // Setting the defined property will automatically trigger change event 29 | // Defined properties are passed down to gizmo and plane 30 | 31 | defineProperty( "camera", camera ); 32 | defineProperty( "object", undefined ); 33 | defineProperty( "enabled", true ); 34 | defineProperty( "axis", null ); 35 | defineProperty( "mode", "translate" ); 36 | defineProperty( "translationSnap", null ); 37 | defineProperty( "rotationSnap", null ); 38 | defineProperty( "scaleSnap", null ); 39 | defineProperty( "space", "world" ); 40 | defineProperty( "size", 1 ); 41 | defineProperty( "dragging", false ); 42 | defineProperty( "showX", true ); 43 | defineProperty( "showY", true ); 44 | defineProperty( "showZ", true ); 45 | 46 | var changeEvent = { type: "change" }; 47 | var mouseDownEvent = { type: "mouseDown" }; 48 | var mouseUpEvent = { type: "mouseUp", mode: scope.mode }; 49 | var objectChangeEvent = { type: "objectChange" }; 50 | 51 | // Reusable utility variables 52 | 53 | var raycaster = new Raycaster(); 54 | 55 | function intersectObjectWithRay( object, raycaster, includeInvisible ) { 56 | 57 | var allIntersections = raycaster.intersectObject( object, true ); 58 | 59 | for ( var i = 0; i < allIntersections.length; i ++ ) { 60 | 61 | if ( allIntersections[ i ].object.visible || includeInvisible ) { 62 | 63 | return allIntersections[ i ]; 64 | 65 | } 66 | 67 | } 68 | 69 | return false; 70 | 71 | } 72 | 73 | var _tempVector = new Vector3(); 74 | var _tempVector2 = new Vector3(); 75 | var _tempQuaternion = new Quaternion(); 76 | var _unit = { 77 | X: new Vector3( 1, 0, 0 ), 78 | Y: new Vector3( 0, 1, 0 ), 79 | Z: new Vector3( 0, 0, 1 ) 80 | }; 81 | 82 | var pointStart = new Vector3(); 83 | var pointEnd = new Vector3(); 84 | var offset = new Vector3(); 85 | var rotationAxis = new Vector3(); 86 | var startNorm = new Vector3(); 87 | var endNorm = new Vector3(); 88 | var rotationAngle = 0; 89 | 90 | var cameraPosition = new Vector3(); 91 | var cameraQuaternion = new Quaternion(); 92 | var cameraScale = new Vector3(); 93 | 94 | var parentPosition = new Vector3(); 95 | var parentQuaternion = new Quaternion(); 96 | var parentQuaternionInv = new Quaternion(); 97 | var parentScale = new Vector3(); 98 | 99 | var worldPositionStart = new Vector3(); 100 | var worldQuaternionStart = new Quaternion(); 101 | var worldScaleStart = new Vector3(); 102 | 103 | var worldPosition = new Vector3(); 104 | var worldQuaternion = new Quaternion(); 105 | var worldQuaternionInv = new Quaternion(); 106 | var worldScale = new Vector3(); 107 | 108 | var eye = new Vector3(); 109 | 110 | var positionStart = new Vector3(); 111 | var quaternionStart = new Quaternion(); 112 | var scaleStart = new Vector3(); 113 | 114 | // TODO: remove properties unused in plane and gizmo 115 | 116 | defineProperty( "worldPosition", worldPosition ); 117 | defineProperty( "worldPositionStart", worldPositionStart ); 118 | defineProperty( "worldQuaternion", worldQuaternion ); 119 | defineProperty( "worldQuaternionStart", worldQuaternionStart ); 120 | defineProperty( "cameraPosition", cameraPosition ); 121 | defineProperty( "cameraQuaternion", cameraQuaternion ); 122 | defineProperty( "pointStart", pointStart ); 123 | defineProperty( "pointEnd", pointEnd ); 124 | defineProperty( "rotationAxis", rotationAxis ); 125 | defineProperty( "rotationAngle", rotationAngle ); 126 | defineProperty( "eye", eye ); 127 | 128 | { 129 | 130 | domElement.addEventListener( "mousedown", onPointerDown, false ); 131 | domElement.addEventListener( "touchstart", onPointerDown, false ); 132 | domElement.addEventListener( "mousemove", onPointerHover, false ); 133 | domElement.addEventListener( "touchmove", onPointerHover, false ); 134 | domElement.addEventListener( "touchmove", onPointerMove, false ); 135 | document.addEventListener( "mouseup", onPointerUp, false ); 136 | domElement.addEventListener( "touchend", onPointerUp, false ); 137 | domElement.addEventListener( "touchcancel", onPointerUp, false ); 138 | domElement.addEventListener( "touchleave", onPointerUp, false ); 139 | 140 | } 141 | 142 | this.dispose = function () { 143 | 144 | domElement.removeEventListener( "mousedown", onPointerDown ); 145 | domElement.removeEventListener( "touchstart", onPointerDown ); 146 | domElement.removeEventListener( "mousemove", onPointerHover ); 147 | document.removeEventListener( "mousemove", onPointerMove ); 148 | domElement.removeEventListener( "touchmove", onPointerHover ); 149 | domElement.removeEventListener( "touchmove", onPointerMove ); 150 | document.removeEventListener( "mouseup", onPointerUp ); 151 | domElement.removeEventListener( "touchend", onPointerUp ); 152 | domElement.removeEventListener( "touchcancel", onPointerUp ); 153 | domElement.removeEventListener( "touchleave", onPointerUp ); 154 | 155 | this.traverse( function ( child ) { 156 | 157 | if ( child.geometry ) child.geometry.dispose(); 158 | if ( child.material ) child.material.dispose(); 159 | 160 | } ); 161 | 162 | }; 163 | 164 | // Set current object 165 | this.attach = function ( object ) { 166 | 167 | this.object = object; 168 | this.visible = true; 169 | 170 | return this; 171 | 172 | }; 173 | 174 | // Detatch from object 175 | this.detach = function () { 176 | 177 | this.object = undefined; 178 | this.visible = false; 179 | this.axis = null; 180 | 181 | return this; 182 | 183 | }; 184 | 185 | // Defined getter, setter and store for a property 186 | function defineProperty( propName, defaultValue ) { 187 | 188 | var propValue = defaultValue; 189 | 190 | Object.defineProperty( scope, propName, { 191 | 192 | get: function () { 193 | 194 | return propValue !== undefined ? propValue : defaultValue; 195 | 196 | }, 197 | 198 | set: function ( value ) { 199 | 200 | if ( propValue !== value ) { 201 | 202 | propValue = value; 203 | _plane[ propName ] = value; 204 | _gizmo[ propName ] = value; 205 | 206 | scope.dispatchEvent( { type: propName + "-changed", value: value } ); 207 | scope.dispatchEvent( changeEvent ); 208 | 209 | } 210 | 211 | } 212 | 213 | } ); 214 | 215 | scope[ propName ] = defaultValue; 216 | _plane[ propName ] = defaultValue; 217 | _gizmo[ propName ] = defaultValue; 218 | 219 | } 220 | 221 | // updateMatrixWorld updates key transformation variables 222 | this.updateMatrixWorld = function () { 223 | 224 | if ( this.object !== undefined ) { 225 | 226 | this.object.updateMatrixWorld(); 227 | 228 | if ( this.object.parent === null ) { 229 | 230 | console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); 231 | 232 | } else { 233 | 234 | this.object.parent.matrixWorld.decompose( parentPosition, parentQuaternion, parentScale ); 235 | 236 | } 237 | 238 | this.object.matrixWorld.decompose( worldPosition, worldQuaternion, worldScale ); 239 | 240 | parentQuaternionInv.copy( parentQuaternion ).inverse(); 241 | worldQuaternionInv.copy( worldQuaternion ).inverse(); 242 | 243 | } 244 | 245 | this.camera.updateMatrixWorld(); 246 | this.camera.matrixWorld.decompose( cameraPosition, cameraQuaternion, cameraScale ); 247 | 248 | eye.copy( cameraPosition ).sub( worldPosition ).normalize(); 249 | 250 | Object3D.prototype.updateMatrixWorld.call( this ); 251 | 252 | }; 253 | 254 | this.pointerHover = function ( pointer ) { 255 | 256 | if ( this.object === undefined || this.dragging === true || ( pointer.button !== undefined && pointer.button !== 0 ) ) return; 257 | 258 | raycaster.setFromCamera( pointer, this.camera ); 259 | 260 | var intersect = intersectObjectWithRay( _gizmo.picker[ this.mode ], raycaster ); 261 | 262 | if ( intersect ) { 263 | 264 | this.axis = intersect.object.name; 265 | 266 | } else { 267 | 268 | this.axis = null; 269 | 270 | } 271 | 272 | }; 273 | 274 | this.pointerDown = function ( pointer ) { 275 | 276 | if ( this.object === undefined || this.dragging === true || ( pointer.button !== undefined && pointer.button !== 0 ) ) return; 277 | 278 | if ( ( pointer.button === 0 || pointer.button === undefined ) && this.axis !== null ) { 279 | 280 | raycaster.setFromCamera( pointer, this.camera ); 281 | 282 | var planeIntersect = intersectObjectWithRay( _plane, raycaster, true ); 283 | 284 | if ( planeIntersect ) { 285 | 286 | var space = this.space; 287 | 288 | if ( this.mode === 'scale' ) { 289 | 290 | space = 'local'; 291 | 292 | } else if ( this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ' ) { 293 | 294 | space = 'world'; 295 | 296 | } 297 | 298 | if ( space === 'local' && this.mode === 'rotate' ) { 299 | 300 | var snap = this.rotationSnap; 301 | 302 | if ( this.axis === 'X' && snap ) this.object.rotation.x = Math.round( this.object.rotation.x / snap ) * snap; 303 | if ( this.axis === 'Y' && snap ) this.object.rotation.y = Math.round( this.object.rotation.y / snap ) * snap; 304 | if ( this.axis === 'Z' && snap ) this.object.rotation.z = Math.round( this.object.rotation.z / snap ) * snap; 305 | 306 | } 307 | 308 | this.object.updateMatrixWorld(); 309 | this.object.parent.updateMatrixWorld(); 310 | 311 | positionStart.copy( this.object.position ); 312 | quaternionStart.copy( this.object.quaternion ); 313 | scaleStart.copy( this.object.scale ); 314 | 315 | this.object.matrixWorld.decompose( worldPositionStart, worldQuaternionStart, worldScaleStart ); 316 | 317 | pointStart.copy( planeIntersect.point ).sub( worldPositionStart ); 318 | 319 | } 320 | 321 | this.dragging = true; 322 | mouseDownEvent.mode = this.mode; 323 | this.dispatchEvent( mouseDownEvent ); 324 | 325 | } 326 | 327 | }; 328 | 329 | this.pointerMove = function ( pointer ) { 330 | 331 | var axis = this.axis; 332 | var mode = this.mode; 333 | var object = this.object; 334 | var space = this.space; 335 | 336 | if ( mode === 'scale' ) { 337 | 338 | space = 'local'; 339 | 340 | } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) { 341 | 342 | space = 'world'; 343 | 344 | } 345 | 346 | if ( object === undefined || axis === null || this.dragging === false || ( pointer.button !== undefined && pointer.button !== 0 ) ) return; 347 | 348 | raycaster.setFromCamera( pointer, this.camera ); 349 | 350 | var planeIntersect = intersectObjectWithRay( _plane, raycaster, true ); 351 | 352 | if ( ! planeIntersect ) return; 353 | 354 | pointEnd.copy( planeIntersect.point ).sub( worldPositionStart ); 355 | 356 | if ( mode === 'translate' ) { 357 | 358 | // Apply translate 359 | 360 | offset.copy( pointEnd ).sub( pointStart ); 361 | 362 | if ( space === 'local' && axis !== 'XYZ' ) { 363 | 364 | offset.applyQuaternion( worldQuaternionInv ); 365 | 366 | } 367 | 368 | if ( axis.indexOf( 'X' ) === - 1 ) offset.x = 0; 369 | if ( axis.indexOf( 'Y' ) === - 1 ) offset.y = 0; 370 | if ( axis.indexOf( 'Z' ) === - 1 ) offset.z = 0; 371 | 372 | if ( space === 'local' && axis !== 'XYZ' ) { 373 | 374 | offset.applyQuaternion( quaternionStart ).divide( parentScale ); 375 | 376 | } else { 377 | 378 | offset.applyQuaternion( parentQuaternionInv ).divide( parentScale ); 379 | 380 | } 381 | 382 | object.position.copy( offset ).add( positionStart ); 383 | 384 | // Apply translation snap 385 | 386 | if ( this.translationSnap ) { 387 | 388 | if ( space === 'local' ) { 389 | 390 | object.position.applyQuaternion( _tempQuaternion.copy( quaternionStart ).inverse() ); 391 | 392 | if ( axis.search( 'X' ) !== - 1 ) { 393 | 394 | object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; 395 | 396 | } 397 | 398 | if ( axis.search( 'Y' ) !== - 1 ) { 399 | 400 | object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; 401 | 402 | } 403 | 404 | if ( axis.search( 'Z' ) !== - 1 ) { 405 | 406 | object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; 407 | 408 | } 409 | 410 | object.position.applyQuaternion( quaternionStart ); 411 | 412 | } 413 | 414 | if ( space === 'world' ) { 415 | 416 | if ( object.parent ) { 417 | 418 | object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); 419 | 420 | } 421 | 422 | if ( axis.search( 'X' ) !== - 1 ) { 423 | 424 | object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; 425 | 426 | } 427 | 428 | if ( axis.search( 'Y' ) !== - 1 ) { 429 | 430 | object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; 431 | 432 | } 433 | 434 | if ( axis.search( 'Z' ) !== - 1 ) { 435 | 436 | object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; 437 | 438 | } 439 | 440 | if ( object.parent ) { 441 | 442 | object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); 443 | 444 | } 445 | 446 | } 447 | 448 | } 449 | 450 | } else if ( mode === 'scale' ) { 451 | 452 | if ( axis.search( 'XYZ' ) !== - 1 ) { 453 | 454 | var d = pointEnd.length() / pointStart.length(); 455 | 456 | if ( pointEnd.dot( pointStart ) < 0 ) d *= - 1; 457 | 458 | _tempVector2.set( d, d, d ); 459 | 460 | } else { 461 | 462 | _tempVector.copy( pointStart ); 463 | _tempVector2.copy( pointEnd ); 464 | 465 | _tempVector.applyQuaternion( worldQuaternionInv ); 466 | _tempVector2.applyQuaternion( worldQuaternionInv ); 467 | 468 | _tempVector2.divide( _tempVector ); 469 | 470 | if ( axis.search( 'X' ) === - 1 ) { 471 | 472 | _tempVector2.x = 1; 473 | 474 | } 475 | 476 | if ( axis.search( 'Y' ) === - 1 ) { 477 | 478 | _tempVector2.y = 1; 479 | 480 | } 481 | 482 | if ( axis.search( 'Z' ) === - 1 ) { 483 | 484 | _tempVector2.z = 1; 485 | 486 | } 487 | 488 | } 489 | 490 | // Apply scale 491 | 492 | object.scale.copy( scaleStart ).multiply( _tempVector2 ); 493 | 494 | if ( this.scaleSnap ) { 495 | 496 | if ( axis.search( 'X' ) !== - 1 ) { 497 | 498 | object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; 499 | 500 | } 501 | 502 | if ( axis.search( 'Y' ) !== - 1 ) { 503 | 504 | object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; 505 | 506 | } 507 | 508 | if ( axis.search( 'Z' ) !== - 1 ) { 509 | 510 | object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; 511 | 512 | } 513 | 514 | } 515 | 516 | } else if ( mode === 'rotate' ) { 517 | 518 | offset.copy( pointEnd ).sub( pointStart ); 519 | 520 | var ROTATION_SPEED = 20 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) ); 521 | 522 | if ( axis === 'E' ) { 523 | 524 | rotationAxis.copy( eye ); 525 | rotationAngle = pointEnd.angleTo( pointStart ); 526 | 527 | startNorm.copy( pointStart ).normalize(); 528 | endNorm.copy( pointEnd ).normalize(); 529 | 530 | rotationAngle *= ( endNorm.cross( startNorm ).dot( eye ) < 0 ? 1 : - 1 ); 531 | 532 | } else if ( axis === 'XYZE' ) { 533 | 534 | rotationAxis.copy( offset ).cross( eye ).normalize(); 535 | rotationAngle = offset.dot( _tempVector.copy( rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED; 536 | 537 | } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) { 538 | 539 | rotationAxis.copy( _unit[ axis ] ); 540 | 541 | _tempVector.copy( _unit[ axis ] ); 542 | 543 | if ( space === 'local' ) { 544 | 545 | _tempVector.applyQuaternion( worldQuaternion ); 546 | 547 | } 548 | 549 | rotationAngle = offset.dot( _tempVector.cross( eye ).normalize() ) * ROTATION_SPEED; 550 | 551 | } 552 | 553 | // Apply rotation snap 554 | 555 | if ( this.rotationSnap ) rotationAngle = Math.round( rotationAngle / this.rotationSnap ) * this.rotationSnap; 556 | 557 | this.rotationAngle = rotationAngle; 558 | 559 | // Apply rotate 560 | if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) { 561 | 562 | object.quaternion.copy( quaternionStart ); 563 | object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) ).normalize(); 564 | 565 | } else { 566 | 567 | rotationAxis.applyQuaternion( parentQuaternionInv ); 568 | object.quaternion.copy( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) ); 569 | object.quaternion.multiply( quaternionStart ).normalize(); 570 | 571 | } 572 | 573 | } 574 | 575 | this.dispatchEvent( changeEvent ); 576 | this.dispatchEvent( objectChangeEvent ); 577 | 578 | }; 579 | 580 | this.pointerUp = function ( pointer ) { 581 | 582 | if ( pointer.button !== undefined && pointer.button !== 0 ) return; 583 | 584 | if ( this.dragging && ( this.axis !== null ) ) { 585 | 586 | mouseUpEvent.mode = this.mode; 587 | this.dispatchEvent( mouseUpEvent ); 588 | 589 | } 590 | 591 | this.dragging = false; 592 | 593 | if ( pointer.button === undefined ) this.axis = null; 594 | 595 | }; 596 | 597 | // normalize mouse / touch pointer and remap {x,y} to view space. 598 | 599 | function getPointer( event ) { 600 | 601 | if ( document.pointerLockElement ) { 602 | 603 | return { 604 | x: 0, 605 | y: 0, 606 | button: event.button 607 | }; 608 | 609 | } else { 610 | 611 | var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; 612 | 613 | var rect = domElement.getBoundingClientRect(); 614 | 615 | return { 616 | x: ( pointer.clientX - rect.left ) / rect.width * 2 - 1, 617 | y: - ( pointer.clientY - rect.top ) / rect.height * 2 + 1, 618 | button: event.button 619 | }; 620 | 621 | } 622 | 623 | } 624 | 625 | // mouse / touch event handlers 626 | 627 | function onPointerHover( event ) { 628 | 629 | if ( ! scope.enabled ) return; 630 | 631 | scope.pointerHover( getPointer( event ) ); 632 | 633 | } 634 | 635 | function onPointerDown( event ) { 636 | 637 | if ( ! scope.enabled ) return; 638 | 639 | document.addEventListener( "mousemove", onPointerMove, false ); 640 | 641 | scope.pointerHover( getPointer( event ) ); 642 | scope.pointerDown( getPointer( event ) ); 643 | 644 | } 645 | 646 | function onPointerMove( event ) { 647 | 648 | if ( ! scope.enabled ) return; 649 | 650 | scope.pointerMove( getPointer( event ) ); 651 | 652 | } 653 | 654 | function onPointerUp( event ) { 655 | 656 | if ( ! scope.enabled ) return; 657 | 658 | document.removeEventListener( "mousemove", onPointerMove, false ); 659 | 660 | scope.pointerUp( getPointer( event ) ); 661 | 662 | } 663 | 664 | // TODO: deprecate 665 | 666 | this.getMode = function () { 667 | 668 | return scope.mode; 669 | 670 | }; 671 | 672 | this.setMode = function ( mode ) { 673 | 674 | scope.mode = mode; 675 | 676 | }; 677 | 678 | this.setTranslationSnap = function ( translationSnap ) { 679 | 680 | scope.translationSnap = translationSnap; 681 | 682 | }; 683 | 684 | this.setRotationSnap = function ( rotationSnap ) { 685 | 686 | scope.rotationSnap = rotationSnap; 687 | 688 | }; 689 | 690 | this.setScaleSnap = function ( scaleSnap ) { 691 | 692 | scope.scaleSnap = scaleSnap; 693 | 694 | }; 695 | 696 | this.setSize = function ( size ) { 697 | 698 | scope.size = size; 699 | 700 | }; 701 | 702 | this.setSpace = function ( space ) { 703 | 704 | scope.space = space; 705 | 706 | }; 707 | 708 | this.update = function () { 709 | 710 | console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' ); 711 | 712 | }; 713 | 714 | }; 715 | 716 | TransformControls.prototype = Object.assign( Object.create( Object3D.prototype ), { 717 | 718 | constructor: TransformControls, 719 | 720 | isTransformControls: true 721 | 722 | } ); 723 | 724 | 725 | var TransformControlsGizmo = function () { 726 | 727 | 'use strict'; 728 | 729 | Object3D.call( this ); 730 | 731 | this.type = 'TransformControlsGizmo'; 732 | 733 | // shared materials 734 | 735 | var gizmoMaterial = new MeshBasicMaterial( { 736 | depthTest: false, 737 | depthWrite: false, 738 | transparent: true, 739 | side: DoubleSide, 740 | fog: false 741 | } ); 742 | 743 | var gizmoLineMaterial = new LineBasicMaterial( { 744 | depthTest: false, 745 | depthWrite: false, 746 | transparent: true, 747 | linewidth: 1, 748 | fog: false 749 | } ); 750 | 751 | // Make unique material for each axis/color 752 | 753 | var matInvisible = gizmoMaterial.clone(); 754 | matInvisible.opacity = 0.15; 755 | 756 | var matHelper = gizmoMaterial.clone(); 757 | matHelper.opacity = 0.33; 758 | 759 | var matRed = gizmoMaterial.clone(); 760 | matRed.color.set( 0xff0000 ); 761 | 762 | var matGreen = gizmoMaterial.clone(); 763 | matGreen.color.set( 0x00ff00 ); 764 | 765 | var matBlue = gizmoMaterial.clone(); 766 | matBlue.color.set( 0x0000ff ); 767 | 768 | var matWhiteTransparent = gizmoMaterial.clone(); 769 | matWhiteTransparent.opacity = 0.25; 770 | 771 | var matYellowTransparent = matWhiteTransparent.clone(); 772 | matYellowTransparent.color.set( 0xffff00 ); 773 | 774 | var matCyanTransparent = matWhiteTransparent.clone(); 775 | matCyanTransparent.color.set( 0x00ffff ); 776 | 777 | var matMagentaTransparent = matWhiteTransparent.clone(); 778 | matMagentaTransparent.color.set( 0xff00ff ); 779 | 780 | var matYellow = gizmoMaterial.clone(); 781 | matYellow.color.set( 0xffff00 ); 782 | 783 | var matLineRed = gizmoLineMaterial.clone(); 784 | matLineRed.color.set( 0xff0000 ); 785 | 786 | var matLineGreen = gizmoLineMaterial.clone(); 787 | matLineGreen.color.set( 0x00ff00 ); 788 | 789 | var matLineBlue = gizmoLineMaterial.clone(); 790 | matLineBlue.color.set( 0x0000ff ); 791 | 792 | var matLineCyan = gizmoLineMaterial.clone(); 793 | matLineCyan.color.set( 0x00ffff ); 794 | 795 | var matLineMagenta = gizmoLineMaterial.clone(); 796 | matLineMagenta.color.set( 0xff00ff ); 797 | 798 | var matLineYellow = gizmoLineMaterial.clone(); 799 | matLineYellow.color.set( 0xffff00 ); 800 | 801 | var matLineGray = gizmoLineMaterial.clone(); 802 | matLineGray.color.set( 0x787878 ); 803 | 804 | var matLineYellowTransparent = matLineYellow.clone(); 805 | matLineYellowTransparent.opacity = 0.25; 806 | 807 | // reusable geometry 808 | 809 | var arrowGeometry = new CylinderBufferGeometry( 0, 0.05, 0.2, 12, 1, false ); 810 | 811 | var scaleHandleGeometry = new BoxBufferGeometry( 0.125, 0.125, 0.125 ); 812 | 813 | var lineGeometry = new BufferGeometry( ); 814 | lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); 815 | 816 | var CircleGeometry = function ( radius, arc ) { 817 | 818 | var geometry = new BufferGeometry( ); 819 | var vertices = []; 820 | 821 | for ( var i = 0; i <= 64 * arc; ++ i ) { 822 | 823 | vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius ); 824 | 825 | } 826 | 827 | geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); 828 | 829 | return geometry; 830 | 831 | }; 832 | 833 | // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position 834 | 835 | var TranslateHelperGeometry = function () { 836 | 837 | var geometry = new BufferGeometry(); 838 | 839 | geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) ); 840 | 841 | return geometry; 842 | 843 | }; 844 | 845 | // Gizmo definitions - custom hierarchy definitions for setupGizmo() function 846 | 847 | var gizmoTranslate = { 848 | X: [ 849 | [ new Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, - Math.PI / 2 ], null, 'fwd' ], 850 | [ new Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, Math.PI / 2 ], null, 'bwd' ], 851 | [ new Line( lineGeometry, matLineRed ) ] 852 | ], 853 | Y: [ 854 | [ new Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], null, null, 'fwd' ], 855 | [ new Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], [ Math.PI, 0, 0 ], null, 'bwd' ], 856 | [ new Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ]] 857 | ], 858 | Z: [ 859 | [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ Math.PI / 2, 0, 0 ], null, 'fwd' ], 860 | [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ - Math.PI / 2, 0, 0 ], null, 'bwd' ], 861 | [ new Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ]] 862 | ], 863 | XYZ: [ 864 | [ new Mesh( new OctahedronBufferGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ], [ 0, 0, 0 ]] 865 | ], 866 | XY: [ 867 | [ new Mesh( new PlaneBufferGeometry( 0.295, 0.295 ), matYellowTransparent.clone() ), [ 0.15, 0.15, 0 ]], 868 | [ new Line( lineGeometry, matLineYellow ), [ 0.18, 0.3, 0 ], null, [ 0.125, 1, 1 ]], 869 | [ new Line( lineGeometry, matLineYellow ), [ 0.3, 0.18, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]] 870 | ], 871 | YZ: [ 872 | [ new Mesh( new PlaneBufferGeometry( 0.295, 0.295 ), matCyanTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]], 873 | [ new Line( lineGeometry, matLineCyan ), [ 0, 0.18, 0.3 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]], 874 | [ new Line( lineGeometry, matLineCyan ), [ 0, 0.3, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]] 875 | ], 876 | XZ: [ 877 | [ new Mesh( new PlaneBufferGeometry( 0.295, 0.295 ), matMagentaTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]], 878 | [ new Line( lineGeometry, matLineMagenta ), [ 0.18, 0, 0.3 ], null, [ 0.125, 1, 1 ]], 879 | [ new Line( lineGeometry, matLineMagenta ), [ 0.3, 0, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]] 880 | ] 881 | }; 882 | 883 | var pickerTranslate = { 884 | X: [ 885 | [ new Mesh( new CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ]] 886 | ], 887 | Y: [ 888 | [ new Mesh( new CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0.6, 0 ]] 889 | ], 890 | Z: [ 891 | [ new Mesh( new CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ]] 892 | ], 893 | XYZ: [ 894 | [ new Mesh( new OctahedronBufferGeometry( 0.2, 0 ), matInvisible ) ] 895 | ], 896 | XY: [ 897 | [ new Mesh( new PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0.2, 0 ]] 898 | ], 899 | YZ: [ 900 | [ new Mesh( new PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ]] 901 | ], 902 | XZ: [ 903 | [ new Mesh( new PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0, 0.2 ], [ - Math.PI / 2, 0, 0 ]] 904 | ] 905 | }; 906 | 907 | var helperTranslate = { 908 | START: [ 909 | [ new Mesh( new OctahedronBufferGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] 910 | ], 911 | END: [ 912 | [ new Mesh( new OctahedronBufferGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] 913 | ], 914 | DELTA: [ 915 | [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ] 916 | ], 917 | X: [ 918 | [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] 919 | ], 920 | Y: [ 921 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] 922 | ], 923 | Z: [ 924 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] 925 | ] 926 | }; 927 | 928 | var gizmoRotate = { 929 | X: [ 930 | [ new Line( CircleGeometry( 1, 0.5 ), matLineRed ) ], 931 | [ new Mesh( new OctahedronBufferGeometry( 0.04, 0 ), matRed ), [ 0, 0, 0.99 ], null, [ 1, 3, 1 ]], 932 | ], 933 | Y: [ 934 | [ new Line( CircleGeometry( 1, 0.5 ), matLineGreen ), null, [ 0, 0, - Math.PI / 2 ]], 935 | [ new Mesh( new OctahedronBufferGeometry( 0.04, 0 ), matGreen ), [ 0, 0, 0.99 ], null, [ 3, 1, 1 ]], 936 | ], 937 | Z: [ 938 | [ new Line( CircleGeometry( 1, 0.5 ), matLineBlue ), null, [ 0, Math.PI / 2, 0 ]], 939 | [ new Mesh( new OctahedronBufferGeometry( 0.04, 0 ), matBlue ), [ 0.99, 0, 0 ], null, [ 1, 3, 1 ]], 940 | ], 941 | E: [ 942 | [ new Line( CircleGeometry( 1.25, 1 ), matLineYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]], 943 | [ new Mesh( new CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 1.17, 0, 0 ], [ 0, 0, - Math.PI / 2 ], [ 1, 1, 0.001 ]], 944 | [ new Mesh( new CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ - 1.17, 0, 0 ], [ 0, 0, Math.PI / 2 ], [ 1, 1, 0.001 ]], 945 | [ new Mesh( new CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, - 1.17, 0 ], [ Math.PI, 0, 0 ], [ 1, 1, 0.001 ]], 946 | [ new Mesh( new CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, 1.17, 0 ], [ 0, 0, 0 ], [ 1, 1, 0.001 ]], 947 | ], 948 | XYZE: [ 949 | [ new Line( CircleGeometry( 1, 1 ), matLineGray ), null, [ 0, Math.PI / 2, 0 ]] 950 | ] 951 | }; 952 | 953 | var helperRotate = { 954 | AXIS: [ 955 | [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] 956 | ] 957 | }; 958 | 959 | var pickerRotate = { 960 | X: [ 961 | [ new Mesh( new TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]], 962 | ], 963 | Y: [ 964 | [ new Mesh( new TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], 965 | ], 966 | Z: [ 967 | [ new Mesh( new TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], 968 | ], 969 | E: [ 970 | [ new Mesh( new TorusBufferGeometry( 1.25, 0.1, 2, 24 ), matInvisible ) ] 971 | ], 972 | XYZE: [ 973 | [ new Mesh( new SphereBufferGeometry( 0.7, 10, 8 ), matInvisible ) ] 974 | ] 975 | }; 976 | 977 | var gizmoScale = { 978 | X: [ 979 | [ new Mesh( scaleHandleGeometry, matRed ), [ 0.8, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], 980 | [ new Line( lineGeometry, matLineRed ), null, null, [ 0.8, 1, 1 ]] 981 | ], 982 | Y: [ 983 | [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.8, 0 ]], 984 | [ new Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ], [ 0.8, 1, 1 ]] 985 | ], 986 | Z: [ 987 | [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.8 ], [ Math.PI / 2, 0, 0 ]], 988 | [ new Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ], [ 0.8, 1, 1 ]] 989 | ], 990 | XY: [ 991 | [ new Mesh( scaleHandleGeometry, matYellowTransparent ), [ 0.85, 0.85, 0 ], null, [ 2, 2, 0.2 ]], 992 | [ new Line( lineGeometry, matLineYellow ), [ 0.855, 0.98, 0 ], null, [ 0.125, 1, 1 ]], 993 | [ new Line( lineGeometry, matLineYellow ), [ 0.98, 0.855, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]] 994 | ], 995 | YZ: [ 996 | [ new Mesh( scaleHandleGeometry, matCyanTransparent ), [ 0, 0.85, 0.85 ], null, [ 0.2, 2, 2 ]], 997 | [ new Line( lineGeometry, matLineCyan ), [ 0, 0.855, 0.98 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]], 998 | [ new Line( lineGeometry, matLineCyan ), [ 0, 0.98, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]] 999 | ], 1000 | XZ: [ 1001 | [ new Mesh( scaleHandleGeometry, matMagentaTransparent ), [ 0.85, 0, 0.85 ], null, [ 2, 0.2, 2 ]], 1002 | [ new Line( lineGeometry, matLineMagenta ), [ 0.855, 0, 0.98 ], null, [ 0.125, 1, 1 ]], 1003 | [ new Line( lineGeometry, matLineMagenta ), [ 0.98, 0, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]] 1004 | ], 1005 | XYZX: [ 1006 | [ new Mesh( new BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 1.1, 0, 0 ]], 1007 | ], 1008 | XYZY: [ 1009 | [ new Mesh( new BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 1.1, 0 ]], 1010 | ], 1011 | XYZZ: [ 1012 | [ new Mesh( new BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 0, 1.1 ]], 1013 | ] 1014 | }; 1015 | 1016 | var pickerScale = { 1017 | X: [ 1018 | [ new Mesh( new CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]] 1019 | ], 1020 | Y: [ 1021 | [ new Mesh( new CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0.5, 0 ]] 1022 | ], 1023 | Z: [ 1024 | [ new Mesh( new CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]] 1025 | ], 1026 | XY: [ 1027 | [ new Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0.85, 0 ], null, [ 3, 3, 0.2 ]], 1028 | ], 1029 | YZ: [ 1030 | [ new Mesh( scaleHandleGeometry, matInvisible ), [ 0, 0.85, 0.85 ], null, [ 0.2, 3, 3 ]], 1031 | ], 1032 | XZ: [ 1033 | [ new Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0, 0.85 ], null, [ 3, 0.2, 3 ]], 1034 | ], 1035 | XYZX: [ 1036 | [ new Mesh( new BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 1.1, 0, 0 ]], 1037 | ], 1038 | XYZY: [ 1039 | [ new Mesh( new BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 1.1, 0 ]], 1040 | ], 1041 | XYZZ: [ 1042 | [ new Mesh( new BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 1.1 ]], 1043 | ] 1044 | }; 1045 | 1046 | var helperScale = { 1047 | X: [ 1048 | [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] 1049 | ], 1050 | Y: [ 1051 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] 1052 | ], 1053 | Z: [ 1054 | [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] 1055 | ] 1056 | }; 1057 | 1058 | // Creates an Object3D with gizmos described in custom hierarchy definition. 1059 | 1060 | var setupGizmo = function ( gizmoMap ) { 1061 | 1062 | var gizmo = new Object3D(); 1063 | 1064 | for ( var name in gizmoMap ) { 1065 | 1066 | for ( var i = gizmoMap[ name ].length; i --; ) { 1067 | 1068 | var object = gizmoMap[ name ][ i ][ 0 ].clone(); 1069 | var position = gizmoMap[ name ][ i ][ 1 ]; 1070 | var rotation = gizmoMap[ name ][ i ][ 2 ]; 1071 | var scale = gizmoMap[ name ][ i ][ 3 ]; 1072 | var tag = gizmoMap[ name ][ i ][ 4 ]; 1073 | 1074 | // name and tag properties are essential for picking and updating logic. 1075 | object.name = name; 1076 | object.tag = tag; 1077 | 1078 | if ( position ) { 1079 | 1080 | object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] ); 1081 | 1082 | } 1083 | 1084 | if ( rotation ) { 1085 | 1086 | object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] ); 1087 | 1088 | } 1089 | 1090 | if ( scale ) { 1091 | 1092 | object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] ); 1093 | 1094 | } 1095 | 1096 | object.updateMatrix(); 1097 | 1098 | var tempGeometry = object.geometry.clone(); 1099 | tempGeometry.applyMatrix4( object.matrix ); 1100 | object.geometry = tempGeometry; 1101 | object.renderOrder = Infinity; 1102 | 1103 | object.position.set( 0, 0, 0 ); 1104 | object.rotation.set( 0, 0, 0 ); 1105 | object.scale.set( 1, 1, 1 ); 1106 | 1107 | gizmo.add( object ); 1108 | 1109 | } 1110 | 1111 | } 1112 | 1113 | return gizmo; 1114 | 1115 | }; 1116 | 1117 | // Reusable utility variables 1118 | 1119 | var tempVector = new Vector3( 0, 0, 0 ); 1120 | var tempEuler = new Euler(); 1121 | var alignVector = new Vector3( 0, 1, 0 ); 1122 | var zeroVector = new Vector3( 0, 0, 0 ); 1123 | var lookAtMatrix = new Matrix4(); 1124 | var tempQuaternion = new Quaternion(); 1125 | var tempQuaternion2 = new Quaternion(); 1126 | var identityQuaternion = new Quaternion(); 1127 | 1128 | var unitX = new Vector3( 1, 0, 0 ); 1129 | var unitY = new Vector3( 0, 1, 0 ); 1130 | var unitZ = new Vector3( 0, 0, 1 ); 1131 | 1132 | // Gizmo creation 1133 | 1134 | this.gizmo = {}; 1135 | this.picker = {}; 1136 | this.helper = {}; 1137 | 1138 | this.add( this.gizmo[ "translate" ] = setupGizmo( gizmoTranslate ) ); 1139 | this.add( this.gizmo[ "rotate" ] = setupGizmo( gizmoRotate ) ); 1140 | this.add( this.gizmo[ "scale" ] = setupGizmo( gizmoScale ) ); 1141 | this.add( this.picker[ "translate" ] = setupGizmo( pickerTranslate ) ); 1142 | this.add( this.picker[ "rotate" ] = setupGizmo( pickerRotate ) ); 1143 | this.add( this.picker[ "scale" ] = setupGizmo( pickerScale ) ); 1144 | this.add( this.helper[ "translate" ] = setupGizmo( helperTranslate ) ); 1145 | this.add( this.helper[ "rotate" ] = setupGizmo( helperRotate ) ); 1146 | this.add( this.helper[ "scale" ] = setupGizmo( helperScale ) ); 1147 | 1148 | // Pickers should be hidden always 1149 | 1150 | this.picker[ "translate" ].visible = false; 1151 | this.picker[ "rotate" ].visible = false; 1152 | this.picker[ "scale" ].visible = false; 1153 | 1154 | // updateMatrixWorld will update transformations and appearance of individual handles 1155 | 1156 | this.updateMatrixWorld = function () { 1157 | 1158 | var space = this.space; 1159 | 1160 | if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation 1161 | 1162 | var quaternion = space === "local" ? this.worldQuaternion : identityQuaternion; 1163 | 1164 | // Show only gizmos for current transform mode 1165 | 1166 | this.gizmo[ "translate" ].visible = this.mode === "translate"; 1167 | this.gizmo[ "rotate" ].visible = this.mode === "rotate"; 1168 | this.gizmo[ "scale" ].visible = this.mode === "scale"; 1169 | 1170 | this.helper[ "translate" ].visible = this.mode === "translate"; 1171 | this.helper[ "rotate" ].visible = this.mode === "rotate"; 1172 | this.helper[ "scale" ].visible = this.mode === "scale"; 1173 | 1174 | 1175 | var handles = []; 1176 | handles = handles.concat( this.picker[ this.mode ].children ); 1177 | handles = handles.concat( this.gizmo[ this.mode ].children ); 1178 | handles = handles.concat( this.helper[ this.mode ].children ); 1179 | 1180 | for ( var i = 0; i < handles.length; i ++ ) { 1181 | 1182 | var handle = handles[ i ]; 1183 | 1184 | // hide aligned to camera 1185 | 1186 | handle.visible = true; 1187 | handle.rotation.set( 0, 0, 0 ); 1188 | handle.position.copy( this.worldPosition ); 1189 | 1190 | var factor; 1191 | 1192 | if ( this.camera.isOrthographicCamera ) { 1193 | 1194 | factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom; 1195 | 1196 | } else { 1197 | 1198 | factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 ); 1199 | 1200 | } 1201 | 1202 | handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 ); 1203 | 1204 | // TODO: simplify helpers and consider decoupling from gizmo 1205 | 1206 | if ( handle.tag === 'helper' ) { 1207 | 1208 | handle.visible = false; 1209 | 1210 | if ( handle.name === 'AXIS' ) { 1211 | 1212 | handle.position.copy( this.worldPositionStart ); 1213 | handle.visible = !! this.axis; 1214 | 1215 | if ( this.axis === 'X' ) { 1216 | 1217 | tempQuaternion.setFromEuler( tempEuler.set( 0, 0, 0 ) ); 1218 | handle.quaternion.copy( quaternion ).multiply( tempQuaternion ); 1219 | 1220 | if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { 1221 | 1222 | handle.visible = false; 1223 | 1224 | } 1225 | 1226 | } 1227 | 1228 | if ( this.axis === 'Y' ) { 1229 | 1230 | tempQuaternion.setFromEuler( tempEuler.set( 0, 0, Math.PI / 2 ) ); 1231 | handle.quaternion.copy( quaternion ).multiply( tempQuaternion ); 1232 | 1233 | if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { 1234 | 1235 | handle.visible = false; 1236 | 1237 | } 1238 | 1239 | } 1240 | 1241 | if ( this.axis === 'Z' ) { 1242 | 1243 | tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) ); 1244 | handle.quaternion.copy( quaternion ).multiply( tempQuaternion ); 1245 | 1246 | if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { 1247 | 1248 | handle.visible = false; 1249 | 1250 | } 1251 | 1252 | } 1253 | 1254 | if ( this.axis === 'XYZE' ) { 1255 | 1256 | tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) ); 1257 | alignVector.copy( this.rotationAxis ); 1258 | handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( zeroVector, alignVector, unitY ) ); 1259 | handle.quaternion.multiply( tempQuaternion ); 1260 | handle.visible = this.dragging; 1261 | 1262 | } 1263 | 1264 | if ( this.axis === 'E' ) { 1265 | 1266 | handle.visible = false; 1267 | 1268 | } 1269 | 1270 | 1271 | } else if ( handle.name === 'START' ) { 1272 | 1273 | handle.position.copy( this.worldPositionStart ); 1274 | handle.visible = this.dragging; 1275 | 1276 | } else if ( handle.name === 'END' ) { 1277 | 1278 | handle.position.copy( this.worldPosition ); 1279 | handle.visible = this.dragging; 1280 | 1281 | } else if ( handle.name === 'DELTA' ) { 1282 | 1283 | handle.position.copy( this.worldPositionStart ); 1284 | handle.quaternion.copy( this.worldQuaternionStart ); 1285 | tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 ); 1286 | tempVector.applyQuaternion( this.worldQuaternionStart.clone().inverse() ); 1287 | handle.scale.copy( tempVector ); 1288 | handle.visible = this.dragging; 1289 | 1290 | } else { 1291 | 1292 | handle.quaternion.copy( quaternion ); 1293 | 1294 | if ( this.dragging ) { 1295 | 1296 | handle.position.copy( this.worldPositionStart ); 1297 | 1298 | } else { 1299 | 1300 | handle.position.copy( this.worldPosition ); 1301 | 1302 | } 1303 | 1304 | if ( this.axis ) { 1305 | 1306 | handle.visible = this.axis.search( handle.name ) !== - 1; 1307 | 1308 | } 1309 | 1310 | } 1311 | 1312 | // If updating helper, skip rest of the loop 1313 | continue; 1314 | 1315 | } 1316 | 1317 | // Align handles to current local or world rotation 1318 | 1319 | handle.quaternion.copy( quaternion ); 1320 | 1321 | if ( this.mode === 'translate' || this.mode === 'scale' ) { 1322 | 1323 | // Hide translate and scale axis facing the camera 1324 | 1325 | var AXIS_HIDE_TRESHOLD = 0.99; 1326 | var PLANE_HIDE_TRESHOLD = 0.2; 1327 | var AXIS_FLIP_TRESHOLD = 0.0; 1328 | 1329 | 1330 | if ( handle.name === 'X' || handle.name === 'XYZX' ) { 1331 | 1332 | if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { 1333 | 1334 | handle.scale.set( 1e-10, 1e-10, 1e-10 ); 1335 | handle.visible = false; 1336 | 1337 | } 1338 | 1339 | } 1340 | 1341 | if ( handle.name === 'Y' || handle.name === 'XYZY' ) { 1342 | 1343 | if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { 1344 | 1345 | handle.scale.set( 1e-10, 1e-10, 1e-10 ); 1346 | handle.visible = false; 1347 | 1348 | } 1349 | 1350 | } 1351 | 1352 | if ( handle.name === 'Z' || handle.name === 'XYZZ' ) { 1353 | 1354 | if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { 1355 | 1356 | handle.scale.set( 1e-10, 1e-10, 1e-10 ); 1357 | handle.visible = false; 1358 | 1359 | } 1360 | 1361 | } 1362 | 1363 | if ( handle.name === 'XY' ) { 1364 | 1365 | if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { 1366 | 1367 | handle.scale.set( 1e-10, 1e-10, 1e-10 ); 1368 | handle.visible = false; 1369 | 1370 | } 1371 | 1372 | } 1373 | 1374 | if ( handle.name === 'YZ' ) { 1375 | 1376 | if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { 1377 | 1378 | handle.scale.set( 1e-10, 1e-10, 1e-10 ); 1379 | handle.visible = false; 1380 | 1381 | } 1382 | 1383 | } 1384 | 1385 | if ( handle.name === 'XZ' ) { 1386 | 1387 | if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { 1388 | 1389 | handle.scale.set( 1e-10, 1e-10, 1e-10 ); 1390 | handle.visible = false; 1391 | 1392 | } 1393 | 1394 | } 1395 | 1396 | // Flip translate and scale axis ocluded behind another axis 1397 | 1398 | if ( handle.name.search( 'X' ) !== - 1 ) { 1399 | 1400 | if ( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) { 1401 | 1402 | if ( handle.tag === 'fwd' ) { 1403 | 1404 | handle.visible = false; 1405 | 1406 | } else { 1407 | 1408 | handle.scale.x *= - 1; 1409 | 1410 | } 1411 | 1412 | } else if ( handle.tag === 'bwd' ) { 1413 | 1414 | handle.visible = false; 1415 | 1416 | } 1417 | 1418 | } 1419 | 1420 | if ( handle.name.search( 'Y' ) !== - 1 ) { 1421 | 1422 | if ( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) { 1423 | 1424 | if ( handle.tag === 'fwd' ) { 1425 | 1426 | handle.visible = false; 1427 | 1428 | } else { 1429 | 1430 | handle.scale.y *= - 1; 1431 | 1432 | } 1433 | 1434 | } else if ( handle.tag === 'bwd' ) { 1435 | 1436 | handle.visible = false; 1437 | 1438 | } 1439 | 1440 | } 1441 | 1442 | if ( handle.name.search( 'Z' ) !== - 1 ) { 1443 | 1444 | if ( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) { 1445 | 1446 | if ( handle.tag === 'fwd' ) { 1447 | 1448 | handle.visible = false; 1449 | 1450 | } else { 1451 | 1452 | handle.scale.z *= - 1; 1453 | 1454 | } 1455 | 1456 | } else if ( handle.tag === 'bwd' ) { 1457 | 1458 | handle.visible = false; 1459 | 1460 | } 1461 | 1462 | } 1463 | 1464 | } else if ( this.mode === 'rotate' ) { 1465 | 1466 | // Align handles to current local or world rotation 1467 | 1468 | tempQuaternion2.copy( quaternion ); 1469 | alignVector.copy( this.eye ).applyQuaternion( tempQuaternion.copy( quaternion ).inverse() ); 1470 | 1471 | if ( handle.name.search( "E" ) !== - 1 ) { 1472 | 1473 | handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( this.eye, zeroVector, unitY ) ); 1474 | 1475 | } 1476 | 1477 | if ( handle.name === 'X' ) { 1478 | 1479 | tempQuaternion.setFromAxisAngle( unitX, Math.atan2( - alignVector.y, alignVector.z ) ); 1480 | tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion ); 1481 | handle.quaternion.copy( tempQuaternion ); 1482 | 1483 | } 1484 | 1485 | if ( handle.name === 'Y' ) { 1486 | 1487 | tempQuaternion.setFromAxisAngle( unitY, Math.atan2( alignVector.x, alignVector.z ) ); 1488 | tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion ); 1489 | handle.quaternion.copy( tempQuaternion ); 1490 | 1491 | } 1492 | 1493 | if ( handle.name === 'Z' ) { 1494 | 1495 | tempQuaternion.setFromAxisAngle( unitZ, Math.atan2( alignVector.y, alignVector.x ) ); 1496 | tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion ); 1497 | handle.quaternion.copy( tempQuaternion ); 1498 | 1499 | } 1500 | 1501 | } 1502 | 1503 | // Hide disabled axes 1504 | handle.visible = handle.visible && ( handle.name.indexOf( "X" ) === - 1 || this.showX ); 1505 | handle.visible = handle.visible && ( handle.name.indexOf( "Y" ) === - 1 || this.showY ); 1506 | handle.visible = handle.visible && ( handle.name.indexOf( "Z" ) === - 1 || this.showZ ); 1507 | handle.visible = handle.visible && ( handle.name.indexOf( "E" ) === - 1 || ( this.showX && this.showY && this.showZ ) ); 1508 | 1509 | // highlight selected axis 1510 | 1511 | handle.material._opacity = handle.material._opacity || handle.material.opacity; 1512 | handle.material._color = handle.material._color || handle.material.color.clone(); 1513 | 1514 | handle.material.color.copy( handle.material._color ); 1515 | handle.material.opacity = handle.material._opacity; 1516 | 1517 | if ( ! this.enabled ) { 1518 | 1519 | handle.material.opacity *= 0.5; 1520 | handle.material.color.lerp( new Color( 1, 1, 1 ), 0.5 ); 1521 | 1522 | } else if ( this.axis ) { 1523 | 1524 | if ( handle.name === this.axis ) { 1525 | 1526 | handle.material.opacity = 1.0; 1527 | handle.material.color.lerp( new Color( 1, 1, 1 ), 0.5 ); 1528 | 1529 | } else if ( this.axis.split( '' ).some( function ( a ) { 1530 | 1531 | return handle.name === a; 1532 | 1533 | } ) ) { 1534 | 1535 | handle.material.opacity = 1.0; 1536 | handle.material.color.lerp( new Color( 1, 1, 1 ), 0.5 ); 1537 | 1538 | } else { 1539 | 1540 | handle.material.opacity *= 0.25; 1541 | handle.material.color.lerp( new Color( 1, 1, 1 ), 0.5 ); 1542 | 1543 | } 1544 | 1545 | } 1546 | 1547 | } 1548 | 1549 | Object3D.prototype.updateMatrixWorld.call( this ); 1550 | 1551 | }; 1552 | 1553 | }; 1554 | 1555 | TransformControlsGizmo.prototype = Object.assign( Object.create( Object3D.prototype ), { 1556 | 1557 | constructor: TransformControlsGizmo, 1558 | 1559 | isTransformControlsGizmo: true 1560 | 1561 | } ); 1562 | 1563 | 1564 | var TransformControlsPlane = function () { 1565 | 1566 | 'use strict'; 1567 | 1568 | Mesh.call( this, 1569 | new PlaneBufferGeometry( 100000, 100000, 2, 2 ), 1570 | new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1 } ) 1571 | ); 1572 | 1573 | this.type = 'TransformControlsPlane'; 1574 | 1575 | var unitX = new Vector3( 1, 0, 0 ); 1576 | var unitY = new Vector3( 0, 1, 0 ); 1577 | var unitZ = new Vector3( 0, 0, 1 ); 1578 | 1579 | var tempVector = new Vector3(); 1580 | var dirVector = new Vector3(); 1581 | var alignVector = new Vector3(); 1582 | var tempMatrix = new Matrix4(); 1583 | var identityQuaternion = new Quaternion(); 1584 | 1585 | this.updateMatrixWorld = function () { 1586 | 1587 | var space = this.space; 1588 | 1589 | this.position.copy( this.worldPosition ); 1590 | 1591 | if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation 1592 | 1593 | unitX.set( 1, 0, 0 ).applyQuaternion( space === "local" ? this.worldQuaternion : identityQuaternion ); 1594 | unitY.set( 0, 1, 0 ).applyQuaternion( space === "local" ? this.worldQuaternion : identityQuaternion ); 1595 | unitZ.set( 0, 0, 1 ).applyQuaternion( space === "local" ? this.worldQuaternion : identityQuaternion ); 1596 | 1597 | // Align the plane for current transform mode, axis and space. 1598 | 1599 | alignVector.copy( unitY ); 1600 | 1601 | switch ( this.mode ) { 1602 | 1603 | case 'translate': 1604 | case 'scale': 1605 | switch ( this.axis ) { 1606 | 1607 | case 'X': 1608 | alignVector.copy( this.eye ).cross( unitX ); 1609 | dirVector.copy( unitX ).cross( alignVector ); 1610 | break; 1611 | case 'Y': 1612 | alignVector.copy( this.eye ).cross( unitY ); 1613 | dirVector.copy( unitY ).cross( alignVector ); 1614 | break; 1615 | case 'Z': 1616 | alignVector.copy( this.eye ).cross( unitZ ); 1617 | dirVector.copy( unitZ ).cross( alignVector ); 1618 | break; 1619 | case 'XY': 1620 | dirVector.copy( unitZ ); 1621 | break; 1622 | case 'YZ': 1623 | dirVector.copy( unitX ); 1624 | break; 1625 | case 'XZ': 1626 | alignVector.copy( unitZ ); 1627 | dirVector.copy( unitY ); 1628 | break; 1629 | case 'XYZ': 1630 | case 'E': 1631 | dirVector.set( 0, 0, 0 ); 1632 | break; 1633 | 1634 | } 1635 | 1636 | break; 1637 | case 'rotate': 1638 | default: 1639 | // special case for rotate 1640 | dirVector.set( 0, 0, 0 ); 1641 | 1642 | } 1643 | 1644 | if ( dirVector.length() === 0 ) { 1645 | 1646 | // If in rotate mode, make the plane parallel to camera 1647 | this.quaternion.copy( this.cameraQuaternion ); 1648 | 1649 | } else { 1650 | 1651 | tempMatrix.lookAt( tempVector.set( 0, 0, 0 ), dirVector, alignVector ); 1652 | 1653 | this.quaternion.setFromRotationMatrix( tempMatrix ); 1654 | 1655 | } 1656 | 1657 | Object3D.prototype.updateMatrixWorld.call( this ); 1658 | 1659 | }; 1660 | 1661 | }; 1662 | 1663 | TransformControlsPlane.prototype = Object.assign( Object.create( Mesh.prototype ), { 1664 | 1665 | constructor: TransformControlsPlane, 1666 | 1667 | isTransformControlsPlane: true 1668 | 1669 | } ); 1670 | 1671 | -------------------------------------------------------------------------------- /public/lib/htmltoimage.js: -------------------------------------------------------------------------------- 1 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).htmlToImage={})}(this,(function(t){"use strict"; 2 | /*! ***************************************************************************** 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | ***************************************************************************** */function n(t,n,e,r){return new(e||(e=Promise))((function(o,i){function u(t){try{f(r.next(t))}catch(t){i(t)}}function c(t){try{f(r.throw(t))}catch(t){i(t)}}function f(t){var n;t.done?o(t.value):(n=t.value,n instanceof e?n:new e((function(t){t(n)}))).then(u,c)}f((r=r.apply(t,n||[])).next())}))}function e(t,n){var e,r,o,i,u={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){return function(i){if(e)throw new TypeError("Generator is already executing.");for(;u;)try{if(e=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return u.label++,{value:i[1],done:!1};case 5:u.label++,r=i[1],i=[0];continue;case 7:i=u.ops.pop(),u.trys.pop();continue;default:if(!(o=u.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){u=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]O||t.height>O)&&(t.width>O&&t.height>O?t.width>t.height?(t.height*=O/t.width,t.width=O):(t.width*=O/t.height,t.height=O):t.width>O?(t.height*=O/t.width,t.width=O):(t.width*=O/t.height,t.height=O))}(e),e.style.width=""+a,e.style.height=""+s,r.backgroundColor&&(o.fillStyle=r.backgroundColor,o.fillRect(0,0,e.width,e.height)),o.drawImage(n,0,0,e.width,e.height),e}))]}))}))}t.getFontEmbedCSS=function(t,r){return void 0===r&&(r={}),n(this,void 0,void 0,(function(){return e(this,(function(n){return[2,j(t,r)]}))}))},t.toBlob=function(t,r){return void 0===r&&(r={}),n(this,void 0,void 0,(function(){return e(this,(function(n){return[2,$(t,r).then(d)]}))}))},t.toCanvas=$,t.toJpeg=function(t,r){return void 0===r&&(r={}),n(this,void 0,void 0,(function(){return e(this,(function(n){return[2,$(t,r).then((function(t){return t.toDataURL("image/jpeg",r.quality||1)}))]}))}))},t.toPixelData=function(t,r){return void 0===r&&(r={}),n(this,void 0,void 0,(function(){var n,o,i;return e(this,(function(e){return n=F(t,r),o=n.width,i=n.height,[2,$(t,r).then((function(t){return t.getContext("2d").getImageData(0,0,o,i).data}))]}))}))},t.toPng=function(t,r){return void 0===r&&(r={}),n(this,void 0,void 0,(function(){return e(this,(function(n){return[2,$(t,r).then((function(t){return t.toDataURL()}))]}))}))},t.toSvg=B,Object.defineProperty(t,"__esModule",{value:!0})})); 16 | //# sourceMappingURL=html-to-image.js.map -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | const express = require('express'); 3 | const app = express(); 4 | const http = require('http'); 5 | const server = http.createServer(app); 6 | var nodemailer = require('nodemailer'); 7 | var transporter = nodemailer.createTransport({ 8 | service: 'gmail', 9 | auth: { 10 | user: process.env.EMAIL, 11 | pass: process.env.PASSWORD 12 | } 13 | }); 14 | var email; 15 | app.use(express.static('public')); 16 | var io = require('socket.io')(server); 17 | io.sockets.on('connection', 18 | function (socket) { 19 | console.log("We have a new client: " + socket.id); 20 | socket.on('email', 21 | function(data) { 22 | var data=JSON.parse(data) 23 | email=data.email; 24 | var name=data.name 25 | var content=JSON.stringify(data.data).split(",")[1] 26 | var mailOptions = { 27 | from: 'greenlab.attestation.ziko.js@gmail.com', 28 | to: email, 29 | subject: 'Greenlab Certificate', 30 | html: '

Hello

Congratulations
You have successfully completed the course

', 31 | attachments: [{ 32 | filename: name+".pdf", 33 | contentType: 'application/pdf', 34 | content:content, 35 | encoding: 'base64', 36 | }] 37 | }; 38 | transporter.sendMail(mailOptions, function(error, info){ 39 | if (error) { 40 | console.log(error); 41 | } else { 42 | console.log('Email sent to : ' + email); 43 | socket.emit("succed",'Email sent to : ' + email +" on "+new Date().toDateString()+" at "+new Date().toTimeString()) 44 | } 45 | }); 46 | } 47 | ); 48 | }) 49 | 50 | 51 | server.listen(3000, () => { 52 | console.log('listening on *:3000'); 53 | }); 54 | -------------------------------------------------------------------------------- /video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zakarialaoui10/greenlab-challenge/83eedc50b5bf7940087867d894efa91c1d249b26/video.mp4 --------------------------------------------------------------------------------