Skip to content

Commit b5ebc2f

Browse files
committed
Conceptual integrations
1 parent 6d46c7c commit b5ebc2f

102 files changed

Lines changed: 12236 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

concepts/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# AppContext Model Runtime Concepts
2+
3+
These demos show the smallest useful shape for driving non-DOM runtimes from
4+
AngularTS app-owned models.
5+
6+
Each demo follows the same pattern:
7+
8+
1. register shared state with `app.model(...)`;
9+
2. inject that model into a controller;
10+
3. let a runtime adapter render from the model;
11+
4. update the model through normal AngularTS bindings.
12+
13+
Run them from the repo dev server so `/dist/angular-ts.umd.min.js` resolves:
14+
15+
```sh
16+
make serve
17+
```
18+
19+
Then open `/concepts/` for the grouped concept index.
20+
21+
Implemented groups:
22+
23+
- Browser drawing runtimes: Canvas, WebGL, WebGPU, HTML-in-Canvas, AudioGraph.
24+
- 3D and rendering runtimes: Three.js, BabylonJS, PlayCanvas.
25+
- Game runtimes: Cockpit Breach, Glyph Rush, Phaser, PixiJS, Excalibur, Unity FPS.
26+
- Data visualization runtimes: ECharts.
27+
- Low-level visualization runtimes: D3.
28+
- CAD, design, and canvas object runtimes: Konva.
29+
- Map runtimes: MapLibre GL.
30+
- Worker, WASM, and data runtimes: Web Worker Compute, Shared WASM Module
31+
Worker, and SQLite WASM.
32+
- Local-first and collaboration runtimes: Yjs.
33+
34+
The broader experiment set is tracked by the runnable demos in this directory.

concepts/audio-graph/app.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
window.angular
2+
.module('audioGraphConcept', [])
3+
.model('synthModel', () => ({
4+
frequency: 440,
5+
volume: 0.1,
6+
playing: false,
7+
}))
8+
.controller(
9+
'DemoCtrl',
10+
class {
11+
static $inject = ['synthModel'];
12+
13+
constructor(synthModel) {
14+
this.synth = synthModel;
15+
this.audio = null;
16+
this.oscillator = null;
17+
this.gain = null;
18+
}
19+
20+
toggle() {
21+
if (this.synth.playing) {
22+
this.stop();
23+
} else {
24+
this.start();
25+
}
26+
}
27+
28+
start() {
29+
this.audio = this.audio || new AudioContext();
30+
this.oscillator = this.audio.createOscillator();
31+
this.gain = this.audio.createGain();
32+
33+
this.oscillator.connect(this.gain);
34+
this.gain.connect(this.audio.destination);
35+
this.oscillator.start();
36+
this.synth.playing = true;
37+
this.sync();
38+
}
39+
40+
stop() {
41+
this.oscillator?.stop();
42+
this.oscillator = null;
43+
this.gain = null;
44+
this.synth.playing = false;
45+
}
46+
47+
sync() {
48+
if (!this.oscillator || !this.gain) return;
49+
50+
this.oscillator.frequency.value = Number(this.synth.frequency);
51+
this.gain.gain.value = Number(this.synth.volume);
52+
requestAnimationFrame(() => this.sync());
53+
}
54+
},
55+
);
56+

concepts/audio-graph/index.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>AngularTS App Model + AudioGraph</title>
6+
<script src="/dist/angular-ts.umd.min.js"></script>
7+
<script src="./app.js"></script>
8+
<style>
9+
body {
10+
font-family: sans-serif;
11+
margin: 24px;
12+
}
13+
14+
label {
15+
display: block;
16+
margin: 12px 0;
17+
}
18+
</style>
19+
</head>
20+
<body ng-app="audioGraphConcept" ng-controller="DemoCtrl as demo">
21+
<button ng-click="demo.toggle()">
22+
{{ demo.synth.playing ? 'Stop' : 'Start' }}
23+
</button>
24+
25+
<label>
26+
Frequency
27+
<input type="range" min="80" max="1200" ng-model="demo.synth.frequency" />
28+
{{ demo.synth.frequency }} Hz
29+
</label>
30+
31+
<label>
32+
Volume
33+
<input type="range" min="0" max="0.4" step="0.01" ng-model="demo.synth.volume" />
34+
{{ demo.synth.volume }}
35+
</label>
36+
</body>
37+
</html>
38+

