Skip to content

Commit 14b4f37

Browse files
authored
Add new physics cluster example (#125)
1 parent e8be197 commit 14b4f37

File tree

5 files changed

+146
-4
lines changed

5 files changed

+146
-4
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Script } from 'playcanvas';
2+
3+
export class Gravity extends Script {
4+
update(dt) {
5+
const { x, y, z } = this.entity.getPosition();
6+
this.entity.rigidbody.applyForce(-x, -y, -z);
7+
}
8+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Script } from 'playcanvas';
2+
3+
/**
4+
* @import { CameraComponent } from 'playcanvas';
5+
*/
6+
7+
export class PhysicalPointer extends Script {
8+
initialize() {
9+
const canvas = this.app.graphicsDevice.canvas;
10+
canvas.addEventListener('pointermove', (event) => {
11+
/** @type {CameraComponent} */
12+
const camera = this.app.root.findComponent('camera');
13+
const { z } = camera.entity.getPosition();
14+
const { x, y } = camera.screenToWorld(event.clientX, event.clientY, z);
15+
this.entity.setPosition(x, y, 0);
16+
});
17+
}
18+
}

examples/js/example-list.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const examples = [
77
{ name: 'Gaussian Splatting', path: 'splat.html' },
88
{ name: 'GLB Loader', path: 'glb.html' },
99
{ name: 'Physics', path: 'physics.html' },
10+
{ name: 'Physics Cluster', path: 'physics-cluster.html' },
1011
{ name: 'Positional Sound', path: 'positional-sound.html' },
1112
{ name: 'Shoe Configurator', path: 'shoe-configurator.html' },
1213
{ name: 'Solar System', path: 'solar-system.html' },

examples/physics-cluster.html

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6+
<title>PlayCanvas Web Components - Physics Cluster</title>
7+
<script type="importmap">
8+
{
9+
"imports": {
10+
"playcanvas": "../node_modules/playcanvas/build/playcanvas.mjs"
11+
}
12+
}
13+
</script>
14+
<script type="module" src="../dist/pwc.mjs"></script>
15+
<link rel="stylesheet" href="css/example.css">
16+
</head>
17+
<body>
18+
<pc-app>
19+
<!-- Modules -->
20+
<pc-module name="Ammo" glue="modules/ammo/ammo.wasm.js" wasm="modules/ammo/ammo.wasm.wasm" fallback="modules/ammo/ammo.js"></pc-module>
21+
<!-- Assets -->
22+
<pc-asset src="assets/skies/octagon-lamps-photo-studio-2k.hdr" id="studio"></pc-asset>
23+
<pc-asset src="assets/scripts/gravity.mjs"></pc-asset>
24+
<pc-asset src="assets/scripts/physical-pointer.mjs"></pc-asset>
25+
<pc-material id="hotpink" diffuse="hotpink"></pc-material>
26+
<pc-material id="mediumseagreen" diffuse="mediumseagreen"></pc-material>
27+
<!-- Scene -->
28+
<pc-scene gravity="0 0 0">
29+
<!-- Sky -->
30+
<pc-sky asset="studio" type="none" lighting></pc-sky>
31+
<!-- Camera -->
32+
<pc-entity name="camera" position="0 0 5">
33+
<pc-camera clear-color="#cccccc" tonemap="neutral"></pc-camera>
34+
</pc-entity>
35+
<!-- Physical Sphere Template -->
36+
<template id="sphere-template">
37+
<pc-entity>
38+
<pc-render type="sphere" material="mediumseagreen"></pc-render>
39+
<pc-collision type="sphere"></pc-collision>
40+
<pc-rigidbody type="dynamic" angular-damping="0.9" linear-damping="0.9" restitution="0.9"></pc-rigidbody>
41+
<pc-scripts>
42+
<pc-script name="gravity"></pc-script>
43+
</pc-scripts>
44+
</pc-entity>
45+
</template>
46+
<!-- Physical Pointer -->
47+
<pc-entity name="pointer" position="0 0 0">
48+
<pc-entity scale="0.5 0.5 0.5">
49+
<pc-render type="sphere"></pc-render>
50+
</pc-entity>
51+
<pc-light type="omni" cast-shadows></pc-light>
52+
<pc-collision type="sphere" radius="0.25"></pc-collision>
53+
<pc-rigidbody type="kinematic" restitution="0.9"></pc-rigidbody>
54+
<pc-scripts>
55+
<pc-script name="physicalPointer"></pc-script>
56+
</pc-scripts>
57+
</pc-entity>
58+
</pc-scene>
59+
</pc-app>
60+
<script>
61+
document.addEventListener('DOMContentLoaded', () => {
62+
const scene = document.querySelector('pc-scene');
63+
const sphereTemplate = document.getElementById('sphere-template');
64+
65+
// Clone and append 40 spheres
66+
for (let i = 0; i < 40; i++) {
67+
// Clone the sphere template
68+
const clone = document.importNode(sphereTemplate.content, true);
69+
const sphereEntity = clone.querySelector('pc-entity');
70+
71+
// Set a random start position
72+
const x = (Math.random() - 0.5) * 10;
73+
const y = (Math.random() - 0.5) * 10;
74+
const z = (Math.random() - 0.5) * 10;
75+
sphereEntity.setAttribute('position', `${x} ${y} ${z}`);
76+
77+
// Append the clone to the scene
78+
scene.appendChild(clone);
79+
}
80+
});
81+
</script>
82+
<script type="module" src="js/example.mjs"></script>
83+
</body>
84+
</html>

src/scene.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Color, Scene } from 'playcanvas';
1+
import { Color, Scene, Vec3 } from 'playcanvas';
22

