diff --git a/.changeset/calm-bugs-fixed.md b/.changeset/calm-bugs-fixed.md new file mode 100644 index 00000000..e73c9ea0 --- /dev/null +++ b/.changeset/calm-bugs-fixed.md @@ -0,0 +1,17 @@ +--- +"@react-three/rapier": patch +"@react-three/rapier-addons": patch +--- + +Bug fixes for 3.0: + +- Forwarded refs on `RigidBody`, colliders and `InstancedRigidBodies` instances are now cleared when the underlying rapier object is destroyed, instead of pointing at freed objects (#780) +- Trimesh and convex hull colliders now read geometry positions correctly for interleaved, normalized and quantized attributes, and indices are converted to `Uint32Array`; failing to build a collider now throws a descriptive error (#781, #627) +- Heightfield collider args are scaled per axis and no longer mutated in place, which compounded scale on remount (#564) +- `mass`, `density` and `massProperties` collider props now update after mount (#711) +- Re-export `ActiveCollisionTypes`, `ActiveEvents`, `ActiveHooks` and `QueryFilterFlags` from the package index (#703) +- Add a regression test for changing the `RigidBody` `type` after mount (#314) +- `InstancedRigidBodies` no longer spreads `key` into JSX, which warned on React 19 (#691) +- `InstancedRigidBodies` disables frustum culling on its instanced mesh by default, since cached bounds go stale as bodies move; pass `frustumCulled` to re-enable it (#683) +- Replace `three-stdlib` with `three/examples/jsm` imports and remove the dependency (#698) +- Document `HeightfieldArgs` (`heights` must have `(rows + 1) * (cols + 1)` values) and warn in development when the length mismatches (#730) diff --git a/.changeset/character-controller.md b/.changeset/character-controller.md new file mode 100644 index 00000000..c02a3a4e --- /dev/null +++ b/.changeset/character-controller.md @@ -0,0 +1,10 @@ +--- +"@react-three/rapier": minor +--- + +Add a kinematic character controller built on Rapier's `KinematicCharacterController`. + +- `useCharacterController(options)` creates and frees the controller (StrictMode safe), applies `offset`, `up`, `slide`, `maxSlopeClimbAngle`, `minSlopeSlideAngle`, `autostep`, `snapToGround`, `applyImpulsesToDynamicBodies`, `characterMass` and `normalNudgeFactor` reactively, and returns a stable `{ controller, move(rigidBody, desiredTranslation, opts) }` api. `move` computes the collision-resolved movement, calls `setNextKinematicTranslation` and returns a pooled `{ movement, grounded, collisions }`. +- `` wraps a `kinematicPosition` `RigidBody`. Its ref exposes `{ controller, rigidBody, grounded, setVelocity(v), translate(delta) }`; the desired motion is applied once per fixed physics step with the fixed `dt`, so it is framerate independent. It also accepts `onGrounded`, `onAirborne` (edge triggered) and `onCharacterCollision`. + +Options reverting to `undefined` restore Rapier's defaults. `` defaults to `colliders={false}`. Gravity and jumping are intentionally left to userland. See the `character-controller` demo. diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..9a89b8a3 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,4 @@ +{ + "mode": "pre", + "tag": "alpha" +} diff --git a/.changeset/r3f-v10-foundation.md b/.changeset/r3f-v10-foundation.md new file mode 100644 index 00000000..c478358a --- /dev/null +++ b/.changeset/r3f-v10-foundation.md @@ -0,0 +1,27 @@ +--- +"@react-three/rapier": major +"@react-three/rapier-addons": major +--- + +Move to the React Three Fiber v10 stack and modernize the package format. + +### Requirements + +- `@react-three/fiber` `>=10.0.0-0` (developed against `10.0.0-canary.a056612`; scheduler fixed-step support needs `@pmndrs/scheduler` 0.3+) +- `react` `>=19.0 <19.3` (the range r3f v10 supports) +- `three` `>=0.185.0` +- Node.js 22+ for tooling (`engines`) +- `@react-three/rapier-addons` now peers on `@react-three/rapier` `^3.0.0-0` + +### Package format + +- Built with rollup + esbuild instead of preconstruct. Each package ships an `exports` map with + ESM (`dist/index.mjs`) and CommonJS (`dist/index.cjs`) builds and matching type declarations + (`.d.mts` / `.d.cts`), and is marked `sideEffects: false`. +- Deep imports into package internals (e.g. `@react-three/rapier/src/...` or `dist/...` files) + are no longer possible; import from the package root. + +### Behaviour notes + +- Under r3f v10 `useFrame`'s state no longer has `clock`; use `state.elapsed` / `state.delta` + in your own frame callbacks. diff --git a/.changeset/scheduler-fixed-step.md b/.changeset/scheduler-fixed-step.md new file mode 100644 index 00000000..a59c5b1b --- /dev/null +++ b/.changeset/scheduler-fixed-step.md @@ -0,0 +1,17 @@ +--- +"@react-three/rapier": major +--- + +Step physics on the react-three-fiber v10 scheduler. + +- With `updateLoop="follow"` (default) and a numeric `timeStep`, each world steps in its own fixed-timestep scheduler phase (`rapier:fixed:`), which runs after r3f's `physics` phase. Changing `timeStep` or `maxSubSteps` retimes that phase in place, and phases are reused when worlds remount. Rendered transforms are synced once per frame in a new `rapier` phase, before `update`, and interpolated by the phase's overstep. A `rapier:late` pass right before `render` re-applies them if an ancestor moved during `update`. The shared `physics` phase timestep is never changed, and multiple `` worlds with different `timeStep`s work side by side. Without scheduler fixed-phase support (`@pmndrs/scheduler` < 0.3), the same behaviour runs on an internal accumulator. +- New `maxSubSteps` prop (default `5`) caps the fixed steps a single frame may run. Surplus time is dropped instead of queued, so slow frames can no longer spiral (#697). +- **Breaking:** `updatePriority` now only orders this world's jobs within their scheduler phase (higher runs first, the r3f v10 convention). It no longer takes over r3f's render loop. Physics always steps before `update`, and r3f keeps rendering. +- `paused` banks no time, so resuming does not burst. `step()` from `useRapier()` works while paused and syncs meshes immediately. +- `useBeforePhysicsStep` and `useAfterPhysicsStep` callbacks receive `(world, dt)`, where `dt` is the step length. Existing single-argument callbacks keep working. +- Collision, contact-force and soft-body tear events are drained after every substep instead of once per frame. +- Bodies are synced parent-first into their parent's current space every frame, so moving, rotating or re-parenting an ancestor of a `` after mount keeps its visual on the simulated body (#36), and nested bodies no longer lag the body that carries them. +- Interpolation no longer lerps across teleports made in `useBeforePhysicsStep`, manual `step()` calls, `setWorld()`, or `paused`/`interpolate` changes. +- `` reuses one geometry and disposes it on unmount, instead of allocating one per frame and leaking on toggle (#666). +- With `frameloop="demand"`, the canvas is invalidated once per frame while any non-fixed body is awake. +- The world is freed after the unmount commit, so child cleanups run against a live world and StrictMode remounts are safe. diff --git a/.changeset/soft-bodies.md b/.changeset/soft-bodies.md new file mode 100644 index 00000000..eaf6d70d --- /dev/null +++ b/.changeset/soft-bodies.md @@ -0,0 +1,19 @@ +--- +"@react-three/rapier": minor +--- + +Add soft bodies: `` and `useSoftBody`, built on Rapier 0.21's deformable bodies. + +Build cloth, rope and volumetric (jelly) bodies from the geometry of a `` or ``. Vertices are welded on position only, and the simulated positions are written into the geometry in the parent's local space once per rendered frame, interpolated with the same alpha as the rigid bodies (so cloth attached to an interpolated body stays on it), with normals and bounds recomputed. Tears are handled after every physics substep. Supports pinned particles, attachments to rigid bodies, stiffness/damping/mass, collisions with rigid bodies, and an `onTear` event. + +```tsx +import { SoftBody } from "@react-three/rapier/soft-bodies"; + + p.y > 0.49} stiffness={12}> + + + +; +``` + +The API lives in the `@react-three/rapier/soft-bodies` entry point (a separate bundle that imports core from `@react-three/rapier`). `RapierContext` gains two internal sets: `softBodyTearCallbacks`, which `` fills from Rapier's soft body tear events after each step, and `frameSyncCallbacks`, which `` calls once per frame with the interpolation alpha after writing the rigid bodies. diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..a91a6fe7 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,5 @@ +# Bulk formatting commits, skipped by `git blame` (GitHub reads this file automatically; +# locally: git config blame.ignoreRevsFile .git-blame-ignore-revs). + +# style: format with prettier 3 +8d7b56b1a0ce4d23fcb275dc72a108aa14c934bf diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 00000000..e12f4370 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,26 @@ +name: Setup +description: Enable Corepack (Yarn 4), set up Node.js with the Yarn cache and install dependencies + +inputs: + node-version: + description: Node.js version + default: 22.x + +runs: + using: composite + steps: + # Corepack must be enabled before setup-node so its Yarn cache step finds Yarn 4 + # (the `packageManager` field), not the runner's global Yarn 1. + - name: Enable Corepack + shell: bash + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: ${{ inputs.node-version }} + cache: yarn + + - name: Install dependencies + shell: bash + run: yarn install --immutable diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5d6f4795..baee81c8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,6 +6,39 @@ version: 2 updates: - package-ecosystem: "npm" - directory: "/packages/react-three-rapier" + directory: "/" # yarn workspaces are discovered from the root schedule: interval: "weekly" + groups: + minor-and-patch: + applies-to: version-updates + update-types: + - "minor" + - "patch" + ignore: + # three, react/react-dom and @react-three/* track r3f v10 / drei v11 prereleases; + # major bumps are deliberate, hand-verified upgrades. + - dependency-name: "three" + update-types: ["version-update:semver-major"] + - dependency-name: "@types/three" + update-types: ["version-update:semver-major"] + - dependency-name: "react" + update-types: ["version-update:semver-major"] + - dependency-name: "react-dom" + update-types: ["version-update:semver-major"] + - dependency-name: "@types/react" + update-types: ["version-update:semver-major"] + - dependency-name: "@types/react-dom" + update-types: ["version-update:semver-major"] + - dependency-name: "@react-three/*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + github-actions: + update-types: + - "minor" + - "patch" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 12861609..36c62c61 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,9 @@ ## Description + ### Type of change + - ๐Ÿ› Bug fix @@ -9,6 +11,7 @@ - ๐Ÿ“ฆ Other (tests, refactoring, docs, etc.) ### Checklist: + - [ ] ๐Ÿ” I have performed a self-review of my code @@ -16,4 +19,4 @@ - [ ] ๐Ÿ“— I have made corresponding changes to the documentation - [ ] โญ๏ธ My changes generate no new warnings - [ ] ๐Ÿงช I have added tests -- [ ] ๐ŸŸข All new and existing unit tests pass \ No newline at end of file +- [ ] ๐ŸŸข All new and existing unit tests pass diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..fd6681a5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: CI + +on: + pull_request: + branches: [main, next] + push: + branches: [main, next] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + typecheck: + name: Typecheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/setup + - name: Typecheck packages and tests + run: yarn typecheck + - name: Typecheck demo + run: yarn workspace demo typecheck + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/setup + # vitest resolves the workspace packages from src, so no build is needed first + - name: Test + run: yarn vitest run + + build: + name: Build & check packages + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/setup + - name: Build + run: yarn build + - name: publint + arethetypeswrong + run: yarn check:packages + + demo: + name: Build demo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/setup + - name: Build demo + run: yarn demo:build + + lint: + name: Lint & format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/setup + - name: Lint + run: yarn lint + - name: Format + if: ${{ !cancelled() }} + run: yarn format:check diff --git a/.github/workflows/r3-rapier-docs.yml b/.github/workflows/r3-rapier-docs.yml index 2f7ee258..6053fe3f 100644 --- a/.github/workflows/r3-rapier-docs.yml +++ b/.github/workflows/r3-rapier-docs.yml @@ -29,31 +29,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v7 - - name: Setup Node.js environment - uses: actions/setup-node@v3.6.0 - with: - node-version: 22.x - - - name: Enable corepack - run: corepack enable - - - name: Install Node Dependencies - run: yarn --immutable + - uses: ./.github/actions/setup - name: Run typedoc run: yarn docs:build - name: Setup Pages - uses: actions/configure-pages@v2 + uses: actions/configure-pages@v6 - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: # Upload docs folder path: "./packages/react-three-rapier/docs" - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbce94ad..e7a2ed1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,44 @@ name: Release +# `main` publishes stable releases. `next` is the 3.0 integration branch and is in changesets +# pre mode (.changeset/pre.json, `yarn prerelease:enter`), so the same flow there opens a +# "Version Packages (alpha)" PR into `next` (the action appends the pre tag) and publishes `x.y.z-alpha.n` under the `alpha` +# dist-tag, leaving `latest` untouched. Run `yarn prerelease:exit` before merging `next` into +# `main`. on: push: branches: - main + - next concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: release: name: Release + if: github.repository_owner == 'pmndrs' runs-on: ubuntu-latest + permissions: + contents: write # changesets pushes the version branch + pull-requests: write # ...and opens the "Version Packages" PR + id-token: write # npm provenance / trusted publishing (OIDC) steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v7 - - name: Setup Node.js 22.x - uses: actions/setup-node@v3 - with: - node-version: 22 - - - name: Install Dependencies - run: yarn --immutable + - uses: ./.github/actions/setup - name: Create Release Pull Request or Publish to npm id: changesets uses: changesets/action@v1 with: - publish: yarn release + version: yarn changeset:version + publish: yarn changeset:publish commit: "chore(changeset): release packages" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # This is a Yarn 4 workspace, so changesets publishes with `yarn npm publish`, which + # reads YARN_NPM_AUTH_TOKEN rather than NPM_TOKEN. NPM_TOKEN is kept because + # changesets/action uses it to write the .npmrc it checks registry state with. + YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index b951d700..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Runs Vitest - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Setup Node.js 22.x - uses: actions/setup-node@v3 - with: - node-version: 22 - - - name: Enable corepack - run: corepack enable - - - name: Install deps - run: yarn --immutable - - # See if the build runs - - name: Run build - run: yarn build - - - name: Run tests - run: npm run test diff --git a/.gitignore b/.gitignore index f246032b..e241d678 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,9 @@ dist packages/react-three-rapier/docs -.yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/.nvmrc b/.nvmrc index 33206cba..a3745049 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v22.0.0 +v22.22.2 diff --git a/.prettierignore b/.prettierignore index 8ec7bfa0..932e2a43 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,10 @@ -dist -docs -readme.md \ No newline at end of file +**/dist +**/node_modules +**/coverage +**/docs/out +packages/react-three-rapier/docs +.yarn +yarn.lock +.changeset/**/*.md +CHANGELOG.md +readme.md diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..bb684893 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/.yarnrc.yml b/.yarnrc.yml index 3186f3f0..063ed601 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1 +1,8 @@ +approvedGitRepositories: + - "**" + +enableScripts: true + nodeLinker: node-modules + +npmMinimalAgeGate: 0 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 00000000..9b7c21e2 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,93 @@ +# Development + +This repository is a Yarn workspace monorepo: + +- `packages/react-three-rapier` - `@react-three/rapier` +- `packages/react-three-rapier-addons` - `@react-three/rapier-addons` +- `demo` - the examples app (Vite, private, not published) + +## Setup + +- **Node 22** (see `.nvmrc`; tooling needs 22.22.2 or newer). +- **Yarn 4**, provided by Corepack through the `packageManager` field. Enable Corepack once: + +```sh +corepack enable +yarn install +``` + +Installing also sets up the git hooks (`simple-git-hooks`): commit messages are checked by +commitlint (conventional commits), and pre-commit runs `yarn typecheck`. + +## Commands + +| Command | What it does | +| --------------------- | ------------------------------------------------------------------------------- | +| `yarn build` | Build every package into its `dist/` (see "Build" below) | +| `yarn typecheck` | `tsc` over the packages' `src` and `tests` | +| `yarn test` | Typecheck, then run the vitest suite once | +| `yarn vitest` | Vitest in watch mode (`yarn test:ui` for the UI) | +| `yarn check:packages` | `publint --strict` and `attw --pack` for each package (run `yarn build` first) | +| `yarn lint` | ESLint (flat config in `eslint.config.mjs`) | +| `yarn format` | Prettier, writing changes (`yarn format:check` only checks) | +| `yarn demo` | Start the demo dev server | +| `yarn demo:build` | Typecheck and build the demo | +| `yarn ci` | Typecheck, build, package checks and tests - roughly what CI runs | +| `yarn changeset` | Add a changeset describing your change (needed for anything that gets released) | + +Tests, the root typecheck and the demo all resolve `@react-three/rapier` and +`@react-three/rapier-addons` to the workspace **sources** (vitest aliases, Vite aliases and +tsconfig `paths`), so none of them needs a build first, and the demo hot-reloads library +changes. + +## Demo app and examples + +The demo runs on **WebGPU by default** when the browser has `navigator.gpu`, otherwise WebGL. +Use the WebGPU / WebGL buttons in the sidebar, or `?renderer=webgl|webgpu` in the URL (the +choice is also remembered in localStorage). Switching reloads the page. Examples must work on +both renderers; if one genuinely cannot (custom `ShaderMaterial`, `onBeforeCompile`, troika +``, postprocessing...) mark it `webglOnly` (below) and the demo shows a notice on WebGPU. + +**Adding an example needs no edits to `App.tsx`.** Routes are discovered with `import.meta.glob` +(`demo/src/examples.ts`) and loaded lazily: + +- Create `demo/src/examples//index.ts(x)` whose **default export** is the example + component. The route is `/` and the sidebar label is the humanised folder name. +- Optionally export `meta` from the same module: `{ title?, description?, webglOnly? }` + (`export { default, meta } from "./MyExample"`). It is shown in a header while the example is open. +- A folder **without** an `index` still registers: every `*Example*.tsx` file in it becomes a + route (`/` for a single file, `//` for several), using the + default export or the first exported capitalised function as the component. +- Examples can use `useDemo()` and `Demo` from `../../App` (re-exported from `demo/src/demo-context.ts`). + +## Build + +Each package's `yarn build` runs `tsc -p tsconfig.build.json` (typecheck + per-file +declarations in `dist/types`) and then rollup with the shared config in +`scripts/rollup-library.mjs`: esbuild produces `dist/.mjs` and `dist/.cjs`, and +rollup-plugin-dts rolls the declarations into `dist/.d.ts`, `.d.mts` and `.d.cts`. + +Entry points are read from the package's `exports` map. `@react-three/rapier/soft-bodies` is the +reference subpath entry: it has `src/soft-bodies/index.ts`, a `"./soft-bodies"` entry in +`exports` (same shape as `"."`), `"dist/soft-bodies.*"` in `files` and a `typesVersions` entry for +node10 resolution. A new subpath needs the same four things. Subpath entries must import core through +`../index`, which the build rewrites to `@react-three/rapier` so core is never bundled twice. + +## Linting and formatting + +ESLint 9 (flat config) and Prettier 3 are configured, the codebase is formatted, and it has no +lint errors. CI runs `yarn lint` and `yarn format:check` as a blocking job, so run `yarn format` +before pushing. Lint warnings (unused variables, hook dependencies) don't fail the build, but +avoid adding new ones. The one-off Prettier 3 reformat is listed in `.git-blame-ignore-revs` +(GitHub skips it automatically; set `blame.ignoreRevsFile` to skip it locally). + +## Releases + +Releases are managed with [changesets](https://github.com/changesets/changesets) and +`.github/workflows/release.yml`, which opens a "Version Packages" PR and publishes when it is +merged. + +- `main` publishes stable versions. +- `next` (the 3.0 integration branch) is in prerelease mode (`.changeset/pre.json`): it publishes + `3.0.0-alpha.n` under the `alpha` dist-tag. Run `yarn prerelease:exit` before merging `next` + into `main`. diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 5925d264..00000000 --- a/babel.config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - presets: [ - [ - "@babel/preset-env", - { - bugfixes: true, - targets: { - esmodules: true - } - } - ], - "@babel/preset-react", - "@babel/preset-typescript" - ] -}; diff --git a/demo/index.html b/demo/index.html index 5986aabf..f119e4ee 100644 --- a/demo/index.html +++ b/demo/index.html @@ -1,4 +1,4 @@ - + diff --git a/demo/package.json b/demo/package.json index 3dc431b8..6dc1cb43 100644 --- a/demo/package.json +++ b/demo/package.json @@ -5,28 +5,28 @@ "license": "MIT", "scripts": { "dev": "vite", + "typecheck": "tsc", "build": "tsc && vite build", "preview": "vite preview" }, "dependencies": { "@react-three/csg": "3.3.0", - "@react-three/drei": "10.0.1", - "@react-three/fiber": "9.0.4", - "@react-three/rapier": "2.2.0", - "@react-three/rapier-addons": "5.0.0", - "@types/three": "0.172.0", + "@react-three/drei": "11.0.0-alpha.7", + "@react-three/fiber": "10.0.0-canary.a056612", + "@react-three/rapier": "workspace:*", + "@react-three/rapier-addons": "workspace:*", + "@types/three": "0.186.0", "leva": "0.10.0", - "r3f-perf": "7.2.3", - "react": "19.0.0", - "react-dom": "19.0.0", + "react": "19.2.8", + "react-dom": "19.2.8", "react-router-dom": "7.1.3", - "three": "0.172.0" + "three": "0.186.1" }, "devDependencies": { - "@types/react": "19.0.8", - "@types/react-dom": "19.0.3", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.7", "@vitejs/plugin-react": "4.3.4", - "typescript": "5.7.3", + "typescript": "5.9.3", "vite": "6.0.11" }, "repository": "https://github.com/pmndrs/react-three-rapier/tree/master/demo" diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 403e858e..8f189737 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -1,67 +1,50 @@ -import { Box, Environment, OrbitControls } from "@react-three/drei"; -import { Canvas } from "@react-three/fiber"; +import { Box, Environment, OrbitControls, Stats } from "@react-three/drei"; +import { Canvas, useStore } from "@react-three/fiber"; import { Physics, RigidBody } from "@react-three/rapier"; -import { Perf } from "r3f-perf"; import { - JSX, ReactNode, StrictMode, Suspense, - createContext, - useContext, + useEffect, + useLayoutEffect, + useMemo, useRef, useState } from "react"; -import { NavLink, NavLinkProps, Route, Routes } from "react-router-dom"; -import { ActiveCollisionTypesExample } from "./examples/active-collision-types/ActiveCollisionTypesExample"; -import { AllCollidersExample } from "./examples/all-colliders/AllCollidersExample"; -import { AllShapesExample } from "./examples/all-shapes/AllShapesExample"; -import { ApiUsage } from "./examples/api-usage/ApiUsageExample"; -import { AttractorExample } from "./examples/attractors/AttractorsExample"; -import { Car } from "./examples/car/CarExample"; -import { Cluster } from "./examples/cluster/ClusterExample"; -import { Colliders } from "./examples/colliders/CollidersExample"; -import { CollisionEventsExample } from "./examples/collision-events/CollisionEventsExample"; -import { ComponentsExample } from "./examples/components/ComponentsExample"; -import { ContactForceEventsExample } from "./examples/contact-force-events/ContactForceEventsExample"; -import { ContactSkinExample } from "./examples/contact-skin/ContactSkinExample"; -import { CradleExample } from "./examples/cradle/CradleExample"; -import { Damping } from "./examples/damping/DampingExample"; -import { DynamicTypeChangeExample } from "./examples/dynamic-type-change/DynamicTypeChangeExample"; -import { ImmutablePropsExample } from "./examples/immutable-props/ImmutablePropsExample"; -import { InstancedMeshes } from "./examples/instanced-meshes/InstancedMeshesExample"; -import { InstancedMeshesCompound } from "./examples/instances-meshes-compound/InstancedMeshesCompoundExample"; -import { Joints } from "./examples/joints/JointsExample"; -import { Kinematics } from "./examples/kinematics/KinematicsExample"; -import { LockedTransformsExample } from "./examples/locked-transforms/LockedTransformsExample"; -import { ManualStepExample } from "./examples/manual-step/ManualStepExamples"; -import { MeshColliderTest } from "./examples/mesh-collider-test/MeshColliderExample"; -import { OneWayPlatform } from "./examples/one-way-platform/OneWayPlatform"; -import { PerformanceExample } from "./examples/performance/PeformanceExample"; -import Shapes from "./examples/plinko/ShapesExample"; -import { RopeJointExample } from "./examples/rope-joint/RopeJointExample"; -import { SensorsExample } from "./examples/sensors/SensorsExample"; -import { SnapshotExample } from "./examples/snapshot/SnapshotExample"; -import { SpringExample } from "./examples/spring/SpringExample"; -import { StutteringExample } from "./examples/stuttering/StutteringExample"; -import { Transforms } from "./examples/transforms/TransformsExample"; -import { OrbitControls as OrbitControlsImpl } from "three-stdlib"; +import { + Navigate, + NavLink, + NavLinkProps, + Route, + Routes, + useLocation +} from "react-router-dom"; +import { demoContext } from "./demo-context"; +import { DEFAULT_EXAMPLE, ExampleMeta, examples } from "./examples"; +import { getRenderer, hasWebGPU, switchRenderer } from "./renderer"; +import type { OrbitControls as OrbitControlsImpl } from "three/addons/controls/OrbitControls.js"; import { useResetOrbitControls } from "./hooks/use-reset-orbit-controls"; -type DemoContext = { - setDebug: (f: boolean) => void; - setPaused: (f: boolean) => void; - setCameraEnabled: (f: boolean) => void; - orbitControlRef: React.RefObject; -}; - -const demoContext = createContext>({}); +// Kept here so examples can keep importing them from "../../App" +export { useDemo } from "./demo-context"; +export type { Demo, DemoContext } from "./demo-context"; -export const useDemo = () => useContext(demoContext); - -export interface Demo { - (props: { children?: ReactNode }): JSX.Element; -} +/** + * r3f v10 leaves `state.gl` null on WebGPU roots (it is the legacy WebGL alias of + * `state.renderer`), but drei 11 alpha still reads `gl.domElement` () and + * `gl.initTexture` (useTexture). Point it at the WebGPU renderer so those keep working. + * Remove once drei uses `state.renderer`. + */ +const GlShim = ({ children }: { children: ReactNode }) => { + const store = useStore(); + const [ready, setReady] = useState(false); + useLayoutEffect(() => { + const { gl, renderer } = store.getState(); + if (!gl && renderer) store.setState({ gl: renderer } as never); + setReady(true); + }, [store]); + return ready ? <>{children} : null; +}; const Floor = () => { return ( @@ -78,41 +61,6 @@ const Floor = () => { ); }; -const routes: Record = { - "": , - joints: , - components: , - cradle: , - transforms: , - cluster: , - "all-shapes": , - attractors: , - car: , - "api-usage": , - kinematics: , - "mesh-collider-test": , - colliders: , - "instanced-meshes": , - damping: , - "instanced-meshes-compound": , - "all-colliders": , - "collision-events": , - "contact-force-events": , - sensors: , - "manual-step": , - "locked-transforms": , - performance: , - "dynamic-type-changes": , - stuttering: , - "immutable-props": , - snapshot: , - spring: , - "rope-joint": , - "active-collision-types": , - "contact-skin": , - "one-way-platform": -}; - export const App = () => { const [debug, setDebug] = useState(false); const [perf, setPerf] = useState(false); @@ -121,6 +69,19 @@ export const App = () => { const [physicsKey, setPhysicsKey] = useState(0); const [cameraEnabled, setCameraEnabled] = useState(true); const orbitControlRef = useRef(null!); + const renderer = useMemo(getRenderer, []); + const { pathname } = useLocation(); + const [meta, setMeta] = useState({}); + + const current = examples.find((e) => "/" + e.path === pathname); + useEffect(() => { + let live = true; + setMeta({}); + current?.loadMeta().then((m) => live && setMeta(m)); + return () => { + live = false; + }; + }, [current]); useResetOrbitControls(); @@ -148,52 +109,96 @@ export const App = () => { }} /> - + - - - + + + + - + - - - {Object.keys(routes).map((key, index, array) => ( - - ))} - - + + + + } + /> + {examples.map(({ path, Component }) => ( + } /> + ))} + + + - + - {perf && } - + {/* r3f-perf does not support r3f v10 yet; drei stands in */} + {perf && } + + + {(meta.title || + meta.description || + (meta.webglOnly && renderer === "webgpu")) && ( +
+ {meta.title && {meta.title}} + {meta.description &&
{meta.description}
} + {meta.webglOnly && renderer === "webgpu" && ( +
+ WebGL only: this example does not support the WebGPU renderer. + Switch to WebGL to see it working. +
+ )} +
+ )} +
{ r3/rapier demos +
+ switchRenderer("webgpu")} + /> + switchRenderer("webgl")} + /> +
+
{ />
- {Object.keys(routes).map((key) => ( - - {key.replace(/-/g, " ") || "Plinko"} + {examples.map(({ path, title }) => ( + + {title} ))}
@@ -276,13 +295,17 @@ const Link = (props: NavLinkProps) => { const ToggleButton = ({ label, value, + disabled, onClick }: { label: string; value: boolean; + disabled?: boolean; onClick(): void; }) => (