Skip to content

Commit

Permalink
Merge 8a84042 into cfb4ba0
Browse files Browse the repository at this point in the history
  • Loading branch information
qmuntal committed Nov 10, 2019
2 parents cfb4ba0 + 8a84042 commit e2354ae
Show file tree
Hide file tree
Showing 12 changed files with 406 additions and 78 deletions.
53 changes: 39 additions & 14 deletions README.md
Expand Up @@ -12,32 +12,57 @@ A Go package for simple, efficient, and robust serialization/deserialization of

## Features

* High parsing speed and moderate memory consumption.
* High parsing speed and moderate memory consumption
* glTF specification v2.0.0
* [x] ASCII glTF.
* [x] Binary glTF(GLB).
* [x] PBR material description.
* [x] ASCII glTF
* [x] Binary glTF(GLB)
* [x] PBR material description
* glTF validaton
* [x] Validate against schemas.
* [ ] Validate coherence.
* [x] Validate against schemas
* [ ] Validate coherence
* Buffers
* [x] Parse BASE64 encoded embedded buffer data(DataURI).
* [x] Load .bin file.
* [x] Parse BASE64 encoded embedded buffer data(DataURI)
* [x] Load .bin file
* Read from io.Reader
* [x] Boilerplate for disk loading.
* [x] Custom callback handlers.
* [x] Automatic ASCII / glTF detection.
* [x] Boilerplate for disk loading
* [x] Custom callback handlers
* [x] Automatic ASCII / glTF detection
* Write to io.Writer
* [x] Boilerplate for disk saving.
* [x] Custom callback handlers.
* [x] Boilerplate for disk saving
* [x] Custom callback handlers
* [x] ASCII / Binary
* Extensions
* [ ] KHR_draco_mesh_compression
* [ ] KHR_lights_punctual
* [x] KHR_lights_punctual
* [x] KHR_materials_pbrSpecularGlossiness
* [x] KHR_materials_unlit
* [ ] KHR_techniques_webgl
* [x] KHR_texture_transform
* [x] Support custom extensions

## Extensions

This module is designed to support dynamic extensions. By default only the core specification is decoded and the data inside the extensions objects are stored as `json.RawMessage` so they can be decoded outside this package or automatically encoded when saving the document.

To decode one of the supported extensions the only required action is to import the associated package, this way the extension will not be stored as `json.RawMessage` but as the type defined in the extension package:

```go
import (
"github.com/qmuntal/gltf"
"github.com/qmuntal/gltf/lightspuntual"
)

func ExampleExension() {
doc, _ := gltf.Open("...")
if v, ok := doc.Extensions[lightspuntual.ExtensionName]; ok {
for _, l := range v.(lightspuntual.Lights) {
fmt.Print(l.Type)
}
}
}
```

To support custom extensions you should use the `gltf.RegisterExtension` method.

## Perfomance

Expand Down
115 changes: 115 additions & 0 deletions lightspuntual/lightspuntual.go
@@ -0,0 +1,115 @@
// This extension defines three "punctual" light types: directional, point and spot.
// Punctual lights are defined as parameterized, infinitely small points
// that emit light in well-defined directions and intensities.
package lightspuntual

import (
"encoding/json"
"math"

"github.com/qmuntal/gltf"
)

const (
// ExtensionName defines the KHR_lights_punctual unique key.
ExtensionName = "KHR_lights_punctual"
)

func init() {
gltf.RegisterExtension(ExtensionName, Unmarshal)
}

type envelop struct {
Lights Lights
Light *LightIndex
}

// Unmarshal decodes the json data into the correct type.
func Unmarshal(data []byte) (interface{}, error) {
var env envelop
if err := json.Unmarshal([]byte(data), &env); err != nil {
return nil, err
}
if env.Light != nil {
return *env.Light, nil
}
return env.Lights, nil
}

const (
// TypeDirectional lights act as though they are infinitely far away and emit light in the direction of the local -z axis.
TypeDirectional = "directional"
// TypePoint lights emit light in all directions from their position in space.
TypePoint = "point"
// TypeSpot lights emit light in a cone in the direction of the local -z axis.
TypeSpot = "spot"
)

