├── .eslintrc.json ├── .gitignore ├── README.md ├── dist ├── bundle.js └── bundle.js.map ├── index.html ├── package-lock.json ├── package.json ├── preview.jpg ├── rollup.config.js ├── src ├── lib │ ├── PaintRenderer.js │ └── Projector.js └── main.js └── style.css /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "ecmaVersion": 2017 10 | }, 11 | "rules": { 12 | "no-console": "off", 13 | "accessor-pairs": [ 14 | "error", 15 | { 16 | "getWithoutSet": false, 17 | "setWithoutGet": false 18 | } 19 | ], 20 | "array-bracket-newline": "off", 21 | "array-bracket-spacing": [ 22 | "error", 23 | "never" 24 | ], 25 | "array-callback-return": "off", 26 | "array-element-newline": "off", 27 | "arrow-body-style": "error", 28 | "arrow-parens": [ 29 | "error", 30 | "as-needed" 31 | ], 32 | "arrow-spacing": "off", 33 | "block-scoped-var": "off", 34 | "block-spacing": "off", 35 | "brace-style": [ 36 | "error", 37 | "1tbs", 38 | { 39 | "allowSingleLine": true 40 | } 41 | ], 42 | "callback-return": "error", 43 | "camelcase": "error", 44 | "capitalized-comments": "off", 45 | "class-methods-use-this": "off", 46 | "comma-dangle": "error", 47 | "comma-spacing": "off", 48 | "comma-style": [ 49 | "error", 50 | "last" 51 | ], 52 | "complexity": "off", 53 | "computed-property-spacing": [ 54 | "error", 55 | "never" 56 | ], 57 | "consistent-return": "off", 58 | "consistent-this": "off", 59 | "curly": "off", 60 | "default-case": "off", 61 | "dot-location": [ 62 | "error", 63 | "property" 64 | ], 65 | "dot-notation": [ 66 | "error", 67 | { 68 | "allowKeywords": true 69 | } 70 | ], 71 | "eol-last": "off", 72 | "eqeqeq": "off", 73 | "for-direction": "error", 74 | "func-call-spacing": "error", 75 | "func-name-matching": "off", 76 | "func-names": "off", 77 | "func-style": "off", 78 | "function-paren-newline": "off", 79 | "generator-star-spacing": "off", 80 | "getter-return": "error", 81 | "global-require": "error", 82 | "guard-for-in": "off", 83 | "handle-callback-err": "error", 84 | "id-blacklist": "error", 85 | "id-length": "off", 86 | "id-match": "error", 87 | "implicit-arrow-linebreak": [ 88 | "error", 89 | "beside" 90 | ], 91 | "indent": "off", 92 | "indent-legacy": "off", 93 | "init-declarations": "off", 94 | "jsx-quotes": "error", 95 | "key-spacing": "off", 96 | "keyword-spacing": "off", 97 | "line-comment-position": "off", 98 | "linebreak-style": [ 99 | "error", 100 | "unix" 101 | ], 102 | "lines-around-comment": "off", 103 | "lines-around-directive": "off", 104 | "lines-between-class-members": [ 105 | "error", 106 | "always", 107 | { 108 | "exceptAfterSingleLine": true 109 | } 110 | ], 111 | "max-depth": "error", 112 | "max-len": "off", 113 | "max-lines": "off", 114 | "max-nested-callbacks": "error", 115 | "max-params": "off", 116 | "max-statements": "off", 117 | "max-statements-per-line": "off", 118 | "multiline-comment-style": [ 119 | "error", 120 | "separate-lines" 121 | ], 122 | "multiline-ternary": [ 123 | "error", 124 | "always-multiline" 125 | ], 126 | "new-parens": "error", 127 | "newline-after-var": "off", 128 | "newline-before-return": "off", 129 | "newline-per-chained-call": "error", 130 | "no-alert": "error", 131 | "no-array-constructor": "error", 132 | "no-await-in-loop": "error", 133 | "no-bitwise": "error", 134 | "no-buffer-constructor": "error", 135 | "no-caller": "error", 136 | "no-catch-shadow": "error", 137 | "no-cond-assign": [ 138 | "error", 139 | "except-parens" 140 | ], 141 | "no-confusing-arrow": "error", 142 | "no-continue": "off", 143 | "no-div-regex": "error", 144 | "no-duplicate-imports": "error", 145 | "no-else-return": "error", 146 | "no-empty-function": "off", 147 | "no-eq-null": "error", 148 | "no-eval": "error", 149 | "no-extend-native": "error", 150 | "no-extra-bind": "error", 151 | "no-extra-label": "error", 152 | "no-extra-parens": "off", 153 | "no-floating-decimal": "error", 154 | "no-implicit-coercion": [ 155 | "error", 156 | { 157 | "boolean": false, 158 | "number": false, 159 | "string": false 160 | } 161 | ], 162 | "no-implicit-globals": "error", 163 | "no-implied-eval": "error", 164 | "no-inline-comments": "off", 165 | "no-inner-declarations": [ 166 | "error", 167 | "functions" 168 | ], 169 | "no-invalid-this": "off", 170 | "no-iterator": "error", 171 | "no-label-var": "error", 172 | "no-labels": "error", 173 | "no-lone-blocks": "error", 174 | "no-lonely-if": "off", 175 | "no-loop-func": "error", 176 | "no-magic-numbers": "off", 177 | "no-mixed-operators": "off", 178 | "no-mixed-requires": "error", 179 | "no-multi-assign": "off", 180 | "no-multi-spaces": "error", 181 | "no-multi-str": "error", 182 | "no-multiple-empty-lines": "error", 183 | "no-native-reassign": "error", 184 | "no-negated-condition": "off", 185 | "no-negated-in-lhs": "error", 186 | "no-nested-ternary": "off", 187 | "no-new": "error", 188 | "no-new-func": "off", 189 | "no-new-object": "error", 190 | "no-new-require": "error", 191 | "no-new-wrappers": "error", 192 | "no-octal-escape": "error", 193 | "no-param-reassign": "off", 194 | "no-path-concat": "error", 195 | "no-plusplus": "off", 196 | "no-process-env": "error", 197 | "no-process-exit": "error", 198 | "no-proto": "error", 199 | "no-prototype-builtins": "error", 200 | "no-restricted-globals": "error", 201 | "no-restricted-imports": "error", 202 | "no-restricted-modules": "error", 203 | "no-restricted-properties": "error", 204 | "no-restricted-syntax": "error", 205 | "no-return-assign": "off", 206 | "no-return-await": "error", 207 | "no-script-url": "error", 208 | "no-self-compare": "error", 209 | "no-sequences": "off", 210 | "no-shadow": "off", 211 | "no-shadow-restricted-names": "error", 212 | "no-spaced-func": "error", 213 | "no-sync": "error", 214 | "no-tabs": "off", 215 | "no-template-curly-in-string": "error", 216 | "no-ternary": "off", 217 | "no-throw-literal": "error", 218 | "no-trailing-spaces": "off", 219 | "no-undef-init": "error", 220 | "no-undefined": "off", 221 | "no-underscore-dangle": "off", 222 | "no-unmodified-loop-condition": "error", 223 | "no-unneeded-ternary": "error", 224 | "no-unused-expressions": "off", 225 | "no-use-before-define": "off", 226 | "no-useless-call": "error", 227 | "no-useless-computed-key": "error", 228 | "no-useless-concat": "error", 229 | "no-useless-constructor": "off", 230 | "no-useless-rename": "error", 231 | "no-useless-return": "off", 232 | "no-var": "off", 233 | "no-void": "off", 234 | "no-warning-comments": "error", 235 | "no-whitespace-before-property": "error", 236 | "no-with": "error", 237 | "nonblock-statement-body-position": "error", 238 | "object-curly-newline": "off", 239 | "object-curly-spacing": "off", 240 | "object-shorthand": "off", 241 | "one-var": "off", 242 | "one-var-declaration-per-line": "off", 243 | "operator-assignment": "off", 244 | "operator-linebreak": "off", 245 | "padded-blocks": "off", 246 | "padding-line-between-statements": "error", 247 | "prefer-arrow-callback": "off", 248 | "prefer-const": "off", 249 | "prefer-destructuring": "off", 250 | "prefer-numeric-literals": "error", 251 | "prefer-promise-reject-errors": "error", 252 | "prefer-reflect": "off", 253 | "prefer-rest-params": "off", 254 | "prefer-spread": "error", 255 | "prefer-template": "off", 256 | "quote-props": "off", 257 | "quotes": "off", 258 | "radix": [ 259 | "error", 260 | "always" 261 | ], 262 | "require-await": "error", 263 | "require-jsdoc": "off", 264 | "rest-spread-spacing": [ 265 | "error", 266 | "never" 267 | ], 268 | "semi": "off", 269 | "semi-spacing": [ 270 | "error", 271 | { 272 | "after": true, 273 | "before": false 274 | } 275 | ], 276 | "semi-style": [ 277 | "error", 278 | "last" 279 | ], 280 | "sort-imports": "error", 281 | "sort-keys": "off", 282 | "sort-vars": "off", 283 | "space-before-blocks": "off", 284 | "space-before-function-paren": "off", 285 | "space-in-parens": [ 286 | "error", 287 | "never" 288 | ], 289 | "space-infix-ops": "off", 290 | "space-unary-ops": "error", 291 | "spaced-comment": "off", 292 | "strict": "off", 293 | "switch-colon-spacing": "error", 294 | "symbol-description": "error", 295 | "template-curly-spacing": [ 296 | "error", 297 | "never" 298 | ], 299 | "template-tag-spacing": [ 300 | "error", 301 | "never" 302 | ], 303 | "unicode-bom": [ 304 | "error", 305 | "never" 306 | ], 307 | "valid-jsdoc": "off", 308 | "vars-on-top": "off", 309 | "wrap-iife": "off", 310 | "wrap-regex": "off", 311 | "yield-star-spacing": "off", 312 | "yoda": "off" 313 | } 314 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using THREE.js in a Houdini Paint 2 | 3 | This is weird and terrible. 4 | 5 | We use a THREE.js Canvas Renderer in a paint worklet!! 6 | 7 | The worklet code is in [main.js](https://github.com/AdaRoseCannon/three-paint/blob/master/src/main.js) 8 | 9 | Use `npm run start` to build it. 10 | 11 | Use it in HTML like so: 12 | 13 | 14 | ```html 15 | 16 | 26 | 27 | 28 |
29 |

Three.js in a Paint Worklet Demo Source: Three Paint on Github. 30 |
© MIT License, Ada Rose Cannon 2018.

31 |
32 | 33 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-paint", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.0.0-beta.52", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.52.tgz", 10 | "integrity": "sha1-GSSDv6DR5GfBAVccIQKcy3SvKAE=", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "7.0.0-beta.52" 14 | } 15 | }, 16 | "@babel/highlight": { 17 | "version": "7.0.0-beta.52", 18 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.52.tgz", 19 | "integrity": "sha1-7ySTFDLwYVXnvDnNuKaze0oos9A=", 20 | "dev": true, 21 | "requires": { 22 | "chalk": "2.4.1", 23 | "esutils": "2.0.2", 24 | "js-tokens": "3.0.2" 25 | } 26 | }, 27 | "@types/estree": { 28 | "version": "0.0.39", 29 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 30 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", 31 | "dev": true 32 | }, 33 | "@types/node": { 34 | "version": "10.5.2", 35 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.2.tgz", 36 | "integrity": "sha512-m9zXmifkZsMHZBOyxZWilMwmTlpC8x5Ty360JKTiXvlXZfBWYpsg9ZZvP/Ye+iZUh+Q+MxDLjItVTWIsfwz+8Q==", 37 | "dev": true 38 | }, 39 | "ansi-styles": { 40 | "version": "3.2.1", 41 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 42 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 43 | "dev": true, 44 | "requires": { 45 | "color-convert": "1.9.2" 46 | } 47 | }, 48 | "arr-diff": { 49 | "version": "2.0.0", 50 | "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", 51 | "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", 52 | "requires": { 53 | "arr-flatten": "1.1.0" 54 | } 55 | }, 56 | "arr-flatten": { 57 | "version": "1.1.0", 58 | "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", 59 | "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" 60 | }, 61 | "array-unique": { 62 | "version": "0.2.1", 63 | "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", 64 | "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" 65 | }, 66 | "async": { 67 | "version": "1.5.2", 68 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 69 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 70 | }, 71 | "braces": { 72 | "version": "1.8.5", 73 | "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", 74 | "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", 75 | "requires": { 76 | "expand-range": "1.8.2", 77 | "preserve": "0.2.0", 78 | "repeat-element": "1.1.2" 79 | } 80 | }, 81 | "buffer-from": { 82 | "version": "1.1.0", 83 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", 84 | "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", 85 | "dev": true 86 | }, 87 | "builtin-modules": { 88 | "version": "2.0.0", 89 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-2.0.0.tgz", 90 | "integrity": "sha512-3U5kUA5VPsRUA3nofm/BXX7GVHKfxz0hOBAPxXrIvHzlDRkQVqEn6yi8QJegxl4LzOHLdvb7XF5dVawa/VVYBg==", 91 | "dev": true 92 | }, 93 | "chalk": { 94 | "version": "2.4.1", 95 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 96 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 97 | "dev": true, 98 | "requires": { 99 | "ansi-styles": "3.2.1", 100 | "escape-string-regexp": "1.0.5", 101 | "supports-color": "5.4.0" 102 | } 103 | }, 104 | "color-convert": { 105 | "version": "1.9.2", 106 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", 107 | "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", 108 | "dev": true, 109 | "requires": { 110 | "color-name": "1.1.1" 111 | } 112 | }, 113 | "color-name": { 114 | "version": "1.1.1", 115 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", 116 | "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", 117 | "dev": true 118 | }, 119 | "colors": { 120 | "version": "1.0.3", 121 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 122 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 123 | }, 124 | "commander": { 125 | "version": "2.16.0", 126 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", 127 | "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", 128 | "dev": true 129 | }, 130 | "corser": { 131 | "version": "2.0.1", 132 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 133 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=" 134 | }, 135 | "debug": { 136 | "version": "3.1.0", 137 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 138 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 139 | "requires": { 140 | "ms": "2.0.0" 141 | } 142 | }, 143 | "ecstatic": { 144 | "version": "3.2.1", 145 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.2.1.tgz", 146 | "integrity": "sha512-BAdHx9LOCG1fwxY8MIydUBskl8UUQrYeC3WE14FA1DPlBzqoG1aOgEkypcSpmiiel8RAj8gW1s40RrclfrpGUg==", 147 | "requires": { 148 | "he": "1.1.1", 149 | "mime": "1.6.0", 150 | "minimist": "1.2.0", 151 | "url-join": "2.0.5" 152 | } 153 | }, 154 | "escape-string-regexp": { 155 | "version": "1.0.5", 156 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 157 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 158 | "dev": true 159 | }, 160 | "estree-walker": { 161 | "version": "0.5.2", 162 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", 163 | "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==" 164 | }, 165 | "esutils": { 166 | "version": "2.0.2", 167 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 168 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 169 | "dev": true 170 | }, 171 | "eventemitter3": { 172 | "version": "3.1.0", 173 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", 174 | "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==" 175 | }, 176 | "expand-brackets": { 177 | "version": "0.1.5", 178 | "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", 179 | "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", 180 | "requires": { 181 | "is-posix-bracket": "0.1.1" 182 | } 183 | }, 184 | "expand-range": { 185 | "version": "1.8.2", 186 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", 187 | "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", 188 | "requires": { 189 | "fill-range": "2.2.4" 190 | } 191 | }, 192 | "extglob": { 193 | "version": "0.3.2", 194 | "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", 195 | "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", 196 | "requires": { 197 | "is-extglob": "1.0.0" 198 | } 199 | }, 200 | "filename-regex": { 201 | "version": "2.0.1", 202 | "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", 203 | "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" 204 | }, 205 | "fill-range": { 206 | "version": "2.2.4", 207 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", 208 | "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", 209 | "requires": { 210 | "is-number": "2.1.0", 211 | "isobject": "2.1.0", 212 | "randomatic": "3.0.0", 213 | "repeat-element": "1.1.2", 214 | "repeat-string": "1.6.1" 215 | } 216 | }, 217 | "follow-redirects": { 218 | "version": "1.5.1", 219 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", 220 | "integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==", 221 | "requires": { 222 | "debug": "3.1.0" 223 | } 224 | }, 225 | "for-in": { 226 | "version": "1.0.2", 227 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 228 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" 229 | }, 230 | "for-own": { 231 | "version": "0.1.5", 232 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", 233 | "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", 234 | "requires": { 235 | "for-in": "1.0.2" 236 | } 237 | }, 238 | "glob-base": { 239 | "version": "0.3.0", 240 | "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", 241 | "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", 242 | "requires": { 243 | "glob-parent": "2.0.0", 244 | "is-glob": "2.0.1" 245 | } 246 | }, 247 | "glob-parent": { 248 | "version": "2.0.0", 249 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", 250 | "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", 251 | "requires": { 252 | "is-glob": "2.0.1" 253 | } 254 | }, 255 | "has-flag": { 256 | "version": "3.0.0", 257 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 258 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 259 | "dev": true 260 | }, 261 | "he": { 262 | "version": "1.1.1", 263 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 264 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" 265 | }, 266 | "http-proxy": { 267 | "version": "1.17.0", 268 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", 269 | "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", 270 | "requires": { 271 | "eventemitter3": "3.1.0", 272 | "follow-redirects": "1.5.1", 273 | "requires-port": "1.0.0" 274 | } 275 | }, 276 | "http-server": { 277 | "version": "0.11.1", 278 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.11.1.tgz", 279 | "integrity": "sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w==", 280 | "requires": { 281 | "colors": "1.0.3", 282 | "corser": "2.0.1", 283 | "ecstatic": "3.2.1", 284 | "http-proxy": "1.17.0", 285 | "opener": "1.4.3", 286 | "optimist": "0.6.1", 287 | "portfinder": "1.0.13", 288 | "union": "0.4.6" 289 | } 290 | }, 291 | "is-buffer": { 292 | "version": "1.1.6", 293 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 294 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 295 | }, 296 | "is-dotfile": { 297 | "version": "1.0.3", 298 | "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", 299 | "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" 300 | }, 301 | "is-equal-shallow": { 302 | "version": "0.1.3", 303 | "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", 304 | "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", 305 | "requires": { 306 | "is-primitive": "2.0.0" 307 | } 308 | }, 309 | "is-extendable": { 310 | "version": "0.1.1", 311 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 312 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" 313 | }, 314 | "is-extglob": { 315 | "version": "1.0.0", 316 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", 317 | "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" 318 | }, 319 | "is-glob": { 320 | "version": "2.0.1", 321 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", 322 | "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", 323 | "requires": { 324 | "is-extglob": "1.0.0" 325 | } 326 | }, 327 | "is-module": { 328 | "version": "1.0.0", 329 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 330 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 331 | "dev": true 332 | }, 333 | "is-number": { 334 | "version": "2.1.0", 335 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", 336 | "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", 337 | "requires": { 338 | "kind-of": "3.2.2" 339 | } 340 | }, 341 | "is-posix-bracket": { 342 | "version": "0.1.1", 343 | "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", 344 | "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" 345 | }, 346 | "is-primitive": { 347 | "version": "2.0.0", 348 | "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", 349 | "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" 350 | }, 351 | "isarray": { 352 | "version": "1.0.0", 353 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 354 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 355 | }, 356 | "isobject": { 357 | "version": "2.1.0", 358 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", 359 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", 360 | "requires": { 361 | "isarray": "1.0.0" 362 | } 363 | }, 364 | "js-tokens": { 365 | "version": "3.0.2", 366 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 367 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 368 | "dev": true 369 | }, 370 | "kind-of": { 371 | "version": "3.2.2", 372 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 373 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 374 | "requires": { 375 | "is-buffer": "1.1.6" 376 | } 377 | }, 378 | "math-random": { 379 | "version": "1.0.1", 380 | "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", 381 | "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=" 382 | }, 383 | "micromatch": { 384 | "version": "2.3.11", 385 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", 386 | "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", 387 | "requires": { 388 | "arr-diff": "2.0.0", 389 | "array-unique": "0.2.1", 390 | "braces": "1.8.5", 391 | "expand-brackets": "0.1.5", 392 | "extglob": "0.3.2", 393 | "filename-regex": "2.0.1", 394 | "is-extglob": "1.0.0", 395 | "is-glob": "2.0.1", 396 | "kind-of": "3.2.2", 397 | "normalize-path": "2.1.1", 398 | "object.omit": "2.0.1", 399 | "parse-glob": "3.0.4", 400 | "regex-cache": "0.4.4" 401 | } 402 | }, 403 | "mime": { 404 | "version": "1.6.0", 405 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 406 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 407 | }, 408 | "minimist": { 409 | "version": "1.2.0", 410 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 411 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 412 | }, 413 | "mkdirp": { 414 | "version": "0.5.1", 415 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 416 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 417 | "requires": { 418 | "minimist": "0.0.8" 419 | }, 420 | "dependencies": { 421 | "minimist": { 422 | "version": "0.0.8", 423 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 424 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 425 | } 426 | } 427 | }, 428 | "ms": { 429 | "version": "2.0.0", 430 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 431 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 432 | }, 433 | "normalize-path": { 434 | "version": "2.1.1", 435 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", 436 | "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", 437 | "requires": { 438 | "remove-trailing-separator": "1.1.0" 439 | } 440 | }, 441 | "object.omit": { 442 | "version": "2.0.1", 443 | "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", 444 | "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", 445 | "requires": { 446 | "for-own": "0.1.5", 447 | "is-extendable": "0.1.1" 448 | } 449 | }, 450 | "opener": { 451 | "version": "1.4.3", 452 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", 453 | "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=" 454 | }, 455 | "optimist": { 456 | "version": "0.6.1", 457 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 458 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 459 | "requires": { 460 | "minimist": "0.0.10", 461 | "wordwrap": "0.0.3" 462 | }, 463 | "dependencies": { 464 | "minimist": { 465 | "version": "0.0.10", 466 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 467 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" 468 | } 469 | } 470 | }, 471 | "parse-glob": { 472 | "version": "3.0.4", 473 | "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", 474 | "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", 475 | "requires": { 476 | "glob-base": "0.3.0", 477 | "is-dotfile": "1.0.3", 478 | "is-extglob": "1.0.0", 479 | "is-glob": "2.0.1" 480 | } 481 | }, 482 | "path-parse": { 483 | "version": "1.0.5", 484 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 485 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", 486 | "dev": true 487 | }, 488 | "portfinder": { 489 | "version": "1.0.13", 490 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", 491 | "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", 492 | "requires": { 493 | "async": "1.5.2", 494 | "debug": "2.6.9", 495 | "mkdirp": "0.5.1" 496 | }, 497 | "dependencies": { 498 | "debug": { 499 | "version": "2.6.9", 500 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 501 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 502 | "requires": { 503 | "ms": "2.0.0" 504 | } 505 | } 506 | } 507 | }, 508 | "preserve": { 509 | "version": "0.2.0", 510 | "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", 511 | "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" 512 | }, 513 | "qs": { 514 | "version": "2.3.3", 515 | "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", 516 | "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=" 517 | }, 518 | "randomatic": { 519 | "version": "3.0.0", 520 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", 521 | "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", 522 | "requires": { 523 | "is-number": "4.0.0", 524 | "kind-of": "6.0.2", 525 | "math-random": "1.0.1" 526 | }, 527 | "dependencies": { 528 | "is-number": { 529 | "version": "4.0.0", 530 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", 531 | "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" 532 | }, 533 | "kind-of": { 534 | "version": "6.0.2", 535 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", 536 | "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" 537 | } 538 | } 539 | }, 540 | "regex-cache": { 541 | "version": "0.4.4", 542 | "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", 543 | "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", 544 | "requires": { 545 | "is-equal-shallow": "0.1.3" 546 | } 547 | }, 548 | "remove-trailing-separator": { 549 | "version": "1.1.0", 550 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", 551 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" 552 | }, 553 | "repeat-element": { 554 | "version": "1.1.2", 555 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", 556 | "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" 557 | }, 558 | "repeat-string": { 559 | "version": "1.6.1", 560 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 561 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" 562 | }, 563 | "requires-port": { 564 | "version": "1.0.0", 565 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 566 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 567 | }, 568 | "resolve": { 569 | "version": "1.8.1", 570 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", 571 | "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", 572 | "dev": true, 573 | "requires": { 574 | "path-parse": "1.0.5" 575 | } 576 | }, 577 | "rollup": { 578 | "version": "0.62.0", 579 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.62.0.tgz", 580 | "integrity": "sha512-mZS0aIGfYzuJySJD78znu9/hCJsNfBzg4lDuZGMj0hFVcYHt2evNRHv8aqiu9/w6z6Qn8AQoVl4iyEjDmisGeA==", 581 | "dev": true, 582 | "requires": { 583 | "@types/estree": "0.0.39", 584 | "@types/node": "10.5.2" 585 | } 586 | }, 587 | "rollup-plugin-json": { 588 | "version": "3.0.0", 589 | "resolved": "https://registry.npmjs.org/rollup-plugin-json/-/rollup-plugin-json-3.0.0.tgz", 590 | "integrity": "sha512-WUAV9/I/uFWvHhyRTqFb+3SIapjISFJS7R1xN/cXxWESrfYo9I8ncHI7AxJHflKRXhBVSv7revBVJh2wvhWh5w==", 591 | "requires": { 592 | "rollup-pluginutils": "2.3.0" 593 | } 594 | }, 595 | "rollup-plugin-node-resolve": { 596 | "version": "3.3.0", 597 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.3.0.tgz", 598 | "integrity": "sha512-9zHGr3oUJq6G+X0oRMYlzid9fXicBdiydhwGChdyeNRGPcN/majtegApRKHLR5drboUvEWU+QeUmGTyEZQs3WA==", 599 | "dev": true, 600 | "requires": { 601 | "builtin-modules": "2.0.0", 602 | "is-module": "1.0.0", 603 | "resolve": "1.8.1" 604 | } 605 | }, 606 | "rollup-plugin-terser": { 607 | "version": "1.0.1", 608 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-1.0.1.tgz", 609 | "integrity": "sha512-VC6chT7QnrV6JzdgkPE0hP/atRBxaa3CPbVXfZJ8nJLjcidSdWftOst098RasYRUTKxJWAgdaJN1+uiZM6iffA==", 610 | "dev": true, 611 | "requires": { 612 | "@babel/code-frame": "7.0.0-beta.52", 613 | "terser": "3.7.8" 614 | } 615 | }, 616 | "rollup-pluginutils": { 617 | "version": "2.3.0", 618 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.0.tgz", 619 | "integrity": "sha512-xB6hsRsjdJdIYWEyYUJy/3ki5g69wrf0luHPGNK3ZSocV6HLNfio59l3dZ3TL4xUwEKgROhFi9jOCt6c5gfUWw==", 620 | "requires": { 621 | "estree-walker": "0.5.2", 622 | "micromatch": "2.3.11" 623 | } 624 | }, 625 | "source-map": { 626 | "version": "0.6.1", 627 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 628 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 629 | "dev": true 630 | }, 631 | "source-map-support": { 632 | "version": "0.5.6", 633 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", 634 | "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", 635 | "dev": true, 636 | "requires": { 637 | "buffer-from": "1.1.0", 638 | "source-map": "0.6.1" 639 | } 640 | }, 641 | "supports-color": { 642 | "version": "5.4.0", 643 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 644 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 645 | "dev": true, 646 | "requires": { 647 | "has-flag": "3.0.0" 648 | } 649 | }, 650 | "terser": { 651 | "version": "3.7.8", 652 | "resolved": "https://registry.npmjs.org/terser/-/terser-3.7.8.tgz", 653 | "integrity": "sha512-mRSo2+e4cZvPl6z2//Cd6DXTURb0ulRU2jZAboyz6hP0+nyp0XrqER6sBam1/7NBZUeD1K1eoz75v3I0lIwmeA==", 654 | "dev": true, 655 | "requires": { 656 | "commander": "2.16.0", 657 | "source-map": "0.6.1", 658 | "source-map-support": "0.5.6" 659 | } 660 | }, 661 | "three": { 662 | "version": "0.92.0", 663 | "resolved": "https://registry.npmjs.org/three/-/three-0.92.0.tgz", 664 | "integrity": "sha1-jT0fWviQ5i2n9MtF0gwJ+lEFfc0=", 665 | "dev": true 666 | }, 667 | "union": { 668 | "version": "0.4.6", 669 | "resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz", 670 | "integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=", 671 | "requires": { 672 | "qs": "2.3.3" 673 | } 674 | }, 675 | "url-join": { 676 | "version": "2.0.5", 677 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 678 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" 679 | }, 680 | "wordwrap": { 681 | "version": "0.0.3", 682 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 683 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" 684 | } 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-paint", 3 | "version": "1.0.0", 4 | "description": "THREE.js in Paint Worklet", 5 | "directories": { 6 | "lib": "lib" 7 | }, 8 | "scripts": { 9 | "start": "rollup -c -i src/main.js -o dist/bundle.js; http-server", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Ada Rose Cannon", 13 | "license": "MIT", 14 | "dependencies": { 15 | "http-server": "^0.11.1", 16 | "rollup-plugin-json": "^3.0.0" 17 | }, 18 | "devDependencies": { 19 | "rollup-plugin-terser": "^1.0.1", 20 | "rollup": "^0.62.0", 21 | "rollup-plugin-node-resolve": "^3.3.0", 22 | "three": "^0.92.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdaRoseCannon/three-paint/369c6bcd35ab98fabbcc723297002c2c7bfaf6e2/preview.jpg -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import "rollup"; /* eslint no-unused-vars: 0*/ 3 | import resolve from "rollup-plugin-node-resolve"; 4 | import json from 'rollup-plugin-json'; 5 | import { terser } from "rollup-plugin-terser"; 6 | 7 | export default { 8 | output: { 9 | format: "iife" 10 | }, 11 | sourcemap: "public/", 12 | intro: ` 13 | /* eslint-disable */ 14 | `, 15 | plugins: [ 16 | resolve({ 17 | module: true, // Default: true 18 | jsnext: true, // Default: false 19 | main: true, // Default: true 20 | browser: true, 21 | extensions: [".js"] // Default: ['.js'] 22 | }), 23 | terser(), // Code minification, 24 | json({ 25 | include: 'node_modules/**', 26 | 27 | // for tree-shaking, properties will be declared as 28 | // variables, using either `var` or `const` 29 | preferConst: true, // Default: false 30 | indent: ' ' 31 | }) 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /src/lib/PaintRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | import { 6 | AdditiveBlending, 7 | Box2, 8 | Color, 9 | CompressedTexture, 10 | DataTexture, 11 | FaceColors, 12 | Material, 13 | Matrix3, 14 | MirroredRepeatWrapping, 15 | MultiplyBlending, 16 | NormalBlending, 17 | REVISION, 18 | RepeatWrapping, 19 | SphericalReflectionMapping, 20 | SubtractiveBlending, 21 | UVMapping, 22 | Vector3, 23 | VertexColors 24 | } from "three"; 25 | 26 | import { 27 | Projector, 28 | RenderableFace, 29 | RenderableLine, 30 | RenderableSprite 31 | } from './Projector.js' 32 | 33 | function SpriteCanvasMaterial (parameters) { 34 | Material.call(this); 35 | this.type = "SpriteCanvasMaterial"; 36 | this.color = new Color(0xffffff); 37 | this.program = function() {}; 38 | this.setValues(parameters); 39 | } 40 | 41 | SpriteCanvasMaterial.prototype = Object.create(Material.prototype); 42 | SpriteCanvasMaterial.prototype.constructor = SpriteCanvasMaterial; 43 | SpriteCanvasMaterial.prototype.clone = function () { 44 | var material = new SpriteCanvasMaterial(); 45 | material.copy(this); 46 | material.color.copy(this.color); 47 | material.program = this.program; 48 | return material; 49 | } 50 | 51 | class PaintRenderer { 52 | constructor(parameters) { 53 | console.log("PaintRenderer", REVISION); 54 | parameters = parameters || {}; 55 | var _this = this, 56 | _context, 57 | _renderData, 58 | _elements, 59 | _lights, 60 | _projector = new Projector(), 61 | _canvasWidth = parameters.size.width, 62 | _canvasHeight = parameters.size.height, 63 | _canvasWidthHalf = Math.floor(_canvasWidth / 2), 64 | _canvasHeightHalf = Math.floor(_canvasHeight / 2), 65 | _viewportX = 0, 66 | _viewportY = 0, 67 | _viewportWidth = _canvasWidth, 68 | _viewportHeight = _canvasHeight, 69 | _pixelRatio = 1, 70 | _clearColor = new Color(0x000000), 71 | _clearAlpha = parameters.alpha === true ? 0 : 1, 72 | _contextGlobalAlpha = 1, 73 | _contextGlobalCompositeOperation = 0, 74 | _contextStrokeStyle = null, 75 | _contextFillStyle = null, 76 | _contextLineWidth = null, 77 | _contextLineCap = null, 78 | _contextLineJoin = null, 79 | _contextLineDash = [], 80 | _v1, 81 | _v2, 82 | _v3, 83 | _v1x, 84 | _v1y, 85 | _v2x, 86 | _v2y, 87 | _v3x, 88 | _v3y, 89 | _color = new Color(), 90 | _diffuseColor = new Color(), 91 | _emissiveColor = new Color(), 92 | _lightColor = new Color(), 93 | _patterns = {}, 94 | _uvs, 95 | _uv1x, 96 | _uv1y, 97 | _uv2x, 98 | _uv2y, 99 | _uv3x, 100 | _uv3y, 101 | _clipBox = new Box2(), 102 | _clearBox = new Box2(), 103 | _elemBox = new Box2(), 104 | _ambientLight = new Color(), 105 | _directionalLights = new Color(), 106 | _pointLights = new Color(), 107 | _vector3 = new Vector3(), // Needed for PointLight 108 | _centroid = new Vector3(), 109 | _normal = new Vector3(), 110 | _normalViewMatrix = new Matrix3(); 111 | 112 | // TODO 113 | // _canvas.mozImageSmoothingEnabled = false; 114 | // _canvas.webkitImageSmoothingEnabled = false; 115 | // _canvas.msImageSmoothingEnabled = false; 116 | // _canvas.imageSmoothingEnabled = false; 117 | // dash+gap fallbacks for Firefox and everything else 118 | this.autoClear = true; 119 | this.sortObjects = true; 120 | this.sortElements = true; 121 | this.info = { 122 | render: { 123 | vertices: 0, 124 | faces: 0 125 | } 126 | }; 127 | this.getPixelRatio = function() { 128 | return _pixelRatio; 129 | }; 130 | this.setPixelRatio = function(value) { 131 | if (value !== undefined) _pixelRatio = value; 132 | }; 133 | this.setContext = function(ctx) { 134 | _context = ctx; 135 | } 136 | this.setSize = function(width, height) { 137 | _canvasWidth = width * _pixelRatio; 138 | _canvasHeight = height * _pixelRatio; 139 | _canvasWidthHalf = Math.floor(_canvasWidth / 2); 140 | _canvasHeightHalf = Math.floor(_canvasHeight / 2); 141 | _clipBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf); 142 | _clipBox.max.set(_canvasWidthHalf, _canvasHeightHalf); 143 | _clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf); 144 | _clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf); 145 | _contextGlobalAlpha = 1; 146 | _contextGlobalCompositeOperation = 0; 147 | _contextStrokeStyle = null; 148 | _contextFillStyle = null; 149 | _contextLineWidth = null; 150 | _contextLineCap = null; 151 | _contextLineJoin = null; 152 | this.setViewport(0, 0, width, height); 153 | }; 154 | this.setViewport = function(x, y, width, height) { 155 | _viewportX = x * _pixelRatio; 156 | _viewportY = y * _pixelRatio; 157 | _viewportWidth = width * _pixelRatio; 158 | _viewportHeight = height * _pixelRatio; 159 | }; 160 | this.setScissor = function() {}; 161 | this.setScissorTest = function() {}; 162 | this.setClearColor = function(color, alpha) { 163 | _clearColor.set(color); 164 | _clearAlpha = alpha !== undefined ? alpha : 1; 165 | _clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf); 166 | _clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf); 167 | }; 168 | this.setClearColorHex = function(hex, alpha) { 169 | console.warn( 170 | "PaintRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead." 171 | ); 172 | this.setClearColor(hex, alpha); 173 | }; 174 | this.getClearColor = function() { 175 | return _clearColor; 176 | }; 177 | this.getClearAlpha = function() { 178 | return _clearAlpha; 179 | }; 180 | this.getMaxAnisotropy = function() { 181 | return 0; 182 | }; 183 | this.clear = function() { 184 | if (_clearBox.isEmpty() === false) { 185 | _clearBox.intersect(_clipBox); 186 | _clearBox.expandByScalar(2); 187 | _clearBox.min.x = _clearBox.min.x + _canvasWidthHalf; 188 | _clearBox.min.y = -_clearBox.min.y + _canvasHeightHalf; // higher y value ! 189 | _clearBox.max.x = _clearBox.max.x + _canvasWidthHalf; 190 | _clearBox.max.y = -_clearBox.max.y + _canvasHeightHalf; // lower y value ! 191 | if (_clearAlpha < 1) { 192 | _context.clearRect( 193 | _clearBox.min.x | 0, 194 | _clearBox.max.y | 0, 195 | (_clearBox.max.x - _clearBox.min.x) | 0, 196 | (_clearBox.min.y - _clearBox.max.y) | 0 197 | ); 198 | } 199 | if (_clearAlpha > 0) { 200 | setOpacity(1); 201 | setBlending(NormalBlending); 202 | setFillStyle( 203 | "rgba(" + 204 | Math.floor(_clearColor.r * 255) + 205 | "," + 206 | Math.floor(_clearColor.g * 255) + 207 | "," + 208 | Math.floor(_clearColor.b * 255) + 209 | "," + 210 | _clearAlpha + 211 | ")" 212 | ); 213 | _context.fillRect( 214 | _clearBox.min.x | 0, 215 | _clearBox.max.y | 0, 216 | (_clearBox.max.x - _clearBox.min.x) | 0, 217 | (_clearBox.min.y - _clearBox.max.y) | 0 218 | ); 219 | } 220 | _clearBox.makeEmpty(); 221 | } 222 | }; 223 | // compatibility 224 | this.clearColor = function() {}; 225 | this.clearDepth = function() {}; 226 | this.clearStencil = function() {}; 227 | this.render = function(scene, camera) { 228 | if (camera.isCamera === undefined) { 229 | console.error( 230 | "PaintRenderer.render: camera is not an instance of Camera." 231 | ); 232 | return; 233 | } 234 | var background = scene.background; 235 | if (background && background.isColor) { 236 | setOpacity(1); 237 | setBlending(NormalBlending); 238 | setFillStyle(background.getStyle()); 239 | _context.fillRect(0, 0, _canvasWidth, _canvasHeight); 240 | } else if (this.autoClear === true) { 241 | this.clear(); 242 | } 243 | _this.info.render.vertices = 0; 244 | _this.info.render.faces = 0; 245 | _context.setTransform( 246 | _viewportWidth / _canvasWidth, 247 | 0, 248 | 0, 249 | -_viewportHeight / _canvasHeight, 250 | _viewportX, 251 | _canvasHeight - _viewportY 252 | ); 253 | _context.translate(_canvasWidthHalf, _canvasHeightHalf); 254 | _renderData = _projector.projectScene( 255 | scene, 256 | camera, 257 | this.sortObjects, 258 | this.sortElements 259 | ); 260 | _elements = _renderData.elements; 261 | _lights = _renderData.lights; 262 | _normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse); 263 | // DEBUG 264 | // setFillStyle( 'rgba( 0, 255, 255, 0.5 )' ); 265 | // _context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y ); 266 | calculateLights(); 267 | for (var e = 0, el = _elements.length; e < el; e++) { 268 | var element = _elements[e]; 269 | var material = element.material; 270 | if (material === undefined || material.opacity === 0) continue; 271 | _elemBox.makeEmpty(); 272 | if (element instanceof RenderableSprite) { 273 | _v1 = element; 274 | _v1.x *= _canvasWidthHalf; 275 | _v1.y *= _canvasHeightHalf; 276 | renderSprite(_v1, element, material); 277 | } else if (element instanceof RenderableLine) { 278 | _v1 = element.v1; 279 | _v2 = element.v2; 280 | _v1.positionScreen.x *= _canvasWidthHalf; 281 | _v1.positionScreen.y *= _canvasHeightHalf; 282 | _v2.positionScreen.x *= _canvasWidthHalf; 283 | _v2.positionScreen.y *= _canvasHeightHalf; 284 | _elemBox.setFromPoints([_v1.positionScreen, _v2.positionScreen]); 285 | if (_clipBox.intersectsBox(_elemBox) === true) { 286 | renderLine(_v1, _v2, element, material); 287 | } 288 | } else if (element instanceof RenderableFace) { 289 | _v1 = element.v1; 290 | _v2 = element.v2; 291 | _v3 = element.v3; 292 | if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1) continue; 293 | if (_v2.positionScreen.z < -1 || _v2.positionScreen.z > 1) continue; 294 | if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1) continue; 295 | _v1.positionScreen.x *= _canvasWidthHalf; 296 | _v1.positionScreen.y *= _canvasHeightHalf; 297 | _v2.positionScreen.x *= _canvasWidthHalf; 298 | _v2.positionScreen.y *= _canvasHeightHalf; 299 | _v3.positionScreen.x *= _canvasWidthHalf; 300 | _v3.positionScreen.y *= _canvasHeightHalf; 301 | if (material.overdraw > 0) { 302 | expand(_v1.positionScreen, _v2.positionScreen, material.overdraw); 303 | expand(_v2.positionScreen, _v3.positionScreen, material.overdraw); 304 | expand(_v3.positionScreen, _v1.positionScreen, material.overdraw); 305 | } 306 | _elemBox.setFromPoints([ 307 | _v1.positionScreen, 308 | _v2.positionScreen, 309 | _v3.positionScreen 310 | ]); 311 | if (_clipBox.intersectsBox(_elemBox) === true) { 312 | renderFace3(_v1, _v2, _v3, 0, 1, 2, element, material); 313 | } 314 | } 315 | // DEBUG 316 | // setLineWidth( 1 ); 317 | // setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' ); 318 | // _context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y ); 319 | _clearBox.union(_elemBox); 320 | } 321 | // DEBUG 322 | // setLineWidth( 1 ); 323 | // setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' ); 324 | // _context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y ); 325 | _context.setTransform(1, 0, 0, 1, 0, 0); 326 | }; 327 | // 328 | function calculateLights() { 329 | _ambientLight.setRGB(0, 0, 0); 330 | _directionalLights.setRGB(0, 0, 0); 331 | _pointLights.setRGB(0, 0, 0); 332 | for (var l = 0, ll = _lights.length; l < ll; l++) { 333 | var light = _lights[l]; 334 | var lightColor = light.color; 335 | if (light.isAmbientLight) { 336 | _ambientLight.add(lightColor); 337 | } else if (light.isDirectionalLight) { 338 | // for sprites 339 | _directionalLights.add(lightColor); 340 | } else if (light.isPointLight) { 341 | // for sprites 342 | _pointLights.add(lightColor); 343 | } 344 | } 345 | } 346 | function calculateLight(position, normal, color) { 347 | var lightPosition; 348 | var amount; 349 | for (var l = 0, ll = _lights.length; l < ll; l++) { 350 | var light = _lights[l]; 351 | _lightColor.copy(light.color); 352 | if (light.isDirectionalLight) { 353 | lightPosition = _vector3 354 | .setFromMatrixPosition(light.matrixWorld) 355 | .normalize(); 356 | amount = normal.dot(lightPosition); 357 | if (amount <= 0) continue; 358 | amount *= light.intensity; 359 | color.add(_lightColor.multiplyScalar(amount)); 360 | } else if (light.isPointLight) { 361 | lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld); 362 | amount = normal.dot( 363 | _vector3.subVectors(lightPosition, position).normalize() 364 | ); 365 | if (amount <= 0) continue; 366 | amount *= 367 | light.distance == 0 368 | ? 1 369 | : 1 - 370 | Math.min( 371 | position.distanceTo(lightPosition) / light.distance, 372 | 1 373 | ); 374 | if (amount == 0) continue; 375 | amount *= light.intensity; 376 | color.add(_lightColor.multiplyScalar(amount)); 377 | } 378 | } 379 | } 380 | function renderSprite(v1, element, material) { 381 | setOpacity(material.opacity); 382 | setBlending(material.blending); 383 | var scaleX = element.scale.x * _canvasWidthHalf; 384 | var scaleY = element.scale.y * _canvasHeightHalf; 385 | var dist = Math.sqrt(scaleX * scaleX + scaleY * scaleY); // allow for rotated sprite 386 | _elemBox.min.set(v1.x - dist, v1.y - dist); 387 | _elemBox.max.set(v1.x + dist, v1.y + dist); 388 | if (material.isSpriteMaterial) { 389 | var texture = material.map; 390 | if (texture !== null) { 391 | var pattern = _patterns[texture.id]; 392 | if (pattern === undefined || pattern.version !== texture.version) { 393 | pattern = textureToPattern(texture); 394 | _patterns[texture.id] = pattern; 395 | } 396 | if (pattern.canvas !== undefined) { 397 | setFillStyle(pattern.canvas); 398 | var bitmap = texture.image; 399 | var ox = bitmap.width * texture.offset.x; 400 | var oy = bitmap.height * texture.offset.y; 401 | var sx = bitmap.width * texture.repeat.x; 402 | var sy = bitmap.height * texture.repeat.y; 403 | var cx = scaleX / sx; 404 | var cy = scaleY / sy; 405 | _context.save(); 406 | _context.translate(v1.x, v1.y); 407 | if (material.rotation !== 0) _context.rotate(material.rotation); 408 | _context.translate(-scaleX / 2, -scaleY / 2); 409 | _context.scale(cx, cy); 410 | _context.translate(-ox, -oy); 411 | _context.fillRect(ox, oy, sx, sy); 412 | _context.restore(); 413 | } 414 | } else { 415 | // no texture 416 | setFillStyle(material.color.getStyle()); 417 | _context.save(); 418 | _context.translate(v1.x, v1.y); 419 | if (material.rotation !== 0) _context.rotate(material.rotation); 420 | _context.scale(scaleX, -scaleY); 421 | _context.fillRect(-0.5, -0.5, 1, 1); 422 | _context.restore(); 423 | } 424 | } else if (material.isSpriteCanvasMaterial) { 425 | setStrokeStyle(material.color.getStyle()); 426 | setFillStyle(material.color.getStyle()); 427 | _context.save(); 428 | _context.translate(v1.x, v1.y); 429 | if (material.rotation !== 0) _context.rotate(material.rotation); 430 | _context.scale(scaleX, scaleY); 431 | material.program(_context); 432 | _context.restore(); 433 | } else if (material.isPointsMaterial) { 434 | setFillStyle(material.color.getStyle()); 435 | _context.save(); 436 | _context.translate(v1.x, v1.y); 437 | if (material.rotation !== 0) _context.rotate(material.rotation); 438 | _context.scale(scaleX * material.size, -scaleY * material.size); 439 | _context.fillRect(-0.5, -0.5, 1, 1); 440 | _context.restore(); 441 | } 442 | // DEBUG 443 | // setStrokeStyle( 'rgb(255,255,0)' ); 444 | // _context.beginPath(); 445 | // _context.moveTo( v1.x - 10, v1.y ); 446 | // _context.lineTo( v1.x + 10, v1.y ); 447 | // _context.moveTo( v1.x, v1.y - 10 ); 448 | // _context.lineTo( v1.x, v1.y + 10 ); 449 | // _context.stroke(); 450 | } 451 | function renderLine(v1, v2, element, material) { 452 | setOpacity(material.opacity); 453 | setBlending(material.blending); 454 | _context.beginPath(); 455 | _context.moveTo(v1.positionScreen.x, v1.positionScreen.y); 456 | _context.lineTo(v2.positionScreen.x, v2.positionScreen.y); 457 | if (material.isLineBasicMaterial) { 458 | setLineWidth(material.linewidth); 459 | setLineCap(material.linecap); 460 | setLineJoin(material.linejoin); 461 | if (material.vertexColors !== VertexColors) { 462 | setStrokeStyle(material.color.getStyle()); 463 | } else { 464 | var colorStyle1 = element.vertexColors[0].getStyle(); 465 | var colorStyle2 = element.vertexColors[1].getStyle(); 466 | if (colorStyle1 === colorStyle2) { 467 | setStrokeStyle(colorStyle1); 468 | } else { 469 | try { 470 | var grad = _context.createLinearGradient( 471 | v1.positionScreen.x, 472 | v1.positionScreen.y, 473 | v2.positionScreen.x, 474 | v2.positionScreen.y 475 | ); 476 | grad.addColorStop(0, colorStyle1); 477 | grad.addColorStop(1, colorStyle2); 478 | } catch (exception) { 479 | grad = colorStyle1; 480 | } 481 | setStrokeStyle(grad); 482 | } 483 | } 484 | if (material.isLineDashedMaterial) { 485 | setLineDash([material.dashSize, material.gapSize]); 486 | } 487 | _context.stroke(); 488 | _elemBox.expandByScalar(material.linewidth * 2); 489 | if (material.isLineDashedMaterial) { 490 | setLineDash([]); 491 | } 492 | } 493 | } 494 | function renderFace3(v1, v2, v3, uv1, uv2, uv3, element, material) { 495 | _this.info.render.vertices += 3; 496 | _this.info.render.faces++; 497 | setOpacity(material.opacity); 498 | setBlending(material.blending); 499 | _v1x = v1.positionScreen.x; 500 | _v1y = v1.positionScreen.y; 501 | _v2x = v2.positionScreen.x; 502 | _v2y = v2.positionScreen.y; 503 | _v3x = v3.positionScreen.x; 504 | _v3y = v3.positionScreen.y; 505 | drawTriangle(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y); 506 | if ( 507 | (material.isMeshLambertMaterial || 508 | material.isMeshPhongMaterial || 509 | material.isMeshStandardMaterial) && 510 | material.map === null 511 | ) { 512 | _diffuseColor.copy(material.color); 513 | _emissiveColor.copy(material.emissive); 514 | if (material.vertexColors === FaceColors) { 515 | _diffuseColor.multiply(element.color); 516 | } 517 | _color.copy(_ambientLight); 518 | _centroid 519 | .copy(v1.positionWorld) 520 | .add(v2.positionWorld) 521 | .add(v3.positionWorld) 522 | .divideScalar(3); 523 | calculateLight(_centroid, element.normalModel, _color); 524 | _color.multiply(_diffuseColor).add(_emissiveColor); 525 | material.wireframe === true 526 | ? strokePath( 527 | _color, 528 | material.wireframeLinewidth, 529 | material.wireframeLinecap, 530 | material.wireframeLinejoin 531 | ) 532 | : fillPath(_color); 533 | } else if ( 534 | material.isMeshBasicMaterial || 535 | material.isMeshLambertMaterial || 536 | material.isMeshPhongMaterial || 537 | material.isMeshStandardMaterial 538 | ) { 539 | if (material.map !== null) { 540 | var mapping = material.map.mapping; 541 | if (mapping === UVMapping) { 542 | _uvs = element.uvs; 543 | patternPath( 544 | _v1x, 545 | _v1y, 546 | _v2x, 547 | _v2y, 548 | _v3x, 549 | _v3y, 550 | _uvs[uv1].x, 551 | _uvs[uv1].y, 552 | _uvs[uv2].x, 553 | _uvs[uv2].y, 554 | _uvs[uv3].x, 555 | _uvs[uv3].y, 556 | material.map 557 | ); 558 | } 559 | } else if (material.envMap !== null) { 560 | if (material.envMap.mapping === SphericalReflectionMapping) { 561 | _normal 562 | .copy(element.vertexNormalsModel[uv1]) 563 | .applyMatrix3(_normalViewMatrix); 564 | _uv1x = 0.5 * _normal.x + 0.5; 565 | _uv1y = 0.5 * _normal.y + 0.5; 566 | _normal 567 | .copy(element.vertexNormalsModel[uv2]) 568 | .applyMatrix3(_normalViewMatrix); 569 | _uv2x = 0.5 * _normal.x + 0.5; 570 | _uv2y = 0.5 * _normal.y + 0.5; 571 | _normal 572 | .copy(element.vertexNormalsModel[uv3]) 573 | .applyMatrix3(_normalViewMatrix); 574 | _uv3x = 0.5 * _normal.x + 0.5; 575 | _uv3y = 0.5 * _normal.y + 0.5; 576 | patternPath( 577 | _v1x, 578 | _v1y, 579 | _v2x, 580 | _v2y, 581 | _v3x, 582 | _v3y, 583 | _uv1x, 584 | _uv1y, 585 | _uv2x, 586 | _uv2y, 587 | _uv3x, 588 | _uv3y, 589 | material.envMap 590 | ); 591 | } 592 | } else { 593 | _color.copy(material.color); 594 | if (material.vertexColors === FaceColors) { 595 | _color.multiply(element.color); 596 | } 597 | material.wireframe === true 598 | ? strokePath( 599 | _color, 600 | material.wireframeLinewidth, 601 | material.wireframeLinecap, 602 | material.wireframeLinejoin 603 | ) 604 | : fillPath(_color); 605 | } 606 | } else if (material.isMeshNormalMaterial) { 607 | _normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix); 608 | _color 609 | .setRGB(_normal.x, _normal.y, _normal.z) 610 | .multiplyScalar(0.5) 611 | .addScalar(0.5); 612 | material.wireframe === true 613 | ? strokePath( 614 | _color, 615 | material.wireframeLinewidth, 616 | material.wireframeLinecap, 617 | material.wireframeLinejoin 618 | ) 619 | : fillPath(_color); 620 | } else { 621 | _color.setRGB(1, 1, 1); 622 | material.wireframe === true 623 | ? strokePath( 624 | _color, 625 | material.wireframeLinewidth, 626 | material.wireframeLinecap, 627 | material.wireframeLinejoin 628 | ) 629 | : fillPath(_color); 630 | } 631 | } 632 | // 633 | function drawTriangle(x0, y0, x1, y1, x2, y2) { 634 | _context.beginPath(); 635 | _context.moveTo(x0, y0); 636 | _context.lineTo(x1, y1); 637 | _context.lineTo(x2, y2); 638 | _context.closePath(); 639 | } 640 | function strokePath(color, linewidth, linecap, linejoin) { 641 | setLineWidth(linewidth); 642 | setLineCap(linecap); 643 | setLineJoin(linejoin); 644 | setStrokeStyle(color.getStyle()); 645 | _context.stroke(); 646 | _elemBox.expandByScalar(linewidth * 2); 647 | } 648 | function fillPath(color) { 649 | setFillStyle(color.getStyle()); 650 | _context.fill(); 651 | } 652 | function textureToPattern(texture) { 653 | if ( 654 | texture.version === 0 || 655 | texture instanceof CompressedTexture || 656 | texture instanceof DataTexture 657 | ) { 658 | return { 659 | canvas: undefined, 660 | version: texture.version 661 | }; 662 | } 663 | var image = texture.image; 664 | if (image.complete === false) { 665 | return { 666 | canvas: undefined, 667 | version: 0 668 | }; 669 | } 670 | var repeatX = 671 | texture.wrapS === RepeatWrapping || 672 | texture.wrapS === MirroredRepeatWrapping; 673 | var repeatY = 674 | texture.wrapT === RepeatWrapping || 675 | texture.wrapT === MirroredRepeatWrapping; 676 | var mirrorX = texture.wrapS === MirroredRepeatWrapping; 677 | var mirrorY = texture.wrapT === MirroredRepeatWrapping; 678 | // 679 | var canvas = document.createElement("canvas"); 680 | canvas.width = image.width * (mirrorX ? 2 : 1); 681 | canvas.height = image.height * (mirrorY ? 2 : 1); 682 | var context = canvas.getContext("2d"); 683 | context.setTransform(1, 0, 0, -1, 0, image.height); 684 | context.drawImage(image, 0, 0); 685 | if (mirrorX === true) { 686 | context.setTransform(-1, 0, 0, -1, image.width, image.height); 687 | context.drawImage(image, -image.width, 0); 688 | } 689 | if (mirrorY === true) { 690 | context.setTransform(1, 0, 0, 1, 0, 0); 691 | context.drawImage(image, 0, image.height); 692 | } 693 | if (mirrorX === true && mirrorY === true) { 694 | context.setTransform(-1, 0, 0, 1, image.width, 0); 695 | context.drawImage(image, -image.width, image.height); 696 | } 697 | var repeat = "no-repeat"; 698 | if (repeatX === true && repeatY === true) { 699 | repeat = "repeat"; 700 | } else if (repeatX === true) { 701 | repeat = "repeat-x"; 702 | } else if (repeatY === true) { 703 | repeat = "repeat-y"; 704 | } 705 | var pattern = _context.createPattern(canvas, repeat); 706 | if (texture.onUpdate) texture.onUpdate(texture); 707 | return { 708 | canvas: pattern, 709 | version: texture.version 710 | }; 711 | } 712 | function patternPath( 713 | x0, 714 | y0, 715 | x1, 716 | y1, 717 | x2, 718 | y2, 719 | u0, 720 | v0, 721 | u1, 722 | v1, 723 | u2, 724 | v2, 725 | texture 726 | ) { 727 | var pattern = _patterns[texture.id]; 728 | if (pattern === undefined || pattern.version !== texture.version) { 729 | pattern = textureToPattern(texture); 730 | _patterns[texture.id] = pattern; 731 | } 732 | if (pattern.canvas !== undefined) { 733 | setFillStyle(pattern.canvas); 734 | } else { 735 | setFillStyle("rgba( 0, 0, 0, 1)"); 736 | _context.fill(); 737 | return; 738 | } 739 | // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 740 | var a, 741 | b, 742 | c, 743 | d, 744 | e, 745 | f, 746 | det, 747 | idet, 748 | offsetX = texture.offset.x / texture.repeat.x, 749 | offsetY = texture.offset.y / texture.repeat.y, 750 | width = texture.image.width * texture.repeat.x, 751 | height = texture.image.height * texture.repeat.y; 752 | u0 = (u0 + offsetX) * width; 753 | v0 = (v0 + offsetY) * height; 754 | u1 = (u1 + offsetX) * width; 755 | v1 = (v1 + offsetY) * height; 756 | u2 = (u2 + offsetX) * width; 757 | v2 = (v2 + offsetY) * height; 758 | x1 -= x0; 759 | y1 -= y0; 760 | x2 -= x0; 761 | y2 -= y0; 762 | u1 -= u0; 763 | v1 -= v0; 764 | u2 -= u0; 765 | v2 -= v0; 766 | det = u1 * v2 - u2 * v1; 767 | if (det === 0) return; 768 | idet = 1 / det; 769 | a = (v2 * x1 - v1 * x2) * idet; 770 | b = (v2 * y1 - v1 * y2) * idet; 771 | c = (u1 * x2 - u2 * x1) * idet; 772 | d = (u1 * y2 - u2 * y1) * idet; 773 | e = x0 - a * u0 - c * v0; 774 | f = y0 - b * u0 - d * v0; 775 | _context.save(); 776 | _context.transform(a, b, c, d, e, f); 777 | _context.fill(); 778 | _context.restore(); 779 | } 780 | // function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) { 781 | // // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120 782 | // var a, b, c, d, e, f, det, idet, 783 | // width = image.width - 1, 784 | // height = image.height - 1; 785 | // u0 *= width; v0 *= height; 786 | // u1 *= width; v1 *= height; 787 | // u2 *= width; v2 *= height; 788 | // x1 -= x0; y1 -= y0; 789 | // x2 -= x0; y2 -= y0; 790 | // u1 -= u0; v1 -= v0; 791 | // u2 -= u0; v2 -= v0; 792 | // det = u1 * v2 - u2 * v1; 793 | // idet = 1 / det; 794 | // a = ( v2 * x1 - v1 * x2 ) * idet; 795 | // b = ( v2 * y1 - v1 * y2 ) * idet; 796 | // c = ( u1 * x2 - u2 * x1 ) * idet; 797 | // d = ( u1 * y2 - u2 * y1 ) * idet; 798 | // e = x0 - a * u0 - c * v0; 799 | // f = y0 - b * u0 - d * v0; 800 | // _context.save(); 801 | // _context.transform( a, b, c, d, e, f ); 802 | // _context.clip(); 803 | // _context.drawImage( image, 0, 0 ); 804 | // _context.restore(); 805 | // } 806 | // Hide anti-alias gaps 807 | function expand(v1, v2, pixels) { 808 | var x = v2.x - v1.x, 809 | y = v2.y - v1.y, 810 | det = x * x + y * y, 811 | idet; 812 | if (det === 0) return; 813 | idet = pixels / Math.sqrt(det); 814 | x *= idet; 815 | y *= idet; 816 | v2.x += x; 817 | v2.y += y; 818 | v1.x -= x; 819 | v1.y -= y; 820 | } 821 | // Context cached methods. 822 | function setOpacity(value) { 823 | if (_contextGlobalAlpha !== value) { 824 | _context.globalAlpha = value; 825 | _contextGlobalAlpha = value; 826 | } 827 | } 828 | function setBlending(value) { 829 | if (_contextGlobalCompositeOperation !== value) { 830 | if (value === NormalBlending) { 831 | _context.globalCompositeOperation = "source-over"; 832 | } else if (value === AdditiveBlending) { 833 | _context.globalCompositeOperation = "lighter"; 834 | } else if (value === SubtractiveBlending) { 835 | _context.globalCompositeOperation = "darker"; 836 | } else if (value === MultiplyBlending) { 837 | _context.globalCompositeOperation = "multiply"; 838 | } 839 | _contextGlobalCompositeOperation = value; 840 | } 841 | } 842 | function setLineWidth(value) { 843 | if (_contextLineWidth !== value) { 844 | _context.lineWidth = value; 845 | _contextLineWidth = value; 846 | } 847 | } 848 | function setLineCap(value) { 849 | // "butt", "round", "square" 850 | if (_contextLineCap !== value) { 851 | _context.lineCap = value; 852 | _contextLineCap = value; 853 | } 854 | } 855 | function setLineJoin(value) { 856 | // "round", "bevel", "miter" 857 | if (_contextLineJoin !== value) { 858 | _context.lineJoin = value; 859 | _contextLineJoin = value; 860 | } 861 | } 862 | function setStrokeStyle(value) { 863 | if (_contextStrokeStyle !== value) { 864 | _context.strokeStyle = value; 865 | _contextStrokeStyle = value; 866 | } 867 | } 868 | function setFillStyle(value) { 869 | if (_contextFillStyle !== value) { 870 | _context.fillStyle = value; 871 | _contextFillStyle = value; 872 | } 873 | } 874 | function setLineDash(value) { 875 | if (_contextLineDash.length !== value.length) { 876 | _context.setLineDash(value); 877 | _contextLineDash = value; 878 | } 879 | } 880 | } 881 | } 882 | 883 | export { PaintRenderer, SpriteCanvasMaterial }; 884 | -------------------------------------------------------------------------------- /src/lib/Projector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author supereggbert / http://www.paulbrunt.co.uk/ 4 | * @author julianwa / https://github.com/julianwa 5 | */ 6 | 7 | import { 8 | BackSide, 9 | Box3, 10 | BufferGeometry, 11 | Color, 12 | DoubleSide, 13 | FrontSide, 14 | Frustum, 15 | Geometry, 16 | Light, 17 | Line, 18 | LineSegments, 19 | Matrix3, 20 | Matrix4, 21 | Mesh, 22 | Points, 23 | Sprite, 24 | Vector2, 25 | Vector3, 26 | Vector4, 27 | VertexColors} from 'three'; 28 | 29 | class RenderableObject { 30 | constructor() { 31 | this.id = 0; 32 | this.object = null; 33 | this.z = 0; 34 | this.renderOrder = 0; 35 | } 36 | } 37 | 38 | class RenderableFace { 39 | constructor() { 40 | this.id = 0; 41 | this.v1 = new RenderableVertex(); 42 | this.v2 = new RenderableVertex(); 43 | this.v3 = new RenderableVertex(); 44 | this.normalModel = new Vector3(); 45 | this.vertexNormalsModel = [ 46 | new Vector3(), 47 | new Vector3(), 48 | new Vector3() 49 | ]; 50 | this.vertexNormalsLength = 0; 51 | this.color = new Color(); 52 | this.material = null; 53 | this.uvs = [new Vector2(), new Vector2(), new Vector2()]; 54 | this.z = 0; 55 | this.renderOrder = 0; 56 | } 57 | } 58 | 59 | class RenderableVertex { 60 | constructor() { 61 | this.position = new Vector3(); 62 | this.positionWorld = new Vector3(); 63 | this.positionScreen = new Vector4(); 64 | this.visible = true; 65 | } 66 | 67 | copy(vertex) { 68 | this.positionWorld.copy(vertex.positionWorld); 69 | this.positionScreen.copy(vertex.positionScreen); 70 | } 71 | } 72 | 73 | 74 | class RenderableLine { 75 | constructor() { 76 | this.id = 0; 77 | this.v1 = new RenderableVertex(); 78 | this.v2 = new RenderableVertex(); 79 | this.vertexColors = [new Color(), new Color()]; 80 | this.material = null; 81 | this.z = 0; 82 | this.renderOrder = 0; 83 | } 84 | } 85 | 86 | class RenderableSprite { 87 | constructor() { 88 | this.id = 0; 89 | this.object = null; 90 | this.x = 0; 91 | this.y = 0; 92 | this.z = 0; 93 | this.rotation = 0; 94 | this.scale = new Vector2(); 95 | this.material = null; 96 | this.renderOrder = 0; 97 | } 98 | } 99 | 100 | class Projector { 101 | constructor() { 102 | var _object, _objectCount, _objectPool = [], _objectPoolLength = 0, _vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0, _face, _faceCount, _facePool = [], _facePoolLength = 0, _line, _lineCount, _linePool = [], _linePoolLength = 0, _sprite, _spriteCount, _spritePool = [], _spritePoolLength = 0, _renderData = { objects: [], lights: [], elements: [] }, _vector3 = new Vector3(), _vector4 = new Vector4(), _clipBox = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)), _boundingBox = new Box3(), _points3 = new Array(3), _viewMatrix = new Matrix4(), _viewProjectionMatrix = new Matrix4(), _modelMatrix, _modelViewProjectionMatrix = new Matrix4(), _normalMatrix = new Matrix3(), _frustum = new Frustum(), _clippedVertex1PositionScreen = new Vector4(), _clippedVertex2PositionScreen = new Vector4(); 103 | // 104 | this.projectVector = function (vector, camera) { 105 | console.warn("Projector: .projectVector() is now vector.project()."); 106 | vector.project(camera); 107 | }; 108 | this.unprojectVector = function (vector, camera) { 109 | console.warn("Projector: .unprojectVector() is now vector.unproject()."); 110 | vector.unproject(camera); 111 | }; 112 | this.pickingRay = function () { 113 | console.error("Projector: .pickingRay() is now raycaster.setFromCamera()."); 114 | }; 115 | // 116 | var RenderList = function () { 117 | var normals = []; 118 | var colors = []; 119 | var uvs = []; 120 | var object = null; 121 | var material = null; 122 | var normalMatrix = new Matrix3(); 123 | function setObject(value) { 124 | object = value; 125 | material = object.material; 126 | normalMatrix.getNormalMatrix(object.matrixWorld); 127 | normals.length = 0; 128 | colors.length = 0; 129 | uvs.length = 0; 130 | } 131 | function projectVertex(vertex) { 132 | var position = vertex.position; 133 | var positionWorld = vertex.positionWorld; 134 | var positionScreen = vertex.positionScreen; 135 | positionWorld.copy(position).applyMatrix4(_modelMatrix); 136 | positionScreen.copy(positionWorld).applyMatrix4(_viewProjectionMatrix); 137 | var invW = 1 / positionScreen.w; 138 | positionScreen.x *= invW; 139 | positionScreen.y *= invW; 140 | positionScreen.z *= invW; 141 | vertex.visible = 142 | positionScreen.x >= -1 && 143 | positionScreen.x <= 1 && 144 | positionScreen.y >= -1 && 145 | positionScreen.y <= 1 && 146 | positionScreen.z >= -1 && 147 | positionScreen.z <= 1; 148 | } 149 | function pushVertex(x, y, z) { 150 | _vertex = getNextVertexInPool(); 151 | _vertex.position.set(x, y, z); 152 | projectVertex(_vertex); 153 | } 154 | function pushNormal(x, y, z) { 155 | normals.push(x, y, z); 156 | } 157 | function pushColor(r, g, b) { 158 | colors.push(r, g, b); 159 | } 160 | function pushUv(x, y) { 161 | uvs.push(x, y); 162 | } 163 | function checkTriangleVisibility(v1, v2, v3) { 164 | if (v1.visible === true || v2.visible === true || v3.visible === true) return true; 165 | _points3[0] = v1.positionScreen; 166 | _points3[1] = v2.positionScreen; 167 | _points3[2] = v3.positionScreen; 168 | return _clipBox.intersectsBox(_boundingBox.setFromPoints(_points3)); 169 | } 170 | function checkBackfaceCulling(v1, v2, v3) { 171 | return ((v3.positionScreen.x - v1.positionScreen.x) * 172 | (v2.positionScreen.y - v1.positionScreen.y) - 173 | (v3.positionScreen.y - v1.positionScreen.y) * 174 | (v2.positionScreen.x - v1.positionScreen.x) < 175 | 0); 176 | } 177 | function pushLine(a, b) { 178 | var v1 = _vertexPool[a]; 179 | var v2 = _vertexPool[b]; 180 | // Clip 181 | v1.positionScreen 182 | .copy(v1.position) 183 | .applyMatrix4(_modelViewProjectionMatrix); 184 | v2.positionScreen 185 | .copy(v2.position) 186 | .applyMatrix4(_modelViewProjectionMatrix); 187 | if (clipLine(v1.positionScreen, v2.positionScreen) === true) { 188 | // Perform the perspective divide 189 | v1.positionScreen.multiplyScalar(1 / v1.positionScreen.w); 190 | v2.positionScreen.multiplyScalar(1 / v2.positionScreen.w); 191 | _line = getNextLineInPool(); 192 | _line.id = object.id; 193 | _line.v1.copy(v1); 194 | _line.v2.copy(v2); 195 | _line.z = Math.max(v1.positionScreen.z, v2.positionScreen.z); 196 | _line.renderOrder = object.renderOrder; 197 | _line.material = object.material; 198 | if (object.material.vertexColors === VertexColors) { 199 | _line.vertexColors[0].fromArray(colors, a * 3); 200 | _line.vertexColors[1].fromArray(colors, b * 3); 201 | } 202 | _renderData.elements.push(_line); 203 | } 204 | } 205 | function pushTriangle(a, b, c) { 206 | var v1 = _vertexPool[a]; 207 | var v2 = _vertexPool[b]; 208 | var v3 = _vertexPool[c]; 209 | if (checkTriangleVisibility(v1, v2, v3) === false) return; 210 | if (material.side === DoubleSide || 211 | checkBackfaceCulling(v1, v2, v3) === true) { 212 | _face = getNextFaceInPool(); 213 | _face.id = object.id; 214 | _face.v1.copy(v1); 215 | _face.v2.copy(v2); 216 | _face.v3.copy(v3); 217 | _face.z = 218 | (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3; 219 | _face.renderOrder = object.renderOrder; 220 | // use first vertex normal as face normal 221 | _face.normalModel.fromArray(normals, a * 3); 222 | _face.normalModel.applyMatrix3(normalMatrix).normalize(); 223 | for (var i = 0; i < 3; i++) { 224 | var normal = _face.vertexNormalsModel[i]; 225 | normal.fromArray(normals, arguments[i] * 3); 226 | normal.applyMatrix3(normalMatrix).normalize(); 227 | var uv = _face.uvs[i]; 228 | uv.fromArray(uvs, arguments[i] * 2); 229 | } 230 | _face.vertexNormalsLength = 3; 231 | _face.material = object.material; 232 | _renderData.elements.push(_face); 233 | } 234 | } 235 | return { 236 | setObject: setObject, 237 | projectVertex: projectVertex, 238 | checkTriangleVisibility: checkTriangleVisibility, 239 | checkBackfaceCulling: checkBackfaceCulling, 240 | pushVertex: pushVertex, 241 | pushNormal: pushNormal, 242 | pushColor: pushColor, 243 | pushUv: pushUv, 244 | pushLine: pushLine, 245 | pushTriangle: pushTriangle 246 | }; 247 | }; 248 | var renderList = new RenderList(); 249 | function projectObject(object) { 250 | if (object.visible === false) return; 251 | if (object instanceof Light) { 252 | _renderData.lights.push(object); 253 | } else if (object instanceof Mesh || 254 | object instanceof Line || 255 | object instanceof Points) { 256 | if (object.material.visible === false) return; 257 | if (object.frustumCulled === true && 258 | _frustum.intersectsObject(object) === false) return; 259 | addObject(object); 260 | } else if (object instanceof Sprite) { 261 | if (object.material.visible === false) return; 262 | if (object.frustumCulled === true && 263 | _frustum.intersectsSprite(object) === false) return; 264 | addObject(object); 265 | } 266 | var children = object.children; 267 | for (var i = 0, l = children.length; i < l; i++) { 268 | projectObject(children[i]); 269 | } 270 | } 271 | function addObject(object) { 272 | _object = getNextObjectInPool(); 273 | _object.id = object.id; 274 | _object.object = object; 275 | _vector3.setFromMatrixPosition(object.matrixWorld); 276 | _vector3.applyMatrix4(_viewProjectionMatrix); 277 | _object.z = _vector3.z; 278 | _object.renderOrder = object.renderOrder; 279 | _renderData.objects.push(_object); 280 | } 281 | this.projectScene = function (scene, camera, sortObjects, sortElements) { 282 | _faceCount = 0; 283 | _lineCount = 0; 284 | _spriteCount = 0; 285 | _renderData.elements.length = 0; 286 | if (scene.autoUpdate === true) scene.updateMatrixWorld(); 287 | if (camera.parent === null) camera.updateMatrixWorld(); 288 | _viewMatrix.copy(camera.matrixWorldInverse); 289 | _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix); 290 | _frustum.setFromMatrix(_viewProjectionMatrix); 291 | // 292 | _objectCount = 0; 293 | _renderData.objects.length = 0; 294 | _renderData.lights.length = 0; 295 | projectObject(scene); 296 | if (sortObjects === true) { 297 | _renderData.objects.sort(painterSort); 298 | } 299 | // 300 | var objects = _renderData.objects; 301 | for (var o = 0, ol = objects.length; o < ol; o++) { 302 | var object = objects[o].object; 303 | var geometry = object.geometry; 304 | renderList.setObject(object); 305 | _modelMatrix = object.matrixWorld; 306 | _vertexCount = 0; 307 | if (object instanceof Mesh) { 308 | if (geometry instanceof BufferGeometry) { 309 | var attributes = geometry.attributes; 310 | var groups = geometry.groups; 311 | if (attributes.position === undefined) continue; 312 | var positions = attributes.position.array; 313 | for (var i = 0, l = positions.length; i < l; i += 3) { 314 | renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]); 315 | } 316 | if (attributes.normal !== undefined) { 317 | var normals = attributes.normal.array; 318 | for (var i = 0, l = normals.length; i < l; i += 3) { 319 | renderList.pushNormal(normals[i], normals[i + 1], normals[i + 2]); 320 | } 321 | } 322 | if (attributes.uv !== undefined) { 323 | var uvs = attributes.uv.array; 324 | for (var i = 0, l = uvs.length; i < l; i += 2) { 325 | renderList.pushUv(uvs[i], uvs[i + 1]); 326 | } 327 | } 328 | if (geometry.index !== null) { 329 | var indices = geometry.index.array; 330 | if (groups.length > 0) { 331 | for (var g = 0; g < groups.length; g++) { 332 | var group = groups[g]; 333 | for (var i = group.start, l = group.start + group.count; i < l; i += 3) { 334 | renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2]); 335 | } 336 | } 337 | } else { 338 | for (var i = 0, l = indices.length; i < l; i += 3) { 339 | renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2]); 340 | } 341 | } 342 | } else { 343 | for (var i = 0, l = positions.length / 3; i < l; i += 3) { 344 | renderList.pushTriangle(i, i + 1, i + 2); 345 | } 346 | } 347 | } else if (geometry instanceof Geometry) { 348 | var vertices = geometry.vertices; 349 | var faces = geometry.faces; 350 | var faceVertexUvs = geometry.faceVertexUvs[0]; 351 | _normalMatrix.getNormalMatrix(_modelMatrix); 352 | var material = object.material; 353 | var isMultiMaterial = Array.isArray(material); 354 | for (var v = 0, vl = vertices.length; v < vl; v++) { 355 | var vertex = vertices[v]; 356 | _vector3.copy(vertex); 357 | if (material.morphTargets === true) { 358 | var morphTargets = geometry.morphTargets; 359 | var morphInfluences = object.morphTargetInfluences; 360 | for (var t = 0, tl = morphTargets.length; t < tl; t++) { 361 | var influence = morphInfluences[t]; 362 | if (influence === 0) continue; 363 | var target = morphTargets[t]; 364 | var targetVertex = target.vertices[v]; 365 | _vector3.x += (targetVertex.x - vertex.x) * influence; 366 | _vector3.y += (targetVertex.y - vertex.y) * influence; 367 | _vector3.z += (targetVertex.z - vertex.z) * influence; 368 | } 369 | } 370 | renderList.pushVertex(_vector3.x, _vector3.y, _vector3.z); 371 | } 372 | for (var f = 0, fl = faces.length; f < fl; f++) { 373 | var face = faces[f]; 374 | material = 375 | isMultiMaterial === true 376 | ? object.material[face.materialIndex] 377 | : object.material; 378 | if (material === undefined) continue; 379 | var side = material.side; 380 | var v1 = _vertexPool[face.a]; 381 | var v2 = _vertexPool[face.b]; 382 | var v3 = _vertexPool[face.c]; 383 | if (renderList.checkTriangleVisibility(v1, v2, v3) === false) continue; 384 | var visible = renderList.checkBackfaceCulling(v1, v2, v3); 385 | if (side !== DoubleSide) { 386 | if (side === FrontSide && visible === false) continue; 387 | if (side === BackSide && visible === true) continue; 388 | } 389 | _face = getNextFaceInPool(); 390 | _face.id = object.id; 391 | _face.v1.copy(v1); 392 | _face.v2.copy(v2); 393 | _face.v3.copy(v3); 394 | _face.normalModel.copy(face.normal); 395 | if (visible === false && 396 | (side === BackSide || side === DoubleSide)) { 397 | _face.normalModel.negate(); 398 | } 399 | _face.normalModel.applyMatrix3(_normalMatrix).normalize(); 400 | var faceVertexNormals = face.vertexNormals; 401 | for (var n = 0, nl = Math.min(faceVertexNormals.length, 3); n < nl; n++) { 402 | var normalModel = _face.vertexNormalsModel[n]; 403 | normalModel.copy(faceVertexNormals[n]); 404 | if (visible === false && 405 | (side === BackSide || side === DoubleSide)) { 406 | normalModel.negate(); 407 | } 408 | normalModel.applyMatrix3(_normalMatrix).normalize(); 409 | } 410 | _face.vertexNormalsLength = faceVertexNormals.length; 411 | var vertexUvs = faceVertexUvs[f]; 412 | if (vertexUvs !== undefined) { 413 | for (var u = 0; u < 3; u++) { 414 | _face.uvs[u].copy(vertexUvs[u]); 415 | } 416 | } 417 | _face.color = face.color; 418 | _face.material = material; 419 | _face.z = 420 | (v1.positionScreen.z + 421 | v2.positionScreen.z + 422 | v3.positionScreen.z) / 423 | 3; 424 | _face.renderOrder = object.renderOrder; 425 | _renderData.elements.push(_face); 426 | } 427 | } 428 | } else if (object instanceof Line) { 429 | _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix); 430 | if (geometry instanceof BufferGeometry) { 431 | var attributes = geometry.attributes; 432 | if (attributes.position !== undefined) { 433 | var positions = attributes.position.array; 434 | for (var i = 0, l = positions.length; i < l; i += 3) { 435 | renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]); 436 | } 437 | if (attributes.color !== undefined) { 438 | var colors = attributes.color.array; 439 | for (var i = 0, l = colors.length; i < l; i += 3) { 440 | renderList.pushColor(colors[i], colors[i + 1], colors[i + 2]); 441 | } 442 | } 443 | if (geometry.index !== null) { 444 | var indices = geometry.index.array; 445 | for (var i = 0, l = indices.length; i < l; i += 2) { 446 | renderList.pushLine(indices[i], indices[i + 1]); 447 | } 448 | } else { 449 | var step = object instanceof LineSegments ? 2 : 1; 450 | for (var i = 0, l = positions.length / 3 - 1; i < l; i += step) { 451 | renderList.pushLine(i, i + 1); 452 | } 453 | } 454 | } 455 | } else if (geometry instanceof Geometry) { 456 | var vertices = object.geometry.vertices; 457 | if (vertices.length === 0) continue; 458 | v1 = getNextVertexInPool(); 459 | v1.positionScreen 460 | .copy(vertices[0]) 461 | .applyMatrix4(_modelViewProjectionMatrix); 462 | var step = object instanceof LineSegments ? 2 : 1; 463 | for (var v = 1, vl = vertices.length; v < vl; v++) { 464 | v1 = getNextVertexInPool(); 465 | v1.positionScreen 466 | .copy(vertices[v]) 467 | .applyMatrix4(_modelViewProjectionMatrix); 468 | if ((v + 1) % step > 0) continue; 469 | v2 = _vertexPool[_vertexCount - 2]; 470 | _clippedVertex1PositionScreen.copy(v1.positionScreen); 471 | _clippedVertex2PositionScreen.copy(v2.positionScreen); 472 | if (clipLine(_clippedVertex1PositionScreen, _clippedVertex2PositionScreen) === true) { 473 | // Perform the perspective divide 474 | _clippedVertex1PositionScreen.multiplyScalar(1 / _clippedVertex1PositionScreen.w); 475 | _clippedVertex2PositionScreen.multiplyScalar(1 / _clippedVertex2PositionScreen.w); 476 | _line = getNextLineInPool(); 477 | _line.id = object.id; 478 | _line.v1.positionScreen.copy(_clippedVertex1PositionScreen); 479 | _line.v2.positionScreen.copy(_clippedVertex2PositionScreen); 480 | _line.z = Math.max(_clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z); 481 | _line.renderOrder = object.renderOrder; 482 | _line.material = object.material; 483 | if (object.material.vertexColors === VertexColors) { 484 | _line.vertexColors[0].copy(object.geometry.colors[v]); 485 | _line.vertexColors[1].copy(object.geometry.colors[v - 1]); 486 | } 487 | _renderData.elements.push(_line); 488 | } 489 | } 490 | } 491 | } else if (object instanceof Points) { 492 | _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix); 493 | if (geometry instanceof Geometry) { 494 | var vertices = object.geometry.vertices; 495 | for (var v = 0, vl = vertices.length; v < vl; v++) { 496 | var vertex = vertices[v]; 497 | _vector4.set(vertex.x, vertex.y, vertex.z, 1); 498 | _vector4.applyMatrix4(_modelViewProjectionMatrix); 499 | pushPoint(_vector4, object, camera); 500 | } 501 | } else if (geometry instanceof BufferGeometry) { 502 | var attributes = geometry.attributes; 503 | if (attributes.position !== undefined) { 504 | var positions = attributes.position.array; 505 | for (var i = 0, l = positions.length; i < l; i += 3) { 506 | _vector4.set(positions[i], positions[i + 1], positions[i + 2], 1); 507 | _vector4.applyMatrix4(_modelViewProjectionMatrix); 508 | pushPoint(_vector4, object, camera); 509 | } 510 | } 511 | } 512 | } else if (object instanceof Sprite) { 513 | _vector4.set(_modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1); 514 | _vector4.applyMatrix4(_viewProjectionMatrix); 515 | pushPoint(_vector4, object, camera); 516 | } 517 | } 518 | if (sortElements === true) { 519 | _renderData.elements.sort(painterSort); 520 | } 521 | return _renderData; 522 | }; 523 | function pushPoint(_vector4, object, camera) { 524 | var invW = 1 / _vector4.w; 525 | _vector4.z *= invW; 526 | if (_vector4.z >= -1 && _vector4.z <= 1) { 527 | _sprite = getNextSpriteInPool(); 528 | _sprite.id = object.id; 529 | _sprite.x = _vector4.x * invW; 530 | _sprite.y = _vector4.y * invW; 531 | _sprite.z = _vector4.z; 532 | _sprite.renderOrder = object.renderOrder; 533 | _sprite.object = object; 534 | _sprite.rotation = object.rotation; 535 | _sprite.scale.x = 536 | object.scale.x * 537 | Math.abs(_sprite.x - 538 | (_vector4.x + camera.projectionMatrix.elements[0]) / 539 | (_vector4.w + camera.projectionMatrix.elements[12])); 540 | _sprite.scale.y = 541 | object.scale.y * 542 | Math.abs(_sprite.y - 543 | (_vector4.y + camera.projectionMatrix.elements[5]) / 544 | (_vector4.w + camera.projectionMatrix.elements[13])); 545 | _sprite.material = object.material; 546 | _renderData.elements.push(_sprite); 547 | } 548 | } 549 | // Pools 550 | function getNextObjectInPool() { 551 | if (_objectCount === _objectPoolLength) { 552 | var object = new RenderableObject(); 553 | _objectPool.push(object); 554 | _objectPoolLength++; 555 | _objectCount++; 556 | return object; 557 | } 558 | return _objectPool[_objectCount++]; 559 | } 560 | function getNextVertexInPool() { 561 | if (_vertexCount === _vertexPoolLength) { 562 | var vertex = new RenderableVertex(); 563 | _vertexPool.push(vertex); 564 | _vertexPoolLength++; 565 | _vertexCount++; 566 | return vertex; 567 | } 568 | return _vertexPool[_vertexCount++]; 569 | } 570 | function getNextFaceInPool() { 571 | if (_faceCount === _facePoolLength) { 572 | var face = new RenderableFace(); 573 | _facePool.push(face); 574 | _facePoolLength++; 575 | _faceCount++; 576 | return face; 577 | } 578 | return _facePool[_faceCount++]; 579 | } 580 | function getNextLineInPool() { 581 | if (_lineCount === _linePoolLength) { 582 | var line = new RenderableLine(); 583 | _linePool.push(line); 584 | _linePoolLength++; 585 | _lineCount++; 586 | return line; 587 | } 588 | return _linePool[_lineCount++]; 589 | } 590 | function getNextSpriteInPool() { 591 | if (_spriteCount === _spritePoolLength) { 592 | var sprite = new RenderableSprite(); 593 | _spritePool.push(sprite); 594 | _spritePoolLength++; 595 | _spriteCount++; 596 | return sprite; 597 | } 598 | return _spritePool[_spriteCount++]; 599 | } 600 | // 601 | function painterSort(a, b) { 602 | if (a.renderOrder !== b.renderOrder) { 603 | return a.renderOrder - b.renderOrder; 604 | } 605 | else if (a.z !== b.z) { 606 | return b.z - a.z; 607 | } 608 | else if (a.id !== b.id) { 609 | return a.id - b.id; 610 | } 611 | 612 | return 0; 613 | 614 | } 615 | function clipLine(s1, s2) { 616 | var alpha1 = 0, alpha2 = 1, 617 | // Calculate the boundary coordinate of each vertex for the near and far clip planes, 618 | // Z = -1 and Z = +1, respectively. 619 | bc1near = s1.z + s1.w, bc2near = s2.z + s2.w, bc1far = -s1.z + s1.w, bc2far = -s2.z + s2.w; 620 | if (bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0) { 621 | // Both vertices lie entirely within all clip planes. 622 | return true; 623 | } 624 | else if ((bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0)) { 625 | // Both vertices lie entirely outside one of the clip planes. 626 | return false; 627 | } 628 | 629 | // The line segment spans at least one clip plane. 630 | if (bc1near < 0) { 631 | // v1 lies outside the near plane, v2 inside 632 | alpha1 = Math.max(alpha1, bc1near / (bc1near - bc2near)); 633 | } 634 | else if (bc2near < 0) { 635 | // v2 lies outside the near plane, v1 inside 636 | alpha2 = Math.min(alpha2, bc1near / (bc1near - bc2near)); 637 | } 638 | if (bc1far < 0) { 639 | // v1 lies outside the far plane, v2 inside 640 | alpha1 = Math.max(alpha1, bc1far / (bc1far - bc2far)); 641 | } 642 | else if (bc2far < 0) { 643 | // v2 lies outside the far plane, v2 inside 644 | alpha2 = Math.min(alpha2, bc1far / (bc1far - bc2far)); 645 | } 646 | if (alpha2 < alpha1) { 647 | // The line segment spans two boundaries, but is outside both of them. 648 | // (This can't happen when we're only clipping against just near/far but good 649 | // to leave the check here for future usage if other clip planes are added.) 650 | return false; 651 | } else { 652 | // Update the s1 and s2 vertices to match the clipped line segment. 653 | s1.lerp(s2, alpha1); 654 | s2.lerp(s1, 1 - alpha2); 655 | return true; 656 | } 657 | 658 | } 659 | } 660 | } 661 | 662 | 663 | export { 664 | Projector, 665 | RenderableFace, 666 | RenderableObject, 667 | RenderableSprite, 668 | RenderableVertex, 669 | RenderableLine 670 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* global registerPaint */ 2 | 3 | import * as typeface from 'three/examples/fonts/optimer_bold.typeface.json'; 4 | import { 5 | AmbientLight, 6 | DirectionalLight, 7 | Font, 8 | Group, 9 | Mesh, 10 | MeshLambertMaterial, 11 | PerspectiveCamera, 12 | Scene, 13 | TextGeometry 14 | } from "three"; 15 | import { PaintRenderer } from "./lib/PaintRenderer.js"; 16 | 17 | const scene = new Scene(); 18 | const camera = new PerspectiveCamera( 19 | 75, 20 | 1, 21 | 0.1, 22 | 1000 23 | ); 24 | 25 | const renderer = new PaintRenderer({size: { 26 | width: 100, 27 | height: 100 28 | }}); 29 | 30 | renderer.setClearColor('black', 0); 31 | renderer.setSize(100, 100); 32 | 33 | // const cubeGeom = new BoxGeometry(1, 1, 1); 34 | const material = new MeshLambertMaterial({ color: 0xaaaaaa, overdraw: 0.5 }); 35 | // const cube = new Mesh(cubeGeom, material); 36 | // scene.add(cube); 37 | 38 | const ambientLight = new AmbientLight(0x202020); 39 | scene.add(ambientLight); 40 | 41 | const directionalLight = new DirectionalLight(0x00ffff); 42 | directionalLight.position.set(1, 1, 0); 43 | 44 | scene.add(directionalLight); 45 | 46 | camera.position.z = 5; 47 | 48 | const font = new Font(typeface); 49 | const typeGeom = new TextGeometry( 50 | `Three.js in CSS 51 | through the 52 | magic of 53 | Houdini!! 54 | `, { 55 | font: font, 56 | size: 1.0, 57 | height: 0.5, 58 | curveSegments: 2 59 | }); 60 | typeGeom.computeBoundingBox(); 61 | const materials = [material, material]; 62 | const type = new Mesh(typeGeom, materials); 63 | type.position.x = -0.5 * (typeGeom.boundingBox.max.x - typeGeom.boundingBox.min.x); 64 | type.position.y = -1 + 0.5 * (typeGeom.boundingBox.max.y - typeGeom.boundingBox.min.y); 65 | type.position.z = 0; 66 | type.rotation.x = 0; 67 | type.rotation.y = Math.PI * 2; 68 | 69 | const group = new Group(); 70 | scene.add(group); 71 | group.add(type); 72 | 73 | registerPaint( 74 | "three", 75 | class { 76 | static get inputProperties() { 77 | return ["--rotate-x", "--rotate-y", "--rotate-z"]; 78 | } 79 | 80 | paint(ctx, size, props) { 81 | const a = Math.min(0.5 * size.width / size.height, 1); 82 | group.scale.set(a,a,a); 83 | group.rotation.set( 84 | Math.PI * Number(props.get("--rotate-x"))/180, 85 | Math.PI * Number(props.get("--rotate-y"))/180, 86 | Math.PI * Number(props.get("--rotate-z"))/180 87 | ); 88 | camera.aspect = size.width / size.height; 89 | camera.updateProjectionMatrix(); 90 | renderer.setContext(ctx); 91 | renderer.setSize(size.width, size.height); 92 | renderer.render(scene, camera); 93 | } 94 | } 95 | ); 96 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --rotate-x: 0; 3 | --rotate-y: 0; 4 | --rotate-z: 0; 5 | } 6 | 7 | html { 8 | margin: 0; 9 | height: 100%; 10 | } 11 | 12 | body { 13 | width: 100%; 14 | height: 100%; 15 | margin: 0; 16 | } 17 | 18 | div { 19 | width: 100%; 20 | height: 100%; 21 | background-color: lavenderblush; 22 | background-image: paint(three); 23 | 24 | font-family: monospace; 25 | display: flex; 26 | align-items: flex-end; 27 | font-size: 1.5rem; 28 | } --------------------------------------------------------------------------------