3D embeds
A flat embed puts a match in a rectangle on your page. This puts it in your world: the stage is a THREE.Group you add to your own scene, and you keep your renderer, your camera, your lighting and your render loop.
4dgsx.com and localhost:3000 today. If you want to build against it from your own origin, get in touch and we'll open it up.Load it
The SDK is ESM served from this origin, and three is a peer — you supply it. Two copies of Three in one page break instanceof across the copies and double the download, so we never bundle it.
With a bundler, npm i three and import the URL directly. Without one, an import map tells the browser what three means:
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.185.1/build/three.module.js"
}
}
</script>Pin whatever Three build you already use; three@0.185.1 is what we test against.
Mount a match
import * as THREE from "three";
import { FourDGSX } from "https://4dgsx.com/sdk/v1/three.js";
// your scene, your renderer, your camera, your loop
const scene = new THREE.Scene();
const gsx = new FourDGSX();
const stage = await gsx.mount(
(await gsx.programme("rfl")).items.find(i => i.state === "replay"),
);
stage.group.position.set(0, 0, 0); // wherever your pitch is
scene.add(stage.group);
stage.play();
// once per frame, from your own loop
function frame(dt) {
stage.update(dt, camera, renderer.domElement.clientHeight);
renderer.render(scene, camera);
}The group is Y-up, in real metres, with the ground at y = 0. Place it, rotate it, scale it — the published look is locked to the match, so moving the stage cannot slide the mown pattern across the pitch or swing the light.
Let it follow the schedule
schedule() polls the programme feed, mounts a match when it airs, tears it down when it ends, and shows a countdown board in between. A match is live at its scheduled time: while a premiere is airing its clock is the wall clock, and play/pause/seek return false rather than moving it.
const slot = gsx.schedule("rfl", {
// a match has started — put it on your pitch
mount: (stage) => { stage.group.position.copy(pitch.position); scene.add(stage.group); },
unmount: (stage) => scene.remove(stage.group),
// between matches, show what's next
showFixture: (board) => { board.position.copy(pitch.position).add(up2m); scene.add(board); },
hideFixture: (board) => scene.remove(board),
});What's yours, and what isn't
The animation, the data and the UI content are as published. Where they land in your world is yours:
// layers the publisher declared — toggle any of them
stage.layers.forEach(l => console.log(l.id, l.on));
stage.setLayer("players.names", false);
// dock slots: the bundle names them, YOU choose the surfaces
stage.docks.attach("main", stadiumBigScreen); // broadcast video
stage.docks.attach("left", statsBoardMesh); // publisher HTML panel
stage.docks.attach("right", lineupBoardMesh);
// audio: anchored sources stay where the publisher put them,
// the rest are yours to place
stage.audio.attachListener(camera);
stage.audio.enable(); // from a user gesture
stage.audio.place("crowd", { position: [0, 6, 22], ref: 10, max: 140 });
stage.audio.place("commentary", { position: [0, 9, 0], ref: 14, max: 160 });Dock slots are semantic — a bundle asks for main, left, right, and you decide which of your surfaces carries each one. Nothing is placed automatically. Audio works the same way with one rule: a source the publisher anchored (a player's mic follows that player) stays where it was put, and place() refuses it. Everything else — crowd, commentary, the broadcast mix — is yours to position.
React to the match
stage.on("event", (e) => {
if (e.type === "goal") world.fireworks();
});
stage.on("statechange", (s) => world.floodlights(s === "live"));
// hud.json is TRUTH — render your own scoreboard from it if you'd rather
stage.hud.teams; // codes, names, kit colours, badges
stage.score; // { a, b } at the current match time
stage.clock; // "9:53"The programme feed
Public JSON, CORS-open, CDN-cached: https://4dgsx.com/api/v1/programme/<channel>
An unaired match carries its teams, title and kick-off time but no bundle URL and no score — they are withheld server-side until the premiere starts and finishes respectively. There is nothing in the payload to fetch early, which is what makes a countdown safe to render from a public feed.
Attribution
Every stage carries a small 4DGSX mark. Move it or dim it to suit your scene — stage.attribution is a normal object — but please leave it in.
SDK v1 · https://4dgsx.com/sdk/v1/three.js
The bundles it plays are an open format — read the spec.