concepts/babylonjs/app.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
window.angular
2+
.module('babylonSharkConcept', [])
3+
.model('sharkModel', () => ({
4+
swimming: true,
5+
speed: 0.03,
6+
heading: 0,
7+
depth: 0,
8+
}))
9+
.controller(
10+
'DemoCtrl',
11+
class {
12+
static $inject = ['sharkModel'];
13+
14+
constructor(sharkModel) {
15+
this.shark = sharkModel;
16+
requestAnimationFrame(() => this.mount());
17+
}
18+
19+
toggleSwim() {
20+
this.shark.swimming = !this.shark.swimming;
21+
}
22+
23+
turn(amount) {
24+
this.shark.heading += amount;
25+
}
26+
27+
dive(amount) {
28+
this.shark.depth = Math.max(-1.4, Math.min(1.2, this.shark.depth + amount));
29+
}
30+
31+
faster() {
32+
this.shark.speed = Math.min(0.12, this.shark.speed + 0.01);
33+
}
34+
35+
slower() {
36+
this.shark.speed = Math.max(0.0, this.shark.speed - 0.01);
37+
}
38+
39+
reset() {
40+
this.shark.heading = 0;
41+
this.shark.depth = 0;
42+
}
43+
44+
async mount() {
45+
const canvas = document.getElementById('babylon-view');
46+
const engine = new BABYLON.Engine(canvas, true);
47+
48+
engine.setHardwareScalingLevel(1.0 / window.devicePixelRatio);
49+
engine.displayLoadingUI();
50+
51+
const scene = new BABYLON.Scene(engine);
52+
const camera = new BABYLON.ArcRotateCamera(
53+
'camera1',
54+
1.0,
55+
1.3,
56+
2.0,
57+
new BABYLON.Vector3(0, 0.5, 0),
58+
scene,
59+
);
60+
const light = new BABYLON.HemisphericLight(
61+
'light',
62+
new BABYLON.Vector3(0, 1, 0),
63+
scene,
64+
);
65+
66+
camera.attachControl(canvas, true);
67+
camera.fov = (27 / 180) * Math.PI;
68+
camera.minZ = 1;
69+
camera.maxZ = 120;
70+
camera.lowerBetaLimit = 0.1;
71+
camera.upperBetaLimit = 1.5;
72+
light.intensity = 0.9;
73+
74+
scene.clearColor.set(0.149, 0.149, 0.149, 1.0);
75+
scene.environmentTexture = new BABYLON.HDRCubeTexture(
76+
'https://assets.babylonjs.com/ibl/parking.hdr',
77+
scene,
78+
128,
79+
);
80+
81+
const sky = createUnderwaterSky(scene);
82+
const shark = await loadShark(scene);
83+
84+
frameCamera(camera, shark.meshes);
85+
sky.scaling.setAll(shark.sceneSize * 4);
86+
sky.position.y = shark.sceneSize * 2 - 1;
87+
engine.hideLoadingUI();
88+
89+
engine.runRenderLoop(() => {
90+
if (this.shark.swimming) {
91+
shark.root.position.x += Math.sin(this.shark.heading) * this.shark.speed;
92+
shark.root.position.z += Math.cos(this.shark.heading) * this.shark.speed;
93+
shark.animationGroups.forEach((group) => {
94+
if (!group.isPlaying) group.play(true);
95+
group.speedRatio = Math.max(0.2, this.shark.speed * 30);
96+
});
97+
} else {
98+
shark.animationGroups.forEach((group) => group.pause());
99+
}
100+
101+
shark.root.rotation.y = this.shark.heading;
102+
shark.root.position.y = this.shark.depth;
103+
scene.render();
104+
});
105+
106+
window.addEventListener('resize', () => engine.resize());
107+
}
108+
},
109+
);
110+
111+
function createUnderwaterSky(scene) {
112+
const sky = BABYLON.MeshBuilder.CreateBox(
113+
'sky',
114+
{ size: 1, sideOrientation: BABYLON.Mesh.BACKSIDE },
115+
scene,
116+
);
117+
const material = new BABYLON.BackgroundMaterial('skyMaterial', scene);
118+
const source = 'https://raw.githubusercontent.com/CedricGuillemet/dump/master/underwater_env3/';
119+
const files = ['px.png', 'py.png', 'pz.png', 'nx.png', 'ny.png', 'nz.png'].map(
120+
(file) => `${source}${file}`,
121+
);
122+
123+
material.reflectionTexture = BABYLON.CubeTexture.CreateFromImages(files, scene);
124+
material.reflectionTexture.coordinatesMode = BABYLON.Constants.TEXTURE_SKYBOX_MODE;
125+
material.enableGroundProjection = true;
126+
sky.material = material;
127+
128+
return sky;
129+
}
130+
131+
async function loadShark(scene) {
132+
const root = new BABYLON.TransformNode('sharkRoot', scene);
133+
const result = await BABYLON.SceneLoader.ImportMeshAsync(
134+
'',
135+
'https://assets.babylonjs.com/meshes/',
136+
'shark.glb',
137+
scene,
138+
);
139+
const meshes = result.meshes.filter((mesh) => mesh instanceof BABYLON.Mesh);
140+
141+
meshes.forEach((mesh) => {
142+
mesh.parent = root;
143+
});
144+
145+
const bounds = calculateBounds(meshes);
146+
const sceneSize = bounds.max.subtract(bounds.min).length();
147+
const center = bounds.max.subtract(bounds.min).scale(0.5).add(bounds.min);
148+
149+
root.position.subtractInPlace(center);
150+
151+
return {
152+
animationGroups: result.animationGroups,
153+
meshes,
154+
root,
155+
sceneSize,
156+
};
157+
}
158+
159+
function frameCamera(camera, meshes) {
160+
const bounds = calculateBounds(meshes);
161+
const sceneSize = bounds.max.subtract(bounds.min).length();
162+
const center = bounds.max.subtract(bounds.min).scale(0.5).add(bounds.min);
163+
164+
if (!isFinite(center.x) || !isFinite(center.y) || !isFinite(center.z)) return;
165+
166+
camera.minZ = 0.1 * sceneSize;
167+
camera.maxZ = 45 * sceneSize;
168+
camera.speed = sceneSize;
169+
camera.wheelPrecision = 100 / sceneSize;
170+
camera.panningSensibility = 5000 / sceneSize;
171+
camera.target = BABYLON.Vector3.Zero();
172+
camera.radius = 2.0 * sceneSize;
173+
camera.beta = Math.PI * 0.4;
174+
camera.upperRadiusLimit = sceneSize * 2;
175+
camera.lowerRadiusLimit = sceneSize;
176+
}
177+
178+
function calculateBounds(meshes) {
179+
const bounds = {
180+
min: new BABYLON.Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE),
181+
max: new BABYLON.Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE),
182+
};
183+
184+
meshes.forEach((mesh) => {
185+
const localBounds = mesh.getHierarchyBoundingVectors(true);
186+
187+
bounds.min = BABYLON.Vector3.Minimize(bounds.min, localBounds.min);
188+
bounds.max = BABYLON.Vector3.Maximize(bounds.max, localBounds.max);
189+
});
190+
191+
return bounds;
192+
}

