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.
- package/README.md +51 -47
- package/dist/index.mjs +49 -46
- package/package.json +54 -49
- package/templates/_shared/model-viewer/public/playcanvas-cube.glb +0 -0
- package/templates/engine/base/README.md +38 -0
- package/templates/engine/base/eslint.config.mjs +3 -0
- package/templates/engine/base/package.json +30 -0
- package/templates/engine/base/prettier.config.mjs +1 -0
- package/templates/engine/base/src/vite-env.d.ts +19 -0
- package/templates/engine/base/tsconfig.json +25 -0
- package/templates/engine/interactive-sphere/index.html +13 -0
- package/templates/engine/interactive-sphere/src/App.ts +180 -0
- package/templates/{vanilla-ts → engine/interactive-sphere}/src/main.ts +8 -8
- package/templates/engine/interactive-sphere/src/style.css +107 -0
- package/templates/engine/interactive-sphere/src/utils.ts +16 -0
- package/templates/engine/model-viewer/index.html +13 -0
- package/templates/engine/model-viewer/src/main.ts +81 -0
- package/templates/engine/model-viewer/src/style.css +8 -0
- package/templates/engine/spinning-cube/index.html +13 -0
- package/templates/engine/spinning-cube/src/main.ts +65 -0
- package/templates/engine/spinning-cube/src/style.css +8 -0
- package/templates/engine/splat-viewer/index.html +13 -0
- package/templates/engine/splat-viewer/src/main.ts +74 -0
- package/templates/engine/splat-viewer/src/style.css +8 -0
- package/templates/index.ts +47 -57
- package/templates/react/base/README.md +38 -0
- package/templates/react/base/eslint.config.mjs +14 -0
- package/templates/react/base/package.json +36 -0
- package/templates/react/base/prettier.config.mjs +1 -0
- package/templates/react/base/src/vite-env.d.ts +19 -0
- package/templates/react/base/tsconfig.json +26 -0
- package/templates/react/base/vite.config.ts +7 -0
- package/templates/react/interactive-sphere/index.html +13 -0
- package/templates/react/interactive-sphere/src/App.css +75 -0
- package/templates/react/interactive-sphere/src/App.tsx +43 -0
- package/templates/react/interactive-sphere/src/Scene.tsx +69 -0
- package/templates/react/interactive-sphere/src/index.css +51 -0
- package/templates/react/interactive-sphere/src/main.tsx +11 -0
- package/templates/react/model-viewer/index.html +13 -0
- package/templates/react/model-viewer/src/App.tsx +43 -0
- package/templates/react/model-viewer/src/index.css +9 -0
- package/templates/react/model-viewer/src/main.tsx +11 -0
- package/templates/react/spinning-cube/index.html +13 -0
- package/templates/react/spinning-cube/src/App.tsx +36 -0
- package/templates/react/spinning-cube/src/index.css +9 -0
- package/templates/react/spinning-cube/src/main.tsx +11 -0
- package/templates/react/splat-viewer/index.html +13 -0
- package/templates/react/splat-viewer/src/App.tsx +39 -0
- package/templates/react/splat-viewer/src/index.css +9 -0
- package/templates/react/splat-viewer/src/main.tsx +11 -0
- package/templates/web-components/base/README.md +66 -0
- package/templates/web-components/base/_gitignore +24 -0
- package/templates/web-components/base/eslint.config.mjs +3 -0
- package/templates/web-components/base/package.json +31 -0
- package/templates/web-components/base/prettier.config.mjs +1 -0
- package/templates/web-components/base/src/assets/playcanvas.png +0 -0
- package/templates/web-components/base/src/vite-env.d.ts +19 -0
- package/templates/web-components/base/tsconfig.json +25 -0
- package/templates/web-components/interactive-sphere/index.html +53 -0
- package/templates/web-components/interactive-sphere/src/assets/typescript.svg +1 -0
- package/templates/web-components/interactive-sphere/src/main.ts +76 -0
- package/templates/web-components/interactive-sphere/src/style.css +111 -0
- package/templates/web-components/model-viewer/index.html +34 -0
- package/templates/web-components/model-viewer/src/main.ts +26 -0
- package/templates/web-components/model-viewer/src/style.css +8 -0
- package/templates/web-components/spinning-cube/index.html +29 -0
- package/templates/web-components/spinning-cube/src/main.ts +20 -0
- package/templates/web-components/spinning-cube/src/style.css +8 -0
- package/templates/web-components/splat-viewer/index.html +31 -0
- package/templates/web-components/splat-viewer/src/main.ts +14 -0
- package/templates/web-components/splat-viewer/src/style.css +8 -0
- package/index.js +0 -3
- package/templates/react-ts/README.md +0 -82
- package/templates/react-ts/index.html +0 -13
- package/templates/react-ts/package.json +0 -29
- package/templates/react-ts/scripts/ensure-node-version.cjs +0 -12
- package/templates/react-ts/src/App.css +0 -87
- package/templates/react-ts/src/App.tsx +0 -46
- package/templates/react-ts/src/Scene.tsx +0 -70
- package/templates/react-ts/src/index.css +0 -73
- package/templates/react-ts/src/main.tsx +0 -10
- package/templates/react-ts/src/vite-env.d.ts +0 -1
- package/templates/react-ts/tsconfig.app.json +0 -27
- package/templates/react-ts/tsconfig.json +0 -7
- package/templates/react-ts/tsconfig.node.json +0 -25
- package/templates/react-ts/vite.config.ts +0 -7
- package/templates/vanilla-ts/README.md +0 -114
- package/templates/vanilla-ts/index.html +0 -13
- package/templates/vanilla-ts/package.json +0 -23
- package/templates/vanilla-ts/public/environment-map.png +0 -0
- package/templates/vanilla-ts/scripts/ensure-node-version.cjs +0 -12
- package/templates/vanilla-ts/src/App.ts +0 -182
- package/templates/vanilla-ts/src/style.css +0 -140
- package/templates/vanilla-ts/src/utils.ts +0 -16
- package/templates/vanilla-ts/src/vite-env.d.ts +0 -1
- package/templates/vanilla-ts/tsconfig.json +0 -25
- /package/templates/{react-ts → _shared/interactive-sphere}/public/environment-map.png +0 -0
- /package/templates/{react-ts → engine/base}/_gitignore +0 -0
- /package/templates/{react-ts → engine/base}/src/assets/playcanvas.png +0 -0
- /package/templates/{vanilla-ts → engine/interactive-sphere}/src/assets/typescript.svg +0 -0
- /package/templates/{vanilla-ts → react/base}/_gitignore +0 -0
- /package/templates/{vanilla-ts → react/base}/src/assets/playcanvas.png +0 -0
- /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
|
|
3
|
-
import playcanvasLogo from './assets/playcanvas.png'
|
|
4
|
-
import
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
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,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,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);
|