3+
import { AppElement } from './app';
34
import { AsyncElement } from './async-element';
4-
import { parseColor } from './utils';
5+
import { parseColor, parseVec3 } from './utils';
56

67
/**
78
* The SceneElement interface provides properties and methods for manipulating
@@ -35,6 +36,11 @@ class SceneElement extends AsyncElement {
3536
*/
3637
private _fogEnd = 1000;
3738

39+
/**
40+
* The gravity of the scene.
41+
*/
42+
private _gravity = new Vec3(0, -9.81, 0);
43+
3844
/**
3945
* The PlayCanvas scene instance.
4046
*/
@@ -56,7 +62,9 @@ class SceneElement extends AsyncElement {
5662
this.scene.fog.density = this._fogDensity;
5763
this.scene.fog.start = this._fogStart;
5864
this.scene.fog.end = this._fogEnd;
59-
// ... set other properties on the scene as well
65+
66+
const appElement = this.parentElement as AppElement;
67+
appElement.app!.systems.rigidbody!.gravity.copy(this._gravity);
6068
}
6169
}
6270

@@ -155,8 +163,28 @@ class SceneElement extends AsyncElement {
155163
return this._fogEnd;
156164
}
157165

166+
/**
167+
* Sets the gravity of the scene.
168+
* @param value - The gravity.
169+
*/
170+
set gravity(value: Vec3) {
171+
this._gravity = value;
172+
if (this.scene) {
173+
const appElement = this.parentElement as AppElement;
174+
appElement.app!.systems.rigidbody!.gravity.copy(value);
175+
}
176+
}
177+
178+
/**
179+
* Gets the gravity of the scene.
180+
* @returns The gravity.
181+
*/
182+
get gravity() {
183+
return this._gravity;
184+
}
185+
158186
static get observedAttributes() {
159-
return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end'];
187+
return ['fog', 'fog-color', 'fog-density', 'fog-start', 'fog-end', 'gravity'];
160188
}
161189

162190
attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -176,6 +204,9 @@ class SceneElement extends AsyncElement {
176204
case 'fog-end':
177205
this.fogEnd = parseFloat(newValue);
178206
break;
207+
case 'gravity':
208+
this.gravity = parseVec3(newValue);
209+
break;
179210
// ... handle other attributes as well
180211
}
181212
}

0 commit comments

Comments
 (0)