@es3d/es3d 1.0.0 → 1.0.1
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/LICENSE +201 -201
- package/README.md +86 -86
- package/docs/SPEC.md +417 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +377 -377
- package/src/cache/geometry-store.js +392 -392
- package/src/cache/mesh-bundle.js +123 -123
- package/src/cache/sha256.js +73 -73
- package/src/format/ast.js +238 -238
- package/src/format/colors.js +93 -93
- package/src/format/compact-parser.js +171 -171
- package/src/format/compact-tokenizer.js +165 -165
- package/src/format/errors.js +247 -247
- package/src/format/json.js +55 -55
- package/src/format/limits.js +14 -14
- package/src/format/parse.js +92 -92
- package/src/format/schema.js +617 -617
- package/src/format/serializer.js +333 -333
- package/src/format/validator.js +330 -330
- package/src/format/value-error.js +12 -12
- package/src/format/value-format.js +314 -314
- package/src/format/values.js +459 -459
- package/src/format/xml-parser.js +136 -136
- package/src/format/xml-tokenizer.js +237 -237
- package/src/geometry/contour.js +86 -86
- package/src/geometry/extrude.js +175 -175
- package/src/geometry/gear.js +85 -85
- package/src/geometry/geometry-cache.js +159 -159
- package/src/geometry/lod-error.js +178 -178
- package/src/geometry/mesh.js +458 -458
- package/src/geometry/primitives.js +299 -299
- package/src/geometry/registry.js +645 -645
- package/src/geometry/revolve.js +136 -136
- package/src/geometry/sweep.js +441 -441
- package/src/index.js +47 -47
- package/src/io/compression.js +67 -67
- package/src/io/convert.js +236 -236
- package/src/io/download.js +13 -13
- package/src/io/external-scene.js +127 -127
- package/src/io/file-loader.js +28 -28
- package/src/io/gltf.js +630 -630
- package/src/io/mesh-import.js +104 -104
- package/src/io/obj.js +352 -352
- package/src/io/step-brep.js +439 -439
- package/src/io/step-exact.js +1149 -1149
- package/src/io/step.js +1418 -1418
- package/src/io/stl.js +193 -193
- package/src/io/svg-import.js +787 -787
- package/src/material/materials.js +154 -154
- package/src/math/mat4.js +475 -475
- package/src/math/random.js +30 -30
- package/src/math/trig.js +65 -65
- package/src/math/vec3.js +81 -81
- package/src/optimize/optimizer.js +579 -579
- package/src/path/path-flatten.js +322 -322
- package/src/path/path-parser.js +332 -332
- package/src/path/shapes.js +129 -129
- package/src/path/triangulate.js +517 -517
- package/src/pipeline.js +74 -74
- package/src/render/camera.js +168 -168
- package/src/render/frame-builder.js +362 -362
- package/src/render/geometry-pool.js +149 -149
- package/src/render/lighting.js +149 -149
- package/src/render/orbit-controls.js +237 -237
- package/src/render/renderer.js +258 -249
- package/src/render/scroll-timeline.js +104 -104
- package/src/render/shaders/glsl.js +285 -285
- package/src/render/shaders/wgsl.js +252 -252
- package/src/render/stage.js +364 -352
- package/src/render/texture-cache.js +40 -40
- package/src/render/textures.js +223 -223
- package/src/render/webgl2/webgl2-backend.js +586 -532
- package/src/render/webgpu/webgpu-backend.js +482 -480
- package/src/scene/flatten.js +754 -754
- package/src/scene/picking.js +161 -161
- package/src/scene/scene-graph.js +320 -320
- package/src/transform/transform.js +211 -211
- package/src/wasm/kernels-binary.js +3 -3
- package/src/wasm/kernels.js +385 -385
package/docs/SPEC.md
CHANGED
|
@@ -1,417 +1,417 @@
|
|
|
1
|
-
# ES3D: Extensible Scalable 3D
|
|
2
|
-
|
|
3
|
-
**Format specification, version 1** · file extension `.es3d` · namespace `https://es3d.org/ns/1`
|
|
4
|
-
|
|
5
|
-
ES3D is a declarative scene format in the spirit of SVG. A document says what a scene contains: boxes, spheres, extruded outlines, instanced groups, lights, cameras. It does not store the triangles a modelling tool happened to emit. A reader turns that description into GPU geometry, at whatever tessellation the view needs.
|
|
6
|
-
|
|
7
|
-
This document defines the semantics. The complete list of elements, attributes, value types, defaults and ranges is generated from the reference implementation's schema in [REFERENCE.md](REFERENCE.md). Where this text and the reference implementation disagree, that is a bug in one of them; please report it.
|
|
8
|
-
|
|
9
|
-
Key words: **must**, **must not**, **should** and **may** are used as in RFC 2119.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## 1. Design goals and non-goals
|
|
14
|
-
|
|
15
|
-
Goals:
|
|
16
|
-
|
|
17
|
-
1. **Human-readable and writable.** A useful scene fits on a screen and can be written by hand, generated by a template, or diffed in version control.
|
|
18
|
-
2. **Primitive- and transform-based.** Geometry is described by parameters (a sphere is a radius), so size does not depend on tessellation and the reader can choose detail per view.
|
|
19
|
-
3. **Reuse is first class.** Definitions (`<defs>`, `<use>`), linear patterns (`<repeat>`) and deterministic procedural placement (`<scatter>`) keep repetitive scenes small, and map directly to GPU instancing.
|
|
20
|
-
4. **Strict.** Malformed input is reported with a location and, where possible, a suggestion. A reader must not silently drop or "repair" geometry.
|
|
21
|
-
5. **Lossless round trips.** Reading and writing a document must not change its meaning; the reference implementation preserves attribute spelling and comments as well.
|
|
22
|
-
|
|
23
|
-
Non-goals, by design: version 1 describes scenes built from shapes, paths and repetition. It does not describe skinning, morph targets or physics, and it is not a CAD format. There is no boundary representation and no exact Boolean.
|
|
24
|
-
|
|
25
|
-
Version 1 also refuses a few things that stalled SVG:
|
|
26
|
-
|
|
27
|
-
- **One document, not a family of profiles.** Compact syntax and JSON are encodings of the same tree. They are not subsets that omit elements. SVG Tiny, Basic and Full left authors guessing which half of the spec a viewer had, and SVG 1.2 Full never shipped.
|
|
28
|
-
- **No script, no event handlers, no style sheets.** A file is a scene. Opening it does not run a program. SVG's mix of picture and script is why "using it as an image" later needed a separate security mode.
|
|
29
|
-
- **No required namespace.** An `xmlns` attribute may appear and is ignored. Leaving it out is not an error. A missing namespace was a common reason early SVG files rendered as an XML error page.
|
|
30
|
-
- **No custom entities.** Only `<`, `>`, `&`, `"`, `'` and numeric character references are expanded. A DOCTYPE is ignored and cannot declare entities, so a document cannot expand itself the way a billion-laughs payload does.
|
|
31
|
-
- **No special compressed extension.** Gzip is `.es3d.gz`. SVG's `.svgz` was often gzipped a second time, or served with the wrong media type. Until `model/vnd.es3d` is registered, servers may send the text as `text/plain; charset=utf-8`.
|
|
32
|
-
|
|
33
|
-
## 2. Conformance
|
|
34
|
-
|
|
35
|
-
A **conforming document** is a document that parses in one of the surface syntaxes of section 4, has an `es3d` root element with `version="1"` (or `1.x`), and produces no errors under the rules of this specification and the schema in REFERENCE.md.
|
|
36
|
-
|
|
37
|
-
A **conforming reader**:
|
|
38
|
-
|
|
39
|
-
- must reject documents with errors, reporting each error with a stable code (section 15), a message, and a location (line and column for text input, an element path otherwise);
|
|
40
|
-
- must report all errors it can find, not only the first (syntax errors that make the rest of the document unreadable are the exception);
|
|
41
|
-
- must not render a document with errors as if it were valid (the reference viewer keeps showing the last valid state and marks it stale);
|
|
42
|
-
- must produce, for every `<scatter>`, the instances defined in section 12.3 (procedural placement is part of the format, not a rendering hint);
|
|
43
|
-
- may choose tessellation freely, within the accuracy rules of section 8.1.
|
|
44
|
-
|
|
45
|
-
The legacy root name `<v3d>` is accepted with a warning (`W_LEGACY_ROOT`).
|
|
46
|
-
|
|
47
|
-
## 3. Coordinate system, units and conventions
|
|
48
|
-
|
|
49
|
-
- Right-handed coordinates, **+Y up**. Cameras look down their local **−Z** axis.
|
|
50
|
-
- Lengths are plain numbers. The root attribute `unit` (`m` by default) is informative only; readers must not rescale.
|
|
51
|
-
- Angles are in **degrees** everywhere (rotations, field of view, arcs, sweeps).
|
|
52
|
-
- Colours are **sRGB**; readers convert to linear space for lighting.
|
|
53
|
-
- 2D outline data (path data, polygons) lives in the local **XY plane with y up**. SVG's y axis points down. A path drawn in SVG coordinates can be used with `scale="1,-1,1"`.
|
|
54
|
-
- Every primitive's default size fits the unit cube centred at the origin, so `<box/>`, `<sphere/>`, `<cylinder/>` … are all visible and directly comparable.
|
|
55
|
-
|
|
56
|
-
## 4. Surface syntaxes
|
|
57
|
-
|
|
58
|
-
All syntaxes produce the same abstract tree of elements, attributes, text and comments. Attribute values are strings in the tree; their typed interpretation (section 6) is defined by the schema. A reader detects the syntax from the first significant character: `<` markup, `[` or `{` JSON, anything else compact. There is no binary encoding of the document.
|
|
59
|
-
|
|
60
|
-
### 4.1 Markup (canonical)
|
|
61
|
-
|
|
62
|
-
An XML subset:
|
|
63
|
-
|
|
64
|
-
```xml
|
|
65
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
66
|
-
<es3d version="1">
|
|
67
|
-
<material id="brass" color="#c9a227" metallic="1" roughness="0.35"/>
|
|
68
|
-
<group position="0,1,0" rotation="0,30,0">
|
|
69
|
-
<box size="2,0.2,1" material="brass"/>
|
|
70
|
-
<sphere radius="0.4" position="0,0.5,0" color="tomato"/>
|
|
71
|
-
</group>
|
|
72
|
-
</es3d>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Supported: elements, single- or double-quoted attributes, comments, the five predefined entities and numeric character references, CDATA sections (treated as text), an optional XML declaration, and a DOCTYPE that is ignored. The DOCTYPE is not read for entity declarations. Not supported (errors): unquoted attribute values, duplicate attributes, text in elements that do not accept text, more than one root element, and any entity other than the five predefined ones. Element and attribute names are case-sensitive. Namespace prefixes are not interpreted. `xmlns` attributes are allowed and ignored, and a document does not need one.
|
|
76
|
-
|
|
77
|
-
### 4.2 Compact syntax (ES3D-C)
|
|
78
|
-
|
|
79
|
-
A brace-structured syntax for hand writing. On the example scenes it is 15–23 % smaller than minified markup and about half the size of indented markup (after gzip the difference shrinks to 5–12 %):
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
es3d version=1
|
|
83
|
-
// comments: // line and /* block */
|
|
84
|
-
material#brass c=#c9a227 metallic=1 roughness=0.35
|
|
85
|
-
group p=0,1,0 rot=0,30,0 {
|
|
86
|
-
box s=2,0.2,1 m=brass
|
|
87
|
-
sphere r=0.4 p=0,0.5,0 c=tomato
|
|
88
|
-
}
|
|
89
|
-
use @tree p=10,0,5
|
|
90
|
-
title "A \"quoted\" title"
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Grammar (newlines are ordinary whitespace):
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
document := comment* root
|
|
97
|
-
root := ('es3d' | 'v3d') header ( '{' statement* '}' | statement* )
|
|
98
|
-
statement := element | comment | ';'
|
|
99
|
-
element := NAME header ( '{' statement* '}' )?
|
|
100
|
-
header := ( '#' ID )? ( '@' REF )? STRING? ( NAME '=' VALUE )*
|
|
101
|
-
VALUE := bare-value | '"' escaped-string '"'
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
`#x` is shorthand for `id="x"`, `@x` for `ref="x"`, and a quoted string in header position is the element's text content. A bare value runs until whitespace, `{`, `}`, `;` or `"`; values containing those characters (transforms with spaces, path data) must be quoted. An element header ends at the first token that is not `NAME=`, so the grammar is LL(2) and needs no schema knowledge.
|
|
105
|
-
|
|
106
|
-
### 4.3 JSON (JsonML)
|
|
107
|
-
|
|
108
|
-
`["es3d", {"version": "1"}, ["box", {"size": "2"}], ["title", {}, "text"]]`. An element is an array of name, attribute object (every value a string) and children. Strings are text. Comments are `["#comment", "text"]`. This form exists for tooling that prefers JSON. It is larger than the markup, and errors inside values have no line or column. It is an encoding of the same tree, not a reduced profile.
|
|
109
|
-
|
|
110
|
-
### 4.4 Document encoding
|
|
111
|
-
|
|
112
|
-
A document is UTF-8 text in one of the three syntaxes above. Gzip (`.es3d.gz`) is ordinary HTTP compression of that text. Meshes that have no procedural description stay inline as `<mesh>` (section 10). The format does not reference glTF, OBJ or any other container for its geometry.
|
|
113
|
-
|
|
114
|
-
### 4.5 Attribute aliases
|
|
115
|
-
|
|
116
|
-
Frequently used attributes have short aliases that are equivalent everywhere (an element must not give both forms): `p` position, `rot` rotation, `sc` scale, `t` transform, `m` material, `c` color, `s` size, `r` radius, `h` height, `n` count, `dir` direction, `href` ref. Writers producing minified output use the aliases; pretty output uses the full names.
|
|
117
|
-
|
|
118
|
-
## 5. Document structure
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
<es3d version="1" [unit] [name]>
|
|
122
|
-
<title>, <desc> optional metadata (text)
|
|
123
|
-
<environment> background and ambient light (first one wins)
|
|
124
|
-
<controls> how viewers let people move the camera (first one wins)
|
|
125
|
-
<material id> materials may appear anywhere; they are document-wide
|
|
126
|
-
<defs> definitions referenced by id; not rendered directly
|
|
127
|
-
<scene> optional wrapper; behaves like <group>
|
|
128
|
-
scene nodes … shapes, containers, <use>, lights, cameras
|
|
129
|
-
</es3d>
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Each element's allowed children are listed in REFERENCE.md. Placing an element where it is not allowed is an error (`E_MISPLACED_ELEMENT`). Ids are unique per document (`E_DUPLICATE_ID`) and are referenced by `material`, `ref`, `path` and `randomMaterial` attributes; references are checked for existence (`E_UNKNOWN_REFERENCE`, with a suggestion), kind (`E_REFERENCE_KIND`) and cycles (`E_CIRCULAR_REFERENCE`).
|
|
133
|
-
|
|
134
|
-
## 6. Values
|
|
135
|
-
|
|
136
|
-
The exact syntax of every value type is listed at the end of REFERENCE.md. General rules:
|
|
137
|
-
|
|
138
|
-
- Numbers follow the SVG/CSS number grammar: optional sign, digits with an optional fraction or a leading `.`, optional exponent (`-0.5`, `.5`, `2e-3`). `NaN`, `Infinity` and hexadecimal are not numbers.
|
|
139
|
-
- Lists accept commas, whitespace or both as separators (`1,2,3`, `1 2 3`, `1, 2, 3`), but not empty items.
|
|
140
|
-
- Vectors have fixed arity; `size` and `scale` also accept one number for all components.
|
|
141
|
-
- Booleans are `true` or `false`.
|
|
142
|
-
- An attribute that fails to parse is an error (`E_INVALID_VALUE`) located inside the value where possible; a value outside its range is `E_RANGE`.
|
|
143
|
-
- Cross-attribute rules (for example "`bevel` must not exceed half the `depth`", "give `count` or `grid`, not both") are errors, never silent clamping.
|
|
144
|
-
|
|
145
|
-
## 7. Transforms and the scene hierarchy
|
|
146
|
-
|
|
147
|
-
Every scene node has a **local matrix**
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
local = transform · T(position) · R(rotation) · S(scale)
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
and its world matrix is `parentWorld · local`. Consequently `position`, `rotation` and `scale` are applied *inside* the `transform` attribute, like `x`/`y` inside the transform of an SVG `<use>`.
|
|
154
|
-
|
|
155
|
-
- `rotation="x,y,z"` is in degrees and applied about X first, then Y, then Z: `R = Rz · Ry · Rx`. Sine and cosine of rotation angles are the functions in section 12.3, not the platform `sin` and `cos`.
|
|
156
|
-
- `scale` may be negative (mirroring); readers must keep faces outward-facing by flipping the winding of mirrored instances.
|
|
157
|
-
- `transform` is an SVG-style list of functions composed **left to right**: `translate(1,0,0) rotateY(90) scale(2)` means `T · Ry · S`; the rightmost function acts on the object first. Functions: `translate(x[,y[,z]])`, `rotate(x,y,z)` (Euler degrees), `rotate(a,ax,ay,az)` (axis-angle; zero axis is an error), `rotateX(a)`, `rotateY(a)`, `rotateZ(a)`, `scale(s)` or `scale(x,y,z)` (two arguments are an error), `quat(x,y,z,w)`, `matrix(m0 … m15)` (column-major, like CSS `matrix3d`). `rotate(45)` is an error, with a hint to use `rotateY(45)`. The 2D SVG meaning of a single angle is ambiguous in 3D.
|
|
158
|
-
- `visible="false"` hides a node and its subtree (they still exist and can be referenced).
|
|
159
|
-
|
|
160
|
-
## 8. Shapes
|
|
161
|
-
|
|
162
|
-
Each shape element produces one renderable geometry in its local frame, centred at the origin unless stated otherwise. Dimensions, defaults and ranges are listed in REFERENCE.md.
|
|
163
|
-
|
|
164
|
-
| Element | Local definition |
|
|
165
|
-
|---|---|
|
|
166
|
-
| `box` | axis-aligned box, `size` = edge lengths; `radius` rounds all edges |
|
|
167
|
-
| `sphere` | radius `radius` |
|
|
168
|
-
| `cylinder` | along Y from −h/2 to +h/2; `radiusTop` makes a truncated cone; `caps="false"` leaves it open |
|
|
169
|
-
| `cone` | base at −h/2, apex at +h/2 |
|
|
170
|
-
| `capsule` | along Y, total height `height` including the hemispherical caps |
|
|
171
|
-
| `plane` | rectangle in XZ facing +Y, double-sided |
|
|
172
|
-
| `torus` | around Y; `radius` to the tube centre, `tube` tube radius, `arc` partial sweep from +X towards −Z |
|
|
173
|
-
| `line` | from `from` to `to`; a hairline, or a tube of `radius` |
|
|
174
|
-
| `polyline` | 3D `points` or the outline of 2D path data `d`; `smooth` = Catmull-Rom; hairline or tube |
|
|
175
|
-
| `polygon` | filled flat polygon in XY, double-sided |
|
|
176
|
-
| `path` | filled flat 2D path in XY, double-sided; inside `<defs>` a reusable outline |
|
|
177
|
-
| `extrude` | 2D path extruded from z = 0 to z = `depth` (or centred with `center="true"`), optional rounded `bevel` |
|
|
178
|
-
| `revolve` | 2D profile revolved around Y; profile x = distance from the axis, y = height |
|
|
179
|
-
| `sweep` | circle (`radius`) or 2D profile swept along a 3D spine (`along`) with rotation-minimising frames, optional `twist` and `taper` |
|
|
180
|
-
| `gear` | involute spur gear with `teeth`, `module`, `pressureAngle`, extruded by `depth`, optional `bore` and `bevel` |
|
|
181
|
-
| `mesh` | explicit triangles (section 10) |
|
|
182
|
-
|
|
183
|
-
### 8.1 Tessellation
|
|
184
|
-
|
|
185
|
-
Shapes are exact mathematical surfaces. A reader may choose the triangles, and every generated mesh must stay within that level's chordal-error bound: the maximum distance from the mesh to the true surface, measured in the generator's canonical units. The bound is part of the geometry. Identical triangles are not required. Platform `sin` and `cos` may be used while tessellating; placement (section 12.3) may not.
|
|
186
|
-
|
|
187
|
-
The reference reader keeps up to four levels. At quality 1 the segment count is `max(minimum, round(base / 2^level))`:
|
|
188
|
-
|
|
189
|
-
| Shape | Base | Minimum | Levels |
|
|
190
|
-
|---|---:|---:|---|
|
|
191
|
-
| Sphere, cylinder, cone | 48 | 8 | 48, 24, 12, 8 |
|
|
192
|
-
| Capsule | 32 | 8 | 32, 16, 8, 8 |
|
|
193
|
-
| Torus sweep / tube | 64 / 24 | 8 / 6 | 64, 32, 16, 8 and 24, 12, 6, 6 |
|
|
194
|
-
| Rounded box | 4 | 1 | 4, 2, 1, 1 |
|
|
195
|
-
|
|
196
|
-
A `segments` attribute (or both torus segment counts) requests one fixed tessellation, which is part of the shape. A `segments="6"` cylinder is a hexagonal prism at every distance. Exact shapes (box, plane, polygon, hairline, authored mesh) have one level and a bound of 0.
|
|
197
|
-
|
|
198
|
-
Each level stores its bound. A frame may draw a level only while `bound × scale × pixelsPerUnit` is at most 1. `scale` is the longest column of the instance matrix. `pixelsPerUnit` is the projected size of one canonical unit. The reference reader picks the coarsest level that satisfies that test. A renderer `lodBias` above 1 (default 1) divides the one-pixel budget; it is not a document attribute. While a level is still being generated, a renderer may draw a coarser one in its place for a few frames (progressive refinement), as long as it keeps drawing until every level passes the test. For a unit sphere the switches fall near 59, 15 and 7 pixels of the sphere's own radius.
|
|
199
|
-
|
|
200
|
-
Closed shapes must produce closed, outward-facing surfaces. Open or flat shapes (planes, polygons, paths, open revolves, `caps="false"`) are rendered double-sided.
|
|
201
|
-
|
|
202
|
-
## 9. Path data
|
|
203
|
-
|
|
204
|
-
`d` attributes use SVG path syntax with all commands, absolute and relative: `M L H V C S Q T A Z` (plus implicit repetition, `m` followed by implicit `l`, and the SVG separator rules). Arcs follow the SVG implementation notes (radii scaled up when too small). Coordinates are in the local XY plane, **y up**.
|
|
205
|
-
|
|
206
|
-
- **Subpaths and fill rules.** A path can contain several closed subpaths. `fillRule` (`nonzero`, default, or `evenodd`) decides which nested contours become holes, exactly as SVG's `fill-rule`. Surfaces are generated where the fill changes, so same-direction nested contours under `nonzero` produce internal walls only where the filled region actually changes.
|
|
207
|
-
- **Open subpaths** are closed implicitly for filling (SVG behaviour); `revolve` uses open profiles as-is.
|
|
208
|
-
- **Degenerate input** is reported, never repaired: subpaths without area produce `W_DEGENERATE_CONTOUR`, a fill that leaves nothing to fill `W_EMPTY_FILL`, and a self-intersecting contour `W_SELF_INTERSECTION` stating how much of the expected area the triangulation covers. Readers must not invent geometry for invalid outlines.
|
|
209
|
-
- `path="id"` references a `<path>` in `<defs>` instead of inline data; a shape gives either `d` or `path`.
|
|
210
|
-
- 3D path data (sweep spines) uses `M L C S Q T Z` with three numbers per point; `H`, `V` and `A` are errors in 3D.
|
|
211
|
-
|
|
212
|
-
`extrude` caps the front and back with the filled region, builds walls from every contour, and offsets the caps inwards by the bevel radius with a quarter-round profile. `revolve` profiles must stay at x ≥ 0 (`E_REVOLVE_AXIS`); closed profiles produce closed solids, open profiles open surfaces, and faces are oriented outwards automatically. `sweep` frames are rotation-minimising (double reflection); closed spines are corrected so the frames meet without a seam.
|
|
213
|
-
|
|
214
|
-
## 10. Meshes (escape hatch)
|
|
215
|
-
|
|
216
|
-
`<mesh positions="x y z …" indices="i j k …"/>` stores triangles explicitly, for content that has no procedural description (scans, sculpted shapes). `normals` and `uvs` are optional; without normals the reader computes them with the crease angle `smooth` (degrees; 0 = flat, 180 = fully smooth). Mesh arrays are float32 data. Index and array-length errors are reported (`E_MESH`), never truncated. A `<mesh>` in `<defs>` can be instanced with `<use>` like anything else.
|
|
217
|
-
|
|
218
|
-
A writer that emits `<mesh>` from floating-point data rounds positions so the error is at most 10⁻⁵ of the scene extent, and rounds normals to two decimal places. Authored documents may use more digits; readers still store the values as float32.
|
|
219
|
-
|
|
220
|
-
## 11. Materials
|
|
221
|
-
|
|
222
|
-
`<material>` defines a metallic/roughness material in the glTF channel layout, stored on the element itself:
|
|
223
|
-
|
|
224
|
-
- `color` is the base colour (sRGB). Its alpha multiplies `opacity`.
|
|
225
|
-
- `metallic` and `roughness` are factors in 0…1.
|
|
226
|
-
- `metallicRoughnessMap` is a linear texture: green multiplies roughness, blue multiplies metallic.
|
|
227
|
-
- `occlusionMap` is a linear texture. Red darkens indirect light (hemisphere and environment), mixed by `occlusionStrength` (default 1). It does not scale direct lights.
|
|
228
|
-
- `emissive` × `emissiveIntensity`, multiplied by `emissiveMap` (sRGB).
|
|
229
|
-
- `map` (base colour, sRGB) and `normalMap` (linear, scaled by `normalScale`). A texture is an image URL or a procedural texture such as `checker(8)` or `bumps(12,0.6)`. `uvScale` repeats the coordinates.
|
|
230
|
-
- `alphaMode` is `auto` (default), `opaque`, `mask` or `blend`. `auto` is `blend` when `opacity` × colour alpha is below 0.999 and `opaque` otherwise. `opaque` ignores alpha. `mask` discards fragments below `alphaCutoff` (default 0.5). `blend` uses opacity as coverage. Texture alpha is visible only with `alphaMode="blend"` and an opacity below 1.
|
|
231
|
-
- `doubleSided` and `unlit`.
|
|
232
|
-
|
|
233
|
-
Texture coordinates, before `uvScale`:
|
|
234
|
-
|
|
235
|
-
- Sphere: `u` is longitude, 0 at +X, increasing through −Z; `v` is 1 at the north pole and 0 at the south pole. The seam is the +X meridian.
|
|
236
|
-
- Cylinder and cone: `u` runs around the axis, 0 at +X; `v` is 0 at the bottom and 1 at the top. Caps use a planar projection in XZ, centred at (0.5, 0.5).
|
|
237
|
-
- Plane: the unit square in XZ, `u` along +X and `v` along −Z.
|
|
238
|
-
- Box: each face is 0…1, with the face's normal as the projection axis.
|
|
239
|
-
- Torus: `u` along the sweep from the +X side, `v` around the tube.
|
|
240
|
-
|
|
241
|
-
Inheritance follows SVG's `fill`:
|
|
242
|
-
|
|
243
|
-
- `material="id"` on any node sets the material for the node and its descendants, and **clears** an inherited colour override.
|
|
244
|
-
- `color="…"` on any node overrides the base colour (of the inherited or own material) for the node and its descendants.
|
|
245
|
-
- A `<use>` passes its material and colour down into the referenced content as *inherited* values; values set inside the definition win.
|
|
246
|
-
- Without any material, shapes use the default material (light grey, roughness 0.5).
|
|
247
|
-
|
|
248
|
-
Identical parameter sets are the same material; readers should deduplicate them (the reference reader binds each distinct material once per frame).
|
|
249
|
-
|
|
250
|
-
## 12. Reuse and procedural placement
|
|
251
|
-
|
|
252
|
-
### 12.1 `<defs>` and `<use>`
|
|
253
|
-
|
|
254
|
-
Children of `<defs>` are not rendered. `<use ref="id">` renders the referenced element (from `<defs>` or anywhere else in the document) as if it were a child of the `<use>`, under the use's transform and style. A definition referenced N times exists once; readers should render repeated geometry with instancing. Reference cycles are errors.
|
|
255
|
-
|
|
256
|
-
### 12.2 `<repeat>`
|
|
257
|
-
|
|
258
|
-
`<repeat count="n" step="transform">` renders its children n times. Copy i (0-based) is placed with `step` applied i times:
|
|
259
|
-
|
|
260
|
-
```
|
|
261
|
-
world(copy i) = world(repeat) · stepⁱ · local(child)
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
so `step="translate(0,0.2,0.3) rotateY(15)"` builds a spiral staircase. `count="0"` renders nothing.
|
|
265
|
-
|
|
266
|
-
### 12.3 `<scatter>` (normative algorithm)
|
|
267
|
-
|
|
268
|
-
`<scatter>` places `count` copies of its children (or `grid="nx,nz"` copies) in its local XZ plane. Every reader must implement this algorithm exactly, in IEEE 754 binary64 with round-to-nearest. The random sequence is integer arithmetic. `sqrt` is `Math.sqrt` (correctly rounded). Disc positions and every rotation matrix use `es3dSin` and `es3dCos` below, in the order written, so two readers produce the same bits.
|
|
269
|
-
|
|
270
|
-
1. The random generator is **mulberry32** seeded with the unsigned 32-bit `seed` (default 1):
|
|
271
|
-
```js
|
|
272
|
-
function mulberry32(seed) {
|
|
273
|
-
let a = seed >>> 0;
|
|
274
|
-
return () => {
|
|
275
|
-
a = (a + 0x6d2b79f5) >>> 0;
|
|
276
|
-
let t = a;
|
|
277
|
-
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
278
|
-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
279
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
```
|
|
283
|
-
2. For copy i = 0 … n−1, draw exactly nine values u0 … u8 in order (all nine are drawn even when unused, so adding an attribute never changes the positions of other attributes' effects).
|
|
284
|
-
3. Position (y = 0):
|
|
285
|
-
- grid: ix = i mod nx, iz = ⌊i / nx⌋; x = (ix − (nx−1)/2)·spacingₓ + (u0 − ½)·jitter·spacingₓ, z = (iz − (nz−1)/2)·spacing_z + (u1 − ½)·jitter·spacing_z;
|
|
286
|
-
- disc (`radius` R): r = R·√u0, a = u1·2π, x = r·es3dCos(a), z = r·es3dSin(a);
|
|
287
|
-
- rectangle (`area` w,d): x = (u0 − ½)·w, z = (u1 − ½)·d.
|
|
288
|
-
4. Rotation (degrees, Euler as in section 7): component k = lo_k + (hi_k − lo_k)·u(2+k) for the ranges of `randomRotation`.
|
|
289
|
-
5. Scale: uniform `randomScale` range → s = lo + (hi − lo)·u5 for all axes; per-axis ranges use u5, u6, u7.
|
|
290
|
-
6. Material: with `randomMaterial="a,b,c"`, copy i uses entry ⌊u8 · count⌋ (clamped to the last entry) and the inherited colour override is cleared.
|
|
291
|
-
7. world(copy i) = world(scatter) · T(position) · R(rotation) · S(scale) · local(child).
|
|
292
|
-
|
|
293
|
-
`grid` and `count` are mutually exclusive, as are `area` and `radius`; a random layout needs one of them.
|
|
294
|
-
|
|
295
|
-
`es3dSin` and `es3dCos` take radians. π, 2π, π/2 and π/4 are `Math.PI`, `Math.PI * 2`, `Math.PI * 0.5` and `Math.PI * 0.25`. A non-finite input is returned unchanged. Otherwise:
|
|
296
|
-
|
|
297
|
-
```
|
|
298
|
-
y = x - 2π · round(x / 2π)
|
|
299
|
-
sign = 1
|
|
300
|
-
if y < 0: sign = -1; y = -y
|
|
301
|
-
if y > π/2: y = π - y
|
|
302
|
-
if y < 0: y = 0
|
|
303
|
-
s = y > π/4 ? kernelCos(π/2 - y) : kernelSin(y)
|
|
304
|
-
es3dSin(x) = sign < 0 ? -s : s
|
|
305
|
-
es3dCos(x) = es3dSin(x + π/2)
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
`kernelSin` and `kernelCos` are Horner polynomials. `z = x*x`. Coefficients, as binary64 literals:
|
|
309
|
-
|
|
310
|
-
```
|
|
311
|
-
S1 = -1.66666666666666324348e-1
|
|
312
|
-
S2 = 8.33333333332248946124e-3
|
|
313
|
-
S3 = -1.98412698298579493134e-4
|
|
314
|
-
S4 = 2.75573137070700676789e-6
|
|
315
|
-
S5 = -2.50507602534068634195e-8
|
|
316
|
-
S6 = 1.58969099521155010221e-10
|
|
317
|
-
kernelSin(x) = x + (z*x) * (S1 + z * (S2 + z * (S3 + z * (S4 + z * (S5 + z * S6)))))
|
|
318
|
-
|
|
319
|
-
C1 = 4.16666666666666019037e-2
|
|
320
|
-
C2 = -1.38888888888741095749e-3
|
|
321
|
-
C3 = 2.48015872894767294178e-5
|
|
322
|
-
C4 = -2.75573143513906633035e-7
|
|
323
|
-
C5 = 2.0875723212981748279e-9
|
|
324
|
-
C6 = -1.13596475577881948265e-11
|
|
325
|
-
r = z * (C1 + z * (C2 + z * (C3 + z * (C4 + z * (C5 + z * C6)))))
|
|
326
|
-
kernelCos(x) = 1 - 0.5*z + z*r
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
Every add, subtract and multiply is one binary64 operation, in that order. `round` is `Math.round` (a tie at n+0.5 rounds towards +∞).
|
|
330
|
-
|
|
331
|
-
### 12.4 `<lod>`
|
|
332
|
-
|
|
333
|
-
`<lod distances="d1,d2,…">` has exactly one more child than distances (`E_LOD_CHILDREN`). Child i is rendered while the distance from the camera to the lod's world origin is below dᵢ (and at least dᵢ₋₁); the last child beyond the last distance. Distances are in the lod's local units (scaled by the cube root of the world scale's determinant). Exporters to static formats write child 0. Automatic per-primitive tessellation LOD (section 8.1) is independent of `<lod>`.
|
|
334
|
-
|
|
335
|
-
### 12.5 Resource limits
|
|
336
|
-
|
|
337
|
-
Exceeding a limit is an error. The document is not rendered as valid.
|
|
338
|
-
|
|
339
|
-
| Limit | Value | Code |
|
|
340
|
-
|---|---:|---|
|
|
341
|
-
| Expanded instances (`repeat`, `scatter`, `use`) | 2,000,000 | `E_TOO_MANY_INSTANCES` |
|
|
342
|
-
| Nesting, counting each element and each use-to-target step | 64 | `E_TOO_DEEP` |
|
|
343
|
-
| Commands in one path, after implicit repetition | 100,000 | `E_PATH_LIMIT` |
|
|
344
|
-
| Copies in one `repeat` or `scatter` | 1,000,000 | `E_RANGE` |
|
|
345
|
-
|
|
346
|
-
## 13. Lights, cameras and environment
|
|
347
|
-
|
|
348
|
-
- `<light type="directional|point|spot|ambient|hemisphere">` with `color`, `intensity`; directional and spot lights take `direction` or `target`; point and spot lights `position` and an optional cut-off `range`; spot lights `angle` (full cone, degrees) and `penumbra` (0–1); hemisphere lights `color` (sky) and `groundColor`. Lights are placed by their ancestors' transforms like any node. Without any light, readers use a default key light so content is visible.
|
|
349
|
-
- `<camera>` is perspective (`fov`, vertical, degrees) or orthographic (`height` of the visible area). Orientation comes from `target` (and `up`) or from `rotation` (looking down −Z); giving both is an error. `near`/`far` are optional. The first camera is the initial view.
|
|
350
|
-
- **Framing on other screens.** `fov` (or `height`) is the vertical extent of a view `aspect` wide and 1 high (default `aspect="1"`). `fit` says how a viewport of another aspect ratio a is filled, like SVG's `preserveAspectRatio`. With s = aspect / a, the vertical extent is scaled by:
|
|
351
|
-
- `contain` (default): max(1, s). The whole framed view stays visible ("meet").
|
|
352
|
-
- `cover`: min(1, s). The viewport is filled and the framed view is cropped ("slice").
|
|
353
|
-
- `width`: s. The framed width is kept.
|
|
354
|
-
- `height`: 1. The framed height is kept.
|
|
355
|
-
|
|
356
|
-
The defaults reproduce the usual rule of applying `fov` to the shorter viewport side. An author who framed a scene for a 16:9 page writes `aspect="1.778"`, and a portrait phone then shows the same width instead of cropping the sides.
|
|
357
|
-
- `<environment>` sets the clear colour (`background`), hemisphere ambient colours (`sky`, `ground`), their `intensity`, and tone-mapping `exposure`. Only the first `<environment>` is used (`W_MULTIPLE_ENVIRONMENTS`).
|
|
358
|
-
- `<controls>` describes how an interactive reader lets people move the camera. Only the first one is used (`W_MULTIPLE_CONTROLS`), and a document without one gets the reader's defaults (usually free orbiting).
|
|
359
|
-
- `mode="orbit"` (default): drag to orbit the target, pan and zoom; `zoom="false"` and `pan="false"` turn those off.
|
|
360
|
-
- `mode="none"`: a fixed view from the first camera.
|
|
361
|
-
- `mode="scroll"`: the first camera is the view, moved only by its scroll-timeline animations (section 14). Dragging is off and the canvas must not capture touch scrolling.
|
|
362
|
-
- Limits keep the camera between `minDistance` and `maxDistance` from its target and between `minPolar` and `maxPolar` degrees from straight up (`maxPolar="90"` keeps it above the target). `targetMin` and `targetMax` bound the orbit target, and so panning.
|
|
363
|
-
- `collide="true"` stops the camera in front of surfaces. A move from the last camera position to the next is blocked by the first surface whose front faces the camera (double-sided surfaces always block). The camera stays far enough from the surface that its near plane does not cut into it, and a move that hits a surface at an angle slides along it. A camera that starts inside an object can leave it, and an orbit target inside an object does not trap the camera. Elements with `collide="false"` (inherited by their children, available on every node) are ignored.
|
|
364
|
-
|
|
365
|
-
## 14. Animation
|
|
366
|
-
|
|
367
|
-
`<animate attr="…" dur="4s" …>` inside a node animates an attribute of its parent: `position`, `rotation` or `scale` of any node, and also `target` and `fov` of a `<camera>` (`E_INVALID_VALUE` elsewhere; animating the target of a camera oriented by `rotation` is `E_CONFLICTING_ATTRIBUTES`). Animation replaces the attribute value, so `transform` still applies outside it.
|
|
368
|
-
|
|
369
|
-
**Values.** Exactly one source is given (`E_MISSING_ATTRIBUTE` without one, `E_CONFLICTING_ATTRIBUTES` with several):
|
|
370
|
-
- `to` (with optional `from`, default: the parent's value): a straight interpolation.
|
|
371
|
-
- `values="v0; v1; …"`: two or more key values separated by semicolons. With `keyTimes="0, t1, …, 1"` (one per value, starting at 0, ending at 1, not decreasing), value i is reached at fraction tᵢ. Without it the keys are evenly spaced. Between keys the value is linear, or with `smooth="true"` on a centripetal Catmull-Rom curve through the keys. The end keys are extended by reflecting their neighbour, as for sweep spines.
|
|
372
|
-
- `along` (position and target only): a path in the same syntax as a sweep's spine (points, 3D path data or `helix()`; `smooth` interpolates points). The fraction is measured along the path's length, so the value moves at constant speed.
|
|
373
|
-
|
|
374
|
-
Each value has 3 numbers (1 or 3 for `scale`, 1 for `fov`).
|
|
375
|
-
|
|
376
|
-
**Timeline.** The animation turns a fraction f in 0..1 into a value, after `easing` (`linear`, `ease-in`, `ease-out`, `ease-in-out`) is applied to f.
|
|
377
|
-
- `timeline="time"` (default): `dur` is required. Before `begin` the animation has no effect. Then it plays `repeat` cycles (default `indefinite`), with `alternate="true"` playing every second cycle backwards, and after the last cycle it holds the end value.
|
|
378
|
-
- `timeline="scroll"`: f is the reader's scroll progress, clamped to 0..1, and `dur`, `begin`, `repeat` and `alternate` are not used. How progress relates to a page is up to the reader. The reference viewer maps a page section's scroll position to it, with optional stops pinned to page elements. A reader without a page holds progress at 0.
|
|
379
|
-
|
|
380
|
-
## 15. Diagnostics
|
|
381
|
-
|
|
382
|
-
Readers report problems in this canonical form:
|
|
383
|
-
|
|
384
|
-
```
|
|
385
|
-
ES3D Parse Error
|
|
386
|
-
line 2, column 11
|
|
387
|
-
|
|
388
|
-
Unknown attribute: raduis
|
|
389
|
-
|
|
390
|
-
Did you mean: radius?
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
The title names the phase (`Parse`, `Reference`, `Geometry`, `Render`, `File`) and severity (`Error`, `Warning`). Codes used by the reference reader:
|
|
394
|
-
|
|
395
|
-
| Group | Codes |
|
|
396
|
-
|---|---|
|
|
397
|
-
| Syntax | `E_SYNTAX`, `E_UNEXPECTED_EOF`, `E_MISMATCHED_TAG`, `E_UNEXPECTED_CLOSE`, `E_UNCLOSED_ELEMENT`, `E_UNQUOTED_ATTRIBUTE`, `E_UNTERMINATED_ATTRIBUTE`, `E_UNTERMINATED_COMMENT`, `E_UNTERMINATED_CDATA`, `E_UNTERMINATED_PI`, `E_UNTERMINATED_DOCTYPE`, `E_UNTERMINATED_STRING`, `E_BAD_ENTITY`, `E_BAD_ESCAPE`, `E_MULTIPLE_ROOTS`, `E_TEXT_OUTSIDE_ROOT`, `E_EMPTY_DOCUMENT`, `E_COMPACT_ROOT`, `E_JSON`, `E_JSON_STRUCTURE` |
|
|
398
|
-
| Structure | `E_ROOT`, `E_VERSION`, `E_UNKNOWN_ELEMENT`, `E_MISPLACED_ELEMENT`, `E_TEXT_NOT_ALLOWED`, `E_MULTIPLE_SCENES` |
|
|
399
|
-
| Attributes | `E_UNKNOWN_ATTRIBUTE`, `E_DUPLICATE_ATTRIBUTE`, `E_MISSING_ATTRIBUTE`, `E_MISSING_VALUE`, `E_INVALID_VALUE`, `E_TYPE`, `E_RANGE`, `E_CONFLICTING_ATTRIBUTES` |
|
|
400
|
-
| References | `E_DUPLICATE_ID`, `E_UNKNOWN_REFERENCE`, `E_REFERENCE_KIND`, `E_CIRCULAR_REFERENCE` |
|
|
401
|
-
| Geometry | `E_GEOMETRY`, `E_MESH`, `E_DEGENERATE`, `E_DEGENERATE_SPINE`, `E_REVOLVE_AXIS`, `E_LOD_CHILDREN`, `E_TOO_MANY_INSTANCES`, `E_TOO_DEEP`, `E_PATH_LIMIT` |
|
|
402
|
-
| Warnings | `W_LEGACY_ROOT`, `W_EXTENSION`, `W_DEF_WITHOUT_ID`, `W_MATERIAL_WITHOUT_ID`, `W_MULTIPLE_ENVIRONMENTS`, `W_MULTIPLE_CONTROLS`, `W_DEGENERATE_CONTOUR`, `W_SELF_INTERSECTION`, `W_EMPTY_FILL`, `W_TEXTURE` |
|
|
403
|
-
|
|
404
|
-
Unknown names get a suggestion when a known name is within a small edit distance or shares a prefix (`raduis` → `radius`, `spotlight` → `spot`, `sphear` → `sphere`).
|
|
405
|
-
|
|
406
|
-
## 16. Extensibility and versioning
|
|
407
|
-
|
|
408
|
-
- Elements whose names start with `x-` (and their subtrees) are vendor extensions: readers that do not understand them skip them with `W_EXTENSION`. Unknown elements without the prefix are errors, so typos are never mistaken for extensions.
|
|
409
|
-
- Unknown attributes are always errors. (A future version may reserve an `x-` attribute prefix as well.)
|
|
410
|
-
- `version="1.x"` documents must remain readable by version-1 readers; incompatible changes require version 2, which version-1 readers reject with `E_VERSION`.
|
|
411
|
-
|
|
412
|
-
## 17. File names and media types
|
|
413
|
-
|
|
414
|
-
| | |
|
|
415
|
-
|---|---|
|
|
416
|
-
| Text (any syntax) | `.es3d`, UTF-8, optionally gzip-compressed (`.es3d.gz`) |
|
|
417
|
-
| Media type (proposed, unregistered) | `model/vnd.es3d` |
|
|
1
|
+
# ES3D: Extensible Scalable 3D
|
|
2
|
+
|
|
3
|
+
**Format specification, version 1** · file extension `.es3d` · namespace `https://es3d.org/ns/1`
|
|
4
|
+
|
|
5
|
+
ES3D is a declarative scene format in the spirit of SVG. A document says what a scene contains: boxes, spheres, extruded outlines, instanced groups, lights, cameras. It does not store the triangles a modelling tool happened to emit. A reader turns that description into GPU geometry, at whatever tessellation the view needs.
|
|
6
|
+
|
|
7
|
+
This document defines the semantics. The complete list of elements, attributes, value types, defaults and ranges is generated from the reference implementation's schema in [REFERENCE.md](REFERENCE.md). Where this text and the reference implementation disagree, that is a bug in one of them; please report it.
|
|
8
|
+
|
|
9
|
+
Key words: **must**, **must not**, **should** and **may** are used as in RFC 2119.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Design goals and non-goals
|
|
14
|
+
|
|
15
|
+
Goals:
|
|
16
|
+
|
|
17
|
+
1. **Human-readable and writable.** A useful scene fits on a screen and can be written by hand, generated by a template, or diffed in version control.
|
|
18
|
+
2. **Primitive- and transform-based.** Geometry is described by parameters (a sphere is a radius), so size does not depend on tessellation and the reader can choose detail per view.
|
|
19
|
+
3. **Reuse is first class.** Definitions (`<defs>`, `<use>`), linear patterns (`<repeat>`) and deterministic procedural placement (`<scatter>`) keep repetitive scenes small, and map directly to GPU instancing.
|
|
20
|
+
4. **Strict.** Malformed input is reported with a location and, where possible, a suggestion. A reader must not silently drop or "repair" geometry.
|
|
21
|
+
5. **Lossless round trips.** Reading and writing a document must not change its meaning; the reference implementation preserves attribute spelling and comments as well.
|
|
22
|
+
|
|
23
|
+
Non-goals, by design: version 1 describes scenes built from shapes, paths and repetition. It does not describe skinning, morph targets or physics, and it is not a CAD format. There is no boundary representation and no exact Boolean.
|
|
24
|
+
|
|
25
|
+
Version 1 also refuses a few things that stalled SVG:
|
|
26
|
+
|
|
27
|
+
- **One document, not a family of profiles.** Compact syntax and JSON are encodings of the same tree. They are not subsets that omit elements. SVG Tiny, Basic and Full left authors guessing which half of the spec a viewer had, and SVG 1.2 Full never shipped.
|
|
28
|
+
- **No script, no event handlers, no style sheets.** A file is a scene. Opening it does not run a program. SVG's mix of picture and script is why "using it as an image" later needed a separate security mode.
|
|
29
|
+
- **No required namespace.** An `xmlns` attribute may appear and is ignored. Leaving it out is not an error. A missing namespace was a common reason early SVG files rendered as an XML error page.
|
|
30
|
+
- **No custom entities.** Only `<`, `>`, `&`, `"`, `'` and numeric character references are expanded. A DOCTYPE is ignored and cannot declare entities, so a document cannot expand itself the way a billion-laughs payload does.
|
|
31
|
+
- **No special compressed extension.** Gzip is `.es3d.gz`. SVG's `.svgz` was often gzipped a second time, or served with the wrong media type. Until `model/vnd.es3d` is registered, servers may send the text as `text/plain; charset=utf-8`.
|
|
32
|
+
|
|
33
|
+
## 2. Conformance
|
|
34
|
+
|
|
35
|
+
A **conforming document** is a document that parses in one of the surface syntaxes of section 4, has an `es3d` root element with `version="1"` (or `1.x`), and produces no errors under the rules of this specification and the schema in REFERENCE.md.
|
|
36
|
+
|
|
37
|
+
A **conforming reader**:
|
|
38
|
+
|
|
39
|
+
- must reject documents with errors, reporting each error with a stable code (section 15), a message, and a location (line and column for text input, an element path otherwise);
|
|
40
|
+
- must report all errors it can find, not only the first (syntax errors that make the rest of the document unreadable are the exception);
|
|
41
|
+
- must not render a document with errors as if it were valid (the reference viewer keeps showing the last valid state and marks it stale);
|
|
42
|
+
- must produce, for every `<scatter>`, the instances defined in section 12.3 (procedural placement is part of the format, not a rendering hint);
|
|
43
|
+
- may choose tessellation freely, within the accuracy rules of section 8.1.
|
|
44
|
+
|
|
45
|
+
The legacy root name `<v3d>` is accepted with a warning (`W_LEGACY_ROOT`).
|
|
46
|
+
|
|
47
|
+
## 3. Coordinate system, units and conventions
|
|
48
|
+
|
|
49
|
+
- Right-handed coordinates, **+Y up**. Cameras look down their local **−Z** axis.
|
|
50
|
+
- Lengths are plain numbers. The root attribute `unit` (`m` by default) is informative only; readers must not rescale.
|
|
51
|
+
- Angles are in **degrees** everywhere (rotations, field of view, arcs, sweeps).
|
|
52
|
+
- Colours are **sRGB**; readers convert to linear space for lighting.
|
|
53
|
+
- 2D outline data (path data, polygons) lives in the local **XY plane with y up**. SVG's y axis points down. A path drawn in SVG coordinates can be used with `scale="1,-1,1"`.
|
|
54
|
+
- Every primitive's default size fits the unit cube centred at the origin, so `<box/>`, `<sphere/>`, `<cylinder/>` … are all visible and directly comparable.
|
|
55
|
+
|
|
56
|
+
## 4. Surface syntaxes
|
|
57
|
+
|
|
58
|
+
All syntaxes produce the same abstract tree of elements, attributes, text and comments. Attribute values are strings in the tree; their typed interpretation (section 6) is defined by the schema. A reader detects the syntax from the first significant character: `<` markup, `[` or `{` JSON, anything else compact. There is no binary encoding of the document.
|
|
59
|
+
|
|
60
|
+
### 4.1 Markup (canonical)
|
|
61
|
+
|
|
62
|
+
An XML subset:
|
|
63
|
+
|
|
64
|
+
```xml
|
|
65
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
66
|
+
<es3d version="1">
|
|
67
|
+
<material id="brass" color="#c9a227" metallic="1" roughness="0.35"/>
|
|
68
|
+
<group position="0,1,0" rotation="0,30,0">
|
|
69
|
+
<box size="2,0.2,1" material="brass"/>
|
|
70
|
+
<sphere radius="0.4" position="0,0.5,0" color="tomato"/>
|
|
71
|
+
</group>
|
|
72
|
+
</es3d>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Supported: elements, single- or double-quoted attributes, comments, the five predefined entities and numeric character references, CDATA sections (treated as text), an optional XML declaration, and a DOCTYPE that is ignored. The DOCTYPE is not read for entity declarations. Not supported (errors): unquoted attribute values, duplicate attributes, text in elements that do not accept text, more than one root element, and any entity other than the five predefined ones. Element and attribute names are case-sensitive. Namespace prefixes are not interpreted. `xmlns` attributes are allowed and ignored, and a document does not need one.
|
|
76
|
+
|
|
77
|
+
### 4.2 Compact syntax (ES3D-C)
|
|
78
|
+
|
|
79
|
+
A brace-structured syntax for hand writing. On the example scenes it is 15–23 % smaller than minified markup and about half the size of indented markup (after gzip the difference shrinks to 5–12 %):
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
es3d version=1
|
|
83
|
+
// comments: // line and /* block */
|
|
84
|
+
material#brass c=#c9a227 metallic=1 roughness=0.35
|
|
85
|
+
group p=0,1,0 rot=0,30,0 {
|
|
86
|
+
box s=2,0.2,1 m=brass
|
|
87
|
+
sphere r=0.4 p=0,0.5,0 c=tomato
|
|
88
|
+
}
|
|
89
|
+
use @tree p=10,0,5
|
|
90
|
+
title "A \"quoted\" title"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Grammar (newlines are ordinary whitespace):
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
document := comment* root
|
|
97
|
+
root := ('es3d' | 'v3d') header ( '{' statement* '}' | statement* )
|
|
98
|
+
statement := element | comment | ';'
|
|
99
|
+
element := NAME header ( '{' statement* '}' )?
|
|
100
|
+
header := ( '#' ID )? ( '@' REF )? STRING? ( NAME '=' VALUE )*
|
|
101
|
+
VALUE := bare-value | '"' escaped-string '"'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`#x` is shorthand for `id="x"`, `@x` for `ref="x"`, and a quoted string in header position is the element's text content. A bare value runs until whitespace, `{`, `}`, `;` or `"`; values containing those characters (transforms with spaces, path data) must be quoted. An element header ends at the first token that is not `NAME=`, so the grammar is LL(2) and needs no schema knowledge.
|
|
105
|
+
|
|
106
|
+
### 4.3 JSON (JsonML)
|
|
107
|
+
|
|
108
|
+
`["es3d", {"version": "1"}, ["box", {"size": "2"}], ["title", {}, "text"]]`. An element is an array of name, attribute object (every value a string) and children. Strings are text. Comments are `["#comment", "text"]`. This form exists for tooling that prefers JSON. It is larger than the markup, and errors inside values have no line or column. It is an encoding of the same tree, not a reduced profile.
|
|
109
|
+
|
|
110
|
+
### 4.4 Document encoding
|
|
111
|
+
|
|
112
|
+
A document is UTF-8 text in one of the three syntaxes above. Gzip (`.es3d.gz`) is ordinary HTTP compression of that text. Meshes that have no procedural description stay inline as `<mesh>` (section 10). The format does not reference glTF, OBJ or any other container for its geometry.
|
|
113
|
+
|
|
114
|
+
### 4.5 Attribute aliases
|
|
115
|
+
|
|
116
|
+
Frequently used attributes have short aliases that are equivalent everywhere (an element must not give both forms): `p` position, `rot` rotation, `sc` scale, `t` transform, `m` material, `c` color, `s` size, `r` radius, `h` height, `n` count, `dir` direction, `href` ref. Writers producing minified output use the aliases; pretty output uses the full names.
|
|
117
|
+
|
|
118
|
+
## 5. Document structure
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
<es3d version="1" [unit] [name]>
|
|
122
|
+
<title>, <desc> optional metadata (text)
|
|
123
|
+
<environment> background and ambient light (first one wins)
|
|
124
|
+
<controls> how viewers let people move the camera (first one wins)
|
|
125
|
+
<material id> materials may appear anywhere; they are document-wide
|
|
126
|
+
<defs> definitions referenced by id; not rendered directly
|
|
127
|
+
<scene> optional wrapper; behaves like <group>
|
|
128
|
+
scene nodes … shapes, containers, <use>, lights, cameras
|
|
129
|
+
</es3d>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Each element's allowed children are listed in REFERENCE.md. Placing an element where it is not allowed is an error (`E_MISPLACED_ELEMENT`). Ids are unique per document (`E_DUPLICATE_ID`) and are referenced by `material`, `ref`, `path` and `randomMaterial` attributes; references are checked for existence (`E_UNKNOWN_REFERENCE`, with a suggestion), kind (`E_REFERENCE_KIND`) and cycles (`E_CIRCULAR_REFERENCE`).
|
|
133
|
+
|
|
134
|
+
## 6. Values
|
|
135
|
+
|
|
136
|
+
The exact syntax of every value type is listed at the end of REFERENCE.md. General rules:
|
|
137
|
+
|
|
138
|
+
- Numbers follow the SVG/CSS number grammar: optional sign, digits with an optional fraction or a leading `.`, optional exponent (`-0.5`, `.5`, `2e-3`). `NaN`, `Infinity` and hexadecimal are not numbers.
|
|
139
|
+
- Lists accept commas, whitespace or both as separators (`1,2,3`, `1 2 3`, `1, 2, 3`), but not empty items.
|
|
140
|
+
- Vectors have fixed arity; `size` and `scale` also accept one number for all components.
|
|
141
|
+
- Booleans are `true` or `false`.
|
|
142
|
+
- An attribute that fails to parse is an error (`E_INVALID_VALUE`) located inside the value where possible; a value outside its range is `E_RANGE`.
|
|
143
|
+
- Cross-attribute rules (for example "`bevel` must not exceed half the `depth`", "give `count` or `grid`, not both") are errors, never silent clamping.
|
|
144
|
+
|
|
145
|
+
## 7. Transforms and the scene hierarchy
|
|
146
|
+
|
|
147
|
+
Every scene node has a **local matrix**
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
local = transform · T(position) · R(rotation) · S(scale)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
and its world matrix is `parentWorld · local`. Consequently `position`, `rotation` and `scale` are applied *inside* the `transform` attribute, like `x`/`y` inside the transform of an SVG `<use>`.
|
|
154
|
+
|
|
155
|
+
- `rotation="x,y,z"` is in degrees and applied about X first, then Y, then Z: `R = Rz · Ry · Rx`. Sine and cosine of rotation angles are the functions in section 12.3, not the platform `sin` and `cos`.
|
|
156
|
+
- `scale` may be negative (mirroring); readers must keep faces outward-facing by flipping the winding of mirrored instances.
|
|
157
|
+
- `transform` is an SVG-style list of functions composed **left to right**: `translate(1,0,0) rotateY(90) scale(2)` means `T · Ry · S`; the rightmost function acts on the object first. Functions: `translate(x[,y[,z]])`, `rotate(x,y,z)` (Euler degrees), `rotate(a,ax,ay,az)` (axis-angle; zero axis is an error), `rotateX(a)`, `rotateY(a)`, `rotateZ(a)`, `scale(s)` or `scale(x,y,z)` (two arguments are an error), `quat(x,y,z,w)`, `matrix(m0 … m15)` (column-major, like CSS `matrix3d`). `rotate(45)` is an error, with a hint to use `rotateY(45)`. The 2D SVG meaning of a single angle is ambiguous in 3D.
|
|
158
|
+
- `visible="false"` hides a node and its subtree (they still exist and can be referenced).
|
|
159
|
+
|
|
160
|
+
## 8. Shapes
|
|
161
|
+
|
|
162
|
+
Each shape element produces one renderable geometry in its local frame, centred at the origin unless stated otherwise. Dimensions, defaults and ranges are listed in REFERENCE.md.
|
|
163
|
+
|
|
164
|
+
| Element | Local definition |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `box` | axis-aligned box, `size` = edge lengths; `radius` rounds all edges |
|
|
167
|
+
| `sphere` | radius `radius` |
|
|
168
|
+
| `cylinder` | along Y from −h/2 to +h/2; `radiusTop` makes a truncated cone; `caps="false"` leaves it open |
|
|
169
|
+
| `cone` | base at −h/2, apex at +h/2 |
|
|
170
|
+
| `capsule` | along Y, total height `height` including the hemispherical caps |
|
|
171
|
+
| `plane` | rectangle in XZ facing +Y, double-sided |
|
|
172
|
+
| `torus` | around Y; `radius` to the tube centre, `tube` tube radius, `arc` partial sweep from +X towards −Z |
|
|
173
|
+
| `line` | from `from` to `to`; a hairline, or a tube of `radius` |
|
|
174
|
+
| `polyline` | 3D `points` or the outline of 2D path data `d`; `smooth` = Catmull-Rom; hairline or tube |
|
|
175
|
+
| `polygon` | filled flat polygon in XY, double-sided |
|
|
176
|
+
| `path` | filled flat 2D path in XY, double-sided; inside `<defs>` a reusable outline |
|
|
177
|
+
| `extrude` | 2D path extruded from z = 0 to z = `depth` (or centred with `center="true"`), optional rounded `bevel` |
|
|
178
|
+
| `revolve` | 2D profile revolved around Y; profile x = distance from the axis, y = height |
|
|
179
|
+
| `sweep` | circle (`radius`) or 2D profile swept along a 3D spine (`along`) with rotation-minimising frames, optional `twist` and `taper` |
|
|
180
|
+
| `gear` | involute spur gear with `teeth`, `module`, `pressureAngle`, extruded by `depth`, optional `bore` and `bevel` |
|
|
181
|
+
| `mesh` | explicit triangles (section 10) |
|
|
182
|
+
|
|
183
|
+
### 8.1 Tessellation
|
|
184
|
+
|
|
185
|
+
Shapes are exact mathematical surfaces. A reader may choose the triangles, and every generated mesh must stay within that level's chordal-error bound: the maximum distance from the mesh to the true surface, measured in the generator's canonical units. The bound is part of the geometry. Identical triangles are not required. Platform `sin` and `cos` may be used while tessellating; placement (section 12.3) may not.
|
|
186
|
+
|
|
187
|
+
The reference reader keeps up to four levels. At quality 1 the segment count is `max(minimum, round(base / 2^level))`:
|
|
188
|
+
|
|
189
|
+
| Shape | Base | Minimum | Levels |
|
|
190
|
+
|---|---:|---:|---|
|
|
191
|
+
| Sphere, cylinder, cone | 48 | 8 | 48, 24, 12, 8 |
|
|
192
|
+
| Capsule | 32 | 8 | 32, 16, 8, 8 |
|
|
193
|
+
| Torus sweep / tube | 64 / 24 | 8 / 6 | 64, 32, 16, 8 and 24, 12, 6, 6 |
|
|
194
|
+
| Rounded box | 4 | 1 | 4, 2, 1, 1 |
|
|
195
|
+
|
|
196
|
+
A `segments` attribute (or both torus segment counts) requests one fixed tessellation, which is part of the shape. A `segments="6"` cylinder is a hexagonal prism at every distance. Exact shapes (box, plane, polygon, hairline, authored mesh) have one level and a bound of 0.
|
|
197
|
+
|
|
198
|
+
Each level stores its bound. A frame may draw a level only while `bound × scale × pixelsPerUnit` is at most 1. `scale` is the longest column of the instance matrix. `pixelsPerUnit` is the projected size of one canonical unit. The reference reader picks the coarsest level that satisfies that test. A renderer `lodBias` above 1 (default 1) divides the one-pixel budget; it is not a document attribute. While a level is still being generated, a renderer may draw a coarser one in its place for a few frames (progressive refinement), as long as it keeps drawing until every level passes the test. For a unit sphere the switches fall near 59, 15 and 7 pixels of the sphere's own radius.
|
|
199
|
+
|
|
200
|
+
Closed shapes must produce closed, outward-facing surfaces. Open or flat shapes (planes, polygons, paths, open revolves, `caps="false"`) are rendered double-sided.
|
|
201
|
+
|
|
202
|
+
## 9. Path data
|
|
203
|
+
|
|
204
|
+
`d` attributes use SVG path syntax with all commands, absolute and relative: `M L H V C S Q T A Z` (plus implicit repetition, `m` followed by implicit `l`, and the SVG separator rules). Arcs follow the SVG implementation notes (radii scaled up when too small). Coordinates are in the local XY plane, **y up**.
|
|
205
|
+
|
|
206
|
+
- **Subpaths and fill rules.** A path can contain several closed subpaths. `fillRule` (`nonzero`, default, or `evenodd`) decides which nested contours become holes, exactly as SVG's `fill-rule`. Surfaces are generated where the fill changes, so same-direction nested contours under `nonzero` produce internal walls only where the filled region actually changes.
|
|
207
|
+
- **Open subpaths** are closed implicitly for filling (SVG behaviour); `revolve` uses open profiles as-is.
|
|
208
|
+
- **Degenerate input** is reported, never repaired: subpaths without area produce `W_DEGENERATE_CONTOUR`, a fill that leaves nothing to fill `W_EMPTY_FILL`, and a self-intersecting contour `W_SELF_INTERSECTION` stating how much of the expected area the triangulation covers. Readers must not invent geometry for invalid outlines.
|
|
209
|
+
- `path="id"` references a `<path>` in `<defs>` instead of inline data; a shape gives either `d` or `path`.
|
|
210
|
+
- 3D path data (sweep spines) uses `M L C S Q T Z` with three numbers per point; `H`, `V` and `A` are errors in 3D.
|
|
211
|
+
|
|
212
|
+
`extrude` caps the front and back with the filled region, builds walls from every contour, and offsets the caps inwards by the bevel radius with a quarter-round profile. `revolve` profiles must stay at x ≥ 0 (`E_REVOLVE_AXIS`); closed profiles produce closed solids, open profiles open surfaces, and faces are oriented outwards automatically. `sweep` frames are rotation-minimising (double reflection); closed spines are corrected so the frames meet without a seam.
|
|
213
|
+
|
|
214
|
+
## 10. Meshes (escape hatch)
|
|
215
|
+
|
|
216
|
+
`<mesh positions="x y z …" indices="i j k …"/>` stores triangles explicitly, for content that has no procedural description (scans, sculpted shapes). `normals` and `uvs` are optional; without normals the reader computes them with the crease angle `smooth` (degrees; 0 = flat, 180 = fully smooth). Mesh arrays are float32 data. Index and array-length errors are reported (`E_MESH`), never truncated. A `<mesh>` in `<defs>` can be instanced with `<use>` like anything else.
|
|
217
|
+
|
|
218
|
+
A writer that emits `<mesh>` from floating-point data rounds positions so the error is at most 10⁻⁵ of the scene extent, and rounds normals to two decimal places. Authored documents may use more digits; readers still store the values as float32.
|
|
219
|
+
|
|
220
|
+
## 11. Materials
|
|
221
|
+
|
|
222
|
+
`<material>` defines a metallic/roughness material in the glTF channel layout, stored on the element itself:
|
|
223
|
+
|
|
224
|
+
- `color` is the base colour (sRGB). Its alpha multiplies `opacity`.
|
|
225
|
+
- `metallic` and `roughness` are factors in 0…1.
|
|
226
|
+
- `metallicRoughnessMap` is a linear texture: green multiplies roughness, blue multiplies metallic.
|
|
227
|
+
- `occlusionMap` is a linear texture. Red darkens indirect light (hemisphere and environment), mixed by `occlusionStrength` (default 1). It does not scale direct lights.
|
|
228
|
+
- `emissive` × `emissiveIntensity`, multiplied by `emissiveMap` (sRGB).
|
|
229
|
+
- `map` (base colour, sRGB) and `normalMap` (linear, scaled by `normalScale`). A texture is an image URL or a procedural texture such as `checker(8)` or `bumps(12,0.6)`. `uvScale` repeats the coordinates.
|
|
230
|
+
- `alphaMode` is `auto` (default), `opaque`, `mask` or `blend`. `auto` is `blend` when `opacity` × colour alpha is below 0.999 and `opaque` otherwise. `opaque` ignores alpha. `mask` discards fragments below `alphaCutoff` (default 0.5). `blend` uses opacity as coverage. Texture alpha is visible only with `alphaMode="blend"` and an opacity below 1.
|
|
231
|
+
- `doubleSided` and `unlit`.
|
|
232
|
+
|
|
233
|
+
Texture coordinates, before `uvScale`:
|
|
234
|
+
|
|
235
|
+
- Sphere: `u` is longitude, 0 at +X, increasing through −Z; `v` is 1 at the north pole and 0 at the south pole. The seam is the +X meridian.
|
|
236
|
+
- Cylinder and cone: `u` runs around the axis, 0 at +X; `v` is 0 at the bottom and 1 at the top. Caps use a planar projection in XZ, centred at (0.5, 0.5).
|
|
237
|
+
- Plane: the unit square in XZ, `u` along +X and `v` along −Z.
|
|
238
|
+
- Box: each face is 0…1, with the face's normal as the projection axis.
|
|
239
|
+
- Torus: `u` along the sweep from the +X side, `v` around the tube.
|
|
240
|
+
|
|
241
|
+
Inheritance follows SVG's `fill`:
|
|
242
|
+
|
|
243
|
+
- `material="id"` on any node sets the material for the node and its descendants, and **clears** an inherited colour override.
|
|
244
|
+
- `color="…"` on any node overrides the base colour (of the inherited or own material) for the node and its descendants.
|
|
245
|
+
- A `<use>` passes its material and colour down into the referenced content as *inherited* values; values set inside the definition win.
|
|
246
|
+
- Without any material, shapes use the default material (light grey, roughness 0.5).
|
|
247
|
+
|
|
248
|
+
Identical parameter sets are the same material; readers should deduplicate them (the reference reader binds each distinct material once per frame).
|
|
249
|
+
|
|
250
|
+
## 12. Reuse and procedural placement
|
|
251
|
+
|
|
252
|
+
### 12.1 `<defs>` and `<use>`
|
|
253
|
+
|
|
254
|
+
Children of `<defs>` are not rendered. `<use ref="id">` renders the referenced element (from `<defs>` or anywhere else in the document) as if it were a child of the `<use>`, under the use's transform and style. A definition referenced N times exists once; readers should render repeated geometry with instancing. Reference cycles are errors.
|
|
255
|
+
|
|
256
|
+
### 12.2 `<repeat>`
|
|
257
|
+
|
|
258
|
+
`<repeat count="n" step="transform">` renders its children n times. Copy i (0-based) is placed with `step` applied i times:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
world(copy i) = world(repeat) · stepⁱ · local(child)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
so `step="translate(0,0.2,0.3) rotateY(15)"` builds a spiral staircase. `count="0"` renders nothing.
|
|
265
|
+
|
|
266
|
+
### 12.3 `<scatter>` (normative algorithm)
|
|
267
|
+
|
|
268
|
+
`<scatter>` places `count` copies of its children (or `grid="nx,nz"` copies) in its local XZ plane. Every reader must implement this algorithm exactly, in IEEE 754 binary64 with round-to-nearest. The random sequence is integer arithmetic. `sqrt` is `Math.sqrt` (correctly rounded). Disc positions and every rotation matrix use `es3dSin` and `es3dCos` below, in the order written, so two readers produce the same bits.
|
|
269
|
+
|
|
270
|
+
1. The random generator is **mulberry32** seeded with the unsigned 32-bit `seed` (default 1):
|
|
271
|
+
```js
|
|
272
|
+
function mulberry32(seed) {
|
|
273
|
+
let a = seed >>> 0;
|
|
274
|
+
return () => {
|
|
275
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
276
|
+
let t = a;
|
|
277
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
278
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
279
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
2. For copy i = 0 … n−1, draw exactly nine values u0 … u8 in order (all nine are drawn even when unused, so adding an attribute never changes the positions of other attributes' effects).
|
|
284
|
+
3. Position (y = 0):
|
|
285
|
+
- grid: ix = i mod nx, iz = ⌊i / nx⌋; x = (ix − (nx−1)/2)·spacingₓ + (u0 − ½)·jitter·spacingₓ, z = (iz − (nz−1)/2)·spacing_z + (u1 − ½)·jitter·spacing_z;
|
|
286
|
+
- disc (`radius` R): r = R·√u0, a = u1·2π, x = r·es3dCos(a), z = r·es3dSin(a);
|
|
287
|
+
- rectangle (`area` w,d): x = (u0 − ½)·w, z = (u1 − ½)·d.
|
|
288
|
+
4. Rotation (degrees, Euler as in section 7): component k = lo_k + (hi_k − lo_k)·u(2+k) for the ranges of `randomRotation`.
|
|
289
|
+
5. Scale: uniform `randomScale` range → s = lo + (hi − lo)·u5 for all axes; per-axis ranges use u5, u6, u7.
|
|
290
|
+
6. Material: with `randomMaterial="a,b,c"`, copy i uses entry ⌊u8 · count⌋ (clamped to the last entry) and the inherited colour override is cleared.
|
|
291
|
+
7. world(copy i) = world(scatter) · T(position) · R(rotation) · S(scale) · local(child).
|
|
292
|
+
|
|
293
|
+
`grid` and `count` are mutually exclusive, as are `area` and `radius`; a random layout needs one of them.
|
|
294
|
+
|
|
295
|
+
`es3dSin` and `es3dCos` take radians. π, 2π, π/2 and π/4 are `Math.PI`, `Math.PI * 2`, `Math.PI * 0.5` and `Math.PI * 0.25`. A non-finite input is returned unchanged. Otherwise:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
y = x - 2π · round(x / 2π)
|
|
299
|
+
sign = 1
|
|
300
|
+
if y < 0: sign = -1; y = -y
|
|
301
|
+
if y > π/2: y = π - y
|
|
302
|
+
if y < 0: y = 0
|
|
303
|
+
s = y > π/4 ? kernelCos(π/2 - y) : kernelSin(y)
|
|
304
|
+
es3dSin(x) = sign < 0 ? -s : s
|
|
305
|
+
es3dCos(x) = es3dSin(x + π/2)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`kernelSin` and `kernelCos` are Horner polynomials. `z = x*x`. Coefficients, as binary64 literals:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
S1 = -1.66666666666666324348e-1
|
|
312
|
+
S2 = 8.33333333332248946124e-3
|
|
313
|
+
S3 = -1.98412698298579493134e-4
|
|
314
|
+
S4 = 2.75573137070700676789e-6
|
|
315
|
+
S5 = -2.50507602534068634195e-8
|
|
316
|
+
S6 = 1.58969099521155010221e-10
|
|
317
|
+
kernelSin(x) = x + (z*x) * (S1 + z * (S2 + z * (S3 + z * (S4 + z * (S5 + z * S6)))))
|
|
318
|
+
|
|
319
|
+
C1 = 4.16666666666666019037e-2
|
|
320
|
+
C2 = -1.38888888888741095749e-3
|
|
321
|
+
C3 = 2.48015872894767294178e-5
|
|
322
|
+
C4 = -2.75573143513906633035e-7
|
|
323
|
+
C5 = 2.0875723212981748279e-9
|
|
324
|
+
C6 = -1.13596475577881948265e-11
|
|
325
|
+
r = z * (C1 + z * (C2 + z * (C3 + z * (C4 + z * (C5 + z * C6)))))
|
|
326
|
+
kernelCos(x) = 1 - 0.5*z + z*r
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Every add, subtract and multiply is one binary64 operation, in that order. `round` is `Math.round` (a tie at n+0.5 rounds towards +∞).
|
|
330
|
+
|
|
331
|
+
### 12.4 `<lod>`
|
|
332
|
+
|
|
333
|
+
`<lod distances="d1,d2,…">` has exactly one more child than distances (`E_LOD_CHILDREN`). Child i is rendered while the distance from the camera to the lod's world origin is below dᵢ (and at least dᵢ₋₁); the last child beyond the last distance. Distances are in the lod's local units (scaled by the cube root of the world scale's determinant). Exporters to static formats write child 0. Automatic per-primitive tessellation LOD (section 8.1) is independent of `<lod>`.
|
|
334
|
+
|
|
335
|
+
### 12.5 Resource limits
|
|
336
|
+
|
|
337
|
+
Exceeding a limit is an error. The document is not rendered as valid.
|
|
338
|
+
|
|
339
|
+
| Limit | Value | Code |
|
|
340
|
+
|---|---:|---|
|
|
341
|
+
| Expanded instances (`repeat`, `scatter`, `use`) | 2,000,000 | `E_TOO_MANY_INSTANCES` |
|
|
342
|
+
| Nesting, counting each element and each use-to-target step | 64 | `E_TOO_DEEP` |
|
|
343
|
+
| Commands in one path, after implicit repetition | 100,000 | `E_PATH_LIMIT` |
|
|
344
|
+
| Copies in one `repeat` or `scatter` | 1,000,000 | `E_RANGE` |
|
|
345
|
+
|
|
346
|
+
## 13. Lights, cameras and environment
|
|
347
|
+
|
|
348
|
+
- `<light type="directional|point|spot|ambient|hemisphere">` with `color`, `intensity`; directional and spot lights take `direction` or `target`; point and spot lights `position` and an optional cut-off `range`; spot lights `angle` (full cone, degrees) and `penumbra` (0–1); hemisphere lights `color` (sky) and `groundColor`. Lights are placed by their ancestors' transforms like any node. Without any light, readers use a default key light so content is visible.
|
|
349
|
+
- `<camera>` is perspective (`fov`, vertical, degrees) or orthographic (`height` of the visible area). Orientation comes from `target` (and `up`) or from `rotation` (looking down −Z); giving both is an error. `near`/`far` are optional. The first camera is the initial view.
|
|
350
|
+
- **Framing on other screens.** `fov` (or `height`) is the vertical extent of a view `aspect` wide and 1 high (default `aspect="1"`). `fit` says how a viewport of another aspect ratio a is filled, like SVG's `preserveAspectRatio`. With s = aspect / a, the vertical extent is scaled by:
|
|
351
|
+
- `contain` (default): max(1, s). The whole framed view stays visible ("meet").
|
|
352
|
+
- `cover`: min(1, s). The viewport is filled and the framed view is cropped ("slice").
|
|
353
|
+
- `width`: s. The framed width is kept.
|
|
354
|
+
- `height`: 1. The framed height is kept.
|
|
355
|
+
|
|
356
|
+
The defaults reproduce the usual rule of applying `fov` to the shorter viewport side. An author who framed a scene for a 16:9 page writes `aspect="1.778"`, and a portrait phone then shows the same width instead of cropping the sides.
|
|
357
|
+
- `<environment>` sets the clear colour (`background`), hemisphere ambient colours (`sky`, `ground`), their `intensity`, and tone-mapping `exposure`. Only the first `<environment>` is used (`W_MULTIPLE_ENVIRONMENTS`).
|
|
358
|
+
- `<controls>` describes how an interactive reader lets people move the camera. Only the first one is used (`W_MULTIPLE_CONTROLS`), and a document without one gets the reader's defaults (usually free orbiting).
|
|
359
|
+
- `mode="orbit"` (default): drag to orbit the target, pan and zoom; `zoom="false"` and `pan="false"` turn those off.
|
|
360
|
+
- `mode="none"`: a fixed view from the first camera.
|
|
361
|
+
- `mode="scroll"`: the first camera is the view, moved only by its scroll-timeline animations (section 14). Dragging is off and the canvas must not capture touch scrolling.
|
|
362
|
+
- Limits keep the camera between `minDistance` and `maxDistance` from its target and between `minPolar` and `maxPolar` degrees from straight up (`maxPolar="90"` keeps it above the target). `targetMin` and `targetMax` bound the orbit target, and so panning.
|
|
363
|
+
- `collide="true"` stops the camera in front of surfaces. A move from the last camera position to the next is blocked by the first surface whose front faces the camera (double-sided surfaces always block). The camera stays far enough from the surface that its near plane does not cut into it, and a move that hits a surface at an angle slides along it. A camera that starts inside an object can leave it, and an orbit target inside an object does not trap the camera. Elements with `collide="false"` (inherited by their children, available on every node) are ignored.
|
|
364
|
+
|
|
365
|
+
## 14. Animation
|
|
366
|
+
|
|
367
|
+
`<animate attr="…" dur="4s" …>` inside a node animates an attribute of its parent: `position`, `rotation` or `scale` of any node, and also `target` and `fov` of a `<camera>` (`E_INVALID_VALUE` elsewhere; animating the target of a camera oriented by `rotation` is `E_CONFLICTING_ATTRIBUTES`). Animation replaces the attribute value, so `transform` still applies outside it.
|
|
368
|
+
|
|
369
|
+
**Values.** Exactly one source is given (`E_MISSING_ATTRIBUTE` without one, `E_CONFLICTING_ATTRIBUTES` with several):
|
|
370
|
+
- `to` (with optional `from`, default: the parent's value): a straight interpolation.
|
|
371
|
+
- `values="v0; v1; …"`: two or more key values separated by semicolons. With `keyTimes="0, t1, …, 1"` (one per value, starting at 0, ending at 1, not decreasing), value i is reached at fraction tᵢ. Without it the keys are evenly spaced. Between keys the value is linear, or with `smooth="true"` on a centripetal Catmull-Rom curve through the keys. The end keys are extended by reflecting their neighbour, as for sweep spines.
|
|
372
|
+
- `along` (position and target only): a path in the same syntax as a sweep's spine (points, 3D path data or `helix()`; `smooth` interpolates points). The fraction is measured along the path's length, so the value moves at constant speed.
|
|
373
|
+
|
|
374
|
+
Each value has 3 numbers (1 or 3 for `scale`, 1 for `fov`).
|
|
375
|
+
|
|
376
|
+
**Timeline.** The animation turns a fraction f in 0..1 into a value, after `easing` (`linear`, `ease-in`, `ease-out`, `ease-in-out`) is applied to f.
|
|
377
|
+
- `timeline="time"` (default): `dur` is required. Before `begin` the animation has no effect. Then it plays `repeat` cycles (default `indefinite`), with `alternate="true"` playing every second cycle backwards, and after the last cycle it holds the end value.
|
|
378
|
+
- `timeline="scroll"`: f is the reader's scroll progress, clamped to 0..1, and `dur`, `begin`, `repeat` and `alternate` are not used. How progress relates to a page is up to the reader. The reference viewer maps a page section's scroll position to it, with optional stops pinned to page elements. A reader without a page holds progress at 0.
|
|
379
|
+
|
|
380
|
+
## 15. Diagnostics
|
|
381
|
+
|
|
382
|
+
Readers report problems in this canonical form:
|
|
383
|
+
|
|
384
|
+
```
|
|
385
|
+
ES3D Parse Error
|
|
386
|
+
line 2, column 11
|
|
387
|
+
|
|
388
|
+
Unknown attribute: raduis
|
|
389
|
+
|
|
390
|
+
Did you mean: radius?
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The title names the phase (`Parse`, `Reference`, `Geometry`, `Render`, `File`) and severity (`Error`, `Warning`). Codes used by the reference reader:
|
|
394
|
+
|
|
395
|
+
| Group | Codes |
|
|
396
|
+
|---|---|
|
|
397
|
+
| Syntax | `E_SYNTAX`, `E_UNEXPECTED_EOF`, `E_MISMATCHED_TAG`, `E_UNEXPECTED_CLOSE`, `E_UNCLOSED_ELEMENT`, `E_UNQUOTED_ATTRIBUTE`, `E_UNTERMINATED_ATTRIBUTE`, `E_UNTERMINATED_COMMENT`, `E_UNTERMINATED_CDATA`, `E_UNTERMINATED_PI`, `E_UNTERMINATED_DOCTYPE`, `E_UNTERMINATED_STRING`, `E_BAD_ENTITY`, `E_BAD_ESCAPE`, `E_MULTIPLE_ROOTS`, `E_TEXT_OUTSIDE_ROOT`, `E_EMPTY_DOCUMENT`, `E_COMPACT_ROOT`, `E_JSON`, `E_JSON_STRUCTURE` |
|
|
398
|
+
| Structure | `E_ROOT`, `E_VERSION`, `E_UNKNOWN_ELEMENT`, `E_MISPLACED_ELEMENT`, `E_TEXT_NOT_ALLOWED`, `E_MULTIPLE_SCENES` |
|
|
399
|
+
| Attributes | `E_UNKNOWN_ATTRIBUTE`, `E_DUPLICATE_ATTRIBUTE`, `E_MISSING_ATTRIBUTE`, `E_MISSING_VALUE`, `E_INVALID_VALUE`, `E_TYPE`, `E_RANGE`, `E_CONFLICTING_ATTRIBUTES` |
|
|
400
|
+
| References | `E_DUPLICATE_ID`, `E_UNKNOWN_REFERENCE`, `E_REFERENCE_KIND`, `E_CIRCULAR_REFERENCE` |
|
|
401
|
+
| Geometry | `E_GEOMETRY`, `E_MESH`, `E_DEGENERATE`, `E_DEGENERATE_SPINE`, `E_REVOLVE_AXIS`, `E_LOD_CHILDREN`, `E_TOO_MANY_INSTANCES`, `E_TOO_DEEP`, `E_PATH_LIMIT` |
|
|
402
|
+
| Warnings | `W_LEGACY_ROOT`, `W_EXTENSION`, `W_DEF_WITHOUT_ID`, `W_MATERIAL_WITHOUT_ID`, `W_MULTIPLE_ENVIRONMENTS`, `W_MULTIPLE_CONTROLS`, `W_DEGENERATE_CONTOUR`, `W_SELF_INTERSECTION`, `W_EMPTY_FILL`, `W_TEXTURE` |
|
|
403
|
+
|
|
404
|
+
Unknown names get a suggestion when a known name is within a small edit distance or shares a prefix (`raduis` → `radius`, `spotlight` → `spot`, `sphear` → `sphere`).
|
|
405
|
+
|
|
406
|
+
## 16. Extensibility and versioning
|
|
407
|
+
|
|
408
|
+
- Elements whose names start with `x-` (and their subtrees) are vendor extensions: readers that do not understand them skip them with `W_EXTENSION`. Unknown elements without the prefix are errors, so typos are never mistaken for extensions.
|
|
409
|
+
- Unknown attributes are always errors. (A future version may reserve an `x-` attribute prefix as well.)
|
|
410
|
+
- `version="1.x"` documents must remain readable by version-1 readers; incompatible changes require version 2, which version-1 readers reject with `E_VERSION`.
|
|
411
|
+
|
|
412
|
+
## 17. File names and media types
|
|
413
|
+
|
|
414
|
+
| | |
|
|
415
|
+
|---|---|
|
|
416
|
+
| Text (any syntax) | `.es3d`, UTF-8, optionally gzip-compressed (`.es3d.gz`) |
|
|
417
|
+
| Media type (proposed, unregistered) | `model/vnd.es3d` |
|