From e42cda919978f5015010f0434d4bade27a82395c Mon Sep 17 00:00:00 2001 From: Jonathan Lurie Date: Fri, 13 Dec 2024 09:25:39 +0100 Subject: [PATCH 01/17] Update image links + replace plugin by module (#13) --- readme.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/readme.md b/readme.md index a178246..549f617 100644 --- a/readme.md +++ b/readme.md @@ -15,26 +15,21 @@

## 3D objects on MapTiler maps -With this MapTiler SDK plugin, you can add 3D objects to your basemap with plenty of customizations from glTF/glb files! Those can be meshes, groups of meshes, point clouds and a mix of all these. +With this MapTiler SDK module, you can add 3D objects to your basemap with plenty of customizations from glTF/glb files! Those can be meshes, groups of meshes, point clouds and a mix of all these. **Here are some examples:** -![](images/plane.jpeg) -[Add an airplane 3D model to your map using the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-plane/) -![](images/ducks-and-posts.jpeg) -[Add multiple 3D models to the map with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-multi/) +[![](images/plane.jpeg)Add an airplane 3D model to your map using the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-plane/) -![](images/cad.jpeg) -[Display a building model based on point cloud data on a map with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cad/) +[![](images/ducks-and-posts.jpeg)Add multiple 3D models to the map with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-multi/) -![](images/dundee.jpeg) -[ Display a 3D building model generated with photogrammetry software with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cloud-dundee/) +[![](images/cad.jpeg)Display a building model based on point cloud data on a map with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cad/) -![](images/dundee2.jpeg) -[ Display a 3D building model generated with photogrammetry software with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cloud-dundee/) +[![](images/dundee.jpeg)Display a 3D building model generated with photogrammetry software with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cloud-dundee/) -![](images/nhm.jpeg) -[Display a point cloud 3D building model on a map with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cloud-nmh/) +[![](images/dundee2.jpeg)Display a 3D building model generated with photogrammetry software with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cloud-dundee/) + +[![](images/nhm.jpeg)Display a point cloud 3D building model on a map with the MapTiler 3D JS Module](https://docs.maptiler.com/sdk-js/examples/3d-js-point-cloud-nmh/) ### Installation From NPM and using the ES module, in a terminal, in your project: @@ -188,7 +183,7 @@ enum SourceOrientation { Z_UP = 3, }; ``` -Note that regardless of the original up axis, this plugin as well as MapTiler SDK/Maplibre GL JS only deal with 3D spaces that follow the [right-hand rule](https://en.wikipedia.org/wiki/Right-hand_rule). +Note that regardless of the original up axis, this module as well as MapTiler SDK/Maplibre GL JS only deal with 3D spaces that follow the [right-hand rule](https://en.wikipedia.org/wiki/Right-hand_rule). - Generic options that apply to both point lights and meshes: ```ts From 515878419553d87e536bd860c746164d4eb6cf96 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Nov 2024 18:56:17 +0100 Subject: [PATCH 02/17] Add position indicators models --- demos/models/position-indicator--y-up.glb | Bin 0 -> 79096 bytes demos/models/position-indicator--z-up.glb | Bin 0 -> 79096 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/models/position-indicator--y-up.glb create mode 100644 demos/models/position-indicator--z-up.glb diff --git a/demos/models/position-indicator--y-up.glb b/demos/models/position-indicator--y-up.glb new file mode 100644 index 0000000000000000000000000000000000000000..a730ce4c67aa97f81dce4c1bf4ca251f16d9590a GIT binary patch literal 79096 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 O`7y_M9{%s2qyGaE&EsVN literal 0 HcmV?d00001 diff --git a/demos/models/position-indicator--z-up.glb b/demos/models/position-indicator--z-up.glb new file mode 100644 index 0000000000000000000000000000000000000000..71ffd2b96f1f7442604b72669e06a8ba49462031 GIT binary patch literal 79096 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 literal 0 HcmV?d00001 From 43adf4e7aea39d806dcc4a0de1ac47a1ed3f9044 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Nov 2024 18:56:43 +0100 Subject: [PATCH 03/17] Install maptiler sdk 3 --- package-lock.json | 308 ++++++++++++---------------------------------- package.json | 2 +- 2 files changed, 79 insertions(+), 231 deletions(-) diff --git a/package-lock.json b/package-lock.json index bd417d5..e3e3b0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@maptiler/3d", "version": "1.0.0", "dependencies": { - "@maptiler/sdk": "^2.4.0", + "@maptiler/sdk": "^3.0.0-rc.2", "lru-cache": "^11.0.1", "three": "^0.168.0" }, @@ -601,6 +601,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", + "license": "ISC", "dependencies": { "get-stream": "^6.0.1", "minimist": "^1.2.6" @@ -620,22 +621,26 @@ "node_modules/@mapbox/point-geometry": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", - "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", + "license": "ISC" }, "node_modules/@mapbox/tiny-sdf": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", - "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==", + "license": "BSD-2-Clause" }, "node_modules/@mapbox/unitbezier": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", - "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" }, "node_modules/@mapbox/vector-tile": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "license": "BSD-3-Clause", "dependencies": { "@mapbox/point-geometry": "~0.1.0" } @@ -644,22 +649,23 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "license": "ISC", "engines": { "node": ">=6.0.0" } }, "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.3.1.tgz", - "integrity": "sha512-5ueL4UDitzVtceQ8J4kY+Px3WK+eZTsmGwha3MBKHKqiHvKrjWWwBCIl1K8BuJSc5OFh83uI8IFNoFvQxX2uUw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-22.0.0.tgz", + "integrity": "sha512-kD8TxV6CdgHIEeam4xODVJNAT3hcvRhRl5RcNiu+FPR/JoMsExAQTruBGtv+jLppj4xt9V56pG/SHK8z6fv6xA==", + "license": "ISC", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", - "quickselect": "^2.0.0", + "quickselect": "^3.0.0", "rw": "^1.3.3", - "sort-object": "^3.0.3", "tinyqueue": "^3.0.0" }, "bin": { @@ -669,23 +675,25 @@ } }, "node_modules/@maptiler/client": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@maptiler/client/-/client-2.0.0.tgz", - "integrity": "sha512-0sxRhpizfkZPY4cEBMUxF9WaWraMPkWNJxi373qaGfP/e8aKFudt5GlHvOKc3hI0bjAyevAdjVJbKC6SUxOGkg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@maptiler/client/-/client-2.2.0.tgz", + "integrity": "sha512-kV4dSJK2PLfRLnl437CQgDJBboHcf+Z7FWkSoPW3ANca/csoYQQOwz42BPNkda/98OT+CviIueeQdNUyeEL1OQ==", + "license": "BSD-3-Clause", "dependencies": { "quick-lru": "^7.0.0" } }, "node_modules/@maptiler/sdk": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@maptiler/sdk/-/sdk-2.4.0.tgz", - "integrity": "sha512-BydcQfLxK7p5lXsUJPRW0TIayjz/A5LE0qd0ljA/BU6qV+coqBfMKDZXvxMmGh54NWbbZ5l0L7Ab7i5opHlzqw==", + "version": "3.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@maptiler/sdk/-/sdk-3.0.0-rc.2.tgz", + "integrity": "sha512-eQBX8lpznnS1ujbukNxEUn8g7o7RQO5OJs/qOv7APDssM1eAt4Jis/GNZt/HAC1S2KPZn/tKTZsFflgFl1Y6Zg==", + "license": "BSD-3-Clause", "dependencies": { - "@maplibre/maplibre-gl-style-spec": "^20.3.1", - "@maptiler/client": "^2.0.0", + "@maplibre/maplibre-gl-style-spec": "^22.0.0", + "@maptiler/client": "^2.2.0", "events": "^3.3.0", "js-base64": "^3.7.4", - "maplibre-gl": "4.7.1", + "maplibre-gl": "^5.0.0-pre.7", "uuid": "^9.0.0" } }, @@ -1086,12 +1094,14 @@ "node_modules/@types/geojson": { "version": "7946.0.14", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", - "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "license": "MIT" }, "node_modules/@types/geojson-vt": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "license": "MIT", "dependencies": { "@types/geojson": "*" } @@ -1099,12 +1109,14 @@ "node_modules/@types/mapbox__point-geometry": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", - "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==" + "integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==", + "license": "MIT" }, "node_modules/@types/mapbox__vector-tile": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz", "integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==", + "license": "MIT", "dependencies": { "@types/geojson": "*", "@types/mapbox__point-geometry": "*", @@ -1123,7 +1135,8 @@ "node_modules/@types/pbf": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", - "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==" + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", + "license": "MIT" }, "node_modules/@types/stats.js": { "version": "0.17.3", @@ -1135,6 +1148,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", "dependencies": { "@types/geojson": "*" } @@ -1346,22 +1360,6 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1378,23 +1376,6 @@ "concat-map": "0.0.1" } }, - "node_modules/bytewise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", - "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", - "dependencies": { - "bytewise-core": "^1.2.2", - "typewise": "^1.0.3" - } - }, - "node_modules/bytewise-core": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", - "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", - "dependencies": { - "typewise-core": "^1.2" - } - }, "node_modules/compare-versions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", @@ -1445,7 +1426,8 @@ "node_modules/earcut": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", - "integrity": "sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==" + "integrity": "sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg==", + "license": "ISC" }, "node_modules/entities": { "version": "4.5.0", @@ -1511,17 +1493,6 @@ "node": ">=0.8.x" } }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1574,12 +1545,14 @@ "node_modules/geojson-vt": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.2.tgz", - "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==" + "integrity": "sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A==", + "license": "ISC" }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -1587,23 +1560,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/gl-matrix": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", - "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==", + "license": "MIT" }, "node_modules/global-prefix": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", "integrity": "sha512-w0Uf9Y9/nyHinEk5vMJKRie+wa4kR5hmDbEhGGds/kG1PwGLLHKRoNMeJOyCQjjBkANlnScqgzcFwGHgmgLkVA==", + "license": "MIT", "dependencies": { "ini": "^4.1.3", "kind-of": "^6.0.3", @@ -1666,7 +1633,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/import-lazy": { "version": "4.0.0", @@ -1681,6 +1649,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -1700,41 +1669,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", "engines": { "node": ">=16" } }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jju": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", @@ -1755,7 +1698,8 @@ "node_modules/json-stringify-pretty-compact": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", - "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==" + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" }, "node_modules/jsonfile": { "version": "4.0.0", @@ -1769,12 +1713,14 @@ "node_modules/kdbush": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", - "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==" + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1825,9 +1771,10 @@ } }, "node_modules/maplibre-gl": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-4.7.1.tgz", - "integrity": "sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==", + "version": "5.0.0-pre.8", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.0.0-pre.8.tgz", + "integrity": "sha512-xZT1cTTdFgqog8sfGzvZY8vFHodWxBmrsWwr73D5SbKtQFReXNdlyvRnYVGhzWpLnsQsOVLtmow4WO8fRWjGQQ==", + "license": "BSD-3-Clause", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/jsonlint-lines-primitives": "^2.0.2", @@ -1836,7 +1783,7 @@ "@mapbox/unitbezier": "^0.0.1", "@mapbox/vector-tile": "^1.3.1", "@mapbox/whoots-js": "^3.1.0", - "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@maplibre/maplibre-gl-style-spec": "^22.0.0", "@types/geojson": "^7946.0.14", "@types/geojson-vt": "3.2.5", "@types/mapbox__point-geometry": "^0.1.4", @@ -1864,11 +1811,6 @@ "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" } }, - "node_modules/maplibre-gl/node_modules/quickselect": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", - "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==" - }, "node_modules/meshoptimizer": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", @@ -1891,6 +1833,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1922,7 +1865,8 @@ "node_modules/murmurhash-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", - "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.7", @@ -1964,6 +1908,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "license": "BSD-3-Clause", "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" @@ -2032,12 +1977,14 @@ "node_modules/potpack": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", - "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==" + "integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==", + "license": "ISC" }, "node_modules/protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", - "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==" + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", @@ -2052,6 +1999,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz", "integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -2060,9 +2008,10 @@ } }, "node_modules/quickselect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", - "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" }, "node_modules/require-from-string": { "version": "2.0.2", @@ -2094,6 +2043,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", "dependencies": { "protocol-buffers-schema": "^3.3.1" } @@ -2136,7 +2086,8 @@ "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" }, "node_modules/semver": { "version": "7.5.4", @@ -2165,52 +2116,6 @@ "node": ">=10" } }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sort-asc": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", - "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sort-desc": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", - "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sort-object": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", - "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", - "dependencies": { - "bytewise": "^1.1.0", - "get-value": "^2.0.2", - "is-extendable": "^0.1.1", - "sort-asc": "^0.2.0", - "sort-desc": "^0.2.0", - "union-value": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2229,40 +2134,6 @@ "node": ">=0.10.0" } }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -2294,6 +2165,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", "dependencies": { "kdbush": "^4.0.2" } @@ -2333,7 +2205,8 @@ "node_modules/tinyqueue": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", - "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -2357,19 +2230,6 @@ "node": ">=14.17" } }, - "node_modules/typewise": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", - "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", - "dependencies": { - "typewise-core": "^1.2.0" - } - }, - "node_modules/typewise-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", - "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" - }, "node_modules/ufo": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", @@ -2382,20 +2242,6 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -2525,6 +2371,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "license": "MIT", "dependencies": { "@mapbox/point-geometry": "0.1.0", "@mapbox/vector-tile": "^1.3.1", @@ -2552,6 +2399,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, diff --git a/package.json b/package.json index 4b8dd3b..3bdc0bd 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "vite-plugin-dts": "^4.1.0" }, "dependencies": { - "@maptiler/sdk": "^2.4.0", + "@maptiler/sdk": "^3.0.0-rc.2", "lru-cache": "^11.0.1", "three": "^0.168.0" } From 3cf451c4ebd22632f3dfc2715e66dd52a2604d5d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Nov 2024 18:56:59 +0100 Subject: [PATCH 04/17] Update 3D layer to sdk 3 --- src/Layer3D.ts | 357 +++++++++++++++------------------------------ src/maptiler-3d.ts | 1 + src/types.ts | 19 +++ src/utils.ts | 36 +++++ 4 files changed, 175 insertions(+), 238 deletions(-) create mode 100644 src/types.ts create mode 100644 src/utils.ts diff --git a/src/Layer3D.ts b/src/Layer3D.ts index c9e0b1f..1e00695 100644 --- a/src/Layer3D.ts +++ b/src/Layer3D.ts @@ -1,22 +1,14 @@ -import { - type CustomLayerInterface, - type Map as MapSDK, - MercatorCoordinate, - type LngLatLike, - type CustomRenderMethodInput, - LngLat, -} from "@maptiler/sdk"; +import { getVersion, LngLat } from "@maptiler/sdk"; +import type { LngLatLike, CustomLayerInterface, CustomRenderMethodInput, Map as MapSDK } from "@maptiler/sdk"; import { Camera, Matrix4, + Mesh, Scene, WebGLRenderer, - Vector3, - type Mesh, type Group, type Object3D, - Quaternion, PointLight, type ColorRepresentation, AmbientLight, @@ -27,6 +19,9 @@ import { import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +import { getTransformationMatrix, isPointLight } from "./utils"; +import { SourceOrientation } from "./types"; + /** * The altitude of a mesh can be relative to the ground surface, or to the mean sea level */ @@ -42,26 +37,6 @@ export enum AltitudeReference { MEAN_SEA_LEVEL = 2, } -/** - * Going from the original 3D space a mesh was created in, to the map 3D space (Z up, right hand) - */ -export enum SourceOrientation { - /** - * The mesh was originaly created in a 3D space that uses the x axis as the up direction - */ - X_UP = 1, - - /** - * The mesh was originaly created in a 3D space that uses the Y axis as the up direction - */ - Y_UP = 2, - - /** - * The mesh was originaly created in a 3D space that uses the Z axis as the up direction - */ - Z_UP = 3, -} - /** * Generic options that apply to both point lights and meshes */ @@ -175,27 +150,6 @@ export type SerializedPointLight = SerializedGenericItem & { decay: number; }; -type Mat4 = - | [ - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - number, - ] - | Float32Array; - export type Layer3DOptions = { /** * Bellow this zoom level, the meshes are not visible @@ -229,37 +183,40 @@ export type Layer3DOptions = { export type Item3D = { id: string; - mercatorCoord: MercatorCoordinate; + mesh: Mesh | Group | Object3D | null; lngLat: LngLat; altitude: number; + scale: number; heading: number; sourceOrientation: SourceOrientation; - mesh: Mesh | Group | Object3D | null; altitudeReference: AltitudeReference; - isLight: boolean; url: string | null; opacity: number; pointSize: number; wireframe: boolean; + additionalTransformationMatrix: Matrix4; }; export class Layer3D implements CustomLayerInterface { - public id: string; + public readonly id: string; public readonly type = "custom"; - public renderingMode: "2d" | "3d" = "3d"; + public readonly renderingMode: "2d" | "3d" = "3d"; + private map!: MapSDK; + public minZoom: number; public maxZoom: number; - private scene!: Scene; - private renderer!: WebGLRenderer; - private map!: MapSDK; - private camera!: Camera; private antialias: boolean; - private items3D = new Map(); - private sceneOrigin: LngLat | null = null; - private sceneOriginMercator: MercatorCoordinate | null = null; - private ambientLight!: AmbientLight; + + private renderer!: WebGLRenderer; + private readonly scene: Scene; + private readonly camera: Camera; + private readonly ambientLight: AmbientLight; + + private readonly items3D = new Map(); constructor(id: string, options: Layer3DOptions = {}) { + console.log("[maptiler-3d-js]", "Using MapTiler SDK JS version:", getVersion()); + this.type = "custom"; this.id = id; this.minZoom = options.minZoom ?? 0; @@ -268,6 +225,7 @@ export class Layer3D implements CustomLayerInterface { this.camera = new Camera(); this.camera.matrixWorldAutoUpdate = false; + this.scene = new Scene(); this.ambientLight = new AmbientLight(options.ambientLightColor ?? 0xffffff, options.ambientLightIntensity ?? 0.5); @@ -288,10 +246,12 @@ export class Layer3D implements CustomLayerInterface { */ onAdd?(map: MapSDK, gl: WebGL2RenderingContext): void { this.map = map; + this.renderer = new WebGLRenderer({ canvas: map.getCanvas(), context: gl, antialias: this.antialias, + powerPreference: "high-performance", }); this.renderer.autoClear = false; @@ -308,27 +268,59 @@ export class Layer3D implements CustomLayerInterface { /** * Automaticaly called by the rendering engine. (should not be called manually) */ - render(_gl: WebGLRenderingContext | WebGL2RenderingContext, matrix: Mat4, _options: CustomRenderMethodInput) { - if (!this.isInZoomRange()) return; - if (this.items3D.size === 0) return; + render(_gl: WebGLRenderingContext | WebGL2RenderingContext, options: CustomRenderMethodInput) { + if (this.isInZoomRange() === false) { + return; + } + + if (this.items3D.size === 0) { + return; + } const mapCenter = this.map.getCenter(); - this.sceneOrigin = new LngLat(mapCenter.lng + 0.01, mapCenter.lat + 0.01); - // this.sceneOrigin = new LngLat(mapCenter.lng, mapCenter.lat) - const offsetFromCenterElevation = this.map.queryTerrainElevation(this.sceneOrigin) || 0; - this.sceneOriginMercator = MercatorCoordinate.fromLngLat(this.sceneOrigin, offsetFromCenterElevation); + const sceneOrigin = new LngLat(mapCenter.lng, mapCenter.lat); + const sceneElevation = this.map.queryTerrainElevation(sceneOrigin) || 0; + const sceneMatrixData = this.map.transform.getMatrixForModel(sceneOrigin, sceneElevation); + const sceneMatrix = new Matrix4().fromArray(sceneMatrixData); + const sceneInverseMatrix = sceneMatrix.clone().invert(); - // Adjust all the meshes and light according to the relative center of the scene - this.reposition(); + for (const [_, item] of this.items3D) { + const model = item.mesh; - const sceneScale = this.sceneOriginMercator.meterInMercatorCoordinateUnits(); + if (model) { + const itemElevation = this.map.queryTerrainElevation(item.lngLat) || 0; + const modelOrigin = item.lngLat; + let modelAltitude = item.altitude; + + if (item.altitudeReference === AltitudeReference.GROUND) { + modelAltitude += itemElevation; + } + + const modelMatrixData = this.map.transform.getMatrixForModel(modelOrigin, modelAltitude); + const modelMatrix = new Matrix4() + .fromArray(modelMatrixData) + .multiply(item.additionalTransformationMatrix) + .premultiply(sceneInverseMatrix); + + model.matrix = modelMatrix; + } + } + + let defaultProjectionData = options.defaultProjectionData; + + if ("_mercatorTransform" in this.map.transform === true) { + defaultProjectionData = + options.defaultProjectionData.projectionTransition === 1 + ? options.defaultProjectionData + : // @ts-expect-error - Accessing private properties: `_mercatorTransform` + this.map.transform._mercatorTransform.getProjectionDataForCustomLayer(); + } + + const matrix = defaultProjectionData.mainMatrix; const m = new Matrix4().fromArray(matrix); - const l = new Matrix4() - .makeTranslation(this.sceneOriginMercator.x, this.sceneOriginMercator.y, this.sceneOriginMercator.z) - .scale(new Vector3(sceneScale, -sceneScale, sceneScale)); - this.camera.projectionMatrix = m.multiply(l); + this.camera.projectionMatrix = m.clone().multiply(sceneMatrix); this.renderer.resetState(); this.renderer.render(this.scene, this.camera); } @@ -346,34 +338,6 @@ export class Layer3D implements CustomLayerInterface { } } - /** - * Adjust the position of all meshes and light relatively to the center of the scene - */ - private reposition() { - if (!this.sceneOrigin || !this.sceneOriginMercator) return; - const terrainExag = this.map.getTerrainExaggeration(); - const sceneElevation = this.map.queryTerrainElevation(this.sceneOrigin) || 0; - const targetElevation = this.map.getCameraTargetElevation(); - - for (const [_itemId, item] of this.items3D) { - // Get the elevation of the terrain at the location of the item - const itemElevationAtPosition = this.map.queryTerrainElevation(item.lngLat) || 0; - - let itemUpShift = itemElevationAtPosition - sceneElevation + item.altitude; - - if (item.altitudeReference === AltitudeReference.MEAN_SEA_LEVEL) { - const actualItemAltitude = targetElevation + itemElevationAtPosition; - itemUpShift -= actualItemAltitude / terrainExag; - } - - const { dEastMeter: itemEast, dNorthMeter: itemNorth } = calculateDistanceMercatorToMeters( - this.sceneOriginMercator, - item.mercatorCoord, - ); - item.mesh?.position.set(itemEast, itemNorth, itemUpShift); - } - } - /** * Add an existing mesh to the map, with options. */ @@ -381,14 +345,12 @@ export class Layer3D implements CustomLayerInterface { this.throwUniqueID(id); const sourceOrientation = options.sourceOrientation ?? SourceOrientation.Y_UP; - const sourceOrientationQuaternion = sourceOrientationToQuaternion(sourceOrientation); const altitude = options.altitude ?? 0; const lngLat = options.lngLat ?? [0, 0]; - const mercatorCoord = MercatorCoordinate.fromLngLat(lngLat, altitude); const heading = options.heading ?? 0; - const headingQuaternion = headingToQuaternion(heading); const visible = options.visible ?? true; const opacity = options.opacity ?? 1; + const scale = options.scale ?? 1; const pointSize = options.pointSize ?? 1; const wireframe = options.wireframe ?? false; @@ -396,10 +358,6 @@ export class Layer3D implements CustomLayerInterface { this.setMeshOpacity(mesh, opacity, false); } - if (options.scale) { - mesh.scale.set(options.scale, options.scale, options.scale); - } - if ("pointSize" in options) { this.setMeshPointSize(mesh, pointSize); } @@ -408,72 +366,33 @@ export class Layer3D implements CustomLayerInterface { this.setMeshWireframe(mesh, wireframe); } - mesh.visible = visible; - mesh.setRotationFromQuaternion(headingQuaternion.multiply(sourceOrientationQuaternion)); - this.scene.add(mesh); + const additionalTransformationMatrix = getTransformationMatrix(scale, heading, sourceOrientation); const item: Item3D = { id, - mercatorCoord, lngLat: LngLat.convert(lngLat), altitude, + scale, sourceOrientation, heading, mesh, altitudeReference: options.altitudeReference ?? AltitudeReference.GROUND, - isLight: "isLight" in mesh && mesh.isLight === true, url: mesh.userData._originalUrl ?? null, opacity: opacity, pointSize: pointSize, wireframe: wireframe, + additionalTransformationMatrix, }; this.items3D.set(id, item); + + mesh.matrixAutoUpdate = false; + mesh.visible = visible; + this.scene.add(mesh); + this.map.triggerRepaint(); } - /** - * Creates a payload that serializes an item (point light or mesh) - */ - // private serializeItem(id: string): SerializedMesh | SerializedPointLight { - // const item = this.items3D.get(id); - // if (!item) throw new Error(`No item with ID ${id}.`); - // if (!item.mesh) throw new Error(`The item with ID ${id} exists but does not contain any mesh object.`); - // if (!item.mesh.userData._originalUrl) throw new Error(`The mesh of the item ${id} was not loaded from a URL.`); - - // if (item.isLight) { - // const mesh = (item.mesh as PointLight); - - // return { - // id: item.id, - // lngLat: item.lngLat.toArray(), - // altitude: item.altitude, - // altitudeReference: item.altitudeReference, - // visible: item.mesh.visible, - // sourceOrientation: item.sourceOrientation, - // heading: item.heading, - // isLight: item.isLight, - // color: mesh.color.getHexString(), - // intensity: mesh.intensity, - // decay: mesh.decay, - // } as SerializedPointLight - - // } - - // return { - // id: item.id, - // lngLat: item.lngLat.toArray(), - // altitude: item.altitude, - // altitudeReference: item.altitudeReference, - // visible: item.mesh.visible, - // sourceOrientation: item.sourceOrientation, - // scale: item.mesh.scale.x, - // heading: item.heading, - // isLight: item.isLight, - // url: item.url, - // } as SerializedMesh; - // } - /** * Modify an existing mesh. The provided options will overwrite * their current state, the omited ones will remain the same. @@ -483,49 +402,36 @@ export class Layer3D implements CustomLayerInterface { if (!item) return; if (!item.mesh) return; - if (typeof options.scale === "number") { - item?.mesh?.scale.set(options.scale, options.scale, options.scale); - } + let isTransformNeedUpdate = false; - let adjustMercator = false; - if (typeof options.altitude === "number") { - item.altitude = options.altitude; - adjustMercator = true; + if (typeof options.visible === "boolean") { + item.mesh.visible = options.visible; } if ("lngLat" in options) { item.lngLat = LngLat.convert(options.lngLat as LngLatLike); - adjustMercator = true; - } - - if (typeof options.altitudeReference === "number") { - item.altitudeReference = options.altitudeReference; } - if (adjustMercator) { - item.mercatorCoord = MercatorCoordinate.fromLngLat(item.lngLat, item.altitude); + if (typeof options.scale === "number") { + item.scale = options.scale; + isTransformNeedUpdate = true; } - if (typeof options.visible === "boolean") { - item.mesh.visible = options.visible; + if (typeof options.altitude === "number") { + item.altitude = options.altitude; } - let quaternionNeedsUpdate = false; - - if ("altitudeReference" in options) { - item.altitudeReference = options.altitudeReference as AltitudeReference; - quaternionNeedsUpdate = true; + if (typeof options.altitudeReference === "number") { + item.altitudeReference = options.altitudeReference; } if (typeof options.heading === "number") { - quaternionNeedsUpdate = true; item.heading = options.heading; + isTransformNeedUpdate = true; } - - if (quaternionNeedsUpdate) { - const sourceOrientationQuaternion = sourceOrientationToQuaternion(item.sourceOrientation); - const headingQuaternion = headingToQuaternion(item.heading); - item.mesh.setRotationFromQuaternion(headingQuaternion.multiply(sourceOrientationQuaternion)); + + if (isTransformNeedUpdate === true) { + item.additionalTransformationMatrix = getTransformationMatrix(item.scale, item.heading, item.sourceOrientation); } if (typeof options.opacity === "number") { @@ -555,62 +461,68 @@ export class Layer3D implements CustomLayerInterface { // Cloning the source item options and overwriting some with the provided options const cloneOptions: MeshOptions = { - lngLat: sourceItem.lngLat, + lngLat: new LngLat(sourceItem.lngLat.lng, sourceItem.lngLat.lat), altitude: sourceItem.altitude, altitudeReference: sourceItem.altitudeReference, visible: sourceItem.mesh.visible, sourceOrientation: sourceItem.sourceOrientation, - scale: sourceItem.mesh.scale.x, + scale: sourceItem.scale, heading: sourceItem.heading, ...options, }; - this.addMesh(id, sourceItem.mesh.clone(), cloneOptions); + const clonedObject = sourceItem.mesh.clone(true); + + clonedObject.traverse((child) => { + if (child instanceof Mesh) { + if (Array.isArray(child.material)) { + child.material = child.material.map((mat) => mat.clone()); + } else { + child.material = child.material.clone(); + } + } + }); + + this.addMesh(id, clonedObject, cloneOptions); } modifyPointLight(id: string, options: PointLightOptions) { const item = this.items3D.get(id); - if (!item) return; - if (!item.mesh) return; - // Not a light? - if (!("isLight" in item.mesh && item.mesh.isLight === true)) return; + if (item === undefined) { + return; + } - const mesh = item.mesh as PointLight; + if (item.mesh === null || isPointLight(item.mesh) === false) { + return; + } if ("decay" in options && typeof options.decay === "number") { - mesh.decay = options.decay; + item.mesh.decay = options.decay; } if ("intensity" in options && typeof options.intensity === "number") { - mesh.intensity = options.intensity; + item.mesh.intensity = options.intensity; } if ("color" in options) { - mesh.color.set(new Color(options.color)); // TODO: not optimal to call a constructor every time + item.mesh.color.set(new Color(options.color)); // TODO: not optimal to call a constructor every time } - let adjustMercator = false; if ("altitude" in options && typeof options.altitude === "number") { item.altitude = options.altitude; - adjustMercator = true; } if ("lngLat" in options) { item.lngLat = LngLat.convert(options.lngLat as LngLatLike); - adjustMercator = true; } if ("altitudeReference" in options && typeof options.altitudeReference === "number") { item.altitudeReference = options.altitudeReference; } - if (adjustMercator) { - item.mercatorCoord = MercatorCoordinate.fromLngLat(item.lngLat, item.altitude); - } - if ("visible" in options && typeof options.visible === "boolean") { - mesh.visible = options.visible; + item.mesh.visible = options.visible; } this.map.triggerRepaint(); @@ -768,34 +680,3 @@ export class Layer3D implements CustomLayerInterface { } } } - -function calculateDistanceMercatorToMeters( - from: MercatorCoordinate, - to: MercatorCoordinate, -): { dEastMeter: number; dNorthMeter: number } { - const mercatorPerMeter = from.meterInMercatorCoordinateUnits(); - // mercator x: 0=west, 1=east - const dEast = to.x - from.x; - const dEastMeter = dEast / mercatorPerMeter; - // mercator y: 0=north, 1=south - const dNorth = from.y - to.y; - const dNorthMeter = dNorth / mercatorPerMeter; - return { dEastMeter, dNorthMeter }; -} - -function sourceOrientationToQuaternion(so: SourceOrientation | undefined): Quaternion { - // Most models and 3D environments are Y up (right hand), so we use this as a default - const yUp = new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 2); - switch (so) { - case SourceOrientation.X_UP: - return new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), -Math.PI / 2); - case SourceOrientation.Z_UP: - return new Quaternion(); - default: - return yUp; - } -} - -function headingToQuaternion(heading: number): Quaternion { - return new Quaternion().setFromAxisAngle(new Vector3(0, 0, 1), (-heading * Math.PI) / 180); -} diff --git a/src/maptiler-3d.ts b/src/maptiler-3d.ts index 2a9b54d..ca2b970 100644 --- a/src/maptiler-3d.ts +++ b/src/maptiler-3d.ts @@ -1 +1,2 @@ export * from "./Layer3D"; +export * from "./types"; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..403f425 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,19 @@ +/** + * Going from the original 3D space a mesh was created in, to the map 3D space (Z up, right hand) + */ +export enum SourceOrientation { + /** + * The mesh was originaly created in a 3D space that uses the x axis as the up direction + */ + X_UP = 1, + + /** + * The mesh was originaly created in a 3D space that uses the Y axis as the up direction + */ + Y_UP = 2, + + /** + * The mesh was originaly created in a 3D space that uses the Z axis as the up direction + */ + Z_UP = 3, +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..9993dd0 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,36 @@ +import { Quaternion, Vector3, Matrix4, type Object3D, type PointLight, MathUtils } from "three"; +import { SourceOrientation } from "./types"; + +const isPointLight = (object3D: Object3D): object3D is PointLight => { + return "isPointLight" in object3D && object3D.isPointLight === true; +}; + +const sourceOrientationToQuaternion = (so: SourceOrientation | undefined): Quaternion => { + const quaternion = new Quaternion(); + + switch (so) { + case SourceOrientation.X_UP: + quaternion.setFromAxisAngle(new Vector3(0, 0, 1), -Math.PI / 2); + break; + + case SourceOrientation.Y_UP: + quaternion.identity(); + break; + + case SourceOrientation.Z_UP: + quaternion.setFromAxisAngle(new Vector3(1, 0, 0), -Math.PI / 2); + break; + } + + return quaternion; +}; + +const getTransformationMatrix = (scale: number, heading: number, orientation: SourceOrientation): Matrix4 => { + const scaleMatrix = new Matrix4().makeScale(scale, scale, scale); + const orientationMatrix = new Matrix4().makeRotationFromQuaternion(sourceOrientationToQuaternion(orientation)); + const headingMatrix = new Matrix4().makeRotationY(MathUtils.degToRad(-heading)); + + return new Matrix4().multiply(scaleMatrix).multiply(orientationMatrix).multiply(headingMatrix); +}; + +export { isPointLight, getTransformationMatrix }; From 1192ab9e56950fbd9680955999de1a996e258658 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Nov 2024 18:57:19 +0100 Subject: [PATCH 05/17] Add example --- demos/mountains.html | 49 ++++++++++++++ index.html | 31 +++++++++ src/demos/mountains.ts | 147 +++++++++++++++++++++++++++++++++++++++++ src/vite-env.d.ts | 1 + 4 files changed, 228 insertions(+) create mode 100644 demos/mountains.html create mode 100644 index.html create mode 100644 src/demos/mountains.ts create mode 100644 src/vite-env.d.ts diff --git a/demos/mountains.html b/demos/mountains.html new file mode 100644 index 0000000..31fbe5d --- /dev/null +++ b/demos/mountains.html @@ -0,0 +1,49 @@ + + + + MapTiler 3D Models + + + + + + + + + +
+ + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..6830180 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + + + Document + + + + +

MapTiler 3D Examples

+ + + diff --git a/src/demos/mountains.ts b/src/demos/mountains.ts new file mode 100644 index 0000000..ba86747 --- /dev/null +++ b/src/demos/mountains.ts @@ -0,0 +1,147 @@ +import * as maptilersdk from "@maptiler/sdk"; +import * as maptiler3d from "../maptiler-3d"; + +import "@maptiler/sdk/dist/maptiler-sdk.css"; + +const apiKey = new URLSearchParams(location.search).get("key") ?? import.meta.env.VITE_MAPTILER_API_KEY; + +if (apiKey === null || apiKey === undefined || apiKey === "") { + alert("Missing URL param with MapTiler API key ?key=XXXXX"); +} + +maptilersdk.config.apiKey = apiKey; + +const mapElement = document.getElementById("map"); + +if (mapElement === null) { + throw new Error("Map element not found"); +} + +const map = new maptilersdk.Map({ + container: mapElement, + style: maptilersdk.MapStyle.OUTDOOR.DEFAULT, + zoom: 14, + center: [86.922623, 27.986065], + antialias: false, + projection: "globe", + maxPitch: 89, + terrain: true, + terrainExaggeration: 1.0223, + terrainControl: true, + maptilerLogo: true, +}); + +const layer3D = new maptiler3d.Layer3D("custom-3D-layer"); + + +const TEMPLATE_OBJECT_ID = "template-object"; +let currentObjectID: string | undefined = undefined; + +const state: maptiler3d.MeshOptions = { + scale: 1000, + altitude: 8749, + heading: 0, + altitudeReference: maptiler3d.AltitudeReference.MEAN_SEA_LEVEL, + wireframe: true, + // https://www.peakbagger.com/peak.aspx?pid=18716 + lngLat: { lng: 86.925812, lat: 27.985087 }, +}; + +const createUI = () => { + // @ts-expect-error - lil added as script in .html file + const gui = new lil.GUI({ width: 400 }); + + const actions = { + addObject: () => { + currentObjectID = `object-${Math.random()}`; + layer3D.cloneMesh(TEMPLATE_OBJECT_ID, currentObjectID, {}); + layer3D.modifyMesh(currentObjectID, { wireframe: false }); + }, + }; + + gui + .add(state, "heading", 0, 360, 0.1) + .onChange((heading: number) => { + layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { heading }); + }) + .name("Heading"); + gui + .add(state, "altitudeReference", { GROUND: 1, MEAN_SEA_LEVEL: 2 }) + .onChange((value: string) => { + const altitudeReference = + value === "MEAN_SEA_LEVEL" ? maptiler3d.AltitudeReference.MEAN_SEA_LEVEL : maptiler3d.AltitudeReference.GROUND; + + layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { altitudeReference }); + }) + .name("Altitude Reference"); + gui + .add(state, "altitude", 0, 10000, 1) + .onChange((altitude: number) => { + layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { altitude }); + }) + .name("Altitude"); + gui + .add(state, "scale", 0, 10000, 1) + .onChange((scale: number) => { + layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { scale }); + }) + .name("Scale"); + gui.add(actions, "addObject").name("Add Object"); + + const toggleProjectionButton = gui + .add( + { + changeProjection: () => { + const currentProjection = map.getProjection().type; + + if (currentProjection === "globe") { + map.setProjection({ type: "mercator" }); + } else if (currentProjection === "mercator") { + toggleProjectionButton.name("Activate Globe"); + map.setProjection({ type: "globe" }); + toggleProjectionButton.name("Activate Mercator"); + } else { + throw new Error(`Unsupported projection: ${currentProjection}`); + } + }, + }, + "changeProjection", + ) + .name("Activate Mercator"); + + return gui; +}; + +createUI(); + +(async () => { + await map.onReadyAsync(); + + map.addLayer(layer3D); + + layer3D.setAmbientLight({ intensity: 2 }); + layer3D.addPointLight("point-light", { intensity: 30 }); + layer3D.modifyPointLight("point-light", { intensity: 100 }); + + await layer3D.addMeshFromURL(TEMPLATE_OBJECT_ID, "models/position-indicator--y-up.glb", { + ...state, + sourceOrientation: maptiler3d.SourceOrientation.Y_UP, + }); + + map.on("mousemove", (e) => { + if (currentObjectID === undefined) { + return; + } + + layer3D.modifyMesh(currentObjectID, { lngLat: e.lngLat }); + }); + + map.on("click", (e) => { + if (currentObjectID === undefined) { + return; + } + + layer3D.modifyMesh(currentObjectID, { lngLat: e.lngLat }); + currentObjectID = undefined; + }); +})(); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// From c62ee815ce4e100ada4612f094f6c578d9dfe137 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Nov 2024 19:18:02 +0100 Subject: [PATCH 06/17] Add comments --- src/Layer3D.ts | 22 ++++++++++++++++++++-- src/demos/mountains.ts | 4 ++++ src/utils.ts | 3 +++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Layer3D.ts b/src/Layer3D.ts index 1e00695..60cbf0a 100644 --- a/src/Layer3D.ts +++ b/src/Layer3D.ts @@ -278,8 +278,13 @@ export class Layer3D implements CustomLayerInterface { } const mapCenter = this.map.getCenter(); + const sceneOrigin = new LngLat(mapCenter.lng, mapCenter.lat); const sceneElevation = this.map.queryTerrainElevation(sceneOrigin) || 0; + + /** + * `getMatrixForModel` returns transformation matrix for current active projection, which can be used to set correct position and orientation of the model. + */ const sceneMatrixData = this.map.transform.getMatrixForModel(sceneOrigin, sceneElevation); const sceneMatrix = new Matrix4().fromArray(sceneMatrixData); const sceneInverseMatrix = sceneMatrix.clone().invert(); @@ -287,7 +292,7 @@ export class Layer3D implements CustomLayerInterface { for (const [_, item] of this.items3D) { const model = item.mesh; - if (model) { + if (model !== null) { const itemElevation = this.map.queryTerrainElevation(item.lngLat) || 0; const modelOrigin = item.lngLat; @@ -297,6 +302,10 @@ export class Layer3D implements CustomLayerInterface { modelAltitude += itemElevation; } + /** + * We are using transformation of scene origin and model for finding relative transofmration of the model to avoid precision issues. + * Center of the map is used as an origin of the scene. + */ const modelMatrixData = this.map.transform.getMatrixForModel(modelOrigin, modelAltitude); const modelMatrix = new Matrix4() .fromArray(modelMatrixData) @@ -309,6 +318,11 @@ export class Layer3D implements CustomLayerInterface { let defaultProjectionData = options.defaultProjectionData; + /** + * Possible a bug. + * The `defaultProjectionData` seems to be incorrect when the globe projection is used and the zoom is high (and projection is changing to mercator). + * Waiting for help: https://github.com/maplibre/maplibre-gl-js/issues/5117 + */ if ("_mercatorTransform" in this.map.transform === true) { defaultProjectionData = options.defaultProjectionData.projectionTransition === 1 @@ -317,6 +331,10 @@ export class Layer3D implements CustomLayerInterface { this.map.transform._mercatorTransform.getProjectionDataForCustomLayer(); } + /** + * https://github.com/maplibre/maplibre-gl-js/blob/v5.0.0-pre.8/test/examples/globe-3d-model.html#L130-L143 + * `mainMatrix` contains the transformation matrix for the current active projection. + */ const matrix = defaultProjectionData.mainMatrix; const m = new Matrix4().fromArray(matrix); @@ -429,7 +447,7 @@ export class Layer3D implements CustomLayerInterface { item.heading = options.heading; isTransformNeedUpdate = true; } - + if (isTransformNeedUpdate === true) { item.additionalTransformationMatrix = getTransformationMatrix(item.scale, item.heading, item.sourceOrientation); } diff --git a/src/demos/mountains.ts b/src/demos/mountains.ts index ba86747..ae30340 100644 --- a/src/demos/mountains.ts +++ b/src/demos/mountains.ts @@ -1,3 +1,7 @@ +/** + * This will be transformed into a script in `mountains.html` file when sdk v3 will be on CDN. + */ + import * as maptilersdk from "@maptiler/sdk"; import * as maptiler3d from "../maptiler-3d"; diff --git a/src/utils.ts b/src/utils.ts index 9993dd0..13edd04 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -25,6 +25,9 @@ const sourceOrientationToQuaternion = (so: SourceOrientation | undefined): Quate return quaternion; }; +/** + * Helper function to create a transformation matrix for model and we can apply in single operation. + */ const getTransformationMatrix = (scale: number, heading: number, orientation: SourceOrientation): Matrix4 => { const scaleMatrix = new Matrix4().makeScale(scale, scale, scale); const orientationMatrix = new Matrix4().makeRotationFromQuaternion(sourceOrientationToQuaternion(orientation)); From 7b49263f8ea482d8b427134dba5e559e7e3f6b09 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Nov 2024 19:24:28 +0100 Subject: [PATCH 07/17] Add offset --- src/Layer3D.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Layer3D.ts b/src/Layer3D.ts index 60cbf0a..807e481 100644 --- a/src/Layer3D.ts +++ b/src/Layer3D.ts @@ -197,6 +197,11 @@ export type Item3D = { additionalTransformationMatrix: Matrix4; }; +// An epsilon to make sure the reference anchor point is not exactly at the center of the viewport, but still very close. +// This is because ThreeJS light shaders were messed up with reference point in the center. +// This issue is only happening because we are doing the projection matrix trick, otherwise we wouldn't bother with epsilon +const EPSILON = 0.01; + export class Layer3D implements CustomLayerInterface { public readonly id: string; public readonly type = "custom"; @@ -279,7 +284,7 @@ export class Layer3D implements CustomLayerInterface { const mapCenter = this.map.getCenter(); - const sceneOrigin = new LngLat(mapCenter.lng, mapCenter.lat); + const sceneOrigin = new LngLat(mapCenter.lng + EPSILON, mapCenter.lat + EPSILON); const sceneElevation = this.map.queryTerrainElevation(sceneOrigin) || 0; /** @@ -338,7 +343,7 @@ export class Layer3D implements CustomLayerInterface { const matrix = defaultProjectionData.mainMatrix; const m = new Matrix4().fromArray(matrix); - this.camera.projectionMatrix = m.clone().multiply(sceneMatrix); + this.camera.projectionMatrix = m.multiply(sceneMatrix); this.renderer.resetState(); this.renderer.render(this.scene, this.camera); } From 8208c2df49ae265571572fb30bba31e55ce77247 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Nov 2024 19:27:17 +0100 Subject: [PATCH 08/17] Make linter happy --- src/demos/mountains.ts | 1 - src/maptiler-3d.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/demos/mountains.ts b/src/demos/mountains.ts index ae30340..39a2591 100644 --- a/src/demos/mountains.ts +++ b/src/demos/mountains.ts @@ -37,7 +37,6 @@ const map = new maptilersdk.Map({ const layer3D = new maptiler3d.Layer3D("custom-3D-layer"); - const TEMPLATE_OBJECT_ID = "template-object"; let currentObjectID: string | undefined = undefined; diff --git a/src/maptiler-3d.ts b/src/maptiler-3d.ts index ca2b970..1bf687f 100644 --- a/src/maptiler-3d.ts +++ b/src/maptiler-3d.ts @@ -1,2 +1,2 @@ export * from "./Layer3D"; -export * from "./types"; \ No newline at end of file +export * from "./types"; From c4ab499319f5ebfc9e3bb649730d05e54528a5e2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 28 Nov 2024 14:27:41 +0100 Subject: [PATCH 09/17] Refactor mount everest example --- demos/mountains.html | 157 ++++++++++++++++++++++++++++++++++++++++- index.html | 14 ++-- src/Layer3D.ts | 46 ++++++++++-- src/demos/mountains.ts | 150 --------------------------------------- 4 files changed, 200 insertions(+), 167 deletions(-) delete mode 100644 src/demos/mountains.ts diff --git a/demos/mountains.html b/demos/mountains.html index 31fbe5d..704bdae 100644 --- a/demos/mountains.html +++ b/demos/mountains.html @@ -9,15 +9,20 @@ /> + + + + + @@ -44,6 +49,152 @@
- + + + + diff --git a/index.html b/index.html index 6830180..5caa850 100644 --- a/index.html +++ b/index.html @@ -14,18 +14,18 @@

MapTiler 3D Examples

diff --git a/src/Layer3D.ts b/src/Layer3D.ts index 807e481..54dc8c2 100644 --- a/src/Layer3D.ts +++ b/src/Layer3D.ts @@ -1,5 +1,11 @@ import { getVersion, LngLat } from "@maptiler/sdk"; -import type { LngLatLike, CustomLayerInterface, CustomRenderMethodInput, Map as MapSDK } from "@maptiler/sdk"; +import type { + LngLatLike, + CustomLayerInterface, + CustomRenderMethodInput, + Map as MapSDK, + Subscription, +} from "@maptiler/sdk"; import { Camera, @@ -195,6 +201,7 @@ export type Item3D = { pointSize: number; wireframe: boolean; additionalTransformationMatrix: Matrix4; + elevation: number; }; // An epsilon to make sure the reference anchor point is not exactly at the center of the viewport, but still very close. @@ -218,6 +225,8 @@ export class Layer3D implements CustomLayerInterface { private readonly ambientLight: AmbientLight; private readonly items3D = new Map(); + private onRemoveCallbacks: Array<()=>void> = []; + private isElevationNeedUpdate = false; constructor(id: string, options: Layer3DOptions = {}) { console.log("[maptiler-3d-js]", "Using MapTiler SDK JS version:", getVersion()); @@ -260,6 +269,16 @@ export class Layer3D implements CustomLayerInterface { }); this.renderer.autoClear = false; + + const { unsubscribe: unsubscribeOnTerrainAnimationStart } = this.map.on("terrainAnimationStart", () => { + this.isElevationNeedUpdate = true; + }); + + const { unsubscribe: unsubscribeOnTerrainAnimationStop } = this.map.on("terrainAnimationStop", () => { + this.isElevationNeedUpdate = false; + }); + + this.onRemoveCallbacks = [unsubscribeOnTerrainAnimationStart, unsubscribeOnTerrainAnimationStop]; } /** @@ -268,6 +287,12 @@ export class Layer3D implements CustomLayerInterface { onRemove?(_map: MapSDK, _gl: WebGLRenderingContext | WebGL2RenderingContext): void { this.clear(); this.renderer.dispose(); + + for (const callback of this.onRemoveCallbacks) { + callback(); + } + + this.onRemoveCallbacks = []; } /** @@ -298,20 +323,21 @@ export class Layer3D implements CustomLayerInterface { const model = item.mesh; if (model !== null) { - const itemElevation = this.map.queryTerrainElevation(item.lngLat) || 0; - const modelOrigin = item.lngLat; - let modelAltitude = item.altitude; - + if (item.altitudeReference === AltitudeReference.GROUND) { - modelAltitude += itemElevation; + if (this.isElevationNeedUpdate === true) { + item.elevation = this.map.queryTerrainElevation(item.lngLat) || 0; + } + + modelAltitude += item.elevation; } /** * We are using transformation of scene origin and model for finding relative transofmration of the model to avoid precision issues. * Center of the map is used as an origin of the scene. */ - const modelMatrixData = this.map.transform.getMatrixForModel(modelOrigin, modelAltitude); + const modelMatrixData = this.map.transform.getMatrixForModel(item.lngLat, modelAltitude); const modelMatrix = new Matrix4() .fromArray(modelMatrixData) .multiply(item.additionalTransformationMatrix) @@ -391,6 +417,9 @@ export class Layer3D implements CustomLayerInterface { const additionalTransformationMatrix = getTransformationMatrix(scale, heading, sourceOrientation); + const elevation = this.map.queryTerrainElevation(lngLat) || 0; + console.log(id, lngLat, elevation); + const item: Item3D = { id, lngLat: LngLat.convert(lngLat), @@ -405,6 +434,7 @@ export class Layer3D implements CustomLayerInterface { pointSize: pointSize, wireframe: wireframe, additionalTransformationMatrix, + elevation, }; this.items3D.set(id, item); @@ -433,6 +463,7 @@ export class Layer3D implements CustomLayerInterface { if ("lngLat" in options) { item.lngLat = LngLat.convert(options.lngLat as LngLatLike); + item.elevation = this.map.queryTerrainElevation(item.lngLat) || 0; } if (typeof options.scale === "number") { @@ -446,6 +477,7 @@ export class Layer3D implements CustomLayerInterface { if (typeof options.altitudeReference === "number") { item.altitudeReference = options.altitudeReference; + item.elevation = this.map.queryTerrainElevation(item.lngLat) || 0; } if (typeof options.heading === "number") { diff --git a/src/demos/mountains.ts b/src/demos/mountains.ts deleted file mode 100644 index 39a2591..0000000 --- a/src/demos/mountains.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * This will be transformed into a script in `mountains.html` file when sdk v3 will be on CDN. - */ - -import * as maptilersdk from "@maptiler/sdk"; -import * as maptiler3d from "../maptiler-3d"; - -import "@maptiler/sdk/dist/maptiler-sdk.css"; - -const apiKey = new URLSearchParams(location.search).get("key") ?? import.meta.env.VITE_MAPTILER_API_KEY; - -if (apiKey === null || apiKey === undefined || apiKey === "") { - alert("Missing URL param with MapTiler API key ?key=XXXXX"); -} - -maptilersdk.config.apiKey = apiKey; - -const mapElement = document.getElementById("map"); - -if (mapElement === null) { - throw new Error("Map element not found"); -} - -const map = new maptilersdk.Map({ - container: mapElement, - style: maptilersdk.MapStyle.OUTDOOR.DEFAULT, - zoom: 14, - center: [86.922623, 27.986065], - antialias: false, - projection: "globe", - maxPitch: 89, - terrain: true, - terrainExaggeration: 1.0223, - terrainControl: true, - maptilerLogo: true, -}); - -const layer3D = new maptiler3d.Layer3D("custom-3D-layer"); - -const TEMPLATE_OBJECT_ID = "template-object"; -let currentObjectID: string | undefined = undefined; - -const state: maptiler3d.MeshOptions = { - scale: 1000, - altitude: 8749, - heading: 0, - altitudeReference: maptiler3d.AltitudeReference.MEAN_SEA_LEVEL, - wireframe: true, - // https://www.peakbagger.com/peak.aspx?pid=18716 - lngLat: { lng: 86.925812, lat: 27.985087 }, -}; - -const createUI = () => { - // @ts-expect-error - lil added as script in .html file - const gui = new lil.GUI({ width: 400 }); - - const actions = { - addObject: () => { - currentObjectID = `object-${Math.random()}`; - layer3D.cloneMesh(TEMPLATE_OBJECT_ID, currentObjectID, {}); - layer3D.modifyMesh(currentObjectID, { wireframe: false }); - }, - }; - - gui - .add(state, "heading", 0, 360, 0.1) - .onChange((heading: number) => { - layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { heading }); - }) - .name("Heading"); - gui - .add(state, "altitudeReference", { GROUND: 1, MEAN_SEA_LEVEL: 2 }) - .onChange((value: string) => { - const altitudeReference = - value === "MEAN_SEA_LEVEL" ? maptiler3d.AltitudeReference.MEAN_SEA_LEVEL : maptiler3d.AltitudeReference.GROUND; - - layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { altitudeReference }); - }) - .name("Altitude Reference"); - gui - .add(state, "altitude", 0, 10000, 1) - .onChange((altitude: number) => { - layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { altitude }); - }) - .name("Altitude"); - gui - .add(state, "scale", 0, 10000, 1) - .onChange((scale: number) => { - layer3D.modifyMesh(TEMPLATE_OBJECT_ID, { scale }); - }) - .name("Scale"); - gui.add(actions, "addObject").name("Add Object"); - - const toggleProjectionButton = gui - .add( - { - changeProjection: () => { - const currentProjection = map.getProjection().type; - - if (currentProjection === "globe") { - map.setProjection({ type: "mercator" }); - } else if (currentProjection === "mercator") { - toggleProjectionButton.name("Activate Globe"); - map.setProjection({ type: "globe" }); - toggleProjectionButton.name("Activate Mercator"); - } else { - throw new Error(`Unsupported projection: ${currentProjection}`); - } - }, - }, - "changeProjection", - ) - .name("Activate Mercator"); - - return gui; -}; - -createUI(); - -(async () => { - await map.onReadyAsync(); - - map.addLayer(layer3D); - - layer3D.setAmbientLight({ intensity: 2 }); - layer3D.addPointLight("point-light", { intensity: 30 }); - layer3D.modifyPointLight("point-light", { intensity: 100 }); - - await layer3D.addMeshFromURL(TEMPLATE_OBJECT_ID, "models/position-indicator--y-up.glb", { - ...state, - sourceOrientation: maptiler3d.SourceOrientation.Y_UP, - }); - - map.on("mousemove", (e) => { - if (currentObjectID === undefined) { - return; - } - - layer3D.modifyMesh(currentObjectID, { lngLat: e.lngLat }); - }); - - map.on("click", (e) => { - if (currentObjectID === undefined) { - return; - } - - layer3D.modifyMesh(currentObjectID, { lngLat: e.lngLat }); - currentObjectID = undefined; - }); -})(); From 9feac75b75742a761c49d47ed3077fa97fdf15b9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 28 Nov 2024 14:45:22 +0100 Subject: [PATCH 10/17] Update examples to sdk v3 --- demos/flight.html | 4 ++-- demos/multi.html | 4 ++-- demos/plane.html | 4 ++-- demos/point_cad_model01.html | 4 ++-- demos/point_cloud_NMH.html | 4 ++-- demos/point_cloud_dundee.html | 4 ++-- demos/point_cloud_shore.html | 4 ++-- index.html | 15 +++++++++++---- src/Layer3D.ts | 12 ++++++++---- 9 files changed, 33 insertions(+), 22 deletions(-) diff --git a/demos/flight.html b/demos/flight.html index 0c43c9f..6b63452 100644 --- a/demos/flight.html +++ b/demos/flight.html @@ -5,8 +5,8 @@ - - + + - +
@@ -94,10 +94,10 @@ map.addLayer(layer3D); // Increasing the intensity of the ambient light - layer3D.setAmbientLight({intensity: 2}); + layer3D.setAmbientLight({ intensity: 2 }); // Adding a point light - layer3D.addPointLight("point-light", {intensity: 30}); + layer3D.addPointLight("point-light", { intensity: 30 }); const gui = new lil.GUI({ width: 400 }); @@ -105,6 +105,7 @@ // Adding a mesh of a plane. const guiObj = { + projection: "mercator", heading: 0, scale: 1, altitude: 3000, @@ -115,7 +116,7 @@ layer3D.removeMesh(originalPlaneID); }, } - + const originalPlaneID = "plane"; await layer3D.addMeshFromURL( originalPlaneID, @@ -135,48 +136,66 @@ map.on("mousemove", (e) => { if (!planeCanMove) return; - layer3D.modifyMesh(originalPlaneID, {lngLat: e.lngLat}) + layer3D.modifyMesh(originalPlaneID, { lngLat: e.lngLat }) }); map.on("click", (e) => { planeCanMove = !planeCanMove; }); - gui.add( guiObj, 'heading', 0, 360, 0.1 ) - .onChange((heading) => { - layer3D.modifyMesh(originalPlaneID, {heading}); - }); - - gui.add( guiObj, 'scale', 0.01, 5, 0.01 ) - .onChange((scale) => { - layer3D.modifyMesh(originalPlaneID, {scale}); - }); - - gui.add( guiObj, 'altitude', 0, 10000, 1 ) - .onChange((altitude) => { - layer3D.modifyMesh(originalPlaneID, {altitude}); - }); - - gui.add( guiObj, 'opacity', 0, 1) - .onChange((opacity) => { - layer3D.modifyMesh(originalPlaneID, {opacity}); - }); - - - gui.add( guiObj, 'altitudeReference', ["MEAN_SEA_LEVEL", "GROUND"]) - .onChange((altRef) => { - layer3D.modifyMesh(originalPlaneID, {altitudeReference: maptiler3d.AltitudeReference[altRef]}); - }); - - gui.add( guiObj, "wireframe" ) - .onChange((wireframe) => { - layer3D.modifyMesh(originalPlaneID, {wireframe}); - }); - - gui.add( guiObj, "removePlane" ); + gui + .add(guiObj, 'projection', ["mercator", "globe"]) + .onChange((projection) => { + switch (projection) { + case "mercator": + map.enableMercatorProjection(true); + break; + + case "globe": + map.enableGlobeProjection(true); + break; + + default: + throw new Error("Unsupported projection"); + } + }); + + gui.add(guiObj, 'heading', 0, 360, 0.1) + .onChange((heading) => { + layer3D.modifyMesh(originalPlaneID, { heading }); + }); + + gui.add(guiObj, 'scale', 0.01, 1000, 0.01) + .onChange((scale) => { + layer3D.modifyMesh(originalPlaneID, { scale }); + }); + + gui.add(guiObj, 'altitude', 0, 10000, 1) + .onChange((altitude) => { + layer3D.modifyMesh(originalPlaneID, { altitude }); + }); + + gui.add(guiObj, 'opacity', 0, 1) + .onChange((opacity) => { + layer3D.modifyMesh(originalPlaneID, { opacity }); + }); + + + gui.add(guiObj, 'altitudeReference', ["MEAN_SEA_LEVEL", "GROUND"]) + .onChange((altRef) => { + layer3D.modifyMesh(originalPlaneID, { altitudeReference: maptiler3d.AltitudeReference[altRef] }); + }); + + gui.add(guiObj, "wireframe") + .onChange((wireframe) => { + layer3D.modifyMesh(originalPlaneID, { wireframe }); + }); + + gui.add(guiObj, "removePlane"); })() - + + \ No newline at end of file diff --git a/index.html b/index.html index 73d7541..0001c3e 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,7 @@

MapTiler 3D JS Examples

Multi
  • - Terrain + Plain
  • Point Cloud - CAD-like view diff --git a/src/Layer3D.ts b/src/Layer3D.ts index e8ee6d0..a76abdd 100644 --- a/src/Layer3D.ts +++ b/src/Layer3D.ts @@ -315,6 +315,10 @@ export class Layer3D implements CustomLayerInterface { return; } + if ([0, 1].includes(options.defaultProjectionData.projectionTransition) === false) { + return; + } + const mapCenter = this.map.getCenter(); const sceneOrigin = new LngLat(mapCenter.lng + EPSILON, mapCenter.lat + EPSILON);