├── .changeset ├── README.md └── config.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── nodejs.yml │ └── release.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── CHANGELOG.md ├── README.md ├── package.json ├── packages ├── cannon-worker-api │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── .prettierrc.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── body.ts │ │ ├── cannon-worker-api.ts │ │ ├── index.ts │ │ ├── props-to-body.js │ │ ├── types.ts │ │ ├── worker.d.ts │ │ └── worker │ │ │ ├── contact-material.ts │ │ │ ├── index.ts │ │ │ ├── material.ts │ │ │ ├── operations │ │ │ ├── add-bodies.ts │ │ │ ├── add-constraint.ts │ │ │ ├── add-ray.ts │ │ │ ├── add-raycast-vehicle.ts │ │ │ ├── add-spring.ts │ │ │ ├── index.ts │ │ │ ├── init.ts │ │ │ └── step.ts │ │ │ ├── state.ts │ │ │ ├── triplet-to-vec3.ts │ │ │ └── types.ts │ └── tsconfig.json ├── react-three-cannon-examples │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .prettierignore │ ├── .prettierrc.json │ ├── CHANGELOG.md │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ │ ├── Beetle.glb │ │ ├── bowl.glb │ │ ├── cup.glb │ │ ├── diamond.glb │ │ ├── draco-gltf │ │ │ ├── draco_decoder.js │ │ │ ├── draco_decoder.wasm │ │ │ └── draco_wasm_wrapper.js │ │ ├── pingpong.glb │ │ └── wheel.glb │ ├── src │ │ ├── App.tsx │ │ ├── colors.ts │ │ ├── demos │ │ │ ├── MondayMorning │ │ │ │ ├── createConfig.ts │ │ │ │ └── index.tsx │ │ │ ├── Pingpong │ │ │ │ ├── Text.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── lerp.d.ts │ │ │ │ └── resources │ │ │ │ │ ├── cross.jpg │ │ │ │ │ ├── firasans_regular.json │ │ │ │ │ └── ping.mp3 │ │ │ ├── Raycast │ │ │ │ ├── index.tsx │ │ │ │ └── prettyPrint.ts │ │ │ ├── RaycastVehicle │ │ │ │ ├── Chassis.tsx │ │ │ │ ├── Vehicle.tsx │ │ │ │ ├── Wheel.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── use-controls.ts │ │ │ ├── demo-Chain.tsx │ │ │ ├── demo-CompoundBody.tsx │ │ │ ├── demo-Constraints.tsx │ │ │ ├── demo-ConvexPolyhedron.tsx │ │ │ ├── demo-CubeHeap.tsx │ │ │ ├── demo-Friction.tsx │ │ │ ├── demo-Heightfield.tsx │ │ │ ├── demo-HingeMotor.tsx │ │ │ ├── demo-KinematicCube.tsx │ │ │ ├── demo-Paused.tsx │ │ │ ├── demo-SphereDebug.tsx │ │ │ ├── demo-Triggers.tsx │ │ │ ├── demo-Trimesh.tsx │ │ │ └── index.ts │ │ ├── index.tsx │ │ ├── styles.ts │ │ └── use-toggled-control.tsx │ ├── tsconfig.json │ └── vite.config.ts └── react-three-cannon │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .npmignore │ ├── .prettierignore │ ├── .prettierrc.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── debug-context.ts │ ├── debug-provider.tsx │ ├── hooks.ts │ ├── index.tsx │ ├── physics-context.ts │ └── physics-provider.tsx │ └── tsconfig.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "minor", 10 | "ignore": ["@react-three/cannon-examples"] 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 👋 Hi there, for issues that could be related to cannon itself or threejs etc, please consider using [github discussions](https://github.com/pmndrs/use-cannon/discussions). Documentation of libraries within the ecosystem can be found at . 11 | 12 | Additionally, issues related to the debug view may belong in [cannon-es-debugger](https://github.com/pmndrs/cannon-es-debugger). 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | 2 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 3 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 4 | 5 | name: Node.js CI 6 | 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | jobs: 14 | build: 15 | name: Build 16 | needs: install 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | cache: 'yarn' 23 | node-version: 20 24 | - run: yarn install 25 | - run: npm i -g npm@latest 26 | - run: npm run build 27 | 28 | eslint: 29 | name: ESLint 30 | needs: install 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions/setup-node@v4 35 | with: 36 | cache: 'yarn' 37 | node-version: 20 38 | - run: yarn install 39 | - run: npm i -g npm@latest 40 | - run: npm run eslint 41 | 42 | install: 43 | name: Install 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: actions/setup-node@v4 48 | with: 49 | cache: 'yarn' 50 | node-version: 20 51 | - run: yarn install 52 | 53 | prettier: 54 | name: Prettier 55 | needs: install 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v4 59 | - uses: actions/setup-node@v4 60 | with: 61 | cache: 'yarn' 62 | node-version: 20 63 | - run: yarn install 64 | - run: npm i -g npm@latest 65 | - run: npm run prettier 66 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | if: github.repository_owner == 'pmndrs' 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20 23 | 24 | - name: Install Dependencies 25 | run: yarn 26 | 27 | - name: Create Release Pull Request or Publish to npm 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | publish: yarn release 32 | commit: "chore(changeset): release packages" 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .docz/ 2 | .rpt2_cache/ 3 | .yarn/ 4 | $RECYCLE.BIN/ 5 | build/ 6 | coverage/ 7 | dist/ 8 | node_modules/ 9 | types/ 10 | .DS_Store 11 | .eslintcache 12 | .idea 13 | .size-snapshot.json 14 | .vscode 15 | Desktop.ini 16 | ehthumbs.db 17 | package-lock.json 18 | Thumbs.db 19 | yarn-error.log 20 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | ./node_modules/.bin/lint-staged 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pmndrs/use-cannon Changelog 2 | 3 | ## v2.5.2 - 2023-01-05 4 | 5 | - [@pmndrs/cannon-worker-api] v2.3.2 6 | - [@react-three/cannon] v6.5.2 7 | - [@react-three/cannon-examples] v2.4.1 8 | - [`README.md`] Update shields badge to match their new routes (@bjornstar) 9 | 10 | ## v2.5.1 - 2022-11-11 11 | 12 | - [@pmndrs/cannon-worker-api] v2.3.1 13 | - [@react-three/cannon] v6.5.1 14 | - [changeset config] set updateInternalDependencies to minor (@bjornstar) 15 | - [github workflows] do not try to publish if we don't have an npm key (@bjornstar) 16 | 17 | ## v2.5.0 - 2022-11-03 18 | 19 | - [@pmndrs/cannon-worker-api] v2.3.0 20 | - [@react-three/cannon] v6.5.0 21 | - [@react-three/cannon-examples] v2.4.0 22 | - Setup changesets & publish workflow (@isaac-mason) 23 | - Do not attempt to publish on forks (@bjornstar) 24 | 25 | ## v2.4.0 - 2022-08-18 26 | 27 | - [@pmndrs/cannon-worker-api] v2.2.0 28 | - [@react-three/cannon] v6.4.0 29 | 30 | ## v2.3.0 - 2022-04-18 31 | 32 | - [@react-three/cannon] v6.3.0 33 | - [@react-three/cannon-examples] v2.3.0 34 | 35 | ## v2.2.0 - 2022-04-08 36 | 37 | - [@react-three/cannon] v6.2.0 38 | - [@react-three/cannon-examples] v2.2.0 39 | 40 | ## v2.1.0 - 2022-04-02 41 | 42 | - [@pmndrs/cannon-worker-api] v2.0.0 43 | - [@react-three/cannon] v6.0.0 44 | - [@react-three/cannon-examples] v2.0.0 45 | - Set monorepo package type to module 46 | 47 | ## v2.0.0 - 2022-04-01 48 | 49 | - [@pmndrs/cannon-worker-api] v2.0.0 50 | - [@react-three/cannon] v6.0.0 51 | - [@react-three/cannon-examples] v2.0.0 52 | 53 | ## v1.1.0 - 2022-03-19 54 | 55 | - [`package.json`] Add .json, .jsx, & .tsx to lint-staged files (@bjornstar) 56 | - [`yarn.lock`] Updated dependencies (@bjornstar) 57 | 58 | ## v1.0.1 - 2022-03-14 59 | 60 | - [`README.md`] Fix examples link (@discosultan) 61 | - Add this CHANGELOG.md (@bjornstar) 62 | - Add a version to the monorepo for better change management (@bjornstar) 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/github/actions/workflow/status/pmndrs/use-cannon/nodejs.yml?branch=master&style=flat&colorA=000000&logo=github)](https://github.com/pmndrs/use-cannon/actions/workflows/nodejs.yml) 2 | [![Discord Shield](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=ffffff)](https://discord.gg/poimandres) 3 | 4 | ![Imgur](https://imgur.com/FpBsJPL.jpg) 5 | 6 | Monorepo for [cannon-es](https://github.com/pmndrs/cannon-es) web worker packages. 7 | 8 | | Package | Description | 9 | | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | 10 | | [`@react-three/cannon`](./packages/react-three-cannon) | React hooks for [cannon-es](https://github.com/pmndrs/cannon-es). Use this in combination with [react-three-fiber](https://github.com/pmndrs/react-three-fiber). | 11 | | [`@pmndrs/cannon-worker-api`](./packages/cannon-worker-api) | Web worker api for [cannon-es](https://github.com/pmndrs/cannon-es). Used by `@react-three/cannon`. | 12 | | [`@react-three/cannon-examples`](./packages/react-three-cannon-examples) | Examples for `@react-three/cannon` | 13 | 14 | ## `use-cannon` Documentation 15 | 16 | Please see the [`@react-three/cannon` README](./packages/react-three-cannon/README.md) documentation and getting started guide for using the react hooks and jsx interface. 17 | 18 | ## Demos 19 | 20 | Check out all of our `@react-three/cannon` examples at https://cannon.pmnd.rs 21 | 22 | The code for the examples lives in [./packages/react-three-cannon-examples](./packages/react-three-cannon-examples) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pmndrs/use-cannon", 3 | "version": "2.5.2", 4 | "private": true, 5 | "description": "monorepo for @pmndrs/use-cannon", 6 | "keywords": [ 7 | "cannon", 8 | "three", 9 | "worker", 10 | "physics", 11 | "react", 12 | "react-three-fiber" 13 | ], 14 | "author": "Paul Henschel", 15 | "contributors": [ 16 | "Cody Persinger (https://github.com/codynova)", 17 | "Bjorn Stromberg (https://github.com/bjornstar)" 18 | ], 19 | "repository": "github:pmndrs/use-cannon", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "@changesets/cli": "^2.26.2", 23 | "husky": "^7.0.4", 24 | "lint-staged": "^12.1.2" 25 | }, 26 | "engines": { 27 | "node": "20", 28 | "npm": ">=8" 29 | }, 30 | "lint-staged": { 31 | "*.{js,jsx,ts,tsx}": "eslint --cache --fix", 32 | "*.{js,jsx,json,md,ts,tsx}": "prettier --write" 33 | }, 34 | "scripts": { 35 | "build": "npm run build --workspaces", 36 | "clean": "npm run clean --workspaces", 37 | "dev": "npm run dev --workspaces", 38 | "release": "npm run build && changeset publish", 39 | "eslint": "npm run eslint --workspaces", 40 | "eslint-fix": "npm run eslint-fix --workspaces", 41 | "prettier": "npm run prettier --workspaces", 42 | "prettier-fix": "npm run prettier-fix --workspaces", 43 | "postinstall": "husky install" 44 | }, 45 | "type": "module", 46 | "workspaces": [ 47 | "packages/cannon-worker-api", 48 | "packages/react-three-cannon", 49 | "packages/react-three-cannon-examples" 50 | ], 51 | "dependencies": {} 52 | } 53 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "prettier"], 7 | "overrides": [ 8 | { 9 | "extends": "plugin:@typescript-eslint/recommended", 10 | "files": ["*.ts"], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "project": ["tsconfig.json"] 14 | }, 15 | "plugins": ["@typescript-eslint", "typescript-enum"], 16 | "rules": { 17 | "@typescript-eslint/ban-types": ["error", { "extendDefaults": true, "types": { "{}": false } }], 18 | "@typescript-eslint/consistent-type-imports": "error", 19 | "@typescript-eslint/explicit-module-boundary-types": "off", 20 | "@typescript-eslint/member-ordering": [ 21 | "error", 22 | { 23 | "default": { 24 | "order": "alphabetically-case-insensitive" 25 | }, 26 | "classes": { 27 | "order": "alphabetically-case-insensitive", 28 | "memberTypes": [ 29 | "public-static-field", 30 | "protected-static-field", 31 | "private-static-field", 32 | "public-instance-field", 33 | "public-decorated-field", 34 | "public-abstract-field", 35 | "protected-instance-field", 36 | "protected-decorated-field", 37 | "protected-abstract-field", 38 | "private-instance-field", 39 | "private-decorated-field", 40 | "private-abstract-field", 41 | "static-field", 42 | "public-field", 43 | "instance-field", 44 | "protected-field", 45 | "private-field", 46 | "abstract-field", 47 | "constructor", 48 | "public-static-method", 49 | "protected-static-method", 50 | "private-static-method", 51 | "public-method", 52 | "protected-method", 53 | "private-method" 54 | ] 55 | } 56 | } 57 | ], 58 | "@typescript-eslint/no-namespace": ["error", { "allowDeclarations": true }], 59 | "@typescript-eslint/no-non-null-assertion": "error", 60 | "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }], 61 | "@typescript-eslint/quotes": [ 62 | "error", 63 | "single", 64 | { 65 | "allowTemplateLiterals": true, 66 | "avoidEscape": true 67 | } 68 | ], 69 | "@typescript-eslint/semi": ["error", "never"], 70 | "typescript-enum/no-enum": "error" 71 | } 72 | } 73 | ], 74 | "parserOptions": { 75 | "ecmaFeatures": { 76 | "jsx": true 77 | }, 78 | "ecmaVersion": 12, 79 | "sourceType": "module" 80 | }, 81 | "plugins": ["es", "simple-import-sort"], 82 | "rules": { 83 | "eol-last": ["error", "always"], 84 | "es/no-logical-assignment-operators": "error", 85 | "es/no-nullish-coalescing-operators": "error", 86 | "no-debugger": "error", 87 | "no-unused-vars": ["error", { "ignoreRestSiblings": true }], 88 | "semi": ["error", "never"], 89 | "simple-import-sort/exports": "error", 90 | "simple-import-sort/imports": "error", 91 | "sort-keys": "error" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/.npmignore: -------------------------------------------------------------------------------- 1 | !dist/ 2 | 3 | node_modules/ 4 | examples/ 5 | Thumbs.db 6 | ehthumbs.db 7 | Desktop.ini 8 | \$RECYCLE.BIN/ 9 | .DS_Store 10 | .vscode 11 | .docz/ 12 | .idea 13 | .rpt2_cache/ 14 | .eslintcache 15 | .eslintignore 16 | .eslintrc.json 17 | .github/ 18 | .husky/ 19 | .prettierignore 20 | .prettierrc.json 21 | rollup.config.js 22 | tsconfig.json 23 | tsconfig.tsbuildinfo 24 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/.prettierignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | node_modules/* 3 | package.json 4 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 110, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pmndrs/cannon-worker-api Changelog 2 | 3 | ## 2.4.0 4 | 5 | ### Minor Changes 6 | 7 | - 800a687: feat(props-to-body): support both quaternion and rotation props, prefer quaternion if both provided (@Soham1803) 8 | - 22d49ef: chore: update @types/three dev dependency 9 | 10 | ## v2.3.2 - 2023-01-05 11 | 12 | - [`README.md`] Improve links to related packages (@bjornstar) 13 | 14 | ## v2.3.1 - 2022-11-11 15 | 16 | - Make sure to include dist in npm package (@bjornstar) 17 | 18 | ## v2.3.0 - 2022-11-03 19 | 20 | - Remove subscriptions when removing bodies and vehicles (@alex-shortt) 21 | 22 | ## v2.2.0 - 2022-08-18 23 | 24 | - Add support for `frictionGravity` on WorldProps (@chnicoloso) 25 | 26 | ## v2.1.0 - 2022-04-02 27 | 28 | - New private method `postMessage` that queues the messages if there is no worker 29 | - New public method: `connect`, we instantiate the worker, add the onmessage handler and flush the messageQueue 30 | - New public method: `disconnect`, removes the onmessage handler (probably unnecessary) 31 | - `init` now takes `world` instead of `state` 32 | 33 | ## v2.0.0 - 2022-04-01 34 | 35 | - `three.js` is now a `peerDependency` and requires r139 or higher 36 | - Updated many `devDependencies` 37 | 38 | ## v1.1.0 - 2022-03-19 39 | 40 | - [WorkerRayHitEvent] from & to are optional (@bjornstar) 41 | - [WorkerRayHitEvent] Omit methods from shape type (@bjornstar) 42 | - [`src/worker`] self.postMessage should be typed to ensure we match API types (@bjornstar) 43 | - [`src/worker`] Stop using non-null assertions (@bjornstar) 44 | - [`addBodies`] If the body or target does not have a uuid, don't process the event (@bjornstar) 45 | - [`addRay`] If the body does not have a uuid, don't process the event (@bjornstar) 46 | - [`package.json`] Has one dependency: `three` (@bjornstar) 47 | - [`.eslintrc.json`] Clean up (@bjornstar) 48 | - [`.eslintrc.json`] Disallow non-null assertions (@bjornstar) 49 | 50 | ## v1.0.1 - 2022-03-14 51 | 52 | - Specify targetPlatform: 'browser' (@isaac-mason) 53 | 54 | ## v1.0.0 - 2022-03-13 55 | 56 | - Initial Release 57 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Paul Henschel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/README.md: -------------------------------------------------------------------------------- 1 | # @pmndrs/cannon-worker-api 2 | 3 | Web worker API for [cannon-es](https://npmjs.com/package/cannon-es). Primarily used in [@react-three/cannon](https://npmjs.com/package/@react-three/cannon). 4 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pmndrs/cannon-worker-api", 3 | "version": "2.4.0", 4 | "description": "An API to use cannon-es in a web worker", 5 | "keywords": [ 6 | "cannon", 7 | "three", 8 | "worker", 9 | "physics" 10 | ], 11 | "author": "Paul Henschel", 12 | "contributors": [ 13 | "Cody Persinger (https://github.com/codynova)", 14 | "Bjorn Stromberg (https://github.com/bjornstar)" 15 | ], 16 | "homepage": "https://github.com/pmndrs/use-cannon/tree/master/packages/cannon-worker-api#readme", 17 | "repository": "github:pmndrs/use-cannon", 18 | "license": "MIT", 19 | "module": "dist/index.js", 20 | "types": "dist/index.d.ts", 21 | "sideEffects": false, 22 | "scripts": { 23 | "build": "tsc && rollup -c", 24 | "clean": "rm -rf dist/*", 25 | "test": "echo tests are missing", 26 | "eslint": "eslint .", 27 | "eslint-fix": "eslint --fix .", 28 | "prettier": "prettier --list-different .", 29 | "prettier-fix": "prettier --write ." 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.17.8", 33 | "@babel/plugin-transform-runtime": "^7.17.0", 34 | "@babel/preset-env": "^7.16.11", 35 | "@babel/preset-typescript": "^7.16.7", 36 | "@rollup/plugin-babel": "^5.3.1", 37 | "@rollup/plugin-commonjs": "^21.0.3", 38 | "@rollup/plugin-node-resolve": "^13.1.3", 39 | "@types/events": "^3.0.0", 40 | "@types/three": "^0.155.0", 41 | "@typescript-eslint/eslint-plugin": "^5.17.0", 42 | "@typescript-eslint/parser": "^5.17.0", 43 | "cannon-es": "^0.20.0", 44 | "eslint": "^8.12.0", 45 | "eslint-config-prettier": "^8.5.0", 46 | "eslint-plugin-es": "^4.1.0", 47 | "eslint-plugin-simple-import-sort": "^7.0.0", 48 | "eslint-plugin-typescript-enum": "^2.1.0", 49 | "events": "^3.3.0", 50 | "prettier": "^2.6.1", 51 | "rollup": "^2.70.1", 52 | "rollup-plugin-web-worker-loader": "^1.6.1", 53 | "typescript": "^4.6.3" 54 | }, 55 | "lint-staged": { 56 | "*.{js,jsx,ts,tsx}": "eslint --cache --fix", 57 | "*.{js,jsx,ts,tsx,md}": "prettier --write" 58 | }, 59 | "peerDependencies": { 60 | "three": ">=0.139" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import pluginCommonjs from '@rollup/plugin-commonjs' 3 | import pluginNodeResolve from '@rollup/plugin-node-resolve' 4 | import pluginWebWorker from 'rollup-plugin-web-worker-loader' 5 | 6 | // These are our dependencies, everything else is in the bundle 7 | const external = ['three'] 8 | const extensions = ['.js', '.ts', '.json'] 9 | 10 | const getBabelOptions = ({ useESModules }, targets) => ({ 11 | babelHelpers: 'runtime', 12 | babelrc: false, 13 | extensions, 14 | include: ['src/**/*', '**/node_modules/**'], 15 | plugins: [['@babel/transform-runtime', { regenerator: false, useESModules }]], 16 | presets: [['@babel/preset-env', { loose: true, modules: false, targets }], '@babel/preset-typescript'], 17 | }) 18 | 19 | export default [ 20 | { 21 | external, 22 | input: `./src/index.ts`, 23 | output: { dir: 'dist', format: 'esm' }, 24 | plugins: [ 25 | pluginCommonjs({ esmExternals: ['events'] }), 26 | pluginNodeResolve({ extensions, preferBuiltins: false }), 27 | pluginWebWorker({ platform: 'base64', sourcemap: false, targetPlatform: 'browser' }), 28 | babel(getBabelOptions({ useESModules: true }, '>1%, not dead, not ie 11, not op_mini all')), 29 | ], 30 | }, 31 | { 32 | external, 33 | input: `./src/index.ts`, 34 | output: { dir: 'dist/debug', format: 'esm' }, 35 | plugins: [ 36 | pluginCommonjs({ esmExternals: ['events'] }), 37 | pluginNodeResolve({ extensions, preferBuiltins: false }), 38 | pluginWebWorker({ platform: 'base64', sourcemap: true, targetPlatform: 'browser' }), 39 | babel(getBabelOptions({ useESModules: true }, '>1%, not dead, not ie 11, not op_mini all')), 40 | ], 41 | }, 42 | ] 43 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/body.ts: -------------------------------------------------------------------------------- 1 | import type { MaterialOptions } from 'cannon-es' 2 | import type { Vector3 } from 'three' 3 | 4 | import type { CollideBeginEvent, CollideEndEvent, CollideEvent, Quad, Triplet, VectorName } from './types' 5 | 6 | export type AtomicProps = { 7 | allowSleep: boolean 8 | angularDamping: number 9 | collisionFilterGroup: number 10 | collisionFilterMask: number 11 | collisionResponse: boolean 12 | fixedRotation: boolean 13 | isTrigger: boolean 14 | linearDamping: number 15 | mass: number 16 | material: MaterialOptions 17 | sleepSpeedLimit: number 18 | sleepTimeLimit: number 19 | userData: Record 20 | } 21 | 22 | export type VectorProps = Record 23 | type VectorTypes = Vector3 | Triplet 24 | 25 | export type BodyProps = Partial & 26 | Partial & { 27 | args?: T 28 | onCollide?: (e: CollideEvent) => void 29 | onCollideBegin?: (e: CollideBeginEvent) => void 30 | onCollideEnd?: (e: CollideEndEvent) => void 31 | /** 32 | * Quaternion is preferred over rotation if both are provided 33 | */ 34 | quaternion?: Quad 35 | rotation?: Triplet 36 | type?: 'Dynamic' | 'Static' | 'Kinematic' 37 | } 38 | 39 | export type BodyPropsArgsRequired = BodyProps & { 40 | args: T 41 | } 42 | 43 | export type ShapeType = 44 | | 'Box' 45 | | 'ConvexPolyhedron' 46 | | 'Cylinder' 47 | | 'Heightfield' 48 | | 'Particle' 49 | | 'Plane' 50 | | 'Sphere' 51 | | 'Trimesh' 52 | export type BodyShapeType = ShapeType | 'Compound' 53 | 54 | export type CylinderArgs = [radiusTop?: number, radiusBottom?: number, height?: number, numSegments?: number] 55 | export type SphereArgs = [radius: number] 56 | export type TrimeshArgs = [vertices: ArrayLike, indices: ArrayLike] 57 | export type HeightfieldArgs = [ 58 | data: number[][], 59 | options: { elementSize?: number; maxValue?: number; minValue?: number }, 60 | ] 61 | export type ConvexPolyhedronArgs = [ 62 | vertices?: V[], 63 | faces?: number[][], 64 | normals?: V[], 65 | axes?: V[], 66 | boundingSphereRadius?: number, 67 | ] 68 | 69 | export type PlaneProps = BodyProps 70 | export type BoxProps = BodyProps 71 | export type CylinderProps = BodyProps 72 | export type ParticleProps = BodyProps 73 | export type SphereProps = BodyProps 74 | export type TrimeshProps = BodyPropsArgsRequired 75 | export type HeightfieldProps = BodyPropsArgsRequired 76 | export type ConvexPolyhedronProps = BodyProps 77 | export interface CompoundBodyProps extends BodyProps { 78 | shapes: BodyProps & { type: ShapeType }[] 79 | } 80 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './body' 2 | export * from './cannon-worker-api' 3 | export * from './props-to-body' 4 | export * from './types' 5 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/props-to-body.js: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Box, 4 | ConvexPolyhedron, 5 | Cylinder, 6 | Heightfield, 7 | Material, 8 | Particle, 9 | Plane, 10 | Quaternion, 11 | Sphere, 12 | Trimesh, 13 | Vec3, 14 | } from 'cannon-es' 15 | /** 16 | * @typedef { import('cannon-es').MaterialOptions } MaterialOptions 17 | */ 18 | 19 | const makeVec3 = ([x, y, z]) => new Vec3(x, y, z) 20 | const prepareSphere = (args) => (Array.isArray(args) ? args : [args]) 21 | const prepareConvexPolyhedron = ([v, faces, n, a, boundingSphereRadius]) => [ 22 | { 23 | axes: a ? a.map(makeVec3) : undefined, 24 | boundingSphereRadius, 25 | faces, 26 | normals: n ? n.map(makeVec3) : undefined, 27 | vertices: v ? v.map(makeVec3) : undefined, 28 | }, 29 | ] 30 | 31 | function createShape(type, args) { 32 | switch (type) { 33 | case 'Box': 34 | return new Box(new Vec3(...args.map((v) => v / 2))) // extents => halfExtents 35 | case 'ConvexPolyhedron': 36 | return new ConvexPolyhedron(...prepareConvexPolyhedron(args)) 37 | case 'Cylinder': 38 | return new Cylinder(...args) // [ radiusTop, radiusBottom, height, numSegments ] = args 39 | case 'Heightfield': 40 | return new Heightfield(...args) // [ Array data, options: {minValue, maxValue, elementSize} ] = args 41 | case 'Particle': 42 | return new Particle() // no args 43 | case 'Plane': 44 | return new Plane() // no args, infinite x and y 45 | case 'Sphere': 46 | return new Sphere(...prepareSphere(args)) // radius = args 47 | case 'Trimesh': 48 | return new Trimesh(...args) // [vertices, indices] = args 49 | } 50 | } 51 | 52 | /** 53 | * @param {THREE.Quaternion} target 54 | * @param {{ rotation?: THREE.Vector3Tuple quaternion?: THREE.Vector4Tuple }} props 55 | * @returns {THREE.Quaternion} 56 | */ 57 | const setQuaternion = (target, { quaternion, rotation }) => { 58 | if (quaternion) { 59 | target.set(...quaternion) 60 | } else if (rotation) { 61 | target.setFromEuler(...rotation) 62 | } 63 | 64 | return target 65 | } 66 | 67 | /** 68 | * @function 69 | * @param {Object} options 70 | * @param {string} options.uuid 71 | * @param {BodyProps} options.props 72 | * @param {BodyShapeType} options.type 73 | * @param {(materialOptions: MaterialOptions) => Material =} options.createMaterial 74 | * @returns {Body} 75 | */ 76 | export const propsToBody = (options) => { 77 | const { uuid, props, type, createMaterial = (materialOptions) => new Material(materialOptions) } = options 78 | const { 79 | angularFactor = [1, 1, 1], 80 | angularVelocity = [0, 0, 0], 81 | args = [], 82 | collisionResponse, 83 | linearFactor = [1, 1, 1], 84 | mass, 85 | material, 86 | onCollide, 87 | position = [0, 0, 0], 88 | rotation, 89 | quaternion, 90 | shapes, 91 | type: bodyType, 92 | velocity = [0, 0, 0], 93 | ...extra 94 | } = props 95 | 96 | const body = new Body({ 97 | ...extra, 98 | mass: bodyType === 'Static' ? 0 : mass, 99 | material: material ? createMaterial(material) : undefined, 100 | type: bodyType ? Body[bodyType.toUpperCase()] : undefined, 101 | }) 102 | body.uuid = uuid 103 | 104 | if (collisionResponse !== undefined) { 105 | body.collisionResponse = collisionResponse 106 | } 107 | 108 | if (type === 'Compound') { 109 | shapes.forEach(({ type, args, position, rotation, quaternion, material, ...extra }) => { 110 | const shapeBody = body.addShape( 111 | createShape(type, args), 112 | position ? new Vec3(...position) : undefined, 113 | setQuaternion(new Quaternion(0, 0, 0, 1), { quaternion, rotation }), 114 | ) 115 | if (material) shapeBody.material = createMaterial(material) 116 | Object.assign(shapeBody, extra) 117 | }) 118 | } else { 119 | body.addShape(createShape(type, args)) 120 | } 121 | 122 | body.position.set(position[0], position[1], position[2]) 123 | body.velocity.set(velocity[0], velocity[1], velocity[2]) 124 | body.angularVelocity.set(angularVelocity[0], angularVelocity[1], angularVelocity[2]) 125 | body.linearFactor.set(linearFactor[0], linearFactor[1], linearFactor[2]) 126 | body.angularFactor.set(angularFactor[0], angularFactor[1], angularFactor[2]) 127 | setQuaternion(body.quaternion, { quaternion, rotation }) 128 | 129 | return body 130 | } 131 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'web-worker:*' { 2 | const WorkerFactory: new () => Worker 3 | export default WorkerFactory 4 | } 5 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/contact-material.ts: -------------------------------------------------------------------------------- 1 | import { ContactMaterial } from 'cannon-es' 2 | 3 | import type { CannonMessageBody } from '../types' 4 | import type { CreateMaterial } from './material' 5 | import type { State } from './state' 6 | import type { WithUUID } from './types' 7 | 8 | export const addContactMaterial = ( 9 | world: State['world'], 10 | createMaterial: CreateMaterial, 11 | [materialA, materialB, options]: CannonMessageBody<'addContactMaterial'>['props'], 12 | uuid: string, 13 | ) => { 14 | const matA = createMaterial(materialA) 15 | const matB = createMaterial(materialB) 16 | const contactMaterial: WithUUID = new ContactMaterial(matA, matB, options) 17 | contactMaterial.uuid = uuid 18 | world.addContactMaterial(contactMaterial) 19 | } 20 | 21 | export const removeContactMaterial = (world: State['world'], cmUUID: string) => { 22 | const index = world.contactmaterials.findIndex(({ uuid }) => uuid === cmUUID) 23 | const [{ id: i }, { id: j }] = world.contactmaterials[index].materials 24 | 25 | world.contactmaterials.splice(index, 1) 26 | delete world.contactMaterialTable.data[i < j ? `${i}-${j}` : `${j}-${i}`] 27 | } 28 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | import { GSSolver, HingeConstraint, NaiveBroadphase, SAPBroadphase, Vec3 } from 'cannon-es' 6 | 7 | import type { CannonMessage } from '../types' 8 | import { addContactMaterial, removeContactMaterial } from './contact-material' 9 | import { createMaterialFactory } from './material' 10 | import { addBodies, addConstraint, addRay, addRaycastVehicle, addSpring, init, step } from './operations' 11 | import { state } from './state' 12 | import type { CannonWorkerGlobalScope } from './types' 13 | 14 | // TODO: Declare this for all files in worker 15 | declare const self: CannonWorkerGlobalScope 16 | 17 | const isHingeConstraint = (c: unknown): c is HingeConstraint => c instanceof HingeConstraint 18 | 19 | function syncBodies() { 20 | state.bodiesNeedSyncing = true 21 | state.bodies = state.world.bodies.reduce( 22 | (bodies, body) => (body.uuid ? { ...bodies, [body.uuid]: body } : bodies), 23 | {}, 24 | ) 25 | } 26 | 27 | const broadphases = { NaiveBroadphase, SAPBroadphase } 28 | const createMaterial = createMaterialFactory(state.materials) 29 | 30 | self.onmessage = ({ data }: { data: CannonMessage }) => { 31 | switch (data.op) { 32 | case 'init': { 33 | init(state.world, data.props) 34 | break 35 | } 36 | case 'step': { 37 | step(state, data) 38 | break 39 | } 40 | case 'addBodies': { 41 | addBodies(state, createMaterial, data) 42 | syncBodies() 43 | break 44 | } 45 | case 'removeBodies': { 46 | for (let i = 0; i < data.uuid.length; i++) { 47 | state.world.removeBody(state.bodies[data.uuid[i]]) 48 | const key = Object.keys(state.subscriptions).find((k) => state.subscriptions[k][0] === data.uuid[i]) 49 | if (key) { 50 | delete state.subscriptions[key] 51 | } 52 | } 53 | syncBodies() 54 | break 55 | } 56 | case 'subscribe': { 57 | const { id, target, type } = data.props 58 | state.subscriptions[id] = [data.uuid, type, target] 59 | break 60 | } 61 | case 'unsubscribe': { 62 | delete state.subscriptions[data.props] 63 | break 64 | } 65 | case 'setPosition': 66 | state.bodies[data.uuid].position.set(data.props[0], data.props[1], data.props[2]) 67 | break 68 | case 'setQuaternion': 69 | state.bodies[data.uuid].quaternion.set(data.props[0], data.props[1], data.props[2], data.props[3]) 70 | break 71 | case 'setRotation': 72 | state.bodies[data.uuid].quaternion.setFromEuler(data.props[0], data.props[1], data.props[2]) 73 | break 74 | case 'setVelocity': 75 | state.bodies[data.uuid].velocity.set(data.props[0], data.props[1], data.props[2]) 76 | break 77 | case 'setAngularVelocity': 78 | state.bodies[data.uuid].angularVelocity.set(data.props[0], data.props[1], data.props[2]) 79 | break 80 | case 'setLinearFactor': 81 | state.bodies[data.uuid].linearFactor.set(data.props[0], data.props[1], data.props[2]) 82 | break 83 | case 'setAngularFactor': 84 | state.bodies[data.uuid].angularFactor.set(data.props[0], data.props[1], data.props[2]) 85 | break 86 | case 'setMass': 87 | state.bodies[data.uuid].mass = data.props 88 | state.bodies[data.uuid].updateMassProperties() 89 | break 90 | case 'setMaterial': 91 | state.bodies[data.uuid].material = data.props ? createMaterial(data.props) : null 92 | break 93 | case 'setLinearDamping': 94 | state.bodies[data.uuid].linearDamping = data.props 95 | break 96 | case 'setAngularDamping': 97 | state.bodies[data.uuid].angularDamping = data.props 98 | break 99 | case 'setAllowSleep': 100 | state.bodies[data.uuid].allowSleep = data.props 101 | break 102 | case 'setSleepSpeedLimit': 103 | state.bodies[data.uuid].sleepSpeedLimit = data.props 104 | break 105 | case 'setSleepTimeLimit': 106 | state.bodies[data.uuid].sleepTimeLimit = data.props 107 | break 108 | case 'setCollisionFilterGroup': 109 | state.bodies[data.uuid].collisionFilterGroup = data.props 110 | break 111 | case 'setCollisionFilterMask': 112 | state.bodies[data.uuid].collisionFilterMask = data.props 113 | break 114 | case 'setCollisionResponse': 115 | state.bodies[data.uuid].collisionResponse = data.props 116 | break 117 | case 'setFixedRotation': 118 | state.bodies[data.uuid].fixedRotation = data.props 119 | break 120 | case 'setFrictionGravity': 121 | state.world.frictionGravity = data.props ? new Vec3(...data.props) : undefined 122 | break 123 | case 'setIsTrigger': 124 | state.bodies[data.uuid].isTrigger = data.props 125 | break 126 | case 'setGravity': 127 | state.world.gravity.set(data.props[0], data.props[1], data.props[2]) 128 | break 129 | case 'setTolerance': 130 | if (state.world.solver instanceof GSSolver) { 131 | state.world.solver.tolerance = data.props 132 | } 133 | break 134 | case 'setIterations': 135 | if (state.world.solver instanceof GSSolver) { 136 | state.world.solver.iterations = data.props 137 | } 138 | break 139 | case 'setBroadphase': 140 | state.world.broadphase = new (broadphases[`${data.props}Broadphase`] || NaiveBroadphase)(state.world) 141 | break 142 | case 'setAxisIndex': 143 | if (state.world.broadphase instanceof SAPBroadphase) { 144 | state.world.broadphase.axisIndex = data.props === undefined || data.props === null ? 0 : data.props 145 | } 146 | break 147 | case 'applyForce': 148 | state.bodies[data.uuid].applyForce(new Vec3(...data.props[0]), new Vec3(...data.props[1])) 149 | break 150 | case 'applyImpulse': 151 | state.bodies[data.uuid].applyImpulse(new Vec3(...data.props[0]), new Vec3(...data.props[1])) 152 | break 153 | case 'applyLocalForce': 154 | state.bodies[data.uuid].applyLocalForce(new Vec3(...data.props[0]), new Vec3(...data.props[1])) 155 | break 156 | case 'applyLocalImpulse': 157 | state.bodies[data.uuid].applyLocalImpulse(new Vec3(...data.props[0]), new Vec3(...data.props[1])) 158 | break 159 | case 'applyTorque': 160 | state.bodies[data.uuid].applyTorque(new Vec3(...data.props[0])) 161 | break 162 | case 'addConstraint': { 163 | addConstraint(state, data) 164 | break 165 | } 166 | case 'removeConstraint': 167 | state.world.constraints 168 | .filter(({ uuid }) => uuid === data.uuid) 169 | .map((c) => state.world.removeConstraint(c)) 170 | if (state.constraints[data.uuid]) { 171 | state.world.removeEventListener('postStep', state.constraints[data.uuid]) 172 | delete state.constraints[data.uuid] 173 | } 174 | break 175 | case 'enableConstraint': 176 | state.world.constraints.filter((c) => c.uuid === data.uuid).map((c) => c.enable()) 177 | break 178 | case 'disableConstraint': 179 | state.world.constraints.filter((c) => c.uuid === data.uuid).map((c) => c.disable()) 180 | break 181 | case 'enableConstraintMotor': 182 | state.world.constraints 183 | .filter((c) => c.uuid === data.uuid) 184 | .filter(isHingeConstraint) 185 | .map((c) => c.enableMotor()) 186 | break 187 | case 'disableConstraintMotor': 188 | state.world.constraints 189 | .filter((c) => c.uuid === data.uuid) 190 | .filter(isHingeConstraint) 191 | .map((c) => c.disableMotor()) 192 | break 193 | case 'setConstraintMotorSpeed': 194 | state.world.constraints 195 | .filter((c) => c.uuid === data.uuid) 196 | .filter(isHingeConstraint) 197 | .map((c) => c.setMotorSpeed(data.props)) 198 | break 199 | case 'setConstraintMotorMaxForce': 200 | state.world.constraints 201 | .filter((c) => c.uuid === data.uuid) 202 | .filter(isHingeConstraint) 203 | .map((c) => c.setMotorMaxForce(data.props)) 204 | break 205 | case 'addSpring': { 206 | addSpring(state, data) 207 | break 208 | } 209 | case 'setSpringStiffness': { 210 | state.springInstances[data.uuid].stiffness = data.props 211 | break 212 | } 213 | case 'setSpringRestLength': { 214 | state.springInstances[data.uuid].restLength = data.props 215 | break 216 | } 217 | case 'setSpringDamping': { 218 | state.springInstances[data.uuid].damping = data.props 219 | break 220 | } 221 | case 'removeSpring': { 222 | state.world.removeEventListener('postStep', state.springs[data.uuid]) 223 | break 224 | } 225 | case 'addRay': { 226 | addRay(state, data) 227 | break 228 | } 229 | case 'removeRay': { 230 | state.world.removeEventListener('preStep', state.rays[data.uuid]) 231 | delete state.rays[data.uuid] 232 | break 233 | } 234 | case 'addRaycastVehicle': { 235 | addRaycastVehicle(state, data) 236 | break 237 | } 238 | case 'removeRaycastVehicle': { 239 | state.world.removeEventListener('preStep', state.vehicles[data.uuid].preStep) 240 | state.world.removeEventListener('postStep', state.vehicles[data.uuid].postStep) 241 | state.vehicles[data.uuid].vehicle.world = null 242 | delete state.vehicles[data.uuid] 243 | const key = Object.keys(state.subscriptions).find((k) => state.subscriptions[k][0] === data.uuid) 244 | if (key) { 245 | delete state.subscriptions[key] 246 | } 247 | break 248 | } 249 | case 'setRaycastVehicleSteeringValue': { 250 | const [value, wheelIndex] = data.props 251 | state.vehicles[data.uuid].vehicle.setSteeringValue(value, wheelIndex) 252 | break 253 | } 254 | case 'applyRaycastVehicleEngineForce': { 255 | const [value, wheelIndex] = data.props 256 | state.vehicles[data.uuid].vehicle.applyEngineForce(value, wheelIndex) 257 | break 258 | } 259 | case 'setRaycastVehicleBrake': { 260 | const [brake, wheelIndex] = data.props 261 | state.vehicles[data.uuid].vehicle.setBrake(brake, wheelIndex) 262 | break 263 | } 264 | case 'addContactMaterial': { 265 | addContactMaterial(state.world, createMaterial, data.props, data.uuid) 266 | break 267 | } 268 | case 'removeContactMaterial': { 269 | removeContactMaterial(state.world, data.uuid) 270 | break 271 | } 272 | case 'wakeUp': { 273 | state.bodies[data.uuid].wakeUp() 274 | break 275 | } 276 | case 'sleep': { 277 | state.bodies[data.uuid].sleep() 278 | break 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/material.ts: -------------------------------------------------------------------------------- 1 | import { Material } from 'cannon-es' 2 | 3 | type MaterialOptions = { 4 | friction?: number 5 | name?: string | symbol 6 | restitution?: number 7 | } 8 | 9 | export type CreateMaterial = (nameOrOptions?: MaterialOptions | string) => Material 10 | 11 | let materialId = 0 12 | 13 | export const createMaterialFactory = 14 | (materials: Record): CreateMaterial => 15 | (nameOrOptions = {}) => { 16 | const materialOptions = 17 | typeof nameOrOptions === 'string' 18 | ? { name: nameOrOptions } 19 | : { name: Symbol.for(`Material${materialId++}`), ...nameOrOptions } 20 | const { name } = materialOptions 21 | materials[name] = materials[name] || new Material(materialOptions) 22 | return materials[name] 23 | } 24 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/add-bodies.ts: -------------------------------------------------------------------------------- 1 | import { propsToBody } from '../../props-to-body' 2 | import type { CannonMessageMap } from '../../types' 3 | import type { CreateMaterial } from '../material' 4 | import type { State } from '../state' 5 | import type { CannonCollideEvent, CannonWorkerGlobalScope } from '../types' 6 | 7 | declare const self: CannonWorkerGlobalScope 8 | 9 | export const addBodies = ( 10 | state: State, 11 | createMaterial: CreateMaterial, 12 | { props, type, uuid }: CannonMessageMap['addBodies'], 13 | ) => { 14 | for (let i = 0; i < uuid.length; i++) { 15 | const body = propsToBody({ 16 | createMaterial, 17 | props: props[i], 18 | type, 19 | uuid: uuid[i], 20 | }) 21 | state.world.addBody(body) 22 | 23 | if (props[i].onCollide) 24 | body.addEventListener('collide', ({ type, body, target, contact }: CannonCollideEvent) => { 25 | if (!body.uuid || !target.uuid) return 26 | 27 | const { ni, ri, rj, bi, bj, id } = contact 28 | const contactPoint = bi.position.vadd(ri) 29 | const contactNormal = bi === body ? ni : ni.scale(-1) 30 | 31 | self.postMessage({ 32 | body: body.uuid, 33 | collisionFilters: { 34 | bodyFilterGroup: body.collisionFilterGroup, 35 | bodyFilterMask: body.collisionFilterMask, 36 | targetFilterGroup: target.collisionFilterGroup, 37 | targetFilterMask: target.collisionFilterMask, 38 | }, 39 | contact: { 40 | // @ts-expect-error TODO: use id instead of uuid 41 | bi: bi.uuid, 42 | // @ts-expect-error TODO: use id instead of uuid 43 | bj: bj.uuid, 44 | // Normal of the contact, relative to the colliding body 45 | contactNormal: contactNormal.toArray(), 46 | // World position of the contact 47 | contactPoint: contactPoint.toArray(), 48 | id, 49 | impactVelocity: contact.getImpactVelocityAlongNormal(), 50 | ni: ni.toArray(), 51 | ri: ri.toArray(), 52 | rj: rj.toArray(), 53 | }, 54 | op: 'event', 55 | target: target.uuid, 56 | type, 57 | }) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/add-constraint.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConeTwistConstraint, 3 | Constraint, 4 | DistanceConstraint, 5 | HingeConstraint, 6 | LockConstraint, 7 | PointToPointConstraint, 8 | } from 'cannon-es' 9 | 10 | import type { CannonMessageMap } from '../../types' 11 | import type { State } from '../state' 12 | import { tripletToVec3 } from '../triplet-to-vec3' 13 | import type { WithUUID } from '../types' 14 | 15 | export const addConstraint = ( 16 | state: State, 17 | { 18 | props: [ 19 | bodyA, 20 | bodyB, 21 | { 22 | angle, 23 | axisA, 24 | axisB, 25 | collideConnected, 26 | distance, 27 | maxForce, 28 | maxMultiplier, 29 | pivotA, 30 | pivotB, 31 | twistAngle, 32 | wakeUpBodies, 33 | }, 34 | ], 35 | type, 36 | uuid, 37 | }: CannonMessageMap['addConstraint'], 38 | ) => { 39 | let constraint: WithUUID 40 | 41 | switch (type) { 42 | case 'PointToPoint': 43 | constraint = new PointToPointConstraint( 44 | state.bodies[bodyA], 45 | tripletToVec3(pivotA), 46 | state.bodies[bodyB], 47 | tripletToVec3(pivotB), 48 | maxForce, 49 | ) 50 | break 51 | case 'ConeTwist': 52 | constraint = new ConeTwistConstraint(state.bodies[bodyA], state.bodies[bodyB], { 53 | angle, 54 | axisA: tripletToVec3(axisA), 55 | axisB: tripletToVec3(axisB), 56 | collideConnected, 57 | maxForce, 58 | pivotA: tripletToVec3(pivotA), 59 | pivotB: tripletToVec3(pivotB), 60 | twistAngle, 61 | }) 62 | break 63 | case 'Hinge': 64 | constraint = new HingeConstraint(state.bodies[bodyA], state.bodies[bodyB], { 65 | axisA: tripletToVec3(axisA), 66 | axisB: tripletToVec3(axisB), 67 | collideConnected, 68 | maxForce, 69 | pivotA: tripletToVec3(pivotA), 70 | pivotB: tripletToVec3(pivotB), 71 | }) 72 | break 73 | case 'Distance': 74 | constraint = new DistanceConstraint(state.bodies[bodyA], state.bodies[bodyB], distance, maxForce) 75 | break 76 | case 'Lock': 77 | constraint = new LockConstraint(state.bodies[bodyA], state.bodies[bodyB], { maxForce }) 78 | break 79 | default: 80 | constraint = new Constraint(state.bodies[bodyA], state.bodies[bodyB], { 81 | collideConnected, 82 | wakeUpBodies, 83 | }) 84 | break 85 | } 86 | constraint.uuid = uuid 87 | state.world.addConstraint(constraint) 88 | 89 | if (maxMultiplier !== undefined) { 90 | const postStepConstraint = () => { 91 | // The multiplier is proportional to how much force is added to the bodies by the constraint. 92 | // If this exceeds a limit the constraint is disabled. 93 | const multiplier = Math.abs(constraint.equations[0].multiplier) 94 | if (multiplier > maxMultiplier) { 95 | constraint.disable() 96 | } 97 | } 98 | state.constraints[uuid] = postStepConstraint 99 | state.world.addEventListener('postStep', state.constraints[uuid]) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/add-ray.ts: -------------------------------------------------------------------------------- 1 | import type { Body, RayOptions } from 'cannon-es' 2 | import { Ray, RAY_MODES, RaycastResult } from 'cannon-es' 3 | 4 | import type { CannonMessageMap } from '../../types' 5 | import type { State } from '../state' 6 | import { tripletToVec3 } from '../triplet-to-vec3' 7 | import type { CannonWorkerGlobalScope, WithUUID } from '../types' 8 | 9 | declare const self: CannonWorkerGlobalScope 10 | 11 | function toUppercase(str: T): Uppercase { 12 | return str.toUpperCase() as Uppercase 13 | } 14 | 15 | export const addRay = ( 16 | state: State, 17 | { props: { from, mode, to, ...rayOptions }, uuid }: CannonMessageMap['addRay'], 18 | ) => { 19 | const ray = new Ray(tripletToVec3(from), tripletToVec3(to)) 20 | 21 | const options: RayOptions = { 22 | mode: RAY_MODES[toUppercase(mode)], 23 | result: new RaycastResult(), 24 | ...rayOptions, 25 | } 26 | 27 | state.rays[uuid] = () => { 28 | ray.intersectWorld(state.world, options) 29 | 30 | if (!options.result || !options.result.body) return 31 | 32 | const { body, shape, rayFromWorld, rayToWorld, hitNormalWorld, hitPointWorld, ...rest } = options.result 33 | 34 | const bodyUUID = (body as WithUUID).uuid 35 | 36 | if (!bodyUUID) return 37 | 38 | self.postMessage({ 39 | body: bodyUUID, 40 | hitNormalWorld: hitNormalWorld.toArray(), 41 | hitPointWorld: hitPointWorld.toArray(), 42 | op: 'event', 43 | ray: { 44 | collisionFilterGroup: ray.collisionFilterGroup, 45 | collisionFilterMask: ray.collisionFilterMask, 46 | direction: ray.direction.toArray(), 47 | from, 48 | to, 49 | uuid, 50 | }, 51 | rayFromWorld: rayFromWorld.toArray(), 52 | rayToWorld: rayToWorld.toArray(), 53 | shape: shape ? { ...shape, body: bodyUUID } : null, 54 | type: 'rayhit', 55 | ...rest, 56 | }) 57 | } 58 | 59 | state.world.addEventListener('preStep', state.rays[uuid]) 60 | } 61 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/add-raycast-vehicle.ts: -------------------------------------------------------------------------------- 1 | import { RaycastVehicle } from 'cannon-es' 2 | 3 | import type { CannonMessageMap } from '../../types' 4 | import type { State } from '../state' 5 | import { tripletToVec3 } from '../triplet-to-vec3' 6 | 7 | export const addRaycastVehicle = (state: State, data: CannonMessageMap['addRaycastVehicle']) => { 8 | const [chassisBody, wheels, wheelInfos, indexForwardAxis, indexRightAxis, indexUpAxis] = data.props 9 | 10 | const vehicle = new RaycastVehicle({ 11 | chassisBody: state.bodies[chassisBody], 12 | indexForwardAxis, 13 | indexRightAxis, 14 | indexUpAxis, 15 | }) 16 | 17 | vehicle.world = state.world 18 | 19 | for (let i = 0; i < wheelInfos.length; i++) { 20 | const { axleLocal, chassisConnectionPointLocal, directionLocal, ...rest } = wheelInfos[i] 21 | 22 | vehicle.addWheel({ 23 | axleLocal: tripletToVec3(axleLocal), 24 | chassisConnectionPointLocal: tripletToVec3(chassisConnectionPointLocal), 25 | directionLocal: tripletToVec3(directionLocal), 26 | ...rest, 27 | }) 28 | } 29 | 30 | const preStep = () => { 31 | vehicle.updateVehicle(state.world.dt) 32 | } 33 | 34 | const postStep = () => { 35 | for (let i = 0; i < vehicle.wheelInfos.length; i++) { 36 | vehicle.updateWheelTransform(i) 37 | 38 | const t = vehicle.wheelInfos[i].worldTransform 39 | const wheelBody = state.bodies[wheels[i]] 40 | 41 | wheelBody.position.copy(t.position) 42 | wheelBody.quaternion.copy(t.quaternion) 43 | } 44 | } 45 | 46 | state.vehicles[data.uuid] = { postStep, preStep, vehicle } 47 | 48 | state.world.addEventListener('preStep', preStep) 49 | state.world.addEventListener('postStep', postStep) 50 | } 51 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/add-spring.ts: -------------------------------------------------------------------------------- 1 | import { Spring } from 'cannon-es' 2 | 3 | import type { CannonMessageMap } from '../../types' 4 | import type { State } from '../state' 5 | import { tripletToVec3 } from '../triplet-to-vec3' 6 | import type { WithUUID } from '../types' 7 | 8 | export const addSpring = ( 9 | state: State, 10 | { 11 | props: [ 12 | bodyA, 13 | bodyB, 14 | { damping, localAnchorA, localAnchorB, restLength, stiffness, worldAnchorA, worldAnchorB }, 15 | ], 16 | uuid, 17 | }: CannonMessageMap['addSpring'], 18 | ) => { 19 | const spring: WithUUID = new Spring(state.bodies[bodyA], state.bodies[bodyB], { 20 | damping, 21 | localAnchorA: tripletToVec3(localAnchorA), 22 | localAnchorB: tripletToVec3(localAnchorB), 23 | restLength, 24 | stiffness, 25 | worldAnchorA: tripletToVec3(worldAnchorA), 26 | worldAnchorB: tripletToVec3(worldAnchorB), 27 | }) 28 | 29 | spring.uuid = uuid 30 | 31 | const postStepSpring = () => spring.applyForce() 32 | 33 | state.springs[uuid] = postStepSpring 34 | state.springInstances[uuid] = spring 35 | 36 | // Compute the force after each step 37 | state.world.addEventListener('postStep', state.springs[uuid]) 38 | } 39 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/index.ts: -------------------------------------------------------------------------------- 1 | export { addBodies } from './add-bodies' 2 | export { addConstraint } from './add-constraint' 3 | export { addRay } from './add-ray' 4 | export { addRaycastVehicle } from './add-raycast-vehicle' 5 | export { addSpring } from './add-spring' 6 | export { init } from './init' 7 | export { step } from './step' 8 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/init.ts: -------------------------------------------------------------------------------- 1 | import type { Body } from 'cannon-es' 2 | import { GSSolver, NaiveBroadphase, SAPBroadphase, SplitSolver, Vec3 } from 'cannon-es' 3 | 4 | import type { CannonMessageProps } from '../../types' 5 | import type { DecoratedWorld } from '../state' 6 | import type { CannonWorkerGlobalScope, WithUUID } from '../types' 7 | 8 | declare const self: CannonWorkerGlobalScope 9 | 10 | type TwoBodies = { 11 | bodyA?: WithUUID 12 | bodyB?: WithUUID 13 | } 14 | 15 | function emitBeginContact({ bodyA, bodyB }: TwoBodies) { 16 | if (!bodyA?.uuid || !bodyB?.uuid) return 17 | self.postMessage({ bodyA: bodyA.uuid, bodyB: bodyB.uuid, op: 'event', type: 'collideBegin' }) 18 | } 19 | 20 | function emitEndContact({ bodyA, bodyB }: TwoBodies) { 21 | if (!bodyA?.uuid || !bodyB?.uuid) return 22 | self.postMessage({ bodyA: bodyA.uuid, bodyB: bodyB.uuid, op: 'event', type: 'collideEnd' }) 23 | } 24 | 25 | export const init = ( 26 | world: DecoratedWorld, 27 | { 28 | allowSleep, 29 | axisIndex = 0, 30 | broadphase, 31 | defaultContactMaterial, 32 | frictionGravity, 33 | gravity, 34 | iterations, 35 | quatNormalizeFast, 36 | quatNormalizeSkip, 37 | solver, 38 | tolerance, 39 | }: CannonMessageProps<'init'>, 40 | ): void => { 41 | world.allowSleep = allowSleep 42 | world.gravity.set(...gravity) 43 | world.frictionGravity = frictionGravity ? new Vec3(...frictionGravity) : undefined 44 | world.quatNormalizeFast = quatNormalizeFast 45 | world.quatNormalizeSkip = quatNormalizeSkip 46 | 47 | if (solver === 'Split') { 48 | world.solver = new SplitSolver(new GSSolver()) 49 | } 50 | 51 | if (world.solver instanceof GSSolver) { 52 | world.solver.tolerance = tolerance 53 | world.solver.iterations = iterations 54 | } 55 | 56 | world.broadphase = broadphase === 'SAP' ? new SAPBroadphase(world) : new NaiveBroadphase() 57 | 58 | if (world.broadphase instanceof SAPBroadphase) { 59 | world.broadphase.axisIndex = axisIndex 60 | } 61 | 62 | world.addEventListener('beginContact', emitBeginContact) 63 | world.addEventListener('endContact', emitEndContact) 64 | 65 | Object.assign(world.defaultContactMaterial, defaultContactMaterial) 66 | } 67 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/operations/step.ts: -------------------------------------------------------------------------------- 1 | import { Quaternion, Vec3 } from 'cannon-es' 2 | 3 | import type { CannonMessageMap, Observation, PropValue, WorkerFrameMessage } from '../../types' 4 | import type { State } from '../state' 5 | import type { CannonWorkerGlobalScope } from '../types' 6 | 7 | declare const self: CannonWorkerGlobalScope 8 | 9 | const isQorV = (v: unknown): v is Quaternion | Vec3 => v instanceof Quaternion || v instanceof Vec3 10 | 11 | export const step = ( 12 | state: State, 13 | { positions, props: { maxSubSteps, stepSize, timeSinceLastCalled }, quaternions }: CannonMessageMap['step'], 14 | ) => { 15 | state.world.step(stepSize, timeSinceLastCalled, maxSubSteps) 16 | 17 | for (let i = 0; i < state.world.bodies.length; i += 1) { 18 | const p = state.world.bodies[i].position 19 | const q = state.world.bodies[i].quaternion 20 | 21 | positions[3 * i + 0] = p.x 22 | positions[3 * i + 1] = p.y 23 | positions[3 * i + 2] = p.z 24 | 25 | quaternions[4 * i + 0] = q.x 26 | quaternions[4 * i + 1] = q.y 27 | quaternions[4 * i + 2] = q.z 28 | quaternions[4 * i + 3] = q.w 29 | } 30 | 31 | const observations: Observation[] = [] 32 | 33 | for (const id of Object.keys(state.subscriptions)) { 34 | const [uuid, type, target = 'bodies'] = state.subscriptions[id] 35 | 36 | const { bodies, vehicles } = state 37 | 38 | const value = 39 | target === 'vehicles' 40 | ? // @ts-expect-error TODO: Differentiate these "types" 41 | vehicles[uuid].vehicle[type] 42 | : // @ts-expect-error TODO: Differentiate these "types" 43 | bodies[uuid][type] 44 | 45 | const serializableValue: PropValue = isQorV(value) ? value.toArray() : value 46 | 47 | observations.push([ 48 | Number(id), 49 | serializableValue, 50 | // @ts-expect-error TODO: Differentiate these "types" 51 | type, 52 | ]) 53 | } 54 | 55 | const message: WorkerFrameMessage['data'] = { 56 | active: state.world.hasActiveBodies, 57 | observations, 58 | op: 'frame', 59 | positions, 60 | quaternions, 61 | } 62 | 63 | if (state.bodiesNeedSyncing) { 64 | message.bodies = state.world.bodies.reduce((bodies: string[], body) => { 65 | if (body.uuid) bodies.push(body.uuid) 66 | return bodies 67 | }, []) 68 | state.bodiesNeedSyncing = false 69 | } 70 | 71 | self.postMessage(message, [positions.buffer, quaternions.buffer]) 72 | } 73 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/state.ts: -------------------------------------------------------------------------------- 1 | import type { Body, Constraint, ContactMaterial, Material, RaycastVehicle, Spring } from 'cannon-es' 2 | import { World } from 'cannon-es' 3 | 4 | import type { SubscriptionName, SubscriptionTarget } from '../types' 5 | import type { WithUUID } from './types' 6 | 7 | export interface DecoratedWorld extends World { 8 | bodies: WithUUID[] 9 | constraints: WithUUID[] 10 | contactmaterials: WithUUID[] 11 | } 12 | 13 | export interface State { 14 | bodies: { [uuid: string]: Body } 15 | bodiesNeedSyncing: boolean 16 | constraints: { [uuid: string]: () => void } 17 | materials: { [uuid: string]: Material } 18 | rays: { [uuid: string]: () => void } 19 | springInstances: { [uuid: string]: Spring } 20 | springs: { [uuid: string]: () => void } 21 | subscriptions: { [id: string]: [uuid: string, type: SubscriptionName, target: SubscriptionTarget] } 22 | vehicles: { [uuid: string]: { postStep: () => void; preStep: () => void; vehicle: RaycastVehicle } } 23 | world: DecoratedWorld 24 | } 25 | 26 | export const state: State = { 27 | bodies: {}, 28 | bodiesNeedSyncing: false, 29 | constraints: {}, 30 | materials: {}, 31 | rays: {}, 32 | springInstances: {}, 33 | springs: {}, 34 | subscriptions: {}, 35 | vehicles: {}, 36 | world: new World(), 37 | } 38 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/triplet-to-vec3.ts: -------------------------------------------------------------------------------- 1 | import { Vec3 } from 'cannon-es' 2 | 3 | import type { Triplet } from '../types' 4 | 5 | export const tripletToVec3 = (t?: Triplet) => (t ? new Vec3(...t) : undefined) 6 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/src/worker/types.ts: -------------------------------------------------------------------------------- 1 | import type { Body, ContactEquation } from 'cannon-es' 2 | 3 | import type { IncomingWorkerMessage } from '../types' 4 | 5 | export type WithUUID = C & { uuid?: string } 6 | 7 | export interface CannonWorkerGlobalScope extends ServiceWorkerGlobalScope { 8 | postMessage(message: IncomingWorkerMessage['data'], transfer: Transferable[]): void 9 | postMessage(message: IncomingWorkerMessage['data'], options?: StructuredSerializeOptions): void 10 | } 11 | 12 | export interface CannonCollideEvent { 13 | body: WithUUID 14 | contact: ContactEquation 15 | target: WithUUID 16 | type: 'collide' 17 | } 18 | -------------------------------------------------------------------------------- /packages/cannon-worker-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "baseUrl": "./src", 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "esModuleInterop": true, 8 | "incremental": true, 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noImplicitThis": false, 12 | "outDir": "dist", 13 | "pretty": true, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "target": "es6" 19 | }, 20 | "exclude": ["./node_modules/**/*"], 21 | "include": ["./src"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | public 4 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["prettier", "plugin:react/recommended", "eslint:recommended"], 7 | "overrides": [ 8 | { 9 | "extends": "plugin:@typescript-eslint/recommended", 10 | "files": ["*.tsx", "*.ts"], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "project": ["tsconfig.json"] 14 | }, 15 | "plugins": ["@typescript-eslint", "typescript-enum"], 16 | "rules": { 17 | "@typescript-eslint/ban-types": ["error", { "extendDefaults": true, "types": { "{}": false } }], 18 | "@typescript-eslint/consistent-type-imports": "error", 19 | "@typescript-eslint/explicit-module-boundary-types": "off", 20 | "@typescript-eslint/member-ordering": [ 21 | "error", 22 | { 23 | "default": { 24 | "order": "alphabetically-case-insensitive" 25 | } 26 | } 27 | ], 28 | "@typescript-eslint/no-namespace": ["error", { "allowDeclarations": true }], 29 | "@typescript-eslint/no-non-null-assertion": "error", 30 | "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }], 31 | "@typescript-eslint/quotes": [ 32 | "error", 33 | "single", 34 | { 35 | "allowTemplateLiterals": true, 36 | "avoidEscape": true 37 | } 38 | ], 39 | "@typescript-eslint/semi": ["error", "never"], 40 | "typescript-enum/no-enum": "error" 41 | } 42 | } 43 | ], 44 | "parserOptions": { 45 | "ecmaFeatures": { 46 | "jsx": true 47 | }, 48 | "ecmaVersion": 12, 49 | "sourceType": "module" 50 | }, 51 | "plugins": ["es", "react", "simple-import-sort"], 52 | "rules": { 53 | "eol-last": ["error", "always"], 54 | "es/no-logical-assignment-operators": "error", 55 | "es/no-nullish-coalescing-operators": "error", 56 | "no-debugger": "error", 57 | "no-unused-vars": ["error", { "ignoreRestSiblings": true }], 58 | "react/no-children-prop": 0, 59 | "react/display-name": 0, 60 | "react/prop-types": 0, 61 | "react/react-in-jsx-scope": 0, 62 | "semi": ["error", "never"], 63 | "simple-import-sort/exports": "error", 64 | "simple-import-sort/imports": "error", 65 | "sort-keys": "error" 66 | }, 67 | "settings": { 68 | "react": { 69 | "version": "detect" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/.prettierignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | public/* 3 | node_modules/* 4 | package.json 5 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 110, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @react-three/cannon-examples Changelog 2 | 3 | ## v2.4.1 - 2023-01-05 4 | 5 | - Update `@react-three/cannon` to v6.5.2 (@bjornstar) 6 | 7 | ## v2.4.0 - 2022-11-03 8 | 9 | - Remove some types & an expect-error that are no longer necessary (@bjornstar) 10 | - Inline the only niceColors that we use (@bjornstar) 11 | - Update `@react-three/cannon` to v6.5.0 (@bjornstar) 12 | - Update `three-stdlib` to v2.17.3 (@bjornstar) 13 | - Update `vite` to v3.2.2 (@bjornstar) 14 | - Replace `vite-react-jsx` with `@vitejs/plugin-react` (@bjornstar) 15 | - Remove `nice-color-palettes` (@bjornstar) 16 | - Remove `@vitejs/plugin-react-refresh` (@bjornstar) 17 | 18 | ## v2.3.0 - 2022-04-18 19 | 20 | - Use accurate ref types for all hooks (@bjornstar) 21 | - Prefer `PropsWithChildren` to `FC` (@bjornstar) 22 | - Update `@types/react` to v18 (@bjornstar) 23 | - Update `@react-three/drei` & `@react-three/fiber` (@bjornstar) 24 | - Update `styled-components` (@bjornstar) 25 | 26 | ## v2.2.0 - 2022-04-08 27 | 28 | - Use `react-dom/client` for react 18 features 29 | - Update `@react-three/cannon` to v6.2.0 30 | 31 | ## v2.1.0 - 2022-04-02 32 | 33 | - Update `@react-three/cannon` to v6.1.0 34 | 35 | ## v2.0.0 - 2022-04-01 36 | 37 | - Updated `react` to v18 38 | - Updated `three.js` to r139 39 | - Updated `@react-three/cannon` to v6 40 | 41 | ## v1.1.0 - 2022-03-19 42 | 43 | - [MondayMorning] Don't use non-null assertions (@bjornstar) 44 | - [`.eslintrc.json`] Clean up (@bjornstar) 45 | - [`.eslintrc.json`] Disallow non-null assertions (@bjornstar) 46 | - [`package.json`] Only devDependencies (@bjornstar) 47 | - [`tsconfig.json`] Alphabetize (@bjornstar) 48 | 49 | ## v1.0.1 - 2022-03-14 50 | 51 | - @react-three/cannon should always be the latest (@bjornstar) 52 | 53 | ## v1.0.0 - 2022-03-13 54 | 55 | - Initial Release 56 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/README.md: -------------------------------------------------------------------------------- 1 | # @react-three/cannon-examples 2 | 3 | To build the examples, both [@pmndrs/cannon-worker-api](../cannon-worker-api/) & [@react-three/cannon](../react-three-cannon) packages must have been built. 4 | 5 | To build the parent projects: 6 | 7 | ```bash 8 | npm run build --workspaces 9 | ``` 10 | 11 | To start the dev server: 12 | 13 | ```bash 14 | npm run dev --workspaces 15 | ``` 16 | 17 | And visit http://localhost:3000 in your browser 18 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @react-three/cannon 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-three/cannon-examples", 3 | "version": "2.4.1", 4 | "description": "Examples for @react-three/cannon", 5 | "private": true, 6 | "scripts": { 7 | "build": "tsc && vite build", 8 | "clean": "rm -rf dist/*", 9 | "dev": "vite", 10 | "serve": "vite preview", 11 | "eslint": "eslint .", 12 | "eslint-fix": "eslint --fix .", 13 | "prettier": "prettier --list-different .", 14 | "prettier-fix": "prettier --write ." 15 | }, 16 | "browserslist": [ 17 | ">0.2%", 18 | "not dead", 19 | "not ie <= 11", 20 | "not op_mini all" 21 | ], 22 | "devDependencies": { 23 | "@react-three/cannon": "^6.6.0", 24 | "@react-three/drei": "^9.97.5", 25 | "@react-three/fiber": "^8.15.16", 26 | "@types/lodash-es": "^4.17.6", 27 | "@types/react": "^18.0.5", 28 | "@types/react-dom": "^17.0.14", 29 | "@types/react-router-dom": "^5.3.3", 30 | "@types/styled-components": "^5.1.25", 31 | "@types/three": "^0.155.0", 32 | "@typescript-eslint/eslint-plugin": "^5.17.0", 33 | "@typescript-eslint/parser": "^5.17.0", 34 | "eslint": "^8.12.0", 35 | "eslint-config-prettier": "^8.5.0", 36 | "eslint-plugin-es": "^4.1.0", 37 | "eslint-plugin-react": "^7.29.4", 38 | "eslint-plugin-simple-import-sort": "^7.0.0", 39 | "eslint-plugin-typescript-enum": "^2.1.0", 40 | "lerp": "^1.0.3", 41 | "lodash-es": "^4.17.21", 42 | "prettier": "^2.6.1", 43 | "react": "^18.0.0", 44 | "react-dom": "^18.0.0", 45 | "react-is": "^18.0.0", 46 | "react-router-dom": "^6.2.2", 47 | "styled-components": "^5.3.5", 48 | "three": "^0.155.0", 49 | "three-stdlib": "^2.29.4", 50 | "typescript": "^4.6.3", 51 | "vite": "^5.1.1", 52 | "vite-react-jsx": "^1.1.2", 53 | "zustand": "^4.5.0" 54 | }, 55 | "lint-staged": { 56 | "*.{js,jsx,ts,tsx}": "eslint --cache --fix", 57 | "*.{js,json,jsx,md,ts,tsx}": "prettier --write" 58 | }, 59 | "license": "MIT", 60 | "sideEffects": false, 61 | "type": "module" 62 | } 63 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/public/Beetle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/public/Beetle.glb -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/public/bowl.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/public/bowl.glb -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/public/cup.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/public/cup.glb -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/public/diamond.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/public/diamond.glb -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/public/draco-gltf/draco_decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/public/draco-gltf/draco_decoder.wasm -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/public/pingpong.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/public/pingpong.glb -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/public/wheel.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/public/wheel.glb -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | import { HashRouter as Router, Link, Route, Routes, useMatch } from 'react-router-dom' 3 | import styled from 'styled-components' 4 | 5 | import { demoList, demos, isDemo } from './demos' 6 | import { GlobalStyle, PageStyle } from './styles' 7 | 8 | const Page = styled(PageStyle)` 9 | padding: 0px; 10 | 11 | & > h1 { 12 | position: absolute; 13 | top: 70px; 14 | left: 60px; 15 | } 16 | 17 | & > a { 18 | position: absolute; 19 | bottom: 60px; 20 | right: 60px; 21 | font-size: 1.2em; 22 | } 23 | ` 24 | 25 | const defaultName = 'MondayMorning' 26 | const visibleComponents = demos 27 | const DefaultComponent = visibleComponents[defaultName].Component 28 | 29 | const RoutedComponent = () => { 30 | const { 31 | params: { name: routeName }, 32 | } = useMatch('/demo/:name') || { params: { name: defaultName } } 33 | const demoName = isDemo(routeName) ? routeName : defaultName 34 | const { Component } = visibleComponents[demoName] 35 | return 36 | } 37 | 38 | function Intro() { 39 | return ( 40 | 41 | 42 | 43 | } /> 44 | } /> 45 | 46 | 47 | 48 | 49 | Github 50 | 51 | 52 | ) 53 | } 54 | 55 | function Demos() { 56 | const { 57 | params: { name: routeName }, 58 | } = useMatch('/demo/:name') || { params: { name: defaultName } } 59 | return ( 60 | 61 | {demoList.map((demoName, key) => ( 62 | 63 | 64 | 65 | ))} 66 | 67 | ) 68 | } 69 | 70 | export default function App() { 71 | return ( 72 | 73 | 74 | 75 | 76 | ) 77 | } 78 | 79 | const DemoPanel = styled.div` 80 | position: absolute; 81 | bottom: 50px; 82 | left: 50px; 83 | max-width: 250px; 84 | ` 85 | 86 | const Spot = styled.div` 87 | display: inline-block; 88 | width: 20px; 89 | height: 20px; 90 | border-radius: 50%; 91 | margin: 8px; 92 | ` 93 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/colors.ts: -------------------------------------------------------------------------------- 1 | // From `nice-color-palettes`, this one is index 17 2 | export default ['#99b898', '#fecea8', '#ff847c', '#e84a5f', '#2a363b'] as const 3 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/MondayMorning/createConfig.ts: -------------------------------------------------------------------------------- 1 | import type { Triplet } from '@react-three/cannon' 2 | 3 | const jointNames = [ 4 | 'neckJoint', 5 | 'leftKneeJoint', 6 | 'rightKneeJoint', 7 | 'leftHipJoint', 8 | 'rightHipJoint', 9 | 'spineJoint', 10 | 'leftShoulder', 11 | 'rightShoulder', 12 | 'leftElbowJoint', 13 | 'rightElbowJoint', 14 | ] 15 | 16 | const shapeNames = [ 17 | 'lowerLeftLeg', 18 | 'lowerRightLeg', 19 | 'upperLeftLeg', 20 | 'upperRightLeg', 21 | 'pelvis', 22 | 'upperBody', 23 | 'head', 24 | 'upperLeftArm', 25 | 'upperRightArm', 26 | 'lowerLeftArm', 27 | 'lowerRightArm', 28 | ] as const 29 | 30 | export type ShapeName = typeof shapeNames[number] 31 | export type JointName = typeof jointNames[number] 32 | 33 | type JointConfig = { 34 | angle: number 35 | axisA: Triplet 36 | axisB: Triplet 37 | bodyA: string 38 | bodyB: string 39 | pivotA: Triplet 40 | pivotB: Triplet 41 | twistAngle?: number 42 | } 43 | type ShapeConfig = { 44 | args: Triplet 45 | color: string 46 | mass: number 47 | position: Triplet 48 | } 49 | 50 | type RagdollConfig = { 51 | joints: Record 52 | shapes: Record 53 | } 54 | 55 | // Converted from the createRagdoll method in CANNON js ragdoll demo 56 | export function createRagdoll(scale: number, angleA = 0, angleB = 0, twistAngle = 0): RagdollConfig { 57 | const shouldersDistance = 0.45 * scale, 58 | upperArmLength = 0.4 * scale, 59 | lowerArmLength = 0.4 * scale, 60 | upperArmSize = 0.15 * scale, 61 | lowerArmSize = 0.15 * scale, 62 | neckLength = 0.1 * scale, 63 | headRadius = 0.2 * scale, 64 | upperBodyLength = 0.6 * scale, 65 | pelvisLength = 0.2 * scale, 66 | upperLegLength = 0.5 * scale, 67 | upperLegSize = 0.15 * scale, 68 | lowerLegSize = 0.15 * scale, 69 | lowerLegLength = 0.5 * scale 70 | 71 | // Lower legs 72 | const lowerLeftLeg: ShapeConfig = { 73 | args: [lowerLegSize * 0.5, lowerLegLength * 0.5, lowerArmSize * 0.5], 74 | color: 'lightblue', 75 | mass: scale, 76 | position: [-shouldersDistance / 3, lowerLegLength / 2, 0], 77 | } 78 | const lowerRightLeg: ShapeConfig = { 79 | args: [lowerLegSize * 0.5, lowerLegLength * 0.5, lowerArmSize * 0.5], 80 | color: 'lightblue', 81 | mass: scale, 82 | position: [shouldersDistance / 3, lowerLegLength / 2, 0], 83 | } 84 | 85 | // Upper legs 86 | const upperLeftLeg: ShapeConfig = { 87 | args: [upperLegSize * 0.5, upperLegLength * 0.5, lowerArmSize * 0.5], 88 | color: 'lightblue', 89 | mass: scale, 90 | position: [-shouldersDistance / 3, lowerLeftLeg.position[1] + lowerLegLength / 2 + upperLegLength / 2, 0], 91 | } 92 | const upperRightLeg: ShapeConfig = { 93 | args: [upperLegSize * 0.5, upperLegLength * 0.5, lowerArmSize * 0.5], 94 | color: 'lightblue', 95 | mass: scale, 96 | position: [shouldersDistance / 3, lowerRightLeg.position[1] + lowerLegLength / 2 + upperLegLength / 2, 0], 97 | } 98 | 99 | // Pelvis 100 | const pelvis: ShapeConfig = { 101 | args: [shouldersDistance * 0.5, pelvisLength * 0.5, lowerArmSize * 0.5], 102 | color: 'lightblue', 103 | mass: scale, 104 | position: [0, upperLeftLeg.position[1] + upperLegLength / 2 + pelvisLength / 2, 0], 105 | } 106 | 107 | // Upper body 108 | const upperBody: ShapeConfig = { 109 | args: [shouldersDistance * 0.5, upperBodyLength * 0.5, lowerArmSize * 0.75], 110 | color: 'indianred', 111 | mass: scale, 112 | position: [0, pelvis.position[1] + pelvisLength / 2 + upperBodyLength / 2, 0], 113 | } 114 | 115 | // Head 116 | const head: ShapeConfig = { 117 | args: [headRadius * 0.6, headRadius * 0.7, headRadius * 0.6], 118 | color: 'lightpink', 119 | mass: scale, 120 | position: [0, upperBody.position[1] + upperBodyLength / 2 + headRadius / 2 + neckLength, 0], 121 | } 122 | 123 | // Upper arms 124 | const upperLeftArm: ShapeConfig = { 125 | args: [upperArmLength * 0.5, upperArmSize * 0.5, upperArmSize * 0.5], 126 | color: 'indianred', 127 | mass: scale, 128 | position: [-shouldersDistance / 2 - upperArmLength / 2, upperBody.position[1] + upperBodyLength / 2, 0], 129 | } 130 | const upperRightArm: ShapeConfig = { 131 | args: [upperArmLength * 0.5, upperArmSize * 0.5, upperArmSize * 0.5], 132 | color: 'indianred', 133 | mass: scale, 134 | position: [shouldersDistance / 2 + upperArmLength / 2, upperBody.position[1] + upperBodyLength / 2, 0], 135 | } 136 | 137 | // lower arms 138 | const lowerLeftArm: ShapeConfig = { 139 | args: [lowerArmLength * 0.5, lowerArmSize * 0.5, lowerArmSize * 0.5], 140 | color: 'lightpink', 141 | mass: scale, 142 | position: [ 143 | upperLeftArm.position[0] - lowerArmLength / 2 - upperArmLength / 2, 144 | upperLeftArm.position[1], 145 | 0, 146 | ], 147 | } 148 | const lowerRightArm: ShapeConfig = { 149 | args: [lowerArmLength * 0.5, lowerArmSize * 0.5, lowerArmSize * 0.5], 150 | color: 'lightpink', 151 | mass: scale, 152 | position: [ 153 | upperRightArm.position[0] + lowerArmLength / 2 + upperArmLength / 2, 154 | upperRightArm.position[1], 155 | 0, 156 | ], 157 | } 158 | 159 | // joints 160 | 161 | // Neck joint 162 | const neckJoint: JointConfig = { 163 | angle: angleA, 164 | axisA: [0, 1, 0], 165 | axisB: [0, 1, 0], 166 | bodyA: 'head', 167 | bodyB: 'upperBody', 168 | pivotA: [0, -headRadius - neckLength / 2, 0], 169 | pivotB: [0, upperBodyLength / 2, 0], 170 | twistAngle: twistAngle, 171 | } 172 | 173 | // Knee joints 174 | const leftKneeJoint: JointConfig = { 175 | angle: angleA, 176 | axisA: [0, 1, 0], 177 | axisB: [0, 1, 0], 178 | bodyA: 'lowerLeftLeg', 179 | bodyB: 'upperLeftLeg', 180 | pivotA: [0, lowerLegLength / 2, 0], 181 | pivotB: [0, -upperLegLength / 2, 0], 182 | twistAngle: twistAngle, 183 | } 184 | const rightKneeJoint: JointConfig = { 185 | angle: angleA, 186 | axisA: [0, 1, 0], 187 | axisB: [0, 1, 0], 188 | bodyA: 'lowerRightLeg', 189 | bodyB: 'upperRightLeg', 190 | pivotA: [0, lowerLegLength / 2, 0], 191 | pivotB: [0, -upperLegLength / 2, 0], 192 | twistAngle: twistAngle, 193 | } 194 | 195 | // Hip joints 196 | const leftHipJoint: JointConfig = { 197 | angle: angleA, 198 | axisA: [0, 1, 0], 199 | axisB: [0, 1, 0], 200 | bodyA: 'upperLeftLeg', 201 | bodyB: 'pelvis', 202 | pivotA: [0, upperLegLength / 2, 0], 203 | pivotB: [-shouldersDistance / 3, -pelvisLength / 2, 0], 204 | twistAngle: twistAngle, 205 | } 206 | const rightHipJoint: JointConfig = { 207 | angle: angleA, 208 | axisA: [0, 1, 0], 209 | axisB: [0, 1, 0], 210 | bodyA: 'upperRightLeg', 211 | bodyB: 'pelvis', 212 | pivotA: [0, upperLegLength / 2, 0], 213 | pivotB: [shouldersDistance / 3, -pelvisLength / 2, 0], 214 | twistAngle: twistAngle, 215 | } 216 | 217 | // Spine 218 | const spineJoint: JointConfig = { 219 | angle: angleA, 220 | axisA: [0, 1, 0], 221 | axisB: [0, 1, 0], 222 | bodyA: 'pelvis', 223 | bodyB: 'upperBody', 224 | pivotA: [0, pelvisLength / 2, 0], 225 | pivotB: [0, -upperBodyLength / 2, 0], 226 | twistAngle: twistAngle, 227 | } 228 | 229 | // Shoulders 230 | const leftShoulder: JointConfig = { 231 | angle: angleB, 232 | axisA: [1, 0, 0], 233 | axisB: [1, 0, 0], 234 | bodyA: 'upperBody', 235 | bodyB: 'upperLeftArm', 236 | pivotA: [upperArmLength / 2, 0, 0], 237 | pivotB: [-shouldersDistance / 2, upperBodyLength / 2, 0], 238 | } 239 | const rightShoulder: JointConfig = { 240 | angle: angleB, 241 | axisA: [1, 0, 0], 242 | axisB: [1, 0, 0], 243 | bodyA: 'upperBody', 244 | bodyB: 'upperRightArm', 245 | pivotA: [-upperArmLength / 2, 0, 0], 246 | pivotB: [shouldersDistance / 2, upperBodyLength / 2, 0], 247 | twistAngle: twistAngle, 248 | } 249 | 250 | // Elbow joint 251 | const leftElbowJoint: JointConfig = { 252 | angle: angleA, 253 | axisA: [1, 0, 0], 254 | axisB: [1, 0, 0], 255 | bodyA: 'lowerLeftArm', 256 | bodyB: 'upperLeftArm', 257 | pivotA: [lowerArmLength / 2, 0, 0], 258 | pivotB: [-upperArmLength / 2, 0, 0], 259 | twistAngle: twistAngle, 260 | } 261 | const rightElbowJoint: JointConfig = { 262 | angle: angleA, 263 | axisA: [1, 0, 0], 264 | axisB: [1, 0, 0], 265 | bodyA: 'lowerRightArm', 266 | bodyB: 'upperRightArm', 267 | pivotA: [-lowerArmLength / 2, 0, 0], 268 | pivotB: [upperArmLength / 2, 0, 0], 269 | twistAngle: twistAngle, 270 | } 271 | 272 | return { 273 | joints: { 274 | leftElbowJoint, 275 | leftHipJoint, 276 | leftKneeJoint, 277 | leftShoulder, 278 | neckJoint, 279 | rightElbowJoint, 280 | rightHipJoint, 281 | rightKneeJoint, 282 | rightShoulder, 283 | spineJoint, 284 | }, 285 | shapes: { 286 | head, 287 | lowerLeftArm, 288 | lowerLeftLeg, 289 | lowerRightArm, 290 | lowerRightLeg, 291 | pelvis, 292 | upperBody, 293 | upperLeftArm, 294 | upperLeftLeg, 295 | upperRightArm, 296 | upperRightLeg, 297 | }, 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Pingpong/Text.tsx: -------------------------------------------------------------------------------- 1 | import type { GroupProps } from '@react-three/fiber' 2 | import { useMemo } from 'react' 3 | import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry' 4 | import { FontLoader } from 'three/examples/jsm/loaders/FontLoader' 5 | 6 | import fontJson from './resources/firasans_regular.json' 7 | 8 | const font = new FontLoader().parse(fontJson) 9 | const geom = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].map( 10 | (number) => new TextGeometry(number, { font, height: 0.1, size: 5 }), 11 | ) 12 | 13 | type TextProps = GroupProps & { 14 | color?: string 15 | count: string 16 | } 17 | 18 | export default function Text({ color = 'white', count, ...props }: TextProps): JSX.Element { 19 | const array = useMemo(() => [...count], [count]) 20 | return ( 21 | 22 | {array.map((char, index) => ( 23 | 28 | 29 | 30 | ))} 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Pingpong/index.tsx: -------------------------------------------------------------------------------- 1 | import { Physics, useBox, usePlane, useSphere } from '@react-three/cannon' 2 | import { useGLTF } from '@react-three/drei' 3 | import { Canvas, useFrame, useLoader } from '@react-three/fiber' 4 | import lerp from 'lerp' 5 | import clamp from 'lodash-es/clamp' 6 | import { Suspense, useRef } from 'react' 7 | import type { Group, Material, Mesh, Object3D, Skeleton } from 'three' 8 | import { TextureLoader } from 'three' 9 | import type { GLTF } from 'three-stdlib/loaders/GLTFLoader' 10 | import { create } from 'zustand' 11 | 12 | import earthImg from './resources/cross.jpg' 13 | import pingSound from './resources/ping.mp3' 14 | import Text from './Text' 15 | 16 | type State = { 17 | api: { 18 | pong: (velocity: number) => void 19 | reset: (welcome: boolean) => void 20 | } 21 | count: number 22 | welcome: boolean 23 | } 24 | 25 | const ping = new Audio(pingSound) 26 | const useStore = create((set) => ({ 27 | api: { 28 | pong(velocity) { 29 | ping.currentTime = 0 30 | ping.volume = clamp(velocity / 20, 0, 1) 31 | ping.play() 32 | if (velocity > 4) set((state) => ({ count: state.count + 1 })) 33 | }, 34 | reset: (welcome) => set((state) => ({ count: welcome ? state.count : 0, welcome })), 35 | }, 36 | count: 0, 37 | welcome: true, 38 | })) 39 | 40 | type PingPongGLTF = GLTF & { 41 | materials: Record<'foam' | 'glove' | 'lower' | 'side' | 'upper' | 'wood', Material> 42 | nodes: Record<'Bone' | 'Bone003' | 'Bone006' | 'Bone010', Object3D> & 43 | Record<'mesh' | 'mesh_1' | 'mesh_2' | 'mesh_3' | 'mesh_4', Mesh> & { 44 | arm: Mesh & { skeleton: Skeleton } 45 | } 46 | } 47 | 48 | function Paddle() { 49 | const { nodes, materials } = useGLTF('/pingpong.glb', '/draco-gltf/') as PingPongGLTF 50 | const { pong } = useStore((state) => state.api) 51 | const welcome = useStore((state) => state.welcome) 52 | const count = useStore((state) => state.count) 53 | const model = useRef(null) 54 | const [ref, api] = useBox( 55 | () => ({ 56 | args: [3.4, 1, 3], 57 | onCollide: (e) => pong(e.contact.impactVelocity), 58 | type: 'Kinematic', 59 | }), 60 | useRef(null), 61 | ) 62 | const values = useRef([0, 0]) 63 | useFrame((state) => { 64 | values.current[0] = lerp(values.current[0], (state.mouse.x * Math.PI) / 5, 0.2) 65 | values.current[1] = lerp(values.current[1], (state.mouse.x * Math.PI) / 5, 0.2) 66 | api.position.set(state.mouse.x * 10, state.mouse.y * 5, 0) 67 | api.rotation.set(0, 0, values.current[1]) 68 | if (!model.current) return 69 | model.current.rotation.x = lerp(model.current.rotation.x, welcome ? Math.PI / 2 : 0, 0.2) 70 | model.current.rotation.y = values.current[0] 71 | }) 72 | 73 | return ( 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ) 101 | } 102 | 103 | function Ball() { 104 | const map = useLoader(TextureLoader, earthImg) 105 | const [ref] = useSphere(() => ({ args: [0.5], mass: 1, position: [0, 5, 0] }), useRef(null)) 106 | return ( 107 | 108 | 109 | 110 | 111 | ) 112 | } 113 | 114 | function ContactGround() { 115 | const { reset } = useStore((state) => state.api) 116 | const [ref] = usePlane( 117 | () => ({ 118 | onCollide: () => reset(true), 119 | position: [0, -10, 0], 120 | rotation: [-Math.PI / 2, 0, 0], 121 | type: 'Static', 122 | }), 123 | useRef(null), 124 | ) 125 | return 126 | } 127 | 128 | const style = (welcome: boolean) => 129 | ({ 130 | color: 'white', 131 | display: welcome ? 'block' : 'none', 132 | fontSize: '1.2em', 133 | left: 50, 134 | position: 'absolute', 135 | top: 50, 136 | } as const) 137 | 138 | export default function () { 139 | const welcome = useStore((state) => state.welcome) 140 | const { reset } = useStore((state) => state.api) 141 | 142 | return ( 143 | <> 144 | welcome && reset(false)} 147 | shadows 148 | > 149 | 150 | 151 | 152 | 163 | 177 | 178 | 179 | 180 | 181 | 182 | {!welcome && } 183 | 184 | 185 | 186 | 187 | 188 |
* click anywhere to start
189 | 190 | ) 191 | } 192 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Pingpong/lerp.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'lerp' { 2 | declare function lerp(start: number, end: number, alpha: number): number 3 | export default lerp 4 | } 5 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Pingpong/resources/cross.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/src/demos/Pingpong/resources/cross.jpg -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Pingpong/resources/firasans_regular.json: -------------------------------------------------------------------------------- 1 | { 2 | "glyphs": { 3 | "0": { 4 | "ha": 775, 5 | "x_min": 0, 6 | "x_max": 0, 7 | "o": "m 388 943 b 699 464 596 943 699 771 b 388 -17 699 156 596 -17 b 76 464 179 -17 76 156 b 388 943 76 772 179 943 m 388 842 b 210 464 271 842 210 736 b 388 85 210 190 271 85 b 565 464 503 85 565 190 b 388 842 565 736 503 842 " 8 | }, 9 | "1": { 10 | "ha": 601, 11 | "x_min": 0, 12 | "x_max": 0, 13 | "o": "m 449 0 l 321 0 l 321 793 l 104 661 l 49 751 l 336 929 l 449 929 " 14 | }, 15 | "2": { 16 | "ha": 688, 17 | "x_min": 0, 18 | "x_max": 0, 19 | "o": "m 317 943 b 596 689 488 943 596 833 b 200 107 596 515 485 392 l 618 107 l 603 0 l 54 0 l 54 101 b 463 683 382 443 463 532 b 310 838 463 781 400 838 b 118 736 232 838 181 807 l 35 803 b 317 943 110 896 200 943 " 20 | }, 21 | "3": { 22 | "ha": 693, 23 | "x_min": 0, 24 | "x_max": 0, 25 | "o": "m 313 943 b 588 707 490 943 588 832 b 407 493 588 589 511 518 b 617 268 524 482 617 408 b 300 -17 617 106 493 -17 b 21 108 189 -17 92 24 l 97 179 b 296 88 158 115 221 88 b 483 269 413 88 483 160 b 299 436 483 397 411 436 l 229 436 l 244 535 l 292 535 b 460 699 383 535 460 590 b 306 840 460 786 399 840 b 114 757 232 840 176 815 l 47 833 b 313 943 126 907 213 943 " 26 | }, 27 | "4": { 28 | "ha": 739, 29 | "x_min": 0, 30 | "x_max": 0, 31 | "o": "m 697 229 l 576 229 l 576 0 l 453 0 l 453 229 l 56 229 l 56 321 l 335 943 l 442 899 l 190 331 l 454 331 l 465 581 l 576 581 l 576 331 l 697 331 " 32 | }, 33 | "5": { 34 | "ha": 696, 35 | "x_min": 0, 36 | "x_max": 0, 37 | "o": "m 583 829 l 221 829 l 221 556 b 369 592 269 581 319 592 b 633 297 526 592 633 482 b 311 -17 633 113 504 -17 b 36 101 193 -17 108 28 l 111 175 b 310 88 169 117 229 88 b 500 300 426 88 500 164 b 331 493 500 443 426 493 b 199 461 281 493 243 482 l 99 461 l 99 929 l 601 929 " 38 | }, 39 | "6": { 40 | "ha": 740, 41 | "x_min": 0, 42 | "x_max": 0, 43 | "o": "m 428 611 b 685 315 564 611 685 514 b 389 -17 685 113 553 -17 b 76 436 163 -17 76 169 b 428 943 76 740 208 943 b 619 886 503 943 565 922 l 569 801 b 426 842 526 828 478 842 b 204 489 292 842 211 701 b 428 611 265 575 340 611 m 389 85 b 556 311 499 85 556 175 b 406 510 556 458 493 510 b 206 382 322 510 253 458 b 389 85 211 179 265 85 " 44 | }, 45 | "7": { 46 | "ha": 617, 47 | "x_min": 0, 48 | "x_max": 0, 49 | "o": "m 575 833 l 228 -14 l 111 25 l 446 825 l 35 825 l 35 929 l 575 929 " 50 | }, 51 | "8": { 52 | "ha": 765, 53 | "x_min": 0, 54 | "x_max": 0, 55 | "o": "m 507 499 b 703 249 635 447 703 365 b 381 -17 703 93 569 -17 b 63 246 189 -17 63 93 b 246 488 63 365 132 438 b 101 704 146 538 101 608 b 383 943 101 861 242 943 b 665 708 522 943 665 867 b 507 499 665 614 613 551 m 383 847 b 226 703 288 847 226 796 b 399 538 226 607 292 572 l 422 529 b 540 704 506 576 540 625 b 383 847 540 790 485 847 m 382 85 b 569 247 499 85 569 150 b 367 443 569 347 517 392 l 332 456 b 196 246 240 411 196 347 b 382 85 196 144 267 85 " 56 | }, 57 | "9": { 58 | "ha": 729, 59 | "x_min": 0, 60 | "x_max": 0, 61 | "o": "m 360 943 b 660 582 560 943 660 800 b 165 -31 660 197 507 68 l 136 65 b 528 449 369 132 518 239 b 319 338 488 386 414 338 b 63 636 175 338 63 451 b 360 943 63 831 199 943 m 346 438 b 531 553 425 438 485 482 b 363 842 535 763 478 842 b 192 633 254 842 192 767 b 346 438 192 500 256 438 " 62 | } 63 | }, 64 | "familyName": "Fira Sans", 65 | "ascender": 1299, 66 | "descender": -368, 67 | "underlinePosition": -104, 68 | "underlineThickness": 69, 69 | "boundingBox": { "yMin": -490, "xMin": -1050, "yMax": 1533, "xMax": 1889 }, 70 | "resolution": 1000, 71 | "original_font_information": { 72 | "format": 0, 73 | "copyright": "Digitized data copyright 2012-2016, The Mozilla Foundation and Telefonica S.A.", 74 | "fontFamily": "Fira Sans", 75 | "fontSubfamily": "Regular", 76 | "uniqueID": "4.203;CTDB;FiraSans-Regular", 77 | "fullName": "Fira Sans Regular", 78 | "version": "Version 4.203;PS 004.203;hotconv 1.0.88;makeotf.lib2.5.64775", 79 | "postScriptName": "FiraSans-Regular", 80 | "trademark": "Fira Sans is a trademark of The Mozilla Corporation.", 81 | "manufacturer": "Carrois Corporate GbR & Edenspiekermann AG", 82 | "designer": "Carrois Corporate & Edenspiekermann AG", 83 | "manufacturerURL": "http://www.carrois.com", 84 | "designerURL": "http://www.carrois.com", 85 | "licence": "Licensed under the Open Font License, version 1.1 or later", 86 | "licenceURL": "http://scripts.sil.org/OFL" 87 | }, 88 | "cssFontWeight": "normal", 89 | "cssFontStyle": "normal" 90 | } 91 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Pingpong/resources/ping.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmndrs/use-cannon/601a417ad42d939467e1d73e99e73f859d083c65/packages/react-three-cannon-examples/src/demos/Pingpong/resources/ping.mp3 -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Raycast/index.tsx: -------------------------------------------------------------------------------- 1 | import type { Triplet } from '@react-three/cannon' 2 | import { Physics, useBox, useRaycastAll, useSphere } from '@react-three/cannon' 3 | import { Html } from '@react-three/drei' 4 | import { OrbitControls } from '@react-three/drei' 5 | import type { GroupProps, Object3DNode } from '@react-three/fiber' 6 | import { Canvas, extend, useFrame, useThree } from '@react-three/fiber' 7 | import { Suspense, useLayoutEffect, useMemo, useRef, useState } from 'react' 8 | import type { Mesh, PerspectiveCamera } from 'three' 9 | import { BufferGeometry, Line as ThreeLine, Vector3 } from 'three' 10 | 11 | import { prettyPrint } from './prettyPrint' 12 | 13 | extend({ ThreeLine }) 14 | 15 | declare global { 16 | namespace JSX { 17 | interface IntrinsicElements { 18 | threeLine: Object3DNode 19 | } 20 | } 21 | } 22 | 23 | function Plane(props: GroupProps) { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | } 37 | 38 | type SphereProps = { 39 | position: Triplet 40 | radius: number 41 | } 42 | 43 | function Sphere({ radius, position }: SphereProps) { 44 | const [ref, api] = useSphere(() => ({ args: [radius], position, type: 'Static' }), useRef(null)) 45 | useFrame(({ clock: { elapsedTime } }) => { 46 | api.position.set(position[0], position[1], Math.sin(elapsedTime / 3) * 2) 47 | }) 48 | return ( 49 | 50 | 51 | 52 | 53 | ) 54 | } 55 | 56 | type CubeProps = { 57 | position: Triplet 58 | size: Triplet 59 | } 60 | 61 | function Cube({ size, position }: CubeProps) { 62 | const [ref, api] = useBox(() => ({ args: size, position, type: 'Static' }), useRef(null)) 63 | useFrame(({ clock: { elapsedTime } }) => { 64 | api.position.set(Math.sin(elapsedTime / 2) * 2, position[1], position[2]) 65 | }) 66 | return ( 67 | 68 | 69 | 70 | 71 | ) 72 | } 73 | 74 | type RayProps = { 75 | from: Triplet 76 | setHit: (e: {}) => void 77 | to: Triplet 78 | } 79 | 80 | function Ray({ from, to, setHit }: RayProps) { 81 | useRaycastAll({ from, to }, setHit) 82 | const geometry = useMemo(() => { 83 | const points = [from, to].map((v) => new Vector3(...v)) 84 | return new BufferGeometry().setFromPoints(points) 85 | }, [from, to]) 86 | 87 | return ( 88 | 89 | 90 | 91 | ) 92 | } 93 | 94 | function Text({ hit }: { hit: unknown }) { 95 | return ( 96 | 97 |
{prettyPrint(hit)}
98 | 99 | ) 100 | } 101 | 102 | function Raycast() { 103 | const [hit, setHit] = useState({}) 104 | 105 | return ( 106 | <> 107 | 108 | 109 | 110 | ) 111 | } 112 | 113 | const Camera = () => { 114 | const cameraRef = useRef(null) 115 | const { gl, camera } = useThree() 116 | const set = useThree((state) => state.set) 117 | const size = useThree((state) => state.size) 118 | 119 | useLayoutEffect(() => { 120 | if (!cameraRef.current) return 121 | cameraRef.current.aspect = size.width / size.height 122 | cameraRef.current.updateProjectionMatrix() 123 | }, [size]) 124 | 125 | useLayoutEffect(() => { 126 | const camera = cameraRef.current 127 | if (!camera) return 128 | set(() => ({ camera })) 129 | }, []) 130 | 131 | useFrame(() => { 132 | if (!cameraRef.current) return 133 | cameraRef.current.updateMatrixWorld() 134 | }) 135 | 136 | return ( 137 | <> 138 | 139 | 148 | 149 | ) 150 | } 151 | 152 | export default () => ( 153 | 154 | 155 | 156 | 157 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | ) 176 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/Raycast/prettyPrint.ts: -------------------------------------------------------------------------------- 1 | const vec3ArrayRegex = (indentCount: number) => 2 | new RegExp( 3 | `\\: \\[\\n {${indentCount}}([+-]?\\d+\\.?\\d*),\\n {${indentCount}}([+-]?\\d+\\.?\\d*),\\n {${indentCount}}([+-]?\\d+\\.?\\d*)\\n {${ 4 | indentCount - 2 5 | }}\\]`, 6 | 'gm', 7 | ) 8 | const vec3ArrayReplacement = (_: unknown, v1: unknown, v2: unknown, v3: unknown) => 9 | `: [ ${v1}, ${v2}, ${v3} ]` 10 | 11 | const shapeDataRegex = /^ {2}"shape": \{.*?^ {2}\}/gms 12 | const shapeDataReplacement = ` "shape": { 13 | // Shape data here... 14 | }` 15 | 16 | const bodyDataRegex = /^ {2}"body": \{.*?^ {2}\}/gms 17 | const bodyDataReplacement = ` "body": { 18 | // Body data here... 19 | }` 20 | 21 | export const prettyPrint = (data: unknown) => { 22 | const indentCount = 2 23 | return JSON.stringify(data, null, indentCount) 24 | .replace(vec3ArrayRegex(indentCount * 2), vec3ArrayReplacement) 25 | .replace(vec3ArrayRegex(indentCount * 3), vec3ArrayReplacement) 26 | .replace(shapeDataRegex, shapeDataReplacement) 27 | .replace(bodyDataRegex, bodyDataReplacement) 28 | } 29 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/RaycastVehicle/Chassis.tsx: -------------------------------------------------------------------------------- 1 | import { useGLTF } from '@react-three/drei' 2 | import { forwardRef } from 'react' 3 | import type { Material, Mesh } from 'three' 4 | import type { GLTF } from 'three-stdlib/loaders/GLTFLoader' 5 | 6 | useGLTF.preload('/Beetle.glb') 7 | 8 | // Initially Auto-generated by: https://github.com/pmndrs/gltfjsx 9 | // Model via KrStolorz on Sketchfab, CC-BY-4.0 10 | // https://sketchfab.com/3d-models/low-poly-volkswagen-beetle-f680ad7e98e445eaafed1a70f2c53911 11 | 12 | const beetleMaterials = [ 13 | 'Black paint', 14 | 'Black plastic', 15 | 'Chrom', 16 | 'Glass', 17 | 'Headlight', 18 | 'Interior (dark)', 19 | 'Interior (light)', 20 | 'License Plate', 21 | 'Orange plastic', 22 | 'Paint', 23 | 'Reflector', 24 | 'Reverse lights', 25 | 'Rubber', 26 | 'Steel', 27 | 'Tail lights', 28 | 'Underbody', 29 | ] as const 30 | type BeetleMaterial = typeof beetleMaterials[number] 31 | 32 | const beetleNodes = [ 33 | 'chassis_1', 34 | 'chassis_2', 35 | 'chassis_3', 36 | 'chassis_4', 37 | 'chassis_5', 38 | 'chassis_6', 39 | 'chassis_7', 40 | 'chassis_8', 41 | 'chassis_9', 42 | 'chassis_10', 43 | 'chassis_11', 44 | 'chassis_12', 45 | 'chassis_13', 46 | 'chassis_14', 47 | 'chassis_15', 48 | 'chassis_16', 49 | ] as const 50 | type BeetleNode = typeof beetleNodes[number] 51 | 52 | type BeetleGLTF = GLTF & { 53 | materials: Record 54 | nodes: Record 55 | } 56 | 57 | export const Chassis = forwardRef((_, ref) => { 58 | const { nodes, materials } = useGLTF('/Beetle.glb') as BeetleGLTF 59 | 60 | return ( 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ) 87 | }) 88 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/RaycastVehicle/Vehicle.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps, WheelInfoOptions } from '@react-three/cannon' 2 | import { useBox, useRaycastVehicle } from '@react-three/cannon' 3 | import { useFrame } from '@react-three/fiber' 4 | import { useEffect, useRef } from 'react' 5 | import type { Group, Mesh } from 'three' 6 | 7 | import { Chassis } from './Chassis' 8 | import { useControls } from './use-controls' 9 | import { Wheel } from './Wheel' 10 | 11 | export type VehicleProps = Required> & { 12 | back?: number 13 | force?: number 14 | front?: number 15 | height?: number 16 | maxBrake?: number 17 | radius?: number 18 | steer?: number 19 | width?: number 20 | } 21 | 22 | function Vehicle({ 23 | angularVelocity, 24 | back = -1.15, 25 | force = 1500, 26 | front = 1.3, 27 | height = -0.04, 28 | maxBrake = 50, 29 | position, 30 | radius = 0.7, 31 | rotation, 32 | steer = 0.5, 33 | width = 1.2, 34 | }: VehicleProps) { 35 | const wheels = [useRef(null), useRef(null), useRef(null), useRef(null)] 36 | 37 | const controls = useControls() 38 | 39 | const wheelInfo: WheelInfoOptions = { 40 | axleLocal: [-1, 0, 0], // This is inverted for asymmetrical wheel models (left v. right sided) 41 | customSlidingRotationalSpeed: -30, 42 | dampingCompression: 4.4, 43 | dampingRelaxation: 10, 44 | directionLocal: [0, -1, 0], // set to same as Physics Gravity 45 | frictionSlip: 2, 46 | maxSuspensionForce: 1e4, 47 | maxSuspensionTravel: 0.3, 48 | radius, 49 | suspensionRestLength: 0.3, 50 | suspensionStiffness: 30, 51 | useCustomSlidingRotationalSpeed: true, 52 | } 53 | 54 | const wheelInfo1: WheelInfoOptions = { 55 | ...wheelInfo, 56 | chassisConnectionPointLocal: [-width / 2, height, front], 57 | isFrontWheel: true, 58 | } 59 | const wheelInfo2: WheelInfoOptions = { 60 | ...wheelInfo, 61 | chassisConnectionPointLocal: [width / 2, height, front], 62 | isFrontWheel: true, 63 | } 64 | const wheelInfo3: WheelInfoOptions = { 65 | ...wheelInfo, 66 | chassisConnectionPointLocal: [-width / 2, height, back], 67 | isFrontWheel: false, 68 | } 69 | const wheelInfo4: WheelInfoOptions = { 70 | ...wheelInfo, 71 | chassisConnectionPointLocal: [width / 2, height, back], 72 | isFrontWheel: false, 73 | } 74 | 75 | const [chassisBody, chassisApi] = useBox( 76 | () => ({ 77 | allowSleep: false, 78 | angularVelocity, 79 | args: [1.7, 1, 4], 80 | mass: 500, 81 | onCollide: (e) => console.log('bonk', e.body.userData), 82 | position, 83 | rotation, 84 | }), 85 | useRef(null), 86 | ) 87 | 88 | const [vehicle, vehicleApi] = useRaycastVehicle( 89 | () => ({ 90 | chassisBody, 91 | wheelInfos: [wheelInfo1, wheelInfo2, wheelInfo3, wheelInfo4], 92 | wheels, 93 | }), 94 | useRef(null), 95 | ) 96 | 97 | useEffect(() => vehicleApi.sliding.subscribe((v) => console.log('sliding', v)), []) 98 | 99 | useFrame(() => { 100 | const { backward, brake, forward, left, reset, right } = controls.current 101 | 102 | for (let e = 2; e < 4; e++) { 103 | vehicleApi.applyEngineForce(forward || backward ? force * (forward && !backward ? -1 : 1) : 0, 2) 104 | } 105 | 106 | for (let s = 0; s < 2; s++) { 107 | vehicleApi.setSteeringValue(left || right ? steer * (left && !right ? 1 : -1) : 0, s) 108 | } 109 | 110 | for (let b = 2; b < 4; b++) { 111 | vehicleApi.setBrake(brake ? maxBrake : 0, b) 112 | } 113 | 114 | if (reset) { 115 | chassisApi.position.set(...position) 116 | chassisApi.velocity.set(0, 0, 0) 117 | chassisApi.angularVelocity.set(...angularVelocity) 118 | chassisApi.rotation.set(...rotation) 119 | } 120 | }) 121 | 122 | return ( 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | ) 131 | } 132 | 133 | export default Vehicle 134 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/RaycastVehicle/Wheel.tsx: -------------------------------------------------------------------------------- 1 | import type { CylinderProps } from '@react-three/cannon' 2 | import { useCompoundBody } from '@react-three/cannon' 3 | import { useGLTF } from '@react-three/drei' 4 | import { forwardRef } from 'react' 5 | import type { Group, Material, Mesh } from 'three' 6 | import type { GLTF } from 'three-stdlib/loaders/GLTFLoader' 7 | 8 | useGLTF.preload('/wheel.glb') 9 | 10 | // Initially Auto-generated by: https://github.com/pmndrs/gltfjsx 11 | 12 | type WheelGLTF = GLTF & { 13 | materials: Record<'Chrom' | 'Rubber' | 'Steel', Material> 14 | nodes: Record<'wheel_1' | 'wheel_2' | 'wheel_3', Mesh> 15 | } 16 | 17 | type WheelProps = CylinderProps & { 18 | leftSide?: boolean 19 | radius: number 20 | } 21 | 22 | export const Wheel = forwardRef(({ leftSide, radius = 0.7, ...props }, ref) => { 23 | const { 24 | materials: { Chrom, Rubber, Steel }, 25 | nodes, 26 | } = useGLTF('/wheel.glb') as WheelGLTF 27 | 28 | useCompoundBody( 29 | () => ({ 30 | collisionFilterGroup: 0, 31 | mass: 1, 32 | material: 'wheel', 33 | shapes: [{ args: [radius, radius, 0.5, 16], rotation: [0, 0, -Math.PI / 2], type: 'Cylinder' }], 34 | type: 'Kinematic', 35 | ...props, 36 | }), 37 | ref, 38 | ) 39 | 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | }) 50 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/RaycastVehicle/index.tsx: -------------------------------------------------------------------------------- 1 | // This demo is also playable without installation here: 2 | // https://codesandbox.io/s/basic-demo-forked-ebr0x 3 | 4 | import type { CylinderArgs, CylinderProps, PlaneProps } from '@react-three/cannon' 5 | import { Debug, Physics, useCylinder, usePlane } from '@react-three/cannon' 6 | import { Environment, OrbitControls } from '@react-three/drei' 7 | import { Canvas } from '@react-three/fiber' 8 | import { Suspense, useRef } from 'react' 9 | import type { Group, Mesh } from 'three' 10 | 11 | import { useToggledControl } from '../../use-toggled-control' 12 | import Vehicle from './Vehicle' 13 | 14 | function Plane(props: PlaneProps) { 15 | const [ref] = usePlane(() => ({ material: 'ground', type: 'Static', ...props }), useRef(null)) 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | 26 | function Pillar(props: CylinderProps) { 27 | const args: CylinderArgs = [0.7, 0.7, 5, 16] 28 | const [ref] = useCylinder( 29 | () => ({ 30 | args, 31 | mass: 10, 32 | ...props, 33 | }), 34 | useRef(null), 35 | ) 36 | return ( 37 | 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | const style = { 45 | color: 'white', 46 | fontSize: '1.2em', 47 | left: 50, 48 | position: 'absolute', 49 | top: 20, 50 | } as const 51 | 52 | const VehicleScene = () => { 53 | const ToggledDebug = useToggledControl(Debug, '?') 54 | 55 | return ( 56 | <> 57 | 58 | 59 | 60 | 61 | 69 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 |
89 |           * WASD to drive, space to brake
90 |           
r to reset 91 |
? to debug 92 |
93 |
94 | 95 | ) 96 | } 97 | 98 | export default VehicleScene 99 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/RaycastVehicle/use-controls.ts: -------------------------------------------------------------------------------- 1 | import type { MutableRefObject } from 'react' 2 | import { useEffect, useRef } from 'react' 3 | 4 | function useKeyControls( 5 | { current }: MutableRefObject>, 6 | map: Record, 7 | ) { 8 | useEffect(() => { 9 | const handleKeydown = ({ key }: KeyboardEvent) => { 10 | if (!isKeyCode(key)) return 11 | current[map[key]] = true 12 | } 13 | window.addEventListener('keydown', handleKeydown) 14 | const handleKeyup = ({ key }: KeyboardEvent) => { 15 | if (!isKeyCode(key)) return 16 | current[map[key]] = false 17 | } 18 | window.addEventListener('keyup', handleKeyup) 19 | return () => { 20 | window.removeEventListener('keydown', handleKeydown) 21 | window.removeEventListener('keyup', handleKeyup) 22 | } 23 | }, [current, map]) 24 | } 25 | 26 | const keyControlMap = { 27 | ' ': 'brake', 28 | ArrowDown: 'backward', 29 | ArrowLeft: 'left', 30 | ArrowRight: 'right', 31 | ArrowUp: 'forward', 32 | a: 'left', 33 | d: 'right', 34 | r: 'reset', 35 | s: 'backward', 36 | w: 'forward', 37 | } as const 38 | 39 | type KeyCode = keyof typeof keyControlMap 40 | type GameControl = typeof keyControlMap[KeyCode] 41 | 42 | const keyCodes = Object.keys(keyControlMap) as KeyCode[] 43 | const isKeyCode = (v: unknown): v is KeyCode => keyCodes.includes(v as KeyCode) 44 | 45 | export function useControls() { 46 | const controls = useRef>({ 47 | backward: false, 48 | brake: false, 49 | forward: false, 50 | left: false, 51 | reset: false, 52 | right: false, 53 | }) 54 | 55 | useKeyControls(controls, keyControlMap) 56 | 57 | return controls 58 | } 59 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-Chain.tsx: -------------------------------------------------------------------------------- 1 | import type { CylinderArgs, Triplet } from '@react-three/cannon' 2 | import { Physics, useBox, useConeTwistConstraint, useCylinder, useSphere } from '@react-three/cannon' 3 | import { Canvas, useFrame } from '@react-three/fiber' 4 | import type { PropsWithChildren } from 'react' 5 | import { createContext, createRef, useCallback, useContext, useMemo, useRef, useState } from 'react' 6 | import type { Mesh, Object3D } from 'three' 7 | import { Color } from 'three' 8 | 9 | const maxMultiplierExamples = [0, 500, 1000, 1500, undefined] as const 10 | 11 | function notUndefined(value: T | undefined): value is T { 12 | return value !== undefined 13 | } 14 | 15 | const parent = createContext({ 16 | position: [0, 0, 0] as Triplet, 17 | ref: createRef(), 18 | }) 19 | 20 | type ChainLinkProps = { 21 | args?: CylinderArgs 22 | color?: Color | string 23 | maxMultiplier?: number 24 | } 25 | 26 | function ChainLink({ 27 | args = [0.5, 0.5, 2, 16], 28 | children, 29 | color = 'white', 30 | maxMultiplier, 31 | }: PropsWithChildren): JSX.Element { 32 | const { 33 | position: [x, y, z], 34 | ref: parentRef, 35 | } = useContext(parent) 36 | 37 | const [, , height = 2] = args 38 | const position: Triplet = [x, y - height, z] 39 | 40 | const [ref] = useCylinder( 41 | () => ({ 42 | args, 43 | linearDamping: 0.8, 44 | mass: 1, 45 | position, 46 | }), 47 | useRef(null), 48 | ) 49 | 50 | useConeTwistConstraint(parentRef, ref, { 51 | angle: Math.PI / 8, 52 | axisA: [0, 1, 0], 53 | axisB: [0, 1, 0], 54 | maxMultiplier, 55 | pivotA: [0, -height / 2, 0], 56 | pivotB: [0, height / 2, 0], 57 | twistAngle: 0, 58 | }) 59 | 60 | return ( 61 | <> 62 | 63 | 64 | 65 | 66 | {children} 67 | 68 | ) 69 | } 70 | 71 | type ChainProps = { 72 | length: number 73 | maxMultiplier?: number 74 | } 75 | 76 | function Chain({ children, length, maxMultiplier }: PropsWithChildren): JSX.Element { 77 | const color = useMemo(() => { 78 | if (maxMultiplier === undefined) return 'white' 79 | 80 | const maxExample = Math.max(...maxMultiplierExamples.filter(notUndefined)) 81 | const red = Math.floor(Math.min(100, (maxMultiplier / maxExample) * 100)) 82 | 83 | return new Color(`rgb(${red}%, 0%, ${100 - red}%)`) 84 | }, [maxMultiplier]) 85 | 86 | return ( 87 | <> 88 | {Array.from({ length }).reduce((acc: React.ReactNode) => { 89 | return ( 90 | 91 | {acc} 92 | 93 | ) 94 | }, children)} 95 | 96 | ) 97 | } 98 | 99 | function PointerHandle({ children, size }: PropsWithChildren<{ size: number }>): JSX.Element { 100 | const position: Triplet = [0, 0, 0] 101 | const args: Triplet = [size, size, size * 2] 102 | 103 | const [ref, api] = useBox(() => ({ args, position, type: 'Kinematic' }), useRef(null)) 104 | 105 | useFrame(({ mouse: { x, y }, viewport: { height, width } }) => { 106 | api.position.set((x * width) / 2, (y * height) / 2, 0) 107 | }) 108 | 109 | return ( 110 | 111 | 112 | 113 | 114 | 115 | {children} 116 | 117 | ) 118 | } 119 | 120 | type StaticHandleProps = { 121 | position: Triplet 122 | radius: number 123 | } 124 | 125 | function StaticHandle({ children, position, radius }: PropsWithChildren): JSX.Element { 126 | const [ref] = useSphere(() => ({ args: [radius], position, type: 'Static' }), useRef(null)) 127 | return ( 128 | 129 | 130 | 131 | 132 | 133 | {children} 134 | 135 | ) 136 | } 137 | 138 | const style = { 139 | color: 'white', 140 | fontSize: '1.2em', 141 | left: 50, 142 | position: 'absolute', 143 | top: 20, 144 | } as const 145 | 146 | function ChainScene(): JSX.Element { 147 | const [resetCount, setResetCount] = useState(0) 148 | 149 | const reset = useCallback(() => { 150 | setResetCount((prev) => prev + 1) 151 | }, []) 152 | 153 | const separation = 4 154 | 155 | return ( 156 | <> 157 | 158 | 159 | 160 | 161 | 169 | 170 | 171 | 172 | 173 | {maxMultiplierExamples.map((maxMultiplier, index) => ( 174 | 179 | 180 | 181 | ))} 182 | 183 | 184 |
185 |
186 |           * move pointer to move the box
187 |           
188 | and break the chain constraints, 189 |
190 | click to reset 191 |
192 |
193 | 194 | ) 195 | } 196 | 197 | export default ChainScene 198 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-CompoundBody.tsx: -------------------------------------------------------------------------------- 1 | import type { CompoundBodyProps, PlaneProps, Triplet } from '@react-three/cannon' 2 | import { Debug, Physics, useCompoundBody, usePlane } from '@react-three/cannon' 3 | import { Canvas } from '@react-three/fiber' 4 | import { useEffect, useRef, useState } from 'react' 5 | import type { Group } from 'three' 6 | 7 | function Plane(props: PlaneProps): JSX.Element { 8 | const [ref] = usePlane(() => ({ type: 'Static', ...props }), useRef(null)) 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | type OurCompoundBodyProps = Pick & { 24 | isTrigger?: boolean 25 | mass?: number 26 | setPosition?: (position: Triplet) => void 27 | setRotation?: (rotation: Triplet) => void 28 | } 29 | 30 | function CompoundBody({ 31 | isTrigger, 32 | mass = 12, 33 | setPosition, 34 | setRotation, 35 | ...props 36 | }: OurCompoundBodyProps): JSX.Element { 37 | const boxSize: Triplet = [1, 1, 1] 38 | const sphereRadius = 0.65 39 | const [ref, api] = useCompoundBody( 40 | () => ({ 41 | isTrigger, 42 | mass, 43 | ...props, 44 | shapes: [ 45 | { args: boxSize, position: [0, 0, 0], rotation: [0, 0, 0], type: 'Box' }, 46 | { args: [sphereRadius], position: [1, 0, 0], rotation: [0, 0, 0], type: 'Sphere' }, 47 | ], 48 | }), 49 | useRef(null), 50 | ) 51 | 52 | useEffect(() => { 53 | if (setPosition) { 54 | return api.position.subscribe(setPosition) 55 | } 56 | }, [api, setPosition]) 57 | 58 | useEffect(() => { 59 | if (setRotation) { 60 | return api.rotation.subscribe(setRotation) 61 | } 62 | }, [api, setRotation]) 63 | 64 | return ( 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ) 76 | } 77 | 78 | export default function (): JSX.Element { 79 | const [ready, set] = useState(false) 80 | useEffect(() => { 81 | const timeout = setTimeout(() => set(true), 2000) 82 | return () => clearTimeout(timeout) 83 | }, []) 84 | 85 | const [copy, setCopy] = useState(false) 86 | useEffect(() => { 87 | const timeout = setTimeout(() => setCopy(true), 1000) 88 | return () => clearTimeout(timeout) 89 | }, []) 90 | 91 | const position = useRef([0, 0, 0]) 92 | const setPosition = ([x, y, z]: Triplet) => { 93 | position.current = [x, y, z] 94 | } 95 | 96 | const rotation = useRef([0, 0, 0]) 97 | const setRotation = ([x, y, z]: Triplet) => { 98 | rotation.current = [x, y, z] 99 | } 100 | 101 | return ( 102 | 103 | 104 | 105 | 114 | 115 | 116 | 117 | 118 | 124 | {ready && } 125 | {copy && ( 126 | 127 | )} 128 | 129 | 130 | 131 | ) 132 | } 133 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-Constraints.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps, SphereProps, Triplet } from '@react-three/cannon' 2 | import { Physics, useBox, useSphere, useSpring } from '@react-three/cannon' 3 | import { Canvas, useFrame } from '@react-three/fiber' 4 | import { forwardRef, useEffect, useRef, useState } from 'react' 5 | import type { Mesh } from 'three' 6 | 7 | const Box = forwardRef((props, fwdRef) => { 8 | const args: Triplet = [1, 1, 1] 9 | const [ref] = useBox( 10 | () => ({ 11 | args, 12 | linearDamping: 0.7, 13 | mass: 1, 14 | ...props, 15 | }), 16 | fwdRef, 17 | ) 18 | return ( 19 | 20 | 21 | 22 | 23 | ) 24 | }) 25 | 26 | const Ball = forwardRef((props, fwdRef) => { 27 | const [ref, { position }] = useSphere(() => ({ args: [0.5], type: 'Kinematic', ...props }), fwdRef) 28 | useFrame(({ mouse: { x, y }, viewport: { height, width } }) => 29 | position.set((x * width) / 2, (y * height) / 2, 0), 30 | ) 31 | return ( 32 | 33 | 34 | 35 | 36 | ) 37 | }) 38 | 39 | const BoxAndBall = () => { 40 | const [box, ball, api] = useSpring(useRef(null), useRef(null), { 41 | damping: 1, 42 | restLength: 2, 43 | stiffness: 100, 44 | }) 45 | const [isDown, setIsDown] = useState(false) 46 | 47 | useEffect(() => api.setRestLength(isDown ? 0 : 2), [isDown]) 48 | 49 | return ( 50 | setIsDown(true)} onPointerUp={() => setIsDown(false)}> 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | const style = { 58 | color: 'white', 59 | fontSize: '1.2em', 60 | left: 50, 61 | position: 'absolute', 62 | top: 20, 63 | } as const 64 | 65 | export default () => { 66 | return ( 67 | <> 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 |
* click to tighten constraint
76 |
77 | 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-ConvexPolyhedron.tsx: -------------------------------------------------------------------------------- 1 | import type { ConvexPolyhedronProps, PlaneProps, Triplet } from '@react-three/cannon' 2 | import { Physics, useConvexPolyhedron, usePlane } from '@react-three/cannon' 3 | import { useGLTF } from '@react-three/drei' 4 | import { Canvas } from '@react-three/fiber' 5 | import { Suspense, useMemo, useRef, useState } from 'react' 6 | import type { BufferGeometry, Mesh } from 'three' 7 | import { BoxGeometry, ConeGeometry } from 'three' 8 | import { Geometry } from 'three-stdlib' 9 | import type { GLTF } from 'three-stdlib/loaders/GLTFLoader' 10 | 11 | function toConvexProps(bufferGeometry: BufferGeometry): [vertices: Triplet[], faces: Triplet[]] { 12 | const geo = new Geometry().fromBufferGeometry(bufferGeometry) 13 | geo.mergeVertices() 14 | const vertices: Triplet[] = geo.vertices.map((v) => [v.x, v.y, v.z]) 15 | const faces: Triplet[] = geo.faces.map((f) => [f.a, f.b, f.c]) 16 | return [vertices, faces] 17 | } 18 | 19 | type DiamondGLTF = GLTF & { 20 | materials: {} 21 | nodes: { Cylinder: Mesh } 22 | } 23 | 24 | function Diamond({ position, rotation }: ConvexPolyhedronProps) { 25 | const { 26 | nodes: { 27 | Cylinder: { geometry }, 28 | }, 29 | } = useGLTF('/diamond.glb') as DiamondGLTF 30 | const args = useMemo(() => toConvexProps(geometry), [geometry]) 31 | const [ref] = useConvexPolyhedron(() => ({ args, mass: 100, position, rotation }), useRef(null)) 32 | 33 | return ( 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | type ConeProps = Pick & { 41 | sides: number 42 | } 43 | // A cone is a convex shape by definition... 44 | function Cone({ position, rotation, sides }: ConeProps) { 45 | const geometry = new ConeGeometry(0.7, 0.7, sides, 1) 46 | const args = useMemo(() => toConvexProps(geometry), [geometry]) 47 | const [ref] = useConvexPolyhedron(() => ({ args, mass: 100, position, rotation }), useRef(null)) 48 | 49 | return ( 50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | type CubeProps = Pick & { 58 | size: number 59 | } 60 | // ...And so is a cube! 61 | function Cube({ position, rotation, size }: CubeProps) { 62 | // note, this is wildly inefficient vs useBox 63 | const geometry = new BoxGeometry(size, size, size) 64 | const args = useMemo(() => toConvexProps(geometry), [geometry]) 65 | const [ref] = useConvexPolyhedron(() => ({ args, mass: 100, position, rotation }), useRef(null)) 66 | return ( 67 | 68 | 69 | 70 | 71 | ) 72 | } 73 | 74 | function Plane(props: PlaneProps) { 75 | const [ref] = usePlane(() => ({ type: 'Static', ...props }), useRef(null)) 76 | return ( 77 | 78 | 79 | 80 | 81 | ) 82 | } 83 | 84 | const style = { 85 | color: 'white', 86 | fontSize: '1.2em', 87 | left: 50, 88 | position: 'absolute', 89 | top: 20, 90 | } as const 91 | 92 | function ConvexPolyhedron() { 93 | const [invertGravity, setInvertGravity] = useState(false) 94 | const toggleInvertGravity = () => setInvertGravity(!invertGravity) 95 | 96 | return ( 97 | <> 98 | 99 | 100 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
124 |
* click to invert gravity
125 |
126 | 127 | ) 128 | } 129 | 130 | export default ConvexPolyhedron 131 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-CubeHeap.tsx: -------------------------------------------------------------------------------- 1 | import type { PlaneProps, Triplet } from '@react-three/cannon' 2 | import { Physics, useBox, usePlane, useSphere } from '@react-three/cannon' 3 | import { Canvas, useFrame } from '@react-three/fiber' 4 | import { useMemo, useRef, useState } from 'react' 5 | import type { InstancedMesh, Mesh } from 'three' 6 | import { Color } from 'three' 7 | 8 | import niceColors from '../colors' 9 | 10 | function Plane(props: PlaneProps) { 11 | const [ref] = usePlane(() => ({ ...props }), useRef(null)) 12 | return ( 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | type InstancedGeometryProps = { 21 | colors: Float32Array 22 | number: number 23 | size: number 24 | } 25 | 26 | const Spheres = ({ colors, number, size }: InstancedGeometryProps) => { 27 | const [ref, { at }] = useSphere( 28 | () => ({ 29 | args: [size], 30 | mass: 1, 31 | position: [Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5], 32 | }), 33 | useRef(null), 34 | ) 35 | useFrame(() => at(Math.floor(Math.random() * number)).position.set(0, Math.random() * 2, 0)) 36 | return ( 37 | 38 | 39 | 40 | 41 | 42 | 43 | ) 44 | } 45 | 46 | const Boxes = ({ colors, number, size }: InstancedGeometryProps) => { 47 | const args: Triplet = [size, size, size] 48 | const [ref, { at }] = useBox( 49 | () => ({ 50 | args, 51 | mass: 1, 52 | position: [Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5], 53 | }), 54 | useRef(null), 55 | ) 56 | useFrame(() => at(Math.floor(Math.random() * number)).position.set(0, Math.random() * 2, 0)) 57 | return ( 58 | 59 | 60 | 61 | 62 | 63 | 64 | ) 65 | } 66 | 67 | const instancedGeometry = { 68 | box: Boxes, 69 | sphere: Spheres, 70 | } 71 | 72 | export default () => { 73 | const [geometry, setGeometry] = useState<'box' | 'sphere'>('box') 74 | const [number] = useState(200) 75 | const [size] = useState(0.1) 76 | 77 | const colors = useMemo(() => { 78 | const array = new Float32Array(number * 3) 79 | const color = new Color() 80 | for (let i = 0; i < number; i++) 81 | color 82 | .set(niceColors[Math.floor(Math.random() * 5)]) 83 | .convertSRGBToLinear() 84 | .toArray(array, i * 3) 85 | return array 86 | }, [number]) 87 | 88 | const InstancedGeometry = instancedGeometry[geometry] 89 | 90 | return ( 91 | (scene.background = new Color('lightblue'))} 94 | onPointerMissed={() => setGeometry((geometry) => (geometry === 'box' ? 'sphere' : 'box'))} 95 | shadows 96 | > 97 | 98 | 106 | 107 | 108 | 109 | 110 | 111 | ) 112 | } 113 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-Friction.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps, PlaneProps } from '@react-three/cannon' 2 | import { Physics, useBox, useContactMaterial, usePlane } from '@react-three/cannon' 3 | import { OrbitControls } from '@react-three/drei' 4 | import { Canvas } from '@react-three/fiber' 5 | import { useRef, useState } from 'react' 6 | import type { Mesh } from 'three' 7 | 8 | const materialColors = { 9 | bouncy: 'yellow', 10 | box: '#BB8E51', 11 | ground: '#9b7653', 12 | rubber: 'darkgrey', 13 | slippery: 'royalblue', 14 | } as const 15 | 16 | const materialNames = ['bouncy', 'box', 'ground', 'rubber', 'slippery'] as const 17 | 18 | const style = { 19 | color: 'white', 20 | fontSize: '1.2em', 21 | left: 50, 22 | position: 'absolute', 23 | top: 20, 24 | } as const 25 | 26 | const bouncyMaterial = { 27 | name: 'bouncy', 28 | /* 29 | Restitution for this material. 30 | If non-negative, it will be used instead of the restitution given by ContactMaterials. 31 | If there's no matching ContactMaterial, the value from .defaultContactMaterial in the World will be used. 32 | */ 33 | restitution: 1.1, 34 | } 35 | 36 | const boxMaterial = 'box' 37 | 38 | const groundMaterial = 'ground' 39 | 40 | /* 41 | Setting the friction on both materials prevents overriding the friction given by ContactMaterials. 42 | Since we want rubber to not be slippery we do not set this here and instead use a ContactMaterial. 43 | See https://github.com/pmndrs/cannon-es/blob/e9f1bccd8caa250cc6e6cdaf85389058e1c9238e/src/world/World.ts#L661-L673 44 | */ 45 | const rubberMaterial = 'rubber' 46 | 47 | const slipperyMaterial = { 48 | /* 49 | Friction for this material. 50 | If non-negative, it will be used instead of the friction given by ContactMaterials. 51 | If there's no matching ContactMaterial, the value from .defaultContactMaterial in the World will be used. 52 | */ 53 | friction: 0, 54 | name: 'slippery', 55 | } 56 | 57 | const Box = ({ args, color = 'white', ...props }: BoxProps & { color?: string }) => { 58 | const [ref] = useBox( 59 | () => ({ 60 | args, 61 | mass: 10, 62 | ...props, 63 | }), 64 | useRef(null), 65 | ) 66 | return ( 67 | 68 | 69 | 70 | 71 | ) 72 | } 73 | 74 | const Plane = (props: PlaneProps) => { 75 | const [ref] = usePlane(() => ({ ...props }), useRef(null)) 76 | return ( 77 | 78 | 79 | 80 | 81 | ) 82 | } 83 | 84 | const useContactMaterials = (rubberSlips: boolean) => { 85 | useContactMaterial(groundMaterial, groundMaterial, { 86 | contactEquationRelaxation: 3, 87 | contactEquationStiffness: 1e8, 88 | friction: 0.4, 89 | frictionEquationStiffness: 1e8, 90 | restitution: 0.3, 91 | }) 92 | 93 | useContactMaterial(boxMaterial, groundMaterial, { 94 | contactEquationRelaxation: 3, 95 | contactEquationStiffness: 1e8, 96 | friction: 0.4, 97 | frictionEquationStiffness: 1e8, 98 | restitution: 0.3, 99 | }) 100 | useContactMaterial(boxMaterial, slipperyMaterial, { 101 | friction: 0, 102 | restitution: 0.3, 103 | }) 104 | 105 | useContactMaterial(groundMaterial, slipperyMaterial, { 106 | friction: 0, 107 | restitution: 0.3, 108 | }) 109 | useContactMaterial(slipperyMaterial, slipperyMaterial, { 110 | friction: 0.1, 111 | restitution: 0.3, 112 | }) 113 | 114 | useContactMaterial(bouncyMaterial, slipperyMaterial, { 115 | friction: 0, 116 | restitution: 0.5, 117 | }) 118 | useContactMaterial(bouncyMaterial, groundMaterial, { 119 | restitution: 0.9, 120 | }) 121 | useContactMaterial(bouncyMaterial, bouncyMaterial, { 122 | restitution: 10.0, // This does nothing because bouncyMaterial already has a restitution 123 | }) 124 | 125 | useContactMaterial( 126 | rubberMaterial, 127 | slipperyMaterial, 128 | { 129 | friction: rubberSlips ? 0 : 1, 130 | restitution: 0.3, 131 | }, 132 | [rubberSlips], 133 | ) 134 | 135 | useContactMaterial(rubberMaterial, bouncyMaterial, { 136 | restitution: 0.5, 137 | }) 138 | } 139 | 140 | function PhysicsContent() { 141 | const [rubberSlips, setRubberSlips] = useState(false) 142 | const toggleRubberSlips = () => setRubberSlips(!rubberSlips) 143 | 144 | useContactMaterials(rubberSlips) 145 | 146 | return ( 147 | 148 | 149 | 150 | 151 | 152 | 159 | 166 | 173 | 174 | 175 | ) 176 | } 177 | 178 | export default () => ( 179 | <> 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 |
189 |
190 |         * Gravity is aiming to the right
191 |         
192 | Click to toggle rubber slipping on slipper material 193 |
194 | Materials: 195 | {materialNames.map((name, key) => ( 196 |
197 | - {name} 198 |
199 | ))} 200 |
201 |
202 | 203 | ) 204 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-Heightfield.tsx: -------------------------------------------------------------------------------- 1 | import type { Triplet } from '@react-three/cannon' 2 | import { Physics, useHeightfield, useSphere } from '@react-three/cannon' 3 | import { OrbitControls } from '@react-three/drei' 4 | import { Canvas, useFrame, useThree } from '@react-three/fiber' 5 | import { useEffect, useLayoutEffect, useMemo, useRef } from 'react' 6 | import type { BufferGeometry, InstancedMesh, Mesh, PerspectiveCamera } from 'three' 7 | import { Color, Float32BufferAttribute } from 'three' 8 | 9 | import niceColors from '../colors' 10 | 11 | type GenerateHeightmapArgs = { 12 | height: number 13 | number: number 14 | scale: number 15 | width: number 16 | } 17 | 18 | /* Generates a 2D array using Worley noise. */ 19 | function generateHeightmap({ width, height, number, scale }: GenerateHeightmapArgs) { 20 | const data = [] 21 | 22 | const seedPoints = [] 23 | for (let i = 0; i < number; i++) { 24 | seedPoints.push([Math.random(), Math.random()]) 25 | } 26 | 27 | let max = 0 28 | for (let i = 0; i < width; i++) { 29 | const row = [] 30 | for (let j = 0; j < height; j++) { 31 | let min = Infinity 32 | seedPoints.forEach((p) => { 33 | const distance2 = (p[0] - i / width) ** 2 + (p[1] - j / height) ** 2 34 | if (distance2 < min) { 35 | min = distance2 36 | } 37 | }) 38 | const d = Math.sqrt(min) 39 | if (d > max) { 40 | max = d 41 | } 42 | row.push(d) 43 | } 44 | data.push(row) 45 | } 46 | 47 | /* Normalize and scale. */ 48 | for (let i = 0; i < width; i++) { 49 | for (let j = 0; j < height; j++) { 50 | data[i][j] *= scale / max 51 | } 52 | } 53 | return data 54 | } 55 | 56 | function HeightmapGeometry({ 57 | elementSize, 58 | heights, 59 | }: { 60 | elementSize: number 61 | heights: number[][] 62 | }): JSX.Element { 63 | const ref = useRef(null) 64 | 65 | useEffect(() => { 66 | if (!ref.current) return 67 | const dx = elementSize 68 | const dy = elementSize 69 | 70 | /* Create the vertex data from the heights. */ 71 | const vertices = heights.flatMap((row, i) => row.flatMap((z, j) => [i * dx, j * dy, z])) 72 | 73 | /* Create the faces. */ 74 | const indices = [] 75 | for (let i = 0; i < heights.length - 1; i++) { 76 | for (let j = 0; j < heights[i].length - 1; j++) { 77 | const stride = heights[i].length 78 | const index = i * stride + j 79 | indices.push(index + 1, index + stride, index + stride + 1) 80 | indices.push(index + stride, index + 1, index) 81 | } 82 | } 83 | 84 | ref.current.setIndex(indices) 85 | ref.current.setAttribute('position', new Float32BufferAttribute(vertices, 3)) 86 | ref.current.computeVertexNormals() 87 | ref.current.computeBoundingBox() 88 | ref.current.computeBoundingSphere() 89 | }, [heights]) 90 | 91 | return 92 | } 93 | 94 | function Heightfield({ 95 | elementSize, 96 | heights, 97 | position, 98 | rotation, 99 | }: { 100 | elementSize: number 101 | heights: number[][] 102 | position: Triplet 103 | rotation: Triplet 104 | }): JSX.Element { 105 | const [ref] = useHeightfield( 106 | () => ({ 107 | args: [ 108 | heights, 109 | { 110 | elementSize, 111 | }, 112 | ], 113 | position, 114 | rotation, 115 | }), 116 | useRef(null), 117 | ) 118 | 119 | return ( 120 | 121 | 122 | 123 | 124 | ) 125 | } 126 | 127 | function Spheres({ columns, rows, spread }: { columns: number; rows: number; spread: number }): JSX.Element { 128 | const number = rows * columns 129 | const [ref] = useSphere( 130 | (index) => ({ 131 | args: [0.2], 132 | mass: 1, 133 | position: [ 134 | ((index % columns) - (columns - 1) / 2) * spread, 135 | 2.0, 136 | (Math.floor(index / columns) - (rows - 1) / 2) * spread, 137 | ], 138 | }), 139 | useRef(null), 140 | ) 141 | const colors = useMemo(() => { 142 | const array = new Float32Array(number * 3) 143 | const color = new Color() 144 | for (let i = 0; i < number; i++) 145 | color 146 | .set(niceColors[Math.floor(Math.random() * 5)]) 147 | .convertSRGBToLinear() 148 | .toArray(array, i * 3) 149 | return array 150 | }, [number]) 151 | 152 | return ( 153 | 154 | 155 | 156 | 157 | 158 | 159 | ) 160 | } 161 | 162 | function Camera(): JSX.Element { 163 | const cameraRef = useRef(null) 164 | const { gl, camera } = useThree() 165 | const set = useThree((state) => state.set) 166 | const size = useThree((state) => state.size) 167 | 168 | useLayoutEffect(() => { 169 | if (!cameraRef.current) return 170 | cameraRef.current.aspect = size.width / size.height 171 | cameraRef.current.updateProjectionMatrix() 172 | }, [size]) 173 | 174 | useLayoutEffect(() => { 175 | const camera = cameraRef.current 176 | if (!camera) return 177 | set(() => ({ camera })) 178 | }, []) 179 | 180 | useFrame(() => { 181 | if (!cameraRef.current) return 182 | cameraRef.current.updateMatrixWorld() 183 | }) 184 | 185 | return ( 186 | <> 187 | 188 | 195 | 196 | ) 197 | } 198 | 199 | export default ({ scale = 10 }) => ( 200 | 201 | 202 | 203 | 204 | 205 | 206 | 217 | 218 | 219 | 220 | ) 221 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-HingeMotor.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps, HingeConstraintOpts, PlaneProps, Triplet } from '@react-three/cannon' 2 | import { Physics, useBox, useHingeConstraint, useLockConstraint, usePlane } from '@react-three/cannon' 3 | import { OrbitControls, PerspectiveCamera } from '@react-three/drei' 4 | import type { MeshStandardMaterialProps, PlaneGeometryProps } from '@react-three/fiber' 5 | import { Canvas, useFrame } from '@react-three/fiber' 6 | import type { PropsWithChildren, RefObject } from 'react' 7 | import { 8 | createContext, 9 | createRef, 10 | forwardRef, 11 | Suspense, 12 | useContext, 13 | useEffect, 14 | useRef, 15 | useState, 16 | } from 'react' 17 | import type { Group, Mesh, Object3D, PerspectiveCamera as Cam } from 'three' 18 | import { Vector3 } from 'three' 19 | 20 | function normalizeSize([px = 0, py = 0, pz = 0]): (scale: Triplet) => Triplet { 21 | return ([ox = 1, oy = 1, oz = 1]) => [px * ox, py * oy, pz * oz] 22 | } 23 | 24 | const GROUP_GROUND = 2 ** 0 25 | const GROUP_BODY = 2 ** 1 26 | 27 | type OurPlaneProps = Pick & Pick 28 | 29 | function Plane({ args, ...props }: OurPlaneProps) { 30 | const [ref] = usePlane( 31 | () => ({ collisionFilterGroup: GROUP_GROUND, type: 'Static', ...props }), 32 | useRef(null), 33 | ) 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ) 46 | } 47 | 48 | const ref = createRef() 49 | const context = createContext<[bodyRef: RefObject, props: BoxShapeProps]>([ref, {}]) 50 | 51 | type ConstraintPartProps = { 52 | config?: HingeConstraintOpts 53 | enableMotor?: boolean 54 | motorSpeed?: number 55 | parentPivot?: Triplet 56 | pivot?: Triplet 57 | } & BoxProps & 58 | BoxShapeProps 59 | 60 | const ConstraintPart = forwardRef>( 61 | ( 62 | { 63 | config = {}, 64 | enableMotor, 65 | motorSpeed = 7, 66 | color, 67 | children, 68 | pivot = [0, 0, 0], 69 | parentPivot = [0, 0, 0], 70 | ...props 71 | }, 72 | ref, 73 | ) => { 74 | const parent = useContext(context) 75 | 76 | const normParentPivot = parent && parent[1].args ? normalizeSize(parent[1].args) : () => undefined 77 | const normPivot = props.args ? normalizeSize(props.args) : () => undefined 78 | 79 | const [bodyRef] = useBox( 80 | () => ({ 81 | collisionFilterGroup: GROUP_BODY, 82 | collisionFilterMask: GROUP_GROUND, 83 | linearDamping: 0.4, 84 | mass: 1, 85 | ...props, 86 | }), 87 | ref, 88 | ) 89 | 90 | const [, , hingeApi] = useHingeConstraint(bodyRef, parent[0], { 91 | axisA: [0, 0, 1], 92 | axisB: [0, 0, 1], 93 | collideConnected: false, 94 | pivotA: normPivot(pivot), 95 | pivotB: normParentPivot(parentPivot), 96 | ...config, 97 | }) 98 | 99 | useEffect(() => { 100 | if (enableMotor) { 101 | hingeApi.enableMotor() 102 | } else { 103 | hingeApi.disableMotor() 104 | } 105 | }, [enableMotor]) 106 | 107 | useEffect(() => { 108 | hingeApi.setMotorSpeed(motorSpeed) 109 | }, [motorSpeed]) 110 | 111 | return ( 112 | 113 | 114 | {children} 115 | 116 | ) 117 | }, 118 | ) 119 | 120 | type BoxShapeProps = Pick & 121 | Pick 122 | const BoxShape = forwardRef>( 123 | ({ args = [1, 1, 1], children, color = 'white', opacity = 1, transparent = false, ...props }, ref) => ( 124 | 125 | 126 | 127 | {children} 128 | 129 | ), 130 | ) 131 | 132 | const Robot = forwardRef((_, legsLeftRef) => { 133 | const [motorSpeed, setMotorSpeed] = useState(7) 134 | 135 | const legsRightRef = useRef(null) 136 | 137 | useLockConstraint(legsRightRef, legsLeftRef, {}) 138 | 139 | return ( 140 | setMotorSpeed(2)} onPointerUp={() => setMotorSpeed(7)}> 141 | 142 | 143 | 144 | ) 145 | }) 146 | 147 | type LegsProps = { 148 | bodyDepth?: number 149 | delay?: number 150 | } & Pick 151 | 152 | const Legs = forwardRef(({ bodyDepth = 0, delay = 0, motorSpeed = 7 }, bodyRef) => { 153 | const horizontalRef = useRef(null) 154 | const frontLegRef = useRef(null) 155 | const frontUpperLegRef = useRef(null) 156 | const backLegRef = useRef(null) 157 | const partDepth = 0.3 158 | const bodyWidth = 10 159 | const bodyHeight = 2 160 | const legLength = 6 161 | const size3 = normalizeSize([1, 3, partDepth]) 162 | const size5 = normalizeSize([1, 5, partDepth]) 163 | const size10 = normalizeSize([1, 10, partDepth]) 164 | 165 | // Hinge constraints for triangulations 166 | useHingeConstraint(frontUpperLegRef, frontLegRef, { 167 | axisA: [0, 0, 1], 168 | axisB: [0, 0, 1], 169 | collideConnected: false, 170 | pivotA: size3([0, 0.5, 0.5]), 171 | pivotB: size5([0, 0.5, -0.5]), 172 | }) 173 | 174 | useHingeConstraint(backLegRef, horizontalRef, { 175 | axisA: [0, 0, 1], 176 | axisB: [0, 0, 1], 177 | collideConnected: false, 178 | pivotA: size5([0, 0.5, 0.5]), 179 | pivotB: size10([0, 0.5, -0.5]), 180 | }) 181 | 182 | const [isWalking, setIsWalking] = useState(false) 183 | 184 | useEffect(() => { 185 | const t = setTimeout(() => setIsWalking(true), delay) 186 | 187 | return () => clearTimeout(t) 188 | }, []) 189 | 190 | return ( 191 | 192 | {/* Body */} 193 | 202 | {/* Upper front leg */} 203 | 212 | {/* Crank */} 213 | 222 | {/* Front leg */} 223 | 232 | {/* Horizontal bar */} 233 | 242 | 243 | 244 | 245 | {/* Back leg */} 246 | 255 | 256 | 257 | ) 258 | }) 259 | 260 | function Obstacles() { 261 | return ( 262 | <> 263 | 272 | 281 | 290 | 291 | ) 292 | } 293 | 294 | const v = new Vector3() 295 | 296 | function Scene() { 297 | const cameraRef = useRef(null) 298 | const robotRef = useRef(null) 299 | 300 | useFrame(() => { 301 | if (!cameraRef.current || !robotRef.current) return 302 | robotRef.current.getWorldPosition(v) 303 | cameraRef.current.lookAt(v) 304 | }) 305 | 306 | return ( 307 | 308 | 309 | 310 | 311 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | ) 333 | } 334 | 335 | const style = { 336 | color: 'white', 337 | fontSize: '1.2em', 338 | left: 50, 339 | position: 'absolute', 340 | top: 20, 341 | } as const 342 | 343 | export default () => { 344 | return ( 345 | <> 346 | 347 | 348 | 349 | 350 | 351 |
352 |
* click to reduce speed
353 |
354 | 355 | ) 356 | } 357 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-KinematicCube.tsx: -------------------------------------------------------------------------------- 1 | import type { PlaneProps, Triplet } from '@react-three/cannon' 2 | import { Physics, useBox, usePlane, useSphere } from '@react-three/cannon' 3 | import type { MeshPhongMaterialProps } from '@react-three/fiber' 4 | import { Canvas, useFrame } from '@react-three/fiber' 5 | import { useMemo, useRef } from 'react' 6 | import type { InstancedMesh, Mesh } from 'three' 7 | import { Color } from 'three' 8 | 9 | import niceColors from '../colors' 10 | 11 | type OurPlaneProps = Pick & Pick 12 | 13 | function Plane({ color, ...props }: OurPlaneProps) { 14 | const [ref] = usePlane(() => ({ ...props }), useRef(null)) 15 | return ( 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | function Box() { 24 | const boxSize: Triplet = [4, 4, 4] 25 | const [ref, api] = useBox(() => ({ args: boxSize, mass: 1, type: 'Kinematic' }), useRef(null)) 26 | useFrame((state) => { 27 | const t = state.clock.getElapsedTime() 28 | api.position.set(Math.sin(t * 2) * 5, Math.cos(t * 2) * 5, 3) 29 | api.rotation.set(Math.sin(t * 6), Math.cos(t * 6), 0) 30 | }) 31 | return ( 32 | 33 | 34 | 35 | 36 | ) 37 | } 38 | 39 | function InstancedSpheres({ number = 100 }) { 40 | const [ref] = useSphere( 41 | (index) => ({ 42 | args: [1], 43 | mass: 1, 44 | position: [Math.random() - 0.5, Math.random() - 0.5, index * 2], 45 | }), 46 | useRef(null), 47 | ) 48 | const colors = useMemo(() => { 49 | const array = new Float32Array(number * 3) 50 | const color = new Color() 51 | for (let i = 0; i < number; i++) 52 | color 53 | .set(niceColors[Math.floor(Math.random() * 5)]) 54 | .convertSRGBToLinear() 55 | .toArray(array, i * 3) 56 | return array 57 | }, [number]) 58 | 59 | return ( 60 | 61 | 62 | 63 | 64 | 65 | 66 | ) 67 | } 68 | 69 | export default () => ( 70 | 71 | 72 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ) 94 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-Paused.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps, PlaneProps } from '@react-three/cannon' 2 | import { Debug, Physics, useBox, usePlane } from '@react-three/cannon' 3 | import { Box, OrbitControls, Plane } from '@react-three/drei' 4 | import type { MeshStandardMaterialProps } from '@react-three/fiber' 5 | import { Canvas } from '@react-three/fiber' 6 | import { useRef, useState } from 'react' 7 | import type { Mesh } from 'three' 8 | 9 | type GroundProps = Pick & PlaneProps 10 | 11 | function Ground({ color, ...props }: GroundProps): JSX.Element { 12 | const [ref] = usePlane(() => ({ ...props }), useRef(null)) 13 | 14 | return ( 15 | 16 | 17 | 18 | ) 19 | } 20 | 21 | function Crate(props: BoxProps): JSX.Element { 22 | const [ref, api] = useBox(() => ({ args: [2, 1, 1], mass: 1, ...props }), useRef(null)) 23 | 24 | return ( 25 | { 28 | api.applyImpulse([0, 5, 2], [0, -1, 0]) 29 | }} 30 | ref={ref} 31 | > 32 | 33 | 34 | ) 35 | } 36 | 37 | function Scene({ isPaused = false }): JSX.Element { 38 | return ( 39 | <> 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ) 63 | } 64 | 65 | export default function Paused() { 66 | const [isPaused, togglePaused] = useState(false) 67 | return ( 68 |
69 |
70 | 76 |
77 | 78 | 79 | 80 | 81 |
82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-SphereDebug.tsx: -------------------------------------------------------------------------------- 1 | import { Debug, Physics, usePlane, useSphere } from '@react-three/cannon' 2 | import { Canvas } from '@react-three/fiber' 3 | import { useRef, useState } from 'react' 4 | import type { Mesh } from 'three' 5 | 6 | function ScalableBall() { 7 | const [ref, api] = useSphere( 8 | () => ({ 9 | args: [1], 10 | mass: 1, 11 | position: [0, 5, 0], 12 | }), 13 | useRef(null), 14 | ) 15 | const [sleeping, setSleeping] = useState(false) 16 | 17 | // Very quick demo to test forced sleep states. Catch ball mid-air to stop it. 18 | const toggle = () => { 19 | if (sleeping) { 20 | setSleeping(false) 21 | api.wakeUp() 22 | } else { 23 | setSleeping(true) 24 | api.sleep() 25 | } 26 | } 27 | 28 | return ( 29 | 30 | 31 | 32 | 33 | ) 34 | } 35 | 36 | function Plane() { 37 | const [ref] = usePlane(() => ({ rotation: [-Math.PI / 2, 0, 0], type: 'Static' }), useRef(null)) 38 | return ( 39 | 40 | 41 | 42 | 43 | ) 44 | } 45 | 46 | export default function App() { 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ) 60 | } 61 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-Triggers.tsx: -------------------------------------------------------------------------------- 1 | import type { BoxProps, PlaneProps } from '@react-three/cannon' 2 | import { Physics, useBox, usePlane, useSphere } from '@react-three/cannon' 3 | import { OrbitControls } from '@react-three/drei' 4 | import { Canvas } from '@react-three/fiber' 5 | import { useRef, useState } from 'react' 6 | import type { Mesh } from 'three' 7 | 8 | function BoxTrigger({ args, onCollide, position }: BoxProps) { 9 | const [ref] = useBox(() => ({ args, isTrigger: true, onCollide, position }), useRef(null)) 10 | return ( 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | function Ball() { 19 | const [ref] = useSphere( 20 | () => ({ 21 | args: [1], 22 | mass: 1, 23 | position: [0, 10, 0], 24 | }), 25 | useRef(null), 26 | ) 27 | return ( 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | function Plane(props: PlaneProps) { 36 | const [ref] = usePlane(() => ({ type: 'Static', ...props }), useRef(null)) 37 | return ( 38 | 39 | 40 | 41 | 42 | ) 43 | } 44 | 45 | export default () => { 46 | const [bg, setbg] = useState('#171720') 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | { 58 | console.log('Collision event on BoxTrigger', e) 59 | setbg('#fe4365') 60 | }} 61 | position={[0, 5, 0]} 62 | /> 63 | 64 | 65 | 66 | 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/demo-Trimesh.tsx: -------------------------------------------------------------------------------- 1 | import type { SphereProps, TrimeshProps } from '@react-three/cannon' 2 | import { Physics, useSphere, useTrimesh } from '@react-three/cannon' 3 | import { OrbitControls, TorusKnot, useGLTF } from '@react-three/drei' 4 | import { Canvas, invalidate } from '@react-three/fiber' 5 | import { useEffect, useRef, useState } from 'react' 6 | import type { BufferGeometry, Mesh } from 'three' 7 | import type { GLTF } from 'three-stdlib/loaders/GLTFLoader' 8 | import { create } from 'zustand' 9 | 10 | type BowlGLTF = GLTF & { 11 | materials: {} 12 | nodes: { 13 | bowl: Mesh & { 14 | geometry: BufferGeometry & { index: ArrayLike } 15 | } 16 | } 17 | } 18 | 19 | type Store = { 20 | isPaused: boolean 21 | pause: () => void 22 | play: () => void 23 | } 24 | 25 | const useStore = create((set) => ({ 26 | isPaused: false, 27 | pause: () => set({ isPaused: true }), 28 | play: () => set({ isPaused: false }), 29 | })) 30 | 31 | function Controls() { 32 | const { isPaused, pause, play } = useStore() 33 | return ( 34 |
35 | Paused? {isPaused ? 'Yes' : 'No'}

36 | 37 | 38 |
39 | ) 40 | } 41 | 42 | const WeirdCheerio = ({ args = [0.1], position }: Pick) => { 43 | const [ref] = useSphere(() => ({ args, mass: 1, position }), useRef(null)) 44 | const [radius] = args 45 | return ( 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | const Bowl = ({ rotation }: Pick) => { 53 | const { 54 | nodes: { 55 | bowl: { geometry }, 56 | }, 57 | } = useGLTF('/bowl.glb') as BowlGLTF 58 | const { 59 | attributes: { 60 | position: { array: vertices }, 61 | }, 62 | index: { array: indices }, 63 | } = geometry 64 | 65 | const [hovered, setHover] = useState(false) 66 | const { isPaused } = useStore() 67 | 68 | const [ref] = useTrimesh( 69 | () => ({ 70 | args: [vertices, indices], 71 | mass: 0, 72 | rotation, 73 | }), 74 | useRef(null), 75 | ) 76 | 77 | useEffect(() => { 78 | if (!isPaused) invalidate() 79 | }, [isPaused]) 80 | 81 | return ( 82 | setHover(true)} 86 | onPointerOut={() => setHover(false)} 87 | > 88 | 89 | 90 | ) 91 | } 92 | 93 | const style = { 94 | color: 'white', 95 | fontSize: '1.2em', 96 | left: 50, 97 | position: 'absolute', 98 | top: 20, 99 | } as const 100 | 101 | export default () => ( 102 | <> 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
117 |
118 |         
119 |       
120 |
121 | 122 | ) 123 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/demos/index.ts: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react' 2 | 3 | const fileDemos = [ 4 | 'Chain', 5 | 'CompoundBody', 6 | 'Constraints', 7 | 'ConvexPolyhedron', 8 | 'CubeHeap', 9 | 'Friction', 10 | 'Heightfield', 11 | 'HingeMotor', 12 | 'KinematicCube', 13 | 'Paused', 14 | 'SphereDebug', 15 | 'Triggers', 16 | 'Trimesh', 17 | ] as const 18 | 19 | const folderDemos = ['MondayMorning', 'Pingpong', 'Raycast', 'RaycastVehicle'] as const 20 | 21 | export const demoList = [...folderDemos, ...fileDemos] as const 22 | 23 | export type Demo = typeof demoList[number] 24 | export const isDemo = (v: unknown): v is Demo => demoList.includes(v as Demo) 25 | 26 | type FolderDemo = typeof folderDemos[number] 27 | const isFolderDemo = (v: unknown): v is FolderDemo => folderDemos.includes(v as FolderDemo) 28 | 29 | export const demos = demoList.reduce((o, demo) => { 30 | o[demo] = { 31 | Component: lazy(() => import(isFolderDemo(demo) ? `./${demo}/index.tsx` : `./demo-${demo}.tsx`)), 32 | } 33 | return o 34 | }, {} as Record }>) 35 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | 3 | import App from './App' 4 | 5 | const root = document.getElementById('root') 6 | 7 | if (root) { 8 | createRoot(root).render() 9 | } 10 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { createGlobalStyle } from 'styled-components' 2 | 3 | export const PageStyle = styled.div` 4 | position: relative; 5 | width: 100%; 6 | height: 100vh; 7 | 8 | & > h1 { 9 | font-family: 'Roboto', sans-serif; 10 | font-weight: 900; 11 | font-size: 8em; 12 | margin: 0; 13 | color: white; 14 | line-height: 0.59em; 15 | letter-spacing: -2px; 16 | } 17 | 18 | @media only screen and (max-width: 1000px) { 19 | & > h1 { 20 | font-size: 5em; 21 | letter-spacing: -1px; 22 | } 23 | } 24 | 25 | & > a { 26 | margin: 0; 27 | color: white; 28 | text-decoration: none; 29 | } 30 | ` 31 | 32 | export const GlobalStyle = createGlobalStyle` 33 | * { 34 | box-sizing: border-box; 35 | } 36 | 37 | html, 38 | body, 39 | #root { 40 | width: 100%; 41 | height: 100%; 42 | margin: 0; 43 | padding: 0; 44 | -webkit-touch-callout: none; 45 | -webkit-user-select: none; 46 | -khtml-user-select: none; 47 | -moz-user-select: none; 48 | -ms-user-select: none; 49 | user-select: none; 50 | overflow: hidden; 51 | } 52 | 53 | #root { 54 | overflow: auto; 55 | } 56 | 57 | body { 58 | position: fixed; 59 | overflow: hidden; 60 | overscroll-behavior-y: none; 61 | font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif; 62 | color: black; 63 | background: #171720; 64 | } 65 | ` 66 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/src/use-toggled-control.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentType, PropsWithChildren } from 'react' 2 | import { useEffect, useMemo, useState } from 'react' 3 | 4 | export const useToggledComponent =

(ToggledComponent: ComponentType

, toggle: boolean) => 5 | useMemo(() => { 6 | return (props: PropsWithChildren

) => 7 | toggle ? : props.children ? <>{props.children} : null 8 | }, [ToggledComponent, toggle]) 9 | 10 | export const useToggledControl =

(ToggledComponent: ComponentType

, keycode: string) => { 11 | const [toggle, setToggle] = useState(false) 12 | 13 | useEffect(() => { 14 | const listener = ({ key }: KeyboardEvent) => { 15 | if (key === keycode) setToggle(!toggle) 16 | } 17 | addEventListener('keyup', listener) 18 | return () => removeEventListener('keyup', listener) 19 | }) 20 | 21 | return useToggledComponent(ToggledComponent, toggle) 22 | } 23 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "baseUrl": "./src", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "esModuleInterop": true, 9 | "incremental": true, 10 | "jsx": "react-jsx", 11 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "noImplicitThis": false, 15 | "outDir": "dist", 16 | "pretty": true, 17 | "removeComments": true, 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "strict": true, 21 | "target": "esnext", 22 | "types": ["vite/client"] 23 | }, 24 | "exclude": ["node_modules"], 25 | "include": ["./src", "vite.config.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /packages/react-three-cannon-examples/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | export default defineConfig({}) 4 | -------------------------------------------------------------------------------- /packages/react-three-cannon/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/react-three-cannon/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:react/recommended", "prettier"], 7 | "overrides": [ 8 | { 9 | "extends": "plugin:@typescript-eslint/recommended", 10 | "files": ["*.tsx", "*.ts"], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "project": ["tsconfig.json"] 14 | }, 15 | "plugins": ["@typescript-eslint", "typescript-enum"], 16 | "rules": { 17 | "@typescript-eslint/ban-types": ["error", { "extendDefaults": true, "types": { "{}": false } }], 18 | "@typescript-eslint/consistent-type-imports": "error", 19 | "@typescript-eslint/explicit-module-boundary-types": "off", 20 | "@typescript-eslint/member-ordering": [ 21 | "error", 22 | { 23 | "default": { 24 | "order": "alphabetically-case-insensitive" 25 | } 26 | } 27 | ], 28 | "@typescript-eslint/no-namespace": ["error", { "allowDeclarations": true }], 29 | "@typescript-eslint/no-non-null-assertion": "error", 30 | "@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }], 31 | "@typescript-eslint/quotes": [ 32 | "error", 33 | "single", 34 | { 35 | "allowTemplateLiterals": true, 36 | "avoidEscape": true 37 | } 38 | ], 39 | "@typescript-eslint/semi": ["error", "never"], 40 | "typescript-enum/no-enum": "error" 41 | } 42 | } 43 | ], 44 | "parserOptions": { 45 | "ecmaFeatures": { 46 | "jsx": true 47 | }, 48 | "ecmaVersion": 12, 49 | "sourceType": "module" 50 | }, 51 | "plugins": ["es", "react", "simple-import-sort"], 52 | "rules": { 53 | "eol-last": ["error", "always"], 54 | "es/no-logical-assignment-operators": "error", 55 | "es/no-nullish-coalescing-operators": "error", 56 | "no-debugger": "error", 57 | "no-unused-vars": ["error", { "ignoreRestSiblings": true }], 58 | "react/no-children-prop": 0, 59 | "react/display-name": 0, 60 | "react/prop-types": 0, 61 | "react/react-in-jsx-scope": 0, 62 | "semi": ["error", "never"], 63 | "simple-import-sort/exports": "error", 64 | "simple-import-sort/imports": "error", 65 | "sort-keys": "error" 66 | }, 67 | "settings": { 68 | "react": { 69 | "version": "detect" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/react-three-cannon/.npmignore: -------------------------------------------------------------------------------- 1 | !dist/ 2 | 3 | node_modules/ 4 | examples/ 5 | Thumbs.db 6 | ehthumbs.db 7 | Desktop.ini 8 | \$RECYCLE.BIN/ 9 | .DS_Store 10 | .vscode 11 | .docz/ 12 | .idea 13 | .rpt2_cache/ 14 | .eslintcache 15 | .eslintignore 16 | .eslintrc.json 17 | .github/ 18 | .husky/ 19 | .prettierignore 20 | .prettierrc.json 21 | rollup.config.js 22 | tsconfig.json 23 | tsconfig.tsbuildinfo 24 | -------------------------------------------------------------------------------- /packages/react-three-cannon/.prettierignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | node_modules/* 3 | package.json 4 | -------------------------------------------------------------------------------- /packages/react-three-cannon/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 110, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": true 9 | } 10 | -------------------------------------------------------------------------------- /packages/react-three-cannon/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @react-three/cannon Changelog 2 | 3 | ## 6.6.0 4 | 5 | ### Minor Changes 6 | 7 | - 22d49ef: chore: update @types/three dev dependency 8 | 9 | ### Patch Changes 10 | 11 | - Updated dependencies [800a687] 12 | - Updated dependencies [22d49ef] 13 | - @pmndrs/cannon-worker-api@2.4.0 14 | 15 | ## v6.5.2 - 2023-01-05 16 | 17 | - [`README.md`] Update shields badge to point to match their new route (@bjornstar) 18 | 19 | ## v6.5.1 - 2022-11-11 20 | 21 | - Make sure to include dist in npm package (@bjornstar) 22 | 23 | ## v6.5.0 - 2022-11-03 24 | 25 | - Update `@pmndrs/cannon-worker-api` to v2.3.0 (@bjornstar) 26 | 27 | ## v6.4.0 - 2022-08-18 28 | 29 | - Add support for `frictionGravity` on WorldProps (@chnicoloso) 30 | 31 | ## v6.3.0 - 2022-04-18 32 | 33 | - DebugProvider explicitly lists children as a prop (@bjornstar) 34 | - Prefer PropsWithChildren over FC (@bjornstar) 35 | - Prefer function declarations over const (@bjornstar) 36 | - [`hooks`] All hooks are now generic, they accept any Object3D and return refs of whatever type was passed in (@bjornstar) 37 | - Update @types/react to v18 (@bjornstar) 38 | 39 | ## v6.2.0 - 2022-04-08 40 | 41 | - Add scaleOverride (@bjornstar) 42 | 43 | ## v6.1.0 - 2022-04-02 44 | 45 | - Now calls `connect` before `init` in a useEffect (instead of useLayoutEffect) 46 | - Update `@pmndrs/cannon-worker-api` to v2.1.0 47 | 48 | ## v6.0.0 - 2022-04-01 49 | 50 | - Removed the Suspense wrapper around Physics, you will need to provide your own suspense boundary from now on 51 | - `react` is now a `peerDependency` and requires v18 or higher 52 | - `three.js` is now a `peerDependency` and requires r139 or higher 53 | - `@react-three/fiber` is now a `peerDependency` and requires v8 or higher 54 | - `@pmndrs/cannon-worker-api` is now a `dependency` 55 | - `cannon-es` is now a `dependency` 56 | - `cannon-es-debugger` is now a `dependency` 57 | - Updated many `devDependencies` 58 | 59 | ## v5.1.0 - 2022-03-19 60 | 61 | - Access the physics context with the `usePhysicsContext` hook, which immediately gives you a clear error message when trying to access physics components or hooks outside of a Physics provider (@bjornstar) 62 | - Renamed `context` to `physicsContext` (@bjornstar) 63 | - Added a `useDebugContext` hook for consistency (@bjornstar) 64 | - [`Provider.tsx`] Renamed to `physics-provider.tsx` (@bjornstar) 65 | - [`Debug.tsx`] Renamed to `debug-provider.tsx` (Still exported as Debug & DebugProps) (@bjornstar) 66 | - [`physics-provider.tsx`] One useState call that contains the whole context (@bjornstar) 67 | - [`physics-provider.tsx`] bodies is not a ref, no need to access current (@bjornstar) 68 | - [`setup.ts`] Removed, split into more appropriately named modules (@bjornstar) 69 | - [`worker.d.ts`] Removed, belongs in cannon-worker-api (@bjornstar) 70 | - [`package.json`] Use dependencies rather than peerDependencies (@bjornstar) 71 | - [`.eslintrc.json`] Clean up (@bjornstar) 72 | - [`.eslintrc.json`] Disallow non-null assertions (@bjornstar) 73 | 74 | ## v5.0.1 - 2022-03-14 75 | 76 | - Bump @pmndrs/cannon-worker-api to v1.0.1 (@bjornstar) 77 | 78 | ## v5.0.0 - 2022-03-13 79 | 80 | - Use newly isolated @pmndrs/cannon-worker-api (@isaac-mason) 81 | - Removed useUpdateWorldPropsEffect (@bjornstar) 82 | - [`package.json`] Added homepage property to go directly to the package (@bjornstar) 83 | - [`rollup.config.js`] Specify targetPlatform: 'browser' (@bjornstar) 84 | - [`tsconfig.json`] Alphabetize contents (@bjornstar) 85 | 86 | ## v4.9.0 - 2022-03-03 87 | 88 | - [`dependencies`] Updated `three` & `@types/three` from `r135` to `r137` (@bjornstar) 89 | - [`examples/dependencies`] Updated `@react-three/drei` from `v8.3.1` to `v8.11.1` (@bjornstar) 90 | - [`examples/dependencies`] Updated `@react-three/drei` from `v8.3.1` to `v8.11.1` (@bjornstar) 91 | - [`examples/dependencies`] Updated `three` & `@types/three` from `r135` to `r137` (@bjornstar) 92 | - [`examples/dependencies`] Updated `three-stdlib` from `2.6.1` to `v2.8.8` (@bjornstar) 93 | - [`esmaples/dependencies`] Removed `postprocessing`, it was unused (@bjornstar) 94 | - [`examples`] Updated GLTF types (@bjornstar) 95 | 96 | ## v4.8.0 - 2022-02-28 97 | 98 | - Created CannonWorkerAPI (@isaac-mason) 99 | - Converted worker to typescript (@bjornstar) 100 | - [`examples/RaycastVehicle`] Use a single keyup/keydown event handler (@bjornstar) 101 | - 102 | 103 | ## v4.7.0 - 2022-02-12 104 | 105 | - [ESLint] Disallow enums (@bjornstar) 106 | - Add missing worker 'setMaterial' op handler (@isaac-mason) 107 | - Add isPaused property (@grndctrl & @bjornstar) 108 | - BREAKING: step renamed to stepSize (default: 1 / 60) 109 | - NEW: maxSubSteps (default: 10) 110 | - NEW: isPaused (fixes Pause Simulation #212) 111 | - NEW: Paused demo 112 | - timeSinceLastCall not tracked in worker 113 | - prefer FC to PropsWithChildren 114 | - REMOVED: type DefaultContactMaterial 115 | 116 | ## v4.6.1 - 2022-01-19 117 | 118 | - [`createMaterialFactory`] Do not use logical assignment operator (@bjornstar) 119 | - [`eslint`] Disallow logical assignment and nullish coalescing operators (@bjornstar) 120 | 121 | ## v4.6.0 - 2022-01-15 122 | 123 | - [`hooks`] Add `useContactMaterial` (@Glavin001) 124 | - [`examples`] Add `Friction` example (@Glavin001) 125 | - [`examples`] Add title to links (@Glavin001) 126 | 127 | ## v4.5.0 - 2022-01-08 128 | 129 | - [`constraintOptns`] Add `maxMultiplier` (@Glavin001) 130 | 131 | ## v4.4.1 - 2022-01-04 132 | 133 | - [Hooks] Destructure and set defaults intead of using `??` (@bjornstar) 134 | - [`useRaycastVehicle`] Use correct ordering for arguments (@bjornstar) 135 | - [`examples/RaycastVehicle`] Reset restores the vehicle to it's initial angularVelocity, position, & rotation (@bjornstar) 136 | 137 | ## v4.4.0 - 2022-01-01 138 | 139 | - Upgrade cannon-es-debugger to 1.0.0 (@marcofugaro) 140 | - [`Debug`] Improve implementation (@bjornstar) 141 | - [`examples/RaycastVehicle`] Press `?` to debug (@bjornstar) 142 | 143 | ## v4.3.1 - 2021-12-30 144 | 145 | - Fix RaycastVehicle example (@marcofugaro) 146 | 147 | ## v4.3.0 - 2021-12-18 148 | 149 | - Add AtomicName & VectorName to the README (@bjornstar) 150 | - Update vite to v2.7.3, change vite.config.js to vite.config.ts (@bjornstar) 151 | - [examples] add missing peer dependency: react-is (@bjornstar) 152 | - Update all dependencies, fix example routes for react-router-dom v6 (@bjornstar) 153 | 154 | ## v4.2.0 - 2021-12-01 155 | 156 | - [Types] Use `PropsWithChildren` from React instead of `children: ReactNode` (@bjornstar) 157 | - [README.md] Update default Physics prop values (@bjornstar) 158 | - export \* from `'./setup'` there are a lot of useful types in here (@bjornstar) 159 | - Build using jsx runtime instead of React runtime for a slightly smaller bundle (@bjornstar) 160 | - [CHANGELOG.md] Add details for v3.1.1 & v3.1.2 (@bjornstar) 161 | 162 | ## v4.1.0 - 2021-11-21 163 | 164 | - Update default gravity value from `-10` to `-9.81` (@alexandernanberg) 165 | - [devDependencies] Update to latest versions (@bjornstar) 166 | - [CHANGELOG.md] Start writing a changelog (@bjornstar) 167 | - [README.md] Replace `boxBufferGeometry` with `boxGeometry` and `planeBufferGeometry` with `planeGeometry` (@drcmda) 168 | - [examples/devDependencies] Update to latest version (@bjornstar) 169 | 170 | ## v4.0.1 - 2021-10-06 171 | 172 | - Fix an bug where multiple rotations shared an array (@bjornstar) 173 | 174 | ## v4.0.0 - 2021-10-05 175 | 176 | - Add quaternion API, convert from quaternion to rotation correctly (@bjornstar) 177 | - useSphere args must be an array (@bjornstar) 178 | - [Typescript] Add types for world messages (like setGravity) (@bjornstar) 179 | - Prefer CannonEvent over global Event type name (@bjornstar) 180 | - [TypeScript] Improve set and subscribe API (@bjornstar) 181 | 182 | ## v3.1.2 - 2021-09-02 183 | 184 | - Rebuild package (@stockHuman) 185 | 186 | ## v3.1.1 - 2021-09-02 187 | 188 | - Fix useRaycastVehicle, getUUID was receiving unintended index values (@bjornstar) 189 | - [README.md] Update demos to point to cannon.pmnd.rs (@bjornstar) 190 | 191 | ## v3.1.0 - 2021-09-01 192 | 193 | - [Examples] Convert Kinematic Cube to TypeScript (#262) (@bjornstar) 194 | - [Examples] Convert Heightmap to TypeScript (#264) (@bjornstar) 195 | - [Examples] Convert SphereDebug to TypeScript (#261) (@bjornstar) 196 | - [Examples] Convert Hinge Motor to TypeScript (#263) (@bjornstar) 197 | - [Examples] Convert Cube Heap to TypeScript (#265) (@bjornstar) 198 | - [Examples] Convert Convex Polyhedron to TypeScript (#266) (@bjornstar) 199 | - [Examples] Convert Compound Body to TypeScript (#268) (@bjornstar) 200 | - [Examples] Convert Constraints to TypeScript (#267) (@bjornstar) 201 | - [Examples] Convert Raycast Vehicle to TypeScript (#270) (@bjornstar) 202 | - [Examples] Convert Chain to TypeScript (#269) (@bjornstar) 203 | - [Examples] Convert Raycast to TypeScript (#271) (@bjornstar) 204 | - [Examples] Convert Ping Pong to TypeScript (@bjornstar) 205 | - [readme.md] Switch build badge from travis to github (@bjornstar) 206 | - Use Ref to allow for forwarded refs (@bjornstar) 207 | - Use React.DependencyList instead of any[] for deps (@bjornstar) 208 | - [CI] Test on node v14 as vercel doesn't support 16 yet (@bjornstar) 209 | 210 | ## v3.0.1 - 2021-08-23 211 | 212 | - Resolve three ourselves to avoid multiple three instances and failed instanceof checks (@bjornstar) 213 | 214 | ## v3.0.0 - 2021-08-21 215 | 216 | - Fix return type of subscribe function (@skuteli) 217 | - [types] mutableRefObject should default to null (@bjornstar) 218 | - Start converting examples to typescript (@bjornstar) 219 | - [CI] Try to build the examples (@bjornstar) 220 | - Fix getUUID (@bjornstar) 221 | - Specify all op strings (@bjornstar) 222 | - Remove .travis.yml, update ignores (@bjornstar) 223 | - [Examples] Readme & Usability Improvements (@bjornstar) 224 | - Convert Triggers example to typescript (@bjornstar) 225 | - Convert Trimesh Example to typescript (@bjornstar) 226 | 227 | ## v2.6.1 - 2021-08-15 228 | 229 | - Rebuild package (@stockHuman) 230 | 231 | ## v2.6.0 - 2021-08-11 232 | 233 | - Switch from CRA to vite (@bjornstar) 234 | - feat: add applyTorque API to body (@a-type) 235 | 236 | ## v2.5.1 - 2021-07-29 237 | 238 | - Update readme.md (@kevinmcalear) 239 | - Improve readme (@bjornstar) 240 | - Wrap in canvas (@bjornstar) 241 | - support missing world attributes (@drcmda) 242 | 243 | ## v2.5.0 - 2021-07-01 244 | 245 | - Add shouldInvalidate to readme code (@aunyks) 246 | - [Examples] CubeHeap, click to change to spheres (@bjornstar) 247 | - Expose WakeUp & Sleep API (stockHuman) 248 | 249 | ## v2.4.0 - 2021-06-28 250 | 251 | - Remove dead code (@Gusted) 252 | - Setup automated hygiene (@bjornstar) 253 | - Add prepare script (@bjornstar) 254 | - Run CI on the master branch (@bjornstar) 255 | - Don't build examples (@bjornstar) 256 | - Add 'shouldInvalidate' prop to Physics provider component to allow for pausing the simulation (@aunyks) 257 | - Update bug_report.md (@stockHuman) 258 | - Integrate pausing functionality (@stockHuman) 259 | - Set printWidth to 110 (@bjornstar) 260 | -------------------------------------------------------------------------------- /packages/react-three-cannon/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Paul Henschel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/react-three-cannon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-three/cannon", 3 | "version": "6.6.0", 4 | "description": "physics based hooks for react-three-fiber", 5 | "keywords": [ 6 | "cannon", 7 | "three", 8 | "react", 9 | "react-three-fiber", 10 | "physics" 11 | ], 12 | "author": "Paul Henschel", 13 | "contributors": [ 14 | "Cody Persinger (https://github.com/codynova)", 15 | "Bjorn Stromberg (https://github.com/bjornstar)" 16 | ], 17 | "homepage": "https://github.com/pmndrs/use-cannon/tree/master/packages/react-three-cannon#readme", 18 | "repository": "github:pmndrs/use-cannon", 19 | "license": "MIT", 20 | "module": "dist/index.js", 21 | "types": "dist/index.d.ts", 22 | "sideEffects": false, 23 | "scripts": { 24 | "build": "tsc && rollup -c", 25 | "clean": "rm -rf dist/*", 26 | "test": "echo tests are missing", 27 | "eslint": "eslint .", 28 | "eslint-fix": "eslint --fix .", 29 | "prettier": "prettier --list-different .", 30 | "prettier-fix": "prettier --write ." 31 | }, 32 | "dependencies": { 33 | "@pmndrs/cannon-worker-api": "^2.4.0", 34 | "cannon-es": "^0.20.0", 35 | "cannon-es-debugger": "^1.0.0" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.17.8", 39 | "@babel/plugin-transform-runtime": "^7.17.0", 40 | "@babel/preset-env": "^7.16.11", 41 | "@babel/preset-react": "^7.16.7", 42 | "@babel/preset-typescript": "^7.16.7", 43 | "@rollup/plugin-babel": "^5.3.1", 44 | "@rollup/plugin-node-resolve": "^13.1.3", 45 | "@types/react": "^18.0.5", 46 | "@types/three": "^0.155.0", 47 | "@typescript-eslint/eslint-plugin": "^5.17.0", 48 | "@typescript-eslint/parser": "^5.17.0", 49 | "eslint": "^8.12.0", 50 | "eslint-config-prettier": "^8.5.0", 51 | "eslint-plugin-es": "^4.1.0", 52 | "eslint-plugin-react": "^7.29.4", 53 | "eslint-plugin-simple-import-sort": "^7.0.0", 54 | "eslint-plugin-typescript-enum": "^2.1.0", 55 | "prettier": "^2.6.1", 56 | "rollup": "^2.70.1", 57 | "typescript": "^4.6.3" 58 | }, 59 | "lint-staged": { 60 | "*.{js,jsx,ts,tsx}": "eslint --cache --fix", 61 | "*.{js,jsx,ts,tsx,md}": "prettier --write" 62 | }, 63 | "peerDependencies": { 64 | "@react-three/fiber": ">=8", 65 | "react": ">=18", 66 | "three": ">=0.139" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/react-three-cannon/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import pluginNodeResolve from '@rollup/plugin-node-resolve' 3 | 4 | // These are our dependencies, everything else is in the bundle 5 | const external = ['@react-three/fiber', 'react', 'react/jsx-runtime', 'three'] 6 | const extensions = ['.js', '.jsx', '.ts', '.tsx', '.json'] 7 | 8 | const getBabelOptions = ({ useESModules }, targets) => ({ 9 | babelHelpers: 'runtime', 10 | babelrc: false, 11 | extensions, 12 | include: ['src/**/*', '**/node_modules/**'], 13 | plugins: [['@babel/transform-runtime', { regenerator: false, useESModules }]], 14 | presets: [ 15 | ['@babel/preset-env', { loose: true, modules: false, targets }], 16 | ['@babel/preset-react', { runtime: 'automatic' }], 17 | '@babel/preset-typescript', 18 | ], 19 | }) 20 | 21 | export default [ 22 | { 23 | external, 24 | input: `./src/index.tsx`, 25 | output: { dir: 'dist', format: 'esm' }, 26 | plugins: [ 27 | pluginNodeResolve({ extensions }), 28 | babel(getBabelOptions({ useESModules: true }, '>1%, not dead, not ie 11, not op_mini all')), 29 | ], 30 | }, 31 | { 32 | external, 33 | input: `./src/index.tsx`, 34 | output: { dir: 'dist/debug', format: 'esm' }, 35 | plugins: [ 36 | pluginNodeResolve({ extensions }), 37 | babel(getBabelOptions({ useESModules: true }, '>1%, not dead, not ie 11, not op_mini all')), 38 | ], 39 | }, 40 | ] 41 | -------------------------------------------------------------------------------- /packages/react-three-cannon/src/debug-context.ts: -------------------------------------------------------------------------------- 1 | import type { BodyProps, BodyShapeType } from '@pmndrs/cannon-worker-api' 2 | import { createContext, useContext } from 'react' 3 | 4 | export type DebugApi = { 5 | add(id: string, props: BodyProps, type: BodyShapeType): void 6 | remove(id: string): void 7 | } 8 | 9 | export const debugContext = createContext(null) 10 | 11 | export const useDebugContext = () => useContext(debugContext) 12 | -------------------------------------------------------------------------------- /packages/react-three-cannon/src/debug-provider.tsx: -------------------------------------------------------------------------------- 1 | import { propsToBody } from '@pmndrs/cannon-worker-api' 2 | import { useFrame } from '@react-three/fiber' 3 | import type { Body, Quaternion as CQuaternion, Vec3, World } from 'cannon-es' 4 | import CannonDebugger from 'cannon-es-debugger' 5 | import type { PropsWithChildren } from 'react' 6 | import { useMemo, useRef, useState } from 'react' 7 | import type { Color, Object3D } from 'three' 8 | import { InstancedMesh, Matrix4, Quaternion, Scene, Vector3 } from 'three' 9 | 10 | import type { DebugApi } from './debug-context' 11 | import { debugContext } from './debug-context' 12 | import { usePhysicsContext } from './physics-context' 13 | 14 | type DebugInfo = { bodies: Body[]; bodyMap: { [uuid: string]: Body } } 15 | 16 | export type DebugProviderProps = { 17 | color?: string | number | Color 18 | impl?: typeof CannonDebugger 19 | scale?: number 20 | } 21 | 22 | const q = new Quaternion() 23 | const s = new Vector3(1, 1, 1) 24 | const v = new Vector3() 25 | const m = new Matrix4() 26 | 27 | const getMatrix = (o: Object3D): Matrix4 => { 28 | if (o instanceof InstancedMesh) { 29 | o.getMatrixAt(parseInt(o.uuid.split('/')[1]), m) 30 | return m 31 | } 32 | return o.matrix 33 | } 34 | 35 | export function DebugProvider({ 36 | children, 37 | color = 'black', 38 | impl = CannonDebugger, 39 | scale = 1, 40 | }: PropsWithChildren): JSX.Element { 41 | const [{ bodies, bodyMap }] = useState({ bodies: [], bodyMap: {} }) 42 | const { refs } = usePhysicsContext() 43 | const [scene] = useState(() => new Scene()) 44 | const cannonDebuggerRef = useRef(impl(scene, { bodies } as World, { color, scale })) 45 | 46 | useFrame(() => { 47 | for (const uuid in bodyMap) { 48 | getMatrix(refs[uuid]).decompose(v, q, s) 49 | bodyMap[uuid].position.copy(v as unknown as Vec3) 50 | bodyMap[uuid].quaternion.copy(q as unknown as CQuaternion) 51 | } 52 | 53 | cannonDebuggerRef.current.update() 54 | }) 55 | 56 | const api = useMemo( 57 | () => ({ 58 | add(uuid, props, type) { 59 | const body = propsToBody({ props, type, uuid }) 60 | bodies.push(body) 61 | bodyMap[uuid] = body 62 | }, 63 | remove(uuid) { 64 | const index = bodies.indexOf(bodyMap[uuid]) 65 | if (index !== -1) bodies.splice(index, 1) 66 | delete bodyMap[uuid] 67 | }, 68 | }), 69 | [bodies, bodyMap], 70 | ) 71 | 72 | return ( 73 | 74 | 75 | {children} 76 | 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /packages/react-three-cannon/src/index.tsx: -------------------------------------------------------------------------------- 1 | export type { DebugProviderProps as DebugProps } from './debug-provider' 2 | export { DebugProvider as Debug } from './debug-provider' 3 | export * from './hooks' 4 | export type { PhysicsProviderProps as PhysicsProps } from './physics-provider' 5 | export { PhysicsProvider as Physics } from './physics-provider' 6 | export type { 7 | AtomicName, 8 | AtomicProps, 9 | BodyProps, 10 | BodyPropsArgsRequired, 11 | BodyShapeType, 12 | BoxProps, 13 | Broadphase, 14 | Buffers, 15 | CannonMessage, 16 | CannonMessageBody, 17 | CannonMessageMap, 18 | CannonMessageProps, 19 | CannonWebWorker, 20 | CollideBeginEvent, 21 | CollideEndEvent, 22 | CollideEvent, 23 | CompoundBodyProps, 24 | ConeTwistConstraintOpts, 25 | ConstraintOptns, 26 | ConstraintTypes, 27 | ConvexPolyhedronArgs, 28 | ConvexPolyhedronProps, 29 | CylinderArgs, 30 | CylinderProps, 31 | DistanceConstraintOpts, 32 | HeightfieldArgs, 33 | HeightfieldProps, 34 | HingeConstraintOpts, 35 | IncomingWorkerMessage, 36 | LockConstraintOpts, 37 | Observation, 38 | ParticleProps, 39 | PlaneProps, 40 | PointToPointConstraintOpts, 41 | PropValue, 42 | Quad, 43 | RayhitEvent, 44 | RayMode, 45 | RayOptions, 46 | Refs, 47 | SetOpName, 48 | ShapeType, 49 | Solver, 50 | SphereArgs, 51 | SphereProps, 52 | SpringOptns, 53 | StepProps, 54 | Subscription, 55 | SubscriptionName, 56 | Subscriptions, 57 | SubscriptionTarget, 58 | TrimeshArgs, 59 | TrimeshProps, 60 | Triplet, 61 | VectorName, 62 | VectorProps, 63 | WheelInfoOptions, 64 | WorkerCollideBeginEvent, 65 | WorkerCollideEndEvent, 66 | WorkerCollideEvent, 67 | WorkerEventMessage, 68 | WorkerFrameMessage, 69 | WorkerRayhitEvent, 70 | WorldPropName, 71 | WorldProps, 72 | } from '@pmndrs/cannon-worker-api' 73 | -------------------------------------------------------------------------------- /packages/react-three-cannon/src/physics-context.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | CannonWorkerAPI, 3 | CollideBeginEvent, 4 | CollideEndEvent, 5 | CollideEvent, 6 | RayhitEvent, 7 | Refs, 8 | Subscriptions, 9 | } from '@pmndrs/cannon-worker-api' 10 | import { createContext, useContext } from 'react' 11 | import type { Vector3 } from 'three' 12 | 13 | type CannonEvent = CollideBeginEvent | CollideEndEvent | CollideEvent | RayhitEvent 14 | type CallbackByType = { 15 | [K in T['type']]?: T extends { type: K } ? (e: T) => void : never 16 | } 17 | 18 | export type CannonEvents = { [uuid: string]: Partial> } 19 | 20 | export type ScaleOverrides = { [uuid: string]: Vector3 } 21 | 22 | export type PhysicsContext = { 23 | bodies: { [uuid: string]: number } 24 | events: CannonEvents 25 | refs: Refs 26 | scaleOverrides: ScaleOverrides 27 | subscriptions: Subscriptions 28 | worker: CannonWorkerAPI 29 | } 30 | 31 | export const physicsContext = createContext(null) 32 | 33 | export const usePhysicsContext = () => { 34 | const context = useContext(physicsContext) 35 | if (!context) 36 | throw new Error( 37 | 'Physics context not found. @react-three/cannon & components can only be used within a Physics provider', 38 | ) 39 | return context 40 | } 41 | -------------------------------------------------------------------------------- /packages/react-three-cannon/src/physics-provider.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | CannonWorkerProps, 3 | WorkerCollideBeginEvent, 4 | WorkerCollideEndEvent, 5 | WorkerCollideEvent, 6 | WorkerFrameMessage, 7 | WorkerRayhitEvent, 8 | } from '@pmndrs/cannon-worker-api' 9 | import { CannonWorkerAPI } from '@pmndrs/cannon-worker-api' 10 | import type { RenderCallback } from '@react-three/fiber' 11 | import { useFrame, useThree } from '@react-three/fiber' 12 | import type { PropsWithChildren } from 'react' 13 | import { useCallback, useEffect, useMemo, useState } from 'react' 14 | import type { Object3D } from 'three' 15 | import { InstancedMesh, Matrix4, Quaternion, Vector3 } from 'three' 16 | 17 | import type { PhysicsContext } from './physics-context' 18 | import { physicsContext } from './physics-context' 19 | 20 | export type PhysicsProviderProps = CannonWorkerProps & { 21 | isPaused?: boolean 22 | maxSubSteps?: number 23 | shouldInvalidate?: boolean 24 | stepSize?: number 25 | } 26 | 27 | const v = new Vector3() 28 | const s = new Vector3(1, 1, 1) 29 | const q = new Quaternion() 30 | const m = new Matrix4() 31 | 32 | function apply( 33 | index: number, 34 | positions: Float32Array, 35 | quaternions: Float32Array, 36 | scale = s, 37 | object?: Object3D, 38 | ) { 39 | if (index !== undefined) { 40 | m.compose(v.fromArray(positions, index * 3), q.fromArray(quaternions, index * 4), scale) 41 | if (object) { 42 | object.matrixAutoUpdate = false 43 | object.matrix.copy(m) 44 | } 45 | return m 46 | } 47 | return m.identity() 48 | } 49 | 50 | const unique = () => { 51 | const values: unknown[] = [] 52 | return (value: unknown) => (values.includes(value) ? false : !!values.push(value)) 53 | } 54 | 55 | export function PhysicsProvider({ 56 | allowSleep = false, 57 | axisIndex = 0, 58 | broadphase = 'Naive', 59 | children, 60 | defaultContactMaterial = { contactEquationStiffness: 1e6 }, 61 | frictionGravity = null, 62 | gravity = [0, -9.81, 0], 63 | isPaused = false, 64 | iterations = 5, 65 | maxSubSteps = 10, 66 | quatNormalizeFast = false, 67 | quatNormalizeSkip = 0, 68 | shouldInvalidate = true, 69 | size = 1000, 70 | solver = 'GS', 71 | stepSize = 1 / 60, 72 | tolerance = 0.001, 73 | }: PropsWithChildren): JSX.Element { 74 | const { invalidate } = useThree() 75 | 76 | const [{ bodies, events, refs, scaleOverrides, subscriptions, worker }] = useState(() => ({ 77 | bodies: {}, 78 | events: {}, 79 | refs: {}, 80 | scaleOverrides: {}, 81 | subscriptions: {}, 82 | worker: new CannonWorkerAPI({ 83 | allowSleep, 84 | axisIndex, 85 | broadphase, 86 | defaultContactMaterial, 87 | frictionGravity, 88 | gravity, 89 | iterations, 90 | quatNormalizeFast, 91 | quatNormalizeSkip, 92 | size, 93 | solver, 94 | tolerance, 95 | }), 96 | })) 97 | 98 | let timeSinceLastCalled = 0 99 | 100 | const loop = useCallback( 101 | (_, delta) => { 102 | if (isPaused) return 103 | timeSinceLastCalled += delta 104 | worker.step({ maxSubSteps, stepSize, timeSinceLastCalled }) 105 | timeSinceLastCalled = 0 106 | }, 107 | [isPaused, maxSubSteps, stepSize], 108 | ) 109 | 110 | const collideHandler = ({ 111 | body, 112 | contact: { bi, bj, ...contactRest }, 113 | target, 114 | ...rest 115 | }: WorkerCollideEvent['data']) => { 116 | const cb = events[target]?.collide 117 | cb && 118 | cb({ 119 | body: refs[body], 120 | contact: { 121 | bi: refs[bi], 122 | bj: refs[bj], 123 | ...contactRest, 124 | }, 125 | target: refs[target], 126 | ...rest, 127 | }) 128 | } 129 | 130 | const collideBeginHandler = ({ bodyA, bodyB }: WorkerCollideBeginEvent['data']) => { 131 | const cbA = events[bodyA]?.collideBegin 132 | cbA && 133 | cbA({ 134 | body: refs[bodyB], 135 | op: 'event', 136 | target: refs[bodyA], 137 | type: 'collideBegin', 138 | }) 139 | const cbB = events[bodyB]?.collideBegin 140 | cbB && 141 | cbB({ 142 | body: refs[bodyA], 143 | op: 'event', 144 | target: refs[bodyB], 145 | type: 'collideBegin', 146 | }) 147 | } 148 | 149 | const collideEndHandler = ({ bodyA, bodyB }: WorkerCollideEndEvent['data']) => { 150 | const cbA = events[bodyA]?.collideEnd 151 | cbA && 152 | cbA({ 153 | body: refs[bodyB], 154 | op: 'event', 155 | target: refs[bodyA], 156 | type: 'collideEnd', 157 | }) 158 | const cbB = events[bodyB]?.collideEnd 159 | cbB && 160 | cbB({ 161 | body: refs[bodyA], 162 | op: 'event', 163 | target: refs[bodyB], 164 | type: 'collideEnd', 165 | }) 166 | } 167 | 168 | const frameHandler = ({ 169 | active, 170 | bodies: uuids = [], 171 | observations, 172 | positions, 173 | quaternions, 174 | }: WorkerFrameMessage['data']) => { 175 | for (let i = 0; i < uuids.length; i++) { 176 | bodies[uuids[i]] = i 177 | } 178 | observations.forEach(([id, value, type]) => { 179 | const subscription = subscriptions[id] || {} 180 | const cb = subscription[type] 181 | // HELP: We clearly know the type of the callback, but typescript can't deal with it 182 | cb && cb(value as never) 183 | }) 184 | 185 | if (!active) return 186 | 187 | for (const ref of Object.values(refs).filter(unique())) { 188 | if (ref instanceof InstancedMesh) { 189 | for (let i = 0; i < ref.count; i++) { 190 | const uuid = `${ref.uuid}/${i}` 191 | const index = bodies[uuid] 192 | if (index !== undefined) { 193 | ref.setMatrixAt(i, apply(index, positions, quaternions, scaleOverrides[uuid])) 194 | ref.instanceMatrix.needsUpdate = true 195 | } 196 | } 197 | } else { 198 | const scale = scaleOverrides[ref.uuid] || ref.scale 199 | apply(bodies[ref.uuid], positions, quaternions, scale, ref) 200 | } 201 | } 202 | 203 | if (shouldInvalidate) { 204 | invalidate() 205 | } 206 | } 207 | 208 | const rayhitHandler = ({ body, ray: { uuid, ...rayRest }, ...rest }: WorkerRayhitEvent['data']) => { 209 | const cb = events[uuid]?.rayhit 210 | cb && 211 | cb({ 212 | body: body ? refs[body] : null, 213 | ray: { uuid, ...rayRest }, 214 | ...rest, 215 | }) 216 | } 217 | 218 | // Run loop *after* all the physics objects have ran theirs! 219 | // Otherwise the buffers will be invalidated by the browser 220 | useFrame(loop) 221 | 222 | useEffect(() => { 223 | worker.connect() 224 | worker.init() 225 | 226 | worker.on('collide', collideHandler) 227 | worker.on('collideBegin', collideBeginHandler) 228 | worker.on('collideEnd', collideEndHandler) 229 | worker.on('frame', frameHandler) 230 | worker.on('rayhit', rayhitHandler) 231 | 232 | return () => { 233 | worker.terminate() 234 | worker.removeAllListeners() 235 | } 236 | }, []) 237 | 238 | useEffect(() => { 239 | worker.axisIndex = axisIndex 240 | }, [axisIndex]) 241 | useEffect(() => { 242 | worker.broadphase = broadphase 243 | }, [broadphase]) 244 | useEffect(() => { 245 | worker.gravity = gravity 246 | }, [gravity]) 247 | useEffect(() => { 248 | worker.iterations = iterations 249 | }, [iterations]) 250 | useEffect(() => { 251 | worker.tolerance = tolerance 252 | }, [tolerance]) 253 | 254 | const value = useMemo( 255 | () => ({ bodies, events, refs, scaleOverrides, subscriptions, worker }), 256 | [bodies, events, refs, subscriptions, worker], 257 | ) 258 | 259 | return {children} 260 | } 261 | -------------------------------------------------------------------------------- /packages/react-three-cannon/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react-jsx", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noImplicitThis": false, 12 | "outDir": "dist", 13 | "pretty": true, 14 | "removeComments": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "target": "es6" 19 | }, 20 | "exclude": ["./node_modules/**/*"], 21 | "include": ["./src"] 22 | } 23 | --------------------------------------------------------------------------------