@velumo/runtime 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action-dispatcher.d.ts +27 -0
- package/dist/action-dispatcher.d.ts.map +1 -0
- package/dist/action-dispatcher.js +34 -0
- package/dist/action-dispatcher.js.map +1 -0
- package/dist/camera.d.ts +51 -0
- package/dist/camera.d.ts.map +1 -0
- package/dist/camera.js +327 -0
- package/dist/camera.js.map +1 -0
- package/dist/controller.d.ts +33 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +109 -0
- package/dist/controller.js.map +1 -0
- package/dist/cue-dispatcher.d.ts +76 -0
- package/dist/cue-dispatcher.d.ts.map +1 -0
- package/dist/cue-dispatcher.js +164 -0
- package/dist/cue-dispatcher.js.map +1 -0
- package/dist/hash.d.ts +8 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +27 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/notes.d.ts +32 -0
- package/dist/notes.d.ts.map +1 -0
- package/dist/notes.js +132 -0
- package/dist/notes.js.map +1 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +76 -0
- package/dist/registry.js.map +1 -0
- package/dist/scene-player.d.ts +40 -0
- package/dist/scene-player.d.ts.map +1 -0
- package/dist/scene-player.js +177 -0
- package/dist/scene-player.js.map +1 -0
- package/dist/spatial.d.ts +11 -0
- package/dist/spatial.d.ts.map +1 -0
- package/dist/spatial.js +28 -0
- package/dist/spatial.js.map +1 -0
- package/dist/theme-bundle.d.ts +32 -0
- package/dist/theme-bundle.d.ts.map +1 -0
- package/dist/theme-bundle.js +8 -0
- package/dist/theme-bundle.js.map +1 -0
- package/dist/timeline.d.ts +46 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +217 -0
- package/dist/timeline.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +19 -0
- package/src/action-dispatcher.ts +81 -0
- package/src/camera.ts +513 -0
- package/src/controller.ts +171 -0
- package/src/cue-dispatcher.ts +299 -0
- package/src/hash.ts +39 -0
- package/src/index.ts +12 -0
- package/src/notes.ts +194 -0
- package/src/registry.ts +194 -0
- package/src/scene-player.ts +244 -0
- package/src/spatial.ts +40 -0
- package/src/theme-bundle.ts +35 -0
- package/src/timeline.ts +323 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"fileNames":["../../../node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/typescript/lib/lib.es2021.d.ts","../../../node_modules/typescript/lib/lib.es2022.d.ts","../../../node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../node_modules/typescript/lib/lib.scripthost.d.ts","../../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/typescript/lib/lib.es2022.full.d.ts","../../core/dist/types.d.ts","../../core/dist/builders.d.ts","../../core/dist/manifest-schema.d.ts","../../core/dist/layout-validation.d.ts","../../core/dist/capabilities.d.ts","../../core/dist/validation.d.ts","../../core/dist/index.d.ts","../src/controller.ts","../src/registry.ts","../src/action-dispatcher.ts","../src/timeline.ts","../src/camera.ts","../src/cue-dispatcher.ts","../src/hash.ts","../src/notes.ts","../src/spatial.ts","../src/scene-player.ts","../src/theme-bundle.ts","../src/index.ts"],"fileIdsList":[[64],[64,65,66,67,68,69],[70,71,72],[74],[70],[70,72,73,74],[71],[71,72,73,74,75,76,77,78,79,80,81],[70,71],[70,74,79],[70,74],[70,72]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"8a15b4607d9a499e2dbeed9ec0d3c0d7372c850b2d5f1fb259e8f6d41d468a84","impliedFormat":1},{"version":"26e0fe14baee4e127f4365d1ae0b276f400562e45e19e35fd2d4c296684715e6","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"2a2de5b9459b3fc44decd9ce6100b72f1b002ef523126c1d3d8b2a4a63d74d78","affectsGlobalScope":true,"impliedFormat":1},{"version":"f13f4b465c99041e912db5c44129a94588e1aafee35a50eab51044833f50b4ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"db9709688f82c9e5f65a119c64d835f906efe5f559d08b11642d56eb85b79357","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b25b8c874acd1a4cf8444c3617e037d444d19080ac9f634b405583fd10ce1f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be57d7c90cf1f8112ee2636a068d8fd181289f82b744160ec56a7dc158a9f5","affectsGlobalScope":true,"impliedFormat":1},{"version":"a917a49ac94cd26b754ab84e113369a75d1a47a710661d7cd25e961cc797065f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d3261badeb7843d157ef3e6f5d1427d0eeb0af0cf9df84a62cfd29fd47ac86e","affectsGlobalScope":true,"impliedFormat":1},{"version":"195daca651dde22f2167ac0d0a05e215308119a3100f5e6268e8317d05a92526","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b11e4285cd2bb164a4dc09248bdec69e9842517db4ca47c1ba913011e44ff2f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0508571a52475e245b02bc50fa1394065a0a3d05277fbf5120c3784b85651799","affectsGlobalScope":true,"impliedFormat":1},{"version":"8f9af488f510c3015af3cc8c267a9e9d96c4dd38a1fdff0e11dc5a544711415b","affectsGlobalScope":true,"impliedFormat":1},{"version":"fc611fea8d30ea72c6bbfb599c9b4d393ce22e2f5bfef2172534781e7d138104","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"01a30f9e8582b369075c0808df71121e6855cb06fd8d3d39511d9ebb66405205","impliedFormat":1},{"version":"8877805003603477c63696f8af86eb14be659de3027a597617e7a95ca9250a92","impliedFormat":99},{"version":"a2c4bcfeeaf83801774f8c21daf4c18b15fd9da4469e222a6708a58d235bb286","impliedFormat":99},{"version":"163a1497b249fb8ac3eac7710af6df75222a21b20434ec35d4489c85f18a397b","impliedFormat":99},{"version":"b1d091ebe44dc4eb7ba1535c76bcbf15df97a0cde19fda1bbd34e1503df10e95","impliedFormat":99},{"version":"baeb0570658dce9269d3a5dabb44a1e6e11d8712f45750ca80885fd0e2dab38e","impliedFormat":99},{"version":"def8ed8b7980dbad419047252c19812799dbf761e797d624a9a030fb318a27ee","impliedFormat":99},{"version":"09451d1bdb65b1b10cafbccc4c762f9181609881ba078e4aabfbb7c6d4effe17","impliedFormat":99},{"version":"bc15e50ef91b8488717676feb2b764cbc1568065d2d4902b64cc6e1d28112781","signature":"4c26794e2a073e20bf40b3482be4f25fa4d6b83c8d4cdb7e3744d8401eef6c38","impliedFormat":99},{"version":"2b99fff0b9b6508c7020a047eb56c8e55b89afc8bc1834741fcc1377db20ac7e","signature":"165426a72c724ac8b6c5068f64aeeedc976a31cddaf7bdb30689ca0e0e885885","impliedFormat":99},{"version":"33f683df740408b1f66efbb4cce476bf2ed8d301b6d4b6c50b0a65afac84887a","signature":"f864cf8286ef0798ae6eb2808965e2038c10ffb940b196a3a6e6364fe534115f","impliedFormat":99},{"version":"25c70402d1573c265a6a0e5c742f694bdc8dcb38900f220a25bf4eccb188944b","signature":"9c6738ae625093663883e4694a79bd8d9c8ff79f0d0bb60b8d6e4ab39393b77d","impliedFormat":99},{"version":"c4f70bdfe4e39a7f3a0204ad07f7f3b41c548669c315dcdf18d9b4215bfc938a","signature":"efbeb4d5e4df969ce57b6fe98d6cabe2097cae2e0695b6ef85c77f7a8045e9a3","impliedFormat":99},{"version":"fa451a10d5cedc04a6587af956ff13df1c224904748127b4771a6bb7fc21eaf1","signature":"d6628b767083fceffd23177e8c53e1348d372e5755c2880f8f2e958d7a6dbafc","impliedFormat":99},{"version":"a818ca32286dc35d4bc7a0415f7929d8ada785e41b1428f915f3f806bf56adc3","signature":"92692f5dcdd41d6cebb9785ebe83f6631e448d468c7e77f56c2fe6f8f32869ba","impliedFormat":99},{"version":"2f96163c2c08e570b3416314858a1f3522a52a57a460b205c642fb64279211ef","signature":"1093522585079cf706e859f4323d23832ef5b62b04805190bcba06614fc8bdcb","impliedFormat":99},{"version":"aa86e431dd4f1db079ad6545800b4a9a152d773649fc75368e0f1bacd68598ca","signature":"4bbc05996c36885d4da5e1d3b140fd16ee7121a4184843447a3bb47b240e3386","impliedFormat":99},{"version":"c0e49d332c7b517f88cea618d562a1f798580222f619479aa51bc5350192d11a","signature":"0eaeeda7b43eb04e6503115b59ea0fc0dcede7d39dc6651a1ae8ba94e5974b46","impliedFormat":99},{"version":"3e822c2174a0ebc674f1ae994f6a45541be23f10026061a9485b915e59d1d7d6","signature":"8cad53853f3c0a8f7391721d625259ba815e33c26139f59e08751fa38c326f6d","impliedFormat":99},{"version":"7200cdc5a2864de9653afbb93d0e66804d35b858d72a95c964a2f39bf6033ef3","signature":"7af78eb71c547cc0353d89ca54074af736128b01e231ab5662e1300adc0e17fd","impliedFormat":99}],"root":[[71,82]],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":199,"outDir":"./","rootDir":"../src","skipLibCheck":true,"sourceMap":true,"strict":true,"target":9,"tsBuildInfoFile":"./tsconfig.tsbuildinfo"},"referencedMap":[[65,1],[68,1],[70,2],[67,1],[69,1],[73,3],[75,4],[71,5],[76,6],[77,7],[82,8],[78,9],[72,5],[80,10],[79,11],[81,12],[74,5]],"latestChangedDtsFile":"./controller.d.ts","version":"6.0.3"}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@velumo/runtime",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@velumo/core": "0.0.0"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -b"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Cue, CueAction, JsonValue } from "@velumo/core";
|
|
2
|
+
import type { RuntimeController } from "./controller.js";
|
|
3
|
+
import type { ActionHandler } from "./registry.js";
|
|
4
|
+
|
|
5
|
+
export type ActionSource = "cue" | "control";
|
|
6
|
+
|
|
7
|
+
// The generalized handler context. Both the timeline cue path and the
|
|
8
|
+
// imperative control (click) path build one of these. The cue-only fields
|
|
9
|
+
// (cue/cueIndex/timeMs) are optional here and narrowed to required on the
|
|
10
|
+
// cue path's CueDispatchEvent.
|
|
11
|
+
export interface ActionContext {
|
|
12
|
+
readonly action: CueAction;
|
|
13
|
+
readonly payload: JsonValue | undefined;
|
|
14
|
+
readonly source: ActionSource;
|
|
15
|
+
// Path asymmetry (by design): `runtime` is ALWAYS set on the control path
|
|
16
|
+
// (a click, source:"control") and NEVER on the cue path (source:"cue"). The
|
|
17
|
+
// cue-only fields below (`cue`/`cueIndex`/`timeMs`) are the mirror image —
|
|
18
|
+
// present on the cue path, absent on the control path. A handler shared by
|
|
19
|
+
// both paths must guard on `source` before reading a path-specific field.
|
|
20
|
+
readonly runtime?: RuntimeController;
|
|
21
|
+
readonly target?: string;
|
|
22
|
+
readonly cue?: Cue;
|
|
23
|
+
readonly cueIndex?: number;
|
|
24
|
+
readonly timeMs?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Generic over the context type so BOTH paths type-check without a
|
|
28
|
+
// contravariance error: the cue path uses ActionDispatcher<CueDispatchEvent>
|
|
29
|
+
// (its registry yields ActionHandler<CueDispatchEvent>), the control path uses
|
|
30
|
+
// the default ActionDispatcher<ActionContext>.
|
|
31
|
+
export interface ActionDispatcherOptions<
|
|
32
|
+
TCtx extends ActionContext = ActionContext,
|
|
33
|
+
> {
|
|
34
|
+
readonly registry: {
|
|
35
|
+
getActionHandler(type: string): ActionHandler<TCtx> | undefined;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Shared lookup+invoke core for both the timeline cue path and the imperative
|
|
40
|
+
// control (click) path. Returns whether a handler existed; NEVER throws for a
|
|
41
|
+
// missing handler. Callers decide the missing-handler policy: the cue path
|
|
42
|
+
// throws (CueDispatcher throws on false), the control path is a no-op.
|
|
43
|
+
export class ActionDispatcher<TCtx extends ActionContext = ActionContext> {
|
|
44
|
+
readonly #registry: ActionDispatcherOptions<TCtx>["registry"];
|
|
45
|
+
|
|
46
|
+
constructor(options: ActionDispatcherOptions<TCtx>) {
|
|
47
|
+
this.#registry = options.registry;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dispatch(actionType: string, context: TCtx): boolean {
|
|
51
|
+
const handler = this.#registry.getActionHandler(actionType);
|
|
52
|
+
if (handler === undefined) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
handler(context);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createActionDispatcher<
|
|
61
|
+
TCtx extends ActionContext = ActionContext,
|
|
62
|
+
>(options: ActionDispatcherOptions<TCtx>): ActionDispatcher<TCtx> {
|
|
63
|
+
return new ActionDispatcher<TCtx>(options);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Convenience for the control (click) path: build the ActionContext from a
|
|
67
|
+
// declared action and dispatch it, returning nothing (a missing handler is a
|
|
68
|
+
// silent no-op).
|
|
69
|
+
export function dispatchControlAction(
|
|
70
|
+
dispatcher: ActionDispatcher<ActionContext>,
|
|
71
|
+
action: CueAction,
|
|
72
|
+
runtime?: RuntimeController,
|
|
73
|
+
): void {
|
|
74
|
+
dispatcher.dispatch(action.type, {
|
|
75
|
+
action,
|
|
76
|
+
payload: action.payload,
|
|
77
|
+
source: "control",
|
|
78
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
79
|
+
...(action.target === undefined ? {} : { target: action.target }),
|
|
80
|
+
});
|
|
81
|
+
}
|
package/src/camera.ts
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { type EasingFunction, lerp } from "./timeline.js";
|
|
2
|
+
|
|
3
|
+
const ERROR_PREFIX = "CameraController: ";
|
|
4
|
+
|
|
5
|
+
export interface CameraVector3 {
|
|
6
|
+
readonly x: number;
|
|
7
|
+
readonly y: number;
|
|
8
|
+
readonly z: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CameraBounds {
|
|
12
|
+
readonly min: CameraVector3;
|
|
13
|
+
readonly max: CameraVector3;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CameraState {
|
|
17
|
+
readonly position: CameraVector3;
|
|
18
|
+
readonly target: CameraVector3;
|
|
19
|
+
readonly zoom: number;
|
|
20
|
+
readonly bounds?: CameraBounds;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CameraTransition {
|
|
24
|
+
readonly to: Partial<Omit<CameraState, "bounds">>;
|
|
25
|
+
readonly durationMs: number;
|
|
26
|
+
readonly easing?: EasingFunction;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CameraKeyframe {
|
|
30
|
+
readonly state: Partial<Omit<CameraState, "bounds">>;
|
|
31
|
+
readonly atMs: number;
|
|
32
|
+
readonly easing?: EasingFunction;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CameraPath {
|
|
36
|
+
readonly keyframes: readonly CameraKeyframe[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type CameraEventName = "camera:change";
|
|
40
|
+
|
|
41
|
+
export interface CameraChangeEvent {
|
|
42
|
+
readonly type: "camera:change";
|
|
43
|
+
readonly state: CameraState;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type CameraEventHandler = (event: CameraChangeEvent) => void;
|
|
47
|
+
|
|
48
|
+
export interface CameraControllerOptions {
|
|
49
|
+
readonly initial?: Partial<Omit<CameraState, "bounds">>;
|
|
50
|
+
readonly bounds?: CameraBounds;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ResolvedState {
|
|
54
|
+
position: CameraVector3;
|
|
55
|
+
target: CameraVector3;
|
|
56
|
+
zoom: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface TransitionAnimation {
|
|
60
|
+
readonly type: "transition";
|
|
61
|
+
readonly startState: ResolvedState;
|
|
62
|
+
readonly to: Partial<Omit<CameraState, "bounds">>;
|
|
63
|
+
readonly durationMs: number;
|
|
64
|
+
readonly easing: EasingFunction;
|
|
65
|
+
elapsedMs: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface PathAnimation {
|
|
69
|
+
readonly type: "path";
|
|
70
|
+
readonly keyframes: readonly CameraKeyframe[];
|
|
71
|
+
readonly startState: ResolvedState;
|
|
72
|
+
elapsedMs: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type ActiveAnimation = TransitionAnimation | PathAnimation;
|
|
76
|
+
|
|
77
|
+
function assertFinite(value: number, field: string): void {
|
|
78
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`${ERROR_PREFIX}${field} must be a finite number; got ${String(value)}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validateVector(value: unknown, field: string): CameraVector3 {
|
|
86
|
+
if (value === null || typeof value !== "object") {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${ERROR_PREFIX}${field} must be an object {x, y, z}; got ${String(value)}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const record = value as Record<string, unknown>;
|
|
92
|
+
for (const axis of ["x", "y", "z"] as const) {
|
|
93
|
+
const axisValue = record[axis];
|
|
94
|
+
if (typeof axisValue !== "number" || !Number.isFinite(axisValue)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`${ERROR_PREFIX}${field}.${axis} must be a finite number; got ${String(axisValue)}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
x: record.x as number,
|
|
102
|
+
y: record.y as number,
|
|
103
|
+
z: record.z as number,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function validateBounds(
|
|
108
|
+
bounds: CameraBounds | undefined,
|
|
109
|
+
): CameraBounds | undefined {
|
|
110
|
+
if (bounds === undefined) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const min = validateVector(bounds.min, "bounds.min");
|
|
114
|
+
const max = validateVector(bounds.max, "bounds.max");
|
|
115
|
+
for (const axis of ["x", "y", "z"] as const) {
|
|
116
|
+
if (min[axis] > max[axis]) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`${ERROR_PREFIX}bounds.min.${axis} (${min[axis]}) must be <= bounds.max.${axis} (${max[axis]})`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { min, max };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function clampPosition(
|
|
126
|
+
position: CameraVector3,
|
|
127
|
+
bounds: CameraBounds | undefined,
|
|
128
|
+
): CameraVector3 {
|
|
129
|
+
if (bounds === undefined) {
|
|
130
|
+
return position;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
x: Math.min(bounds.max.x, Math.max(bounds.min.x, position.x)),
|
|
134
|
+
y: Math.min(bounds.max.y, Math.max(bounds.min.y, position.y)),
|
|
135
|
+
z: Math.min(bounds.max.z, Math.max(bounds.min.z, position.z)),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function copyVector(value: CameraVector3): CameraVector3 {
|
|
140
|
+
return { x: value.x, y: value.y, z: value.z };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function copyBounds(bounds: CameraBounds): CameraBounds {
|
|
144
|
+
return { min: copyVector(bounds.min), max: copyVector(bounds.max) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class CameraController {
|
|
148
|
+
readonly #handlers = new Map<CameraEventName, Set<CameraEventHandler>>();
|
|
149
|
+
readonly #bounds: CameraBounds | undefined;
|
|
150
|
+
#position: CameraVector3;
|
|
151
|
+
#target: CameraVector3;
|
|
152
|
+
#zoom: number;
|
|
153
|
+
#animation: ActiveAnimation | undefined;
|
|
154
|
+
|
|
155
|
+
constructor(options: CameraControllerOptions = {}) {
|
|
156
|
+
this.#bounds = validateBounds(options.bounds);
|
|
157
|
+
|
|
158
|
+
const initial = options.initial ?? {};
|
|
159
|
+
|
|
160
|
+
const initialPosition =
|
|
161
|
+
initial.position !== undefined
|
|
162
|
+
? validateVector(initial.position, "initial.position")
|
|
163
|
+
: { x: 0, y: 0, z: 0 };
|
|
164
|
+
const initialTarget =
|
|
165
|
+
initial.target !== undefined
|
|
166
|
+
? validateVector(initial.target, "initial.target")
|
|
167
|
+
: { x: 0, y: 0, z: 0 };
|
|
168
|
+
let initialZoom = 1;
|
|
169
|
+
if (initial.zoom !== undefined) {
|
|
170
|
+
assertFinite(initial.zoom, "initial.zoom");
|
|
171
|
+
initialZoom = initial.zoom;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.#position = clampPosition(initialPosition, this.#bounds);
|
|
175
|
+
this.#target = initialTarget;
|
|
176
|
+
this.#zoom = initialZoom;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
getState(): CameraState {
|
|
180
|
+
const state: { -readonly [K in keyof CameraState]: CameraState[K] } = {
|
|
181
|
+
position: copyVector(this.#position),
|
|
182
|
+
target: copyVector(this.#target),
|
|
183
|
+
zoom: this.#zoom,
|
|
184
|
+
};
|
|
185
|
+
if (this.#bounds !== undefined) {
|
|
186
|
+
state.bounds = copyBounds(this.#bounds);
|
|
187
|
+
}
|
|
188
|
+
return state;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
setState(partial: Partial<Omit<CameraState, "bounds">>): void {
|
|
192
|
+
let nextPosition = this.#position;
|
|
193
|
+
let nextTarget = this.#target;
|
|
194
|
+
let nextZoom = this.#zoom;
|
|
195
|
+
|
|
196
|
+
if (partial.position !== undefined) {
|
|
197
|
+
nextPosition = clampPosition(
|
|
198
|
+
validateVector(partial.position, "setState.position"),
|
|
199
|
+
this.#bounds,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
if (partial.target !== undefined) {
|
|
203
|
+
nextTarget = validateVector(partial.target, "setState.target");
|
|
204
|
+
}
|
|
205
|
+
if (partial.zoom !== undefined) {
|
|
206
|
+
assertFinite(partial.zoom, "setState.zoom");
|
|
207
|
+
nextZoom = partial.zoom;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.#position = nextPosition;
|
|
211
|
+
this.#target = nextTarget;
|
|
212
|
+
this.#zoom = nextZoom;
|
|
213
|
+
this.#emit();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
transitionTo(transition: CameraTransition): void {
|
|
217
|
+
assertFinite(transition.durationMs, "transitionTo.durationMs");
|
|
218
|
+
if (transition.durationMs < 0) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`${ERROR_PREFIX}transitionTo.durationMs must be >= 0; got ${transition.durationMs}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const validatedTo: {
|
|
224
|
+
position?: CameraVector3;
|
|
225
|
+
target?: CameraVector3;
|
|
226
|
+
zoom?: number;
|
|
227
|
+
} = {};
|
|
228
|
+
if (transition.to.position !== undefined) {
|
|
229
|
+
validatedTo.position = validateVector(
|
|
230
|
+
transition.to.position,
|
|
231
|
+
"transitionTo.to.position",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (transition.to.target !== undefined) {
|
|
235
|
+
validatedTo.target = validateVector(
|
|
236
|
+
transition.to.target,
|
|
237
|
+
"transitionTo.to.target",
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (transition.to.zoom !== undefined) {
|
|
241
|
+
assertFinite(transition.to.zoom, "transitionTo.to.zoom");
|
|
242
|
+
validatedTo.zoom = transition.to.zoom;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.#animation = {
|
|
246
|
+
type: "transition",
|
|
247
|
+
startState: this.#snapshotResolvedState(),
|
|
248
|
+
to: validatedTo,
|
|
249
|
+
durationMs: transition.durationMs,
|
|
250
|
+
easing: transition.easing ?? linearEasing,
|
|
251
|
+
elapsedMs: 0,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
playPath(path: CameraPath): void {
|
|
256
|
+
const keyframes = path.keyframes;
|
|
257
|
+
if (keyframes.length === 0) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`${ERROR_PREFIX}playPath.keyframes must contain at least one keyframe; got empty array`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (keyframes[0].atMs !== 0) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`${ERROR_PREFIX}playPath.keyframes[0].atMs must equal 0; got ${keyframes[0].atMs}`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const copiedKeyframes: CameraKeyframe[] = [];
|
|
268
|
+
let prevAtMs = -Infinity;
|
|
269
|
+
for (let i = 0; i < keyframes.length; i += 1) {
|
|
270
|
+
const kf = keyframes[i];
|
|
271
|
+
assertFinite(kf.atMs, `playPath.keyframes[${i}].atMs`);
|
|
272
|
+
if (i > 0 && kf.atMs <= prevAtMs) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`${ERROR_PREFIX}playPath.keyframes[${i}].atMs (${kf.atMs}) must be strictly greater than keyframes[${i - 1}].atMs (${prevAtMs})`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
prevAtMs = kf.atMs;
|
|
278
|
+
|
|
279
|
+
const copiedState: {
|
|
280
|
+
position?: CameraVector3;
|
|
281
|
+
target?: CameraVector3;
|
|
282
|
+
zoom?: number;
|
|
283
|
+
} = {};
|
|
284
|
+
if (kf.state.position !== undefined) {
|
|
285
|
+
copiedState.position = validateVector(
|
|
286
|
+
kf.state.position,
|
|
287
|
+
`playPath.keyframes[${i}].state.position`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (kf.state.target !== undefined) {
|
|
291
|
+
copiedState.target = validateVector(
|
|
292
|
+
kf.state.target,
|
|
293
|
+
`playPath.keyframes[${i}].state.target`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
if (kf.state.zoom !== undefined) {
|
|
297
|
+
assertFinite(kf.state.zoom, `playPath.keyframes[${i}].state.zoom`);
|
|
298
|
+
copiedState.zoom = kf.state.zoom;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
copiedKeyframes.push({
|
|
302
|
+
state: copiedState,
|
|
303
|
+
atMs: kf.atMs,
|
|
304
|
+
...(kf.easing !== undefined ? { easing: kf.easing } : {}),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.#animation = {
|
|
309
|
+
type: "path",
|
|
310
|
+
keyframes: copiedKeyframes,
|
|
311
|
+
startState: this.#snapshotResolvedState(),
|
|
312
|
+
elapsedMs: 0,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
advance(deltaMs: number): void {
|
|
317
|
+
assertFinite(deltaMs, "advance.deltaMs");
|
|
318
|
+
if (deltaMs < 0) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`${ERROR_PREFIX}advance.deltaMs must be non-negative; got ${deltaMs}`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (deltaMs === 0) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (this.#animation === undefined) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (this.#animation.type === "transition") {
|
|
331
|
+
this.#advanceTransition(this.#animation, deltaMs);
|
|
332
|
+
} else {
|
|
333
|
+
this.#advancePath(this.#animation, deltaMs);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.#emit();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
on(eventName: CameraEventName, handler: CameraEventHandler): () => void {
|
|
340
|
+
let handlers = this.#handlers.get(eventName);
|
|
341
|
+
if (handlers === undefined) {
|
|
342
|
+
handlers = new Set();
|
|
343
|
+
this.#handlers.set(eventName, handlers);
|
|
344
|
+
}
|
|
345
|
+
handlers.add(handler);
|
|
346
|
+
|
|
347
|
+
let disposed = false;
|
|
348
|
+
return () => {
|
|
349
|
+
if (disposed) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
disposed = true;
|
|
353
|
+
handlers.delete(handler);
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#snapshotResolvedState(): ResolvedState {
|
|
358
|
+
return {
|
|
359
|
+
position: copyVector(this.#position),
|
|
360
|
+
target: copyVector(this.#target),
|
|
361
|
+
zoom: this.#zoom,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#advanceTransition(anim: TransitionAnimation, deltaMs: number): void {
|
|
366
|
+
const newElapsed = anim.elapsedMs + deltaMs;
|
|
367
|
+
const rawProgress =
|
|
368
|
+
anim.durationMs === 0 ? 1 : Math.min(1, newElapsed / anim.durationMs);
|
|
369
|
+
const t = anim.easing(rawProgress);
|
|
370
|
+
|
|
371
|
+
if (anim.to.position !== undefined) {
|
|
372
|
+
const to = anim.to.position;
|
|
373
|
+
this.#position = clampPosition(
|
|
374
|
+
{
|
|
375
|
+
x: lerp(anim.startState.position.x, to.x, t),
|
|
376
|
+
y: lerp(anim.startState.position.y, to.y, t),
|
|
377
|
+
z: lerp(anim.startState.position.z, to.z, t),
|
|
378
|
+
},
|
|
379
|
+
this.#bounds,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (anim.to.target !== undefined) {
|
|
383
|
+
const to = anim.to.target;
|
|
384
|
+
this.#target = {
|
|
385
|
+
x: lerp(anim.startState.target.x, to.x, t),
|
|
386
|
+
y: lerp(anim.startState.target.y, to.y, t),
|
|
387
|
+
z: lerp(anim.startState.target.z, to.z, t),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (anim.to.zoom !== undefined) {
|
|
391
|
+
this.#zoom = lerp(anim.startState.zoom, anim.to.zoom, t);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (rawProgress >= 1) {
|
|
395
|
+
this.#animation = undefined;
|
|
396
|
+
} else {
|
|
397
|
+
anim.elapsedMs = newElapsed;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#advancePath(anim: PathAnimation, deltaMs: number): void {
|
|
402
|
+
const newElapsed = anim.elapsedMs + deltaMs;
|
|
403
|
+
const keyframes = anim.keyframes;
|
|
404
|
+
const totalMs = keyframes[keyframes.length - 1].atMs;
|
|
405
|
+
|
|
406
|
+
if (newElapsed >= totalMs) {
|
|
407
|
+
const finalResolved = resolveStateAtKeyframe(
|
|
408
|
+
keyframes.length - 1,
|
|
409
|
+
anim.startState,
|
|
410
|
+
keyframes,
|
|
411
|
+
);
|
|
412
|
+
this.#position = clampPosition(finalResolved.position, this.#bounds);
|
|
413
|
+
this.#target = finalResolved.target;
|
|
414
|
+
this.#zoom = finalResolved.zoom;
|
|
415
|
+
this.#animation = undefined;
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
let segIdx = 0;
|
|
420
|
+
for (let i = 0; i < keyframes.length - 1; i += 1) {
|
|
421
|
+
if (
|
|
422
|
+
keyframes[i].atMs <= newElapsed &&
|
|
423
|
+
newElapsed < keyframes[i + 1].atMs
|
|
424
|
+
) {
|
|
425
|
+
segIdx = i;
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const segStart = keyframes[segIdx];
|
|
431
|
+
const segEnd = keyframes[segIdx + 1];
|
|
432
|
+
const segDuration = segEnd.atMs - segStart.atMs;
|
|
433
|
+
const segElapsed = newElapsed - segStart.atMs;
|
|
434
|
+
const rawProgress = segDuration === 0 ? 1 : segElapsed / segDuration;
|
|
435
|
+
const easing = segEnd.easing ?? linearEasing;
|
|
436
|
+
const t = easing(rawProgress);
|
|
437
|
+
|
|
438
|
+
const stateAtSegStart = resolveStateAtKeyframe(
|
|
439
|
+
segIdx,
|
|
440
|
+
anim.startState,
|
|
441
|
+
keyframes,
|
|
442
|
+
);
|
|
443
|
+
const stateAtSegEnd = resolveStateAtKeyframe(
|
|
444
|
+
segIdx + 1,
|
|
445
|
+
anim.startState,
|
|
446
|
+
keyframes,
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
this.#position = clampPosition(
|
|
450
|
+
{
|
|
451
|
+
x: lerp(stateAtSegStart.position.x, stateAtSegEnd.position.x, t),
|
|
452
|
+
y: lerp(stateAtSegStart.position.y, stateAtSegEnd.position.y, t),
|
|
453
|
+
z: lerp(stateAtSegStart.position.z, stateAtSegEnd.position.z, t),
|
|
454
|
+
},
|
|
455
|
+
this.#bounds,
|
|
456
|
+
);
|
|
457
|
+
this.#target = {
|
|
458
|
+
x: lerp(stateAtSegStart.target.x, stateAtSegEnd.target.x, t),
|
|
459
|
+
y: lerp(stateAtSegStart.target.y, stateAtSegEnd.target.y, t),
|
|
460
|
+
z: lerp(stateAtSegStart.target.z, stateAtSegEnd.target.z, t),
|
|
461
|
+
};
|
|
462
|
+
this.#zoom = lerp(stateAtSegStart.zoom, stateAtSegEnd.zoom, t);
|
|
463
|
+
|
|
464
|
+
anim.elapsedMs = newElapsed;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#emit(): void {
|
|
468
|
+
const handlers = this.#handlers.get("camera:change");
|
|
469
|
+
if (handlers === undefined) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const event: CameraChangeEvent = {
|
|
473
|
+
type: "camera:change",
|
|
474
|
+
state: this.getState(),
|
|
475
|
+
};
|
|
476
|
+
for (const handler of [...handlers]) {
|
|
477
|
+
handler(event);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function linearEasing(t: number): number {
|
|
483
|
+
return t;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function resolveStateAtKeyframe(
|
|
487
|
+
idx: number,
|
|
488
|
+
start: ResolvedState,
|
|
489
|
+
keyframes: readonly CameraKeyframe[],
|
|
490
|
+
): ResolvedState {
|
|
491
|
+
let position = copyVector(start.position);
|
|
492
|
+
let target = copyVector(start.target);
|
|
493
|
+
let zoom = start.zoom;
|
|
494
|
+
for (let i = 0; i <= idx; i += 1) {
|
|
495
|
+
const kf = keyframes[i];
|
|
496
|
+
if (kf.state.position !== undefined) {
|
|
497
|
+
position = copyVector(kf.state.position);
|
|
498
|
+
}
|
|
499
|
+
if (kf.state.target !== undefined) {
|
|
500
|
+
target = copyVector(kf.state.target);
|
|
501
|
+
}
|
|
502
|
+
if (kf.state.zoom !== undefined) {
|
|
503
|
+
zoom = kf.state.zoom;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return { position, target, zoom };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export function createCameraController(
|
|
510
|
+
options?: CameraControllerOptions,
|
|
511
|
+
): CameraController {
|
|
512
|
+
return new CameraController(options);
|
|
513
|
+
}
|