// LightIndex is the id of the light referenced by this node.
type LightIndex uint32

// Spot defines the spot cone.
type Spot struct {
InnerConeAngle float64 `json:"innerConeAngle,omitempty"`
OuterConeAngle *float64 `json:"outerConeAngle,omitempty"`
}

// OuterConeAngleOrDefault returns the OuterConeAngle if it is not nil, else return the default one.
func (s *Spot) OuterConeAngleOrDefault() float64 {
if s.OuterConeAngle == nil {
return math.Pi / 4
}
return *s.OuterConeAngle
}

// UnmarshalJSON unmarshal the spot with the correct default values.
func (s *Spot) UnmarshalJSON(data []byte) error {
type alias Spot
tmp := alias(Spot{OuterConeAngle: gltf.Float64(math.Pi / 4)})
err := json.Unmarshal(data, &tmp)
if err == nil {
*s = Spot(tmp)
}
return err
}

// Lights defines a list of Lights.
type Lights []*Light

// Light defines a directional, point, or spot light.
// When a light's type is spot, the spot property on the light is required.
type Light struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Color *gltf.RGB `json:"color,omitempty"`
Intensity *float64 `json:"intensity,omitempty"`
Range *float64 `json:"range,omitempty"`
Spot *Spot `json:"spot,omitempty"`
}

// IntensityOrDefault returns the itensity if it is not nil, else return the default one.
func (l *Light) IntensityOrDefault() float64 {
if l.Intensity == nil {
return 1
}
return *l.Intensity
}

// ColorOrDefault returns the color if it is not nil, else return the default one.
func (l *Light) ColorOrDefault() gltf.RGB {
if l.Color == nil {
return *gltf.NewRGB()
}
return *l.Color
}

// UnmarshalJSON unmarshal the light with the correct default values.
func (l *Light) UnmarshalJSON(data []byte) error {
type alias Light
tmp := alias(Light{Color: gltf.NewRGB(), Intensity: gltf.Float64(1), Range: gltf.Float64(math.Inf(0))})
err := json.Unmarshal(data, &tmp)
if err == nil {
*l = Light(tmp)
}
return err
}
152 changes: 152 additions & 0 deletions lightspuntual/lightspuntual_test.go
@@ -0,0 +1,152 @@
package lightspuntual

import (
"math"
"reflect"
"testing"

"github.com/go-test/deep"
"github.com/qmuntal/gltf"
)

func TestLight_IntensityOrDefault(t *testing.T) {
tests := []struct {
name string
l *Light
want float64
}{
{"empty", &Light{}, 1},
{"other", &Light{Intensity: gltf.Float64(0.5)}, 0.5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.l.IntensityOrDefault(); got != tt.want {
t.Errorf("Light.IntensityOrDefault() = %v, want %v", got, tt.want)
}
})
}
}

func TestLight_ColorOrDefault(t *testing.T) {
tests := []struct {
name string
l *Light
want gltf.RGB
}{
{"empty", &Light{}, *gltf.NewRGB()},
{"other", &Light{Color: &gltf.RGB{R: 0.8, G: 0.8, B: 0.8}}, gltf.RGB{R: 0.8, G: 0.8, B: 0.8}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.l.ColorOrDefault(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Light.ColorOrDefault() = %v, want %v", got, tt.want)
}
})
}
}

func TestSpot_OuterConeAngleOrDefault(t *testing.T) {
tests := []struct {
name string
s *Spot
want float64
}{
{"empty", &Spot{}, math.Pi / 4},
{"other", &Spot{OuterConeAngle: gltf.Float64(0.5)}, 0.5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.s.OuterConeAngleOrDefault(); got != tt.want {
t.Errorf("Spot.OuterConeAngleOrDefault() = %v, want %v", got, tt.want)
}
})
}
}

