├── .babelrc
├── .eslintignore
├── .eslintrc.json
├── .github
├── CODEOWNERS
├── bundlewatch.config.json
├── dependabot.yml
├── sync-repo-settings.yaml
└── workflows
│ ├── bundlewatch.yml
│ ├── codeql.yml
│ ├── dependabot.yml
│ ├── docs.yml
│ ├── e2e.yml
│ ├── package.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .prettierrc
├── .releaserc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── e2e
└── README.md
├── examples
├── anchor.ts
├── basic.ts
├── config.ts
├── hemispheres.ts
├── orientation.ts
└── raycasting.ts
├── jest.config.js
├── package-lock.json
├── package.json
├── rollup.config.examples.js
├── rollup.config.js
├── src
├── __tests__
│ ├── __utils__
│ │ └── createWebGlContext.ts
│ ├── three.test.ts
│ └── util.test.ts
├── index.ts
├── three.ts
└── util.ts
├── tsconfig.examples.json
├── tsconfig.json
└── typedoc.cjs
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/env",
5 | {
6 | "targets": {
7 | "browsers": "ie>=11, > 0.25%, not dead"
8 | },
9 | "corejs": "3.6",
10 | "useBuiltIns": "usage"
11 | }
12 | ]
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | bazel-*
2 | coverage/
3 | dist/
4 | docs/
5 | lib/
6 | node_modules/
7 | public/
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended", "plugin:prettier/recommended"],
3 | "parserOptions": {
4 | "ecmaVersion": 12,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "jsx": true
8 | }
9 | },
10 | "plugins": ["jest"],
11 | "rules": {
12 | "no-var": 2,
13 | "prefer-arrow-callback": 2
14 | },
15 | "overrides": [
16 | {
17 | "files": ["*.ts", "*.tsx"],
18 | "parser": "@typescript-eslint/parser",
19 | "plugins": ["@typescript-eslint"],
20 | "extends": ["plugin:@typescript-eslint/recommended"],
21 | "rules": {
22 | "@typescript-eslint/ban-ts-comment": 0,
23 | "@typescript-eslint/ban-types": 1,
24 | "@typescript-eslint/no-empty-function": 1,
25 | "@typescript-eslint/member-ordering": 1,
26 | "@typescript-eslint/explicit-member-accessibility": [
27 | 1,
28 | {
29 | "accessibility": "explicit",
30 | "overrides": {
31 | "accessors": "explicit",
32 | "constructors": "no-public",
33 | "methods": "explicit",
34 | "properties": "explicit",
35 | "parameterProperties": "explicit"
36 | }
37 | }
38 | ]
39 | }
40 | }
41 | ],
42 | "env": {
43 | "browser": true,
44 | "node": true,
45 | "es6": true,
46 | "jest/globals": true
47 | },
48 | "globals": { "google": "readonly" }
49 | }
50 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
16 |
17 | .github/ @googlemaps/admin
18 |
--------------------------------------------------------------------------------
/.github/bundlewatch.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | {
4 | "path": "dist/index.*.js"
5 | }
6 | ],
7 | "ci": {
8 | "trackBranches": ["main"],
9 | "repoBranchBase": "main"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | version: 2
16 | updates:
17 | - package-ecosystem: "npm"
18 | directory: "/"
19 | schedule:
20 | interval: "weekly"
21 |
--------------------------------------------------------------------------------
/.github/sync-repo-settings.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # https://github.com/googleapis/repo-automation-bots/tree/main/packages/sync-repo-settings
16 |
17 | rebaseMergeAllowed: true
18 | squashMergeAllowed: true
19 | mergeCommitAllowed: false
20 | deleteBranchOnMerge: true
21 | branchProtectionRules:
22 | - pattern: main
23 | isAdminEnforced: false
24 | requiresStrictStatusChecks: false
25 | requiredStatusCheckContexts:
26 | - 'cla/google'
27 | - 'test'
28 | - 'snippet-bot check'
29 | - 'header-check'
30 | requiredApprovingReviewCount: 1
31 | requiresCodeOwnerReviews: true
32 | - pattern: master
33 | isAdminEnforced: false
34 | requiresStrictStatusChecks: false
35 | requiredStatusCheckContexts:
36 | - 'cla/google'
37 | - 'test'
38 | - 'snippet-bot check'
39 | - 'header-check'
40 | requiredApprovingReviewCount: 1
41 | requiresCodeOwnerReviews: true
42 | permissionRules:
43 | - team: admin
44 | permission: admin
45 |
--------------------------------------------------------------------------------
/.github/workflows/bundlewatch.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Bundlewatch
16 |
17 | on:
18 | push:
19 | branches:
20 | - main
21 | pull_request:
22 | types: [synchronize, opened]
23 |
24 | jobs:
25 | bundlewatch:
26 | runs-on: ubuntu-latest
27 | env:
28 | CI_BRANCH_BASE: main
29 | steps:
30 | - uses: actions/checkout@v2
31 | - uses: jackyef/bundlewatch-gh-action@b9753bc9b3ea458ff21069eaf6206e01e046f0b5
32 | with:
33 | build-script: npm i
34 | bundlewatch-github-token: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}
35 | bundlewatch-config: .github/bundlewatch.config.json
36 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # For most projects, this workflow file will not need changing; you simply need
16 | # to commit it to your repository.
17 | #
18 | # You may wish to alter this file to override the set of languages analyzed,
19 | # or to provide custom queries or build logic.
20 | #
21 | # ******** NOTE ********
22 | # We have attempted to detect the languages in your repository. Please check
23 | # the `language` matrix defined below to confirm you have the correct set of
24 | # supported CodeQL languages.
25 | #
26 | name: "CodeQL"
27 |
28 | on:
29 | push:
30 | branches: [main]
31 | pull_request:
32 | # The branches below must be a subset of the branches above
33 | branches: [main]
34 | schedule:
35 | - cron: "0 13 * * *"
36 |
37 | jobs:
38 | analyze:
39 | name: Analyze
40 | runs-on: ubuntu-latest
41 | permissions:
42 | actions: read
43 | contents: read
44 | security-events: write
45 |
46 | strategy:
47 | fail-fast: false
48 | matrix:
49 | language: ["javascript"]
50 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
51 | # Learn more:
52 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
53 |
54 | steps:
55 | - name: Checkout repository
56 | uses: actions/checkout@v2
57 |
58 | # Initializes the CodeQL tools for scanning.
59 | - name: Initialize CodeQL
60 | uses: github/codeql-action/init@v1
61 | with:
62 | languages: ${{ matrix.language }}
63 | # If you wish to specify custom queries, you can do so here or in a config file.
64 | # By default, queries listed here will override any specified in a config file.
65 | # Prefix the list here with "+" to use these queries and those in the config file.
66 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
67 |
68 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
69 | # If this step fails, then you should remove it and run the build manually (see below)
70 | - name: Autobuild
71 | uses: github/codeql-action/autobuild@v1
72 |
73 | # ℹ️ Command-line programs to run using the OS shell.
74 | # 📚 https://git.io/JvXDl
75 |
76 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
77 | # and modify them (or add more) to build your code if your project
78 | # uses a compiled language
79 |
80 | #- run: |
81 | # make bootstrap
82 | # make release
83 |
84 | - name: Perform CodeQL Analysis
85 | uses: github/codeql-action/analyze@v1
86 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Dependabot
16 | on: pull_request
17 |
18 | permissions:
19 | contents: write
20 |
21 | jobs:
22 | dependabot:
23 | runs-on: ubuntu-latest
24 | if: ${{ github.actor == 'dependabot[bot]' }}
25 | env:
26 | PR_URL: ${{github.event.pull_request.html_url}}
27 | GITHUB_TOKEN: ${{secrets.SYNCED_GITHUB_TOKEN_REPO}}
28 | steps:
29 | - name: approve
30 | run: gh pr review --approve "$PR_URL"
31 | - name: merge
32 | run: gh pr merge --auto --squash --delete-branch "$PR_URL"
33 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Docs
16 | on: [push, pull_request]
17 | jobs:
18 | test:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v2
22 | - uses: actions/cache@v2
23 | with:
24 | path: ~/.npm
25 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
26 | restore-keys: |
27 | ${{ runner.os }}-node-
28 | - run: |
29 | npm i
30 | npm run docs
31 | - uses: peaceiris/actions-gh-pages@v3
32 | if: github.ref == 'refs/heads/main'
33 | with:
34 | github_token: ${{ secrets.GITHUB_TOKEN }}
35 | publish_dir: ./docs
36 | user_name: 'googlemaps-bot'
37 | user_email: 'googlemaps-bot@users.noreply.github.com'
38 | commit_message: ${{ github.event.head_commit.message }}
39 |
--------------------------------------------------------------------------------
/.github/workflows/e2e.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: e2e
16 | on:
17 | push:
18 | schedule:
19 | - cron: "0 12 * * *"
20 | jobs:
21 | chrome:
22 | runs-on: ubuntu-latest
23 | services:
24 | hub:
25 | image: selenium/standalone-chrome
26 | volumes:
27 | - ${{ github.workspace }}:${{ github.workspace }}
28 | ports:
29 | - 4444:4444
30 | steps:
31 | - uses: actions/checkout@v2
32 | - run: npm i
33 | - run: npm run test:e2e
34 | firefox:
35 | runs-on: ubuntu-latest
36 | services:
37 | hub:
38 | image: selenium/standalone-firefox
39 | volumes:
40 | - ${{ github.workspace }}:${{ github.workspace }}
41 | ports:
42 | - 4444:4444
43 | steps:
44 | - uses: actions/checkout@v2
45 | - run: npm i
46 | - run: npm run test:e2e
47 | env:
48 | BROWSER: firefox
49 |
--------------------------------------------------------------------------------
/.github/workflows/package.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Package
16 | on:
17 | - push
18 | - pull_request
19 | jobs:
20 | package:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - uses: actions/checkout@v2
24 | - run: npm i
25 | - uses: jpoehnelt/verify-npm-files-action@main
26 | with:
27 | keys: |
28 | types
29 | main
30 | module
31 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Release
16 | on:
17 | push:
18 | branches:
19 | - main
20 | concurrency: release
21 | jobs:
22 | build:
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/setup-node@v3
26 | with:
27 | node-version: '16'
28 | - name: Checkout
29 | uses: actions/checkout@v3
30 | with:
31 | token: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }}
32 | - uses: actions/cache@v2
33 | with:
34 | path: ~/.npm
35 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
36 | restore-keys: |
37 | ${{ runner.os }}-node-
38 | - name: Test
39 | run: |
40 | npm i
41 | npm run lint
42 | npm test
43 | - name: Release
44 | uses: cycjimmy/semantic-release-action@v3
45 | with:
46 | semantic_version: 19
47 | extra_plugins: |
48 | @semantic-release/commit-analyzer@^9
49 | semantic-release-interval
50 | @semantic-release/release-notes-generator@^10
51 | @semantic-release/git
52 | @semantic-release/github@^8
53 | @semantic-release/npm@^9
54 | @googlemaps/semantic-release-config
55 | semantic-release-npm-deprecate
56 | env:
57 | GH_TOKEN: ${{ secrets.SYNCED_GITHUB_TOKEN_REPO }}
58 | NPM_TOKEN: ${{ secrets.NPM_WOMBAT_TOKEN }}
59 | RUNNER_DEBUG: 1
60 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: Test
16 | on: [push, pull_request]
17 | jobs:
18 | test:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v2
22 | - uses: actions/cache@v2
23 | with:
24 | path: ~/.npm
25 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
26 | restore-keys: |
27 | ${{ runner.os }}-node-
28 | - run: npm i
29 | - run: npm run lint
30 | - run: npm test
31 | - uses: codecov/codecov-action@v1
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist/
3 | .npmrc
4 | **/docs
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # TypeScript v1 declaration files
50 | typings/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | .npm
57 |
58 | # Optional eslint cache
59 | .eslintcache
60 |
61 | # Microbundle cache
62 | .rpt2_cache/
63 | .rts2_cache_cjs/
64 | .rts2_cache_es/
65 | .rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variables file
77 | .env
78 | .env.test
79 |
80 | # parcel-bundler cache (https://parceljs.org/)
81 | .cache
82 |
83 | # next.js build output
84 | .next
85 |
86 | # nuxt.js build output
87 | .nuxt
88 |
89 | # gatsby files
90 | .cache/
91 | public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
109 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
110 |
111 | # User-specific stuff
112 | .idea/**/workspace.xml
113 | .idea/**/tasks.xml
114 | .idea/**/usage.statistics.xml
115 | .idea/**/dictionaries
116 | .idea/**/shelf
117 |
118 | # Generated files
119 | .idea/**/contentModel.xml
120 |
121 | # Sensitive or high-churn files
122 | .idea/**/dataSources/
123 | .idea/**/dataSources.ids
124 | .idea/**/dataSources.local.xml
125 | .idea/**/sqlDataSources.xml
126 | .idea/**/dynamic.xml
127 | .idea/**/uiDesigner.xml
128 | .idea/**/dbnavigator.xml
129 |
130 | # Gradle
131 | .idea/**/gradle.xml
132 | .idea/**/libraries
133 |
134 | # Gradle and Maven with auto-import
135 | # When using Gradle or Maven with auto-import, you should exclude module files,
136 | # since they will be recreated, and may cause churn. Uncomment if using
137 | # auto-import.
138 | # .idea/modules.xml
139 | # .idea/*.iml
140 | # .idea/modules
141 | # *.iml
142 | # *.ipr
143 |
144 | # CMake
145 | cmake-build-*/
146 |
147 | # Mongo Explorer plugin
148 | .idea/**/mongoSettings.xml
149 |
150 | # File-based project format
151 | *.iws
152 |
153 | # IntelliJ
154 | out/
155 |
156 | # mpeltonen/sbt-idea plugin
157 | .idea_modules/
158 |
159 | # JIRA plugin
160 | atlassian-ide-plugin.xml
161 |
162 | # Cursive Clojure plugin
163 | .idea/replstate.xml
164 |
165 | # Crashlytics plugin (for Android Studio and IntelliJ)
166 | com_crashlytics_export_strings.xml
167 | crashlytics.properties
168 | crashlytics-build.properties
169 | fabric.properties
170 |
171 | # Editor-based Rest Client
172 | .idea/httpRequests
173 |
174 | # Android studio 3.1+ serialized cache file
175 | .idea/caches/build_file_checksums.ser
176 |
177 | # General
178 | .DS_Store
179 | .AppleDouble
180 | .LSOverride
181 |
182 | # Icon must end with two \r
183 | Icon
184 |
185 |
186 | # Thumbnails
187 | ._*
188 |
189 | # Files that might appear in the root of a volume
190 | .DocumentRevisions-V100
191 | .fseventsd
192 | .Spotlight-V100
193 | .TemporaryItems
194 | .Trashes
195 | .VolumeIcon.icns
196 | .com.apple.timemachine.donotpresent
197 |
198 | # Directories potentially created on remote AFP share
199 | .AppleDB
200 | .AppleDesktop
201 | Network Trash Folder
202 | Temporary Items
203 | .apdisk
204 |
205 | # Windows thumbnail cache files
206 | Thumbs.db
207 | Thumbs.db:encryptable
208 | ehthumbs.db
209 | ehthumbs_vista.db
210 |
211 | # Dump file
212 | *.stackdump
213 |
214 | # Folder config file
215 | [Dd]esktop.ini
216 |
217 | # Recycle Bin used on file shares
218 | $RECYCLE.BIN/
219 |
220 | # Windows Installer files
221 | *.cab
222 | *.msi
223 | *.msix
224 | *.msm
225 | *.msp
226 |
227 | # Windows shortcuts
228 | *.lnk
229 | .vscode
230 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | { "trailingComma": "es5" }
2 |
--------------------------------------------------------------------------------
/.releaserc:
--------------------------------------------------------------------------------
1 | extends: "@googlemaps/semantic-release-config"
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to become a contributor and submit your own code
2 |
3 | 1. Submit an issue describing your proposed change to the repo in question.
4 | 1. The repo owner will respond to your issue promptly.
5 | 1. Fork the desired repo, develop and test your code changes.
6 | 1. Ensure that your code adheres to the existing style in the code to which
7 | you are contributing.
8 | 1. Ensure that your code has an appropriate set of tests which all pass.
9 | 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling.
10 | 1. Submit a pull request.
11 |
12 | ## Running the tests
13 |
14 | 1. Install dependencies:
15 |
16 | npm i
17 | 1. Run lint
18 |
19 | npm run lint
20 | npm run format # will fix some issues
21 |
22 | 1. Run the tests:
23 |
24 | # Run unit tests.
25 | npm test
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Google Maps ThreeJS Overlay View and Utilities
2 |
3 | [](https://www.npmjs.com/package/@googlemaps/three)
4 | 
5 | 
6 | [](https://codecov.io/gh/googlemaps/js-three)
7 | 
8 | [](https://github.com/semantic-release/semantic-release)
9 | [](https://github.com/apps/in-solidarity)
10 | [](https://discord.gg/jRteCzP)
11 |
12 | ## Description
13 |
14 | Add [three.js](https://threejs.org) objects to Google Maps Platform JS. The
15 | library provides a `ThreeJSOverlayView` class extending `google.maps.WebGLOverlayView`
16 | and utility functions for converting geo-coordinates (latitude/longitude) to
17 | vectors in the coordinate system used by three.js.
18 |
19 | ## Install
20 |
21 | Available via npm as the package [@googlemaps/three](https://www.npmjs.com/package/@googlemaps/three).
22 |
23 | ```
24 | npm i @googlemaps/three
25 | ```
26 |
27 | Alternatively you can load the package directly to the html document using
28 | unpkg or other CDNs. In this case, make sure to load three.js before loading
29 | this library:
30 |
31 | ```
32 |
33 |
34 | ```
35 |
36 | When adding via unpkg, the package can be accessed as
37 | `google.maps.plugins.three`. A version can be specified by using
38 | `https://unpkg.com/@googlemaps/three@VERSION/dist/...`.
39 |
40 | The third option to use it is via ES-Module imports, similar to how the
41 | three.js examples work. For this, you first need to specify an
42 | [importmap](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap)
43 | (example using unpkg.com, but it works the same way with any other CDN
44 | or self-hosted files):
45 |
46 | ```html
47 |
55 | ```
56 |
57 | In order to support browsers that don't yet implement importmap, you can
58 | use the [es-module-shims package](https://github.com/guybedford/es-module-shims).
59 |
60 | After that, you can use three.js and the ThreeJSOverlayView like you would when
61 | using a bundler.
62 |
63 | ```html
64 |
70 | ```
71 |
72 | ## Documentation
73 |
74 | Checkout the reference [documentation](https://googlemaps.github.io/js-three/index.html).
75 |
76 | ### Coordinates, Projection and Anchor-Points
77 |
78 | The coordinate system within the three.js scene (so-called 'world
79 | coordinates') is a right-handed coordinate system in z-up orientation.
80 | The y-axis is pointing true north, and the x-axis is pointing east. The
81 | units are meters. So the point `new Vector3(0, 50, 10)` is 10 meters
82 | above ground and 50 meters east of the specified anchor point.
83 |
84 | This anchor-point and orientation can be set in the constructor, or by using the
85 | `setAnchor()` and `setUpAxis()`-methods (be aware that all object-positions in
86 | your scene depend on the anchor-point and orientation, so they have to be
87 | recomputed when either of them is changed):
88 |
89 | ```typescript
90 | import { ThreeJSOverlayView } from "@googlemaps/three";
91 |
92 | const overlay = new ThreeJSOverlayView({
93 | anchor: { lat: 37.7793, lng: -122.4192, altitude: 0 },
94 | upAxis: "Y",
95 | });
96 |
97 | overlay.setAnchor({ lat: 35.680432, lng: 139.769013, altitude: 0 });
98 | overlay.setUpAxis("Z");
99 | // can also be specified as Vector3:
100 | overlay.setUpAxis(new Vector3(0, 0, 1));
101 | ```
102 |
103 | > The default up-axis used in this library is the z-axis (+x is east
104 | > and +y is north), which is different from the y-up orientation normally
105 | > used in three.
106 |
107 | All computations on the GPU related to the position use float32 numbers,
108 | which limits the possible precision to about 7 decimal digits. Because
109 | of this, we cannot use a global reference system and still have the
110 | precision to show details in the meters to centimeters range.
111 |
112 | This is where the anchor point is important. The anchor specifies the
113 | geo-coordinates (lat/lng/altitude) where the origin of the world-space
114 | coordinate system is, and you should always define it close to where the
115 | objects are placed in the scene - unless of course you are only working with
116 | large-scale (city-sized) objects distributed globally.
117 |
118 | Another reason why setting the anchor close to the objects in the scene
119 | is generally a good idea: In the mercator map-projection used in Google Maps,
120 | the scaling of meters is only accurate in regions close to the equator. This
121 | can be compensated for by applying a scale factor that depends on the
122 | latitude of the anchor. This scale factor is factored into the coordinate
123 | calculations in WebGlOverlayView based on the latitude of the anchor.
124 |
125 | #### Converting coordinates
126 |
127 | When you need more than just a single georeferenced object in your scene,
128 | you need to compute the world-space position for those coordinates. The
129 | ThreeJSOverlayView class provides a helper function for this conversion that
130 | takes the current `anchor` and `upAxis` into account:
131 |
132 | ```typescript
133 | const coordinates = { lat: 12.34, lng: 56.78 };
134 | const position: Vector3 = overlay.latLngAltitudeToVector3(coordinates);
135 |
136 | // alternative: pass the Vector3 to write the position
137 | // to as the second parameter, so to set the position of a mesh:
138 | overlay.latLngAltitudeToVector3(coordinates, mesh.position);
139 | ```
140 |
141 | ### Raycasting and Interactions
142 |
143 | If you want to add interactivity to any three.js content, you typically
144 | have to implement raycasting. We took care of that for you, and the
145 | ThreeJSOverlayView provides a method `overlay.raycast()` for this. To make
146 | use of it, you first have to keep track of mouse movements on the map:
147 |
148 | ```js
149 | import { Vector2 } from "three";
150 |
151 | // ...
152 |
153 | const mapDiv = map.getDiv();
154 | const mousePosition = new Vector2();
155 |
156 | map.addListener("mousemove", (ev) => {
157 | const { domEvent } = ev;
158 | const { left, top, width, height } = mapDiv.getBoundingClientRect();
159 |
160 | const x = domEvent.clientX - left;
161 | const y = domEvent.clientY - top;
162 |
163 | mousePosition.x = 2 * (x / width) - 1;
164 | mousePosition.y = 1 - 2 * (y / height);
165 |
166 | // since the actual raycasting is performed when the next frame is
167 | // rendered, we have to make sure that it will be called for the next frame.
168 | overlay.requestRedraw();
169 | });
170 | ```
171 |
172 | With the mouse position being always up to date, you can then use the
173 | `raycast()` function in the `onBeforeDraw` callback.
174 | In this example, we change the color of the object under the cursor:
175 |
176 | ```js
177 | const DEFAULT_COLOR = 0xffffff;
178 | const HIGHLIGHT_COLOR = 0xff0000;
179 |
180 | let highlightedObject = null;
181 |
182 | overlay.onBeforeDraw = () => {
183 | const intersections = overlay.raycast(mousePosition);
184 | if (highlightedObject) {
185 | highlightedObject.material.color.setHex(DEFAULT_COLOR);
186 | }
187 |
188 | if (intersections.length === 0) return;
189 |
190 | highlightedObject = intersections[0].object;
191 | highlightedObject.material.color.setHex(HIGHLIGHT_COLOR);
192 | };
193 | ```
194 |
195 | The full examples can be found in [`./examples/raycasting.ts`](./examples/raycasting.ts).
196 |
197 | ## Example
198 |
199 | The following example provides a skeleton for adding objects to the map with this library.
200 |
201 | ```js
202 | import * as THREE from "three";
203 | import { ThreeJSOverlayView, latLngToVector3 } from "@googlemaps/three";
204 |
205 | // when loading via UMD, remove the imports and use this instead:
206 | // const { ThreeJSOverlayView, latLngToVector3 } = google.maps.plugins.three;
207 |
208 | const map = new google.maps.Map(document.getElementById("map"), mapOptions);
209 | const overlay = new ThreeJSOverlayView({
210 | map,
211 | upAxis: "Y",
212 | anchor: mapOptions.center,
213 | });
214 |
215 | // create a box mesh
216 | const box = new THREE.Mesh(
217 | new THREE.BoxGeometry(10, 50, 10),
218 | new THREE.MeshMatcapMaterial()
219 | );
220 | // move the box up so the origin of the box is at the bottom
221 | box.geometry.translateY(25);
222 |
223 | // set position at center of map
224 | box.position.copy(overlay.latLngAltitudeToVector3(mapOptions.center));
225 |
226 | // add box mesh to the scene
227 | overlay.scene.add(box);
228 |
229 | // rotate the box using requestAnimationFrame
230 | const animate = () => {
231 | box.rotateY(THREE.MathUtils.degToRad(0.1));
232 |
233 | requestAnimationFrame(animate);
234 | };
235 |
236 | // start animation loop
237 | requestAnimationFrame(animate);
238 | ```
239 |
240 | This adds a box to the map.
241 |
242 |
243 |
244 | ## Demos
245 |
246 | View the package in action:
247 |
248 | - [Basic Example](https://googlemaps.github.io/js-three/public/basic/)
249 | - [Anchor Example](https://googlemaps.github.io/js-three/public/anchor/)
250 | - [Orientation Example](https://googlemaps.github.io/js-three/public/orientation/)
251 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Report a security issue
2 |
3 | To report a security issue, please use https://g.co/vulnz. We use
4 | https://g.co/vulnz for our intake, and do coordination and disclosure here on
5 | GitHub (including using GitHub Security Advisory). The Google Security Team will
6 | respond within 5 working days of your report on g.co/vulnz.
7 |
8 | To contact us about other bugs, please open an issue on GitHub.
9 |
10 | > **Note**: This file is synchronized from the https://github.com/googlemaps/.github repository.
11 |
--------------------------------------------------------------------------------
/e2e/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlemaps/js-three/19874f82388ddb9ef344904ef2a9459a936cc16a/e2e/README.md
--------------------------------------------------------------------------------
/examples/anchor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { LOADER_OPTIONS, MAP_ID } from "./config";
18 | import { ThreeJSOverlayView, WORLD_SIZE } from "../src";
19 |
20 | import { Loader } from "@googlemaps/js-api-loader";
21 | import { AxesHelper } from "three";
22 |
23 | const mapOptions = {
24 | center: {
25 | lat: 45,
26 | lng: 0,
27 | },
28 | mapId: MAP_ID,
29 | zoom: 5,
30 | heading: -45,
31 | tilt: 45,
32 | };
33 |
34 | new Loader(LOADER_OPTIONS).load().then(() => {
35 | const map = new google.maps.Map(document.getElementById("map"), mapOptions);
36 | const overlay = new ThreeJSOverlayView({
37 | anchor: { ...mapOptions.center, altitude: 0 },
38 | map,
39 | });
40 |
41 | overlay.scene.add(new AxesHelper(WORLD_SIZE));
42 | });
43 |
--------------------------------------------------------------------------------
/examples/basic.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { LOADER_OPTIONS, MAP_ID } from "./config";
18 | import { ThreeJSOverlayView } from "../src";
19 |
20 | import { Loader } from "@googlemaps/js-api-loader";
21 | import { BoxGeometry, MathUtils, Mesh, MeshMatcapMaterial } from "three";
22 |
23 | const mapOptions = {
24 | center: {
25 | lng: -122.343787,
26 | lat: 47.607465,
27 | },
28 | mapId: MAP_ID,
29 | zoom: 15,
30 | heading: 45,
31 | tilt: 67,
32 | };
33 |
34 | new Loader(LOADER_OPTIONS).load().then(() => {
35 | // create the map and ThreeJS Overlay
36 | const map = new google.maps.Map(document.getElementById("map"), mapOptions);
37 | const overlay = new ThreeJSOverlayView({ map });
38 |
39 | // Create a box mesh
40 | const box = new Mesh(
41 | new BoxGeometry(100, 200, 500),
42 | new MeshMatcapMaterial()
43 | );
44 |
45 | // set position at center of map
46 | const pos = overlay.latLngAltitudeToVector3(mapOptions.center);
47 | box.position.copy(pos);
48 |
49 | // set position vertically
50 | box.position.z = 25;
51 |
52 | // add box mesh to the scene
53 | overlay.scene.add(box);
54 |
55 | // rotate the box using requestAnimationFrame
56 | const animate = () => {
57 | box.rotateZ(MathUtils.degToRad(0.1));
58 |
59 | requestAnimationFrame(animate);
60 | };
61 |
62 | requestAnimationFrame(animate);
63 | });
64 |
--------------------------------------------------------------------------------
/examples/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { LoaderOptions } from "@googlemaps/js-api-loader";
18 |
19 | export const MAP_ID = "7b9a897acd0a63a4";
20 |
21 | export const LOADER_OPTIONS: LoaderOptions = {
22 | apiKey: "AIzaSyD8xiaVPWB02OeQkJOenLiJzdeUHzlhu00",
23 | version: "beta",
24 | libraries: [],
25 | };
26 |
--------------------------------------------------------------------------------
/examples/hemispheres.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { LOADER_OPTIONS, MAP_ID } from "./config";
18 | import { ThreeJSOverlayView } from "../src";
19 |
20 | import { Loader } from "@googlemaps/js-api-loader";
21 | import { BoxGeometry, Mesh, MeshMatcapMaterial } from "three";
22 |
23 | const mapOptions = {
24 | center: {
25 | lng: 0,
26 | lat: 0,
27 | },
28 | mapId: MAP_ID,
29 | zoom: 4,
30 | tilt: 67,
31 | };
32 |
33 | new Loader(LOADER_OPTIONS).load().then(() => {
34 | // create the map and overlay
35 | const map = new google.maps.Map(document.getElementById("map"), mapOptions);
36 | const overlay = new ThreeJSOverlayView({ map });
37 |
38 | [
39 | { lat: 45, lng: -90 },
40 | { lat: 45, lng: 90 },
41 | { lat: -45, lng: -90 },
42 | { lat: -45, lng: 90 },
43 | ].forEach((latLng: google.maps.LatLngLiteral) => {
44 | // create a box mesh with origin on the ground, in z-up orientation
45 | const geometry = new BoxGeometry(10, 50, 10)
46 | .translate(0, 25, 0)
47 | .rotateX(Math.PI / 2);
48 |
49 | const box = new Mesh(geometry, new MeshMatcapMaterial());
50 |
51 | // make it huge
52 | box.scale.multiplyScalar(10000);
53 |
54 | // set position at center of map
55 | overlay.latLngAltitudeToVector3(latLng, box.position);
56 |
57 | // add box mesh to the scene
58 | overlay.scene.add(box);
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/examples/orientation.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { LOADER_OPTIONS, MAP_ID } from "./config";
18 | import { ThreeJSOverlayView, WORLD_SIZE } from "../src";
19 |
20 | import { Loader } from "@googlemaps/js-api-loader";
21 | import { AxesHelper, Scene } from "three";
22 |
23 | const mapOptions = {
24 | center: {
25 | lat: 0,
26 | lng: 0,
27 | },
28 | mapId: MAP_ID,
29 | zoom: 5,
30 | heading: -45,
31 | tilt: 45,
32 | };
33 |
34 | new Loader(LOADER_OPTIONS).load().then(() => {
35 | const map = new google.maps.Map(document.getElementById("map"), mapOptions);
36 | const scene = new Scene();
37 |
38 | scene.add(new AxesHelper(WORLD_SIZE));
39 |
40 | new ThreeJSOverlayView({ scene, map });
41 | });
42 |
--------------------------------------------------------------------------------
/examples/raycasting.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { LOADER_OPTIONS } from "./config";
18 | import { ThreeJSOverlayView } from "../src";
19 |
20 | import { Loader } from "@googlemaps/js-api-loader";
21 | import {
22 | AxesHelper,
23 | CylinderGeometry,
24 | GridHelper,
25 | MathUtils,
26 | Mesh,
27 | MeshMatcapMaterial,
28 | Vector2,
29 | } from "three";
30 |
31 | // the corners of the field in the Levi’s Stadium in Santa Clara
32 | const coordinates = [
33 | { lng: -121.9702904, lat: 37.4034362 },
34 | { lng: -121.9698018, lat: 37.4027095 },
35 | { lng: -121.9693109, lat: 37.402918 },
36 | { lng: -121.969804, lat: 37.4036465 },
37 | ];
38 | const center = { lng: -121.9698032, lat: 37.4031777, altitude: 0 };
39 |
40 | const DEFAULT_COLOR = 0xffffff;
41 | const HIGHLIGHT_COLOR = 0xff0000;
42 |
43 | const mapOptions = {
44 | center,
45 | mapId: "7057886e21226ff7",
46 | zoom: 18,
47 | tilt: 67.5,
48 | disableDefaultUI: true,
49 | backgroundColor: "transparent",
50 | gestureHandling: "greedy",
51 | };
52 |
53 | new Loader(LOADER_OPTIONS).load().then(() => {
54 | // create the map and overlay
55 | const map = new google.maps.Map(document.getElementById("map"), mapOptions);
56 | const overlay = new ThreeJSOverlayView({ map, anchor: center, upAxis: "Y" });
57 |
58 | const mapDiv = map.getDiv();
59 | const mousePosition = new Vector2();
60 |
61 | map.addListener("mousemove", (ev: google.maps.MapMouseEvent) => {
62 | const domEvent = ev.domEvent as MouseEvent;
63 | const { left, top, width, height } = mapDiv.getBoundingClientRect();
64 |
65 | const x = domEvent.clientX - left;
66 | const y = domEvent.clientY - top;
67 |
68 | mousePosition.x = 2 * (x / width) - 1;
69 | mousePosition.y = 1 - 2 * (y / height);
70 |
71 | // since the actual raycasting is performed when the next frame is
72 | // rendered, we have to make sure that it will be called for the next frame.
73 | overlay.requestRedraw();
74 | });
75 |
76 | // grid- and axes helpers to help with the orientation
77 | const grid = new GridHelper(1);
78 |
79 | grid.rotation.y = MathUtils.degToRad(28.1);
80 | grid.scale.set(48.8, 0, 91.44);
81 | overlay.scene.add(grid);
82 | overlay.scene.add(new AxesHelper(20));
83 |
84 | const meshes = coordinates.map((p) => {
85 | const mesh = new Mesh(
86 | new CylinderGeometry(2, 1, 20, 24, 1),
87 | new MeshMatcapMaterial()
88 | );
89 | mesh.geometry.translate(0, mesh.geometry.parameters.height / 2, 0);
90 | overlay.latLngAltitudeToVector3(p, mesh.position);
91 |
92 | overlay.scene.add(mesh);
93 |
94 | return mesh;
95 | });
96 |
97 | let highlightedObject: (typeof meshes)[number] | null = null;
98 |
99 | overlay.onBeforeDraw = () => {
100 | const intersections = overlay.raycast(mousePosition, meshes, {
101 | recursive: false,
102 | });
103 |
104 | if (highlightedObject) {
105 | // when there's a previously highlighted object, reset the highlighting
106 | highlightedObject.material.color.setHex(DEFAULT_COLOR);
107 | }
108 |
109 | if (intersections.length === 0) {
110 | // reset default cursor when no object is under the cursor
111 | map.setOptions({ draggableCursor: null });
112 | highlightedObject = null;
113 | return;
114 | }
115 |
116 | // change the color of the object and update the map-cursor to indicate
117 | // the object is clickable.
118 | highlightedObject = intersections[0].object;
119 | highlightedObject.material.color.setHex(HIGHLIGHT_COLOR);
120 | map.setOptions({ draggableCursor: "pointer" });
121 | };
122 | });
123 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export default {
18 | roots: ["/src"],
19 | preset: "ts-jest",
20 | testPathIgnorePatterns: ["/dist/", "/__utils__/"],
21 | testEnvironment: "jsdom",
22 | setupFilesAfterEnv: ["jest-extended/all"],
23 | };
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@googlemaps/three",
3 | "version": "4.0.18",
4 | "type": "module",
5 | "keywords": [
6 | "google",
7 | "maps",
8 | "webgl",
9 | "threejs"
10 | ],
11 | "homepage": "https://github.com/googlemaps/js-three",
12 | "bugs": {
13 | "url": "https://github.com/googlemaps/js-three/issues"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/googlemaps/js-three.git"
18 | },
19 | "license": "Apache-2.0",
20 | "author": "Justin Poehnelt",
21 | "source": "src/index.ts",
22 | "main": "dist/index.umd.js",
23 | "exports": {
24 | ".": {
25 | "import": {
26 | "types": "./dist/index.d.ts",
27 | "default": "./dist/index.esm.js"
28 | },
29 | "require": "./dist/index.umd.js",
30 | "umd": "./dist/index.umd.js",
31 | "browser": "./dist/index.esm.js"
32 | }
33 | },
34 | "unpkg": "dist/index.min.js",
35 | "module": "dist/index.esm.js",
36 | "types": "dist/index.d.ts",
37 | "files": [
38 | "/src",
39 | "/dist"
40 | ],
41 | "scripts": {
42 | "build:examples": "rm -rf public && rollup -c rollup.config.examples.js",
43 | "docs": "typedoc src/index.ts && npm run build:examples && cp -r dist docs/dist && cp -r public docs/public",
44 | "format": "eslint . --fix",
45 | "lint": "eslint .",
46 | "prepare": "rm -rf dist && rollup -c",
47 | "start": "run-p start:*",
48 | "start:rollup": "rollup -c rollup.config.examples.js -w --no-watch.clearScreen",
49 | "start:server": "http-server ./public",
50 | "test": "jest --coverage=true src/*",
51 | "test:e2e": "jest --passWithNoTests e2e/*"
52 | },
53 | "peerDependencies": {
54 | "three": "*"
55 | },
56 | "devDependencies": {
57 | "@babel/preset-env": "^7.25.7",
58 | "@babel/preset-modules": "^0.1.6",
59 | "@babel/runtime-corejs3": "^7.25.7",
60 | "@googlemaps/jest-mocks": "^2.21.4",
61 | "@googlemaps/js-api-loader": "^1.16.8",
62 | "@rollup/plugin-babel": "^6.0.4",
63 | "@rollup/plugin-commonjs": "^26.0.1",
64 | "@rollup/plugin-html": "^1.0.3",
65 | "@rollup/plugin-json": "^6.1.0",
66 | "@rollup/plugin-node-resolve": "^15.2.4",
67 | "@rollup/plugin-terser": "^0.4.4",
68 | "@rollup/plugin-typescript": "^11.1.6",
69 | "@types/d3-random": "^3.0.3",
70 | "@types/google.maps": "^3.58.1",
71 | "@types/jest": "^29.5.14",
72 | "@types/proj4": "^2.5.5",
73 | "@types/selenium-webdriver": "^4.1.25",
74 | "@types/stats.js": "^0.17.3",
75 | "@types/three": "^0.156.0",
76 | "@typescript-eslint/eslint-plugin": "^7.0.0",
77 | "@typescript-eslint/parser": "^6.19.1",
78 | "core-js": "^3.37.1",
79 | "d3-random": "^3.0.1",
80 | "eslint": "^8.57.0",
81 | "eslint-config-prettier": "^9.1.0",
82 | "eslint-plugin-jest": "^28.9.0",
83 | "eslint-plugin-prettier": "^5.1.3",
84 | "geckodriver": "^5.0.0",
85 | "http-server": "^14.1.1",
86 | "jest": "^29.7.0",
87 | "jest-environment-jsdom": "^29.7.0",
88 | "jest-extended": "^4.0.2",
89 | "jest-webgl-canvas-mock": "^2.5.3",
90 | "npm-run-all": "^4.1.5",
91 | "prettier": "^3.3.3",
92 | "rollup": "^4.22.4",
93 | "selenium-webdriver": "^4.23.0",
94 | "three": "^0.161.0",
95 | "ts-jest": "^29.2.5",
96 | "typedoc": "^0.25.13",
97 | "typescript": "^5.4.5"
98 | },
99 | "publishConfig": {
100 | "access": "public",
101 | "registry": "https://wombat-dressing-room.appspot.com"
102 | }
103 | }
--------------------------------------------------------------------------------
/rollup.config.examples.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import fs from "node:fs";
17 | import path from "node:path";
18 | import * as url from "node:url";
19 |
20 | import html, { makeHtmlAttributes } from "@rollup/plugin-html";
21 |
22 | import commonjs from "@rollup/plugin-commonjs";
23 |
24 | import jsonNodeResolve from "@rollup/plugin-json";
25 | import { nodeResolve } from "@rollup/plugin-node-resolve";
26 |
27 | import typescript from "@rollup/plugin-typescript";
28 |
29 | const template = ({ attributes, files, meta, publicPath, title }) => {
30 | const scripts = (files.js || [])
31 | .map(({ fileName }) => {
32 | const attrs = makeHtmlAttributes(attributes.script);
33 | return ``;
34 | })
35 | .join("\n");
36 |
37 | const links = (files.css || [])
38 | .map(({ fileName }) => {
39 | const attrs = makeHtmlAttributes(attributes.link);
40 | return ``;
41 | })
42 | .join("\n");
43 |
44 | const metas = meta
45 | .map((input) => {
46 | const attrs = makeHtmlAttributes(input);
47 | return ``;
48 | })
49 | .join("\n");
50 |
51 | return `
52 |
53 |
54 |
55 | ${metas}
56 | ${title}
57 |
64 | ${links}
65 |
66 |
67 |
68 | ${scripts}
69 |
70 | `;
71 | };
72 |
73 | const typescriptOptions = {
74 | tsconfig: "tsconfig.examples.json",
75 | compilerOptions: {
76 | sourceMap: true,
77 | inlineSources: true,
78 | },
79 | };
80 |
81 | const dirname = url.fileURLToPath(new URL(".", import.meta.url));
82 | const examples = fs
83 | .readdirSync(path.join(dirname, "examples"))
84 | .filter((f) => f !== "config.ts")
85 | .map((f) => f.slice(0, f.length - 3));
86 |
87 | export default examples.map((name) => ({
88 | input: `examples/${name}.ts`,
89 |
90 | plugins: [
91 | typescript(typescriptOptions),
92 | commonjs(),
93 | nodeResolve(),
94 | jsonNodeResolve(),
95 | ],
96 | output: {
97 | dir: `public/${name}`,
98 | sourcemap: "inline",
99 | plugins: [
100 | html({
101 | fileName: `index.html`,
102 | title: `@googlemaps/three: ${name}`,
103 | template,
104 | }),
105 | ],
106 | manualChunks: (id) => {
107 | if (id.includes("node_modules")) {
108 | return "vendor";
109 | }
110 | },
111 | },
112 | }));
113 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { babel } from "@rollup/plugin-babel";
18 | import commonjs from "@rollup/plugin-commonjs";
19 | import terser from "@rollup/plugin-terser";
20 | import typescript from "@rollup/plugin-typescript";
21 | import { nodeResolve } from "@rollup/plugin-node-resolve";
22 |
23 | const babelOptions = {
24 | extensions: [".js", ".ts"],
25 | babelHelpers: "bundled",
26 | };
27 |
28 | const terserOptions = { output: { comments: "some" } };
29 |
30 | export default [
31 | {
32 | input: "src/index.ts",
33 | plugins: [
34 | typescript({ tsconfig: "./tsconfig.json", declarationDir: "./" }),
35 |
36 | commonjs(),
37 | babel(babelOptions),
38 | nodeResolve(),
39 | terser(terserOptions),
40 | ],
41 | external: ["three"],
42 | output: [
43 | {
44 | file: "dist/index.umd.js",
45 | format: "umd",
46 | sourcemap: true,
47 | name: "google.maps.plugins.three",
48 | globals: {
49 | three: "THREE",
50 | },
51 | },
52 | {
53 | file: "dist/index.min.js",
54 | format: "iife",
55 | sourcemap: true,
56 | name: "google.maps.plugins.three",
57 | globals: {
58 | three: "THREE",
59 | },
60 | },
61 | ],
62 | },
63 | {
64 | input: "src/index.ts",
65 | plugins: [
66 | typescript({ tsconfig: "./tsconfig.json", declarationDir: "./" }),
67 |
68 | commonjs(),
69 | babel(babelOptions),
70 | nodeResolve(),
71 | terser(terserOptions),
72 | ],
73 | external: ["three"],
74 | output: {
75 | file: "dist/index.dev.js",
76 | format: "iife",
77 | sourcemap: true,
78 | name: "google.maps.plugins.three",
79 | globals: {
80 | three: "THREE",
81 | },
82 | },
83 | },
84 | {
85 | input: "src/index.ts",
86 | external: ["three"],
87 | plugins: [
88 | typescript({ tsconfig: "./tsconfig.json", declarationDir: "./" }),
89 | babel({
90 | presets: ["@babel/preset-modules"],
91 | babelrc: false,
92 | extensions: [".js", ".ts"],
93 | babelHelpers: "bundled",
94 | }),
95 | terser(terserOptions),
96 | ],
97 | output: {
98 | file: "dist/index.esm.js",
99 | sourcemap: true,
100 | format: "esm",
101 | },
102 | },
103 | ];
104 |
--------------------------------------------------------------------------------
/src/__tests__/__utils__/createWebGlContext.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // using 'jest-webgl-canvas-mock' as intended as a setup-script in the jest
18 | // configuration causes an error 'TypeError: Cannot redefine property: window'
19 | // in newer node-version (last known working version is 18.13.0), which is why
20 | // we do the initialization manually here.
21 | // @ts-ignore
22 | import registerWebglMock from "jest-webgl-canvas-mock/lib/window.js";
23 |
24 | /**
25 | * Creates a mocked WebGL 1.0 context (based on the one provided by
26 | * the jest-webgl-canvas-mock package) three.js can work with.
27 | */
28 | export function createWebGlContext() {
29 | registerWebglMock(window);
30 |
31 | const gl = new WebGLRenderingContext();
32 | const glParameters: Record = {
33 | [gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS]: 8,
34 | [gl.VERSION]: "WebGL 1.0 (OpenGL ES 2.0 Chromium)",
35 | [gl.SCISSOR_BOX]: [0, 0, 100, 100],
36 | [gl.VIEWPORT]: [0, 0, 100, 100],
37 | };
38 |
39 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
40 | const glExtensions: Record = {
41 | EXT_blend_minmax: {},
42 | };
43 |
44 | jest.spyOn(gl, "getContextAttributes").mockReturnValue({});
45 | jest.spyOn(gl, "getParameter").mockImplementation((key) => glParameters[key]);
46 | jest.spyOn(gl, "getShaderPrecisionFormat").mockImplementation(() => ({
47 | rangeMin: 127,
48 | rangeMax: 127,
49 | precision: 23,
50 | }));
51 |
52 | const getExtensionOrig = gl.getExtension;
53 | jest.spyOn(gl, "getExtension").mockImplementation((id) => {
54 | return glExtensions[id] || getExtensionOrig(id);
55 | });
56 |
57 | const canvas = document.createElement("canvas");
58 | canvas.width = 200;
59 | canvas.height = 100;
60 |
61 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
62 | (gl as any).canvas = canvas;
63 |
64 | return gl;
65 | }
66 |
--------------------------------------------------------------------------------
/src/__tests__/three.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /* eslint-disable @typescript-eslint/explicit-member-accessibility */
18 |
19 | // prevent "WARNING: Multiple instances of Three.js being imported.” when
20 | // importing three.js
21 | Object.defineProperty(window, "__THREE__", {
22 | get: () => null,
23 | set: () => null,
24 | configurable: false,
25 | });
26 |
27 | import { ThreeJSOverlayView, ThreeJSOverlayViewOptions } from "../three";
28 | import * as util from "../util";
29 |
30 | import {
31 | BoxGeometry,
32 | Camera,
33 | Group,
34 | Light,
35 | Matrix4,
36 | Mesh,
37 | Object3D,
38 | PerspectiveCamera,
39 | RaycasterParameters,
40 | Scene,
41 | Vector2,
42 | Vector3,
43 | Vector4,
44 | WebGLRenderer,
45 | } from "three";
46 |
47 | import "jest-extended";
48 | import { initialize, Map } from "@googlemaps/jest-mocks";
49 | import { createWebGlContext } from "./__utils__/createWebGlContext";
50 |
51 | // setup mocked dependencies
52 | jest.mock("../util");
53 |
54 | beforeEach(() => {
55 | initialize();
56 | google.maps.WebGLOverlayView = jest.fn().mockImplementation(() => {
57 | return new (class extends google.maps.MVCObject {
58 | getMap = jest.fn();
59 | setMap = jest.fn();
60 | requestRedraw = jest.fn();
61 | requestStateUpdate = jest.fn();
62 | addListener = jest.fn().mockImplementation(() => {
63 | return { remove: jest.fn() } as google.maps.MapsEventListener;
64 | });
65 | })();
66 | });
67 | });
68 |
69 | afterEach(() => {
70 | jest.restoreAllMocks();
71 | });
72 |
73 | describe("basic functions", () => {
74 | test("instantiates with defaults", () => {
75 | const overlay = new ThreeJSOverlayView();
76 |
77 | expect(overlay["overlay"]).toBeDefined();
78 | expect(overlay["camera"]).toBeInstanceOf(PerspectiveCamera);
79 |
80 | expect(overlay.scene).toBeInstanceOf(Scene);
81 |
82 | // required hooks must be defined
83 | expect(overlay["overlay"].onAdd).toBeDefined();
84 | expect(overlay["overlay"].onRemove).toBeDefined();
85 | expect(overlay["overlay"].onContextLost).toBeDefined();
86 | expect(overlay["overlay"].onContextRestored).toBeDefined();
87 | expect(overlay["overlay"].onDraw).toBeDefined();
88 | });
89 |
90 | test("instantiates with map and calls setMap", () => {
91 | const map = new Map(
92 | document.createElement("div"),
93 | {}
94 | ) as unknown as google.maps.Map;
95 |
96 | const overlay = new ThreeJSOverlayView({
97 | map,
98 | });
99 |
100 | expect(overlay["overlay"].setMap).toHaveBeenCalledWith(map);
101 | });
102 |
103 | test("setMap is called on overlay", () => {
104 | const map = new Map(
105 | document.createElement("div"),
106 | {}
107 | ) as unknown as google.maps.Map;
108 | const overlay = new ThreeJSOverlayView();
109 | overlay.setMap(map);
110 |
111 | expect(overlay["overlay"].setMap).toHaveBeenCalledWith(map);
112 | });
113 |
114 | test("getMap is called on overlay", () => {
115 | const overlay = new ThreeJSOverlayView();
116 | overlay.getMap();
117 |
118 | expect(overlay["overlay"].getMap).toHaveBeenCalledWith();
119 | });
120 |
121 | test("addListener is called on overlay", () => {
122 | const overlay = new ThreeJSOverlayView();
123 | const handler = jest.fn();
124 | const eventName = "foo";
125 |
126 | expect(overlay.addListener(eventName, handler)).toBeDefined();
127 | expect(overlay["overlay"].addListener).toHaveBeenCalledWith(
128 | eventName,
129 | handler
130 | );
131 | });
132 | });
133 |
134 | describe("MVCObject interface", () => {
135 | let overlay: ThreeJSOverlayView;
136 | let webglOverlay: google.maps.WebGLOverlayView;
137 |
138 | beforeEach(() => {
139 | overlay = new ThreeJSOverlayView();
140 | webglOverlay = overlay["overlay"];
141 | });
142 |
143 | test.each([
144 | ["bindTo", "eventName", () => void 0, "targetKey", true],
145 | ["get", "key"],
146 | ["notify", "key"],
147 | ["set", "key", "value"],
148 | ["setValues", { key: "value" }],
149 | ["unbind", "key"],
150 | ["unbindAll"],
151 | ] as const)(
152 | "method '%s' is forwarded to overlay",
153 | (method: keyof google.maps.MVCObject, ...args) => {
154 | overlay[method].call(overlay, ...args);
155 | expect(webglOverlay[method]).toHaveBeenCalledWith(...args);
156 | }
157 | );
158 | });
159 |
160 | describe("WebGLOverlayView interface", () => {
161 | let overlay: ThreeJSOverlayView;
162 | let gl: WebGLRenderingContext;
163 | let transformer: google.maps.CoordinateTransformer;
164 | const projMatrixArray = new Float64Array([
165 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
166 | ]);
167 |
168 | beforeEach(() => {
169 | overlay = new ThreeJSOverlayView();
170 | gl = createWebGlContext();
171 |
172 | transformer = {
173 | fromLatLngAltitude: jest.fn(() => projMatrixArray),
174 | getCameraParams: jest.fn(),
175 | };
176 | });
177 |
178 | test("onContextRestored creates the renderer", () => {
179 | overlay.onContextRestored({ gl });
180 | const renderer: WebGLRenderer = overlay["renderer"];
181 | expect(renderer).toBeDefined();
182 |
183 | const viewport = renderer.getViewport(new Vector4());
184 | expect(viewport.x).toEqual(0);
185 | expect(viewport.y).toEqual(0);
186 | expect(viewport.width).toEqual(gl.canvas.width);
187 | expect(viewport.height).toEqual(gl.canvas.height);
188 | });
189 |
190 | test("onDraw renders the scene and resets the state", () => {
191 | overlay.onContextRestored({ gl });
192 | const renderer: WebGLRenderer = overlay["renderer"];
193 | let scene: Object3D, camera: Camera;
194 | const renderSpy = jest
195 | .spyOn(renderer, "render")
196 | .mockImplementation((s, c) => {
197 | scene = s;
198 | camera = c;
199 | });
200 | const resetStateSpy = jest.spyOn(renderer, "resetState");
201 |
202 | overlay.onDraw({ gl, transformer });
203 |
204 | expect(renderSpy).toHaveBeenCalled();
205 | expect(scene).toBe(overlay.scene);
206 | expect(camera.projectionMatrix).toEqual(
207 | new Matrix4().fromArray(projMatrixArray)
208 | );
209 | expect(resetStateSpy).toHaveBeenCalledAfter(renderSpy);
210 | });
211 |
212 | test("onBeforeDraw gets called before render", () => {
213 | overlay.onContextRestored({ gl });
214 | const renderer: WebGLRenderer = overlay["renderer"];
215 | const renderSpy = jest
216 | .spyOn(renderer, "render")
217 | .mockImplementation(() => void 0);
218 |
219 | overlay.onBeforeDraw = jest.fn();
220 | overlay.onDraw({
221 | gl,
222 | transformer,
223 | });
224 |
225 | expect(overlay.onBeforeDraw).toHaveBeenCalled();
226 | expect(overlay.onBeforeDraw).toHaveBeenCalledBefore(renderSpy);
227 | });
228 |
229 | test("onContextLost disposes of renderer", () => {
230 | overlay.onContextRestored({ gl });
231 |
232 | const renderer: WebGLRenderer = overlay["renderer"];
233 | const disposeSpy = jest.spyOn(renderer, "dispose");
234 | overlay.onContextLost();
235 |
236 | expect(disposeSpy).toHaveBeenCalled();
237 | expect(overlay["renderer"]).toBeNull();
238 | });
239 |
240 | test("requestRedraw is forwarded to overlay", () => {
241 | overlay.requestRedraw();
242 |
243 | expect(overlay["overlay"].requestRedraw).toHaveBeenCalledWith();
244 | });
245 |
246 | test("requestStateUpdate is forwarded to overlay", () => {
247 | overlay.requestStateUpdate();
248 |
249 | expect(overlay["overlay"].requestStateUpdate).toHaveBeenCalledWith();
250 | });
251 | });
252 |
253 | describe("setUpAxis() / scene orientation", () => {
254 | const latLngAlt = { lat: 0, lng: 0, altitude: 10 };
255 |
256 | beforeEach(() => {
257 | const mockedUtil = util as jest.Mocked;
258 | mockedUtil.latLngToVector3Relative.mockImplementation(
259 | (p, r, target = new Vector3()) => {
260 | return target.set(1, 2, 3);
261 | }
262 | );
263 | });
264 |
265 | test.each([
266 | [undefined, { x: 1, y: 2, z: 3 }],
267 | ["Z", { x: 1, y: 2, z: 3 }],
268 | ["Y", { x: 1, y: 3, z: -2 }],
269 | [new Vector3(1, 0, 0), { x: 3, y: 2, z: -1 }],
270 | ])("upAxis: %s", (upAxis, expectedCoords) => {
271 | const overlay = new ThreeJSOverlayView({
272 | upAxis: upAxis as ThreeJSOverlayViewOptions["upAxis"],
273 | });
274 |
275 | const v3 = overlay.latLngAltitudeToVector3(latLngAlt);
276 | expect(v3.x).toBeCloseTo(expectedCoords.x, 8);
277 | expect(v3.y).toBeCloseTo(expectedCoords.y, 8);
278 | expect(v3.z).toBeCloseTo(expectedCoords.z, 8);
279 | });
280 |
281 | test("error for invalid upAxis values", () => {
282 | const mock = jest.spyOn(console, "warn").mockImplementation(() => void 0);
283 | const overlay = new ThreeJSOverlayView({
284 | upAxis: "a" as ThreeJSOverlayViewOptions["upAxis"],
285 | });
286 |
287 | expect(mock).toHaveBeenCalled();
288 |
289 | // check that the default z-up is used
290 | const v3 = overlay.latLngAltitudeToVector3(latLngAlt);
291 |
292 | expect(v3.x).toBeCloseTo(1, 8);
293 | expect(v3.y).toBeCloseTo(2, 8);
294 | expect(v3.z).toBeCloseTo(3, 8);
295 | });
296 | });
297 |
298 | describe("latLngAltitudeToVector3()", () => {
299 | let mockedUtil: jest.Mocked;
300 | beforeEach(() => {
301 | mockedUtil = jest.mocked(util);
302 | const { latLngToVector3Relative } = mockedUtil;
303 |
304 | latLngToVector3Relative.mockImplementation(
305 | (p, r, target = new Vector3()) => {
306 | return target.set(1, 2, 3);
307 | }
308 | );
309 | });
310 |
311 | test("calls util-functions", () => {
312 | const overlay = new ThreeJSOverlayView({
313 | anchor: { lat: 5, lng: 6, altitude: 7 },
314 | });
315 | const p = { lat: 0, lng: 0, altitude: 0 };
316 | const v3 = overlay.latLngAltitudeToVector3(p);
317 |
318 | expect(mockedUtil.latLngToVector3Relative).toHaveBeenCalled();
319 | expect(v3).toEqual(new Vector3(1, 2, 3));
320 | });
321 |
322 | test("writes value to target parameter", () => {
323 | const overlay = new ThreeJSOverlayView({
324 | anchor: { lat: 5, lng: 6, altitude: 7 },
325 | });
326 | const p = { lat: 0, lng: 0, altitude: 0 };
327 | const t = new Vector3();
328 | const v3 = overlay.latLngAltitudeToVector3(p, t);
329 |
330 | expect(mockedUtil.latLngToVector3Relative).toHaveBeenCalled();
331 | expect(v3).toBe(t);
332 | expect(t).toEqual(new Vector3(1, 2, 3));
333 | });
334 | });
335 |
336 | describe("addDefaultLighting()", () => {
337 | test("lights are added to the default scene", () => {
338 | const overlay = new ThreeJSOverlayView();
339 |
340 | const lights: Light[] = [];
341 | overlay.scene.traverse((o) => {
342 | if ((o as Light).isLight) lights.push(o as Light);
343 | });
344 |
345 | expect(lights).not.toHaveLength(0);
346 | });
347 |
348 | test("addDefaultLighting:false", () => {
349 | const overlay = new ThreeJSOverlayView({ addDefaultLighting: false });
350 |
351 | const lights: Light[] = [];
352 | overlay.scene.traverse((o) => {
353 | if ((o as Light).isLight) lights.push(o as Light);
354 | });
355 |
356 | expect(lights).toHaveLength(0);
357 | });
358 | });
359 |
360 | describe("raycast()", () => {
361 | let overlay: ThreeJSOverlayView;
362 | let camera: PerspectiveCamera;
363 | let box: Mesh;
364 |
365 | // these values were taken from a running application and are known to work
366 | const projMatrix = [
367 | 0.024288994132302996, -0.0001544860884193919, -0.00004410021260124961,
368 | -0.00004410021260124961, 6.275603421503094e-20, 0.017096574772793482,
369 | -0.002943529080808796, -0.002943529080808796, -0.00028262805230606344,
370 | -0.01327650198026164, -0.0037899629741724055, -0.0037899629741724055,
371 | -0.10144748239547549, 0.2775102128618734, 0.4125525158446316,
372 | 1.079219172577191,
373 | ];
374 | const boxPosition = new Vector3(0.12366377626911729, 0, 52.06138372088319);
375 | const mouseHitPosition = new Vector2(-0.131, -0.464);
376 |
377 | beforeEach(() => {
378 | overlay = new ThreeJSOverlayView();
379 |
380 | // this could be done by providing a mocked CoordinateTransformer
381 | // to the onDraw function, but this is arguably easier (although
382 | // it's not ideal to access protected members)
383 | camera = overlay["camera"];
384 | camera.projectionMatrix.fromArray(projMatrix);
385 |
386 | box = new Mesh(new BoxGeometry());
387 | box.position.copy(boxPosition);
388 | });
389 |
390 | test("returns an empty array for an empty scene", () => {
391 | const res = overlay.raycast(new Vector2(0, 0));
392 | expect(res).toEqual([]);
393 | });
394 |
395 | test("returns correct results in a known to work setting", () => {
396 | overlay.scene.add(box);
397 | box.updateMatrixWorld(true);
398 |
399 | // check for no hit at [0,0]
400 | expect(overlay.raycast(new Vector2(0, 0))).toEqual([]);
401 |
402 | let res;
403 |
404 | // we know where the box would be rendered
405 | res = overlay.raycast(mouseHitPosition);
406 | expect(res).toHaveLength(1);
407 | expect(res[0].object).toBe(box);
408 |
409 | // check that it ignores {recursive:false} here and returns the same result
410 | const res2 = overlay.raycast(mouseHitPosition, { recursive: false });
411 | expect(res2).toEqual(res);
412 |
413 | // test calls with explicit object-list
414 | res = overlay.raycast(mouseHitPosition, [box], { recursive: false });
415 | expect(res).toEqual(res);
416 |
417 | const box2 = new Mesh(new BoxGeometry());
418 | res = overlay.raycast(mouseHitPosition, [box2]);
419 | expect(res).toEqual([]);
420 |
421 | // test recursion
422 | const g = new Group();
423 | g.add(box);
424 | res = overlay.raycast(mouseHitPosition, [g], { recursive: false });
425 | expect(res).toEqual([]);
426 | });
427 |
428 | test("sets and restores raycaster parameters", () => {
429 | const raycaster = overlay["raycaster"];
430 |
431 | const origParams = {} as unknown as RaycasterParameters;
432 | const customParams = {} as unknown as RaycasterParameters;
433 |
434 | let currParams = origParams;
435 | let intersectParams = null;
436 |
437 | const setParamsMock = jest.fn((v) => (currParams = v));
438 | const getParamsMock = jest.fn(() => origParams);
439 |
440 | jest.spyOn(raycaster, "intersectObjects").mockImplementation(() => {
441 | intersectParams = currParams;
442 | return [];
443 | });
444 |
445 | Object.defineProperty(raycaster, "params", {
446 | get: getParamsMock,
447 | set: setParamsMock,
448 | });
449 |
450 | overlay.scene.add(box);
451 | box.updateMatrixWorld(true);
452 |
453 | overlay.raycast(mouseHitPosition, { raycasterParameters: customParams });
454 |
455 | expect(setParamsMock).toHaveBeenCalledTimes(2);
456 |
457 | const [[arg1], [arg2]] = setParamsMock.mock.calls;
458 | expect(arg1).toBe(customParams);
459 | expect(arg2).toBe(origParams);
460 | expect(intersectParams).toBe(customParams);
461 | });
462 | });
463 |
--------------------------------------------------------------------------------
/src/__tests__/util.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { initialize } from "@googlemaps/jest-mocks";
18 | import {
19 | latLngToXY,
20 | latLngToVector3Relative,
21 | toLatLngAltitudeLiteral,
22 | xyToLatLng,
23 | } from "../util";
24 |
25 | beforeEach(() => {
26 | initialize();
27 | });
28 |
29 | describe("toLatLngAltitudeLiteral()", () => {
30 | test.each([
31 | ["LatLngLiteral", { lat: 10, lng: 20 }, { lat: 10, lng: 20, altitude: 0 }],
32 | [
33 | "LatLngAltitudeLiteral",
34 | { lat: 10, lng: 20, altitude: 30 },
35 | { lat: 10, lng: 20, altitude: 30 },
36 | ],
37 | ["LatLng", { lat: 10, lng: 20 }, { lat: 10, lng: 20, altitude: 0 }],
38 | [
39 | "LatLngAltitude",
40 | { lat: 10, lng: 20, altitude: 30 },
41 | { lat: 10, lng: 20, altitude: 30 },
42 | ],
43 | ] as const)("toLatLngAltitudeLiteral: %p", (type, json, output) => {
44 | let input: Parameters[0] = json;
45 |
46 | if (type === "LatLng" || type === "LatLngAltitude") {
47 | input = new google.maps[type]({ lat: 0, lng: 0 });
48 | (input as google.maps.LatLng).toJSON = jest.fn(() => json);
49 | }
50 |
51 | expect(toLatLngAltitudeLiteral(input)).toEqual(output);
52 | });
53 | });
54 |
55 | test.each([
56 | [
57 | { lng: 0, lat: 0 },
58 | { x: 0, y: 0 },
59 | ],
60 | [
61 | { lng: -90, lat: 45 },
62 | { x: -10007559.105973555, y: 5615239.936637378 },
63 | ],
64 | [
65 | { lng: 90, lat: -45 },
66 | { x: 10007559.105973555, y: -5615239.936637378 },
67 | ],
68 | [
69 | { lng: 90, lat: 45 },
70 | { x: 10007559.105973555, y: 5615239.936637378 },
71 | ],
72 | [
73 | { lng: -90, lat: -45 },
74 | { x: -10007559.105973555, y: -5615239.936637378 },
75 | ],
76 | [
77 | { lng: 151.2093, lat: -33.8688 },
78 | { x: 16813733.4125, y: -4006716.49009 },
79 | ],
80 | ])(
81 | "latLngToXY and xyToLatLng are correct for %p",
82 | (latLng: google.maps.LatLngLiteral, expected: { x: number; y: number }) => {
83 | const [x, y] = latLngToXY(latLng);
84 | expect(x).toBeCloseTo(expected.x);
85 | expect(y).toBeCloseTo(expected.y);
86 |
87 | const { lat, lng } = xyToLatLng([x, y]);
88 | expect(lat).toBeCloseTo(latLng.lat);
89 | expect(lng).toBeCloseTo(latLng.lng);
90 | }
91 | );
92 |
93 | test.each([
94 | // 0 same
95 | {
96 | latLng: { lat: 0, lng: 0 },
97 | reference: { lat: 0, lng: 0 },
98 | relative: { x: 0, y: 0 },
99 | },
100 | // 1 northwest of reference
101 | {
102 | latLng: { lat: 0, lng: 0 },
103 | reference: { lat: -1, lng: 1 },
104 | relative: {
105 | x: -111178.17,
106 | y: 111183.81,
107 | },
108 | },
109 | // 2 northeast of reference
110 | {
111 | latLng: { lat: 0, lng: 2 },
112 | reference: { lat: -1, lng: 1 },
113 | relative: {
114 | x: 111178.17,
115 | y: 111183.81,
116 | },
117 | },
118 | // 3 southeast of reference
119 | {
120 | latLng: { lat: -2, lng: 2 },
121 | reference: { lat: -1, lng: 1 },
122 | relative: {
123 | x: 111178.17,
124 | y: -111217.69,
125 | },
126 | },
127 | // 4 southwest of reference
128 | {
129 | latLng: { lat: -2, lng: 0 },
130 | reference: { lat: -1, lng: 1 },
131 | relative: {
132 | x: -111178.17,
133 | y: -111217.69,
134 | },
135 | },
136 | {
137 | latLng: { lat: 48.861168, lng: 2.324197 },
138 | reference: { lat: 48.862676, lng: 2.319095 },
139 | relative: {
140 | x: 373.22,
141 | y: -167.68,
142 | },
143 | },
144 | ])(
145 | "latLngToVector3Relative is correct: %# %j",
146 | ({ latLng, reference, relative }) => {
147 | const vector = latLngToVector3Relative(
148 | { ...latLng, altitude: 0 },
149 | { ...reference, altitude: 0 }
150 | );
151 | expect(vector.x).toBeCloseTo(relative.x, 2);
152 | expect(vector.y).toBeCloseTo(relative.y, 2);
153 | expect(vector.z).toBeCloseTo(0, 2);
154 | }
155 | );
156 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export * from "./three";
18 | export * from "./util";
19 |
--------------------------------------------------------------------------------
/src/three.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {
18 | DirectionalLight,
19 | Euler,
20 | HemisphereLight,
21 | Intersection,
22 | MathUtils,
23 | Matrix4,
24 | Object3D,
25 | PCFSoftShadowMap,
26 | PerspectiveCamera,
27 | Quaternion,
28 | Raycaster,
29 | RaycasterParameters,
30 | REVISION,
31 | Scene,
32 | Vector2,
33 | Vector3,
34 | WebGLRenderer,
35 | } from "three";
36 | import { latLngToVector3Relative, toLatLngAltitudeLiteral } from "./util";
37 |
38 | import type { LatLngTypes } from "./util";
39 |
40 | // Since r162, the sRGBEncoding constant is no longer exported from three.
41 | // The value is kept here to keep compatibility with older three.js versions.
42 | // This will be removed with the next major release.
43 | const sRGBEncoding = 3001;
44 |
45 | const DEFAULT_UP = new Vector3(0, 0, 1);
46 |
47 | export interface RaycastOptions {
48 | /**
49 | * Set to true to also test children of the specified objects for
50 | * intersections.
51 | *
52 | * @default false
53 | */
54 | recursive?: boolean;
55 |
56 | /**
57 | * Update the inverse-projection-matrix before casting the ray (set this
58 | * to false if you need to run multiple raycasts for the same frame).
59 | *
60 | * @default true
61 | */
62 | updateMatrix?: boolean;
63 |
64 | /**
65 | * Additional parameters to pass to the three.js raycaster.
66 | *
67 | * @see https://threejs.org/docs/#api/en/core/Raycaster.params
68 | */
69 | raycasterParameters?: RaycasterParameters;
70 | }
71 |
72 | export interface ThreeJSOverlayViewOptions {
73 | /**
74 | * The anchor for the scene.
75 | *
76 | * @default {lat: 0, lng: 0, altitude: 0}
77 | */
78 | anchor?: LatLngTypes;
79 |
80 | /**
81 | * The axis pointing up in the scene. Can be specified as "Z", "Y" or a
82 | * Vector3, in which case the normalized vector will become the up-axis.
83 | *
84 | * @default "Z"
85 | */
86 | upAxis?: "Z" | "Y" | Vector3;
87 |
88 | /**
89 | * The map the overlay will be added to.
90 | * Can be set at initialization or by calling `setMap(map)`.
91 | */
92 | map?: google.maps.Map;
93 |
94 | /**
95 | * The scene object to render in the overlay. If no scene is specified, a
96 | * new scene is created and can be accessed via `overlay.scene`.
97 | */
98 | scene?: Scene;
99 |
100 | /**
101 | * The animation mode controls when the overlay will redraw, either
102 | * continuously (`always`) or on demand (`ondemand`). When using the
103 | * on demand mode, the overlay will re-render whenever the map renders
104 | * (camera movements) or when `requestRedraw()` is called.
105 | *
106 | * To achieve animations in this mode, you can either use an outside
107 | * animation-loop that calls `requestRedraw()` as long as needed or call
108 | * `requestRedraw()` from within the `onBeforeRender` function to
109 | *
110 | * @default "ondemand"
111 | */
112 | animationMode?: "always" | "ondemand";
113 |
114 | /**
115 | * Add default lighting to the scene.
116 | * @default true
117 | */
118 | addDefaultLighting?: boolean;
119 | }
120 |
121 | /* eslint-disable @typescript-eslint/no-empty-function */
122 |
123 | /**
124 | * Add a [three.js](https://threejs.org) scene as a [Google Maps WebGLOverlayView](http://goo.gle/WebGLOverlayView-ref).
125 | */
126 | export class ThreeJSOverlayView implements google.maps.WebGLOverlayView {
127 | /** {@inheritDoc ThreeJSOverlayViewOptions.scene} */
128 | public readonly scene: Scene;
129 |
130 | /** {@inheritDoc ThreeJSOverlayViewOptions.animationMode} */
131 | public animationMode: "always" | "ondemand" = "ondemand";
132 |
133 | /** {@inheritDoc ThreeJSOverlayViewOptions.anchor} */
134 | protected anchor: google.maps.LatLngAltitudeLiteral;
135 | protected readonly camera: PerspectiveCamera;
136 | protected readonly rotationArray: Float32Array = new Float32Array(3);
137 | protected readonly rotationInverse: Quaternion = new Quaternion();
138 | protected readonly projectionMatrixInverse = new Matrix4();
139 |
140 | protected readonly overlay: google.maps.WebGLOverlayView;
141 | protected renderer: WebGLRenderer;
142 | protected raycaster: Raycaster = new Raycaster();
143 |
144 | constructor(options: ThreeJSOverlayViewOptions = {}) {
145 | const {
146 | anchor = { lat: 0, lng: 0, altitude: 0 },
147 | upAxis = "Z",
148 | scene,
149 | map,
150 | animationMode = "ondemand",
151 | addDefaultLighting = true,
152 | } = options;
153 |
154 | this.overlay = new google.maps.WebGLOverlayView();
155 | this.renderer = null;
156 | this.camera = null;
157 | this.animationMode = animationMode;
158 |
159 | this.setAnchor(anchor);
160 | this.setUpAxis(upAxis);
161 |
162 | this.scene = scene ?? new Scene();
163 | if (addDefaultLighting) this.initSceneLights();
164 |
165 | this.overlay.onAdd = this.onAdd.bind(this);
166 | this.overlay.onRemove = this.onRemove.bind(this);
167 | this.overlay.onContextLost = this.onContextLost.bind(this);
168 | this.overlay.onContextRestored = this.onContextRestored.bind(this);
169 | this.overlay.onStateUpdate = this.onStateUpdate.bind(this);
170 | this.overlay.onDraw = this.onDraw.bind(this);
171 |
172 | this.camera = new PerspectiveCamera();
173 |
174 | if (map) {
175 | this.setMap(map);
176 | }
177 | }
178 |
179 | /**
180 | * Sets the anchor-point.
181 | * @param anchor
182 | */
183 | public setAnchor(anchor: LatLngTypes) {
184 | this.anchor = toLatLngAltitudeLiteral(anchor);
185 | }
186 |
187 | /**
188 | * Sets the axis to use as "up" in the scene.
189 | * @param axis
190 | */
191 | public setUpAxis(axis: "Y" | "Z" | Vector3): void {
192 | const upVector = new Vector3(0, 0, 1);
193 | if (typeof axis !== "string") {
194 | upVector.copy(axis);
195 | } else {
196 | if (axis.toLowerCase() === "y") {
197 | upVector.set(0, 1, 0);
198 | } else if (axis.toLowerCase() !== "z") {
199 | console.warn(`invalid value '${axis}' specified as upAxis`);
200 | }
201 | }
202 |
203 | upVector.normalize();
204 |
205 | const q = new Quaternion();
206 | q.setFromUnitVectors(upVector, DEFAULT_UP);
207 |
208 | // inverse rotation is needed in latLngAltitudeToVector3()
209 | this.rotationInverse.copy(q).invert();
210 |
211 | // copy to rotationArray for transformer.fromLatLngAltitude()
212 | const euler = new Euler().setFromQuaternion(q, "XYZ");
213 | this.rotationArray[0] = MathUtils.radToDeg(euler.x);
214 | this.rotationArray[1] = MathUtils.radToDeg(euler.y);
215 | this.rotationArray[2] = MathUtils.radToDeg(euler.z);
216 | }
217 |
218 | /**
219 | * Runs raycasting for the specified screen-coordinates against all objects
220 | * in the scene.
221 | *
222 | * @param p normalized screenspace coordinates of the
223 | * mouse-cursor. x/y are in range [-1, 1], y is pointing up.
224 | * @param options raycasting options. In this case the `recursive` option
225 | * has no effect as it is always recursive.
226 | * @return the list of intersections
227 | */
228 | public raycast(p: Vector2, options?: RaycastOptions): Intersection[];
229 |
230 | /**
231 | * Runs raycasting for the specified screen-coordinates against the specified
232 | * list of objects.
233 | *
234 | * Note for typescript users: the returned Intersection objects can only be
235 | * properly typed for non-recursive lookups (this is handled by the internal
236 | * signature below).
237 | *
238 | * @param p normalized screenspace coordinates of the
239 | * mouse-cursor. x/y are in range [-1, 1], y is pointing up.
240 | * @param objects list of objects to test
241 | * @param options raycasting options.
242 | */
243 | public raycast(
244 | p: Vector2,
245 | objects: Object3D[],
246 | options?: RaycastOptions & { recursive: true }
247 | ): Intersection[];
248 |
249 | // additional signature to enable typings in returned objects when possible
250 | public raycast(
251 | p: Vector2,
252 | objects: T[],
253 | options?:
254 | | Omit
255 | | (RaycastOptions & { recursive: false })
256 | ): Intersection[];
257 |
258 | // implemetation
259 | public raycast(
260 | p: Vector2,
261 | optionsOrObjects?: Object3D[] | RaycastOptions,
262 | options: RaycastOptions = {}
263 | ): Intersection[] {
264 | let objects: Object3D[];
265 | if (Array.isArray(optionsOrObjects)) {
266 | objects = optionsOrObjects || null;
267 | } else {
268 | objects = [this.scene];
269 | options = { ...optionsOrObjects, recursive: true };
270 | }
271 |
272 | const {
273 | updateMatrix = true,
274 | recursive = false,
275 | raycasterParameters,
276 | } = options;
277 |
278 | // when `raycast()` is called from within the `onBeforeRender()` callback,
279 | // the mvp-matrix for this frame has already been computed and stored in
280 | // `this.camera.projectionMatrix`.
281 | // The mvp-matrix transforms world-space meters to clip-space
282 | // coordinates. The inverse matrix created here does the exact opposite
283 | // and converts clip-space coordinates to world-space.
284 | if (updateMatrix) {
285 | this.projectionMatrixInverse.copy(this.camera.projectionMatrix).invert();
286 | }
287 |
288 | // create two points (with different depth) from the mouse-position and
289 | // convert them into world-space coordinates to set up the ray.
290 | this.raycaster.ray.origin
291 | .set(p.x, p.y, 0)
292 | .applyMatrix4(this.projectionMatrixInverse);
293 |
294 | this.raycaster.ray.direction
295 | .set(p.x, p.y, 0.5)
296 | .applyMatrix4(this.projectionMatrixInverse)
297 | .sub(this.raycaster.ray.origin)
298 | .normalize();
299 |
300 | // back up the raycaster parameters
301 | const oldRaycasterParams = this.raycaster.params;
302 | if (raycasterParameters) {
303 | this.raycaster.params = raycasterParameters;
304 | }
305 |
306 | const results = this.raycaster.intersectObjects(objects, recursive);
307 |
308 | // reset raycaster params to whatever they were before
309 | this.raycaster.params = oldRaycasterParams;
310 |
311 | return results;
312 | }
313 |
314 | /**
315 | * Overwrite this method to handle any GL state updates outside the
316 | * render animation frame.
317 | * @param options
318 | */
319 | public onStateUpdate(options: google.maps.WebGLStateOptions): void;
320 | public onStateUpdate(): void {}
321 |
322 | /**
323 | * Overwrite this method to fetch or create intermediate data structures
324 | * before the overlay is drawn that don’t require immediate access to the
325 | * WebGL rendering context.
326 | */
327 | public onAdd(): void {}
328 |
329 | /**
330 | * Overwrite this method to update your scene just before a new frame is
331 | * drawn.
332 | */
333 | public onBeforeDraw(): void {}
334 |
335 | /**
336 | * This method is called when the overlay is removed from the map with
337 | * `overlay.setMap(null)`, and is where you can remove all intermediate
338 | * objects created in onAdd.
339 | */
340 | public onRemove(): void {}
341 |
342 | /**
343 | * Triggers the map to update GL state.
344 | */
345 | public requestStateUpdate(): void {
346 | this.overlay.requestStateUpdate();
347 | }
348 |
349 | /**
350 | * Triggers the map to redraw a frame.
351 | */
352 | public requestRedraw(): void {
353 | this.overlay.requestRedraw();
354 | }
355 |
356 | /**
357 | * Returns the map the overlay is added to.
358 | */
359 | public getMap(): google.maps.Map {
360 | return this.overlay.getMap();
361 | }
362 |
363 | /**
364 | * Adds the overlay to the map.
365 | * @param map The map to access the div, model and view state.
366 | */
367 | public setMap(map: google.maps.Map): void {
368 | this.overlay.setMap(map);
369 | }
370 |
371 | /**
372 | * Adds the given listener function to the given event name. Returns an
373 | * identifier for this listener that can be used with
374 | * google.maps.event.removeListener
.
375 | */
376 | public addListener(
377 | eventName: string,
378 | handler: (...args: unknown[]) => void
379 | ): google.maps.MapsEventListener {
380 | return this.overlay.addListener(eventName, handler);
381 | }
382 |
383 | /**
384 | * This method is called once the rendering context is available. Use it to
385 | * initialize or bind any WebGL state such as shaders or buffer objects.
386 | * @param options that allow developers to restore the GL context.
387 | */
388 | public onContextRestored({ gl }: google.maps.WebGLStateOptions) {
389 | this.renderer = new WebGLRenderer({
390 | canvas: gl.canvas,
391 | context: gl,
392 | ...gl.getContextAttributes(),
393 | });
394 | this.renderer.autoClear = false;
395 | this.renderer.autoClearDepth = false;
396 | this.renderer.shadowMap.enabled = true;
397 | this.renderer.shadowMap.type = PCFSoftShadowMap;
398 |
399 | // Since r152, default outputColorSpace is SRGB
400 | // Deprecated outputEncoding kept for backwards compatibility
401 | if (Number(REVISION) < 152) {
402 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
403 | (this.renderer as any).outputEncoding = sRGBEncoding;
404 | }
405 |
406 | const { width, height } = gl.canvas;
407 | this.renderer.setViewport(0, 0, width, height);
408 | }
409 |
410 | /**
411 | * This method is called when the rendering context is lost for any reason,
412 | * and is where you should clean up any pre-existing GL state, since it is
413 | * no longer needed.
414 | */
415 | public onContextLost() {
416 | if (!this.renderer) {
417 | return;
418 | }
419 |
420 | this.renderer.dispose();
421 | this.renderer = null;
422 | }
423 |
424 | /**
425 | * Implement this method to draw WebGL content directly on the map. Note
426 | * that if the overlay needs a new frame drawn then call {@link
427 | * ThreeJSOverlayView.requestRedraw}.
428 | * @param options that allow developers to render content to an associated
429 | * Google basemap.
430 | */
431 | public onDraw({ gl, transformer }: google.maps.WebGLDrawOptions): void {
432 | this.camera.projectionMatrix.fromArray(
433 | transformer.fromLatLngAltitude(this.anchor, this.rotationArray)
434 | );
435 |
436 | gl.disable(gl.SCISSOR_TEST);
437 |
438 | this.onBeforeDraw();
439 |
440 | this.renderer.render(this.scene, this.camera);
441 | this.renderer.resetState();
442 |
443 | if (this.animationMode === "always") this.requestRedraw();
444 | }
445 |
446 | /**
447 | * Convert coordinates from WGS84 Latitude Longitude to world-space
448 | * coordinates while taking the origin and orientation into account.
449 | */
450 | public latLngAltitudeToVector3(
451 | position: LatLngTypes,
452 | target = new Vector3()
453 | ) {
454 | latLngToVector3Relative(
455 | toLatLngAltitudeLiteral(position),
456 | this.anchor,
457 | target
458 | );
459 |
460 | target.applyQuaternion(this.rotationInverse);
461 |
462 | return target;
463 | }
464 |
465 | // MVCObject interface forwarded to the overlay
466 |
467 | /**
468 | * Binds a View to a Model.
469 | */
470 | public bindTo(
471 | key: string,
472 | target: google.maps.MVCObject,
473 | targetKey?: string,
474 | noNotify?: boolean
475 | ): void {
476 | this.overlay.bindTo(key, target, targetKey, noNotify);
477 | }
478 |
479 | /**
480 | * Gets a value.
481 | */
482 | public get(key: string) {
483 | return this.overlay.get(key);
484 | }
485 |
486 | /**
487 | * Notify all observers of a change on this property. This notifies both
488 | * objects that are bound to the object's property as well as the object
489 | * that it is bound to.
490 | */
491 | public notify(key: string): void {
492 | this.overlay.notify(key);
493 | }
494 |
495 | /**
496 | * Sets a value.
497 | */
498 | public set(key: string, value: unknown): void {
499 | this.overlay.set(key, value);
500 | }
501 |
502 | /**
503 | * Sets a collection of key-value pairs.
504 | */
505 | public setValues(values?: object): void {
506 | this.overlay.setValues(values);
507 | }
508 |
509 | /**
510 | * Removes a binding. Unbinding will set the unbound property to the current
511 | * value. The object will not be notified, as the value has not changed.
512 | */
513 | public unbind(key: string): void {
514 | this.overlay.unbind(key);
515 | }
516 |
517 | /**
518 | * Removes all bindings.
519 | */
520 | public unbindAll(): void {
521 | this.overlay.unbindAll();
522 | }
523 |
524 | /**
525 | * Creates lights (directional and hemisphere light) to illuminate the model
526 | * (roughly approximates the lighting of buildings in maps)
527 | */
528 | private initSceneLights() {
529 | const hemiLight = new HemisphereLight(0xffffff, 0x444444, 1);
530 | hemiLight.position.set(0, -0.2, 1).normalize();
531 |
532 | const dirLight = new DirectionalLight(0xffffff);
533 | dirLight.position.set(0, 10, 100);
534 |
535 | this.scene.add(hemiLight, dirLight);
536 | }
537 | }
538 |
--------------------------------------------------------------------------------
/src/util.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { MathUtils, Vector3 } from "three";
18 |
19 | export type LatLngTypes =
20 | | google.maps.LatLngLiteral
21 | | google.maps.LatLng
22 | | google.maps.LatLngAltitudeLiteral
23 | | google.maps.LatLngAltitude;
24 |
25 | // shorthands for math-functions, makes equations more readable
26 | const { atan, cos, exp, log, tan, PI } = Math;
27 | const { degToRad, radToDeg } = MathUtils;
28 |
29 | export const EARTH_RADIUS = 6371010.0;
30 | export const WORLD_SIZE = Math.PI * EARTH_RADIUS;
31 |
32 | /**
33 | * Converts any of the supported position formats into the
34 | * google.maps.LatLngAltitudeLiteral format used for the calculations.
35 | * @param point
36 | */
37 | export function toLatLngAltitudeLiteral(
38 | point: LatLngTypes
39 | ): google.maps.LatLngAltitudeLiteral {
40 | if (
41 | window.google &&
42 | google.maps &&
43 | (point instanceof google.maps.LatLng ||
44 | point instanceof google.maps.LatLngAltitude)
45 | ) {
46 | return { altitude: 0, ...point.toJSON() };
47 | }
48 |
49 | return { altitude: 0, ...(point as google.maps.LatLngLiteral) };
50 | }
51 |
52 | /**
53 | * Converts latitude and longitude to world space coordinates relative
54 | * to a reference location with y up.
55 | */
56 | export function latLngToVector3Relative(
57 | point: google.maps.LatLngAltitudeLiteral,
58 | reference: google.maps.LatLngAltitudeLiteral,
59 | target = new Vector3()
60 | ) {
61 | const [px, py] = latLngToXY(point);
62 | const [rx, ry] = latLngToXY(reference);
63 |
64 | target.set(px - rx, py - ry, 0);
65 |
66 | // apply the spherical mercator scale-factor for the reference latitude
67 | target.multiplyScalar(cos(degToRad(reference.lat)));
68 |
69 | target.z = point.altitude - reference.altitude;
70 |
71 | return target;
72 | }
73 |
74 | /**
75 | * Converts WGS84 latitude and longitude to (uncorrected) WebMercator meters.
76 | * (WGS84 --> WebMercator (EPSG:3857))
77 | */
78 | export function latLngToXY(position: google.maps.LatLngLiteral): number[] {
79 | return [
80 | EARTH_RADIUS * degToRad(position.lng),
81 | EARTH_RADIUS * log(tan(0.25 * PI + 0.5 * degToRad(position.lat))),
82 | ];
83 | }
84 |
85 | /**
86 | * Converts WebMercator meters to WGS84 latitude/longitude.
87 | * (WebMercator (EPSG:3857) --> WGS84)
88 | */
89 | export function xyToLatLng(p: number[]): google.maps.LatLngLiteral {
90 | const [x, y] = p;
91 |
92 | return {
93 | lat: radToDeg(PI * 0.5 - 2.0 * atan(exp(-y / EARTH_RADIUS))),
94 | lng: radToDeg(x) / EARTH_RADIUS,
95 | };
96 | }
97 |
--------------------------------------------------------------------------------
/tsconfig.examples.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": false,
5 | "noEmit": true,
6 | "outDir": null,
7 | "declarationDir": null,
8 | "noImplicitAny": false,
9 | "resolveJsonModule": true
10 | },
11 | "include": ["src/**/*", "examples/**/*"]
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "declarationDir": "./dist",
5 | "noImplicitAny": true,
6 | "outDir": "./dist",
7 | "sourceMap": true,
8 | "esModuleInterop": true,
9 | "lib": ["DOM", "ESNext", "ES2019"],
10 | "target": "ES2020",
11 | "module": "ES2020",
12 | "moduleResolution": "node",
13 | "skipLibCheck": true,
14 | "resolveJsonModule": true
15 | },
16 | "include": ["src/**/*"],
17 | "exclude": ["node_modules", "./dist"]
18 | }
19 |
--------------------------------------------------------------------------------
/typedoc.cjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | module.exports = {
18 | out: "docs",
19 | exclude: ["**/node_modules/**", "**/*.spec.ts", "**/*.test.ts"],
20 | name: "@googlemaps/three",
21 | excludePrivate: true,
22 | media: "assets",
23 | };
24 |
--------------------------------------------------------------------------------