create-playcanvas 0.2.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +51 -47
  2. package/dist/index.mjs +49 -46
  3. package/package.json +54 -49
  4. package/templates/_shared/model-viewer/public/playcanvas-cube.glb +0 -0
  5. package/templates/engine/base/README.md +38 -0
  6. package/templates/engine/base/eslint.config.mjs +3 -0
  7. package/templates/engine/base/package.json +30 -0
  8. package/templates/engine/base/prettier.config.mjs +1 -0
  9. package/templates/engine/base/src/vite-env.d.ts +19 -0
  10. package/templates/engine/base/tsconfig.json +25 -0
  11. package/templates/engine/interactive-sphere/index.html +13 -0
  12. package/templates/engine/interactive-sphere/src/App.ts +180 -0
  13. package/templates/{vanilla-ts → engine/interactive-sphere}/src/main.ts +8 -8
  14. package/templates/engine/interactive-sphere/src/style.css +107 -0
  15. package/templates/engine/interactive-sphere/src/utils.ts +16 -0
  16. package/templates/engine/model-viewer/index.html +13 -0
  17. package/templates/engine/model-viewer/src/main.ts +81 -0
  18. package/templates/engine/model-viewer/src/style.css +8 -0
  19. package/templates/engine/spinning-cube/index.html +13 -0
  20. package/templates/engine/spinning-cube/src/main.ts +65 -0
  21. package/templates/engine/spinning-cube/src/style.css +8 -0
  22. package/templates/engine/splat-viewer/index.html +13 -0
  23. package/templates/engine/splat-viewer/src/main.ts +74 -0
  24. package/templates/engine/splat-viewer/src/style.css +8 -0
  25. package/templates/index.ts +47 -57
  26. package/templates/react/base/README.md +38 -0
  27. package/templates/react/base/eslint.config.mjs +14 -0
  28. package/templates/react/base/package.json +36 -0
  29. package/templates/react/base/prettier.config.mjs +1 -0
  30. package/templates/react/base/src/vite-env.d.ts +19 -0
  31. package/templates/react/base/tsconfig.json +26 -0
  32. package/templates/react/base/vite.config.ts +7 -0
  33. package/templates/react/interactive-sphere/index.html +13 -0
  34. package/templates/react/interactive-sphere/src/App.css +75 -0
  35. package/templates/react/interactive-sphere/src/App.tsx +43 -0
  36. package/templates/react/interactive-sphere/src/Scene.tsx +69 -0
  37. package/templates/react/interactive-sphere/src/index.css +51 -0
  38. package/templates/react/interactive-sphere/src/main.tsx +11 -0
  39. package/templates/react/model-viewer/index.html +13 -0
  40. package/templates/react/model-viewer/src/App.tsx +43 -0
  41. package/templates/react/model-viewer/src/index.css +9 -0
  42. package/templates/react/model-viewer/src/main.tsx +11 -0
  43. package/templates/react/spinning-cube/index.html +13 -0
  44. package/templates/react/spinning-cube/src/App.tsx +36 -0
  45. package/templates/react/spinning-cube/src/index.css +9 -0
  46. package/templates/react/spinning-cube/src/main.tsx +11 -0
  47. package/templates/react/splat-viewer/index.html +13 -0
  48. package/templates/react/splat-viewer/src/App.tsx +39 -0
  49. package/templates/react/splat-viewer/src/index.css +9 -0
  50. package/templates/react/splat-viewer/src/main.tsx +11 -0
  51. package/templates/web-components/base/README.md +66 -0
  52. package/templates/web-components/base/_gitignore +24 -0
  53. package/templates/web-components/base/eslint.config.mjs +3 -0
  54. package/templates/web-components/base/package.json +31 -0
  55. package/templates/web-components/base/prettier.config.mjs +1 -0
  56. package/templates/web-components/base/src/assets/playcanvas.png +0 -0
  57. package/templates/web-components/base/src/vite-env.d.ts +19 -0
  58. package/templates/web-components/base/tsconfig.json +25 -0
  59. package/templates/web-components/interactive-sphere/index.html +53 -0
  60. package/templates/web-components/interactive-sphere/src/assets/typescript.svg +1 -0
  61. package/templates/web-components/interactive-sphere/src/main.ts +76 -0
  62. package/templates/web-components/interactive-sphere/src/style.css +111 -0
  63. package/templates/web-components/model-viewer/index.html +34 -0
  64. package/templates/web-components/model-viewer/src/main.ts +26 -0
  65. package/templates/web-components/model-viewer/src/style.css +8 -0
  66. package/templates/web-components/spinning-cube/index.html +29 -0
  67. package/templates/web-components/spinning-cube/src/main.ts +20 -0
  68. package/templates/web-components/spinning-cube/src/style.css +8 -0
  69. package/templates/web-components/splat-viewer/index.html +31 -0
  70. package/templates/web-components/splat-viewer/src/main.ts +14 -0
  71. package/templates/web-components/splat-viewer/src/style.css +8 -0
  72. package/index.js +0 -3
  73. package/templates/react-ts/README.md +0 -82
  74. package/templates/react-ts/index.html +0 -13
  75. package/templates/react-ts/package.json +0 -29
  76. package/templates/react-ts/scripts/ensure-node-version.cjs +0 -12
  77. package/templates/react-ts/src/App.css +0 -87
  78. package/templates/react-ts/src/App.tsx +0 -46
  79. package/templates/react-ts/src/Scene.tsx +0 -70
  80. package/templates/react-ts/src/index.css +0 -73
  81. package/templates/react-ts/src/main.tsx +0 -10
  82. package/templates/react-ts/src/vite-env.d.ts +0 -1
  83. package/templates/react-ts/tsconfig.app.json +0 -27
  84. package/templates/react-ts/tsconfig.json +0 -7
  85. package/templates/react-ts/tsconfig.node.json +0 -25
  86. package/templates/react-ts/vite.config.ts +0 -7
  87. package/templates/vanilla-ts/README.md +0 -114
  88. package/templates/vanilla-ts/index.html +0 -13
  89. package/templates/vanilla-ts/package.json +0 -23
  90. package/templates/vanilla-ts/public/environment-map.png +0 -0
  91. package/templates/vanilla-ts/scripts/ensure-node-version.cjs +0 -12
  92. package/templates/vanilla-ts/src/App.ts +0 -182
  93. package/templates/vanilla-ts/src/style.css +0 -140
  94. package/templates/vanilla-ts/src/utils.ts +0 -16
  95. package/templates/vanilla-ts/src/vite-env.d.ts +0 -1
  96. package/templates/vanilla-ts/tsconfig.json +0 -25
  97. /package/templates/{react-ts → _shared/interactive-sphere}/public/environment-map.png +0 -0
  98. /package/templates/{react-ts → engine/base}/_gitignore +0 -0
  99. /package/templates/{react-ts → engine/base}/src/assets/playcanvas.png +0 -0
  100. /package/templates/{vanilla-ts → engine/interactive-sphere}/src/assets/typescript.svg +0 -0
  101. /package/templates/{vanilla-ts → react/base}/_gitignore +0 -0
  102. /package/templates/{vanilla-ts → react/base}/src/assets/playcanvas.png +0 -0
  103. /package/templates/{react-ts → react/interactive-sphere}/src/assets/react.svg +0 -0