func TestLight_UnmarshalJSON(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
l *Light
args args
want *Light
wantErr bool
}{
{"default", new(Light), args{[]byte("{}")}, &Light{
Color: gltf.NewRGB(), Intensity: gltf.Float64(1), Range: gltf.Float64(math.Inf(0)),
}, false},
{"nodefault", new(Light), args{[]byte(`{
"color": [0.3, 0.7, 1.0],
"name": "AAA",
"intensity": 40.0,
"type": "spot",
"range": 10.0,
"spot": {
"innerConeAngle": 1.0,
"outerConeAngle": 2.0
}
}`)}, &Light{
Name: "AAA", Type: "spot", Color: &gltf.RGB{R: 0.3, G: 0.7, B: 1.0}, Intensity: gltf.Float64(40), Range: gltf.Float64(10),
Spot: &Spot{
InnerConeAngle: 1.0,
OuterConeAngle: gltf.Float64(2.0),
},
}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.l.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
t.Errorf("Light.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(tt.l, tt.want) {
t.Errorf("Light.UnmarshalJSON() = %+v, want %+v", tt.l, tt.want)
}
})
}
}

func TestUnmarshal(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
want interface{}
wantErr bool
}{
{"error", args{[]byte(`{"light: 1}`)}, nil, true},
{"index", args{[]byte(`{"light": 1}`)}, LightIndex(1), false},
{"lights", args{[]byte(`{"lights": [
{
"color": [1.0, 0.9, 0.7],
"name": "Directional",
"intensity": 3.0,
"type": "directional"
},
{
"color": [1.0, 0.0, 0.0],
"name": "Point",
"intensity": 20.0,
"type": "point"
}
]}`)}, Lights{
{Color: &gltf.RGB{R: 1.0, G: 0.9, B: 0.7}, Name: "Directional", Intensity: gltf.Float64(3.0), Type: "directional", Range: gltf.Float64(math.Inf(0))},
{Color: &gltf.RGB{R: 1.0}, Name: "Point", Intensity: gltf.Float64(20.0), Type: "point", Range: gltf.Float64(math.Inf(0))},
}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Unmarshal(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := deep.Equal(got, tt.want); diff != nil {
t.Errorf("Unmarshal() = %v", diff)
}
})
}
}
3 changes: 1 addition & 2 deletions marshal.go
Expand Up @@ -224,8 +224,7 @@ func (ext *Extensions) UnmarshalJSON(data []byte) error {
if err == nil {
for key, value := range raw {
if extFactory, ok := extensions[key]; ok {
n := extFactory()
err := json.Unmarshal(value, n)
n, err := extFactory(value)
if err != nil {
(*ext)[key] = value
} else {
Expand Down
6 changes: 5 additions & 1 deletion marshal_test.go
Expand Up @@ -379,7 +379,11 @@ func (f *fakeExt) UnmarshalJSON(data []byte) error {
}

func TestExtensions_UnmarshalJSON(t *testing.T) {
RegisterExtension("fake_ext", func() json.Unmarshaler { return new(fakeExt) })
RegisterExtension("fake_ext", func(data []byte) (interface{}, error) {
e := new(fakeExt)
err := json.Unmarshal(data, e)
return e, err
})
type args struct {
data []byte
}
Expand Down
14 changes: 8 additions & 6 deletions specular/specular.go
Expand Up @@ -8,17 +8,19 @@ import (
)

const (
// ExtPBRSpecularGlossiness defines the PBRSpecularGlossiness unique key.
ExtPBRSpecularGlossiness = "KHR_materials_pbrSpecularGlossiness"
// ExtensionName defines the PBRSpecularGlossiness unique key.
ExtensionName = "KHR_materials_pbrSpecularGlossiness"
)

// New returns a new specular.PBRSpecularGlossiness.
func New() json.Unmarshaler {
return new(PBRSpecularGlossiness)
// Unmarshal decodes the json data into the correct type.
func Unmarshal(data []byte) (interface{}, error) {
pbr := new(PBRSpecularGlossiness)
err := json.Unmarshal(data, pbr)
return pbr, err
}

func init() {
gltf.RegisterExtension(ExtPBRSpecularGlossiness, New)
gltf.RegisterExtension(ExtensionName, Unmarshal)
}

// PBRSpecularGlossiness defines a specular-glossiness material model.
Expand Down

0 comments on commit e2354ae

Please sign in to comment.