wired:scene
Documents and the prims they hold: the scene a script reads and edits.
A document is a tree of prims. A prim is nothing but an id. Everything about it, from its place in the tree to its mesh, is a property. Each property is composed from layers, strongest first:
shared: what each peer present in the space states, one layer per peer, the author's strongest.local: what this peer states, for itself only.- the document's durable value, which
commitwrites.
A script may write its own document, the documents it creates, and every document authored by the same user as its own. It may read any document it can open, and write only those.
Interface properties
Uses vec2, vec3, transform, color, document-id, prim-id.
The built-in properties a prim can carry.
topology
How a mesh's vertices are assembled into primitives.
point-list | |
line-list | |
line-strip | |
triangle-list | |
triangle-strip |
vertex-attribute
A per-vertex stream of a mesh, and how many floats each vertex takes.
position | 3 floats, in the prim's own frame. Required for a mesh to draw. |
normal | 3 floats, unit length. |
tangent | 4 floats: a unit tangent and the bitangent sign. |
color | 4 floats, linear RGBA. |
uv0 | 2 floats. |
uv1 | 2 floats. |
vertex-stream
attribute: vertex-attribute | |
values: list<f32> | Packed per vertex, every stream of a mesh holding the same number of vertices. At most 4 MiB. |
alpha-mode
opaque | |
mask | Discards fragments below alpha-cutoff. |
blend | |
add | |
multiply | |
premultiplied |
material
A physically based material. Every unset field takes the renderer's
default. Textures are relations, set beside it.
base-color: option<color> | |
emissive: option<color> | Added light, in linear RGB. Alpha is ignored. |
metallic: option<f32> | 0 to 1. |
roughness: option<f32> | 0 to 1. |
alpha-mode: option<alpha-mode> | |
alpha-cutoff: option<f32> | |
double-sided: option<bool> |
address-mode
How a sampler treats coordinates outside 0 to 1.
repeat | |
mirror-repeat | |
clamp-to-edge |
filter-mode
linear | |
nearest |
image-sampler
How an image is sampled. The encoded image itself is image-data.
address-mode-u: option<address-mode> | |
address-mode-v: option<address-mode> | |
address-mode-w: option<address-mode> | |
mag-filter: option<filter-mode> | |
min-filter: option<filter-mode> | |
mipmap-filter: option<filter-mode> | |
srgb: option<bool> | Whether the image holds sRGB color, which is the default, rather than linear data such as a normal map. |
collider-capsule
Along the prim's Y axis, centered on its origin.
height: f32 | Of the cylinder between the two hemispheres. |
radius: f32 |
collider-cylinder
Along the prim's Y axis, centered on its origin.
height: f32 | |
radius: f32 |
collider
A collision shape in the prim's own frame.
capsule(collider-capsule) | |
convex-hull | The hull of collider-vertices. |
cuboid(vec3) | A box of these full extents, centered on the origin. |
cylinder(collider-cylinder) | |
sphere(f32) | A sphere of this radius. |
trimesh | The triangles of collider-vertices and collider-indices. |
rigid-body-kind
dynamic | Moved by the simulation. |
kinematic | Moved only by its transform, pushing dynamic bodies aside. |
static | Never moves. |
rigid-body
Makes the prim's collider part of the simulation. Unset fields take the simulation's defaults.
kind: rigid-body-kind | |
mass: option<f32> | Kilograms. |
friction: option<f32> | |
restitution: option<f32> | 0 for no bounce, 1 for a perfectly elastic one. |
linear-damping: option<f32> | |
angular-damping: option<f32> |
link-id
link-id = tuple<u64, u64>128 bits shared by the two halves of a two-way portal.
portal-destination
space: document-id | The space the portal looks into. |
link: option<link-id> | Pairs this portal with the one in space bearing the same link. A destination without one is one-way. |
portal
A rectangular window into another space, in the prim's XY plane, facing +Z.
width: f32 | |
height: f32 | |
destination: option<portal-destination> |
spawn
Marks the prim as a spawn area: arriving users appear at a random
point within radius of its origin, on its XZ plane.
radius: f32 |
text-align
left | |
center | |
right |
text-anchor
Which line of the text block sits on the prim's origin.
baseline | |
top | |
middle | |
bottom |
text-billboard
none | |
yaw | Turns about Y to face the viewer, staying upright. |
full | Turns fully to face the viewer. |
text
A string drawn in the prim's XY plane, reading along +X. One style for the whole string. There is no markup.
value: string | At most 4 KiB. |
size: option<f32> | Em height in meters. |
align: option<text-align> | |
anchor: option<text-anchor> | |
wrap: option<f32> | Wrap width in meters. Unset breaks only on newlines. |
line-height: option<f32> | A multiple of the font's line spacing. |
color: option<color> | |
outline: option<color> | Drawn behind the glyphs. |
outline-width: option<f32> | In ems. |
emissive: option<f32> | Glow strength, 0 for none. |
billboard: option<text-billboard> |
relation
A named link from one prim to another in the same document.
shader-binding | The shader graph this prim draws with, authored on another prim. |
base-color-texture | |
emissive-texture | |
metallic-roughness-texture | |
normal-texture | |
occlusion-texture | |
shader-texture(u8) | The texture a shader graph samples at this slot, 0 to 3. |
custom(string) | An application-defined relation, named group/field. The group must not be one the host defines. |
property
A property and its value. Each case names one key.
parent(option<prim-id>) | The prim's parent, none for a root of the document. |
name(string) | At most 1 KiB. |
transform(transform) | Relative to the parent, or to the document for a root. |
reference(document-id) | Places another document under this prim, as it currently stands. |
mesh-topology(topology) | |
mesh-vertices(vertex-stream) | |
mesh-indices(list<u32>) | Indices into the vertex streams. At most 4 MiB. |
material(material) | |
image-sampler(image-sampler) | |
image-data(list<u8>) | An encoded PNG, JPEG or KTX2 image. At most 16 MiB. |
text(text) | |
collider(collider) | |
collider-vertices(list<f32>) | Packed XYZ positions. At most 4 MiB. |
collider-indices(list<u32>) | Triangle indices into collider-vertices. At most 4 MiB. |
rigid-body(rigid-body) | |
gravity-scale(f32) | A multiple of the world's gravity. Defaults to 1. |
portal(portal) | |
spawn(spawn) | |
relation(tuple<relation, prim-id>) | |
custom(tuple<string, list<u8>>) | Application data under group/field, outside the groups the host defines. At most 64 KiB. The host stores and replicates the bytes but never reads them. |
property-key
Names a property without its value.
parent | |
name | |
transform | |
reference | |
mesh-topology | |
mesh-vertices(vertex-attribute) | |
mesh-indices | |
material | |
image-sampler | |
image-data | |
text | |
collider | |
collider-vertices | |
collider-indices | |
rigid-body | |
gravity-scale | |
portal | |
spawn | |
relation(relation) | |
custom(string) |
Interface document
Uses transform, document-id, prim-id, prim-ref, error, property, property-key.
layer
Where a write lands. Neither layer is durable.
local | This peer only, gone when the document unloads. Every peer runs the same scripts, so a deterministic local write is seen everywhere without being sent anywhere. Reach for shared when the value depends on one peer, such as user input. |
shared | Every peer present in the space, live, until this peer leaves. Each peer's shared writes form its own layer on the document, and each viewer decides what that layer contributes. The author's layer contributes every property and is the strongest. Any other peer's contributes only properties in the state group, and a portal pairing when the viewer trusts that peer to open portals. Its other writes are kept but have no effect. Relations cannot be shared. |
edit
set(tuple<prim-id, property>) | Sets a property. Setting parent moves the prim. The prim must exist, and the new parent must not lie beneath it. |
clear(tuple<prim-id, property-key>) | Drops this layer's value for a key, so weaker layers show through. A shared value stays stated for the session, so clearing one states that the key has no value, hiding weaker layers too. parent cannot be cleared. Use remove. |
remove(prim-id) | Takes a prim and everything beneath it out of the scene. |
anchor
What a placed document's root hangs from.
space | The root of the space. |
prim(prim-ref) | A prim of any loaded document. |
document
A document in the world. Holding one is the capability to read it, and
to write it when the script may write it. Writes to a document the
script may not write fail with forbidden.
id
id: func() -> document-idmay-write
may-write: func() -> boolWhether the script may write this document: its own, one it created, or one authored by the same user as its own.
contains
contains: func(prim: prim-id) -> boolWhether prim is in the scene.
roots
roots: func() -> list<prim-id>Prims with no parent, in id order.
prims
prims: func() -> list<prim-id>Every prim in the scene, in no particular order.
children
In id order. Empty for a prim not in the scene.
find-by-name
find-by-name: func(name: string) -> list<prim-id>Every prim whose name is exactly name.
get
get: func(prim: prim-id, key: property-key) -> option<property>The composed value of a property: the strongest layer's. For
transform that is the stated value, not where physics has since
moved the prim. See world-transform.
get-many
get-many: func(keys: list<tuple<prim-id, property-key>>) -> list<option<property>>get for many keys in one call, answered in order.
keys
keys: func(prim: prim-id) -> list<property-key>Every key that has a value on prim.
world-transform
Where prim is in the world this frame, including what physics and
animation did to it since its transform was written.
offset-to
The transform from this document's root to other's, or none when
either is not placed.
create-prim
Adds a prim under parent, or as a root, and answers its id.
apply
Applies every edit in layer together, or none of them, so peers
never see half a batch. At most 4096 edits. In local, an edit naming
a prim the document does not hold fails the batch with not-found.
commit
commit: func(keys: list<tuple<prim-id, property-key>>) -> result<_, error>Makes the live value of each key durable, in the document itself
when this peer can write it, or as an override in the document
referencing it. Fails with forbidden when it can write neither.
Takes effect only on the device that authors the script's own document. Elsewhere it succeeds and does nothing, so a script running on every peer commits once. A script outside any space runs on one peer, which always commits.
place
Puts the document into the scene, or moves it, at offset from
anchor. Per peer, never stored.
A created document is held out of the scene until it is placed, so it can be built in full and then appear at once.
script-document
The document the calling script belongs to.
script-prim
script-prim: func() -> prim-idThe prim the calling script is attached to, in its own document.
open-document
open-document: func(id: document-id) -> result<option<document>, error>A document loaded on this peer, or none when it is not.
create-document
Mints an empty document the script may write, held until it is placed.
copy-document
Mints a document holding what source durably holds, which the two
then diverge from. Live layers are not copied.
delete-document
Removes a document this script created from the world, and deletes this peer's copy of it.
Source
/// Documents and the prims they hold: the scene a script reads and edits.
///
/// A document is a tree of prims. A prim is nothing but an id. Everything
/// about it, from its place in the tree to its mesh, is a property. Each
/// property is composed from layers, strongest first:
///
/// - `shared`: what each peer present in the space states, one layer per
/// peer, the author's strongest.
/// - `local`: what this peer states, for itself only.
/// - the document's durable value, which `commit` writes.
///
/// A script may write its own document, the documents it creates, and every
/// document authored by the same user as its own. It may read any document
/// it can open, and write only those.
package wired:scene;
/// The built-in properties a prim can carry.
interface properties {
use wired:core/math.{vec2, vec3, transform, color};
use wired:core/ids.{document-id, prim-id};
/// How a mesh's vertices are assembled into primitives.
enum topology {
point-list,
line-list,
line-strip,
triangle-list,
triangle-strip,
}
/// A per-vertex stream of a mesh, and how many floats each vertex takes.
enum vertex-attribute {
/// 3 floats, in the prim's own frame. Required for a mesh to draw.
position,
/// 3 floats, unit length.
normal,
/// 4 floats: a unit tangent and the bitangent sign.
tangent,
/// 4 floats, linear RGBA.
color,
/// 2 floats.
uv0,
/// 2 floats.
uv1,
}
record vertex-stream {
attribute: vertex-attribute,
/// Packed per vertex, every stream of a mesh holding the same number of
/// vertices. At most 4 MiB.
values: list<f32>,
}
enum alpha-mode {
opaque,
/// Discards fragments below `alpha-cutoff`.
mask,
blend,
add,
multiply,
premultiplied,
}
/// A physically based material. Every unset field takes the renderer's
/// default. Textures are `relation`s, set beside it.
record material {
base-color: option<color>,
/// Added light, in linear RGB. Alpha is ignored.
emissive: option<color>,
/// 0 to 1.
metallic: option<f32>,
/// 0 to 1.
roughness: option<f32>,
alpha-mode: option<alpha-mode>,
alpha-cutoff: option<f32>,
double-sided: option<bool>,
}
/// How a sampler treats coordinates outside 0 to 1.
enum address-mode {
repeat,
mirror-repeat,
clamp-to-edge,
}
enum filter-mode {
linear,
nearest,
}
/// How an image is sampled. The encoded image itself is `image-data`.
record image-sampler {
address-mode-u: option<address-mode>,
address-mode-v: option<address-mode>,
address-mode-w: option<address-mode>,
mag-filter: option<filter-mode>,
min-filter: option<filter-mode>,
mipmap-filter: option<filter-mode>,
/// Whether the image holds sRGB color, which is the default, rather
/// than linear data such as a normal map.
srgb: option<bool>,
}
/// Along the prim's Y axis, centered on its origin.
record collider-capsule {
/// Of the cylinder between the two hemispheres.
height: f32,
radius: f32,
}
/// Along the prim's Y axis, centered on its origin.
record collider-cylinder {
height: f32,
radius: f32,
}
/// A collision shape in the prim's own frame.
variant collider {
capsule(collider-capsule),
/// The hull of `collider-vertices`.
convex-hull,
/// A box of these full extents, centered on the origin.
cuboid(vec3),
cylinder(collider-cylinder),
/// A sphere of this radius.
sphere(f32),
/// The triangles of `collider-vertices` and `collider-indices`.
trimesh,
}
enum rigid-body-kind {
/// Moved by the simulation.
dynamic,
/// Moved only by its transform, pushing dynamic bodies aside.
kinematic,
/// Never moves.
%static,
}
/// Makes the prim's collider part of the simulation. Unset fields take
/// the simulation's defaults.
record rigid-body {
kind: rigid-body-kind,
/// Kilograms.
mass: option<f32>,
friction: option<f32>,
/// 0 for no bounce, 1 for a perfectly elastic one.
restitution: option<f32>,
linear-damping: option<f32>,
angular-damping: option<f32>,
}
/// 128 bits shared by the two halves of a two-way portal.
type link-id = tuple<u64, u64>;
record portal-destination {
/// The space the portal looks into.
space: document-id,
/// Pairs this portal with the one in `space` bearing the same link. A
/// destination without one is one-way.
link: option<link-id>,
}
/// A rectangular window into another space, in the prim's XY plane,
/// facing +Z.
record portal {
width: f32,
height: f32,
destination: option<portal-destination>,
}
/// Marks the prim as a spawn area: arriving users appear at a random
/// point within `radius` of its origin, on its XZ plane.
record spawn {
radius: f32,
}
enum text-align {
left,
center,
right,
}
/// Which line of the text block sits on the prim's origin.
enum text-anchor {
baseline,
top,
middle,
bottom,
}
enum text-billboard {
none,
/// Turns about Y to face the viewer, staying upright.
yaw,
/// Turns fully to face the viewer.
full,
}
/// A string drawn in the prim's XY plane, reading along +X. One style for
/// the whole string. There is no markup.
record text {
/// At most 4 KiB.
value: string,
/// Em height in meters.
size: option<f32>,
align: option<text-align>,
anchor: option<text-anchor>,
/// Wrap width in meters. Unset breaks only on newlines.
wrap: option<f32>,
/// A multiple of the font's line spacing.
line-height: option<f32>,
color: option<color>,
/// Drawn behind the glyphs.
outline: option<color>,
/// In ems.
outline-width: option<f32>,
/// Glow strength, 0 for none.
emissive: option<f32>,
billboard: option<text-billboard>,
}
/// A named link from one prim to another in the same document.
variant relation {
/// The shader graph this prim draws with, authored on another prim.
shader-binding,
base-color-texture,
emissive-texture,
metallic-roughness-texture,
normal-texture,
occlusion-texture,
/// The texture a shader graph samples at this slot, 0 to 3.
shader-texture(u8),
/// An application-defined relation, named `group/field`. The group must
/// not be one the host defines.
custom(string),
}
/// A property and its value. Each case names one key.
variant property {
/// The prim's parent, `none` for a root of the document.
parent(option<prim-id>),
/// At most 1 KiB.
name(string),
/// Relative to the parent, or to the document for a root.
transform(transform),
/// Places another document under this prim, as it currently stands.
reference(document-id),
mesh-topology(topology),
mesh-vertices(vertex-stream),
/// Indices into the vertex streams. At most 4 MiB.
mesh-indices(list<u32>),
material(material),
image-sampler(image-sampler),
/// An encoded PNG, JPEG or KTX2 image. At most 16 MiB.
image-data(list<u8>),
text(text),
collider(collider),
/// Packed XYZ positions. At most 4 MiB.
collider-vertices(list<f32>),
/// Triangle indices into `collider-vertices`. At most 4 MiB.
collider-indices(list<u32>),
rigid-body(rigid-body),
/// A multiple of the world's gravity. Defaults to 1.
gravity-scale(f32),
portal(portal),
spawn(spawn),
relation(tuple<relation, prim-id>),
/// Application data under `group/field`, outside the groups the host
/// defines. At most 64 KiB. The host stores and replicates the bytes but
/// never reads them.
custom(tuple<string, list<u8>>),
}
/// Names a property without its value.
variant property-key {
parent,
name,
transform,
reference,
mesh-topology,
mesh-vertices(vertex-attribute),
mesh-indices,
material,
image-sampler,
image-data,
text,
collider,
collider-vertices,
collider-indices,
rigid-body,
gravity-scale,
portal,
spawn,
relation(relation),
custom(string),
}
}
interface document {
use wired:core/math.{transform};
use wired:core/ids.{document-id, prim-id, prim-ref};
use wired:core/error.{error};
use properties.{property, property-key};
/// Where a write lands. Neither layer is durable.
enum layer {
/// This peer only, gone when the document unloads.
///
/// Every peer runs the same scripts, so a deterministic local write is
/// seen everywhere without being sent anywhere. Reach for `shared` when
/// the value depends on one peer, such as user input.
local,
/// Every peer present in the space, live, until this peer leaves.
///
/// Each peer's shared writes form its own layer on the document, and each
/// viewer decides what that layer contributes. The author's layer
/// contributes every property and is the strongest. Any other peer's
/// contributes only properties in the `state` group, and a portal pairing
/// when the viewer trusts that peer to open portals. Its other writes are
/// kept but have no effect. Relations cannot be shared.
shared,
}
variant edit {
/// Sets a property. Setting `parent` moves the prim. The prim must exist,
/// and the new parent must not lie beneath it.
set(tuple<prim-id, property>),
/// Drops this layer's value for a key, so weaker layers show through.
/// A `shared` value stays stated for the session, so clearing one states
/// that the key has no value, hiding weaker layers too. `parent` cannot be
/// cleared. Use `remove`.
clear(tuple<prim-id, property-key>),
/// Takes a prim and everything beneath it out of the scene.
remove(prim-id),
}
/// What a placed document's root hangs from.
variant anchor {
/// The root of the space.
space,
/// A prim of any loaded document.
prim(prim-ref),
}
/// A document in the world. Holding one is the capability to read it, and
/// to write it when the script may write it. Writes to a document the
/// script may not write fail with `forbidden`.
resource document {
id: func() -> document-id;
/// Whether the script may write this document: its own, one it created,
/// or one authored by the same user as its own.
may-write: func() -> bool;
/// Whether `prim` is in the scene.
contains: func(prim: prim-id) -> bool;
/// Prims with no parent, in id order.
roots: func() -> list<prim-id>;
/// Every prim in the scene, in no particular order.
prims: func() -> list<prim-id>;
/// In id order. Empty for a prim not in the scene.
children: func(prim: prim-id) -> list<prim-id>;
/// Every prim whose `name` is exactly `name`.
find-by-name: func(name: string) -> list<prim-id>;
/// The composed value of a property: the strongest layer's. For
/// `transform` that is the stated value, not where physics has since
/// moved the prim. See `world-transform`.
get: func(prim: prim-id, key: property-key) -> option<property>;
/// `get` for many keys in one call, answered in order.
get-many: func(keys: list<tuple<prim-id, property-key>>) -> list<option<property>>;
/// Every key that has a value on `prim`.
keys: func(prim: prim-id) -> list<property-key>;
/// Where `prim` is in the world this frame, including what physics and
/// animation did to it since its `transform` was written.
world-transform: func(prim: prim-id) -> result<transform, error>;
/// The transform from this document's root to `other`'s, or `none` when
/// either is not placed.
offset-to: func(other: borrow<document>) -> option<transform>;
/// Adds a prim under `parent`, or as a root, and answers its id.
create-prim: func(layer: layer, parent: option<prim-id>) -> result<prim-id, error>;
/// Applies every edit in `layer` together, or none of them, so peers
/// never see half a batch. At most 4096 edits. In `local`, an edit naming
/// a prim the document does not hold fails the batch with `not-found`.
apply: func(layer: layer, edits: list<edit>) -> result<_, error>;
/// Makes the live value of each key durable, in the document itself
/// when this peer can write it, or as an override in the document
/// referencing it. Fails with `forbidden` when it can write neither.
///
/// Takes effect only on the device that authors the script's own
/// document. Elsewhere it succeeds and does nothing, so a script running
/// on every peer commits once. A script outside any space runs on one
/// peer, which always commits.
commit: func(keys: list<tuple<prim-id, property-key>>) -> result<_, error>;
/// Puts the document into the scene, or moves it, at `offset` from
/// `anchor`. Per peer, never stored.
///
/// A created document is held out of the scene until it is placed, so it
/// can be built in full and then appear at once.
place: func(anchor: anchor, offset: transform) -> result<_, error>;
}
/// The document the calling script belongs to.
script-document: func() -> result<document, error>;
/// The prim the calling script is attached to, in its own document.
script-prim: func() -> prim-id;
/// A document loaded on this peer, or `none` when it is not.
open-document: func(id: document-id) -> result<option<document>, error>;
/// Mints an empty document the script may write, held until it is placed.
create-document: func() -> result<document, error>;
/// Mints a document holding what `source` durably holds, which the two
/// then diverge from. Live layers are not copied.
copy-document: func(source: borrow<document>) -> result<document, error>;
/// Removes a document this script created from the world, and deletes
/// this peer's copy of it.
delete-document: func(document: document) -> result<_, error>;
}