@@ -0,0 +1,19 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module 'playcanvas/scripts/esm/camera-controls.mjs' {
4
+ import type { Script } from 'playcanvas';
5
+
6
+ export const CameraControls: typeof Script & (new () => Script);
7
+ }
8
+
9
+ declare module 'playcanvas/scripts/esm/grid.mjs' {
10
+ import type { Script } from 'playcanvas';
11
+
12
+ export const Grid: typeof Script & (new () => Script);
13
+ }
14
+
15
+ declare module 'playcanvas/scripts/esm/sky/procedural-sky.mjs' {
16
+ import type { Script } from 'playcanvas';
17
+
18
+ export const ProceduralSky: typeof Script & (new () => Script);
19
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedSideEffectImports": true
23
+ },
24
+ "include": ["src"]
25
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + TS</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,180 @@
1
+ import type { Layer, Texture } from 'playcanvas';
2
+ import {
3
+ createGraphicsDevice,
4
+ AppBase,
5
+ AppOptions,
6
+ RenderComponentSystem,
7
+ CameraComponentSystem,
8
+ ScriptComponentSystem,
9
+ TextureHandler,
10
+ ContainerHandler,
11
+ FILLMODE_FILL_WINDOW,
12
+ RESOLUTION_AUTO,
13
+ Entity,
14
+ Color,
15
+ Vec3,
16
+ Picker,
17
+ EVENT_MOUSEMOVE,
18
+ EVENT_MOUSEUP,
19
+ TouchDevice,
20
+ Mouse,
21
+ StandardMaterial,
22
+ Asset,
23
+ AssetListLoader,
24
+ TEXTURETYPE_RGBP
25
+ } from 'playcanvas';
26
+ import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
27
+ import { Grid } from 'playcanvas/scripts/esm/grid.mjs';
28
+
29
+ import { throttle } from './utils';
30
+
31
+ const HOVER_COLOR = new Color(1, 0.647, 0);
32
+ const DEFAULT_COLOR = new Color(0.827, 0.827, 0.827);
33
+
34
+ // Assets to load
35
+ const assets = {
36
+ envAtlas: new Asset(
37
+ 'env-atlas',
38
+ 'texture',
39
+ { url: '/environment-map.png' },
40
+ {
41
+ type: TEXTURETYPE_RGBP,
42
+ mipmaps: false
43
+ }
44
+ )
45
+ };
46
+
47
+ /**
48
+ * Setup the PlayCanvas app
49
+ * @param canvas - The canvas element
50
+ * @param onClick - The function to call when the user clicks on the sphere
51
+ */
52
+ const setupApp = async (canvas: HTMLCanvasElement, onClick: () => void) => {
53
+ if (!canvas) {
54
+ throw new Error('Canvas not found');
55
+ }
56
+
57
+ // Create graphics device
58
+ const device = await createGraphicsDevice(canvas);
59
+
60
+ // Create app options
61
+ const createOptions = new AppOptions();
62
+ createOptions.graphicsDevice = device;
63
+ createOptions.mouse = new Mouse(document.body);
64
+ createOptions.touch = new TouchDevice(document.body);
65
+ createOptions.componentSystems = [RenderComponentSystem, CameraComponentSystem, ScriptComponentSystem];
66
+ createOptions.resourceHandlers = [TextureHandler, ContainerHandler];
67
+
68
+ // Create app
69
+ const app = new AppBase(canvas);
70
+ app.init(createOptions);
71
+
72
+ // Set the canvas to fill the window
73
+ app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
74
+ app.setCanvasResolution(RESOLUTION_AUTO);
75
+
76
+ // Ensure canvas is resized when window changes size
77
+ const resize = () => app.resizeCanvas();
78
+ window.addEventListener('resize', resize);
79
+
80
+ app.once('destroy', () => {
81
+ window.removeEventListener('resize', resize);
82
+ });
83
+
84
+ // Load assets
85
+ await new Promise<void>((resolve) => {
86
+ new AssetListLoader(Object.values(assets), app.assets).load(() => resolve());
87
+ });
88
+
89
+ app.start();
90
+
91
+ // Set up environment lighting (no skybox, just IBL)
92
+ app.scene.envAtlas = assets.envAtlas.resource as Texture;
93
+ const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
94
+ if (skyboxLayer) {
95
+ skyboxLayer.enabled = false;
96
+ }
97
+
98
+ // Create sphere entity
99
+ const sphere = new Entity('sphere');
100
+ sphere.setPosition(new Vec3(0, 0.5, 0));
101
+
102
+ // Create a new material
103
+ const material = new StandardMaterial();
104
+ material.diffuse.copy(DEFAULT_COLOR);
105
+ material.update();
106
+
107
+ sphere.addComponent('render', {
108
+ type: 'sphere',
109
+ material: material
110
+ });
111
+ app.root.addChild(sphere);
112
+
113
+ // Create camera entity
114
+ const camera = new Entity('camera');
115
+ camera.addComponent('camera', {
116
+ clearColor: new Color(0.09, 0.09, 0.09)
117
+ });
118
+ camera.setPosition(new Vec3(4, 1, 4));
119
+ app.root.addChild(camera);
120
+
121
+ // Create camera controls
122
+ camera.addComponent('script');
123
+ camera.script?.create(CameraControls);
124
+
125
+ // Create grid entity
126
+ const grid = new Entity('grid');
127
+ grid.addComponent('script');
128
+ grid.script?.create(Grid);
129
+ grid.setLocalScale(1000, 1000, 1000);
130
+ app.root.addChild(grid);
131
+
132
+ // Create a picker for mouse interaction
133
+ const picker = new Picker(app, 1, 1);
134
+ const worldLayer = app.scene.layers.getLayerByName('World');
135
+
136
+ const intersectsSphere = (x: number, y: number, layer: Layer) => {
137
+ if (!camera.camera) {
138
+ return Promise.resolve(false);
139
+ }
140
+
141
+ const pickerScale = 0.5;
142
+ picker.resize(canvas.clientWidth * pickerScale, canvas.clientHeight * pickerScale);
143
+
144
+ if (!layer) {
145
+ return Promise.resolve(false);
146
+ }
147
+
148
+ picker.prepare(camera.camera, app.scene, [layer]);
149
+
150
+ return picker.getSelectionAsync(x * pickerScale, y * pickerScale, 1, 1).then((items) => {
151
+ if (items.length === 0) return false;
152
+ return items[0] === sphere.render?.meshInstances[0];
153
+ });
154
+ };
155
+
156
+ // On mouse move, check if hovering over sphere and update cursor/color
157
+ app.mouse?.on(
158
+ EVENT_MOUSEMOVE,
159
+ throttle((event) => {
160
+ if (!worldLayer) return;
161
+ intersectsSphere(event.x, event.y, worldLayer).then((intersects) => {
162
+ material.diffuse.copy(intersects ? HOVER_COLOR : DEFAULT_COLOR);
163
+ document.body.style.cursor = intersects ? 'pointer' : 'default';
164
+ material.update();
165
+ });
166
+ }, 100)
167
+ );
168
+
169
+ // On mouse up, check if clicked on sphere and call onClick
170
+ app.mouse?.on(EVENT_MOUSEUP, (event) => {
171
+ if (!worldLayer || !onClick) return;
172
+ intersectsSphere(event.x, event.y, worldLayer).then((intersects) => {
173
+ if (intersects) {
174
+ onClick();
175
+ }
176
+ });
177
+ });
178
+ };
179
+
180
+ export { setupApp };
@@ -1,7 +1,7 @@
1
- import './style.css'
2
- import typescriptLogo from './assets/typescript.svg'
3
- import playcanvasLogo from './assets/playcanvas.png'
4
- import { setupApp } from './App'
1
+ import './style.css';
2
+ import { setupApp } from './App';
3
+ import playcanvasLogo from './assets/playcanvas.png';
4
+ import typescriptLogo from './assets/typescript.svg';
5
5
 
