Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/data-gpu-samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"type": "module",
"private": true,
"scripts": {
"predev": "pnpm --filter @adobe/data-gpu build",
"dev": "vite",
"dev:all": "pnpm --parallel --filter @adobe/data --filter @adobe/data-gpu --filter data-gpu-samples run dev",
"dev:all": "pnpm --filter @adobe/data-gpu build && pnpm --parallel --filter @adobe/data --filter @adobe/data-gpu --filter data-gpu-samples run dev",
"build": "vite build"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import { Database } from "@adobe/data/ecs";
import { Vec3 } from "@adobe/data/math";
import { pbrRender, Model, Orbit } from "@adobe/data-gpu";
import { pbrIblRender, Model, Orbit } from "@adobe/data-gpu";

export const pbrIblInstancedPlugin = Database.Plugin.create({
extends: Database.Plugin.combine(pbrRender, Orbit.plugin),
extends: Database.Plugin.combine(pbrIblRender, Orbit.plugin),
transactions: {
initializeScene(t, args: {
modelUrl: string;
Expand All @@ -19,25 +19,25 @@ export const pbrIblInstancedPlugin = Database.Plugin.create({
environmentUrl: args.envUrl ?? t.resources.light.environmentUrl,
color: args.lightColor ?? t.resources.light.color,
};
const geoId = Model.plugin.transactions.insertGeometry(t, { modelUrl: args.modelUrl });
const meshId = Model.plugin.transactions.insertGltfMesh(t, { url: args.modelUrl });
const offset = (args.grid - 1) / 2;
for (let x = 0; x < args.grid; x++) {
for (let z = 0; z < args.grid; z++) {
Model.plugin.transactions.insertModel(t, {
geometry: geoId,
mesh: meshId,
position: [(x - offset) * args.spacing, 0, (z - offset) * args.spacing],
});
}
}
t.resources.orbit = {
...t.resources.orbit,
fitGeometry: geoId,
fitMesh: meshId,
fitRadiusOffset: offset * args.spacing,
fitRadiusFactor: 2,
fitHeightFactor: 0.5,
fitCenter: [0, 0, 0],
};
return geoId;
return meshId;
},
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
// © 2026 Adobe. MIT License. See /LICENSE for details.

import { Database } from "@adobe/data/ecs";
import { Vec3 } from "@adobe/data/math";
import { pbrRender, Model, Orbit } from "@adobe/data-gpu";
import { pbrIblRender, Model, Orbit } from "@adobe/data-gpu";

export const pbrModelIblPlugin = Database.Plugin.create({
extends: Database.Plugin.combine(pbrRender, Orbit.plugin),
extends: Database.Plugin.combine(pbrIblRender, Orbit.plugin),
transactions: {
initializeScene(t, args: {
modelUrl: string;
envUrl?: string;
lightColor?: Vec3;
lightColor?: readonly [number, number, number];
orbitFit?: { radiusFactor: number; heightFactor: number };
}): number {
t.resources.light = {
...t.resources.light,
environmentUrl: args.envUrl ?? t.resources.light.environmentUrl,
color: args.lightColor ?? t.resources.light.color,
};
const geoId = Model.plugin.transactions.insertGeometry(t, { modelUrl: args.modelUrl });
Model.plugin.transactions.insertModel(t, { geometry: geoId });
const meshId = Model.plugin.transactions.insertGltfMesh(t, { url: args.modelUrl });
Model.plugin.transactions.insertModel(t, { mesh: meshId });
t.resources.orbit = {
...t.resources.orbit,
fitGeometry: geoId,
fitMesh: meshId,
fitRadiusFactor: args.orbitFit?.radiusFactor ?? t.resources.orbit.fitRadiusFactor,
fitHeightFactor: args.orbitFit?.heightFactor ?? t.resources.orbit.fitHeightFactor,
};
return geoId;
return meshId;
},
},
});
Expand Down
33 changes: 10 additions & 23 deletions packages/data-gpu-samples/src/samples/ragdoll/ragdoll-service.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,35 @@
// © 2026 Adobe. MIT License. See /LICENSE for details.

import { Database } from "@adobe/data/ecs";
import { pbrRender, pbrSkinning, boneColliders, joltRagdoll, physicsRenderBridge, shapeGeometry, ragdollTrigger, rapierSolver, Model, Orbit } from "@adobe/data-gpu";
import { pbrFactorRender, requireMaterial, pbrSkinning, boneColliders, joltRagdoll, physicsRenderBridge, shapeGeometry, ragdollTrigger, rapierSolver, Model, Orbit } from "@adobe/data-gpu";

/**
* ragdoll — a rigged humanoid walks, then goes limp and collapses onto the floor.
* The same scene runs **side by side on both solvers** through a shared base
* (`ragdollScene`): per-bone capsules are auto-fitted from the skin and track the
* walk, then `triggerRagdoll` flips them to dynamic so the skinned mesh flops.
*
* The two panels use **different ragdoll backends** through the same scene +
* `ragdollTrigger`: Jolt runs its **native `Ragdoll`** (`joltRagdoll` — swing-twist
* limits, parent-child collision filtering, pose-driven), while Rapier runs **our
* generic `boneColliders`** (free-ball, since Rapier's binding has no cone joint).
*/
const ragdollScene = Database.Plugin.create({
extends: Database.Plugin.combine(pbrRender, pbrSkinning, shapeGeometry, physicsRenderBridge, ragdollTrigger, Orbit.plugin),
extends: Database.Plugin.combine(pbrFactorRender, pbrSkinning, shapeGeometry, physicsRenderBridge, ragdollTrigger, Orbit.plugin),
transactions: {
initializeScene(t, args: { modelUrl: string; envUrl?: string }): number {
t.resources.light = { ...t.resources.light, environmentUrl: args.envUrl ?? t.resources.light.environmentUrl, color: [0.55, 0.55, 0.55] };
const geoId = Model.plugin.transactions.insertGeometry(t, { modelUrl: args.modelUrl });
Model.plugin.transactions.insertModel(t, { geometry: geoId, position: [0, 0.9, 0] }); // lifted, so the ragdoll drops onto the floor
const meshId = Model.plugin.transactions.insertGltfMesh(t, { url: args.modelUrl });
Model.plugin.transactions.insertModel(t, { mesh: meshId, position: [0, 0.9, 0] });
t.archetypes.StaticCollider.insert({
colliderShape: "box", halfExtents: [4, 0.25, 4], material: t.resources.materials.stone,
colliderShape: "box", halfExtents: [4, 0.25, 4], material: requireMaterial(t, "stone"),
position: [0, -0.25, 0], rotation: [0, 0, 0, 1],
});
t.resources.orbit = { ...t.resources.orbit, center: [0, 0.4, 0], radius: 3.2, height: 1.4, autoSpinSpeed: 0.15 };
return geoId;
return meshId;
},
},
systems: {
// Start the model's first clip looping once skeleton + clips have loaded.
autoplayAnimation: {
schedule: { during: ["update"] },
create: db => {
const started = new Set<number>();
return () => {
for (const arch of db.store.queryArchetypes(["_skeletonJoints", "_skeletonGeometry"])) {
const ids = arch.columns.id, jc = arch.columns._skeletonJoints, gc = arch.columns._skeletonGeometry;
for (const arch of db.store.queryArchetypes(["_skeletonJoints", "_skeletonMesh"])) {
const ids = arch.columns.id, jc = arch.columns._skeletonJoints, mc = arch.columns._skeletonMesh;
for (let i = 0; i < arch.rowCount; i++) {
const skeleton = ids.get(i);
if (started.has(skeleton)) continue;
const clips = (db.store.read(gc.get(i)) as { _animationClipRefs?: number[] } | null)?._animationClipRefs ?? [];
const clips = (db.store.read(mc.get(i)) as { animationClipRefs?: number[] } | null)?.animationClipRefs ?? [];
if (clips.length === 0) continue;
db.store.archetypes.Animation.insert({
animationClipRef: clips[0], animationTargets: [...jc.get(i)],
Expand All @@ -53,7 +41,6 @@ const ragdollScene = Database.Plugin.create({
};
},
},
// Walk for a few seconds, then go limp (the active ragdoll backend handles it).
autoRagdoll: {
schedule: { during: ["update"] },
create: db => {
Expand All @@ -69,4 +56,4 @@ const ragdollScene = Database.Plugin.create({
});

export const ragdollRapierPlugin = Database.Plugin.combine(ragdollScene, boneColliders, rapierSolver);
export const ragdollJoltPlugin = Database.Plugin.combine(ragdollScene, joltRagdoll); // joltRagdoll brings joltSolver
export const ragdollJoltPlugin = Database.Plugin.combine(ragdollScene, joltRagdoll);
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { rigidStackShader } from "./rigid-stack-render.wgsl.js";
* The original lightweight debug renderer for rigid-stack — flat Lambertian,
* bodies tinted by their material's baseColorFactor and brightened by speed,
* geometry vertex-pulled from packed instance buffers. Kept as a selectable
* alternative to `pbrRender`: combine `rigidStackDebugRender` instead of
* `pbrRender` (and drop shapeGeometry/physicsRenderBridge) to use it.
* alternative to `pbrFactorRender`: combine `rigidStackDebugRender` instead of
* `pbrFactorRender` (and drop shapeGeometry/physicsRenderBridge) to use it.
*/
const POSITION_STRIDE = 12;
const CUBE_STRIDE = 24;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Database, type Entity } from "@adobe/data/ecs";
import { Quat } from "@adobe/data/math";
import { pbrRender, rapierSolver, joltSolver, shapeGeometry, physicsRenderBridge, modelCollider, jointData, ColliderShape, Orbit } from "@adobe/data-gpu";
import { pbrFactorRender, requireMaterial, rapierSolver, joltSolver, shapeGeometry, physicsRenderBridge, modelCollider, jointData, ColliderShape, Orbit, standardMaterialNames, Model } from "@adobe/data-gpu";

// Studio HDR for IBL © Poly Haven, CC0.
const ENV_URL = "https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/studio_small_09_1k.hdr";
Expand Down Expand Up @@ -86,7 +86,7 @@ const DROPS: Drop[] = (() => {
* side. The drop sequence is deterministic (seeded), so both see it identically.
*/
const rigidStackScene = Database.Plugin.create({
extends: Database.Plugin.combine(pbrRender, shapeGeometry, physicsRenderBridge, modelCollider, jointData, Orbit.plugin),
extends: Database.Plugin.combine(pbrFactorRender, shapeGeometry, physicsRenderBridge, modelCollider, jointData, Orbit.plugin),
resources: {
_spawnAccum: { default: 0 as number, transient: true },
_spawnElapsed: { default: 0 as number, transient: true },
Expand All @@ -107,7 +107,7 @@ const rigidStackScene = Database.Plugin.create({
// Stone bin: a floor slab (top face at y = 0) and four walls, all
// immovable StaticCollider boxes. The render bridge gives them
// geometry once the shape meshes load — no separate render-only prop.
const stone = t.resources.materials.stone;
const stone = requireMaterial(t, "stone");
const wall = (position: [number, number, number], halfExtents: [number, number, number]) =>
t.archetypes.StaticCollider.insert({ colliderShape: "box", halfExtents, material: stone, position, rotation: IDENTITY });
wall([0, -0.5, 0], [BIN + 1, 0.5, BIN + 1]); // floor slab (top at y = 0)
Expand All @@ -120,7 +120,7 @@ const rigidStackScene = Database.Plugin.create({
// dropped bodies land on it and slide down. World-space verts; up-facing
// winding so it renders (front faces) and collides (trimesh is two-sided).
t.archetypes.MeshCollider.insert({
colliderShape: "mesh", halfExtents: [0, 0, 0], material: t.resources.materials.steel,
colliderShape: "mesh", halfExtents: [0, 0, 0], material: requireMaterial(t, "steel"),
position: [0, 0, 0], rotation: IDENTITY,
colliderMesh: {
// sloped quad in the left zone: high at the −x wall, low toward centre
Expand All @@ -131,7 +131,7 @@ const rigidStackScene = Database.Plugin.create({
// Dynamic block stack: a grid of unit cubes resting on the floor.
// A small gap on every axis avoids initial face-coincidence
// (degenerate SAT normals); they settle into contact.
const wood = t.resources.materials.wood;
const wood = requireMaterial(t, "wood");
const GAP = 1.04;
const x0 = -(STACK_W - 1) / 2 * GAP, z0 = -(STACK_D - 1) / 2 * GAP;
for (let y = 0; y < STACK_H; y++) {
Expand All @@ -149,19 +149,19 @@ const rigidStackScene = Database.Plugin.create({
// its pose is authored each frame by the `sweep` system; the solver moves
// it as a kinematic body that pushes the dynamics but is never pushed back.
t.resources._sweeper = t.archetypes.RigidBody.insert({
bodyType: "kinematic", colliderShape: "box", halfExtents: [0.4, 1.0, BIN - 1], material: t.resources.materials.steel,
bodyType: "kinematic", colliderShape: "box", halfExtents: [0.4, 1.0, BIN - 1], material: requireMaterial(t, "steel"),
position: [SWEEP_CX - SWEEP_AMP, SWEEP_Y, 0], rotation: IDENTITY, linearVelocity: [0, 0, 0], angularVelocity: [0, 0, 0],
});
// A downloaded glTF model dropped as dynamic bodies: it renders in full
// detail but collides as a convex hull auto-generated from its mesh
// (colliderShape "hull" + no collision data ⇒ modelCollider fills it). One
// shared Geometry, three staggered instances. (To hand-author the collider
// instead, pass `convexPoints`/`colliderMesh` here and generation is skipped.)
const helmet = t.archetypes.Geometry.insert({ modelUrl: HELMET_URL });
const helmet = Model.plugin.transactions.insertGltfMesh(t, { url: HELMET_URL });
for (let i = 0; i < 3; i++) {
t.archetypes.ModelBody.insert({
geometry: helmet, scale: [1.5, 1.5, 1.5], visible: true, parent: 0,
bodyType: "dynamic", colliderShape: "hull", halfExtents: [0, 0, 0], material: t.resources.materials.steel,
mesh: helmet, scale: [1.5, 1.5, 1.5], visible: true, parent: 0,
bodyType: "dynamic", colliderShape: "hull", halfExtents: [0, 0, 0], material: requireMaterial(t, "steel"),
position: [SPAWN_CX - 2 + i * 2, 15 + i * 5, -1 + i],
rotation: randomQuat(seededRng(0x5eed + i)), linearVelocity: [0, 0, 0], angularVelocity: [0, 0, 0],
});
Expand All @@ -172,7 +172,7 @@ const rigidStackScene = Database.Plugin.create({
// anchors coincide in world space, so the chain forms taut.
const LINK_HY = 0.4, LINK_R = 0.22, LINK_END = LINK_HY + LINK_R, LINK_LEN = 2 * LINK_END;
const CX = SWEEP_CX, CZ = BIN - 3, ANCHOR_Y = 9, ANCHOR_HH = 0.25;
const steel = t.resources.materials.steel;
const steel = requireMaterial(t, "steel");
const anchor = t.archetypes.StaticCollider.insert({
colliderShape: "box", halfExtents: [0.3, ANCHOR_HH, 0.3], material: steel,
position: [CX, ANCHOR_Y, CZ], rotation: IDENTITY,
Expand Down Expand Up @@ -213,7 +213,7 @@ const rigidStackScene = Database.Plugin.create({
},
spawnBody(t, args: { index: number }) {
const d = DROPS[args.index];
const material = Object.values(t.resources.materials)[args.index % Object.keys(t.resources.materials).length];
const material = requireMaterial(t, standardMaterialNames[args.index % standardMaterialNames.length]);
const common = {
bodyType: "dynamic" as const, colliderShape: d.shape, halfExtents: d.he, material,
position: d.pos, rotation: d.quat, linearVelocity: [0, 0, 0] as [number, number, number], angularVelocity: [0, 0, 0] as [number, number, number],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import { Database } from "@adobe/data/ecs";
import { Vec3 } from "@adobe/data/math";
import { pbrRender, pbrSkinning, Model, Orbit } from "@adobe/data-gpu";
import { pbrIblRender, pbrSkinning, Model, Orbit } from "@adobe/data-gpu";

export const skinnedFoxPlugin = Database.Plugin.create({
extends: Database.Plugin.combine(pbrRender, pbrSkinning, Orbit.plugin),
extends: Database.Plugin.combine(pbrIblRender, pbrSkinning, Orbit.plugin),
transactions: {
initializeScene(t, args: {
modelUrl: string;
Expand All @@ -18,15 +18,15 @@ export const skinnedFoxPlugin = Database.Plugin.create({
environmentUrl: args.envUrl ?? t.resources.light.environmentUrl,
color: args.lightColor ?? t.resources.light.color,
};
const geoId = Model.plugin.transactions.insertGeometry(t, { modelUrl: args.modelUrl });
Model.plugin.transactions.insertModel(t, { geometry: geoId });
const meshId = Model.plugin.transactions.insertGltfMesh(t, { url: args.modelUrl });
Model.plugin.transactions.insertModel(t, { mesh: meshId });
t.resources.orbit = {
...t.resources.orbit,
fitGeometry: geoId,
fitMesh: meshId,
fitRadiusFactor: args.orbitFit?.radiusFactor ?? t.resources.orbit.fitRadiusFactor,
fitHeightFactor: args.orbitFit?.heightFactor ?? t.resources.orbit.fitHeightFactor,
};
return geoId;
return meshId;
},
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
animation,
Model,
Orbit,
pbrRender,
pbrIblRender,
picking,
type AnimationTrack,
} from "@adobe/data-gpu";
Expand Down Expand Up @@ -40,7 +40,7 @@ interface PlanetSpec {
}

export const solarSystemPlugin = Database.Plugin.create({
extends: Database.Plugin.combine(pbrRender, sphere, animation, Orbit.plugin, picking),
extends: Database.Plugin.combine(pbrIblRender, sphere, animation, Orbit.plugin, picking),
transactions: {
initializeScene(t) {
t.resources.orbit = {
Expand All @@ -61,7 +61,7 @@ export const solarSystemPlugin = Database.Plugin.create({
segments: 64,
});
const planetId = Model.plugin.transactions.insertModel(t, {
geometry: geo,
mesh: geo,
position: spec.position,
scale: spec.scale,
parent: spec.parent ?? 0,
Expand Down Expand Up @@ -120,8 +120,8 @@ export const solarSystemPlugin = Database.Plugin.create({
pickAndFit(db, args: { x: number; y: number }) {
const hit = db.actions.pickFromScreen(args);
if (!hit) return;
const geo = db.read(hit.entity)?.geometry;
if (geo) db.transactions.setOrbit({ fitGeometry: geo });
const mesh = db.read(hit.entity)?.mesh;
if (mesh) db.transactions.setOrbit({ fitMesh: mesh });
},
},
});
Expand Down
Loading
Loading