concepts/babylonjs/index.html

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>AngularTS App Model + Babylon.js Shark</title>
6+
<script src="/dist/angular-ts.umd.min.js"></script>
7+
<script src="https://cdn.babylonjs.com/babylon.js"></script>
8+
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
9+
<script src="./app.js"></script>
10+
<style>
11+
body {
12+
font-family: sans-serif;
13+
margin: 24px;
14+
}
15+
16+
button {
17+
margin-right: 8px;
18+
}
19+
20+
canvas {
21+
border: 1px solid #ddd;
22+
display: block;
23+
margin-top: 16px;
24+
}
25+
</style>
26+
</head>
27+
<body ng-app="babylonSharkConcept" ng-controller="DemoCtrl as demo">
28+
<button ng-click="demo.toggleSwim()">
29+
{{ demo.shark.swimming ? 'Pause' : 'Swim' }}
30+
</button>
31+
<button ng-click="demo.turn(-0.25)">Turn Left</button>
32+
<button ng-click="demo.turn(0.25)">Turn Right</button>
33+
<button ng-click="demo.dive(-0.2)">Dive</button>
34+
<button ng-click="demo.dive(0.2)">Surface</button>
35+
<button ng-click="demo.faster()">Faster</button>
36+
<button ng-click="demo.slower()">Slower</button>
37+
<button ng-click="demo.reset()">Reset</button>
38+
39+
<p>
40+
Speed: {{ demo.shark.speed.toFixed(2) }},
41+
Depth: {{ demo.shark.depth.toFixed(1) }}
42+
</p>
43+
44+
<canvas id="babylon-view" width="640" height="360"></canvas>
45+
</body>
46+
</html>

0 commit comments

Comments
 (0)