-
-
Notifications
You must be signed in to change notification settings - Fork 32
/
gltf.go
507 lines (455 loc) · 21.7 KB
/
gltf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
package gltf
import (
"encoding/base64"
"errors"
"strings"
"sync"
)
// Index is an utility function that returns a pointer to a int.
func Index(i int) *int {
return &i
}
// Float is an utility function that returns a pointer to a float64.
func Float(val float64) *float64 {
return &val
}
// An Asset is metadata about the glTF asset.
type Asset struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Copyright string `json:"copyright,omitempty"` // A copyright message suitable for display to credit the content creator.
Generator string `json:"generator,omitempty"` // Tool that generated this glTF model. Useful for debugging.
Version string `json:"version"` // The glTF version that this asset targets.
MinVersion string `json:"minVersion,omitempty"` // The minimum glTF version that this asset targets.
}
// Document defines the root object for a glTF asset.
type Document struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
ExtensionsUsed []string `json:"extensionsUsed,omitempty"`
ExtensionsRequired []string `json:"extensionsRequired,omitempty"`
Accessors []*Accessor `json:"accessors,omitempty"`
Animations []*Animation `json:"animations,omitempty"`
Asset Asset `json:"asset"`
Buffers []*Buffer `json:"buffers,omitempty"`
BufferViews []*BufferView `json:"bufferViews,omitempty"`
Cameras []*Camera `json:"cameras,omitempty"`
Images []*Image `json:"images,omitempty"`
Materials []*Material `json:"materials,omitempty"`
Meshes []*Mesh `json:"meshes,omitempty"`
Nodes []*Node `json:"nodes,omitempty"`
Samplers []*Sampler `json:"samplers,omitempty"`
Scene *int `json:"scene,omitempty"`
Scenes []*Scene `json:"scenes,omitempty"`
Skins []*Skin `json:"skins,omitempty"`
Textures []*Texture `json:"textures,omitempty"`
}
// NewDocument returns a new Document with sane defaults.
func NewDocument() *Document {
return &Document{
Scene: Index(0),
Scenes: []*Scene{{Name: "Root Scene"}},
Asset: Asset{
Generator: "qmuntal/gltf",
Version: "2.0",
},
}
}
// An Accessor is a typed view into a bufferView.
// An accessor provides a typed view into a bufferView or a subset of a bufferView
// similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer.
type Accessor struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
BufferView *int `json:"bufferView,omitempty"`
ByteOffset int `json:"byteOffset,omitempty"`
ComponentType ComponentType `json:"componentType"`
Normalized bool `json:"normalized,omitempty"` // Specifies whether integer data values should be normalized.
Count int `json:"count"` // The number of attributes referenced by this accessor.
Type AccessorType `json:"type"`
Max []float64 `json:"max,omitempty"` // Maximum value of each component in this attribute.
Min []float64 `json:"min,omitempty"` // Minimum value of each component in this attribute.
Sparse *Sparse `json:"sparse,omitempty"` // Sparse storage of attributes that deviate from their initialization value.
}
// Sparse storage of attributes that deviate from their initialization value.
type Sparse struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Count int `json:"count"` // Number of entries stored in the sparse array.
Indices SparseIndices `json:"indices"` // Index array of size count that points to those accessor attributes that deviate from their initialization value.
Values SparseValues `json:"values"` // Array of size count times number of components, storing the displaced accessor attributes pointed by indices.
}
// SparseValues stores the displaced accessor attributes pointed by accessor.sparse.indices.
type SparseValues struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
BufferView int `json:"bufferView"`
ByteOffset int `json:"byteOffset,omitempty"`
}
// SparseIndices defines the indices of those attributes that deviate from their initialization value.
type SparseIndices struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
BufferView int `json:"bufferView"`
ByteOffset int `json:"byteOffset,omitempty"`
ComponentType ComponentType `json:"componentType"`
}
// A Buffer points to binary geometry, animation, or skins.
// If Data length is 0 and the Buffer is an external resource the Data won't be flushed,
// which can be useful when there is no need to load data in memory.
type Buffer struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
URI string `json:"uri,omitempty"`
ByteLength int `json:"byteLength"`
Data []byte `json:"-"`
}
// IsEmbeddedResource returns true if the buffer points to an embedded resource.
func (b *Buffer) IsEmbeddedResource() bool {
return strings.HasPrefix(b.URI, mimetypeApplicationOctet)
}
// EmbeddedResource defines the buffer as an embedded resource and encodes the URI so it points to the the resource.
func (b *Buffer) EmbeddedResource() {
b.URI = mimetypeApplicationOctet + "," + base64.StdEncoding.EncodeToString(b.Data)
}
// marshalData decode the buffer from the URI. If the buffer is not en embedded resource the returned array will be empty.
func (b *Buffer) marshalData() ([]byte, error) {
if !b.IsEmbeddedResource() {
return nil, nil
}
startPos := len(mimetypeApplicationOctet) + 1
if len(b.URI) < startPos {
return nil, errors.New("gltf: Invalid base64 content")
}
sl, err := base64.StdEncoding.DecodeString(b.URI[startPos:])
if len(sl) == 0 || err != nil {
return nil, err
}
return sl, nil
}
// BufferView is a view into a buffer generally representing a subset of the buffer.
type BufferView struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Buffer int `json:"buffer"`
ByteOffset int `json:"byteOffset,omitempty"`
ByteLength int `json:"byteLength"`
ByteStride int `json:"byteStride,omitempty"`
Target Target `json:"target,omitempty"`
Name string `json:"name,omitempty"`
}
// The Scene contains a list of root nodes.
type Scene struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
Nodes []int `json:"nodes,omitempty"`
}
// A Node in the node hierarchy.
// It can have either a matrix or any combination of translation/rotation/scale (TRS) properties.
type Node struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
Camera *int `json:"camera,omitempty"`
Children []int `json:"children,omitempty"`
Skin *int `json:"skin,omitempty"`
Matrix [16]float64 `json:"matrix"` // A 4x4 transformation matrix stored in column-major order.
Mesh *int `json:"mesh,omitempty"`
Rotation [4]float64 `json:"rotation"` // The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar.
Scale [3]float64 `json:"scale"`
Translation [3]float64 `json:"translation"`
Weights []float64 `json:"weights,omitempty"` // The weights of the instantiated Morph Target.
}
// MatrixOrDefault returns the node matrix if it represents a valid affine matrix, else return the default one.
func (n *Node) MatrixOrDefault() [16]float64 {
if n.Matrix == emptyMatrix {
return DefaultMatrix
}
return n.Matrix
}
// RotationOrDefault returns the node rotation if it represents a valid quaternion, else return the default one.
func (n *Node) RotationOrDefault() [4]float64 {
if n.Rotation == emptyRotation {
return DefaultRotation
}
return n.Rotation
}
// ScaleOrDefault returns the node scale if it represents a valid scale factor, else return the default one.
func (n *Node) ScaleOrDefault() [3]float64 {
if n.Scale == emptyScale {
return DefaultScale
}
return n.Scale
}
// TranslationOrDefault returns the node translation.
func (n *Node) TranslationOrDefault() [3]float64 {
return n.Translation
}
// Skin defines joints and matrices.
type Skin struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
InverseBindMatrices *int `json:"inverseBindMatrices,omitempty"` // The index of the accessor containing the floating-point 4x4 inverse-bind matrices.
Skeleton *int `json:"skeleton,omitempty"` // The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root.
Joints []int `json:"joints"` // Indices of skeleton nodes, used as joints in this skin.
}
// A Camera projection. A node can reference a camera to apply a transform to place the camera in the scene.
type Camera struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
Orthographic *Orthographic `json:"orthographic,omitempty"`
Perspective *Perspective `json:"perspective,omitempty"`
}
// Orthographic camera containing properties to create an orthographic projection matrix.
type Orthographic struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Xmag float64 `json:"xmag"` // The horizontal magnification of the view.
Ymag float64 `json:"ymag"` // The vertical magnification of the view.
Zfar float64 `json:"zfar"` // The distance to the far clipping plane.
Znear float64 `json:"znear"` // The distance to the near clipping plane.
}
// Perspective camera containing properties to create a perspective projection matrix.
type Perspective struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
AspectRatio *float64 `json:"aspectRatio,omitempty"`
Yfov float64 `json:"yfov"` // The vertical field of view in radians.
Zfar *float64 `json:"zfar,omitempty"` // The distance to the far clipping plane.
Znear float64 `json:"znear"` // The distance to the near clipping plane.
}
// A Mesh is a set of primitives to be rendered. A node can contain one mesh. A node's transform places the mesh in the scene.
type Mesh struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
Primitives []*Primitive `json:"primitives"`
Weights []float64 `json:"weights,omitempty"`
}
// Primitive defines the geometry to be rendered with the given material.
type Primitive struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Attributes PrimitiveAttributes `json:"attributes"`
Indices *int `json:"indices,omitempty"` // The index of the accessor that contains the indices.
Material *int `json:"material,omitempty"`
Mode PrimitiveMode `json:"mode,omitempty"`
Targets []PrimitiveAttributes `json:"targets,omitempty"` // Only POSITION, NORMAL, and TANGENT supported.
}
// The Material appearance of a primitive.
type Material struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
PBRMetallicRoughness *PBRMetallicRoughness `json:"pbrMetallicRoughness,omitempty"`
NormalTexture *NormalTexture `json:"normalTexture,omitempty"`
OcclusionTexture *OcclusionTexture `json:"occlusionTexture,omitempty"`
EmissiveTexture *TextureInfo `json:"emissiveTexture,omitempty"`
EmissiveFactor [3]float64 `json:"emissiveFactor,omitempty"`
AlphaMode AlphaMode `json:"alphaMode,omitempty"`
AlphaCutoff *float64 `json:"alphaCutoff,omitempty"`
DoubleSided bool `json:"doubleSided,omitempty"`
}
// AlphaCutoffOrDefault returns the scale if it is not nil, else return the default one.
func (m *Material) AlphaCutoffOrDefault() float64 {
if m.AlphaCutoff == nil {
return 0.5
}
return *m.AlphaCutoff
}
// A NormalTexture references to a normal texture.
type NormalTexture struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Index *int `json:"index,omitempty"`
TexCoord int `json:"texCoord,omitempty"` // The index of texture's TEXCOORD attribute used for texture coordinate mapping.
Scale *float64 `json:"scale,omitempty"`
}
// ScaleOrDefault returns the scale if it is not nil, else return the default one.
func (n *NormalTexture) ScaleOrDefault() float64 {
if n.Scale == nil {
return 1
}
return *n.Scale
}
// An OcclusionTexture references to an occlusion texture
type OcclusionTexture struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Index *int `json:"index,omitempty"`
TexCoord int `json:"texCoord,omitempty"` // The index of texture's TEXCOORD attribute used for texture coordinate mapping.
Strength *float64 `json:"strength,omitempty"`
}
// StrengthOrDefault returns the strength if it is not nil, else return the default one.
func (o *OcclusionTexture) StrengthOrDefault() float64 {
if o.Strength == nil {
return 1
}
return *o.Strength
}
// PBRMetallicRoughness defines a set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology.
type PBRMetallicRoughness struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
BaseColorFactor *[4]float64 `json:"baseColorFactor,omitempty"`
BaseColorTexture *TextureInfo `json:"baseColorTexture,omitempty"`
MetallicFactor *float64 `json:"metallicFactor,omitempty"`
RoughnessFactor *float64 `json:"roughnessFactor,omitempty"`
MetallicRoughnessTexture *TextureInfo `json:"metallicRoughnessTexture,omitempty"`
}
// MetallicFactorOrDefault returns the metallic factor if it is not nil, else return the default one.
func (p *PBRMetallicRoughness) MetallicFactorOrDefault() float64 {
if p.MetallicFactor == nil {
return 1
}
return *p.MetallicFactor
}
// RoughnessFactorOrDefault returns the roughness factor if it is not nil, else return the default one.
func (p *PBRMetallicRoughness) RoughnessFactorOrDefault() float64 {
if p.RoughnessFactor == nil {
return 1
}
return *p.RoughnessFactor
}
// BaseColorFactorOrDefault returns the base color factor if it is not nil, else return the default one.
func (p *PBRMetallicRoughness) BaseColorFactorOrDefault() [4]float64 {
if p.BaseColorFactor == nil {
return [4]float64{1, 1, 1, 1}
}
return *p.BaseColorFactor
}
// TextureInfo references to a texture.
type TextureInfo struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Index int `json:"index"`
TexCoord int `json:"texCoord,omitempty"` // The index of texture's TEXCOORD attribute used for texture coordinate mapping.
}
// A Texture and its sampler.
type Texture struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
Sampler *int `json:"sampler,omitempty"`
Source *int `json:"source,omitempty"`
}
// Sampler of a texture for filtering and wrapping modes.
type Sampler struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
MagFilter MagFilter `json:"magFilter,omitempty"`
MinFilter MinFilter `json:"minFilter,omitempty"`
WrapS WrappingMode `json:"wrapS,omitempty"`
WrapT WrappingMode `json:"wrapT,omitempty"`
}
// Image data used to create a texture. Image can be referenced by URI or bufferView index.
// mimeType is required in the latter case.
type Image struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
URI string `json:"uri,omitempty"`
MimeType string `json:"mimeType,omitempty"` // Manadatory if BufferView is defined.
BufferView *int `json:"bufferView,omitempty"` // Use this instead of the image's uri property.
}
// IsEmbeddedResource returns true if the buffer points to an embedded resource.
func (im *Image) IsEmbeddedResource() bool {
return strings.HasPrefix(im.URI, mimetypeImagePNG) || strings.HasPrefix(im.URI, mimetypeImageJPG)
}
// MarshalData decode the image from the URI. If the image is not en embedded resource the returned array will be empty.
func (im *Image) MarshalData() ([]byte, error) {
if !im.IsEmbeddedResource() {
return []byte{}, nil
}
mimetype := mimetypeImagePNG
if strings.HasPrefix(im.URI, mimetypeImageJPG) {
mimetype = mimetypeImageJPG
}
startPos := len(mimetype) + 1
if len(im.URI) < startPos {
return []byte{}, errors.New("gltf: Invalid base64 content")
}
return base64.StdEncoding.DecodeString(im.URI[startPos:])
}
// An Animation keyframe.
type Animation struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Name string `json:"name,omitempty"`
Channels []*AnimationChannel `json:"channels"`
Samplers []*AnimationSampler `json:"samplers"`
}
// AnimationSampler combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target).
type AnimationSampler struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Input int `json:"input"` // The index of an accessor containing keyframe input values.
Interpolation Interpolation `json:"interpolation,omitempty"`
Output int `json:"output"` // The index of an accessor containing keyframe output values.
}
// The AnimationChannel targets an animation's sampler at a node's property.
type AnimationChannel struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Sampler int `json:"sampler"`
Target AnimationChannelTarget `json:"target"`
}
// AnimationChannelTarget describes the index of the node and TRS property that an animation channel targets.
// The Path represents the name of the node's TRS property to modify, or the "weights" of the Morph Targets it instantiates.
// For the "translation" property, the values that are provided by the sampler are the translation along the x, y, and z axes.
// For the "rotation" property, the values are a quaternion in the order (x, y, z, w), where w is the scalar.
// For the "scale" property, the values are the scaling factors along the x, y, and z axes.
type AnimationChannelTarget struct {
Extensions Extensions `json:"extensions,omitempty"`
Extras any `json:"extras,omitempty"`
Node *int `json:"node,omitempty"`
Path TRSProperty `json:"path"`
}
// Extensions is map where the keys are the extension identifiers and the values are the extensions payloads.
// If a key matches with one of the supported extensions the value will be marshalled as a pointer to the extension struct.
// If a key does not match with any of the supported extensions the value will be a json.RawMessage so its decoding can be delayed.
type Extensions map[string]any
var (
extMu sync.RWMutex
extensions = make(map[string]func([]byte) (any, error))
)
// RegisterExtension registers a function that returns a new extension of the given
// byte array. This is intended to be called from the init function in
// packages that implement extensions.
func RegisterExtension(key string, f func([]byte) (any, error)) {
extMu.Lock()
defer extMu.Unlock()
extensions[key] = f
}
func queryExtension(key string) (func([]byte) (any, error), bool) {
extMu.RLock()
ext, ok := extensions[key]
extMu.RUnlock()
return ext, ok
}
// SizeOfElement returns the size, in bytes, of an element.
// The element size may not be (component size) * (number of components),
// as some of the elements are tightly packed in order to ensure
// that they are aligned to 4-byte boundaries.
func SizeOfElement(c ComponentType, t AccessorType) int {
// special cases
switch {
case (t == AccessorVec3 || t == AccessorVec2) && (c == ComponentByte || c == ComponentUbyte):
return 4
case t == AccessorVec3 && (c == ComponentShort || c == ComponentUshort):
return 8
case t == AccessorMat2 && (c == ComponentByte || c == ComponentUbyte):
return 8
case t == AccessorMat3 && (c == ComponentByte || c == ComponentUbyte):
return 12
case t == AccessorMat3 && (c == ComponentShort || c == ComponentUshort):
return 24
}
return c.ByteSize() * t.Components()
}