6
6
  let count = 0;
7
7
 
@@ -16,7 +16,7 @@ document.querySelector<HTMLDivElement>('#root')!.innerHTML = `
16
16
  <img src="${playcanvasLogo}" class="playcanvas-logo logo" alt="PlayCanvas logo" />
17
17
  </a>
18
18
  <a href="https://www.typescriptlang.org/" target="_blank">
19
- <img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
19
+ <img src="${typescriptLogo}" class="logo typescript" alt="TypeScript logo" />
20
20
  </a>
21
21
  </header>
22
22
  </div>
@@ -33,13 +33,13 @@ document.querySelector<HTMLDivElement>('#root')!.innerHTML = `
33
33
  </p>
34
34
  </div>
35
35
  </div>
36
- `
36
+ `;
37
37
 
38
38
  const counterElement = document.getElementById('counter')!;
39
39
 
40
40
  const increment = () => {
41
- count++;
42
- counterElement.textContent = `Click Count: ${count}`;
41
+ count++;
42
+ counterElement.textContent = `Click Count: ${count}`;
43
43
  };
44
44
 
45
45
  setupApp(document.getElementById('application-canvas') as HTMLCanvasElement, increment);
@@ -0,0 +1,107 @@
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ header {
17
+ display: flex;
18
+ flex-direction: row;
19
+ align-items: center;
20
+ padding: 2rem;
21
+ }
22
+
23
+ a {
24
+ font-weight: 500;
25
+ color: #646cff;
26
+ pointer-events: auto;
27
+ text-decoration: inherit;
28
+ }
29
+ a:hover {
30
+ color: #535bf2;
31
+ }
32
+
33
+ body {
34
+ width: 100%;
35
+ height: 100%;
36
+ padding: 0;
37
+ margin: 0;
38
+ overflow: hidden;
39
+ }
40
+
41
+ h1 {
42
+ font-size: 2.4em;
43
+ line-height: 1.1;
44
+ opacity: 0.85;
45
+ }
46
+
47
+ .logo {
48
+ height: 2em;
49
+ padding: 0.5em;
50
+ will-change: filter;
51
+ transition: filter 300ms;
52
+ }
53
+
54
+ .playcanvas-logo {
55
+ border-radius: 100%;
56
+ }
57
+
58
+ .logo:hover {
59
+ filter: drop-shadow(0 0 2em #646cffaa);
60
+ }
61
+
62
+ .logo.typescript:hover {
63
+ filter: drop-shadow(0 0 2em #3178c6aa);
64
+ }
65
+
66
+ .read-the-docs {
67
+ color: #888;
68
+ }
69
+
70
+ @media (prefers-color-scheme: light) {
71
+ :root {
72
+ color: #213547;
73
+ background-color: #ffffff;
74
+ }
75
+ a:hover {
76
+ color: #747bff;
77
+ }
78
+ }
79
+
80
+ .overlay {
81
+ margin: 0 auto;
82
+ text-align: center;
83
+ display: flex;
84
+ flex-direction: column;
85
+ align-items: center;
86
+ justify-content: stretch;
87
+ width: 100vw;
88
+ height: 100vh;
89
+ pointer-events: none;
90
+ }
91
+
92
+ .overlay > .grow {
93
+ flex-grow: 1;
94
+ }
95
+
96
+ .absolute {
97
+ position: absolute;
98
+ top: 0;
99
+ left: 0;
100
+ }
101
+
102
+ .pill {
103
+ background-color: #000;
104
+ color: #fff;
105
+ padding: 0.5rem 1rem;
106
+ border-radius: 0.5rem;
107
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Simple throttle helper
3
+ * @param fn - The function to throttle
4
+ * @param interval - The interval in milliseconds
5
+ * @returns A throttled function
6
+ */
7
+ export const throttle = <T extends unknown[]>(fn: (...args: T) => void, interval = 100) => {
8
+ let last = 0;
9
+ return (...args: T) => {
10
+ const now = Date.now();
11
+ if (now - last >= interval) {
12
+ last = now;
13
+ fn(...args);
14
+ }
15
+ };
16
+ };
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + TS</title>
8
+ </head>
9
+ <body>
10
+ <canvas id="application-canvas"></canvas>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,81 @@
1
+ import type { ContainerResource } from 'playcanvas';
2
+ import {
3
+ AppBase,
4
+ AppOptions,
5
+ Asset,
6
+ AssetListLoader,
7
+ CameraComponentSystem,
8
+ ContainerHandler,
9
+ Entity,
10
+ FILLMODE_FILL_WINDOW,
11
+ RESOLUTION_AUTO,
12
+ RenderComponentSystem,
13
+ ScriptComponentSystem,
14
+ TONEMAP_ACES2,
15
+ TextureHandler,
16
+ createGraphicsDevice
17
+ } from 'playcanvas';
18
+ import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
19
+ import { ProceduralSky } from 'playcanvas/scripts/esm/sky/procedural-sky.mjs';
20
+
21
+ import './style.css';
22
+
23
+ // Swap this for your own model - anything you drop in public/ is served from the site root
24
+ const MODEL_URL = '/playcanvas-cube.glb';
25
+
26
+ const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
27
+
28
+ const device = await createGraphicsDevice(canvas);
29
+ device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
30
+
31
+ const createOptions = new AppOptions();
32
+ createOptions.graphicsDevice = device;
33
+ createOptions.componentSystems = [RenderComponentSystem, CameraComponentSystem, ScriptComponentSystem];
34
+ createOptions.resourceHandlers = [TextureHandler, ContainerHandler];
35
+
36
+ const app = new AppBase(canvas);
37
+ app.init(createOptions);
38
+
39
+ // Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
40
+ app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
41
+ app.setCanvasResolution(RESOLUTION_AUTO);
42
+
43
+ // Ensure canvas is resized when window changes size
44
+ const resize = () => app.resizeCanvas();
45
+ window.addEventListener('resize', resize);
46
+ app.on('destroy', () => {
47
+ window.removeEventListener('resize', resize);
48
+ });
49
+
50
+ // Load the model before starting, so the first frame already has something in it
51
+ const model = new Asset('model', 'container', { url: MODEL_URL });
52
+
53
+ await new Promise<void>((resolve) => {
54
+ new AssetListLoader([model], app.assets).load(() => resolve());
55
+ });
56
+
57
+ app.start();
58
+
59
+ // A container asset holds the whole glTF scene, so instantiate it to get an entity hierarchy
60
+ app.root.addChild((model.resource as ContainerResource).instantiateRenderEntity());
61
+
62
+ // The procedural sky is both the background and the image-based lighting, so no light or
63
+ // environment map asset is needed. Its default luminance is scene-scale bright, so turn it down to
64
+ // keep the model out of clipping
65
+ const sky = new Entity('sky');
66
+ sky.addComponent('script');
67
+ sky.script?.create(ProceduralSky, {
68
+ properties: { luminance: 0.2 }
69
+ });
70
+ app.root.addChild(sky);
71
+
72
+ // Create camera entity with orbit controls. The sky is HDR, so it needs tone mapping to bring it
73
+ // back into displayable range - without it the whole image blows out to white
74
+ const camera = new Entity('camera');
75
+ camera.addComponent('camera', {
76
+ toneMapping: TONEMAP_ACES2
77
+ });
78
+ camera.setPosition(0, 0, 3);
79
+ camera.addComponent('script');
80
+ camera.script?.create(CameraControls);
81
+ app.root.addChild(camera);
@@ -0,0 +1,8 @@
1
+ body {
2
+ margin: 0;
3
+ overflow: hidden;
4
+ }
5
+
6
+ canvas {
7
+ display: block;
8
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + TS</title>
8
+ </head>
9
+ <body>
10
+ <canvas id="application-canvas"></canvas>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,65 @@
1
+ import {
2
+ AppBase,
3
+ AppOptions,
4
+ CameraComponentSystem,
5
+ Color,
6
+ ContainerHandler,
7
+ Entity,
8
+ FILLMODE_FILL_WINDOW,
9
+ LightComponentSystem,
10
+ RESOLUTION_AUTO,
11
+ RenderComponentSystem,
12
+ TextureHandler,
13
+ createGraphicsDevice
14
+ } from 'playcanvas';
15
+
16
+ import './style.css';
17
+
18
+ const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
19
+
20
+ const device = await createGraphicsDevice(canvas);
21
+ device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
22
+
23
+ const createOptions = new AppOptions();
24
+ createOptions.graphicsDevice = device;
25
+ createOptions.componentSystems = [RenderComponentSystem, CameraComponentSystem, LightComponentSystem];
26
+ createOptions.resourceHandlers = [TextureHandler, ContainerHandler];
27
+
28
+ const app = new AppBase(canvas);
29
+ app.init(createOptions);
30
+ app.start();
31
+
32
+ // Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
33
+ app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
34
+ app.setCanvasResolution(RESOLUTION_AUTO);
35
+
36
+ // Ensure canvas is resized when window changes size
37
+ const resize = () => app.resizeCanvas();
38
+ window.addEventListener('resize', resize);
39
+ app.on('destroy', () => {
40
+ window.removeEventListener('resize', resize);
41
+ });
42
+
43
+ // Create box entity
44
+ const box = new Entity('cube');
45
+ box.addComponent('render', {
46
+ type: 'box'
47
+ });
48
+ app.root.addChild(box);
49
+
50
+ // Create camera entity
51
+ const camera = new Entity('camera');
52
+ camera.addComponent('camera', {
53
+ clearColor: new Color(0.5, 0.6, 0.9)
54
+ });
55
+ camera.setPosition(0, 0, 3);
56
+ app.root.addChild(camera);
57
+
58
+ // Create directional light entity
59
+ const light = new Entity('light');
60
+ light.addComponent('light');
61
+ light.setEulerAngles(45, 0, 0);
62
+ app.root.addChild(light);
63
+
64
+ // Rotate the box according to the delta time since the last frame
65
+ app.on('update', (dt: number) => box.rotate(10 * dt, 20 * dt, 30 * dt));
@@ -0,0 +1,8 @@
1
+ body {
2
+ margin: 0;
3
+ overflow: hidden;
4
+ }
5
+
6
+ canvas {
7
+ display: block;
8
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/src/assets/playcanvas.png" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>PlayCanvas + TS</title>
8
+ </head>
9
+ <body>
10
+ <canvas id="application-canvas"></canvas>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,74 @@
1
+ import {
2
+ AppBase,
3
+ AppOptions,
4
+ Asset,
5
+ AssetListLoader,
6
+ CameraComponentSystem,
7
+ Entity,
8
+ FILLMODE_FILL_WINDOW,
9
+ GSplatComponentSystem,
10
+ GSplatHandler,
11
+ RESOLUTION_AUTO,
12
+ ScriptComponentSystem,
13
+ TextureHandler,
14
+ createGraphicsDevice
15
+ } from 'playcanvas';
16
+ import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
17
+
18
+ import './style.css';
19
+
20
+ // Hosted so the template stays small. Capture or edit your own splat at https://superspl.at, then
21
+ // drop the .sog or .ply in public/ and point this at it instead
22
+ const SPLAT_URL = 'https://developer.playcanvas.com/assets/toy-cat.sog';
23
+
24
+ const canvas = document.getElementById('application-canvas') as HTMLCanvasElement;
25
+
26
+ // Antialiasing is disabled - splat rendering is fragment bound, so MSAA costs a lot and adds little
27
+ const device = await createGraphicsDevice(canvas, { antialias: false });
28
+ device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
29
+
30
+ const createOptions = new AppOptions();
31
+ createOptions.graphicsDevice = device;
32
+ createOptions.componentSystems = [CameraComponentSystem, GSplatComponentSystem, ScriptComponentSystem];
33
+
34
+ // TextureHandler is required as well as GSplatHandler - a .sog is a container whose splat data is
35
+ // stored as textures, and without it the splat loads but silently renders nothing
36
+ createOptions.resourceHandlers = [TextureHandler, GSplatHandler];
37
+
38
+ const app = new AppBase(canvas);
39
+ app.init(createOptions);
40
+
41
+ // Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
42
+ app.setCanvasFillMode(FILLMODE_FILL_WINDOW);
43
+ app.setCanvasResolution(RESOLUTION_AUTO);
44
+
45
+ // Ensure canvas is resized when window changes size
46
+ const resize = () => app.resizeCanvas();
47
+ window.addEventListener('resize', resize);
48
+ app.on('destroy', () => {
49
+ window.removeEventListener('resize', resize);
50
+ });
51
+
52
+ // Load the splat before starting, so the first frame already has something in it
53
+ const splat = new Asset('splat', 'gsplat', { url: SPLAT_URL });
54
+
55
+ await new Promise<void>((resolve) => {
56
+ new AssetListLoader([splat], app.assets).load(() => resolve());
57
+ });
58
+
59
+ app.start();
60
+
61
+ // Splats are captured in their own space, so this one needs offsetting and flipping upright
62
+ const toyCat = new Entity('toy cat');
63
+ toyCat.addComponent('gsplat', { asset: splat });
64
+ toyCat.setPosition(0, -0.7, 0);
65
+ toyCat.setEulerAngles(0, 0, 180);
66
+ app.root.addChild(toyCat);
67
+
68
+ // Create camera entity with orbit controls
69
+ const camera = new Entity('camera');
70
+ camera.addComponent('camera');
71
+ camera.setPosition(0, 0, 2.5);
72
+ camera.addComponent('script');
73
+ camera.script?.create(CameraControls);
74
+ app.root.addChild(camera);