diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b36fde..ebb8e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [2023-03-13 - Version 1.1.0](https://github.com/microsoft/scenepic/releases/tag/v1.1.0) +As part of this 1.1 minor release a few final issues were addressed: + +New Features +- Full pinch, zoom and rotate with two fingers is now supported + +Bug fixes +- Dropdown menus will now correctly appear on top of canvases in the z-order +- Dropdown menus will align on the opposite side if not doing so would result + appearing outside the viewbox + ## [2023-03-13 - Version 1.0.19](https://github.com/microsoft/scenepic/releases/tag/v1.0.19) Point release adding pan/zoom functionality to 2D canvases. diff --git a/RELEASE_NOTES b/RELEASE_NOTES index ed18941..8d95b2b 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1 +1,9 @@ -Point release adding pan/zoom functionality to 2D canvases. \ No newline at end of file +As part of this 1.1 minor release a few final issues were addressed: + +New Features +- Full pinch, zoom and rotate with two fingers is now supported + +Bug fixes +- Dropdown menus will now correctly appear on top of canvases in the z-order +- Dropdown menus will align on the opposite side if not doing so would result + appearing outside the viewbox \ No newline at end of file diff --git a/VERSION b/VERSION index e7ad390..1cc5f65 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.19 \ No newline at end of file +1.1.0 \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 21716c9..29ff136 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ copyright = "2021, Microsoft" author = "ScenePic Team" # The full version, including alpha/beta/rc tags -release = "1.0.19" +release = "1.1.0" # -- General configuration --------------------------------------------------- diff --git a/package-lock.json b/package-lock.json index ef72199..a1237f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scenepic", - "version": "1.0.19", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scenepic", - "version": "1.0.19", + "version": "1.1.0", "license": "MIT", "devDependencies": { "@types/file-saver": "^2.0.5", @@ -2053,6 +2053,7 @@ }, "node_modules/npm/node_modules/@colors/colors": { "version": "1.5.0", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2062,16 +2063,19 @@ }, "node_modules/npm/node_modules/@gar/promisify": { "version": "1.1.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/arborist": { "version": "5.6.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2122,6 +2126,7 @@ }, "node_modules/npm/node_modules/@npmcli/ci-detect": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -2130,6 +2135,7 @@ }, "node_modules/npm/node_modules/@npmcli/config": { "version": "4.2.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2148,6 +2154,7 @@ }, "node_modules/npm/node_modules/@npmcli/disparity-colors": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2159,6 +2166,7 @@ }, "node_modules/npm/node_modules/@npmcli/fs": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2171,6 +2179,7 @@ }, "node_modules/npm/node_modules/@npmcli/git": { "version": "3.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2190,6 +2199,7 @@ }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { "version": "1.0.7", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2205,6 +2215,7 @@ }, "node_modules/npm/node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { "version": "1.1.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2213,6 +2224,7 @@ }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { "version": "2.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2227,6 +2239,7 @@ }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2241,6 +2254,7 @@ }, "node_modules/npm/node_modules/@npmcli/move-file": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2253,11 +2267,13 @@ }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/node-gyp": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -2266,6 +2282,7 @@ }, "node_modules/npm/node_modules/@npmcli/package-json": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2277,6 +2294,7 @@ }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2288,6 +2306,7 @@ }, "node_modules/npm/node_modules/@npmcli/query": { "version": "1.2.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2301,6 +2320,7 @@ }, "node_modules/npm/node_modules/@npmcli/run-script": { "version": "4.2.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2316,6 +2336,7 @@ }, "node_modules/npm/node_modules/@tootallnate/once": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2324,11 +2345,13 @@ }, "node_modules/npm/node_modules/abbrev": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/agent-base": { "version": "6.0.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2340,6 +2363,7 @@ }, "node_modules/npm/node_modules/agentkeepalive": { "version": "4.2.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2353,6 +2377,7 @@ }, "node_modules/npm/node_modules/aggregate-error": { "version": "3.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2365,6 +2390,7 @@ }, "node_modules/npm/node_modules/ansi-regex": { "version": "5.0.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2373,6 +2399,7 @@ }, "node_modules/npm/node_modules/ansi-styles": { "version": "4.3.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2387,16 +2414,19 @@ }, "node_modules/npm/node_modules/aproba": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/archy": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/are-we-there-yet": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2409,16 +2439,19 @@ }, "node_modules/npm/node_modules/asap": { "version": "2.0.6", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/bin-links": { "version": "3.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2435,6 +2468,7 @@ }, "node_modules/npm/node_modules/bin-links/node_modules/npm-normalize-package-bin": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -2443,6 +2477,7 @@ }, "node_modules/npm/node_modules/binary-extensions": { "version": "2.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2451,6 +2486,7 @@ }, "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2459,6 +2495,7 @@ }, "node_modules/npm/node_modules/builtins": { "version": "5.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2467,6 +2504,7 @@ }, "node_modules/npm/node_modules/cacache": { "version": "16.1.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2495,6 +2533,7 @@ }, "node_modules/npm/node_modules/chalk": { "version": "4.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2510,6 +2549,7 @@ }, "node_modules/npm/node_modules/chownr": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -2518,6 +2558,7 @@ }, "node_modules/npm/node_modules/cidr-regex": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -2529,6 +2570,7 @@ }, "node_modules/npm/node_modules/clean-stack": { "version": "2.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2537,6 +2579,7 @@ }, "node_modules/npm/node_modules/cli-columns": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2549,6 +2592,7 @@ }, "node_modules/npm/node_modules/cli-table3": { "version": "0.6.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2563,6 +2607,7 @@ }, "node_modules/npm/node_modules/clone": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2571,6 +2616,7 @@ }, "node_modules/npm/node_modules/cmd-shim": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2582,6 +2628,7 @@ }, "node_modules/npm/node_modules/color-convert": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2593,11 +2640,13 @@ }, "node_modules/npm/node_modules/color-name": { "version": "1.1.4", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/color-support": { "version": "1.1.3", + "dev": true, "inBundle": true, "license": "ISC", "bin": { @@ -2606,6 +2655,7 @@ }, "node_modules/npm/node_modules/columnify": { "version": "1.6.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2618,21 +2668,25 @@ }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/concat-map": { "version": "0.0.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/console-control-strings": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -2644,6 +2698,7 @@ }, "node_modules/npm/node_modules/debug": { "version": "4.3.4", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2660,11 +2715,13 @@ }, "node_modules/npm/node_modules/debug/node_modules/ms": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/debuglog": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2673,6 +2730,7 @@ }, "node_modules/npm/node_modules/defaults": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2681,11 +2739,13 @@ }, "node_modules/npm/node_modules/delegates": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/depd": { "version": "1.1.2", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2694,6 +2754,7 @@ }, "node_modules/npm/node_modules/dezalgo": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2703,6 +2764,7 @@ }, "node_modules/npm/node_modules/diff": { "version": "5.1.0", + "dev": true, "inBundle": true, "license": "BSD-3-Clause", "engines": { @@ -2711,11 +2773,13 @@ }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/encoding": { "version": "0.1.13", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2725,6 +2789,7 @@ }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2733,16 +2798,19 @@ }, "node_modules/npm/node_modules/err-code": { "version": "2.0.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/fastest-levenshtein": { "version": "1.0.12", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/fs-minipass": { "version": "2.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2754,16 +2822,19 @@ }, "node_modules/npm/node_modules/fs.realpath": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/function-bind": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/gauge": { "version": "4.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2782,6 +2853,7 @@ }, "node_modules/npm/node_modules/glob": { "version": "8.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2800,11 +2872,13 @@ }, "node_modules/npm/node_modules/graceful-fs": { "version": "4.2.10", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/has": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2816,6 +2890,7 @@ }, "node_modules/npm/node_modules/has-flag": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2824,11 +2899,13 @@ }, "node_modules/npm/node_modules/has-unicode": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { "version": "5.2.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2840,11 +2917,13 @@ }, "node_modules/npm/node_modules/http-cache-semantics": { "version": "4.1.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2858,6 +2937,7 @@ }, "node_modules/npm/node_modules/https-proxy-agent": { "version": "5.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2870,6 +2950,7 @@ }, "node_modules/npm/node_modules/humanize-ms": { "version": "1.2.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2878,6 +2959,7 @@ }, "node_modules/npm/node_modules/iconv-lite": { "version": "0.6.3", + "dev": true, "inBundle": true, "license": "MIT", "optional": true, @@ -2890,6 +2972,7 @@ }, "node_modules/npm/node_modules/ignore-walk": { "version": "5.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2901,6 +2984,7 @@ }, "node_modules/npm/node_modules/imurmurhash": { "version": "0.1.4", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2909,6 +2993,7 @@ }, "node_modules/npm/node_modules/indent-string": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2917,11 +3002,13 @@ }, "node_modules/npm/node_modules/infer-owner": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/inflight": { "version": "1.0.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2931,11 +3018,13 @@ }, "node_modules/npm/node_modules/inherits": { "version": "2.0.4", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/ini": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -2944,6 +3033,7 @@ }, "node_modules/npm/node_modules/init-package-json": { "version": "3.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -2961,11 +3051,13 @@ }, "node_modules/npm/node_modules/ip": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/ip-regex": { "version": "4.3.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -2974,6 +3066,7 @@ }, "node_modules/npm/node_modules/is-cidr": { "version": "4.0.2", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -2985,6 +3078,7 @@ }, "node_modules/npm/node_modules/is-core-module": { "version": "2.10.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -2996,6 +3090,7 @@ }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -3004,21 +3099,25 @@ }, "node_modules/npm/node_modules/is-lambda": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/json-parse-even-better-errors": { "version": "2.3.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/json-stringify-nice": { "version": "1.1.4", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -3027,6 +3126,7 @@ }, "node_modules/npm/node_modules/jsonparse": { "version": "1.3.1", + "dev": true, "engines": [ "node >= 0.2.0" ], @@ -3035,16 +3135,19 @@ }, "node_modules/npm/node_modules/just-diff": { "version": "5.1.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/just-diff-apply": { "version": "5.4.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { "version": "6.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3059,6 +3162,7 @@ }, "node_modules/npm/node_modules/libnpmdiff": { "version": "4.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3077,6 +3181,7 @@ }, "node_modules/npm/node_modules/libnpmexec": { "version": "4.0.14", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3101,6 +3206,7 @@ }, "node_modules/npm/node_modules/libnpmfund": { "version": "3.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3112,6 +3218,7 @@ }, "node_modules/npm/node_modules/libnpmhook": { "version": "8.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3124,6 +3231,7 @@ }, "node_modules/npm/node_modules/libnpmorg": { "version": "4.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3136,6 +3244,7 @@ }, "node_modules/npm/node_modules/libnpmpack": { "version": "4.1.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3149,6 +3258,7 @@ }, "node_modules/npm/node_modules/libnpmpublish": { "version": "6.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3164,6 +3274,7 @@ }, "node_modules/npm/node_modules/libnpmsearch": { "version": "5.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3175,6 +3286,7 @@ }, "node_modules/npm/node_modules/libnpmteam": { "version": "4.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3187,6 +3299,7 @@ }, "node_modules/npm/node_modules/libnpmversion": { "version": "3.0.7", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3202,6 +3315,7 @@ }, "node_modules/npm/node_modules/lru-cache": { "version": "7.13.2", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -3210,6 +3324,7 @@ }, "node_modules/npm/node_modules/make-fetch-happen": { "version": "10.2.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3236,6 +3351,7 @@ }, "node_modules/npm/node_modules/minimatch": { "version": "5.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3247,6 +3363,7 @@ }, "node_modules/npm/node_modules/minipass": { "version": "3.3.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3258,6 +3375,7 @@ }, "node_modules/npm/node_modules/minipass-collect": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3269,6 +3387,7 @@ }, "node_modules/npm/node_modules/minipass-fetch": { "version": "2.1.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3285,6 +3404,7 @@ }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3296,6 +3416,7 @@ }, "node_modules/npm/node_modules/minipass-json-stream": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3305,6 +3426,7 @@ }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3316,6 +3438,7 @@ }, "node_modules/npm/node_modules/minipass-sized": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3327,6 +3450,7 @@ }, "node_modules/npm/node_modules/minizlib": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3339,6 +3463,7 @@ }, "node_modules/npm/node_modules/mkdirp": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -3350,6 +3475,7 @@ }, "node_modules/npm/node_modules/mkdirp-infer-owner": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3363,16 +3489,19 @@ }, "node_modules/npm/node_modules/ms": { "version": "2.1.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { "version": "0.0.8", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/negotiator": { "version": "0.6.3", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -3381,6 +3510,7 @@ }, "node_modules/npm/node_modules/node-gyp": { "version": "9.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3404,6 +3534,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { "version": "1.1.11", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3413,6 +3544,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/glob": { "version": "7.2.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3432,6 +3564,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { "version": "3.1.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3443,6 +3576,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3457,6 +3591,7 @@ }, "node_modules/npm/node_modules/nopt": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3471,6 +3606,7 @@ }, "node_modules/npm/node_modules/normalize-package-data": { "version": "4.0.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -3485,6 +3621,7 @@ }, "node_modules/npm/node_modules/npm-audit-report": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3496,6 +3633,7 @@ }, "node_modules/npm/node_modules/npm-bundled": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3507,6 +3645,7 @@ }, "node_modules/npm/node_modules/npm-bundled/node_modules/npm-normalize-package-bin": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -3515,6 +3654,7 @@ }, "node_modules/npm/node_modules/npm-install-checks": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -3526,11 +3666,13 @@ }, "node_modules/npm/node_modules/npm-normalize-package-bin": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/npm-package-arg": { "version": "9.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3545,6 +3687,7 @@ }, "node_modules/npm/node_modules/npm-packlist": { "version": "5.1.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3562,6 +3705,7 @@ }, "node_modules/npm/node_modules/npm-packlist/node_modules/npm-normalize-package-bin": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -3570,6 +3714,7 @@ }, "node_modules/npm/node_modules/npm-pick-manifest": { "version": "7.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3584,6 +3729,7 @@ }, "node_modules/npm/node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -3592,6 +3738,7 @@ }, "node_modules/npm/node_modules/npm-profile": { "version": "6.2.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3604,6 +3751,7 @@ }, "node_modules/npm/node_modules/npm-registry-fetch": { "version": "13.3.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3621,11 +3769,13 @@ }, "node_modules/npm/node_modules/npm-user-validate": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/npmlog": { "version": "6.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3640,6 +3790,7 @@ }, "node_modules/npm/node_modules/once": { "version": "1.4.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3648,6 +3799,7 @@ }, "node_modules/npm/node_modules/opener": { "version": "1.5.2", + "dev": true, "inBundle": true, "license": "(WTFPL OR MIT)", "bin": { @@ -3656,6 +3808,7 @@ }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3670,6 +3823,7 @@ }, "node_modules/npm/node_modules/pacote": { "version": "13.6.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3704,6 +3858,7 @@ }, "node_modules/npm/node_modules/parse-conflict-json": { "version": "2.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3717,6 +3872,7 @@ }, "node_modules/npm/node_modules/path-is-absolute": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -3725,6 +3881,7 @@ }, "node_modules/npm/node_modules/postcss-selector-parser": { "version": "6.0.10", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3737,6 +3894,7 @@ }, "node_modules/npm/node_modules/proc-log": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -3745,6 +3903,7 @@ }, "node_modules/npm/node_modules/promise-all-reject-late": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -3753,6 +3912,7 @@ }, "node_modules/npm/node_modules/promise-call-limit": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -3761,11 +3921,13 @@ }, "node_modules/npm/node_modules/promise-inflight": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3778,6 +3940,7 @@ }, "node_modules/npm/node_modules/promzard": { "version": "0.3.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3786,6 +3949,7 @@ }, "node_modules/npm/node_modules/qrcode-terminal": { "version": "0.12.0", + "dev": true, "inBundle": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" @@ -3793,6 +3957,7 @@ }, "node_modules/npm/node_modules/read": { "version": "1.0.7", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3804,6 +3969,7 @@ }, "node_modules/npm/node_modules/read-cmd-shim": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -3812,6 +3978,7 @@ }, "node_modules/npm/node_modules/read-package-json": { "version": "5.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3826,6 +3993,7 @@ }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "2.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3838,6 +4006,7 @@ }, "node_modules/npm/node_modules/read-package-json/node_modules/npm-normalize-package-bin": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -3846,6 +4015,7 @@ }, "node_modules/npm/node_modules/readable-stream": { "version": "3.6.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3859,6 +4029,7 @@ }, "node_modules/npm/node_modules/readdir-scoped-modules": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3870,6 +4041,7 @@ }, "node_modules/npm/node_modules/retry": { "version": "0.12.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -3878,6 +4050,7 @@ }, "node_modules/npm/node_modules/rimraf": { "version": "3.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3892,6 +4065,7 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.11", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -3901,6 +4075,7 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/glob": { "version": "7.2.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3920,6 +4095,7 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { "version": "3.1.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3931,6 +4107,7 @@ }, "node_modules/npm/node_modules/safe-buffer": { "version": "5.2.1", + "dev": true, "funding": [ { "type": "github", @@ -3950,12 +4127,14 @@ }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT", "optional": true }, "node_modules/npm/node_modules/semver": { "version": "7.3.7", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3970,6 +4149,7 @@ }, "node_modules/npm/node_modules/semver/node_modules/lru-cache": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -3981,16 +4161,19 @@ }, "node_modules/npm/node_modules/set-blocking": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/signal-exit": { "version": "3.0.7", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/smart-buffer": { "version": "4.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -4000,6 +4183,7 @@ }, "node_modules/npm/node_modules/socks": { "version": "2.7.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4013,6 +4197,7 @@ }, "node_modules/npm/node_modules/socks-proxy-agent": { "version": "7.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4026,6 +4211,7 @@ }, "node_modules/npm/node_modules/spdx-correct": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -4035,11 +4221,13 @@ }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.3.0", + "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4049,11 +4237,13 @@ }, "node_modules/npm/node_modules/spdx-license-ids": { "version": "3.0.11", + "dev": true, "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/ssri": { "version": "9.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4065,6 +4255,7 @@ }, "node_modules/npm/node_modules/string_decoder": { "version": "1.3.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4073,6 +4264,7 @@ }, "node_modules/npm/node_modules/string-width": { "version": "4.2.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4086,6 +4278,7 @@ }, "node_modules/npm/node_modules/strip-ansi": { "version": "6.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4097,6 +4290,7 @@ }, "node_modules/npm/node_modules/supports-color": { "version": "7.2.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4108,6 +4302,7 @@ }, "node_modules/npm/node_modules/tar": { "version": "6.1.11", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4124,16 +4319,19 @@ }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { "version": "1.3.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/treeverse": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -4142,6 +4340,7 @@ }, "node_modules/npm/node_modules/unique-filename": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4153,6 +4352,7 @@ }, "node_modules/npm/node_modules/unique-slug": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4164,11 +4364,13 @@ }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/validate-npm-package-license": { "version": "3.0.4", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -4178,6 +4380,7 @@ }, "node_modules/npm/node_modules/validate-npm-package-name": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4189,11 +4392,13 @@ }, "node_modules/npm/node_modules/walk-up-path": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/wcwidth": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -4202,6 +4407,7 @@ }, "node_modules/npm/node_modules/which": { "version": "2.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4216,6 +4422,7 @@ }, "node_modules/npm/node_modules/wide-align": { "version": "1.1.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4224,11 +4431,13 @@ }, "node_modules/npm/node_modules/wrappy": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/write-file-atomic": { "version": "4.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -4241,6 +4450,7 @@ }, "node_modules/npm/node_modules/yallist": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, diff --git a/package.json b/package.json index 8114282..210834f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scenepic", - "version": "1.0.19", + "version": "1.1.0", "description": "3D Visualization Made Easy", "author": "ScenePic Team", "license": "MIT", diff --git a/python_examples/overlap.py b/python_examples/overlap.py new file mode 100644 index 0000000..a5359ce --- /dev/null +++ b/python_examples/overlap.py @@ -0,0 +1,60 @@ +"""Test script for dropdown CSS""" + +import random +from typing import List +import numpy as np +import scenepic as sp + + +def _create_cubes(scene: sp.Scene, num_cubes: int) -> List[sp.Mesh]: + meshes = [] + for i in range(num_cubes): + color = np.random.rand(3) + mesh: sp.Mesh = scene.create_mesh(layer_id="cube{}".format(i), shared_color=color) + mesh.add_cube() + meshes.append(mesh) + + return meshes + + +def _add_objects(canvas: sp.Canvas3D, cubes: List[sp.Mesh]): + frame = canvas.create_frame() + num_cubes = np.random.randint(5, 10) + choices = np.random.choice(len(cubes), num_cubes, replace=False) + for index in choices: + pos = np.random.rand(3) * 2 - 1 + frame.add_mesh(cubes[index], sp.Transforms.Translate(pos)) + + +def _add_circles(canvas: sp.Canvas2D): + frame: sp.Frame2D = canvas.create_frame() + num_circles = np.random.randint(5, 10) + for i in range(num_circles): + pos = np.random.rand(2) * 100 + radius = np.random.random() * 10 + color = np.random.rand(3) + frame.add_circle(pos[0], pos[1], radius, fill_color=color, layer_id="circle{}".format(i)) + + +def _main(): + scene = sp.Scene() + + cubes = _create_cubes(scene, 20) + for row in range(1, 5): + for col in range(1, 5): + canvasID = "canvas" + str(row) + str(col) + if random.random() < 0.5: + canvas = scene.create_canvas_3d(canvasID, 100, 100) + _add_objects(canvas, cubes) + else: + canvas = scene.create_canvas_2d(canvasID, 100, 100) + _add_circles(canvas) + + scene.place(canvasID, str(row), str(col)) + + scene.grid("400px", "auto auto auto auto", "auto auto auto auto") + scene.save_as_html("overlap.html") + + +if __name__ == "__main__": + _main() diff --git a/src/scenepic/version.py b/src/scenepic/version.py index 80dcba6..4ec5bdc 100644 --- a/src/scenepic/version.py +++ b/src/scenepic/version.py @@ -3,4 +3,4 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -__version__ = "1.0.19" +__version__ = "1.1.0" diff --git a/tssrc/CSSStyles.ts b/tssrc/CSSStyles.ts index 0763c47..8d3b837 100644 --- a/tssrc/CSSStyles.ts +++ b/tssrc/CSSStyles.ts @@ -79,6 +79,7 @@ var css = ` top: 5px; right: 5px; user-select: none; + z-index: 10; } .scenepic-dropdown:after { @@ -99,6 +100,14 @@ var css = ` margin-top: -9px; } +.scenepic-left-table { + left: 1px +} + +.scenepic-right-table { + right: -1px +} + .scenepic-dropdown-table { list-style: none !important; margin: 0 !important; @@ -106,7 +115,6 @@ var css = ` position: absolute; top: 100%; border: inherit; - right: -1px; visibility: hidden; background-color: white; pointer-events: none; @@ -330,15 +338,14 @@ input.scenepic-volume-slider { var initialized = false; -export default function InitializeCSSStyles() -{ - if (initialized) return; - - var head = document.getElementsByTagName("head")[0]; - var style = document.createElement("style"); - style.type = "text/css"; - style.appendChild(document.createTextNode(css)); - head.appendChild(style); +export default function InitializeCSSStyles() { + if (initialized) return; - initialized = true; + var head = document.getElementsByTagName("head")[0]; + var style = document.createElement("style"); + style.type = "text/css"; + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + + initialized = true; } diff --git a/tssrc/Canvas2D.ts b/tssrc/Canvas2D.ts index aa27346..a6e04c4 100644 --- a/tssrc/Canvas2D.ts +++ b/tssrc/Canvas2D.ts @@ -1,30 +1,26 @@ import Misc from "./Misc" -import {ObjectCache, CanvasBase} from "./CanvasBase" +import { ObjectCache, CanvasBase } from "./CanvasBase" import { mat3, vec2 } from "gl-matrix"; -abstract class Primitive -{ - readonly layerId : string; +abstract class Primitive { + readonly layerId: string; - constructor(layerId: string) - { + constructor(layerId: string) { this.layerId = layerId; } - GetObjectCacheId() : string { return null; } + GetObjectCacheId(): string { return null; } abstract Draw(HTMLCanvasElement, CanvasRenderingContext2D, ObjectCache); } -class LinesPrimitive extends Primitive -{ - readonly strokeStyle : string; - readonly lineWidth : number; - readonly fillStyle : string; - readonly coordinates : Float32Array; - readonly closePath : boolean; +class LinesPrimitive extends Primitive { + readonly strokeStyle: string; + readonly lineWidth: number; + readonly fillStyle: string; + readonly coordinates: Float32Array; + readonly closePath: boolean; - constructor(strokeStyle : string, lineWidth : number, fillStyle : string, coordinates : Float32Array, closePath : boolean, layerId : string) - { + constructor(strokeStyle: string, lineWidth: number, fillStyle: string, coordinates: Float32Array, closePath: boolean, layerId: string) { super(layerId); this.strokeStyle = strokeStyle; this.lineWidth = lineWidth; @@ -32,22 +28,19 @@ class LinesPrimitive extends Primitive this.coordinates = coordinates; this.closePath = closePath; } - - Draw(canvas : HTMLCanvasElement, context : CanvasRenderingContext2D, objectCache : ObjectCache) - { + + Draw(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, objectCache: ObjectCache) { context.beginPath(); context.moveTo(this.coordinates[0] * window.devicePixelRatio, this.coordinates[1] * window.devicePixelRatio); - for(var i = 0; i < this.coordinates.length; i += 2) + for (var i = 0; i < this.coordinates.length; i += 2) context.lineTo(this.coordinates[i] * window.devicePixelRatio, this.coordinates[i + 1] * window.devicePixelRatio); if (this.closePath) context.closePath(); - if (this.fillStyle != null) - { + if (this.fillStyle != null) { context.fillStyle = this.fillStyle; context.fill(); } - if (this.strokeStyle != null && this.lineWidth > 0) - { + if (this.strokeStyle != null && this.lineWidth > 0) { context.strokeStyle = this.strokeStyle; context.lineWidth = this.lineWidth; context.stroke(); @@ -55,16 +48,14 @@ class LinesPrimitive extends Primitive } } -class CirclePrimitive extends Primitive -{ - readonly strokeStyle : string; - readonly lineWidth : number; - readonly fillStyle : string; - readonly center : Float32Array; - readonly radius : number; +class CirclePrimitive extends Primitive { + readonly strokeStyle: string; + readonly lineWidth: number; + readonly fillStyle: string; + readonly center: Float32Array; + readonly radius: number; - constructor(strokeStyle : string, lineWidth : number, fillStyle : string, center : Float32Array, radius : number, layerId : string) - { + constructor(strokeStyle: string, lineWidth: number, fillStyle: string, center: Float32Array, radius: number, layerId: string) { super(layerId); this.strokeStyle = strokeStyle; this.lineWidth = lineWidth; @@ -72,18 +63,15 @@ class CirclePrimitive extends Primitive this.center = center; this.radius = radius; } - - Draw(canvas : HTMLCanvasElement, context : CanvasRenderingContext2D, objectCache : ObjectCache) - { + + Draw(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, objectCache: ObjectCache) { context.beginPath(); context.arc(this.center[0] * window.devicePixelRatio, this.center[1] * window.devicePixelRatio, this.radius * window.devicePixelRatio, 0.0, 2.0 * Math.PI, false); - if (this.fillStyle != null) - { + if (this.fillStyle != null) { context.fillStyle = this.fillStyle; context.fill(); } - if (this.strokeStyle != null && this.lineWidth > 0) - { + if (this.strokeStyle != null && this.lineWidth > 0) { context.strokeStyle = this.strokeStyle; context.lineWidth = this.lineWidth; context.stroke(); @@ -91,16 +79,14 @@ class CirclePrimitive extends Primitive } } -class TextPrimitive extends Primitive -{ - readonly text : string; - readonly fillStyle : string; - readonly sizeInPixels : number; - readonly fontFamily : string; - readonly position : Float32Array; +class TextPrimitive extends Primitive { + readonly text: string; + readonly fillStyle: string; + readonly sizeInPixels: number; + readonly fontFamily: string; + readonly position: Float32Array; - constructor(text : string, fillStyle : string, sizeInPixels : number, fontFamily : string, position : Float32Array, layerId : string) - { + constructor(text: string, fillStyle: string, sizeInPixels: number, fontFamily: string, position: Float32Array, layerId: string) { super(layerId); this.text = text; this.fillStyle = fillStyle; @@ -109,8 +95,7 @@ class TextPrimitive extends Primitive this.position = position; } - Draw(canvas : HTMLCanvasElement, context : CanvasRenderingContext2D, objectCache : ObjectCache) - { + Draw(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, objectCache: ObjectCache) { context.fillStyle = this.fillStyle; let sizeInPixels = this.sizeInPixels * window.devicePixelRatio; let left = this.position[0] * window.devicePixelRatio; @@ -121,16 +106,14 @@ class TextPrimitive extends Primitive } } -class ImagePrimitive extends Primitive -{ - readonly imageId : string; - readonly positionType : string; - readonly position : Float32Array - readonly scale : number; - readonly smoothed : boolean; +class ImagePrimitive extends Primitive { + readonly imageId: string; + readonly positionType: string; + readonly position: Float32Array + readonly scale: number; + readonly smoothed: boolean; - constructor(imageId : string, positionType : string, position : Float32Array, scale : number, smoothed : boolean, layerId : string) - { + constructor(imageId: string, positionType: string, position: Float32Array, scale: number, smoothed: boolean, layerId: string) { super(layerId); this.imageId = imageId; this.positionType = positionType; @@ -139,13 +122,11 @@ class ImagePrimitive extends Primitive this.smoothed = smoothed; } - GetObjectCacheId() - { + GetObjectCacheId() { return this.imageId; } - Draw(canvas : HTMLCanvasElement, context : CanvasRenderingContext2D, objectCache : ObjectCache) - { + Draw(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, objectCache: ObjectCache) { // Get current image var currentImage = objectCache.GetObject(this.imageId); if (currentImage == null) return; // Indicates image not available (e.g. not completely loaded yet) @@ -157,8 +138,7 @@ class ImagePrimitive extends Primitive var centered = true; var scaleX = canvas.clientWidth / currentImage.naturalWidth; var scaleY = canvas.clientHeight / currentImage.naturalHeight; - switch(this.positionType) - { + switch (this.positionType) { case "fill": scaleX = scaleY = Math.max(scaleX, scaleY); break; @@ -184,16 +164,14 @@ class ImagePrimitive extends Primitive } } -class VideoPrimitive extends Primitive -{ - readonly videoId : string; - readonly positionType : string; - readonly position : Float32Array - readonly scale : number; - readonly smoothed : boolean; +class VideoPrimitive extends Primitive { + readonly videoId: string; + readonly positionType: string; + readonly position: Float32Array + readonly scale: number; + readonly smoothed: boolean; - constructor(videoId : string, positionType : string, position : Float32Array, scale : number, smoothed : boolean, layerId : string) - { + constructor(videoId: string, positionType: string, position: Float32Array, scale: number, smoothed: boolean, layerId: string) { super(layerId); this.videoId = videoId; this.positionType = positionType; @@ -202,15 +180,13 @@ class VideoPrimitive extends Primitive this.smoothed = smoothed; } - GetObjectCacheId() - { + GetObjectCacheId() { return this.videoId; } - Draw(canvas : HTMLCanvasElement, context : CanvasRenderingContext2D, objectCache : ObjectCache) - { + Draw(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D, objectCache: ObjectCache) { // Get current image - let currentVideo : HTMLVideoElement = objectCache.GetObject(this.videoId); + let currentVideo: HTMLVideoElement = objectCache.GetObject(this.videoId); if (currentVideo == null) return; // Indicates video not available (e.g. not completely loaded yet) // Turn off image smoothing @@ -220,8 +196,7 @@ class VideoPrimitive extends Primitive let centered = true; let scaleX = canvas.clientWidth / currentVideo.videoWidth; let scaleY = canvas.clientHeight / currentVideo.videoHeight; - switch(this.positionType) - { + switch (this.positionType) { case "fill": scaleX = scaleY = Math.max(scaleX, scaleY); break; @@ -248,30 +223,31 @@ class VideoPrimitive extends Primitive } -export default class Canvas2D extends CanvasBase -{ - context : CanvasRenderingContext2D = null; // Rendering context +export default class Canvas2D extends CanvasBase { + context: CanvasRenderingContext2D = null; // Rendering context - backgroundStyle : string; // Background color + backgroundStyle: string; // Background color // Dictionary from primitiveId to layer settings (wireframe, filled, opacity) - layerSettings : {[layerId: string]: {[key: string]: number|boolean}} = {}; - layerIds : string[] = []; + layerSettings: { [layerId: string]: { [key: string]: number | boolean } } = {}; + layerIds: string[] = []; globalFill = true; globalOpacity = 1.0; - framePrimitives : Primitive[][] = []; // The primitives for each frame [frameIndex] + framePrimitives: Primitive[][] = []; // The primitives for each frame [frameIndex] - currentPrimitives : Primitive[] = null; // The primitives for the currently displayed frame + currentPrimitives: Primitive[] = null; // The primitives for the currently displayed frame - frameCoordinates : Float32Array[] = []; // The coordinates for each frame [frameIndex] + frameCoordinates: Float32Array[] = []; // The coordinates for each frame [frameIndex] - scale : number; - focusPoint : vec2; - initFocusPoint : vec2; + center: vec2; + scale: number; + angle: number; + focusPoint: vec2; + focusPointDelta: vec2; + transform: mat3; - constructor(canvasId : string, public frameRate : number, public width : number, public height : number, objectCache : ObjectCache, public SetStatus : (status : string) => void, public SetWarning : (message : string) => void, public RequestRedraw : () => void, public ReportFrameIdChange : (canvasId : string, frameId : string) => void) - { + constructor(canvasId: string, public frameRate: number, public width: number, public height: number, objectCache: ObjectCache, public SetStatus: (status: string) => void, public SetWarning: (message: string) => void, public RequestRedraw: () => void, public ReportFrameIdChange: (canvasId: string, frameId: string) => void) { // Base class constructor super(canvasId, frameRate, width, height, objectCache, SetStatus, SetWarning, RequestRedraw, ReportFrameIdChange); @@ -279,6 +255,8 @@ export default class Canvas2D extends CanvasBase this.context = this.htmlCanvas.getContext('2d'); this.backgroundStyle = "#000000"; + this.center = vec2.fromValues(width, height); + vec2.scale(this.center, this.center, window.devicePixelRatio / 2); this.ResetView(); @@ -287,15 +265,14 @@ export default class Canvas2D extends CanvasBase } // Adds UI for the control of certain layers - SetLayerSettings(layerSettings : any) - { + SetLayerSettings(layerSettings: any) { // Delete any previous controls while (this.dropdownTable.childElementCount > 2) this.dropdownTable.removeChild(this.dropdownTable.lastChild); this.layerSettings = layerSettings; this.layerIds = [null]; - for(let layerId in layerSettings){ + for (let layerId in layerSettings) { this.layerIds.push(layerId); } @@ -303,8 +280,7 @@ export default class Canvas2D extends CanvasBase var BorderStyle = "1px solid #cccccc"; var headerRow = document.createElement("tr"); headerRow.style.borderBottom = BorderStyle; - for(var headerName of ["Visible", "Opacity", "Layer Id"]) - { + for (var headerName of ["Visible", "Opacity", "Layer Id"]) { var headerItem = document.createElement("th"); headerItem.className = "scenepic-dropdown-header"; headerItem.innerHTML = headerName; @@ -313,8 +289,7 @@ export default class Canvas2D extends CanvasBase this.dropdownTable.appendChild(headerRow); // Create row helper function - var createRow = (id, label) => - { + var createRow = (id, label) => { // Create fill checkbox var checkboxFill = document.createElement("input"); checkboxFill.type = "checkbox"; @@ -338,14 +313,12 @@ export default class Canvas2D extends CanvasBase return [checkboxFill, sliderOpacity, labelLayer]; }; - var addRow = (rowItems, border) => - { + var addRow = (rowItems, border) => { // Create table row var tr = document.createElement("tr"); if (border) tr.style.borderTop = BorderStyle; - var addControl = (el, className) => - { + var addControl = (el, className) => { var td = document.createElement("td"); td.appendChild(el); td.className = className; @@ -362,8 +335,7 @@ export default class Canvas2D extends CanvasBase addRow(createRow("<<>>", "Global"), true); } - ShowLayerFilled(layerId : string) : boolean - { + ShowLayerFilled(layerId: string): boolean { if (layerId == "<<>>") return this.globalFill; if (layerId == null) @@ -375,28 +347,24 @@ export default class Canvas2D extends CanvasBase return true; } - ToggleLayerFilled(index: number) - { - if(index >= this.layerIds.length) { + ToggleLayerFilled(index: number) { + if (index >= this.layerIds.length) { return } let layerId = this.layerIds[index]; let filled = this.ShowLayerFilled(layerId); this.SetLayerFilled(layerId, !filled); - } + } - SetLayerFilled(layerId : string, filled : boolean) - { + SetLayerFilled(layerId: string, filled: boolean) { if (layerId == null) return; - if (layerId == "<<>>") - { + if (layerId == "<<>>") { this.globalFill = filled; } - else - { + else { if (!(layerId in this.layerSettings)) this.layerSettings[layerId] = {}; @@ -407,8 +375,7 @@ export default class Canvas2D extends CanvasBase this.PrepareBuffers(); } - GetLayerOpacity(layerId : string) : number - { + GetLayerOpacity(layerId: string): number { if (layerId == "<<>>") return this.globalOpacity; if (layerId == null || !(layerId in this.layerSettings) || !("opacity" in this.layerSettings[layerId])) @@ -417,17 +384,14 @@ export default class Canvas2D extends CanvasBase return this.layerSettings[layerId]["opacity"]; } - SetLayerOpacity(layerId : string, opacity : number) - { + SetLayerOpacity(layerId: string, opacity: number) { if (layerId == null) return; - if (layerId == "<<>>") - { + if (layerId == "<<>>") { this.globalOpacity = opacity; } - else - { + else { if (!(layerId in this.layerSettings)) this.layerSettings[layerId] = {}; this.layerSettings[layerId]["opacity"] = opacity; @@ -435,10 +399,9 @@ export default class Canvas2D extends CanvasBase // Prepare buffers and force redraw this.PrepareBuffers(); - } + } - GetLayerRenderOrder(layerId : string) : number - { + GetLayerRenderOrder(layerId: string): number { if (layerId == null || !(layerId in this.layerSettings) || !("renderOrder" in this.layerSettings[layerId])) return -1e3; // Backmost layer - bit of a hack else @@ -446,10 +409,8 @@ export default class Canvas2D extends CanvasBase } // Execute a single canvas command - ExecuteCanvasCommand(command : any) - { - switch(command["CommandType"]) - { + ExecuteCanvasCommand(command: any) { + switch (command["CommandType"]) { case "SetBackgroundStyle": this.backgroundStyle = String(command["Value"]); break; @@ -470,32 +431,31 @@ export default class Canvas2D extends CanvasBase } } - AddPrimitive(frameIndex : number, primitive : Primitive) - { - if(primitive.layerId == null){ + AddPrimitive(frameIndex: number, primitive: Primitive) { + if (primitive.layerId == null) { this.framePrimitives[frameIndex].push(primitive); - }else{ - if(!(primitive.layerId in this.layerSettings)){ + } else { + if (!(primitive.layerId in this.layerSettings)) { this.layerSettings[primitive.layerId] = {}; this.SetLayerSettings(this.layerSettings); } let renderOrder = this.GetLayerRenderOrder(primitive.layerId); let index = 0; - let lastId : string = null; - for(let other of this.framePrimitives[frameIndex]){ - if(other.layerId == lastId){ + let lastId: string = null; + for (let other of this.framePrimitives[frameIndex]) { + if (other.layerId == lastId) { index += 1; continue; } - - if(other.layerId == primitive.layerId){ + + if (other.layerId == primitive.layerId) { break; } lastId = other.layerId; let otherOrder = this.GetLayerRenderOrder(other.layerId); - if(renderOrder < otherOrder){ + if (renderOrder < otherOrder) { break; } @@ -505,41 +465,37 @@ export default class Canvas2D extends CanvasBase } if (this.currentFrameIndex == frameIndex) - this.PrepareBuffers(); + this.PrepareBuffers(); } - SetCoordinates(frameIndex: number, coordinates: Float32Array) - { + SetCoordinates(frameIndex: number, coordinates: Float32Array) { this.frameCoordinates[frameIndex] = coordinates; } - GetCoordinate(frameIndex: number, index: number) - { + GetCoordinate(frameIndex: number, index: number) { let start = index * 2; let end = start + 2; return this.frameCoordinates[frameIndex].slice(start, end); } - AddLines(frameIndex: number, lines: Uint16Array, style: Uint8Array, width: Float32Array, layerIds: [string, number][]) - { + AddLines(frameIndex: number, lines: Uint16Array, style: Uint8Array, width: Float32Array, layerIds: [string, number][]) { let numLines = width.length; let layerId = null; let nextLayer = numLines; let currentLayer = 0; - if(layerIds != null){ + if (layerIds != null) { layerId = layerIds[0][0]; nextLayer = currentLayer + layerIds[0][1]; } - for(let i=0; i Math.abs(dx)) { + delta = dy; + } + return 2 * delta / (window.devicePixelRatio * this.htmlCanvas.clientWidth); } - private setTransform() : void - { - let transform = this.getTransform(); + private updateTransform() { + const toFocus = mat3.fromTranslation(mat3.create(), this.focusPoint); + const fromCenter = mat3.fromTranslation(mat3.create(), vec2.negate(vec2.create(), this.center)); + const scale = mat3.fromScaling(mat3.create(), vec2.fromValues(this.scale, this.scale)); + const rotate = mat3.fromRotation(mat3.create(), this.angle); + + this.transform = mat3.create(); + mat3.multiply(this.transform, fromCenter, this.transform); + mat3.multiply(this.transform, scale, this.transform); + mat3.multiply(this.transform, rotate, this.transform); + mat3.multiply(this.transform, toFocus, this.transform); + } + + private setTransform(): void { this.context.setTransform( - transform[0], transform[1], - transform[3], transform[4], - transform[6], transform[7]) + this.transform[0], this.transform[1], + this.transform[3], this.transform[4], + this.transform[6], this.transform[7]) } // Render scene method - Render() - { + Render() { // Clear this.context.fillStyle = this.backgroundStyle; - const inv = mat3.invert(mat3.create(), this.getTransform()); - let x = 0; - let y = 0; + this.context.resetTransform(); let width = this.htmlCanvas.clientWidth * window.devicePixelRatio; let height = this.htmlCanvas.clientHeight * window.devicePixelRatio; - if(inv[0] > 1){ - const tl = vec2.transformMat3(vec2.create(), vec2.fromValues(0, 0), inv); - const br = vec2.transformMat3(vec2.create(), vec2.fromValues(width, height), inv); - x = tl[0]; - y = tl[1]; - width = br[0] - x; - height = br[1] - y; - } - this.context.fillRect(x, y, width, height); + this.context.fillRect(0, 0, width, height); + this.setTransform(); // Composite primitives if (this.currentPrimitives == null) return; - for(var primitive of this.currentPrimitives){ + for (var primitive of this.currentPrimitives) { let opacity = this.GetLayerOpacity(primitive.layerId) * this.globalOpacity; let filled = this.ShowLayerFilled(primitive.layerId) && this.globalFill; - if(!filled || opacity == 0.0){ + if (!filled || opacity == 0.0) { continue; } @@ -750,14 +698,12 @@ export default class Canvas2D extends CanvasBase } } - StartPlaying() - { + StartPlaying() { // pre-cache - for(let primitives of this.framePrimitives){ - for(let primitive of primitives){ + for (let primitives of this.framePrimitives) { + for (let primitive of primitives) { let cacheId = primitive.GetObjectCacheId(); - if(cacheId != null) - { + if (cacheId != null) { this.objectCache.AddUser(cacheId); } } @@ -766,35 +712,31 @@ export default class Canvas2D extends CanvasBase super.StartPlaying(); } - StopPlaying() - { - super.StopPlaying(); + StopPlaying() { + super.StopPlaying(); // free up the cache - for(let primitives of this.framePrimitives){ - for(let primitive of primitives){ + for (let primitives of this.framePrimitives) { + for (let primitive of primitives) { let imageId = primitive.GetObjectCacheId(); - if(imageId != null) - { + if (imageId != null) { this.objectCache.RemoveUser(imageId); } } } } - HandleKeyDown(key : string) : [boolean, boolean] - { + HandleKeyDown(key: string): [boolean, boolean] { var result = super.HandleKeyDown(key); if (result[0]) return result; // Already handled let handled = true; key = key.toLowerCase(); - switch(key) - { + switch (key) { case "r": this.ResetView(); break; - + default: handled = false; break; @@ -803,32 +745,72 @@ export default class Canvas2D extends CanvasBase return [handled, false]; } - HandlePointerDown(point: vec2, event: PointerEvent) : void { - this.initFocusPoint = vec2.copy(vec2.create(), this.focusPoint); - super.HandlePointerDown(point, event); + pointerCenter(): vec2 { + if (this.pointerCoords.size == 1) { + return this.pointerCoords.get(this.pid0); + } else { + const p0 = this.pointerCoords.get(this.pid0); + const p1 = this.pointerCoords.get(this.pid1); + let center = vec2.add(vec2.create(), p0, p1); + vec2.scale(center, center, 0.5); + return center; + } } - HandlePointerMove(point: vec2, event: PointerEvent): void { + updateFocusPointDelta(): void { + if (this.pointerCoords.size == 0) { + return + } + + const center = this.pointerCenter(); + const focusPoint = vec2.scale(vec2.create(), this.focusPoint, 1 / window.devicePixelRatio); + this.focusPointDelta = vec2.subtract(vec2.create(), focusPoint, center); + } + + HandlePointerDown(point: vec2, event: PointerEvent): void { + super.HandlePointerDown(point, event); + + this.updateFocusPointDelta(); + this.updateTransform(); + } + + HandlePointerUp(event: PointerEvent): void { + super.HandlePointerUp(event); + + this.updateFocusPointDelta(); + this.updateTransform(); + } + + HandlePointerMoveWithTwist(point: vec2, twistAngle: number, event: PointerEvent): void { const countPointers = this.pointerCoords.size; - if (countPointers == 0) return; + if (countPointers == 0 || countPointers > 2) return; - const init = this.initPointerCoords.get(event.pointerId); - const transform = this.getTransform(); - const inv = mat3.invert(mat3.create(), transform); + if (countPointers == 2) { + const pinchZoom = this.PinchZoom(point, event); + this.scale = this.scale * pinchZoom.distanceRatio; + this.angle = this.angle + pinchZoom.angleDelta; - const source = vec2.transformMat3(vec2.create(), init, inv); - const dest = vec2.transformMat3(vec2.create(), point, inv); - const delta = vec2.subtract(vec2.create(), source, dest); - - vec2.add(this.focusPoint, this.initFocusPoint, delta); + // Set focus to mean of two points + vec2.add(this.focusPoint, pinchZoom.center, this.focusPointDelta); + vec2.scale(this.focusPoint, this.focusPoint, window.devicePixelRatio); + } else { + this.angle += twistAngle; + + if (twistAngle == 0) { + vec2.add(this.focusPoint, point, this.focusPointDelta); + vec2.scale(this.focusPoint, this.focusPoint, window.devicePixelRatio); + } + } + + this.updateTransform(); this.setTransform(); - super.HandlePointerMove(point, event); } HandleMouseWheel(event: WheelEvent): void { const factor = Math.pow(1.1, event.deltaY > 0 ? -1 : 1); this.scale *= factor; + this.updateTransform(); this.setTransform(); } } diff --git a/tssrc/Canvas3D.ts b/tssrc/Canvas3D.ts index 3bc1692..47058f2 100644 --- a/tssrc/Canvas3D.ts +++ b/tssrc/Canvas3D.ts @@ -1,6 +1,6 @@ -import {mat3, mat4, vec3, vec4, quat, vec2} from "gl-matrix"; +import { mat3, mat4, vec3, vec4, quat, vec2 } from "gl-matrix"; import Misc from "./Misc" -import {ObjectCache, CanvasBase} from "./CanvasBase" +import { ObjectCache, CanvasBase } from "./CanvasBase" import Mesh from "./Mesh"; import ShaderProgram from "./Shaders"; import WebGLMeshBuffers from "./WebGLMeshBuffers"; @@ -21,10 +21,10 @@ var FocusPointMeshDefinition = { var DefaultCamera = { "WorldToCamera": mat4.fromValues(1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, -4.0, 1.0), - "FoVYDegrees" : 45.0 + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, -4.0, 1.0), + "FoVYDegrees": 45.0 }; var DefaultNearCropDistance = 0.01; @@ -34,36 +34,33 @@ var DegreesToRadians = Math.PI / 180.0; // Default shading parameters var DefaultShading = { - "BackgroundColor" : [0.0, 0.0, 0.0, 1.0], - "AmbientLightColor" : [0.5, 0.5, 0.5], - "DirectionalLightColor" : [0.5, 0.5, 0.5], - "DirectionalLightDir" : [2.0, -1.0, 1.0] + "BackgroundColor": [0.0, 0.0, 0.0, 1.0], + "AmbientLightColor": [0.5, 0.5, 0.5], + "DirectionalLightColor": [0.5, 0.5, 0.5], + "DirectionalLightDir": [2.0, -1.0, 1.0] }; // Represents an instantiation of a mesh in a frame -class MeshInstance -{ - meshId : string; - transform : mat4; +class MeshInstance { + meshId: string; + transform: mat4; - constructor(meshId : string, transform : mat4) - { + constructor(meshId: string, transform: mat4) { this.meshId = meshId; this.transform = transform; } } -class MeshData -{ +class MeshData { constructor( - public mesh : Mesh, - public m2wMatrix : mat4, - public m2vMatrix : mat4, - public buffer : WebGLMeshBuffers, - public opacity : number, - public filled : boolean, - public wireframe : boolean, - public viewDistance : number){} + public mesh: Mesh, + public m2wMatrix: mat4, + public m2vMatrix: mat4, + public buffer: WebGLMeshBuffers, + public opacity: number, + public filled: boolean, + public wireframe: boolean, + public viewDistance: number) { } public Render(gl: WebGL2RenderingContext, v2sMatrix: mat4) { // Optionally turn off back-face culling @@ -76,27 +73,27 @@ class MeshData this.buffer.RenderBuffer(v2sMatrix, this.m2vMatrix, this.opacity, this.filled, this.wireframe); } - public ComputeCentroid() : vec3 { + public ComputeCentroid(): vec3 { let centroid = vec3.create(); const norm = 1.0 / this.mesh.CountVertices(); - for(let i=0, j=0; ithis.mesh.vertexBuffer.subarray(j, j + 3), - this.m2wMatrix); + this.mesh.vertexBuffer.subarray(j, j + 3), + this.m2wMatrix); vec3.scale(p, p, norm) vec3.add(centroid, centroid, p); } - if(this.mesh.CountInstances() == 1){ + if (this.mesh.CountInstances() == 1) { return centroid; } let instanceCentroid = vec3.create(); const instanceNorm = 1.0 / this.mesh.CountInstances(); - for(let i=0, j=0; i < this.mesh.CountInstances(); i++, j += this.mesh.ElementsPerInstance){ + for (let i = 0, j = 0; i < this.mesh.CountInstances(); i++, j += this.mesh.ElementsPerInstance) { let p = vec3.add(vec3.create(), - this.mesh.instanceBuffer.subarray(j, j + 3), - instanceCentroid); + this.mesh.instanceBuffer.subarray(j, j + 3), + instanceCentroid); vec3.scale(p, p, instanceNorm); vec3.add(instanceCentroid, instanceCentroid, p); } @@ -105,35 +102,34 @@ class MeshData } } -export default class Canvas3D extends CanvasBase -{ - allMeshes : any = null // Maps from meshIds to mesh objects - gl : WebGL2RenderingContext; - sp : ShaderProgram; +export default class Canvas3D extends CanvasBase { + allMeshes: any = null // Maps from meshIds to mesh objects + gl: WebGL2RenderingContext; + sp: ShaderProgram; // Dictionary from layerId to layer settings (wireframe, filled, opacity) - layerSettings : {[layerId: string]: {[key: string]: number|boolean}} = {}; - layerIds : string[] = []; + layerSettings: { [layerId: string]: { [key: string]: number | boolean } } = {}; + layerIds: string[] = []; // Shading parameters - bgColor : vec4; - ambientLightColor : vec3; - directionalLightColor : vec3; - directionalLightDir : vec3; + bgColor: vec4; + ambientLightColor: vec3; + directionalLightColor: vec3; + directionalLightDir: vec3; globalFill = true; globalWireframe = false; globalOpacity = 1.0; // Mesh picker - setFocusToPicked : boolean; - pickPoint : vec2; - meshPicker : MeshPicker; + setFocusToPicked: boolean; + pickPoint: vec2; + meshPicker: MeshPicker; // Frame instances - frameInstances : MeshInstance[][] = []; // [frameIndex][mesh instance within frame] + frameInstances: MeshInstance[][] = []; // [frameIndex][mesh instance within frame] // Mesh buffers *for the current frame* - meshBuffers : any = {}; // The webgl mesh buffers for the currently selected frame: dictionary from meshId to webGLMeshBuffer + meshBuffers: any = {}; // The webgl mesh buffers for the currently selected frame: dictionary from meshId to webGLMeshBuffer // Pointer speeds pointerAltKeyMultiplier = 0.2; @@ -160,19 +156,19 @@ export default class Canvas3D extends CanvasBase } // Focus points for user interaction: center of rotation and used for locking view - globalFocusPoint : Float32Array = new Float32Array([0.0, 0.0, 0.0]); - initialFocusPoints : Float32Array[] = []; // Per-frame focus points (initial version - used for reset camera) - currentFocusPoints : Float32Array[] = []; // Per-frame focus points (current version - used for display) - focusPointMeshBuffer : WebGLMeshBuffers; - showFocusPoint : boolean = false; + globalFocusPoint: Float32Array = new Float32Array([0.0, 0.0, 0.0]); + initialFocusPoints: Float32Array[] = []; // Per-frame focus points (initial version - used for reset camera) + currentFocusPoints: Float32Array[] = []; // Per-frame focus points (current version - used for display) + focusPointMeshBuffer: WebGLMeshBuffers; + showFocusPoint: boolean = false; // Copy of last set camera config - globalCameraParams : Object = null; - frameCameraParams : Object[] = []; // Per-frame cameras - onCameraTrack : boolean = false; + globalCameraParams: Object = null; + frameCameraParams: Object[] = []; // Per-frame cameras + onCameraTrack: boolean = false; // Frame layer settings - frameLayerSettings : Object[] = []; + frameLayerSettings: Object[] = []; // Lock view settings lockViewXY = false; @@ -180,14 +176,13 @@ export default class Canvas3D extends CanvasBase // Orbit settings orbitCamera = false; - lastOrbitTime : Date = null; + lastOrbitTime: Date = null; // View matrices - w2vMatrix : mat4; // World to view (i.e. camera) - v2sMatrix : mat4; // View to screen (i.e. projection) + w2vMatrix: mat4; // World to view (i.e. camera) + v2sMatrix: mat4; // View to screen (i.e. projection) - constructor(canvasId : string, public frameRate : number, public width : number, public height : number, allMeshes : any, objectCache : ObjectCache, public SetStatus : (status : string) => void, public SetWarning : (message : string) => void, public RequestRedraw : () => void, public ReportFrameIdChange : (canvasId : string, frameId : string) => void, public SimulateKeyPress : (canvasId : string, key : string) => void) - { + constructor(canvasId: string, public frameRate: number, public width: number, public height: number, allMeshes: any, objectCache: ObjectCache, public SetStatus: (status: string) => void, public SetWarning: (message: string) => void, public RequestRedraw: () => void, public ReportFrameIdChange: (canvasId: string, frameId: string) => void, public SimulateKeyPress: (canvasId: string, key: string) => void) { // Base class constructor super(canvasId, frameRate, width, height, objectCache, SetStatus, SetWarning, RequestRedraw, ReportFrameIdChange); @@ -210,17 +205,15 @@ export default class Canvas3D extends CanvasBase this.cameraModeDisplay.style.visibility = "visible"; // Create dropdown - this.SetLayerSettings({}); + this.SetLayerSettings({}); // Start render loop this.StartRenderLoop(); } - InitializeWebGL() - { + InitializeWebGL() { // Init gl - try - { + try { // Get gl context this.gl = this.htmlCanvas.getContext("webgl2"); @@ -228,8 +221,7 @@ export default class Canvas3D extends CanvasBase this.gl.getExtension("OES_element_index_uint"); } catch (e) { } - if (this.gl == null) - { + if (this.gl == null) { this.SetWarning("Could not init WebGL"); return; } @@ -248,8 +240,7 @@ export default class Canvas3D extends CanvasBase this.PrepareBuffers(); } - TearDownWebGL() - { + TearDownWebGL() { this.gl = null; this.sp = null; if (this.focusPointMeshBuffer != null) @@ -258,105 +249,85 @@ export default class Canvas3D extends CanvasBase this.PrepareBuffers(); } - private updateCameraVelocityValue(keyPlus: string, keyMinus: string, index: number) - { - if (this.dirKeyPresses[keyPlus] > this.dirKeyPresses[keyMinus]) - { + private updateCameraVelocityValue(keyPlus: string, keyMinus: string, index: number) { + if (this.dirKeyPresses[keyPlus] > this.dirKeyPresses[keyMinus]) { this.cameraVelocity[index] = this.keyDownSpeed } - else if(this.dirKeyPresses[keyMinus] > 0) - { + else if (this.dirKeyPresses[keyMinus] > 0) { this.cameraVelocity[index] = -this.keyDownSpeed; } - else - { + else { this.cameraVelocity[index] = 0; - } + } } - private updateCameraVelocity() - { + private updateCameraVelocity() { this.updateCameraVelocityValue("w", "s", 0); this.updateCameraVelocityValue("a", "d", 1); this.updateCameraVelocityValue("q", "e", 2); } - - HandlePointerMoveWithTwist(point: vec2, twistAngle: number, event: PointerEvent) - { + + HandlePointerMoveWithTwist(point: vec2, twistAngle: number, event: PointerEvent) { const countPointers = this.pointerCoords.size; if (countPointers == 0) return; const old = this.pointerCoords.get(event.pointerId); let delta = vec2.subtract(vec2.create(), point, old); - if (event.altKey) - { + if (event.altKey) { vec2.scale(delta, delta, this.pointerAltKeyMultiplier); } - if(this.FirstPerson) - { + if (this.FirstPerson) { const init = this.initPointerCoords.get(event.pointerId); let diff = vec2.subtract(vec2.create(), point, init); const length = vec2.length(diff); const deadZone = 10; - if(length > deadZone) - { + if (length > deadZone) { let scale = Math.min(2, (length - deadZone) / deadZone); vec2.scale(diff, diff, scale / length); } - else - { + else { diff = vec2.create(); } vec2.scale(diff, diff, this.pointerRotationSpeed); this.SetCameraRotationalVelocity(diff); } - else - { + else if (countPointers == 2) { + // Deal with pinch-zoom + const pinchZoom = this.PinchZoom(point, event); + + // Change in distances + var zOld = this.GetCurrentFocusPointInViewSpace()[2]; + var zNew = zOld / pinchZoom.distanceRatio; + this.TranslateCamera(vec3.fromValues(0.0, 0.0, zNew - zOld)); + + const focusDelta = this.ComputeFocusPointRelativeViewSpaceTranslation(old, point); + this.TranslateCamera(focusDelta); + + this.RotateCamera(0.0, 0.0, -pinchZoom.angleDelta); + } + else { // Deal with basic events - if (event.ctrlKey) - { + if (event.ctrlKey) { // Treat as twist of camera this.RotateCamera(0.0, 0.0, twistAngle); } - else if (this.showFocusPoint) - { + else if (this.showFocusPoint) { // Translate the 3D center of rotation this.SetFocusPointPositionFromPixelCoordinates(point); } - else if (event.shiftKey || countPointers > 1) // Treat as translation of camera + else if (event.shiftKey) // Treat as translation of camera { const focusDelta = this.ComputeFocusPointRelativeViewSpaceTranslation(old, point); this.TranslateCamera(focusDelta); } - else // Treat as rotation of camera about center of rotation + else if (countPointers == 1) // Treat as rotation of camera about center of rotation { vec2.scale(delta, delta, this.pointerRotationSpeed); // NB y and x are deliberately crossed over - this.RotateCamera(delta[1], delta[0], 0.0); - } - - // Deal with pinch-zoom - if (countPointers == 2) - { - // Get other coordinate - let other : vec2; - for (let pid of this.pointerCoords.keys()) - { - if (pid == event.pointerId) continue; - other = this.pointerCoords.get(pid); - } - - // Compute delta between two points - const oldDist = vec2.distance(old, other); - const pointDist = vec2.distance(point, other); - - // Change in distances - var zOld = this.GetCurrentFocusPointInViewSpace()[2]; - var zNew = zOld * oldDist / pointDist; - this.TranslateCamera(vec3.fromValues(0.0, 0.0, zNew - zOld)); + this.RotateCamera(delta[1], delta[0], 0.0); } } @@ -364,48 +335,42 @@ export default class Canvas3D extends CanvasBase } HandlePointerUp(event: PointerEvent) { - this.SetCameraRotationalVelocity(vec2.create()); + this.SetCameraRotationalVelocity(vec2.create()); super.HandlePointerUp(event); } HandleMouseWheel(event: WheelEvent) { let deltaZ = -event.deltaY * this.mouseWheelTranslationSpeed; - if (event.altKey){ + if (event.altKey) { deltaZ *= this.pointerAltKeyMultiplier; } var delta = vec3.fromValues(0.0, 0.0, deltaZ); - this.TranslateCamera(delta); + this.TranslateCamera(delta); } - HandleKeyUp(key: string) - { + HandleKeyUp(key: string) { key = key.toLowerCase(); - if(this.firstPerson && key in this.dirKeyPresses) - { + if (this.firstPerson && key in this.dirKeyPresses) { this.dirKeyPresses[key] = 0; this.updateCameraVelocity(); } - else - { - for(let key in this.dirKeyPresses) - { + else { + for (let key in this.dirKeyPresses) { this.dirKeyPresses[key] = 0; } } } - HandleKeyDown(key : string) : [boolean, boolean] - { + HandleKeyDown(key: string): [boolean, boolean] { var result = super.HandleKeyDown(key); if (result[0]) return result; // Already handled var handled = true; key = key.toLowerCase(); - switch(key) - { + switch (key) { case "r": this.ResetView(); break; @@ -426,8 +391,7 @@ export default class Canvas3D extends CanvasBase break; } - if(this.firstPerson && key in this.dirKeyPresses) - { + if (this.firstPerson && key in this.dirKeyPresses) { this.dirKeyPresses[key] = Date.now(); this.updateCameraVelocity(); } @@ -436,15 +400,14 @@ export default class Canvas3D extends CanvasBase } // Adds UI for the control of certain layers - SetLayerSettings(layerSettings : any) - { + SetLayerSettings(layerSettings: any) { // Delete any previous controls while (this.dropdownTable.childElementCount > 2) this.dropdownTable.removeChild(this.dropdownTable.lastChild); this.layerSettings = layerSettings; this.layerIds = [null]; - for(let layerId in layerSettings){ + for (let layerId in layerSettings) { this.layerIds.push(layerId); } @@ -452,8 +415,7 @@ export default class Canvas3D extends CanvasBase var BorderStyle = "1px solid #cccccc"; var headerRow = document.createElement("tr"); headerRow.style.borderBottom = BorderStyle; - for(var headerName of ["Wire", "Fill", "Opacity", "Layer Id"]) - { + for (var headerName of ["Wire", "Fill", "Opacity", "Layer Id"]) { var headerItem = document.createElement("th"); headerItem.className = "scenepic-dropdown-header"; headerItem.innerHTML = headerName; @@ -462,8 +424,7 @@ export default class Canvas3D extends CanvasBase this.dropdownTable.appendChild(headerRow); // Create row helper function - var createRow = (id, label) => - { + var createRow = (id, label) => { // Create wireframe checkbox var checkboxWireframe = document.createElement("input"); checkboxWireframe.type = "checkbox"; @@ -494,14 +455,12 @@ export default class Canvas3D extends CanvasBase return [checkboxWireframe, checkboxFill, sliderOpacity, labelLayer]; }; - var addRow = (rowItems, border) => - { + var addRow = (rowItems, border) => { // Create table row var tr = document.createElement("tr"); if (border) tr.style.borderTop = BorderStyle; - var addControl = (el, className) => - { + var addControl = (el, className) => { var td = document.createElement("td"); td.appendChild(el); td.className = className; @@ -519,8 +478,7 @@ export default class Canvas3D extends CanvasBase addRow(createRow("<<>>", "Global"), true); } - ConfigureUserInterface(command : any) - { + ConfigureUserInterface(command: any) { if ("PointerAltKeyMultiplier" in command) this.pointerAltKeyMultiplier = command["PointerAltKeyMultiplier"]; if ("PointerRotationSpeed" in command) this.pointerRotationSpeed = command["PointerRotationSpeed"]; if ("MouseWheelTranslationSpeed" in command) this.mouseWheelTranslationSpeed = command["MouseWheelTranslationSpeed"]; @@ -528,24 +486,20 @@ export default class Canvas3D extends CanvasBase if ("LayerDropdownVisibility" in command) this.dropdown.style.visibility = command["LayerDropdownVisibility"] } - ParseFocusPoint(command : any) - { + ParseFocusPoint(command: any) { var position = Misc.Base64ToFloat32Array(command["Position"]); - if ("OrientationAxisAngle" in command) - { + if ("OrientationAxisAngle" in command) { var focusPoint = new Float32Array(6); focusPoint.set(position, 0); focusPoint.set(Misc.Base64ToFloat32Array(command["OrientationAxisAngle"]), 3); return focusPoint; } - else - { + else { return position; } } - SetGlobalCamera(value : Object) - { + SetGlobalCamera(value: Object) { if (value == null) return; this.globalCameraParams = value; @@ -556,10 +510,8 @@ export default class Canvas3D extends CanvasBase } // Execute a single canvas command - ExecuteCanvasCommand(command : any) - { - switch(command["CommandType"]) - { + ExecuteCanvasCommand(command: any) { + switch (command["CommandType"]) { case "ConfigureUserInterface": this.ConfigureUserInterface(command); break; @@ -592,10 +544,8 @@ export default class Canvas3D extends CanvasBase } // Execute a single frame command - ExecuteFrameCommand(command : any, frameIndex : number) - { - switch(command["CommandType"]) - { + ExecuteFrameCommand(command: any, frameIndex: number) { + switch (command["CommandType"]) { case "SetFocusPoint": var focusPoint = this.ParseFocusPoint(command); this.SetPerFrameFocusPoint(frameIndex, focusPoint); @@ -619,7 +569,7 @@ export default class Canvas3D extends CanvasBase case "SetLayerSettings": var layerSettings = Misc.GetDefault(command, "Value", null); - if (layerSettings != null){ + if (layerSettings != null) { this.SetPerFrameLayerSettings(frameIndex, layerSettings); break; } @@ -630,15 +580,14 @@ export default class Canvas3D extends CanvasBase } } - SetCamera(params : Object) - { + SetCamera(params: Object) { if (params == null) return; - if (params.hasOwnProperty("WorldToCamera")){ + if (params.hasOwnProperty("WorldToCamera")) { this.w2vMatrix = Misc.Base64ToFloat32Array(params["WorldToCamera"]); this.v2sMatrix = Misc.Base64ToFloat32Array(params["Projection"]); } - else{ + else { console.warn("The legacy SetCamera command is deprecated.") var camCenter = Misc.Base64ToFloat32Array(params["Center"]); var camLookAt = Misc.Base64ToFloat32Array(params["LookAt"]); @@ -650,50 +599,43 @@ export default class Canvas3D extends CanvasBase } } - ResetView() - { + ResetView() { this.onCameraTrack = true; var currentFrame = this.currentFrameIndex; this.SetCamera(this.frameCameraParams[currentFrame]); this.currentFocusPoints[currentFrame] = new Float32Array(this.initialFocusPoints[currentFrame]) } - ToggleLockView(translation : boolean, orientation : boolean) - { + ToggleLockView(translation: boolean, orientation: boolean) { if (translation) this.lockViewXY = !this.lockViewXY; if (orientation) this.lockViewOrientation = !this.lockViewOrientation; } - ToggleOrbitCamera() - { + ToggleOrbitCamera() { this.orbitCamera = !this.orbitCamera; this.lastOrbitTime = null; } - SetPerFrameFocusPoint(frameIndex : number, focusPoint : Float32Array) - { + SetPerFrameFocusPoint(frameIndex: number, focusPoint: Float32Array) { if (focusPoint == null) return; this.initialFocusPoints[frameIndex] = focusPoint; // For reset support this.currentFocusPoints[frameIndex] = new Float32Array(focusPoint); // Copy } - SetPerFrameCamera(frameIndex : number, value : Object) - { - if ( value == null) return; + SetPerFrameCamera(frameIndex: number, value: Object) { + if (value == null) return; this.frameCameraParams[frameIndex] = value; // For reset support } - SetPerFrameLayerSettings(frameIndex : number, value : Object) - { - if ( value == null) return; - + SetPerFrameLayerSettings(frameIndex: number, value: Object) { + if (value == null) return; + this.frameLayerSettings[frameIndex] = value; } - SetGlobalFocusPoint(focusPoint : Float32Array) - { + SetGlobalFocusPoint(focusPoint: Float32Array) { if (focusPoint == null) return; this.globalFocusPoint = focusPoint; @@ -701,8 +643,7 @@ export default class Canvas3D extends CanvasBase this.SetPerFrameFocusPoint(frameIndex, focusPoint); // Nice side-effect: object is aliased so will stay shared when user moves focus point } - SetShading(params : Object) - { + SetShading(params: Object) { // Background color this.bgColor = Misc.Base64ToFloat32Array(params["BackgroundColor"]); @@ -713,35 +654,31 @@ export default class Canvas3D extends CanvasBase vec3.normalize(this.directionalLightDir, this.directionalLightDir); } - AllocateFrame() - { + AllocateFrame() { this.frameInstances.push([]); this.initialFocusPoints.push(this.globalFocusPoint); this.currentFocusPoints.push(this.globalFocusPoint); this.frameCameraParams.push(this.globalCameraParams); } - DeallocateFrame(frameIndex : number) - { + DeallocateFrame(frameIndex: number) { this.frameInstances[frameIndex] = []; } - AddMesh(frameIndex : number, meshId : string, meshTransform : mat4) - { + AddMesh(frameIndex: number, meshId: string, meshTransform: mat4) { // Add the mesh instance var instance = new MeshInstance(meshId, meshTransform); var instances = this.frameInstances[frameIndex]; instances.push(instance); let mesh = this.allMeshes[meshId]; - if(mesh.layerId != null && !(mesh.layerId in this.layerSettings)){ + if (mesh.layerId != null && !(mesh.layerId in this.layerSettings)) { this.layerSettings[mesh.layerId] = {}; this.SetLayerSettings(this.layerSettings); } // Update display if this is the selected mesh - if (this.currentFrameIndex == frameIndex) - { + if (this.currentFrameIndex == frameIndex) { if (!(meshId in this.allMeshes)) this.SetWarning("Mesh " + meshId + " does not exist!"); @@ -750,22 +687,18 @@ export default class Canvas3D extends CanvasBase } } - RemoveMesh(frameIndex : number, meshId : string) - { + RemoveMesh(frameIndex: number, meshId: string) { // Add the mesh id var instances = this.frameInstances[frameIndex]; var meshIndex = 0; var edited = false; - while (meshIndex < instances.length) - { - if (instances[meshIndex].meshId == meshId) - { + while (meshIndex < instances.length) { + if (instances[meshIndex].meshId == meshId) { // Remove instances.splice(meshIndex, 1); edited = true; } - else - { + else { meshIndex++; } } @@ -775,17 +708,14 @@ export default class Canvas3D extends CanvasBase this.PrepareBuffers(); } - NotifyMeshUpdated(meshId : string) - { + NotifyMeshUpdated(meshId: string) { if (this.frameInstances.length == 0) return; // No frames var currentFrameInstances = this.frameInstances[this.currentFrameIndex]; - for(var instance of currentFrameInstances) - { - if (instance.meshId == meshId) - { + for (var instance of currentFrameInstances) { + if (instance.meshId == meshId) { // Clear up any existing buffers if (meshId in this.meshBuffers && this.meshBuffers[meshId] != null) - this.meshBuffers[meshId].Finalize(); + this.meshBuffers[meshId].Finalize(); // Recreate new buffers using updated mesh data this.meshBuffers[meshId] = this.GetMeshBuffers(this.allMeshes[meshId]); @@ -793,12 +723,10 @@ export default class Canvas3D extends CanvasBase } } - private GetMeshBuffers(mesh : Mesh) - { + private GetMeshBuffers(mesh: Mesh) { var textureId = mesh.textureId; var textureSrc = null; - if (textureId != null) - { + if (textureId != null) { var textureSrc = this.objectCache.GetObject(textureId); if (textureSrc == null) return null; // Not able to create buffers yet @@ -806,17 +734,14 @@ export default class Canvas3D extends CanvasBase return new WebGLMeshBuffers(this.gl, this.sp, mesh, textureSrc); } - NotifyTextureUpdated(textureId : string) - { + NotifyTextureUpdated(textureId: string) { if (this.frameInstances.length == 0) return; // No frames var currentFrameInstances = this.frameInstances[this.currentFrameIndex]; - for(var instance of currentFrameInstances) - { + for (var instance of currentFrameInstances) { var meshId = instance.meshId; var mesh = this.allMeshes[meshId]; - if (mesh.textureId == textureId) - { + if (mesh.textureId == textureId) { if (this.meshBuffers[meshId] != null) this.meshBuffers[meshId].Finalize(); // Clear up existing buffers this.meshBuffers[meshId] = this.GetMeshBuffers(mesh); // Recreate @@ -824,21 +749,19 @@ export default class Canvas3D extends CanvasBase } } - PrepareBuffers() : void - { + PrepareBuffers(): void { super.PrepareBuffers(); // Get new set of meshIds - var meshIds : string[] = []; + var meshIds: string[] = []; if (this.gl != null && this.currentFrameIndex < this.frameInstances.length) // Check for e.g. webgl context lost { - for(var instance of this.frameInstances[this.currentFrameIndex]) + for (var instance of this.frameInstances[this.currentFrameIndex]) meshIds.push(instance.meshId); } // Get set of meshIds that need turning in to buffers - var meshIdsToAdd : string[] = []; - for(var meshId of meshIds) - { + var meshIdsToAdd: string[] = []; + for (var meshId of meshIds) { var mesh = this.allMeshes[meshId]; if (mesh != null && this.IsLayerVisible(mesh.layerId) && !(meshId in this.meshBuffers)) // Visible and not already cached { @@ -851,13 +774,11 @@ export default class Canvas3D extends CanvasBase } // Finalize meshIds not currently used - for(var meshId in this.meshBuffers) - { + for (var meshId in this.meshBuffers) { var mesh = this.allMeshes[meshId]; if (!this.IsLayerVisible(mesh.layerId) || meshIds.indexOf(meshId) == -1) // Invisible or no longer present { - if (meshId in this.meshBuffers) - { + if (meshId in this.meshBuffers) { if (this.meshBuffers[meshId] != null) this.meshBuffers[meshId].Finalize(); delete this.meshBuffers[meshId]; @@ -869,15 +790,13 @@ export default class Canvas3D extends CanvasBase } // Loop over meshes - for(var meshId of meshIdsToAdd) - { + for (var meshId of meshIdsToAdd) { var mesh = this.allMeshes[meshId]; this.meshBuffers[meshId] = this.GetMeshBuffers(mesh); } } - GetCurrentFocusPointInViewSpace() - { + GetCurrentFocusPointInViewSpace() { var focusPoint = this.currentFocusPoints[this.currentFrameIndex]; var focusPointPosition = focusPoint.subarray(0, 3); var focusPointView = vec3.create(); @@ -885,14 +804,17 @@ export default class Canvas3D extends CanvasBase return focusPointView; } - ComputeCameraTwist(point: vec2, event: PointerEvent) - { + ComputeCameraTwist(point: vec2, event: PointerEvent): number { const old = this.pointerCoords.get(event.pointerId); + if (old == undefined) { + return 0; + } + // Compute projection of focal point into canvas image var focusPointImage = vec3.create(); vec3.transformMat4(focusPointImage, this.GetCurrentFocusPointInViewSpace(), this.v2sMatrix); const focus = vec2.fromValues((focusPointImage[0] + 1.0) * 0.5 * this.width, - (-focusPointImage[1] + 1.0) * 0.5 * this.height); + (-focusPointImage[1] + 1.0) * 0.5 * this.height); // Compute rotation angle const angleInitial = Math.atan2(old[1] - focus[1], old[0] - focus[0]); @@ -900,16 +822,14 @@ export default class Canvas3D extends CanvasBase return angleInitial - angleNew; } - SetFocusPointPositionFromPixelCoordinates(pixel: vec2) - { + SetFocusPointPositionFromPixelCoordinates(pixel: vec2) { if (this.lockViewXY || this.lockViewOrientation) return; - + this.pickPoint = pixel; this.setFocusToPicked = true; } - ComputeFocusPointRelativeViewSpaceTranslation(old : vec2, point: vec2) - { + ComputeFocusPointRelativeViewSpaceTranslation(old: vec2, point: vec2) { var focusPointZ = Math.abs(this.GetCurrentFocusPointInViewSpace()[2]); var clientRect = this.htmlCanvas.getBoundingClientRect(); @@ -929,8 +849,7 @@ export default class Canvas3D extends CanvasBase return vec3.fromValues(oldView[0] - newView[0], oldView[1] - newView[1], 0.0); // oldView[2] - newView[2] should be == 0.0 } - RotateCamera(rotateAboutX : number, rotateAboutY : number, rotateAboutZ : number) - { + RotateCamera(rotateAboutX: number, rotateAboutY: number, rotateAboutZ: number) { this.onCameraTrack = false; var delta = vec3.fromValues(rotateAboutX, rotateAboutY, rotateAboutZ); @@ -956,24 +875,21 @@ export default class Canvas3D extends CanvasBase mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix); } - SetCameraRotationalVelocity(rotate: vec2) - { + SetCameraRotationalVelocity(rotate: vec2) { vec2.copy(this.cameraRotationalVelocity, rotate); } - SwivelCamera(rotate: vec2) - { + SwivelCamera(rotate: vec2) { this.onCameraTrack = false; let transform = mat4.create(); mat4.rotateY(transform, transform, rotate[0]); mat4.rotateX(transform, transform, rotate[1]); - + mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix); } - MoveCamera(moveForward: number, moveRight: number, moveUp: number) - { + MoveCamera(moveForward: number, moveRight: number, moveUp: number) { this.onCameraTrack = false; let transform = mat4.create(); @@ -981,8 +897,7 @@ export default class Canvas3D extends CanvasBase mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix); } - ScaleCamera(factor : number) - { + ScaleCamera(factor: number) { this.onCameraTrack = false; // Compute focus point location in view space @@ -1005,8 +920,7 @@ export default class Canvas3D extends CanvasBase mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix); } - TranslateCamera(delta : vec3) - { + TranslateCamera(delta: vec3) { this.onCameraTrack = false; // Compute translation matrix @@ -1017,17 +931,15 @@ export default class Canvas3D extends CanvasBase mat4.multiply(this.w2vMatrix, transform, this.w2vMatrix); } - IsLayerVisible(layerId : string) : boolean - { + IsLayerVisible(layerId: string): boolean { var showFilled = this.ShowLayerFilled(layerId) && this.globalFill; var showWireframe = this.ShowLayerWireframe(layerId) || (this.ShowLayerFilled(layerId) && this.globalWireframe); var opacity = this.GetLayerOpacity(layerId) * this.globalOpacity; return (showFilled || showWireframe) && opacity > 0.0; } - ToggleLayerFilled(index: number) - { - if(index >= this.layerIds.length) { + ToggleLayerFilled(index: number) { + if (index >= this.layerIds.length) { return } @@ -1036,8 +948,7 @@ export default class Canvas3D extends CanvasBase this.SetLayerFilled(layerId, !filled); } - ShowLayerFilled(layerId : string) : boolean - { + ShowLayerFilled(layerId: string): boolean { if (layerId == "<<>>") return this.globalFill; if (layerId == null) @@ -1049,16 +960,13 @@ export default class Canvas3D extends CanvasBase return true; } - SetLayerFilled(layerId : string, filled : boolean) - { + SetLayerFilled(layerId: string, filled: boolean) { if (layerId == null) return; - if (layerId == "<<>>") - { + if (layerId == "<<>>") { this.globalFill = filled; } - else - { + else { if (!(layerId in this.layerSettings)) this.layerSettings[layerId] = {}; this.layerSettings[layerId]["filled"] = filled; @@ -1068,8 +976,7 @@ export default class Canvas3D extends CanvasBase this.PrepareBuffers(); } - ShowLayerWireframe(layerId : string) : boolean - { + ShowLayerWireframe(layerId: string): boolean { if (layerId == "<<>>") return this.globalWireframe; if (layerId == null) @@ -1081,17 +988,14 @@ export default class Canvas3D extends CanvasBase return false; } - SetLayerWireframe(layerId : string, wireframe : boolean) - { + SetLayerWireframe(layerId: string, wireframe: boolean) { if (layerId == null) return; - if (layerId == "<<>>") - { + if (layerId == "<<>>") { this.globalWireframe = wireframe; } - else - { + else { if (!(layerId in this.layerSettings)) this.layerSettings[layerId] = {}; this.layerSettings[layerId]["wireframe"] = wireframe; @@ -1101,8 +1005,7 @@ export default class Canvas3D extends CanvasBase this.PrepareBuffers(); } - GetLayerOpacity(layerId : string) - { + GetLayerOpacity(layerId: string) { if (layerId == "<<>>") return this.globalOpacity; if (layerId == null || !(layerId in this.layerSettings) || !("opacity" in this.layerSettings[layerId])) @@ -1111,17 +1014,14 @@ export default class Canvas3D extends CanvasBase return this.layerSettings[layerId]["opacity"]; } - SetLayerOpacity(layerId : string, opacity : number) - { + SetLayerOpacity(layerId: string, opacity: number) { if (layerId == null) return; - if (layerId == "<<>>") - { + if (layerId == "<<>>") { this.globalOpacity = opacity; } - else - { + else { if (!(layerId in this.layerSettings)) this.layerSettings[layerId] = {}; this.layerSettings[layerId]["opacity"] = opacity; @@ -1131,20 +1031,16 @@ export default class Canvas3D extends CanvasBase this.PrepareBuffers(); } - GetLayerRenderOrder(layerId : string) - { + GetLayerRenderOrder(layerId: string) { if (layerId == null || !(layerId in this.layerSettings) || !("renderOrder" in this.layerSettings[layerId])) return 1e3; // Last layer - bit of a hack else return this.layerSettings[layerId]["renderOrder"]; } - AllMeshBuffersReady() - { - if (this.frameInstances.length > 0) - { - for(var instance of this.frameInstances[this.currentFrameIndex]) - { + AllMeshBuffersReady() { + if (this.frameInstances.length > 0) { + for (var instance of this.frameInstances[this.currentFrameIndex]) { // Look up mesh var meshId = instance.meshId; var mesh = this.allMeshes[meshId]; @@ -1162,24 +1058,20 @@ export default class Canvas3D extends CanvasBase } // Override base class implementation to deal with focus point lock - ShowFrame(frameIndex : number) - { + ShowFrame(frameIndex: number) { frameIndex = Math.min(frameIndex, this.frameIds.length - 1); // Just in case // Get old and new focus points var oldFocusPoint = this.currentFocusPoints[this.currentFrameIndex]; var newFocusPoint = this.currentFocusPoints[frameIndex]; - if (this.frameLayerSettings[frameIndex]) - { + if (this.frameLayerSettings[frameIndex]) { let layerSettings = this.frameLayerSettings[frameIndex]; - for(let layerId in layerSettings) - { + for (let layerId in layerSettings) { this.SetLayerFilled(layerId, layerSettings[layerId]["filled"]); this.SetLayerWireframe(layerId, layerSettings[layerId]["wireframe"]); this.SetLayerOpacity(layerId, layerSettings[layerId]["opacity"]); - if ( "renderOrder" in layerSettings ) - { + if ("renderOrder" in layerSettings) { this.layerSettings[layerId]["renderOrder"] = layerSettings[layerId]["renderOrder"] } @@ -1193,7 +1085,7 @@ export default class Canvas3D extends CanvasBase // Lock camera rotation to focus point? if (this.lockViewOrientation && oldFocusPoint.length == 6 && newFocusPoint.length == 6) // Does the focus point contain an axis angle rotation? { - var aaToMat = (axisAngle : vec3) => { var angle = vec3.length(axisAngle); var axis = vec3.create(); vec3.normalize(axis, axisAngle); var rotation = mat4.create(); mat4.fromRotation(rotation, angle, axis); return rotation; } + var aaToMat = (axisAngle: vec3) => { var angle = vec3.length(axisAngle); var axis = vec3.create(); vec3.normalize(axis, axisAngle); var rotation = mat4.create(); mat4.fromRotation(rotation, angle, axis); return rotation; } var oldRotation = aaToMat(oldFocusPoint.subarray(3)); var newRotation = aaToMat(newFocusPoint.subarray(3)); @@ -1208,8 +1100,7 @@ export default class Canvas3D extends CanvasBase } // Lock camera translation to focus point? - if (this.lockViewXY || this.lockViewOrientation) - { + if (this.lockViewXY || this.lockViewOrientation) { var oldFPView = vec3.create(); vec3.transformMat4(oldFPView, oldFocusPoint.subarray(0, 3), w2vMatrixOld); @@ -1230,8 +1121,7 @@ export default class Canvas3D extends CanvasBase return super.ShowFrame(frameIndex); } - Render() - { + Render() { // Check for gl to be ready if (this.gl == null) return; @@ -1240,11 +1130,9 @@ export default class Canvas3D extends CanvasBase if (!this.AllMeshBuffersReady()) return; - if(this.firstPerson) - { + if (this.firstPerson) { this.MoveCamera(this.cameraVelocity[0], this.cameraVelocity[1], this.cameraVelocity[2]); - if(this.cameraRotationalVelocity[0] != 0 || this.cameraRotationalVelocity[1] != 0) - { + if (this.cameraRotationalVelocity[0] != 0 || this.cameraRotationalVelocity[1] != 0) { this.SwivelCamera(this.cameraRotationalVelocity); } } @@ -1256,12 +1144,11 @@ export default class Canvas3D extends CanvasBase } // Render scene method with given w2v and v2s matrices - RenderViewport() - { + RenderViewport() { const gl = this.gl; const v2sMatrix = this.v2sMatrix; - if (this.onCameraTrack){ + if (this.onCameraTrack) { this.SetCamera(this.frameCameraParams[this.currentFrameIndex]) } @@ -1282,13 +1169,12 @@ export default class Canvas3D extends CanvasBase gl.uniform3fv(this.sp.directionalLightDirPtr, this.directionalLightDir); // Get list of meshes to draw - let opaqueMeshData : MeshData[] = []; - let transparentMeshData : MeshData[] = []; - if (this.frameInstances.length > 0) - { + let opaqueMeshData: MeshData[] = []; + let transparentMeshData: MeshData[] = []; + if (this.frameInstances.length > 0) { var instances = this.frameInstances[this.currentFrameIndex]; - for(var meshInstance of instances) // Loop over mesh instances + for (var meshInstance of instances) // Loop over mesh instances { // Look up mesh var meshId = meshInstance.meshId; @@ -1314,11 +1200,9 @@ export default class Canvas3D extends CanvasBase mat4.multiply(m2vMatrix, w2vMatrix, meshInstance.transform); // Set properties for labels - if (mesh.isLabel) - { + if (mesh.isLabel) { var textureSrc = this.objectCache.GetObject(mesh.textureId); - if (textureSrc != null) - { + if (textureSrc != null) { buffer.labelWidthNormalized = textureSrc.desiredWidthPixels / this.htmlCanvas.width; buffer.labelHeightNormalized = textureSrc.desiredHeightPixels / this.htmlCanvas.height; @@ -1330,8 +1214,7 @@ export default class Canvas3D extends CanvasBase } // Is this transparent or not? - if (opacity < 1.0 || (mesh.textureId != null && (mesh.useTextureAlpha || mesh.isLabel))) - { + if (opacity < 1.0 || (mesh.textureId != null && (mesh.useTextureAlpha || mesh.isLabel))) { // Transform center of mass to view coordinates to get z-ordering var viewCoM = vec3.create(); vec3.transformMat4(viewCoM, mesh.centerOfMass, m2vMatrix); @@ -1343,8 +1226,7 @@ export default class Canvas3D extends CanvasBase transparentMeshData.push(new MeshData(mesh, meshInstance.transform, m2vMatrix, buffer, opacity, filled, wireframe, viewDistance)); } - else - { + else { opaqueMeshData.push(new MeshData(mesh, meshInstance.transform, m2vMatrix, buffer, 1.0, filled, wireframe, 0)); } } @@ -1359,12 +1241,11 @@ export default class Canvas3D extends CanvasBase // Draw transparent meshes, sorted by decreasing distance from camera gl.depthMask(false); gl.enable(gl.BLEND); - var sortedTransparentMeshData = transparentMeshData.sort((a,b) => a.viewDistance - b.viewDistance); + var sortedTransparentMeshData = transparentMeshData.sort((a, b) => a.viewDistance - b.viewDistance); sortedTransparentMeshData.forEach(data => data.Render(gl, v2sMatrix)); // Show focus point - if (this.showFocusPoint) - { + if (this.showFocusPoint) { gl.disable(gl.DEPTH_TEST); var focusPoint = this.currentFocusPoints[this.currentFrameIndex]; var focusPointPosition = focusPoint.subarray(0, 3); @@ -1376,23 +1257,21 @@ export default class Canvas3D extends CanvasBase } // If enabled, orbit the camera - if (this.orbitCamera) - { + if (this.orbitCamera) { // Adjust for any framerate changes var now = new Date(); - if (this.lastOrbitTime != null) - { + if (this.lastOrbitTime != null) { var deltaTime = now.getTime() - this.lastOrbitTime.getTime(); this.RotateCamera(0.0, 0.0025 * deltaTime, 0.0); // Rotate about y axis } this.lastOrbitTime = now; } - if (this.setFocusToPicked && opaqueMeshData.length > 0){ + if (this.setFocusToPicked && opaqueMeshData.length > 0) { let buffers: [WebGLMeshBuffers, mat4][] = []; - for(let i=0; ifocusPoint.subarray(0, 3); vec3.transformMat4(focusPointPosition, view, v2wMatrix); - }else{ + } else { const pickedCentroid = opaqueMeshData[picked - 1].ComputeCentroid(); var focusPoint = this.currentFocusPoints[this.currentFrameIndex]; focusPoint[0] = pickedCentroid[0]; diff --git a/tssrc/CanvasBase.ts b/tssrc/CanvasBase.ts index 6a82352..2b61860 100644 --- a/tssrc/CanvasBase.ts +++ b/tssrc/CanvasBase.ts @@ -1,23 +1,20 @@ import { vec2 } from "gl-matrix"; import Misc from "./Misc" -export abstract class CachedObject -{ - object : any; // The object that is created and cached on demand - countUsers : number; // Set of current users of htmlImage - cachePermanently : boolean; +export abstract class CachedObject { + object: any; // The object that is created and cached on demand + countUsers: number; // Set of current users of htmlImage + cachePermanently: boolean; // Constructor - constructor(cachePermanently : boolean = false) - { + constructor(cachePermanently: boolean = false) { this.object = null; this.countUsers = 0; this.cachePermanently = cachePermanently; } // Add a new user of the image - AddUser(callback : () => void) - { + AddUser(callback: () => void) { // Store the user if new this.countUsers += 1; @@ -29,8 +26,7 @@ export abstract class CachedObject } // Remove a user of the image - RemoveUser() - { + RemoveUser() { // Remove the user this.countUsers -= 1; @@ -39,76 +35,65 @@ export abstract class CachedObject return; // Retire the html image after small delay (giving chance for reuse) - var self : CachedObject = this; - setTimeout(function() - { - if (self.countUsers == 0) - { + var self: CachedObject = this; + setTimeout(function () { + if (self.countUsers == 0) { self.object = null; } }, 50); } - abstract GenerateObject(callback : () => void); + abstract GenerateObject(callback: () => void); - abstract GetBlob() : Blob; + abstract GetBlob(): Blob; - GetObject() - { + GetObject() { return this.object; } } -export class CachedImage extends CachedObject -{ - src : string; // The image source, either as a base64 encoded string or as a path to a file on disk - +export class CachedImage extends CachedObject { + src: string; // The image source, either as a base64 encoded string or as a path to a file on disk + // Constructor simply stores the source - constructor(src : string, cachePermanently : boolean = false) - { + constructor(src: string, cachePermanently: boolean = false) { super(cachePermanently); this.src = src; } // Generate cached object - GenerateObject(callback : () => void) - { + GenerateObject(callback: () => void) { // Create cached htmlImage this.object = new Image(); this.object.addEventListener("load", callback); this.object.src = this.src; } - GetObject() - { + GetObject() { if (this.object == null || !(this.object).complete) return null; else return this.object; } - GetBlob() - { + GetBlob() { return Misc.DataUrlToBlob(this.src); } } -export class CachedAudioTrack extends CachedObject -{ - src : string; - audio : HTMLAudioElement; +export class CachedAudioTrack extends CachedObject { + src: string; + audio: HTMLAudioElement; // Constructor simply stores the source - constructor(audio : HTMLAudioElement, src : string, cachePermanently : boolean = false) - { + constructor(audio: HTMLAudioElement, src: string, cachePermanently: boolean = false) { super(cachePermanently); this.audio = audio; this.src = src; } // Generate cached object - GenerateObject(callback : () => void) - { + GenerateObject(callback: () => void) { this.object = this.audio; this.object.addEventListener("loadeddata", callback); let blobUrl = URL.createObjectURL(this.GetBlob()); @@ -116,36 +101,31 @@ export class CachedAudioTrack extends CachedObject } - GetObject() - { + GetObject() { if (this.object == null) return null; else return this.object; } - GetBlob() - { + GetBlob() { return Misc.DataUrlToBlob(this.src); } } -export class CachedVideo extends CachedObject -{ - src : string; - video : HTMLVideoElement; +export class CachedVideo extends CachedObject { + src: string; + video: HTMLVideoElement; // Constructor simply stores the source - constructor(video: HTMLVideoElement, src : string, cachePermanently : boolean = false) - { + constructor(video: HTMLVideoElement, src: string, cachePermanently: boolean = false) { super(cachePermanently); this.video = video; this.src = src; } // Generate cached object - GenerateObject(callback : () => void) - { + GenerateObject(callback: () => void) { this.object = this.video; this.object.addEventListener("loadeddata", () => { callback(); @@ -154,35 +134,31 @@ export class CachedVideo extends CachedObject this.object.src = blobUrl; } - GetObject() - { + GetObject() { if (this.object == null) return null; else return this.object; } - GetBlob() - { + GetBlob() { return Misc.DataUrlToBlob(this.src); } } // A textual label -export class CachedLabel extends CachedObject -{ - text : string; - fillStyle : string; - fontFamily : string; - fontSizePx : number; - horizAlign : number; - vertAlign : number; - offsetDistance : number; +export class CachedLabel extends CachedObject { + text: string; + fillStyle: string; + fontFamily: string; + fontSizePx: number; + horizAlign: number; + vertAlign: number; + offsetDistance: number; // Constructor simply stores the source - constructor(text : string, fillStyle: string, fontFamily : string, fontSizePx : number, horizAlign : number, vertAlign : number, offsetDistance : number, cachePermanently : boolean = false) - { + constructor(text: string, fillStyle: string, fontFamily: string, fontSizePx: number, horizAlign: number, vertAlign: number, offsetDistance: number, cachePermanently: boolean = false) { super(cachePermanently); this.text = text; @@ -194,8 +170,7 @@ export class CachedLabel extends CachedObject this.offsetDistance = offsetDistance; } - GenerateObject(callback : () => void) - { + GenerateObject(callback: () => void) { var HeightCompensationFudgeFactor = 1.2; // Hack since no way to get required height // Create cached bitmap @@ -224,84 +199,69 @@ export class CachedLabel extends CachedObject callback(); } - GetBlob() : Blob - { + GetBlob(): Blob { throw new Error("Not Implemented"); } } -export class ObjectCache -{ - cachedObjects : any; - owners : {[id: string]: string} = {}; - token : number = 0; - callback : (string) => void; +export class ObjectCache { + cachedObjects: any; + owners: { [id: string]: string } = {}; + token: number = 0; + callback: (string) => void; - constructor(callback : (string) => void) - { + constructor(callback: (string) => void) { this.cachedObjects = {} this.callback = callback; } - AddUser(id : string) - { + AddUser(id: string) { if (id in this.cachedObjects) this.cachedObjects[id].AddUser(() => this.callback(id)); } - RemoveUser(id : string) - { + RemoveUser(id: string) { if (id in this.cachedObjects) this.cachedObjects[id].RemoveUser(); } - DefineImageFromSrc(id : string, src : string, cachePermanently : boolean) - { + DefineImageFromSrc(id: string, src: string, cachePermanently: boolean) { this.cachedObjects[id] = new CachedImage(src, cachePermanently); } - DefineImageFromFile(id : string, src : string) - { + DefineImageFromFile(id: string, src: string) { this.DefineImageFromSrc(id, src, true); // For now, we always permanently cache images loaded explicitly from disk } - DefineImageFromBase64(id : string, type : string, dataBase64 : string) - { + DefineImageFromBase64(id: string, type: string, dataBase64: string) { this.DefineImageFromSrc(id, "data:" + type + ";base64," + dataBase64, false); } - DefineAudioTrackFromBase64(id : string, audio: HTMLAudioElement, type : string, dataBase64 : string) - { + DefineAudioTrackFromBase64(id: string, audio: HTMLAudioElement, type: string, dataBase64: string) { this.cachedObjects[id] = new CachedAudioTrack(audio, "data:" + type + ";base64," + dataBase64, true); } - DefineVideoFromBase64(id : string, video : HTMLVideoElement, type : string, dataBase64 : string) - { + DefineVideoFromBase64(id: string, video: HTMLVideoElement, type: string, dataBase64: string) { this.cachedObjects[id] = new CachedVideo(video, "data:" + type + ";base64," + dataBase64, true); } - DefineLabel(id : string, text : string, fillStyle: string, fontFamily : string, fontSizePx : number, horizAlign : number, vertAlign : number, offsetDistance : number) - { + DefineLabel(id: string, text: string, fillStyle: string, fontFamily: string, fontSizePx: number, horizAlign: number, vertAlign: number, offsetDistance: number) { this.cachedObjects[id] = new CachedLabel(text, fillStyle, fontFamily, fontSizePx, horizAlign, vertAlign, offsetDistance, false); } - Acquire(id : string) : string - { - if(id in this.owners) - { + Acquire(id: string): string { + if (id in this.owners) { return null; } let token = this.token.toString() this.token += 1; this.owners[id] = token - return token; + return token; } - Release(id : string, token : string) : boolean - { - if(id in this.owners && this.owners[id] == token) - { + Release(id: string, token: string): boolean { + if (id in this.owners && this.owners[id] == token) { delete this.owners[id]; return true; } @@ -309,8 +269,7 @@ export class ObjectCache return false; } - GetObject(id : string) - { + GetObject(id: string) { if (!(id in this.cachedObjects)) return null; @@ -318,8 +277,7 @@ export class ObjectCache return this.cachedObjects[id].GetObject(); } - GetBlob(id : string) - { + GetBlob(id: string) { if (!(id in this.cachedObjects)) return null; else @@ -327,84 +285,83 @@ export class ObjectCache } } -export abstract class CanvasBase -{ - objectCache : ObjectCache = null // Maps from ids to image/bitmap objects +export class PinchZoomInfo { + constructor(public distanceRatio: number, public angleDelta: number, public center: vec2) { } +} - static TIME_PER_REDRAW = 1.0/60; +export abstract class CanvasBase { + objectCache: ObjectCache = null // Maps from ids to image/bitmap objects + + static TIME_PER_REDRAW = 1.0 / 60; static FPS_DECAY = 0.5; static FPS_FREQUENCY = 200; - container : HTMLDivElement; - public htmlCanvas : HTMLCanvasElement; - sliderContainer : HTMLDivElement; - slider : HTMLInputElement; - sliderTextBox : HTMLSpanElement; - sliderTextBoxLength : number = 0; + container: HTMLDivElement; + public htmlCanvas: HTMLCanvasElement; + sliderContainer: HTMLDivElement; + slider: HTMLInputElement; + sliderTextBox: HTMLSpanElement; + sliderTextBoxLength: number = 0; sliderTextBoxPadding = 5; // In pixels - dropdown : HTMLDivElement; - dropdownTable : HTMLTableElement; + dropdown: HTMLDivElement; + dropdownTable: HTMLTableElement; - cameraModeCounterMax : number = 60; - cameraModeCounter : number = 0; - cameraModeDisplay : HTMLDivElement; + cameraModeCounterMax: number = 60; + cameraModeCounter: number = 0; + cameraModeDisplay: HTMLDivElement; // FPS - fpsToggle : HTMLInputElement; - fpsDisplay : HTMLDivElement; - fps : number; - numFrames : number; + fpsToggle: HTMLInputElement; + fpsDisplay: HTMLDivElement; + fps: number; + numFrames: number; startTime: number; lastFrameTime: number; // Pointer events - pointerCoords : Map; - initPointerCoords : Map; + pointerCoords: Map; + initPointerCoords: Map; + pid0: number; + pid1: number; // media - mediaElement : HTMLMediaElement = null; - mediaToken : string; - startMedia : boolean = false; - mediaId : string = null; - globalVolume : number | undefined = 1.0; - volumeContainer : HTMLTableRowElement; - volumeInput : HTMLInputElement; - volumeToggle : HTMLInputElement; - seekTarget : number = null; + mediaElement: HTMLMediaElement = null; + mediaToken: string; + startMedia: boolean = false; + mediaId: string = null; + globalVolume: number | undefined = 1.0; + volumeContainer: HTMLTableRowElement; + volumeInput: HTMLInputElement; + volumeToggle: HTMLInputElement; + seekTarget: number = null; // playback controls isPlaying: boolean = false; startPlayback: boolean = false; stopPlayback: boolean = false; - playbackRate : number = 1; + playbackRate: number = 1; - get Volume() : number | undefined - { + get Volume(): number | undefined { return this.globalVolume; } - set Volume(volume: number | undefined) - { + set Volume(volume: number | undefined) { this.globalVolume = volume; this.UpdateVolume(); } - get PlaybackRate() : number | undefined - { - if(this.mediaElement == null) - { + get PlaybackRate(): number | undefined { + if (this.mediaElement == null) { return undefined; } return this.mediaElement.playbackRate; } - set PlaybackRate(playbackRate: number) - { - if(this.mediaElement != null) - { + set PlaybackRate(playbackRate: number) { + if (this.mediaElement != null) { this.mediaElement.playbackRate = playbackRate; } @@ -412,44 +369,40 @@ export abstract class CanvasBase this.fps = 0 } - handlesMouse : boolean; - firstPerson : boolean = false; + handlesMouse: boolean; + firstPerson: boolean = false; currentFrameIndex = 0; // Current frame index lastRedraw = 0; - dataUrl : string = null; - isRecording : boolean = false; + dataUrl: string = null; + isRecording: boolean = false; - get IsRecording() : boolean - { + get IsRecording(): boolean { return this.isRecording; } - set IsRecording(isRecording : boolean) - { + set IsRecording(isRecording: boolean) { this.dataUrl = null; this.isRecording = isRecording; } - get DataUrl() : string - { + get DataUrl(): string { return this.dataUrl; } // Frames gotoFrame = null; // If none null, then this frame will be shown (one time only) - frameIdToIndexMap : any = {}; - frameIds : string[] = []; // Inverse of frameIdToIndexMap + frameIdToIndexMap: any = {}; + frameIds: string[] = []; // Inverse of frameIdToIndexMap - constructor(canvasId : string, public frameRate : number, public width : number, public height : number, objectCache : ObjectCache, public SetStatus : (status : string) => void, public SetWarning : (message : string) => void, public RequestRedraw : () => void, public ReportFrameIdChange : (canvasId : string, frameId : string) => void) - { + constructor(canvasId: string, public frameRate: number, public width: number, public height: number, objectCache: ObjectCache, public SetStatus: (status: string) => void, public SetWarning: (message: string) => void, public RequestRedraw: () => void, public ReportFrameIdChange: (canvasId: string, frameId: string) => void) { // Store image this.objectCache = objectCache; // Create span this.container = document.createElement("div"); this.container.className = "scenepic-canvas-container"; - + // Create html canvas this.htmlCanvas = document.createElement("canvas"); this.htmlCanvas.style.width = width.toString() + "px"; @@ -462,19 +415,24 @@ export abstract class CanvasBase // Create dropdown menu this.dropdown = document.createElement("div"); this.dropdown.className = "scenepic-dropdown"; - this.dropdown.addEventListener("click", event => { - if (event.target == this.dropdown) { + this.dropdown.addEventListener("click", event => { + if (event.target == this.dropdown) { this.dropdown.classList.toggle("is-active"); - this.htmlCanvas.focus(); - } + this.htmlCanvas.focus(); + } }); this.dropdownTable = document.createElement("table"); - this.dropdownTable.className = "scenepic-dropdown-table"; + this.dropdownTable.classList.add("scenepic-dropdown-table"); + if (width < 300) { + this.dropdownTable.classList.add("scenepic-left-table"); + } else { + this.dropdownTable.classList.add("scenepic-right-table"); + } this.dropdown.appendChild(this.dropdownTable); this.container.appendChild(this.dropdown); this.htmlCanvas.addEventListener("click", () => { - this.dropdown.classList.remove("is-active"); - this.htmlCanvas.focus(); + this.dropdown.classList.remove("is-active"); + this.htmlCanvas.focus(); }); // Create slider @@ -507,16 +465,14 @@ export abstract class CanvasBase this.initPointerCoords = new Map(); } - private addCameraModeUI() - { + private addCameraModeUI() { this.cameraModeDisplay = document.createElement("div"); this.cameraModeDisplay.className = "scenepic-camera-mode"; this.cameraModeDisplay.style.visibility = "hidden"; this.container.appendChild(this.cameraModeDisplay); } - private addFPSUI() - { + private addFPSUI() { // Create FPS container let container = document.createElement("tr"); this.fpsToggle = document.createElement("input"); @@ -541,8 +497,7 @@ export abstract class CanvasBase this.fps = 0.0 } - private addVolumeUI() - { + private addVolumeUI() { this.volumeInput = document.createElement("input"); this.volumeInput.type = "range"; this.volumeInput.min = "0"; @@ -571,113 +526,123 @@ export abstract class CanvasBase this.dropdownTable.appendChild(this.volumeContainer); } - HandlePointerDown(point: vec2, event: PointerEvent) - { - this.pointerCoords.set(event.pointerId, point); - this.initPointerCoords.set(event.pointerId, point); + PinchZoom(point: vec2, event: PointerEvent): PinchZoomInfo { + const old0 = this.pointerCoords.get(this.pid0); + const old1 = this.pointerCoords.get(this.pid1); + const new0 = this.pid0 == event.pointerId ? point : old0; + const new1 = this.pid1 == event.pointerId ? point : old1; + + const oldDist = vec2.distance(old0, old1); + const newDist = vec2.distance(new0, new1); + + const oldAngle = Math.atan2(old1[1] - old0[1], old1[0] - old0[0]); + const newAngle = Math.atan2(new1[1] - new0[1], new1[0] - new0[0]); + + let center = vec2.add(vec2.create(), new0, new1); + vec2.scale(center, center, 0.5); + return new PinchZoomInfo(newDist / oldDist, newAngle - oldAngle, center); } - HandlePointerMove(point: vec2, event: PointerEvent) - { - if(this.pointerCoords.size == 0){ + HandlePointerDown(point: vec2, event: PointerEvent) { + this.pointerCoords.set(event.pointerId, point); + this.initPointerCoords.set(event.pointerId, point); + + if (this.pointerCoords.size == 1) { + this.pid0 = event.pointerId; + } else if (this.pointerCoords.size == 2) { + this.pid1 = event.pointerId; + } + } + + HandlePointerMove(point: vec2, event: PointerEvent) { + if (this.pointerCoords.size == 0) { return; } this.pointerCoords.set(event.pointerId, point); } - HandlePointerUp(event: PointerEvent) - { + HandlePointerUp(event: PointerEvent) { this.pointerCoords.delete(event.pointerId); this.initPointerCoords.delete(event.pointerId); + + if (this.pointerCoords.size == 1) { + if (this.pid0 == event.pointerId) { + this.pid0 = this.pid1; + } + } else if (this.pointerCoords.size == 2) { + const pids = this.pointerCoords.keys(); + this.pid0 = pids.next().value; + this.pid1 = pids.next().value; + } } - HandleMouseWheel(event: WheelEvent) - { + HandleMouseWheel(event: WheelEvent) { } - SetMedia(mediaId: string) - { + SetMedia(mediaId: string) { this.mediaId = mediaId; this.mediaElement = this.objectCache.GetObject(mediaId); - if(this.mediaElement != null) - { + if (this.mediaElement != null) { this.mediaToken = this.objectCache.Acquire(mediaId); this.volumeContainer.style.visibility = "inherit"; this.UpdateVolume(); } - else - { + else { console.warn("Invalid mediaId:" + mediaId); } } - UpdateFPSDisplay() - { - if(this.fpsToggle.checked) - { + UpdateFPSDisplay() { + if (this.fpsToggle.checked) { this.fpsDisplay.style.visibility = "visible"; } - else - { + else { this.fpsDisplay.style.visibility = "hidden"; } } - UpdateVolume() - { - if(this.mediaElement == null) - { + UpdateVolume() { + if (this.mediaElement == null) { return; } - if(this.globalVolume == undefined) - { + if (this.globalVolume == undefined) { this.mediaElement.volume = 0.0; } - else - { - if(this.volumeToggle.checked) - { + else { + if (this.volumeToggle.checked) { this.mediaElement.volume = this.volumeInput.valueAsNumber * this.globalVolume; } - else - { + else { this.mediaElement.volume = 0; } } } - get FrameCount() : number - { + get FrameCount(): number { return this.frameIds.length; } - get FirstPerson() : boolean - { + get FirstPerson(): boolean { return this.firstPerson; } - set FirstPerson(firstPerson: boolean) - { - if(firstPerson != this.firstPerson) - { + set FirstPerson(firstPerson: boolean) { + if (firstPerson != this.firstPerson) { this.cameraModeCounter = this.cameraModeCounterMax; this.firstPerson = firstPerson; - } + } } - HandleKeyUp(key: string) - { + HandleKeyUp(key: string) { } - HandleKeyDown(key : string) : [boolean, boolean] - { + HandleKeyDown(key: string): [boolean, boolean] { var handled = true; var changedFrame = true; - switch(key.toLowerCase()) - { + switch (key.toLowerCase()) { case "arrowleft": case "left": changedFrame = this.ShowPreviousFrame(); @@ -708,12 +673,10 @@ export abstract class CanvasBase // Execute possibly multiple nested canvas commands - ExecuteCanvasCommands(command : any) - { + ExecuteCanvasCommands(command: any) { // Support recursive parsing of sub-lists of commands - if (Array.isArray(command)) - { - for(var com of command) + if (Array.isArray(command)) { + for (var com of command) this.ExecuteCanvasCommands(com); return; } @@ -727,10 +690,8 @@ export abstract class CanvasBase } // Execute a single canvas command (likely to be called via sub-class call to super.ExecuteCanvasCommand) - ExecuteCanvasCommand(command : any) - { - switch(command["CommandType"]) - { + ExecuteCanvasCommand(command: any) { + switch (command["CommandType"]) { case "AddFrame": var frameId = String(command["FrameId"]); this.AddFrame(frameId); @@ -757,12 +718,10 @@ export abstract class CanvasBase } // Execute possibly multiple nested frame commands - ExecuteFrameCommands(frameId : string, command : any) - { + ExecuteFrameCommands(frameId: string, command: any) { // Support recursive parsing of sub-lists of commands - if (Array.isArray(command)) - { - for(var com of command) + if (Array.isArray(command)) { + for (var com of command) this.ExecuteFrameCommands(frameId, com); return; } @@ -777,14 +736,12 @@ export abstract class CanvasBase } // Execute a single frame command (likely to be called via sub-class call to super.ExecuteFrameCommand) - ExecuteFrameCommand(command : any, frameIndex : number) - { - switch(command["CommandType"]) - { + ExecuteFrameCommand(command: any, frameIndex: number) { + switch (command["CommandType"]) { case "ClearFrame": this.ClearFrame(frameIndex); break; - + case "ShowFrame": this.ShowFrame(frameIndex); break; @@ -794,12 +751,10 @@ export abstract class CanvasBase break; } } - - AddFrame(frameId : string) - { + + AddFrame(frameId: string) { // Check unique - if (frameId in this.frameIdToIndexMap) - { + if (frameId in this.frameIdToIndexMap) { //this.SetWarning("Frame id " + frameId + " already exists in SPCanvas."); // Simply ignore frame redefinitions return; @@ -817,27 +772,24 @@ export abstract class CanvasBase this.sliderContainer.style.visibility = frameIndex > 0 ? "visible" : "hidden"; // Goto frame - if (this.gotoFrame != null && this.gotoFrame == frameIndex) - { + if (this.gotoFrame != null && this.gotoFrame == frameIndex) { this.ShowFrame(frameIndex); this.gotoFrame = null; } // Update frameid text - if (this.currentFrameIndex == frameIndex) - { + if (this.currentFrameIndex == frameIndex) { this.UpdateSliderTextBox(frameId); this.ReportFrameIdChange(this.htmlCanvas.id, this.GetCurrentFrameId()); } } - abstract AllocateFrame() : void; - abstract DeallocateFrame(frameIndex : number) : void; - NotifyMeshUpdated(meshId : string) {} - NotifyTextureUpdated(meshId : string) {} - - ClearFrame(frameIndex : number) - { + abstract AllocateFrame(): void; + abstract DeallocateFrame(frameIndex: number): void; + NotifyMeshUpdated(meshId: string) { } + NotifyTextureUpdated(meshId: string) { } + + ClearFrame(frameIndex: number) { // Check frame exists if (frameIndex >= this.frameIds.length) return; @@ -846,38 +798,30 @@ export abstract class CanvasBase this.PrepareBuffers(); } - GetCurrentFrameId() - { + GetCurrentFrameId() { if (this.frameIds == null || this.frameIds.length == 0) return null; return this.frameIds[this.currentFrameIndex]; } // Shows a frame in the canvas - ShowFrame(frameIndex : number) - { - if (this.currentFrameIndex == frameIndex) - { + ShowFrame(frameIndex: number) { + if (this.currentFrameIndex == frameIndex) { // Only update if needed (avoids slider value change potentially causing infinite loop) return; } - if(this.mediaElement != null) - { - if(this.mediaToken != null) - { + if (this.mediaElement != null) { + if (this.mediaToken != null) { // this canvas owns the media file, so we update the timestamp let newTime = frameIndex / this.frameRate; - if(this.isPlaying) - { + if (this.isPlaying) { let frameDiff = frameIndex - this.currentFrameIndex; - if(frameDiff < 0 || frameDiff > 3) - { + if (frameDiff < 0 || frameDiff > 3) { this.mediaElement.currentTime = newTime; } } - else - { + else { this.mediaElement.currentTime = newTime; } } @@ -896,16 +840,14 @@ export abstract class CanvasBase return true; } - ShowPreviousFrame() - { + ShowPreviousFrame() { var newFrameIndex = this.currentFrameIndex - 1; if (newFrameIndex == -1) // Wrap-around newFrameIndex = this.frameIds.length - 1; return this.ShowFrame(newFrameIndex); } - ShowNextFrame() - { + ShowNextFrame() { var newFrameIndex = this.currentFrameIndex + 1; if (newFrameIndex >= this.frameIds.length) { // Wrap-around @@ -915,44 +857,38 @@ export abstract class CanvasBase return this.ShowFrame(newFrameIndex); } - UpdateSliderTextBox(frameId : string) - { + UpdateSliderTextBox(frameId: string) { // Update text this.sliderTextBox.innerHTML = frameId; // Update min-width - if (frameId.length > this.sliderTextBoxLength) - { + if (frameId.length > this.sliderTextBoxLength) { this.sliderTextBoxLength = frameId.length; var width = this.sliderTextBox.offsetWidth - 2 * this.sliderTextBoxPadding; this.sliderTextBox.style.minWidth = width.toString() + "px"; } } - + // Rebuild buffers - protected PrepareBuffers() : void - { + protected PrepareBuffers(): void { } // Render method - abstract Render() : void; + abstract Render(): void; stopRenderLoop = false; - StopRenderLoop() - { + StopRenderLoop() { this.stopRenderLoop = true; } - StartRenderLoop() - { + StartRenderLoop() { this.stopRenderLoop = false; requestAnimationFrame(() => this.Tick()); } // Render loop - Tick() - { + Tick() { // Check for disable flag if (this.stopRenderLoop) return; @@ -961,23 +897,20 @@ export abstract class CanvasBase var pixelRatio = window.devicePixelRatio; var displayWidth = Math.floor(this.htmlCanvas.clientWidth * pixelRatio); var displayHeight = Math.floor(this.htmlCanvas.clientHeight * pixelRatio); - if (this.htmlCanvas.width != displayWidth || this.htmlCanvas.height != displayHeight) - { + if (this.htmlCanvas.width != displayWidth || this.htmlCanvas.height != displayHeight) { this.htmlCanvas.width = displayWidth; this.htmlCanvas.height = displayHeight; } let timestamp = Date.now(); - if(this.cameraModeCounter > 0) - { + if (this.cameraModeCounter > 0) { this.cameraModeDisplay.style.opacity = (this.cameraModeCounter / this.cameraModeCounterMax).toString(); this.cameraModeDisplay.innerText = this.firstPerson ? "First Person" : "Focus"; this.cameraModeCounter -= 1; } - if(this.startPlayback) - { + if (this.startPlayback) { this.startPlayback = false; this.fps = 0.0; this.isPlaying = true; @@ -987,56 +920,46 @@ export abstract class CanvasBase this.startMedia = true; } - if(this.stopPlayback) - { + if (this.stopPlayback) { this.startMedia = false; this.stopPlayback = false; this.isPlaying = false; this.fpsDisplay.innerText = ""; - if(this.mediaElement != null) - { + if (this.mediaElement != null) { this.mediaElement.pause(); } } - if(this.isPlaying) - { - let newFrameIndex : number; + if (this.isPlaying) { + let newFrameIndex: number; - if(this.mediaElement != null) - { + if (this.mediaElement != null) { newFrameIndex = Math.round(this.mediaElement.currentTime * this.frameRate); - if (newFrameIndex >= this.frameIds.length) - { + if (newFrameIndex >= this.frameIds.length) { this.mediaElement.currentTime = 0; } } - else - { + else { let frameDuration = 1000 / (this.playbackRate * this.frameRate); let timestamp = Date.now(); let elapsed = timestamp - this.lastFrameTime; newFrameIndex = this.currentFrameIndex; - while(elapsed > frameDuration) - { + while (elapsed > frameDuration) { newFrameIndex += 1; elapsed -= frameDuration; this.lastFrameTime += frameDuration; - if (newFrameIndex >= this.frameIds.length) - { + if (newFrameIndex >= this.frameIds.length) { // Wrap-around newFrameIndex = 0; } - } + } } - if(newFrameIndex != this.currentFrameIndex) - { + if (newFrameIndex != this.currentFrameIndex) { this.ShowFrame(newFrameIndex); this.numFrames += 1; let duration = timestamp - this.startTime; - if(duration >= CanvasBase.FPS_FREQUENCY) - { + if (duration >= CanvasBase.FPS_FREQUENCY) { let sample = (1000 * this.numFrames) / (timestamp - this.startTime); this.fps = this.fps * CanvasBase.FPS_DECAY + (1 - CanvasBase.FPS_DECAY) * sample; this.numFrames = 0; @@ -1046,8 +969,7 @@ export abstract class CanvasBase } } - if(this.startMedia && this.mediaElement != null) - { + if (this.startMedia && this.mediaElement != null) { this.startMedia = false; this.mediaElement.playbackRate = this.playbackRate; this.mediaElement.play(); @@ -1055,28 +977,24 @@ export abstract class CanvasBase // Ensure this function is called again at the next sensible timeslot requestAnimationFrame(() => this.Tick()); - - if(timestamp - this.lastRedraw > CanvasBase.TIME_PER_REDRAW) - { + + if (timestamp - this.lastRedraw > CanvasBase.TIME_PER_REDRAW) { // Render this.Render(); this.lastRedraw = timestamp; } - if(this.isRecording) - { + if (this.isRecording) { this.dataUrl = this.htmlCanvas.toDataURL(); } } - StartPlaying() - { + StartPlaying() { this.startPlayback = true; this.stopPlayback = false; } - StopPlaying() - { + StopPlaying() { this.stopPlayback = true; this.startPlayback = false; } diff --git a/tssrc/DropDownMenu.ts b/tssrc/DropDownMenu.ts index 7acaeda..e2630e6 100644 --- a/tssrc/DropDownMenu.ts +++ b/tssrc/DropDownMenu.ts @@ -1,14 +1,12 @@ -export default class DropDownMenu -{ - title : string; - header : HTMLSpanElement; - wrapper : HTMLDivElement; // Contains both contents and inputBox - contents : HTMLSpanElement; - dropDownMenu : HTMLSelectElement; +export default class DropDownMenu { + title: string; + header: HTMLSpanElement; + wrapper: HTMLDivElement; // Contains both contents and inputBox + contents: HTMLSpanElement; + dropDownMenu: HTMLSelectElement; - constructor(title : string, style : string, parent : HTMLElement) - { + constructor(title: string, style: string, parent: HTMLElement) { // Create div var div = document.createElement("div"); div.className = "scenepic"; // CSS style class name @@ -40,10 +38,8 @@ export default class DropDownMenu parent.appendChild(div); } - SetItems(items : string[]) - { - items.forEach(item => - { + SetItems(items: string[]) { + items.forEach(item => { // Create table row var option = document.createElement("option"); option.value = item; @@ -54,24 +50,20 @@ export default class DropDownMenu this.Update(); } - SetItemDisabled(index : number, disabled : boolean) - { + SetItemDisabled(index: number, disabled: boolean) { this.dropDownMenu.options[index].disabled = disabled; } - SetSelection(index : number) - { + SetSelection(index: number) { this.dropDownMenu.selectedIndex = index; } - SetTitle(htmlTitle : string) - { + SetTitle(htmlTitle: string) { this.title = htmlTitle; this.Update(); } - Update() - { + Update() { this.header.style.display = (this.title == null || this.title == "") ? "none" : ""; this.header.innerHTML = this.title; this.wrapper.style.display = "inline-block"; diff --git a/tssrc/Graph.ts b/tssrc/Graph.ts index 1bb400a..529699c 100644 --- a/tssrc/Graph.ts +++ b/tssrc/Graph.ts @@ -29,19 +29,19 @@ class Rect { this.height = height; } - get topEdge() : number { + get topEdge(): number { return this.y; } - get bottomEdge() : number { + get bottomEdge(): number { return this.y + this.height; } - get leftEdge() : number { + get leftEdge(): number { return this.x; } - get rightEdge() : number { + get rightEdge(): number { return this.x + this.width; } @@ -93,27 +93,27 @@ class Layout { width: number; height: number; - get topEdge() : number { + get topEdge(): number { return this.y; } - get bottomEdge() : number { + get bottomEdge(): number { return this.y + this.height; } - get leftEdge() : number { + get leftEdge(): number { return this.x; } - get rightEdge() : number { + get rightEdge(): number { return this.x + this.width; } - constructor({top = new Rect(), - right = new Rect(), - bottom = new Rect(), - left = new Rect(), - center = new Rect()} : {top? : Rect | Layout, right? : Rect | Layout, bottom? : Rect | Layout, left? : Rect | Layout, center? : Rect | Layout} = {}) { + constructor({ top = new Rect(), + right = new Rect(), + bottom = new Rect(), + left = new Rect(), + center = new Rect() }: { top?: Rect | Layout, right?: Rect | Layout, bottom?: Rect | Layout, left?: Rect | Layout, center?: Rect | Layout } = {}) { this.x = this.y = this.width = this.height = 0; this.top = top; this.right = right; @@ -404,7 +404,7 @@ export default class Graph extends CanvasBase { let layout = new Layout(); let nameRect = new Rect(0, 0, nameColumnWidth, nameHeight); - let nameLayout = new Layout({left: nameRect}); + let nameLayout = new Layout({ left: nameRect }); switch (this.nameAlign) { case "top": layout.top = nameLayout; @@ -426,34 +426,34 @@ export default class Graph extends CanvasBase { let valueRect = new Rect(0, 0, valueColumnWidth, valueHeight); switch (this.valueAlign) { case "top": - if(layout.top instanceof Layout){ + if (layout.top instanceof Layout) { layout.top.right = valueRect; - }else{ - layout.top = new Layout({right: valueRect}) + } else { + layout.top = new Layout({ right: valueRect }) } break; case "bottom": - if(layout.bottom instanceof Layout){ + if (layout.bottom instanceof Layout) { layout.bottom.right = valueRect; - }else{ - layout.bottom = new Layout({right: valueRect}) + } else { + layout.bottom = new Layout({ right: valueRect }) } break; case "left": - if(layout.left instanceof Layout){ + if (layout.left instanceof Layout) { layout.left.right = valueRect; - }else{ - layout.left = new Layout({right: valueRect}) + } else { + layout.left = new Layout({ right: valueRect }) } break; case "right": - if(layout.right instanceof Layout){ + if (layout.right instanceof Layout) { layout.right.right = valueRect; - }else{ - layout.right = new Layout({right: valueRect}) + } else { + layout.right = new Layout({ right: valueRect }) } break; } diff --git a/tssrc/Mesh.ts b/tssrc/Mesh.ts index fbae269..63f66f1 100644 --- a/tssrc/Mesh.ts +++ b/tssrc/Mesh.ts @@ -1,53 +1,51 @@ // NB ONLY TO BE USED INTERNALLY, NOT IN API import Misc from "./Misc" -import {vec3, vec4, mat4} from "gl-matrix"; +import { vec3, vec4, mat4 } from "gl-matrix"; import { VertexBuffer, VertexBufferType } from "./VertexBuffers"; -export default class Mesh -{ - static readonly ElementsPerTriangle : number = 3; - static readonly ElementsPerLine : number = 2; - ElementsPerVertex : number; - VertexOffsetPosition : number = 0; - VertexOffsetNormal : number = 3; - VertexOffsetColor : number = 0; // Dummy value - simplifies logic in webglmeshbuffers - VertexOffsetTexture : number = 0; // Dummy value - simplifies logic in webglmeshbuffers +export default class Mesh { + static readonly ElementsPerTriangle: number = 3; + static readonly ElementsPerLine: number = 2; + ElementsPerVertex: number; + VertexOffsetPosition: number = 0; + VertexOffsetNormal: number = 3; + VertexOffsetColor: number = 0; // Dummy value - simplifies logic in webglmeshbuffers + VertexOffsetTexture: number = 0; // Dummy value - simplifies logic in webglmeshbuffers - ElementsPerInstance : number; - InstanceOffsetPosition : number = 0; - InstanceOffsetRotation : number = 0; - InstanceOffsetColor : number = 0; + ElementsPerInstance: number; + InstanceOffsetPosition: number = 0; + InstanceOffsetRotation: number = 0; + InstanceOffsetColor: number = 0; // Host memory buffers - centerOfMass : vec3; // Mean of vertex positions, to allow depth ordering for improved transparency - vertexBuffer : Float32Array; // Vertex.Length elements per vertex - bytesPerIndex : number; // 2 or 4 - triangleBuffer : ArrayBuffer; // 3 elements (v1, v2, v3) per triangle - lineBuffer : ArrayBuffer; // 2 elements (v1, v2) per line - wireframeEdgeBuffer : ArrayBuffer; // 2 elements (v1, v2) per edge, used for wireframe - instanceBuffer : Float32Array; // Used for instancing - instanceBufferHasRotations : boolean; - instanceBufferHasColors : boolean; - color : vec3; // Either null (per-vertex coloring stored in vertex buffer) or a 3-vector (shared color across whole mesh) - textureId : string; - isBillboard : boolean; // If true, zero out rotation - isLabel : boolean; // If true, treat the mesh specially as a label - nnTexture : boolean; // If true, render textures using nearest neighbor interpolation, otherwise linear - useTextureAlpha : boolean; // If true, use the texture's alpha channel for transparency. Otherwise treat as opaque. - cameraSpace : boolean; // If true, the mesh is defined in camera-space rather than world space and will thus not move with mouse commands - layerId : string; // Unique identifier for a layer of Meshes with shared properties - doubleSided : boolean; // If true, then back-face culling is turned off + centerOfMass: vec3; // Mean of vertex positions, to allow depth ordering for improved transparency + vertexBuffer: Float32Array; // Vertex.Length elements per vertex + bytesPerIndex: number; // 2 or 4 + triangleBuffer: ArrayBuffer; // 3 elements (v1, v2, v3) per triangle + lineBuffer: ArrayBuffer; // 2 elements (v1, v2) per line + wireframeEdgeBuffer: ArrayBuffer; // 2 elements (v1, v2) per edge, used for wireframe + instanceBuffer: Float32Array; // Used for instancing + instanceBufferHasRotations: boolean; + instanceBufferHasColors: boolean; + color: vec3; // Either null (per-vertex coloring stored in vertex buffer) or a 3-vector (shared color across whole mesh) + textureId: string; + isBillboard: boolean; // If true, zero out rotation + isLabel: boolean; // If true, treat the mesh specially as a label + nnTexture: boolean; // If true, render textures using nearest neighbor interpolation, otherwise linear + useTextureAlpha: boolean; // If true, use the texture's alpha channel for transparency. Otherwise treat as opaque. + cameraSpace: boolean; // If true, the mesh is defined in camera-space rather than world space and will thus not move with mouse commands + layerId: string; // Unique identifier for a layer of Meshes with shared properties + doubleSided: boolean; // If true, then back-face culling is turned off // Properties - CountVertices() : number { return this.vertexBuffer.length / this.ElementsPerVertex; } - CountTriangles() : number { return this.triangleBuffer.byteLength / (this.bytesPerIndex * Mesh.ElementsPerTriangle); } - CountLines() : number { return this.lineBuffer.byteLength / (this.bytesPerIndex * Mesh.ElementsPerLine); } - CountWireframeEdges() : number { return this.wireframeEdgeBuffer != null ? this.wireframeEdgeBuffer.byteLength / (this.bytesPerIndex * Mesh.ElementsPerLine) : 0; } - CountInstances() : number { return this.instanceBuffer != null ? (this.instanceBuffer.length / this.ElementsPerInstance) : 1; } + CountVertices(): number { return this.vertexBuffer.length / this.ElementsPerVertex; } + CountTriangles(): number { return this.triangleBuffer.byteLength / (this.bytesPerIndex * Mesh.ElementsPerTriangle); } + CountLines(): number { return this.lineBuffer.byteLength / (this.bytesPerIndex * Mesh.ElementsPerLine); } + CountWireframeEdges(): number { return this.wireframeEdgeBuffer != null ? this.wireframeEdgeBuffer.byteLength / (this.bytesPerIndex * Mesh.ElementsPerLine) : 0; } + CountInstances(): number { return this.instanceBuffer != null ? (this.instanceBuffer.length / this.ElementsPerInstance) : 1; } // Constructor - constructor(vertexBuffer : Float32Array, bytesPerIndex : number, triangleBuffer : ArrayBuffer, lineBuffer : ArrayBuffer, color : vec3, textureId : string = null, nnTexture : boolean = true, useTextureAlpha = true, instanceBuffer : Float32Array = null, instanceBufferHasRotations = false, instanceBufferHasColors = false) - { + constructor(vertexBuffer: Float32Array, bytesPerIndex: number, triangleBuffer: ArrayBuffer, lineBuffer: ArrayBuffer, color: vec3, textureId: string = null, nnTexture: boolean = true, useTextureAlpha = true, instanceBuffer: Float32Array = null, instanceBufferHasRotations = false, instanceBufferHasColors = false) { this.vertexBuffer = vertexBuffer; this.bytesPerIndex = bytesPerIndex; this.triangleBuffer = triangleBuffer; @@ -63,19 +61,17 @@ export default class Mesh this.layerId = null; // Deal with instance buffer - if (instanceBuffer == null) - { + if (instanceBuffer == null) { this.instanceBuffer = null; this.instanceBufferHasRotations = false; this.instanceBufferHasColors = false; } - else - { + else { this.instanceBuffer = instanceBuffer; this.instanceBufferHasRotations = instanceBufferHasRotations; this.instanceBufferHasColors = instanceBufferHasColors; } - + // Compute number of elements per vertex this.ElementsPerVertex = 6; // Position and normal: X,Y,Z,NX,NY,NZ if (this.color == null) // I.e. per-vertex rather than per-mesh color @@ -83,31 +79,26 @@ export default class Mesh this.VertexOffsetColor = this.ElementsPerVertex; this.ElementsPerVertex += 3; // Per-vertex color: R,G,B } - if (this.textureId != null) - { + if (this.textureId != null) { this.VertexOffsetTexture = this.ElementsPerVertex; this.ElementsPerVertex += 2; // Texture coords: U,V } // Compute number of elements per instance this.ElementsPerInstance = 3; // Translation: X,Y,Z - if (this.instanceBufferHasRotations) - { + if (this.instanceBufferHasRotations) { this.InstanceOffsetRotation = this.ElementsPerInstance; this.ElementsPerInstance += 4; // Quaternion rotation: W,X,Y,Z } - if (this.instanceBufferHasColors) - { + if (this.instanceBufferHasColors) { this.InstanceOffsetColor = this.ElementsPerInstance; this.ElementsPerInstance += 3; // Color: R,G,B } // Compute center of mass this.centerOfMass = vec3.create(); - if (this.CountVertices() > 0) - { - for(var i = 0; i < this.vertexBuffer.length; i += this.ElementsPerVertex) - { + if (this.CountVertices() > 0) { + for (var i = 0; i < this.vertexBuffer.length; i += this.ElementsPerVertex) { this.centerOfMass[0] += this.vertexBuffer[i + 0]; this.centerOfMass[1] += this.vertexBuffer[i + 1]; this.centerOfMass[2] += this.vertexBuffer[i + 2]; @@ -118,32 +109,28 @@ export default class Mesh } } - Update(buffer : Float32Array, update_flags: VertexBufferType) : Mesh - { - if(this.CountInstances() > 1) - { + Update(buffer: Float32Array, update_flags: VertexBufferType): Mesh { + if (this.CountInstances() > 1) { return this.ToInstanceBuffer(buffer, update_flags); } - else - { + else { return this.ToVertexBuffer(buffer, update_flags); } } - ToInstanceBuffer(update : Float32Array, updateFlags: VertexBufferType) : Mesh - { + ToInstanceBuffer(update: Float32Array, updateFlags: VertexBufferType): Mesh { let instanceBuffer = VertexBuffer.Update(this.instanceBuffer, this.ElementsPerInstance, update, updateFlags); let mesh = new Mesh(this.vertexBuffer, - this.bytesPerIndex, - this.triangleBuffer, - this.lineBuffer, - this.color, - this.textureId, - this.nnTexture, - this.useTextureAlpha, - instanceBuffer, - this.instanceBufferHasRotations, - this.instanceBufferHasColors) + this.bytesPerIndex, + this.triangleBuffer, + this.lineBuffer, + this.color, + this.textureId, + this.nnTexture, + this.useTextureAlpha, + instanceBuffer, + this.instanceBufferHasRotations, + this.instanceBufferHasColors) mesh.cameraSpace = this.cameraSpace; mesh.layerId = this.layerId; mesh.doubleSided = this.doubleSided; @@ -152,20 +139,19 @@ export default class Mesh return mesh; } - ToVertexBuffer(update : Float32Array, updateFlags: VertexBufferType) : Mesh - { + ToVertexBuffer(update: Float32Array, updateFlags: VertexBufferType): Mesh { let vertexBuffer = VertexBuffer.Update(this.vertexBuffer, this.ElementsPerVertex, update, updateFlags); let mesh = new Mesh(vertexBuffer, - this.bytesPerIndex, - this.triangleBuffer, - this.lineBuffer, - this.color, - this.textureId, - this.nnTexture, - this.useTextureAlpha, - this.instanceBuffer, - this.instanceBufferHasRotations, - this.instanceBufferHasColors) + this.bytesPerIndex, + this.triangleBuffer, + this.lineBuffer, + this.color, + this.textureId, + this.nnTexture, + this.useTextureAlpha, + this.instanceBuffer, + this.instanceBufferHasRotations, + this.instanceBufferHasColors) mesh.cameraSpace = this.cameraSpace; mesh.layerId = this.layerId; mesh.doubleSided = this.doubleSided; @@ -174,15 +160,13 @@ export default class Mesh return mesh; } - static Parse(definition : any) - { - var color : vec3 = null; - var textureId : string = null; - var nnTexture : boolean = true; - var useTextureAlpha : boolean = true; + static Parse(definition: any) { + var color: vec3 = null; + var textureId: string = null; + var nnTexture: boolean = true; + var useTextureAlpha: boolean = true; - switch(definition["PrimitiveType"]) - { + switch (definition["PrimitiveType"]) { case "SingleColorMesh": color = Misc.Base64ToFloat32Array(definition["Color"]); textureId = Misc.GetDefault(definition, "TextureId", null); @@ -191,9 +175,8 @@ export default class Mesh case "MultiColorMesh": let vertexBuffer = Misc.Base64ToFloat32Array(definition["VertexBuffer"]); var indexBufferType = definition["IndexBufferType"]; - var bytesPerIndex : number, triangleBuffer : ArrayBuffer, lineBuffer : ArrayBuffer; - if (indexBufferType == "UInt16") - { + var bytesPerIndex: number, triangleBuffer: ArrayBuffer, lineBuffer: ArrayBuffer; + if (indexBufferType == "UInt16") { bytesPerIndex = 2; triangleBuffer = Misc.Base64ToUInt16Array(definition["TriangleBuffer"]).buffer; lineBuffer = Misc.Base64ToUInt16Array(definition["LineBuffer"]).buffer; @@ -209,20 +192,17 @@ export default class Mesh var instanceBufferHasColors = Misc.GetDefault(definition, "InstanceBufferHasColors", false); return new Mesh(vertexBuffer, bytesPerIndex, triangleBuffer, lineBuffer, color, textureId, nnTexture, useTextureAlpha, instanceBuffer, instanceBufferHasRotations, instanceBufferHasColors); default: - throw "Unknown \"Primitive\" type in mesh: " + JSON.stringify(definition); + throw "Unknown \"Primitive\" type in mesh: " + JSON.stringify(definition); } } - GetWireframeEdgeBuffer() - { - if (this.wireframeEdgeBuffer == null) - { + GetWireframeEdgeBuffer() { + if (this.wireframeEdgeBuffer == null) { this.wireframeEdgeBuffer = new ArrayBuffer(this.triangleBuffer.byteLength * 2); - var triangleView : any = this.bytesPerIndex == 2 ? new Uint16Array(this.triangleBuffer) : new Uint32Array(this.triangleBuffer); - var wireframeView : any = this.bytesPerIndex == 2 ? new Uint16Array(this.wireframeEdgeBuffer) : new Uint32Array(this.wireframeEdgeBuffer); + var triangleView: any = this.bytesPerIndex == 2 ? new Uint16Array(this.triangleBuffer) : new Uint32Array(this.triangleBuffer); + var wireframeView: any = this.bytesPerIndex == 2 ? new Uint16Array(this.wireframeEdgeBuffer) : new Uint32Array(this.wireframeEdgeBuffer); - for(var i = 0; i < triangleView.length; i += 3) - { + for (var i = 0; i < triangleView.length; i += 3) { wireframeView[2 * i + 5] = wireframeView[2 * i + 0] = triangleView[i + 0]; wireframeView[2 * i + 2] = wireframeView[2 * i + 1] = triangleView[i + 1]; wireframeView[2 * i + 4] = wireframeView[2 * i + 3] = triangleView[i + 2]; diff --git a/tssrc/MeshPicker.ts b/tssrc/MeshPicker.ts index 0e51ed3..e438018 100644 --- a/tssrc/MeshPicker.ts +++ b/tssrc/MeshPicker.ts @@ -4,11 +4,11 @@ import ShaderProgram from "./Shaders"; import WebGLMeshBuffers from "./WebGLMeshBuffers"; export class MeshPicker { - readonly frameBuffer : WebGLFramebuffer; - readonly program : ShaderProgram; - data : Uint8Array; + readonly frameBuffer: WebGLFramebuffer; + readonly program: ShaderProgram; + data: Uint8Array; - constructor(gl : WebGL2RenderingContext, width: number, height: number){ + constructor(gl: WebGL2RenderingContext, width: number, height: number) { this.program = new ShaderProgram(gl, "pickerVertex", "pickerFragment"); let targetTexture = Misc.AssertNotNull(gl.createTexture(), "WebGL returned a null texture"); @@ -17,9 +17,9 @@ export class MeshPicker { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, - width, height, 0, - gl.RGBA, gl.UNSIGNED_BYTE, null); - + width, height, 0, + gl.RGBA, gl.UNSIGNED_BYTE, null); + let depthBuffer = Misc.AssertNotNull(gl.createRenderbuffer(), "WebGL returned a null render buffer"); gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); @@ -28,14 +28,14 @@ export class MeshPicker { gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture, 0); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); this.data = new Uint8Array(4); gl.bindFramebuffer(gl.FRAMEBUFFER, null); } - Pick(gl : WebGL2RenderingContext, meshes: [WebGLMeshBuffers, mat4][], point: vec2, v2sMatrix: mat4) : number { + Pick(gl: WebGL2RenderingContext, meshes: [WebGLMeshBuffers, mat4][], point: vec2, v2sMatrix: mat4): number { const canvas = gl.canvas; gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); @@ -50,7 +50,7 @@ export class MeshPicker { const program = this.program; gl.useProgram(program.program); - meshes.forEach(function([buffer, w2vMatrix]) { + meshes.forEach(function ([buffer, w2vMatrix]) { buffer.RenderPicker(program, v2sMatrix, w2vMatrix) }); diff --git a/tssrc/Misc.ts b/tssrc/Misc.ts index c68b990..778e9af 100644 --- a/tssrc/Misc.ts +++ b/tssrc/Misc.ts @@ -1,60 +1,50 @@ -import {mat3, mat4, vec3, vec4, quat} from "gl-matrix"; +import { mat3, mat4, vec3, vec4, quat } from "gl-matrix"; import * as pako from "pako"; -export default class Misc -{ - static Sign(value: number): number - { - if(value < 0) - { +export default class Misc { + static Sign(value: number): number { + if (value < 0) { return -1; } - - if(value > 0) - { + + if (value > 0) { return 1; } - + return 0; } - static Mat3FromMat4(m4 : mat4) : mat3 - { + static Mat3FromMat4(m4: mat4): mat3 { var m3 = mat3.create(); mat3.fromMat4(m3, m4); return m3; } - static NormalizeVec3InPlace(vec : Float32Array, ind : number) - { - var norm = 1.0/Math.sqrt(vec[ind] * vec[ind] + vec[ind+1] * vec[ind+1] + vec[ind+2] * vec[ind+2]); + static NormalizeVec3InPlace(vec: Float32Array, ind: number) { + var norm = 1.0 / Math.sqrt(vec[ind] * vec[ind] + vec[ind + 1] * vec[ind + 1] + vec[ind + 2] * vec[ind + 2]); vec[ind] *= norm; - vec[ind+1] *= norm; - vec[ind+2] *= norm; + vec[ind + 1] *= norm; + vec[ind + 2] *= norm; } - static GetDefault(obj, name, defaultValue) : any - { + static GetDefault(obj, name, defaultValue): any { if (name in obj) return obj[name]; else return defaultValue; } - static PushOrCreateArray(obj, key, value) - { + static PushOrCreateArray(obj, key, value) { if (!(key in obj)) obj[key] = []; obj[key].push(value); } - static DecoderArray : any = null; + static DecoderArray: any = null; - static Base64ToArrayBuffer(base64str : string) - { + static Base64ToArrayBuffer(base64str: string) { // Initialize decoder array if necessary (not threadsafe) - if (Misc.DecoderArray == null) - { + if (Misc.DecoderArray == null) { const CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; Misc.DecoderArray = {}; for (var i = 0; i < CODES.length; i++) @@ -70,8 +60,7 @@ export default class Misc var aView = new Uint8Array(aBuff); // Decode - for(var i = 0, j = 0; i < countBytes; i += 3) - { + for (var i = 0, j = 0; i < countBytes; i += 3) { // Get characters var c0 = base64str.charCodeAt(j++); var c1 = base64str.charCodeAt(j++); @@ -85,11 +74,11 @@ export default class Misc var e3 = Misc.DecoderArray[c3]; aView[i] = (e0 << 2) | (e1 >> 4); - if (e2 != 64) aView[i+1] = ((e1 & 15) << 4) | (e2 >> 2); - if (e3 != 64) aView[i+2] = ((e2 & 3) << 6) | e3; + if (e2 != 64) aView[i + 1] = ((e1 & 15) << 4) | (e2 >> 2); + if (e3 != 64) aView[i + 2] = ((e2 & 3) << 6) | e3; } - let result : Uint8Array; + let result: Uint8Array; try { result = pako.inflate(aView); } catch (err) { @@ -99,29 +88,25 @@ export default class Misc return result.buffer; } - static DataUrlToBlob(dataUrl: string) : Blob - { + static DataUrlToBlob(dataUrl: string): Blob { let parts = dataUrl.split(','); let mime = parts[0].match(/:(.*?);/)[1]; let array = new Uint8Array(Misc.Base64ToArrayBuffer(parts[1])) - return new Blob([array], {"type": mime}); + return new Blob([array], { "type": mime }); } - static Base64ToBlob(base64: string, mime: string) : Blob - { + static Base64ToBlob(base64: string, mime: string): Blob { let array = new Uint8Array(Misc.Base64ToArrayBuffer(base64)); - return new Blob([array], {"type": mime}); + return new Blob([array], { "type": mime }); } - static DataUrlToArrayBuffer(dataUrl: string) : ArrayBuffer - { + static DataUrlToArrayBuffer(dataUrl: string): ArrayBuffer { let parts = dataUrl.split(','); return Misc.Base64ToArrayBuffer(parts[1]); } // Convert either from Base64 string or from regular array to Float32Array - static Base64ToFloat32Array(obj : any) - { + static Base64ToFloat32Array(obj: any) { if (obj == null) return null; if (typeof obj == "string") return new Float32Array(Misc.Base64ToArrayBuffer(obj)); @@ -129,8 +114,7 @@ export default class Misc return new Float32Array(obj); } - static Base64ToUInt8Array(obj: any) - { + static Base64ToUInt8Array(obj: any) { if (obj == null) return null; if (typeof obj == "string") return new Uint8Array(Misc.Base64ToArrayBuffer(obj)); @@ -139,8 +123,7 @@ export default class Misc } // Convert either from Base64 string or from regular array to Int16Array - static Base64ToUInt16Array(obj : any) - { + static Base64ToUInt16Array(obj: any) { if (obj == null) return null; if (typeof obj == "string") return new Uint16Array(Misc.Base64ToArrayBuffer(obj)); @@ -149,8 +132,7 @@ export default class Misc } // Convert either from Base64 string or from regular array to Int32Array - static Base64ToUInt32Array(obj : any) - { + static Base64ToUInt32Array(obj: any) { if (obj == null) return null; if (typeof obj == "string") return new Uint32Array(Misc.Base64ToArrayBuffer(obj)); @@ -158,24 +140,20 @@ export default class Misc return new Uint32Array(obj); } - static GetSearchValue(name : string) - { + static GetSearchValue(name: string) { var searchStr = location.search.substring(1); var vars = searchStr.split('&'); - for (var i = 0; i < vars.length; i++) - { + for (var i = 0; i < vars.length; i++) { var pair = vars[i].split('='); - if (decodeURIComponent(pair[0]) == name) - { + if (decodeURIComponent(pair[0]) == name) { return decodeURIComponent(pair[1]); } } return null; } - static StyleToHtmlHex(style: Uint8Array) : string | null - { - if(style[0] == 1){ + static StyleToHtmlHex(style: Uint8Array): string | null { + if (style[0] == 1) { return null; } @@ -185,24 +163,22 @@ export default class Misc return "#" + Misc.Byte2Hex(red) + Misc.Byte2Hex(green) + Misc.Byte2Hex(blue); } - static Byte2Hex (value: number) : string - { - const hex_string = "0123456789ABCDEF"; - return String(hex_string.substr((value >> 4) & 0x0F,1)) + hex_string.substr(value & 0x0F,1); + static Byte2Hex(value: number): string { + const hex_string = "0123456789ABCDEF"; + return String(hex_string.substr((value >> 4) & 0x0F, 1)) + hex_string.substr(value & 0x0F, 1); } - static IsPow2(val : number) - { + static IsPow2(val: number) { var l = Math.log(val) / Math.log(2); return l == Math.floor(l); } - static AssertNotNull(value: T | null, error: string) : T { - if(value == null){ + static AssertNotNull(value: T | null, error: string): T { + if (value == null) { throw new Error(error) } - + return value } - + } diff --git a/tssrc/SPScene.ts b/tssrc/SPScene.ts index 81b5bc4..30a6a07 100644 --- a/tssrc/SPScene.ts +++ b/tssrc/SPScene.ts @@ -1,4 +1,4 @@ -import {ObjectCache, CanvasBase} from "./CanvasBase" +import { ObjectCache, CanvasBase } from "./CanvasBase" import Canvas3D from "./Canvas3D"; import Canvas2D from "./Canvas2D"; import Graph from "./Graph"; @@ -8,40 +8,39 @@ import InitializeCSSStyles from "./CSSStyles"; import Misc from "./Misc" import Mesh from "./Mesh"; import { VertexBufferType } from "./VertexBuffers"; -import {vec2, vec3} from "gl-matrix"; -import {saveAs} from "file-saver"; +import { vec2, vec3 } from "gl-matrix"; +import { saveAs } from "file-saver"; import * as JSZip from "jszip"; const StatusPanelName = "___STATUS___"; const LogPanelName = "___LOG___"; const HelpPanelName = "___HELP___"; -export default class SPScene -{ - div : HTMLDivElement; - statusDiv : HTMLDivElement; - progressDiv : HTMLDivElement; - progressBar : HTMLSpanElement; - canvasDiv : HTMLDivElement = null; - statusDivNeedsPadding : boolean; +export default class SPScene { + div: HTMLDivElement; + statusDiv: HTMLDivElement; + progressDiv: HTMLDivElement; + progressBar: HTMLSpanElement; + canvasDiv: HTMLDivElement = null; + statusDivNeedsPadding: boolean; // Scene identifier - sceneId : string; + sceneId: string; // Set of global meshes allMeshes = {}; // Set of global images and text labels that can be used as textures - objectCache : ObjectCache; + objectCache: ObjectCache; // Text panels by id - textPanels : Map = new Map(); + textPanels: Map = new Map(); // Drop down menus by id - dropDownMenus : Map = new Map(); + dropDownMenus: Map = new Map(); // Canvases (3D and 2D) - canvases : Map = new Map(); + canvases: Map = new Map(); // Mesh keyframes meshKeyframes = {}; @@ -50,38 +49,36 @@ export default class SPScene canvasGroups = {}; // WebSocket for use in interactive mode - ws : WebSocket = null; + ws: WebSocket = null; // Playback rate - playbackRate : number = 1.0; - media : HTMLMediaElement[] = []; - isPlaying : boolean = false; - frameRate : number = 30.0; - timePerFrame : number = 1.0 / 30.0; + playbackRate: number = 1.0; + media: HTMLMediaElement[] = []; + isPlaying: boolean = false; + frameRate: number = 30.0; + timePerFrame: number = 1.0 / 30.0; // Global controls - recordButton : HTMLButtonElement; - playPauseToggle : HTMLInputElement; - volumeToggle : HTMLInputElement; - volumeSlider : HTMLInputElement; + recordButton: HTMLButtonElement; + playPauseToggle: HTMLInputElement; + volumeToggle: HTMLInputElement; + volumeSlider: HTMLInputElement; // Recording - recordingZip : JSZip; - canvasZipFolders : {[id: string] : JSZip}; - numFramesPerCanvas : {[id: string]: number}; - currentRecordingFrame : number; - maxFrames : number; + recordingZip: JSZip; + canvasZipFolders: { [id: string]: JSZip }; + numFramesPerCanvas: { [id: string]: number }; + currentRecordingFrame: number; + maxFrames: number; - constructor(element : HTMLElement = null) // element is passed in if being used from Jupyter + constructor(element: HTMLElement = null) // element is passed in if being used from Jupyter { // Create image and label cache - var self : SPScene = this; // Since the lambda below would have a different "this" - this.objectCache = new ObjectCache(textureId => - { + var self: SPScene = this; // Since the lambda below would have a different "this" + this.objectCache = new ObjectCache(textureId => { // Update all canvases that are consuming this image - for(var canvasId in self.canvases) - { - var canvas : CanvasBase = self.canvases[canvasId]; + for (var canvasId in self.canvases) { + var canvas: CanvasBase = self.canvases[canvasId]; canvas.NotifyTextureUpdated(textureId); } }); @@ -185,8 +182,7 @@ export default class SPScene this.UpdateStatusPadding(); } - private createToggleButton(title: string, icon: string) : [HTMLLabelElement, HTMLInputElement] - { + private createToggleButton(title: string, icon: string): [HTMLLabelElement, HTMLInputElement] { let label = document.createElement("label"); label.className = "scenepic-toggle-button"; label.title = title; @@ -202,46 +198,37 @@ export default class SPScene return [label, checkbox]; } - ResetAllViews() - { - for(let canvasId in this.canvases) - { + ResetAllViews() { + for (let canvasId in this.canvases) { let canvas = this.canvases[canvasId]; - if(canvas instanceof Canvas3D) - { + if (canvas instanceof Canvas3D) { canvas.ResetView(); } } } - UpdateVolume() - { + UpdateVolume() { let volume = undefined; - if(this.volumeToggle.checked) - { + if (this.volumeToggle.checked) { volume = this.volumeSlider.valueAsNumber; } - for(let canvasId in this.canvases) - { - let canvas : CanvasBase = this.canvases[canvasId]; + for (let canvasId in this.canvases) { + let canvas: CanvasBase = this.canvases[canvasId]; canvas.Volume = volume; } } - Record() - { + Record() { this.numFramesPerCanvas = {} this.canvasZipFolders = {} this.maxFrames = 0; this.recordingZip = new JSZip(); - for(let canvasId in this.canvases) - { + for (let canvasId in this.canvases) { this.canvasZipFolders[canvasId] = this.recordingZip.folder(canvasId); - let canvas : CanvasBase = this.canvases[canvasId]; + let canvas: CanvasBase = this.canvases[canvasId]; canvas.IsRecording = true; - if(canvas.FrameCount > this.maxFrames) - { + if (canvas.FrameCount > this.maxFrames) { this.maxFrames = canvas.FrameCount; } @@ -255,40 +242,34 @@ export default class SPScene setTimeout(() => this.recordFrame(), 1); } - recordFrame() - { + recordFrame() { let imageName = "frame_" + String(this.currentRecordingFrame).padStart(4, "0") + ".png"; let allDefined = true; - for(let canvasId in this.canvases) - { - let canvas : CanvasBase = this.canvases[canvasId]; + for (let canvasId in this.canvases) { + let canvas: CanvasBase = this.canvases[canvasId]; canvas.ShowFrame(this.currentRecordingFrame % this.numFramesPerCanvas[canvasId]); - if(canvas.DataUrl != null) - { + if (canvas.DataUrl != null) { let imageBlob = Misc.DataUrlToBlob(canvas.DataUrl); this.canvasZipFolders[canvasId].file(imageName, imageBlob); } - else - { + else { console.warn("Frame " + this.currentRecordingFrame + " undefined for canvas " + canvasId); allDefined = false; } } - if(allDefined){ + if (allDefined) { this.currentRecordingFrame += 1; } let percentComplete = 100 * this.currentRecordingFrame / this.maxFrames; this.progressBar.style.width = percentComplete + "%"; - if(this.currentRecordingFrame == this.maxFrames) - { - let media : Set = new Set(); + if (this.currentRecordingFrame == this.maxFrames) { + let media: Set = new Set(); this.recordButton.classList.remove("is-blinking"); - for(let canvasId in this.canvases) - { - let canvas : CanvasBase = this.canvases[canvasId]; - if(canvas.mediaId != null){ + for (let canvasId in this.canvases) { + let canvas: CanvasBase = this.canvases[canvasId]; + if (canvas.mediaId != null) { media.add(canvas.mediaId); } @@ -296,34 +277,30 @@ export default class SPScene canvas.ShowFrame(0); } - for(let mediaId of media) - { + for (let mediaId of media) { let blob = this.objectCache.GetBlob(mediaId); this.recordingZip.file(mediaId + "." + blob.type, blob); } - this.recordingZip.generateAsync({type:"blob"}) - .then(function(content) { - const filename = document.title.replace(' ', '_').toLowerCase() + ".zip"; - saveAs(content, filename); - }); + this.recordingZip.generateAsync({ type: "blob" }) + .then(function (content) { + const filename = document.title.replace(' ', '_').toLowerCase() + ".zip"; + saveAs(content, filename); + }); - this.progressDiv.style.visibility = "hidden"; + this.progressDiv.style.visibility = "hidden"; } - else - { + else { setTimeout(() => this.recordFrame(), 0); } } - UpdateStatusPadding() - { + UpdateStatusPadding() { if (this.statusDivNeedsPadding) this.div.setAttribute("style", `padding-bottom:${this.statusDiv.offsetHeight}px;`); } - SetStatus(status : string) - { + SetStatus(status: string) { var html = ""; html += ""; html += status; @@ -332,8 +309,7 @@ export default class SPScene this.UpdateStatusPadding(); } - AddLogMessage(message : string, color : string) - { + AddLogMessage(message: string, color: string) { if (!(LogPanelName in this.textPanels)) this.AddTextPanel(LogPanelName, "Log", null, this.statusDiv); @@ -348,29 +324,24 @@ export default class SPScene this.UpdateStatusPadding(); } - AddWarning(message : string) - { + AddWarning(message: string) { this.AddLogMessage("WARNING: " + message, "red"); } - ConnectToServer(address : string) - { + ConnectToServer(address: string) { // Try to create websocket connection to server this.SetStatus("Connecting to " + address); this.ws = new WebSocket(address); this.ws.onerror = event => this.AddWarning("WebSockets connection to " + address + " failed to open!"); this.ws.onmessage = event => this.MessageReceived(event); - this.ws.onopen = event => - { + this.ws.onopen = event => { this.AddLogMessage("WebSockets connection to " + address + "opened successfully", "green"); - if (this.sceneId != null) - { + if (this.sceneId != null) { this.SetSceneId(this.sceneId); } // Send initial frame id for all canvases - for(var canvasId in this.canvases) - { + for (var canvasId in this.canvases) { var canvas = this.canvases[canvasId]; this.ReportFrameIdChange(canvasId, canvas.GetCurrentFrameId()); } @@ -379,8 +350,7 @@ export default class SPScene this.SetStatus("done"); } - MessageReceived(event : MessageEvent) - { + MessageReceived(event: MessageEvent) { // Decode from json var timeReceived = new Date().getTime(); var data = JSON.parse(event.data); @@ -401,12 +371,11 @@ export default class SPScene this.ExecuteSceneCommands(commands); // Compute timings - var jsonEncodeDuration : any = "Unknown"; - var transmitDuration : any = "Unknown"; + var jsonEncodeDuration: any = "Unknown"; + var transmitDuration: any = "Unknown"; var jsonDecodeDuration = timeParsed - timeReceived; var sceneParseDuration = new Date().getTime() - timeParsed; - if ("Timings" in data) - { + if ("Timings" in data) { var timings = data["Timings"] var jsonEncodeStart = timings[0]; var messageSendStart = timings[1]; @@ -416,11 +385,9 @@ export default class SPScene this.SetStatus("Updated. Timings: enc:" + jsonEncodeDuration + "ms, tx:" + transmitDuration + "ms, dec:" + jsonDecodeDuration + "ms, parse:" + sceneParseDuration + "ms"); } - private AddTextPanel(id : string, title : string, style : string, parent : HTMLElement, startMinimized = false, addInputBox = false) - { + private AddTextPanel(id: string, title: string, style: string, parent: HTMLElement, startMinimized = false, addInputBox = false) { // Check unique - if (id in this.textPanels) - { + if (id in this.textPanels) { this.AddWarning("TextPanel id " + id + " already exists in SPScene"); return; } @@ -438,8 +405,7 @@ export default class SPScene this.UpdateStatusPadding(); } - private SetTextPanelValue(id : string, htmlContents : string, append = false) - { + private SetTextPanelValue(id: string, htmlContents: string, append = false) { // Check exists if (!(id in this.textPanels)) this.AddWarning("TextPanel id " + id + " not found. Try AddTextPanel first."); @@ -449,8 +415,7 @@ export default class SPScene this.UpdateStatusPadding(); } - private SetTextPanelTitle(id : string, title : string) - { + private SetTextPanelTitle(id: string, title: string) { // Check exists if (!(id in this.textPanels)) this.AddWarning("TextPanel id " + id + " not found. Try AddTextPanel first."); @@ -459,8 +424,7 @@ export default class SPScene this.UpdateStatusPadding(); } - private SetTextPanelInputText(id : string, text : string) - { + private SetTextPanelInputText(id: string, text: string) { // Check exists if (!(id in this.textPanels)) this.AddWarning("TextPanel id " + id + " not found. Try AddTextPanel first."); @@ -469,11 +433,9 @@ export default class SPScene this.UpdateStatusPadding(); } - private AddDropDownMenu(id : string, title : string, style : string, parent : HTMLElement) - { + private AddDropDownMenu(id: string, title: string, style: string, parent: HTMLElement) { // Check unique - if (id in this.dropDownMenus) - { + if (id in this.dropDownMenus) { this.AddWarning("DropDownMenu id " + id + " already exists in SPScene"); return; } @@ -484,7 +446,7 @@ export default class SPScene // Add event to report var host = this; let menu = this.dropDownMenus.get(id) - + menu.dropDownMenu.onchange = () => host.ReportDropDownMenuChange(id, menu.dropDownMenu.selectedIndex); menu.header.addEventListener("click", () => this.UpdateStatusPadding()); @@ -492,8 +454,7 @@ export default class SPScene this.UpdateStatusPadding(); } - private SetDropDownMenuItems(id : string, items : string[]) - { + private SetDropDownMenuItems(id: string, items: string[]) { // Check exists if (!(id in this.dropDownMenus)) this.AddWarning("DropDownMenu id " + id + " not found. Try AddDropDownMenu first."); @@ -503,8 +464,7 @@ export default class SPScene this.UpdateStatusPadding(); } - private SetDropDownMenuItemDisabled(id : string, index : number, disabled : boolean) - { + private SetDropDownMenuItemDisabled(id: string, index: number, disabled: boolean) { // Check exists if (!(id in this.dropDownMenus)) this.AddWarning("DropDownMenu id " + id + " not found. Try AddDropDownMenu first."); @@ -514,8 +474,7 @@ export default class SPScene this.UpdateStatusPadding(); } - private SetDropDownMenuSelection(id : string, index : number) - { + private SetDropDownMenuSelection(id: string, index: number) { // Check exists if (!(id in this.dropDownMenus)) this.AddWarning("DropDownMenu id " + id + " not found. Try AddDropDownMenu first."); @@ -526,13 +485,10 @@ export default class SPScene } - private GetParentDOMElement(htmlId : string) - { + private GetParentDOMElement(htmlId: string) { var parent = htmlId != null ? document.getElementById(htmlId) : null; - if (parent == null) - { - if (this.canvasDiv == null) - { + if (parent == null) { + if (this.canvasDiv == null) { this.canvasDiv = document.createElement("div"); this.canvasDiv.className = "scenepic"; // CSS style class name this.div.appendChild(this.canvasDiv); @@ -542,12 +498,10 @@ export default class SPScene return parent; } - private RequestRedraw() - { + private RequestRedraw() { } - private AddCanvas3D(canvasId : string, width : number, height : number, parent : HTMLElement) - { + private AddCanvas3D(canvasId: string, width: number, height: number, parent: HTMLElement) { // Check unique if (canvasId in this.canvases) // Loops over keys (ids) in dictionary { @@ -562,8 +516,7 @@ export default class SPScene this.InitializeCanvas(canvas, canvasId, parent, true); } - private AddCanvas2D(canvasId : string, width : number, height : number, parent : HTMLElement) - { + private AddCanvas2D(canvasId: string, width: number, height: number, parent: HTMLElement) { // Check unique if (canvasId in this.canvases) // Loops over keys (ids) in dictionary { @@ -578,8 +531,7 @@ export default class SPScene this.InitializeCanvas(canvas, canvasId, parent, true); } - private AddGraph(canvasId : string, width : number, height : number, parent : HTMLElement) - { + private AddGraph(canvasId: string, width: number, height: number, parent: HTMLElement) { // Check unique if (canvasId in this.canvases) // Loops over keys (ids) in dictionary { @@ -594,8 +546,7 @@ export default class SPScene this.InitializeCanvas(canvas, canvasId, parent, false); } - InitializeCanvas(canvas : CanvasBase, canvasId : string, parent : HTMLElement, pointerEvents : boolean) - { + InitializeCanvas(canvas: CanvasBase, canvasId: string, parent: HTMLElement, pointerEvents: boolean) { // Save this.canvases[canvasId] = canvas; canvas.handlesMouse = pointerEvents; @@ -605,26 +556,25 @@ export default class SPScene // Add event handlers var htmlCanvas = canvas.htmlCanvas; - if (pointerEvents) - { + if (pointerEvents) { htmlCanvas.addEventListener("pointerdown", event => this.HandlePointerDown(event), true); htmlCanvas.addEventListener("pointerup", event => this.HandlePointerUp(event), true); htmlCanvas.addEventListener("pointermove", event => this.HandlePointerMove(event), true); htmlCanvas.addEventListener("pointerout", event => this.HandlePointerUp(event), true); htmlCanvas.addEventListener("wheel", event => this.HandleMouseWheel(event), true); } - htmlCanvas.addEventListener("keydown", event => { + htmlCanvas.addEventListener("keydown", event => { let propagate = this.HandleKeyDown(canvasId, event.key, event.altKey, event.ctrlKey, event.shiftKey, event.metaKey); - if(!propagate) + if (!propagate) event.preventDefault() }, true); htmlCanvas.addEventListener("keyup", event => { this.HandleKeyUp(canvasId, event); }) htmlCanvas.addEventListener("mousemove", event => { event.preventDefault(); }, true); // Consume drag in IE htmlCanvas.addEventListener("mousedown", event => { htmlCanvas.focus(); event.preventDefault(); }, true); // Consume drag in IE canvas.slider.oninput = event => this.SliderChanged(event); - canvas.slider.onkeydown = event => { - let propagate = this.HandleKeyDown(canvasId, event.key, event.altKey, event.ctrlKey, event.shiftKey, event.metaKey); - if(!propagate) + canvas.slider.onkeydown = event => { + let propagate = this.HandleKeyDown(canvasId, event.key, event.altKey, event.ctrlKey, event.shiftKey, event.metaKey); + if (!propagate) event.preventDefault() }; htmlCanvas.focus(); // Ensure keypresses are dealt with @@ -634,15 +584,13 @@ export default class SPScene canvas.slider.setAttribute("SPCanvasId", canvasId); // Store in canvas group for event linking - this.canvasGroups[canvasId] = { }; + this.canvasGroups[canvasId] = {}; this.canvasGroups[canvasId][canvasId] = true; } // Add new or replace existing mesh - DefineMesh(meshId : string, layerId : string, definition : any, cameraSpace : boolean, doubleSided : boolean, isBillboard : boolean, isLabel : boolean) - { - try - { + DefineMesh(meshId: string, layerId: string, definition: any, cameraSpace: boolean, doubleSided: boolean, isBillboard: boolean, isLabel: boolean) { + try { var mesh = Mesh.Parse(definition); mesh.cameraSpace = cameraSpace; mesh.layerId = layerId; @@ -651,29 +599,25 @@ export default class SPScene mesh.isLabel = isLabel; this.allMeshes[meshId] = mesh; } - catch (e) - { + catch (e) { this.AddWarning(e); } // Update all canvases that are consuming this mesh - for(var canvasId in this.canvases) - { + for (var canvasId in this.canvases) { var canvas = this.canvases[canvasId]; canvas.NotifyMeshUpdated(meshId); } } // Update an existing mesh to create a new mesh - UpdateMesh(baseMeshId: string, meshId: string, buffer: Float32Array|Uint16Array, frameIndex: number, keyframeIndex: number, min: number, max: number, updateFlags: VertexBufferType) - { + UpdateMesh(baseMeshId: string, meshId: string, buffer: Float32Array | Uint16Array, frameIndex: number, keyframeIndex: number, min: number, max: number, updateFlags: VertexBufferType) { let unquantizedBuffer: Float32Array; if (buffer instanceof Uint16Array) { let range = (max - min) / 65535.0; unquantizedBuffer = new Float32Array(buffer.length); let keyframeVertexBuffer = this.meshKeyframes[baseMeshId + keyframeIndex]; - for(let i=0; i < unquantizedBuffer.length; ++i) - { + for (let i = 0; i < unquantizedBuffer.length; ++i) { unquantizedBuffer[i] = buffer[i] * range + min + keyframeVertexBuffer[i]; } } else { @@ -681,31 +625,26 @@ export default class SPScene this.meshKeyframes[baseMeshId + frameIndex] = unquantizedBuffer; } - try - { + try { var mesh = this.allMeshes[baseMeshId]; this.allMeshes[meshId] = mesh.Update(unquantizedBuffer, updateFlags); } - catch (e) - { + catch (e) { this.AddWarning(e); } // Update all canvases that are consuming this mesh - for(var canvasId in this.canvases) - { + for (var canvasId in this.canvases) { var canvas = this.canvases[canvasId]; canvas.NotifyMeshUpdated(meshId); } } // Execute commands - ExecuteSceneCommands(command : any) - { + ExecuteSceneCommands(command: any) { // Support recursive parsing of sub-lists of commands - if (Array.isArray(command)) - { - for(var com of command) + if (Array.isArray(command)) { + for (var com of command) this.ExecuteSceneCommands(com); return; } @@ -714,11 +653,9 @@ export default class SPScene if (!("CommandType" in command)) this.AddWarning("Expecting \"CommandType\" in Scene Command object: " + JSON.stringify(command)); - switch(command["CommandType"]) - { + switch (command["CommandType"]) { case "ConfigureUserInterface": // Provided for convenience - really a per canvas option - for(var canvasId in this.canvases) - { + for (var canvasId in this.canvases) { var canvas = this.canvases[canvasId]; if (canvas instanceof Canvas3D) canvas.ConfigureUserInterface(command); @@ -744,7 +681,7 @@ export default class SPScene var isBillboard = Misc.GetDefault(command, "IsBillboard", false); var isLabel = Misc.GetDefault(command, "IsLabel", false); this.DefineMesh(meshId, layerId, definition, cameraSpace, doubleSided, isBillboard, isLabel); - break; + break; case "UpdateMesh": var baseMeshId = String(command["BaseMeshId"]); @@ -754,8 +691,8 @@ export default class SPScene var min = Misc.GetDefault(command, "MinValue", 0); var max = Misc.GetDefault(command, "MaxValue", 0); var updateFlags = command["UpdateFlags"] as number as VertexBufferType; - var buffer: Float32Array|Uint16Array; - if ("QuantizedBuffer" in command) + var buffer: Float32Array | Uint16Array; + if ("QuantizedBuffer" in command) buffer = Misc.Base64ToUInt16Array(command["QuantizedBuffer"]) else buffer = Misc.Base64ToFloat32Array(command["VertexBuffer"]) @@ -943,11 +880,9 @@ export default class SPScene } // Close down the webpage - ShutDown() - { + ShutDown() { var children = document.body.children; - for (var i = 0; i < children.length; ++i) - { + for (var i = 0; i < children.length; ++i) { document.body.removeChild(children[i]); } document.write("Session closed. Window closing..."); @@ -955,10 +890,8 @@ export default class SPScene } // Load scripts from file - LoadScripts(filenames : string[]) - { - var loadJSON = (id : string, filename : string, successHandler, failureHandler) => - { + LoadScripts(filenames: string[]) { + var loadJSON = (id: string, filename: string, successHandler, failureHandler) => { var elm = document.createElement("script"); elm.id = id; elm.setAttribute("type", "text/javascript"); @@ -968,13 +901,11 @@ export default class SPScene document.body.appendChild(elm); }; - var loadScript = (index : number) => - { + var loadScript = (index: number) => { if (index >= filenames.length) { this.SetStatus("done"); return; } var id = "__loaded-script-" + index.toString() + "__"; - var success = event => - { + var success = event => { // Execute the script var commands = (window).ScriptCommands; if (commands != null) @@ -997,21 +928,17 @@ export default class SPScene loadScript(0); } - private SetSceneId(id : string) - { + private SetSceneId(id: string) { this.sceneId = id; this.ReportSceneIdChange(); this.SetStatus("SceneId: " + this.sceneId); } // Link events across multiple canvases - private LinkCanvasEvents(ids : string[]) - { - for (var i = 0; i < ids.length; i++) - { + private LinkCanvasEvents(ids: string[]) { + for (var i = 0; i < ids.length; i++) { var group = this.canvasGroups[ids[i]]; - for (var j = 0; j < ids.length; j++) - { + for (var j = 0; j < ids.length; j++) { var id = ids[j]; if (id in group) continue; group[id] = true; @@ -1019,95 +946,79 @@ export default class SPScene } } - private GetEventCanvasId(event : any) - { + private GetEventCanvasId(event: any) { return (event.target).getAttribute("SPCanvasId"); } - private GetTargetCanvases(canvasId : string) : Canvas2D[] | Canvas3D[] | null - { + private GetTargetCanvases(canvasId: string): Canvas2D[] | Canvas3D[] | null { if (canvasId == null) return null; const canvasGroup = this.canvasGroups[canvasId]; const canvasIds = Object.keys(canvasGroup); const source = this.canvases[canvasId]; - if(source instanceof Canvas3D){ + if (source instanceof Canvas3D) { return canvasIds.map(id => this.canvases[id]) - .filter(canvas => canvas instanceof Canvas3D) - .map(canvas => canvas) - }else if(source instanceof Canvas2D){ + .filter(canvas => canvas instanceof Canvas3D) + .map(canvas => canvas) + } else if (source instanceof Canvas2D) { return canvasIds.map(id => this.canvases[id]) - .filter(canvas => canvas instanceof Canvas2D) - .map(canvas => canvas) - }else { + .filter(canvas => canvas instanceof Canvas2D) + .map(canvas => canvas) + } else { return null } } - private ReportReceived(ack_data : any) - { - if (this.ws != null && this.ws.readyState == WebSocket.OPEN) - { - var obj = { "Type" : "AcknowledgeCommand", "AckData" : ack_data }; + private ReportReceived(ack_data: any) { + if (this.ws != null && this.ws.readyState == WebSocket.OPEN) { + var obj = { "Type": "AcknowledgeCommand", "AckData": ack_data }; this.ws.send(JSON.stringify(obj)); } } - private ReportSceneIdChange() - { - if (this.ws != null && this.ws.readyState == WebSocket.OPEN && this.sceneId != null) - { - var obj = { "Type" : "SceneIdChange", "SceneId" : this.sceneId }; + private ReportSceneIdChange() { + if (this.ws != null && this.ws.readyState == WebSocket.OPEN && this.sceneId != null) { + var obj = { "Type": "SceneIdChange", "SceneId": this.sceneId }; this.ws.send(JSON.stringify(obj)); } } - private ReportKeyPress(key, altKey : boolean, ctrlKey : boolean, shiftKey : boolean, metaKey : boolean, canvasId : string, frameId : string) - { - if (this.ws != null && this.ws.readyState == WebSocket.OPEN) - { - var obj = { "Type" : "KeyPress", "Key" : key, "Alt" : altKey, "Ctrl" : ctrlKey, "Shift" : shiftKey, "Meta" : metaKey, "CanvasId" : canvasId }; + private ReportKeyPress(key, altKey: boolean, ctrlKey: boolean, shiftKey: boolean, metaKey: boolean, canvasId: string, frameId: string) { + if (this.ws != null && this.ws.readyState == WebSocket.OPEN) { + var obj = { "Type": "KeyPress", "Key": key, "Alt": altKey, "Ctrl": ctrlKey, "Shift": shiftKey, "Meta": metaKey, "CanvasId": canvasId }; if (frameId != null) obj["FrameId"] = frameId; this.ws.send(JSON.stringify(obj)); } } - private ReportInputBoxChange(textPanelId : string, value : string) - { - if (this.ws != null && this.ws.readyState == WebSocket.OPEN) - { - var obj = { "Type" : "InputBoxChange", "TextPanelId" : textPanelId, "Value" : value }; + private ReportInputBoxChange(textPanelId: string, value: string) { + if (this.ws != null && this.ws.readyState == WebSocket.OPEN) { + var obj = { "Type": "InputBoxChange", "TextPanelId": textPanelId, "Value": value }; this.ws.send(JSON.stringify(obj)); } } - private ReportDropDownMenuChange(dropDownMenuId : string, index : number) - { - if (this.ws != null && this.ws.readyState == WebSocket.OPEN) - { - var obj = { "Type" : "DropDownMenuChange", "DropDownMenuId" : dropDownMenuId, "Index" : index }; + private ReportDropDownMenuChange(dropDownMenuId: string, index: number) { + if (this.ws != null && this.ws.readyState == WebSocket.OPEN) { + var obj = { "Type": "DropDownMenuChange", "DropDownMenuId": dropDownMenuId, "Index": index }; this.ws.send(JSON.stringify(obj)); } } - private ReportFrameIdChange(canvasId : string, frameId : string) - { - if (this.ws != null && this.ws.readyState == WebSocket.OPEN) - { - var obj = { "Type" : "FrameIdChange", "CanvasId" : canvasId, "FrameId" : frameId }; + private ReportFrameIdChange(canvasId: string, frameId: string) { + if (this.ws != null && this.ws.readyState == WebSocket.OPEN) { + var obj = { "Type": "FrameIdChange", "CanvasId": canvasId, "FrameId": frameId }; this.ws.send(JSON.stringify(obj)); } } - private HandleKeyUp(canvasId: string, event: KeyboardEvent) - { + private HandleKeyUp(canvasId: string, event: KeyboardEvent) { const canvases = this.GetTargetCanvases(canvasId); if (canvases == null) return; canvases.forEach((canvas: CanvasBase) => canvas.HandleKeyUp(event.key)); } - private HandleKeyDown(canvasId : string, key : string, altKey : boolean, ctrlKey : boolean, shiftKey : boolean, metaKey : boolean, reportToServer : boolean = true) - { + private HandleKeyDown(canvasId: string, key: string, altKey: boolean, ctrlKey: boolean, shiftKey: boolean, metaKey: boolean, reportToServer: boolean = true) { var returnValue = true; var canvases = this.GetTargetCanvases(canvasId); @@ -1118,11 +1029,9 @@ export default class SPScene let toggleFirstPerson = false; // Only handle unmodified keypresses - if (!altKey && !ctrlKey && !shiftKey && !metaKey) - { + if (!altKey && !ctrlKey && !shiftKey && !metaKey) { // Global keypresses - switch(key) - { + switch (key) { case " ": this.playPauseToggle.checked = !this.playPauseToggle.checked; this.PlayVideo(); @@ -1142,10 +1051,8 @@ export default class SPScene } // Canvas-local keypresses - for(var canvas of canvases) - { - if(toggleFirstPerson) - { + for (var canvas of canvases) { + if (toggleFirstPerson) { canvas.FirstPerson = !canvas.FirstPerson; } @@ -1162,23 +1069,20 @@ export default class SPScene if (altKey) { let index = Number.parseInt(key); - for(var canvas of canvases) - { + for (var canvas of canvases) { canvas.ToggleLayerFilled(index); } } // Display timings - if (showTimings) - { + if (showTimings) { var timeTaken = new Date().getTime() - timeStarted; this.SetStatus("Updated. Timings: " + timeTaken + "ms"); } // Send message to any open server connections for interactive sessions - if (reportToServer) - { - var canvas : Canvas3D | Canvas2D = this.canvases[canvasId]; + if (reportToServer) { + var canvas: Canvas3D | Canvas2D = this.canvases[canvasId]; var frameId = canvas.GetCurrentFrameId() this.ReportKeyPress(key, altKey, ctrlKey, shiftKey, metaKey, canvasId, frameId); } @@ -1186,83 +1090,67 @@ export default class SPScene return returnValue; } - private updatePlaybackRate() - { - for(let canvasId in this.canvases){ - let canvas : CanvasBase = this.canvases[canvasId]; + private updatePlaybackRate() { + for (let canvasId in this.canvases) { + let canvas: CanvasBase = this.canvases[canvasId]; canvas.PlaybackRate = this.playbackRate; } } - private faster() - { + private faster() { this.playbackRate = Math.min(2, this.playbackRate * 1.2); this.updatePlaybackRate(); } - private slower() - { + private slower() { this.playbackRate = Math.max(0.03, this.playbackRate / 1.2); this.updatePlaybackRate(); } - private PlayVideo() - { - if(this.playPauseToggle.checked) - { - for(var canvasId in this.canvases) - { - let canvas : CanvasBase = this.canvases[canvasId]; + private PlayVideo() { + if (this.playPauseToggle.checked) { + for (var canvasId in this.canvases) { + let canvas: CanvasBase = this.canvases[canvasId]; canvas.StartPlaying(); } this.isPlaying = true; - if(this.media.length > 1) - { + if (this.media.length > 1) { this.sync(0); } } - else - { + else { this.isPlaying = false; - for(var canvasId in this.canvases) - { - let canvas : CanvasBase = this.canvases[canvasId]; + for (var canvasId in this.canvases) { + let canvas: CanvasBase = this.canvases[canvasId]; canvas.StopPlaying(); - } + } } } - private sync(mainIndex) - { + private sync(mainIndex) { let main = this.media[mainIndex]; let timestamp = main.currentTime; - for(let element of this.media) - { - if(element == main) - { + for (let element of this.media) { + if (element == main) { continue; } - if(Math.abs(element.currentTime - timestamp) < this.timePerFrame) - { + if (Math.abs(element.currentTime - timestamp) < this.timePerFrame) { continue; } - if(element.readyState === 4) - { + if (element.readyState === 4) { element.currentTime = timestamp; } } - if(this.isPlaying) - { + if (this.isPlaying) { requestAnimationFrame(() => this.sync(mainIndex)); } } - private SliderChanged(event : Event) - { + private SliderChanged(event: Event) { // Get new value var slider = event.target; var value = slider.valueAsNumber; @@ -1275,8 +1163,7 @@ export default class SPScene canvases.forEach((canvas: CanvasBase) => canvas.ShowFrame(value)); } - private HandlePointerDown(event : PointerEvent) - { + private HandlePointerDown(event: PointerEvent) { var targetCanvasId = this.GetEventCanvasId(event); var canvases = this.GetTargetCanvases(targetCanvasId); if (canvases == null) return; @@ -1285,13 +1172,12 @@ export default class SPScene const point = vec2.fromValues(event.clientX - clientRect.left, event.clientY - clientRect.top); - canvases.forEach((canvas: CanvasBase) => canvas.HandlePointerDown(point, event)); + canvases.forEach((canvas: CanvasBase) => canvas.HandlePointerDown(point, event)); this.HandlePointerMove(event); } - private HandlePointerUp(event : PointerEvent) - { + private HandlePointerUp(event: PointerEvent) { var canvases = this.GetTargetCanvases(this.GetEventCanvasId(event)); if (canvases == null) return; @@ -1299,8 +1185,7 @@ export default class SPScene } - private HandlePointerMove(event : PointerEvent) - { + private HandlePointerMove(event: PointerEvent) { const targetCanvasId = this.GetEventCanvasId(event); const canvases = this.GetTargetCanvases(targetCanvasId); if (canvases == null) return; @@ -1308,19 +1193,13 @@ export default class SPScene const targetCanvas = this.canvases[targetCanvasId]; const clientRect = targetCanvas.htmlCanvas.getBoundingClientRect(); const point = vec2.fromValues(event.clientX - clientRect.left, event.clientY - clientRect.top); - if(targetCanvas instanceof Canvas3D){ - const canvases3D = canvases as Canvas3D[]; - const twistAngle = event.ctrlKey ? targetCanvas.ComputeCameraTwist(point, event) : 0; - canvases3D.forEach((canvas: Canvas3D) => canvas.HandlePointerMoveWithTwist(point, twistAngle, event)); - }else{ - canvases.forEach((canvas: CanvasBase) => canvas.HandlePointerMove(point, event)); - } + const twistAngle = event.ctrlKey ? targetCanvas.ComputeCameraTwist(point, event) : 0; + canvases.forEach((canvas: Canvas2D | Canvas3D) => canvas.HandlePointerMoveWithTwist(point, twistAngle, event)); event.preventDefault(); } - private HandleMouseWheel(event : WheelEvent) - { + private HandleMouseWheel(event: WheelEvent) { var canvases = this.GetTargetCanvases(this.GetEventCanvasId(event)); if (canvases == null) return; diff --git a/tssrc/ScenePic.ts b/tssrc/ScenePic.ts index 2e0e95d..648aab9 100644 --- a/tssrc/ScenePic.ts +++ b/tssrc/ScenePic.ts @@ -1,18 +1,18 @@ import SPScene from "./SPScene" -function scenepic(id: string, commands: any, wait=100){ - let element : HTMLElement | null = null; - if(id != null){ +function scenepic(id: string, commands: any, wait = 100) { + let element: HTMLElement | null = null; + if (id != null) { element = document.getElementById(id); - if(element == null){ + if (element == null) { console.warn(id + " does not exist in the DOM, retrying"); setTimeout(() => { scenepic(id, commands, wait * 2); }, wait); - }else{ + } else { let scene = new SPScene(element); scene.ExecuteSceneCommands(commands); } - }else{ + } else { let scene = new SPScene(); scene.ExecuteSceneCommands(commands); } diff --git a/tssrc/Shaders.ts b/tssrc/Shaders.ts index ccc5b0b..75c7a08 100644 --- a/tssrc/Shaders.ts +++ b/tssrc/Shaders.ts @@ -193,7 +193,7 @@ void main() "pickerVertex": { type: "x-shader/x-vertex", script: -`#version 300 es + `#version 300 es // Vertex inputs in vec3 vertexPositionIn; @@ -224,8 +224,8 @@ void main() `}, "pickerFragment": { type: "x-shader/x-fragment", - script: -`#version 300 es + script: + `#version 300 es precision mediump float; uniform int u_id; diff --git a/tssrc/TextPanel.ts b/tssrc/TextPanel.ts index 4430ae0..38bf277 100644 --- a/tssrc/TextPanel.ts +++ b/tssrc/TextPanel.ts @@ -1,15 +1,13 @@ -export default class TextPanel -{ - minimized : boolean; - title : string; - header : HTMLSpanElement; - wrapper : HTMLDivElement; // Contains both contents and inputBox - contents : HTMLSpanElement; - inputBox : HTMLTextAreaElement; +export default class TextPanel { + minimized: boolean; + title: string; + header: HTMLSpanElement; + wrapper: HTMLDivElement; // Contains both contents and inputBox + contents: HTMLSpanElement; + inputBox: HTMLTextAreaElement; - constructor(id : string, title : string, style : string, parent : HTMLElement, startMinimized = false, addInputBox = false) - { + constructor(id: string, title: string, style: string, parent: HTMLElement, startMinimized = false, addInputBox = false) { // Create div var div = document.createElement("div"); div.className = "scenepic"; // CSS style class name @@ -50,30 +48,26 @@ export default class TextPanel parent.appendChild(div); } - SetContents(htmlContents : string, append = false) - { + SetContents(htmlContents: string, append = false) { if (append) this.contents.innerHTML += htmlContents; else this.contents.innerHTML = htmlContents; } - SetTitle(htmlTitle : string) - { + SetTitle(htmlTitle: string) { this.title = htmlTitle; this.Update(); } - Update() - { + Update() { this.header.style.display = (this.title == null || this.title == "") ? "none" : ""; this.header.innerHTML = this.title + (this.minimized ? " ▽" : " ▷"); var newDisplayValue = this.minimized ? "none" : "inline-block"; this.wrapper.style.display = newDisplayValue; } - SetInputBoxContents(htmlContents : string) - { + SetInputBoxContents(htmlContents: string) { this.inputBox.value = htmlContents; } } diff --git a/tssrc/VertexBuffers.ts b/tssrc/VertexBuffers.ts index afef16e..5ea38e3 100644 --- a/tssrc/VertexBuffers.ts +++ b/tssrc/VertexBuffers.ts @@ -1,81 +1,66 @@ -export const enum VertexBufferType -{ +export const enum VertexBufferType { None = 0, Positions = 1, Normals = 2, Colors = 4, - Rotations = 8 + Rotations = 8 }; export class VertexBuffer { private static partialCopy(buffer: Float32Array, bufferStride: number, - update: Float32Array, updateOffset: number, updateSize: number) : Float32Array - { + update: Float32Array, updateOffset: number, updateSize: number): Float32Array { buffer = Float32Array.from(buffer); - for(let b_i=0, u_i=0; b_i < buffer.length; b_i += bufferStride, u_i += updateSize) - { + for (let b_i = 0, u_i = 0; b_i < buffer.length; b_i += bufferStride, u_i += updateSize) { buffer.set(update.slice(u_i, u_i + updateSize), b_i + updateOffset); } return buffer; } - private static copyPosition(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { - if(elementsPerRow == 3) - { + private static copyPosition(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { + if (elementsPerRow == 3) { return update; } return this.partialCopy(buffer, elementsPerRow, update, 0, 3); } - private static copyNormal(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { + private static copyNormal(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { return this.partialCopy(buffer, elementsPerRow, update, 3, 3); } - private static copyRotation(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { + private static copyRotation(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { return this.partialCopy(buffer, elementsPerRow, update, 3, 4); } - private static copyColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { - return this.partialCopy(buffer, elementsPerRow, update, elementsPerRow - 3, 3); + private static copyColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { + return this.partialCopy(buffer, elementsPerRow, update, elementsPerRow - 3, 3); } - private static copyPositionRotation(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { - if(elementsPerRow == 7) - { + private static copyPositionRotation(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { + if (elementsPerRow == 7) { return update; } return this.partialCopy(buffer, elementsPerRow, update, 0, 7); } - private static copyPositionNormal(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { - if(elementsPerRow == 6) - { + private static copyPositionNormal(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { + if (elementsPerRow == 6) { return update; } return this.partialCopy(buffer, elementsPerRow, update, 0, 6); } - private static copyPositionColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { - if(elementsPerRow == 6) - { + private static copyPositionColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { + if (elementsPerRow == 6) { return update; } let offset = elementsPerRow - 3; buffer = Float32Array.from(buffer); - for(let b_i=0, u_i=0; b_i < buffer.length; b_i += elementsPerRow, u_i += 6) - { + for (let b_i = 0, u_i = 0; b_i < buffer.length; b_i += elementsPerRow, u_i += 6) { buffer.set(update.slice(u_i, u_i + 3), b_i); buffer.set(update.slice(u_i + 3, u_i + 6), b_i + offset); } @@ -83,50 +68,46 @@ export class VertexBuffer { return buffer; } - private static copyRotationColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { + private static copyRotationColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { return this.partialCopy(buffer, elementsPerRow, update, 3, 7); } - private static copyNormalColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number) : Float32Array - { + private static copyNormalColor(buffer: Float32Array, update: Float32Array, elementsPerRow: number): Float32Array { return this.partialCopy(buffer, elementsPerRow, update, 3, 6); } - static Update(buffer: Float32Array, elementsPerRow: number, update: Float32Array, updateFlags: VertexBufferType) : Float32Array - { - switch(updateFlags) - { + static Update(buffer: Float32Array, elementsPerRow: number, update: Float32Array, updateFlags: VertexBufferType): Float32Array { + switch (updateFlags) { case VertexBufferType.Positions: return this.copyPosition(buffer, update, elementsPerRow); - + case VertexBufferType.Normals: return this.copyNormal(buffer, update, elementsPerRow); - + case VertexBufferType.Rotations: return this.copyRotation(buffer, update, elementsPerRow); - + case VertexBufferType.Colors: return this.copyColor(buffer, update, elementsPerRow); - + case VertexBufferType.Positions | VertexBufferType.Rotations: return this.copyPositionRotation(buffer, update, elementsPerRow); - + case VertexBufferType.Positions | VertexBufferType.Normals: return this.copyPositionNormal(buffer, update, elementsPerRow); - + case VertexBufferType.Positions | VertexBufferType.Colors: return this.copyPositionColor(buffer, update, elementsPerRow); - + case VertexBufferType.Rotations | VertexBufferType.Colors: return this.copyRotationColor(buffer, update, elementsPerRow); - + case VertexBufferType.Normals | VertexBufferType.Colors: return this.copyNormalColor(buffer, update, elementsPerRow); - + case VertexBufferType.Positions | VertexBufferType.Normals | VertexBufferType.Colors: return update; - + case VertexBufferType.Positions | VertexBufferType.Rotations | VertexBufferType.Colors: return update; } diff --git a/tssrc/WebGLMeshBuffers.ts b/tssrc/WebGLMeshBuffers.ts index 42c51ac..4bebf67 100644 --- a/tssrc/WebGLMeshBuffers.ts +++ b/tssrc/WebGLMeshBuffers.ts @@ -1,47 +1,45 @@ -import {quat, vec3, mat3, mat4} from "gl-matrix"; +import { quat, vec3, mat3, mat4 } from "gl-matrix"; import Mesh from "./Mesh"; import Misc from "./Misc" import ShaderProgram from "./Shaders"; -export default class WebGLMeshBuffers -{ - gl : WebGL2RenderingContext; - sp : ShaderProgram; - m : Mesh; +export default class WebGLMeshBuffers { + gl: WebGL2RenderingContext; + sp: ShaderProgram; + m: Mesh; - color : vec3; + color: vec3; - vertexDataBuffer : WebGLBuffer; - triangleIndexBuffer : WebGLBuffer; - lineIndexBuffer : WebGLBuffer; - wireframeEdgeIndexBuffer : WebGLBuffer; - instanceDataBuffer : WebGLBuffer; + vertexDataBuffer: WebGLBuffer; + triangleIndexBuffer: WebGLBuffer; + lineIndexBuffer: WebGLBuffer; + wireframeEdgeIndexBuffer: WebGLBuffer; + instanceDataBuffer: WebGLBuffer; - hasTexture : boolean; - texture : WebGLTexture; - - indexType : number; + hasTexture: boolean; + texture: WebGLTexture; - m2wMatrix : mat4; + indexType: number; - id : number; + m2wMatrix: mat4; + + id: number; // Used for label computations - labelWidthNormalized : number = -1.0; - labelHeightNormalized : number = -1.0; - labelTranslateScreenX : number = 0.0; - labelTranslateScreenY : number = 0.0; - labelTranslateWorldX : number = 0.0; - labelTranslateWorldY : number = 0.0; + labelWidthNormalized: number = -1.0; + labelHeightNormalized: number = -1.0; + labelTranslateScreenX: number = 0.0; + labelTranslateScreenY: number = 0.0; + labelTranslateWorldX: number = 0.0; + labelTranslateWorldY: number = 0.0; - constructor(gl : WebGL2RenderingContext, sp : ShaderProgram, m : Mesh, textureSrc = null) - { + constructor(gl: WebGL2RenderingContext, sp: ShaderProgram, m: Mesh, textureSrc = null) { this.gl = gl; this.sp = sp; this.m = m; this.color = m.color; - + this.indexType = m.bytesPerIndex == 2 ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT; // Create and bind vertex buffer @@ -85,18 +83,16 @@ export default class WebGLMeshBuffers else gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([1, 1, 1, 1])); // Dummy texture gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, m.nnTexture ? gl.NEAREST : gl.LINEAR); - if (textureSrc != null && (Misc.IsPow2(textureSrc.width) && Misc.IsPow2(textureSrc.height))) - { + if (textureSrc != null && (Misc.IsPow2(textureSrc.width) && Misc.IsPow2(textureSrc.height))) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); gl.generateMipmap(gl.TEXTURE_2D); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, m.nnTexture ? gl.NEAREST : gl.LINEAR_MIPMAP_NEAREST) } - else - { + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, m.nnTexture ? gl.NEAREST : gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, m.nnTexture ? gl.NEAREST : gl.LINEAR); } gl.bindTexture(gl.TEXTURE_2D, null); @@ -104,8 +100,7 @@ export default class WebGLMeshBuffers this.m2wMatrix = mat4.create(); } - Finalize() - { + Finalize() { var gl = this.gl; gl.deleteBuffer(this.vertexDataBuffer); gl.deleteBuffer(this.triangleIndexBuffer); @@ -117,8 +112,7 @@ export default class WebGLMeshBuffers gl.deleteTexture(this.texture); } - ApplyBillboardEffect(m2vMatrix : mat4) - { + ApplyBillboardEffect(m2vMatrix: mat4) { // Get rotational matrix component of m2vMatrix var det = mat4.determinant(m2vMatrix); var scale = det != 0.0 ? 1.0 / Math.pow(det, 1.0 / 3.0) : 1.0; @@ -132,12 +126,11 @@ export default class WebGLMeshBuffers // Invert and transform quat.invert(q, q); var transform = mat4.create(); - mat4.fromRotationTranslationScaleOrigin(transform, q, [0,0,0], [1,1,1], this.m.centerOfMass); + mat4.fromRotationTranslationScaleOrigin(transform, q, [0, 0, 0], [1, 1, 1], this.m.centerOfMass); mat4.multiply(m2vMatrix, m2vMatrix, transform); } - ApplyLabel(m2vMatrix : mat4, v2sMatrix : mat4) - { + ApplyLabel(m2vMatrix: mat4, v2sMatrix: mat4) { this.ApplyBillboardEffect(m2vMatrix); // Helper function - careful - this references, not copies the array @@ -163,23 +156,21 @@ export default class WebGLMeshBuffers // Transform with alignment translation and scale factor var translate = vec3.fromValues(this.labelTranslateScreenX * labelDepth / v2sMatrix[0] + this.labelTranslateWorldX, - this.labelTranslateScreenY * labelDepth / v2sMatrix[5] + this.labelTranslateWorldY, - 0.0); + this.labelTranslateScreenY * labelDepth / v2sMatrix[5] + this.labelTranslateWorldY, + 0.0); var scale = vec3.fromValues(scaleFactorX, scaleFactorY, 1.0); var transform = mat4.create(); mat4.fromRotationTranslationScale(transform, quat.create(), translate, scale); mat4.mul(m2vMatrix, m2vMatrix, transform); } - RenderBuffer(v2sMatrix : mat4, w2vMatrix : mat4, opacity : number, renderFilled : boolean, renderWireframe : boolean) - { + RenderBuffer(v2sMatrix: mat4, w2vMatrix: mat4, opacity: number, renderFilled: boolean, renderWireframe: boolean) { const gl = this.gl; const sp = this.sp; const m = this.m; // Ensure we have wireframe buffer if needed - if (renderWireframe && this.wireframeEdgeIndexBuffer == null) - { + if (renderWireframe && this.wireframeEdgeIndexBuffer == null) { this.wireframeEdgeIndexBuffer = gl.createBuffer(); var cpuBuffer = m.GetWireframeEdgeBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.wireframeEdgeIndexBuffer); @@ -199,7 +190,7 @@ export default class WebGLMeshBuffers // Set model view matrix (i.e. concatenation of m2w, w2v) gl.uniformMatrix4fv(sp.m2vMatrixPtr, false, m2vMatrix); - + // Compute normals transformation matrix var normMatrix = Misc.Mat3FromMat4(m2vMatrix); mat3.invert(normMatrix, normMatrix); @@ -213,71 +204,59 @@ export default class WebGLMeshBuffers // Bind vertex buffers gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexDataBuffer); - if (true) - { + if (true) { gl.enableVertexAttribArray(sp.vertexPositionAttribLoc); gl.vertexAttribPointer(sp.vertexPositionAttribLoc, 3, gl.FLOAT, false, m.ElementsPerVertex * 4, m.VertexOffsetPosition * 4); } - if (true) - { + if (true) { gl.enableVertexAttribArray(sp.vertexNormalAttribLoc); gl.vertexAttribPointer(sp.vertexNormalAttribLoc, 3, gl.FLOAT, false, m.ElementsPerVertex * 4, m.VertexOffsetNormal * 4); } - if (m.VertexOffsetColor != 0) - { + if (m.VertexOffsetColor != 0) { gl.enableVertexAttribArray(sp.vertexColorAttribLoc); gl.vertexAttribPointer(sp.vertexColorAttribLoc, 3, gl.FLOAT, false, m.ElementsPerVertex * 4, m.VertexOffsetColor * 4); } - else - { + else { gl.disableVertexAttribArray(sp.vertexColorAttribLoc); } - if (m.VertexOffsetTexture != 0) - { + if (m.VertexOffsetTexture != 0) { gl.enableVertexAttribArray(sp.vertexTextureAttribLoc); gl.vertexAttribPointer(sp.vertexTextureAttribLoc, 2, gl.FLOAT, false, m.ElementsPerVertex * 4, m.VertexOffsetTexture * 4); // VertexOffsetTexture = 0 when no texture used } - else - { + else { gl.disableVertexAttribArray(sp.vertexTextureAttribLoc); } - + // Bind instance buffers gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceDataBuffer); - if (m.instanceBuffer != null) - { + if (m.instanceBuffer != null) { gl.enableVertexAttribArray(sp.instancePositionAttribLoc); gl.vertexAttribPointer(sp.instancePositionAttribLoc, 3, gl.FLOAT, false, m.ElementsPerInstance * 4, m.InstanceOffsetPosition * 4); gl.vertexAttribDivisor(sp.instancePositionAttribLoc, 1); } - else - { + else { gl.disableVertexAttribArray(sp.instancePositionAttribLoc); gl.vertexAttribDivisor(sp.instancePositionAttribLoc, 0); } - if (m.instanceBufferHasRotations) - { + if (m.instanceBufferHasRotations) { gl.enableVertexAttribArray(sp.instanceRotationAttribLoc); gl.vertexAttribPointer(sp.instanceRotationAttribLoc, 4, gl.FLOAT, false, m.ElementsPerInstance * 4, m.InstanceOffsetRotation * 4); gl.vertexAttribDivisor(sp.instanceRotationAttribLoc, 1); } - else - { + else { gl.disableVertexAttribArray(sp.instanceRotationAttribLoc); gl.vertexAttribDivisor(sp.instanceRotationAttribLoc, 0); } - if (m.instanceBufferHasColors) - { + if (m.instanceBufferHasColors) { gl.enableVertexAttribArray(sp.instanceColorAttribLoc); gl.vertexAttribPointer(sp.instanceColorAttribLoc, 3, gl.FLOAT, false, m.ElementsPerInstance * 4, m.InstanceOffsetColor * 4); gl.vertexAttribDivisor(sp.instanceColorAttribLoc, 1); } - else - { + else { gl.disableVertexAttribArray(sp.instanceColorAttribLoc); gl.vertexAttribDivisor(sp.instanceColorAttribLoc, 0); } - + // Set use instance rotation uniform1i gl.uniform1i(sp.useInstanceRotation, m.instanceBufferHasRotations ? 1 : 0); @@ -293,8 +272,7 @@ export default class WebGLMeshBuffers gl.uniform1f(sp.alphaPtr, opacity); // Draw triangles - if (renderFilled) - { + if (renderFilled) { gl.uniform1i(sp.shadingTypePtr, this.m.isLabel ? 2 : (this.hasTexture ? 1 : 0)); gl.uniform1f(sp.lightingMultiplierPtr, 1.0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.triangleIndexBuffer); @@ -302,8 +280,7 @@ export default class WebGLMeshBuffers } // Draw wireframe edges - if (renderWireframe) - { + if (renderWireframe) { gl.uniform1i(sp.shadingTypePtr, -1); gl.uniform1f(sp.lightingMultiplierPtr, opacity == 1.0 ? 0.4 : 0.5); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.wireframeEdgeIndexBuffer); @@ -322,8 +299,7 @@ export default class WebGLMeshBuffers gl.bindTexture(gl.TEXTURE_2D, null); } - RenderPicker(program: ShaderProgram, v2sMatrix : mat4, w2vMatrix : mat4) - { + RenderPicker(program: ShaderProgram, v2sMatrix: mat4, w2vMatrix: mat4) { const gl = this.gl; const sp = program; const m = this.m; @@ -344,40 +320,35 @@ export default class WebGLMeshBuffers // Set model view matrix (i.e. concatenation of m2w, w2v) gl.uniformMatrix4fv(sp.m2vMatrixPtr, false, m2vMatrix); - + // Bind vertex buffers gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexDataBuffer); - if (true) - { + if (true) { gl.enableVertexAttribArray(sp.vertexPositionAttribLoc); gl.vertexAttribPointer(sp.vertexPositionAttribLoc, 3, gl.FLOAT, false, m.ElementsPerVertex * 4, m.VertexOffsetPosition * 4); } - + // Bind instance buffers gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceDataBuffer); - if (m.instanceBuffer != null) - { + if (m.instanceBuffer != null) { gl.enableVertexAttribArray(sp.instancePositionAttribLoc); gl.vertexAttribPointer(sp.instancePositionAttribLoc, 3, gl.FLOAT, false, m.ElementsPerInstance * 4, m.InstanceOffsetPosition * 4); gl.vertexAttribDivisor(sp.instancePositionAttribLoc, 1); } - else - { + else { gl.disableVertexAttribArray(sp.instancePositionAttribLoc); gl.vertexAttribDivisor(sp.instancePositionAttribLoc, 0); } - if (m.instanceBufferHasRotations) - { + if (m.instanceBufferHasRotations) { gl.enableVertexAttribArray(sp.instanceRotationAttribLoc); gl.vertexAttribPointer(sp.instanceRotationAttribLoc, 4, gl.FLOAT, false, m.ElementsPerInstance * 4, m.InstanceOffsetRotation * 4); gl.vertexAttribDivisor(sp.instanceRotationAttribLoc, 1); } - else - { + else { gl.disableVertexAttribArray(sp.instanceRotationAttribLoc); gl.vertexAttribDivisor(sp.instanceRotationAttribLoc, 0); } - + // Set use instance rotation uniform1i gl.uniform1i(sp.useInstanceRotation, m.instanceBufferHasRotations ? 1 : 0); diff --git a/tssrc/ts_assets.py b/tssrc/ts_assets.py index 8f01b93..34b5604 100644 --- a/tssrc/ts_assets.py +++ b/tssrc/ts_assets.py @@ -1,4 +1,4 @@ -"""Script to create the holodeck assets.""" +"""Script to create the embedded TypeScript assets.""" import scenepic as sp