objloader

This commit is contained in:
ouczbs 2023-12-22 22:04:27 +08:00
commit d5ac5461e2
50 changed files with 7866 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.log
.idea/*
*/.ipynb_checkpoints/*

84
asset/model/cube.obj Normal file
View File

@ -0,0 +1,84 @@
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v 0.500000 0.500000 0.500000
v 0.500000 0.500000 -0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 0.500000 0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 0.500000 0.500000
v 0.500000 0.500000 -0.500000
v -0.500000 0.500000 0.500000
v -0.500000 0.500000 -0.500000
v 0.500000 -0.500000 0.500000
v 0.500000 -0.500000 -0.500000
v -0.500000 -0.500000 0.500000
v -0.500000 -0.500000 -0.500000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
f 1/1/1 2/2/2 4/4/4
f 4/4/4 2/2/2 3/3/3
f 5/5/5 8/8/8 6/6/6
f 8/8/8 7/7/7 6/6/6
f 9/9/9 11/11/11 10/10/10
f 10/10/10 11/11/11 12/12/12
f 13/13/13 15/15/15 14/14/14
f 14/14/14 15/15/15 16/16/16
f 17/17/17 18/18/18 19/19/19
f 18/18/18 20/20/20 19/19/19
f 21/21/21 23/23/23 22/22/22
f 22/22/22 23/23/23 24/24/24

4811
asset/model/sphere.obj Normal file

File diff suppressed because it is too large Load Diff

17
cmd/main.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"zworld/engine"
"zworld/engine/core/zlog"
"zworld/engine/rule"
"zworld/plugin/objloader"
)
func main() {
w := engine.NewWorld(nil, engine.FTimeRuleOption(&rule.FTimeRule{}), engine.FActorRuleOption(&rule.FActorRule{}))
for i := 1; i < 100; i++ {
w.Tick()
}
err, mesh := objloader.LoadObj("asset/model/sphere.obj")
zlog.Infof("err", err, len(mesh.Positions))
}

View File

@ -0,0 +1 @@
package Component

View File

@ -0,0 +1 @@
package Component

View File

@ -0,0 +1,4 @@
package Component
type IComponent interface {
}

17
engine/actor/actor.go Normal file
View File

@ -0,0 +1,17 @@
package actor
import (
"zworld/engine/actor/Component"
)
type FActorCompWrap struct {
Render Component.IComponent
}
type FActor struct {
CompList []Component.IComponent
CompWrap *FActorCompWrap
}
func (r *FActor) Tick(delta float32) {
}

16
engine/core/zlog/type.go Normal file
View File

@ -0,0 +1,16 @@
package zlog
import (
"go.uber.org/zap/zapcore"
)
// Level is type of log levels
type Level = zapcore.Level
type FLogf = func(template string, args ...interface{})
type FLog = func(args ...interface{})
var (
Debugf, Infof, Warnf, Errorf, Panicf, Fatalf FLogf
Debug, Error, Panic, Fatal FLog
)

98
engine/core/zlog/zlog.go Normal file
View File

@ -0,0 +1,98 @@
package zlog
import (
"go.uber.org/zap"
"strings"
)
var (
cfg zap.Config
logger *zap.Logger
sugar *zap.SugaredLogger
source string
currentLevel Level
)
func init() {
currentLevel = zap.DebugLevel
cfg = zap.NewDevelopmentConfig()
cfg.Development = true
rebuildLoggerFromCfg()
}
// SetSource sets the component name (dispatcher/gate/game) of gwlog module
func SetSource(source_ string) {
source = source_
rebuildLoggerFromCfg()
}
func SetParseLevel(slv string) {
lv := ParseLevel(slv)
SetLevel(lv)
}
// SetLevel sets the zlog level
func SetLevel(lv Level) {
currentLevel = lv
cfg.Level.SetLevel(lv)
}
// GetLevel get the current zlog level
func GetLevel() Level {
return currentLevel
}
// SetOutput sets the output writer
func SetOutput(outputs []string) {
cfg.OutputPaths = outputs
rebuildLoggerFromCfg()
}
// ParseLevel converts string to Levels
func ParseLevel(s string) Level {
if strings.ToLower(s) == "debug" {
return zap.DebugLevel
} else if strings.ToLower(s) == "info" {
return zap.InfoLevel
} else if strings.ToLower(s) == "warn" || strings.ToLower(s) == "warning" {
return zap.WarnLevel
} else if strings.ToLower(s) == "error" {
return zap.ErrorLevel
} else if strings.ToLower(s) == "panic" {
return zap.PanicLevel
} else if strings.ToLower(s) == "fatal" {
return zap.FatalLevel
}
Errorf("ParseLevel: unknown level: %s", s)
return zap.DebugLevel
}
func rebuildLoggerFromCfg() {
newLogger, err := cfg.Build()
if err != nil {
panic(err)
return
}
if logger != nil {
logger.Sync()
}
logger = newLogger
if source != "" {
logger = logger.With(zap.String("source", source))
}
sugar = logger.Sugar()
initFLog()
}
func initFLog() {
Debugf = sugar.Debugf
Infof = sugar.Infof
Warnf = sugar.Warnf
Errorf = sugar.Errorf
Panicf = sugar.Panicf
Fatalf = sugar.Fatalf
Debug = sugar.Debug
Error = sugar.Error
Panic = sugar.Panic
Fatal = sugar.Fatal
}

View File

@ -0,0 +1,15 @@
package instance
import (
"github.com/vkngwrapper/extensions/v2/ext_debug_utils"
"github.com/vkngwrapper/extensions/v2/khr_get_physical_device_properties2"
"github.com/vkngwrapper/extensions/v2/khr_surface"
)
var extensions = []string{
khr_surface.ExtensionName,
khr_get_physical_device_properties2.ExtensionName,
ext_debug_utils.ExtensionName,
"VK_KHR_xcb_surface",
}

View File

@ -0,0 +1,16 @@
package instance
import (
"github.com/vkngwrapper/extensions/v2/ext_debug_utils"
"github.com/vkngwrapper/extensions/v2/khr_get_physical_device_properties2"
"github.com/vkngwrapper/extensions/v2/khr_surface"
)
var extensions = []string{
khr_surface.ExtensionName,
ext_debug_utils.ExtensionName,
khr_get_physical_device_properties2.ExtensionName,
"VK_EXT_debug_report",
"VK_EXT_metal_surface",
}

View File

@ -0,0 +1,62 @@
package instance
import (
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/vkngwrapper/core/v2"
"github.com/vkngwrapper/core/v2/common"
"github.com/vkngwrapper/core/v2/core1_0"
)
var layers = []string{
"VK_LAYER_KHRONOS_validation",
//"VK_LAYER_LUNARG_api_dump",
}
type T interface {
EnumeratePhysicalDevices() []core1_0.PhysicalDevice
Destroy()
Ptr() core1_0.Instance
}
type instance struct {
ptr core1_0.Instance
}
func New(appName string) T {
loader, err := core.CreateLoaderFromProcAddr(glfw.GetVulkanGetInstanceProcAddress())
if err != nil {
panic(err)
}
handle, _, err := loader.CreateInstance(nil, core1_0.InstanceCreateInfo{
APIVersion: common.APIVersion(common.CreateVersion(1, 1, 0)),
ApplicationName: appName,
ApplicationVersion: common.CreateVersion(0, 1, 0),
EngineName: "goworld",
EngineVersion: common.CreateVersion(0, 2, 1),
EnabledLayerNames: layers,
EnabledExtensionNames: extensions,
})
if err != nil {
panic(err)
}
return &instance{
ptr: handle,
}
}
func (i *instance) Ptr() core1_0.Instance {
return i.ptr
}
func (i *instance) Destroy() {
i.ptr.Destroy(nil)
i.ptr = nil
}
func (i *instance) EnumeratePhysicalDevices() []core1_0.PhysicalDevice {
r, _, err := i.ptr.EnumeratePhysicalDevices()
if err != nil {
panic(err)
}
return r
}

21
engine/rule/actor.go Normal file
View File

@ -0,0 +1,21 @@
package rule
type FActor struct {
}
func (r *FActor) Tick(delta float32) {
}
type FActorRule struct {
actorList []*FActor
}
func (r *FActorRule) Tick(w IWorld, delta float32) {
for _, actor := range r.actorList {
actor.Tick(delta)
}
}
func (r *FActorRule) GetPriority() RulePriority {
return EActorRule
}

11
engine/rule/render.go Normal file
View File

@ -0,0 +1,11 @@
package rule
type FRenderRule struct {
}
func (r *FRenderRule) Tick(w IWorld, delta float32) {
}
func (r *FRenderRule) GetPriority() RulePriority {
return ERenderRule
}

21
engine/rule/time.go Normal file
View File

@ -0,0 +1,21 @@
package rule
type FTimeRule struct {
isPause bool
interval float32
}
func (r *FTimeRule) GetInterval() float32 {
if r.isPause {
return 0
}
return r.interval
}
func (r *FTimeRule) Tick(w IWorld, delta float32) {
if r.isPause {
return
}
}
func (r *FTimeRule) GetPriority() RulePriority {
return ETimeRule
}

16
engine/rule/type.go Normal file
View File

@ -0,0 +1,16 @@
package rule
type IWorld interface {
}
type IRule interface {
Tick(w IWorld, delta float32)
GetPriority() RulePriority
}
type RulePriority int
const (
ETimeRule = iota
ERenderRule
EActorRule
)

7
engine/type.go Normal file
View File

@ -0,0 +1,7 @@
package engine
import "zworld/engine/rule"
type (
IRule = rule.IRule
)

26
engine/world.go Normal file
View File

@ -0,0 +1,26 @@
package engine
import (
"zworld/engine/rule"
)
type FWorldRuleWrap struct {
TimeRule *rule.FTimeRule
ActorRule *rule.FActorRule
}
type FWorld struct {
Name string
RuleList []IRule
RuleWrap *FWorldRuleWrap
}
func (w *FWorld) Tick() {
delta := w.RuleWrap.TimeRule.GetInterval()
if delta == 0 {
w.RuleWrap.TimeRule.Tick(w, delta)
return
}
for _, rule := range w.RuleList {
rule.Tick(w, delta)
}
}

39
engine/world_new.go Normal file
View File

@ -0,0 +1,39 @@
package engine
import (
"sort"
"zworld/engine/rule"
)
type FWorldOption func(*FWorld)
func FTimeRuleOption(rule *rule.FTimeRule) FWorldOption {
return func(world *FWorld) {
world.RuleWrap.TimeRule = rule
world.RuleList = append(world.RuleList, rule)
}
}
func FActorRuleOption(rule *rule.FActorRule) FWorldOption {
return func(world *FWorld) {
world.RuleWrap.ActorRule = rule
world.RuleList = append(world.RuleList, rule)
}
}
func NewWorld(wrap *FWorldRuleWrap, ops ...FWorldOption) *FWorld {
w := FWorld{}
if wrap == nil {
wrap = &FWorldRuleWrap{}
}
w.RuleWrap = wrap
for _, op := range ops {
op(&w)
}
sort.Slice(w.RuleList, func(i, j int) bool {
pi, pj := w.RuleList[i].GetPriority(), w.RuleList[j].GetPriority()
if pi != pj {
return pi < pj
}
return i < j
})
return &w
}

18
go.mod Normal file
View File

@ -0,0 +1,18 @@
module zworld
go 1.20
require (
github.com/CannibalVox/cgoparam v1.1.0 // indirect
github.com/go-gl/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/ojrac/opensimplex-go v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/vkngwrapper/core/v2 v2.2.1 // indirect
github.com/vkngwrapper/extensions/v2 v2.2.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
)

55
go.sum Normal file
View File

@ -0,0 +1,55 @@
github.com/CannibalVox/VKng v0.0.0-20220707035000-0931f864c378 h1:VT+Uklgvu+BMI2MLouYtTd/HL7uqE/ds3n9yeP8bj8I=
github.com/CannibalVox/cgoparam v1.1.0 h1:6UDDhOpT06csFE2vkcanXsIJmebMc9o+6Vzhvi4i0wY=
github.com/CannibalVox/cgoparam v1.1.0/go.mod h1:9LDFLuHVgE+IIBDd1QFN3dPqmGQN9bS6H+NPizMv2fA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/go-gl/glfw v0.0.0-20221017161538-93cebf72946b h1:2hdUMUOJuLQkhaPAwoyOeSzoaBydYEkXkBEuqDuDBfg=
github.com/go-gl/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:wyvWpaEu9B/VQiV1jsPs7Mha9I7yto/HqIBw197ZAzk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/ojrac/opensimplex-go v1.0.2 h1:l4vs0D+JCakcu5OV0kJ99oEaWJfggSc9jiLpxaWvSzs=
github.com/ojrac/opensimplex-go v1.0.2/go.mod h1:NwbXFFbXcdGgIFdiA7/REME+7n/lOf1TuEbLiZYOWnM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/vkngwrapper/core/v2 v2.2.1 h1:8xw2tuIXAeyNQj4mnDA7BHO6T6f7ba08UbJZB7UM6xg=
github.com/vkngwrapper/core/v2 v2.2.1/go.mod h1:EWABLJZGHa8nyeO4Bh9eR/V862HAz+Fvk5DitkOvYF4=
github.com/vkngwrapper/extensions/v2 v2.2.0 h1:2ZP+Nom2EbefqgR2EPherJRS836wSWPoXeOLvV7aUuY=
github.com/vkngwrapper/extensions/v2 v2.2.0/go.mod h1:55exjYwTUyQVNS/zhrC/Or/c2CA4Q9Cj/88Tu9EqlJ0=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

8
library/zos/error.go Normal file
View File

@ -0,0 +1,8 @@
package zos
import "errors"
var (
ErrBufferFull = errors.New("read: buffer full")
ErrBufferEOF = errors.New("read: buffer EOF")
)

77
library/zos/line.go Normal file
View File

@ -0,0 +1,77 @@
package zos
import "strconv"
type FByteView struct {
Line []byte
sign byte
err error
}
func NewByteView(line []byte) *FByteView {
return &FByteView{
Line: line,
sign: ' ',
err: nil,
}
}
func (v *FByteView) Err() error {
return v.err
}
func (v *FByteView) SetSign(sign byte) {
v.sign = sign
}
func (v *FByteView) GetToken() []byte {
if v.sign == ' ' {
return v.ReadTokenBySpace()
}
return v.ReadTokenByChar(v.sign)
}
func (v *FByteView) GetInt() int32 {
if v.err != nil {
return 0
}
token := string(v.GetToken())
num, err := strconv.ParseInt(token, 10, 32)
v.err = err
return int32(num)
}
func (v *FByteView) GetFloat() float32 {
if v.err != nil {
return 0
}
token := string(v.GetToken())
num, err := strconv.ParseFloat(token, 32)
v.err = err
return float32(num)
}
func (v *FByteView) SkipWhitespace() {
r, w := 0, len(v.Line)
for r < w && (v.Line[r] == ' ' || v.Line[r] == '\t') {
r++
}
if r > 0 {
v.Line = v.Line[r:w]
}
}
func (v *FByteView) ReadTokenBySpace() []byte {
v.SkipWhitespace()
r, w := 0, len(v.Line)
for r < w && (v.Line[r] != ' ' && v.Line[r] != '\t') {
r++
}
token := v.Line[0:r]
v.Line = v.Line[r:w]
return token
}
func (v *FByteView) ReadTokenByChar(char byte) []byte {
v.SkipWhitespace()
r, w := 0, len(v.Line)
for r < w && (v.Line[r] != char) {
r++
}
token := v.Line[0:r]
v.Line = v.Line[r:w]
return token
}

127
library/zos/reader.go Normal file
View File

@ -0,0 +1,127 @@
package zos
import (
"bytes"
"io"
"os"
)
func min(a int, b int) int {
if a < b {
return a
}
return b
}
type FReader struct {
io io.Reader
buf []byte
r, w int
err error
}
func NewReader(io io.Reader) *FReader {
return &FReader{
io: io,
buf: make([]byte, 4096),
r: 0,
w: 0,
}
}
func (b *FReader) Read(data []byte) (int, error) {
n, rn := len(data), 0
for rn < n {
d := min(b.w-b.r, n-rn)
if d > 0 {
copy(data[rn:rn+d], b.buf[b.r:b.r+d])
b.r += d
rn += d
if rn >= n {
break
}
}
err := b.fill()
if err != nil {
return rn, err
}
}
return rn, nil
}
func (b *FReader) ReadLine() ([]byte, error) {
line, err := b.ReadSlice('\n')
i := len(line) - 1
if i >= 0 && line[i] == '\r' {
return line[:i], err
}
return line, err
}
func (b *FReader) ReadLineFast() ([]byte, error) {
line, err := b.ReadSliceFast('\n')
i := len(line) - 1
if i >= 0 && line[i] == '\r' {
return line[:i], err
}
return line, err
}
func (b *FReader) ReadSlice(delim byte) ([]byte, error) {
var lines []byte
for {
line, err := b.ReadSliceFast(delim)
if err == nil || err != ErrBufferFull {
return line, err
}
lines = append(lines, line...)
}
return lines, nil
}
func (b *FReader) ReadSliceFast(delim byte) ([]byte, error) {
for {
// Search buffer.
if i := bytes.IndexByte(b.buf[b.r+0:b.w], delim); i >= 0 {
i += 1
b.r += i
return b.buf[b.r-i : b.r-1], nil
}
err := b.fill()
if err != nil {
return b.buf[b.r:], err
}
}
}
func (b *FReader) fill() error {
d := b.w - b.r
if d >= len(b.buf) {
b.r = 0
b.w = 0
return ErrBufferFull
}
// Slide existing data to beginning.
if b.r > 0 {
copy(b.buf, b.buf[b.r:b.w])
b.w -= b.r
b.r = 0
}
// Read new data: try a limited number of times.
n, err := b.io.Read(b.buf[b.w:])
b.w += n
return err
}
func (b *FReader) Clear() {
b.r = 0
b.w = 0
}
func (b *FReader) Seek(pos, offset int) {
r := b.r + offset
if r >= 0 && r <= b.w {
b.r = r
return
}
f, ok := b.io.(*os.File)
if ok {
f.Seek(int64(pos), 0)
b.r = 0
b.w = 0
}
}

View File

@ -0,0 +1,10 @@
package byte4
// T is a 4-component vector of uint8 (bytes)
type T struct {
X, Y, Z, W byte
}
func New(x, y, z, w byte) T {
return T{x, y, z, w}
}

View File

@ -0,0 +1,32 @@
package ivec2
var Zero = T{}
var One = T{X: 1, Y: 1}
var UnitX = T{X: 1}
var UnitY = T{Y: 1}
type T struct {
X int
Y int
}
func New(x, y int) T {
return T{
X: x,
Y: y,
}
}
func (v T) Add(v2 T) T {
return T{
X: v.X + v2.X,
Y: v.Y + v2.Y,
}
}
func (v T) Sub(v2 T) T {
return T{
X: v.X - v2.X,
Y: v.Y - v2.Y,
}
}

317
plugin/math/mat4/mat4.go Normal file
View File

@ -0,0 +1,317 @@
// Based on code from github.com/go-gl/mathgl:
// Copyright 2014 The go-gl Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mat4
import (
"bytes"
"fmt"
"text/tabwriter"
"golang.org/x/image/math/f32"
"zworld/plugin/math"
"zworld/plugin/math/vec3"
"zworld/plugin/math/vec4"
)
// T holds a 4x4 float32 matrix
type T f32.Mat4
// Add performs an element-wise addition of two matrices, this is
// equivalent to iterating over every element of m and adding the corresponding value of m2.
func (m *T) Add(m2 *T) T {
return T{
m[0] + m2[0], m[1] + m2[1], m[2] + m2[2], m[3] + m2[3],
m[4] + m2[4], m[5] + m2[5], m[6] + m2[6], m[7] + m2[7],
m[8] + m2[8], m[9] + m2[9], m[10] + m2[10], m[11] + m2[11],
m[12] + m2[12], m[13] + m2[13], m[14] + m2[14], m[15] + m2[15],
}
}
// Sub performs an element-wise subtraction of two matrices, this is
// equivalent to iterating over every element of m and subtracting the corresponding value of m2.
func (m *T) Sub(m2 *T) T {
return T{
m[0] - m2[0], m[1] - m2[1], m[2] - m2[2], m[3] - m2[3],
m[4] - m2[4], m[5] - m2[5], m[6] - m2[6], m[7] - m2[7],
m[8] - m2[8], m[9] - m2[9], m[10] - m2[10], m[11] - m2[11],
m[12] - m2[12], m[13] - m2[13], m[14] - m2[14], m[15] - m2[15],
}
}
// Scale performs a scalar multiplcation of the matrix. This is equivalent to iterating
// over every element of the matrix and multiply it by c.
func (m T) Scale(c float32) T {
return T{
m[0] * c, m[1] * c, m[2] * c, m[3] * c,
m[4] * c, m[5] * c, m[6] * c, m[7] * c,
m[8] * c, m[9] * c, m[10] * c, m[11] * c,
m[12] * c, m[13] * c, m[14] * c, m[15] * c,
}
}
// VMul multiplies a vec4 with the matrix
func (m *T) VMul(v vec4.T) vec4.T {
return vec4.T{
X: m[0]*v.X + m[4]*v.Y + m[8]*v.Z + m[12]*v.W,
Y: m[1]*v.X + m[5]*v.Y + m[9]*v.Z + m[13]*v.W,
Z: m[2]*v.X + m[6]*v.Y + m[10]*v.Z + m[14]*v.W,
W: m[3]*v.X + m[7]*v.Y + m[11]*v.Z + m[15]*v.W,
}
}
// TransformPoint transforms a point to world space
func (m *T) TransformPoint(v vec3.T) vec3.T {
p := vec4.Extend(v, 1)
vt := m.VMul(p)
return vt.XYZ().Scaled(1 / vt.W)
}
// TransformDir transforms a direction vector to world space
func (m *T) TransformDir(v vec3.T) vec3.T {
p := vec4.Extend(v, 0)
vt := m.VMul(p)
return vt.XYZ()
}
// Mul performs a "matrix product" between this matrix and another of the same dimension
func (a *T) Mul(b *T) T {
return T{
a[0]*b[0] + a[4]*b[1] + a[8]*b[2] + a[12]*b[3],
a[1]*b[0] + a[5]*b[1] + a[9]*b[2] + a[13]*b[3],
a[2]*b[0] + a[6]*b[1] + a[10]*b[2] + a[14]*b[3],
a[3]*b[0] + a[7]*b[1] + a[11]*b[2] + a[15]*b[3],
a[0]*b[4] + a[4]*b[5] + a[8]*b[6] + a[12]*b[7],
a[1]*b[4] + a[5]*b[5] + a[9]*b[6] + a[13]*b[7],
a[2]*b[4] + a[6]*b[5] + a[10]*b[6] + a[14]*b[7],
a[3]*b[4] + a[7]*b[5] + a[11]*b[6] + a[15]*b[7],
a[0]*b[8] + a[4]*b[9] + a[8]*b[10] + a[12]*b[11],
a[1]*b[8] + a[5]*b[9] + a[9]*b[10] + a[13]*b[11],
a[2]*b[8] + a[6]*b[9] + a[10]*b[10] + a[14]*b[11],
a[3]*b[8] + a[7]*b[9] + a[11]*b[10] + a[15]*b[11],
a[0]*b[12] + a[4]*b[13] + a[8]*b[14] + a[12]*b[15],
a[1]*b[12] + a[5]*b[13] + a[9]*b[14] + a[13]*b[15],
a[2]*b[12] + a[6]*b[13] + a[10]*b[14] + a[14]*b[15],
a[3]*b[12] + a[7]*b[13] + a[11]*b[14] + a[15]*b[15],
}
}
// Transpose produces the transpose of this matrix. For any MxN matrix
// the transpose is an NxM matrix with the rows swapped with the columns. For instance
// the transpose of the Mat3x2 is a Mat2x3 like so:
//
// [[a b]] [[a c e]]
// [[c d]] = [[b d f]]
// [[e f]]
func (m *T) Transpose() T {
return T{
m[0], m[4], m[8], m[12],
m[1], m[5], m[9], m[13],
m[2], m[6], m[10], m[14],
m[3], m[7], m[11], m[15],
}
}
// Det returns the determinant of a matrix. It is a measure of a square matrix's
// singularity and invertability, among other things. In this library, the
// determinant is hard coded based on pre-computed cofactor expansion, and uses
// no loops. Of course, the addition and multiplication must still be done.
func (m *T) Det() float32 {
return m[0]*m[5]*m[10]*m[15] - m[0]*m[5]*m[11]*m[14] - m[0]*m[6]*m[9]*m[15] + m[0]*m[6]*m[11]*m[13] +
m[0]*m[7]*m[9]*m[14] - m[0]*m[7]*m[10]*m[13] - m[1]*m[4]*m[10]*m[15] + m[1]*m[4]*m[11]*m[14] +
m[1]*m[6]*m[8]*m[15] - m[1]*m[6]*m[11]*m[12] - m[1]*m[7]*m[8]*m[14] + m[1]*m[7]*m[10]*m[12] +
m[2]*m[4]*m[9]*m[15] - m[2]*m[4]*m[11]*m[13] - m[2]*m[5]*m[8]*m[15] + m[2]*m[5]*m[11]*m[12] +
m[2]*m[7]*m[8]*m[13] - m[2]*m[7]*m[9]*m[12] - m[3]*m[4]*m[9]*m[14] + m[3]*m[4]*m[10]*m[13] +
m[3]*m[5]*m[8]*m[14] - m[3]*m[5]*m[10]*m[12] - m[3]*m[6]*m[8]*m[13] + m[3]*m[6]*m[9]*m[12]
}
// Invert computes the inverse of a square matrix. An inverse is a square matrix such that when multiplied by the
// original, yields the identity.
//
// M_inv * M = M * M_inv = I
//
// In this library, the math is precomputed, and uses no loops, though the multiplications, additions, determinant calculation, and scaling
// are still done. This can still be (relatively) expensive for a 4x4.
//
// This function checks the determinant to see if the matrix is invertible.
// If the determinant is 0.0, this function returns the zero matrix. However, due to floating point errors, it is
// entirely plausible to get a false positive or negative.
// In the future, an alternate function may be written which takes in a pre-computed determinant.
func (m *T) Invert() T {
det := m.Det()
if math.Equal(det, float32(0.0)) {
return T{}
}
retMat := T{
-m[7]*m[10]*m[13] + m[6]*m[11]*m[13] + m[7]*m[9]*m[14] - m[5]*m[11]*m[14] - m[6]*m[9]*m[15] + m[5]*m[10]*m[15],
m[3]*m[10]*m[13] - m[2]*m[11]*m[13] - m[3]*m[9]*m[14] + m[1]*m[11]*m[14] + m[2]*m[9]*m[15] - m[1]*m[10]*m[15],
-m[3]*m[6]*m[13] + m[2]*m[7]*m[13] + m[3]*m[5]*m[14] - m[1]*m[7]*m[14] - m[2]*m[5]*m[15] + m[1]*m[6]*m[15],
m[3]*m[6]*m[9] - m[2]*m[7]*m[9] - m[3]*m[5]*m[10] + m[1]*m[7]*m[10] + m[2]*m[5]*m[11] - m[1]*m[6]*m[11],
m[7]*m[10]*m[12] - m[6]*m[11]*m[12] - m[7]*m[8]*m[14] + m[4]*m[11]*m[14] + m[6]*m[8]*m[15] - m[4]*m[10]*m[15],
-m[3]*m[10]*m[12] + m[2]*m[11]*m[12] + m[3]*m[8]*m[14] - m[0]*m[11]*m[14] - m[2]*m[8]*m[15] + m[0]*m[10]*m[15],
m[3]*m[6]*m[12] - m[2]*m[7]*m[12] - m[3]*m[4]*m[14] + m[0]*m[7]*m[14] + m[2]*m[4]*m[15] - m[0]*m[6]*m[15],
-m[3]*m[6]*m[8] + m[2]*m[7]*m[8] + m[3]*m[4]*m[10] - m[0]*m[7]*m[10] - m[2]*m[4]*m[11] + m[0]*m[6]*m[11],
-m[7]*m[9]*m[12] + m[5]*m[11]*m[12] + m[7]*m[8]*m[13] - m[4]*m[11]*m[13] - m[5]*m[8]*m[15] + m[4]*m[9]*m[15],
m[3]*m[9]*m[12] - m[1]*m[11]*m[12] - m[3]*m[8]*m[13] + m[0]*m[11]*m[13] + m[1]*m[8]*m[15] - m[0]*m[9]*m[15],
-m[3]*m[5]*m[12] + m[1]*m[7]*m[12] + m[3]*m[4]*m[13] - m[0]*m[7]*m[13] - m[1]*m[4]*m[15] + m[0]*m[5]*m[15],
m[3]*m[5]*m[8] - m[1]*m[7]*m[8] - m[3]*m[4]*m[9] + m[0]*m[7]*m[9] + m[1]*m[4]*m[11] - m[0]*m[5]*m[11],
m[6]*m[9]*m[12] - m[5]*m[10]*m[12] - m[6]*m[8]*m[13] + m[4]*m[10]*m[13] + m[5]*m[8]*m[14] - m[4]*m[9]*m[14],
-m[2]*m[9]*m[12] + m[1]*m[10]*m[12] + m[2]*m[8]*m[13] - m[0]*m[10]*m[13] - m[1]*m[8]*m[14] + m[0]*m[9]*m[14],
m[2]*m[5]*m[12] - m[1]*m[6]*m[12] - m[2]*m[4]*m[13] + m[0]*m[6]*m[13] + m[1]*m[4]*m[14] - m[0]*m[5]*m[14],
-m[2]*m[5]*m[8] + m[1]*m[6]*m[8] + m[2]*m[4]*m[9] - m[0]*m[6]*m[9] - m[1]*m[4]*m[10] + m[0]*m[5]*m[10],
}
return retMat.Scale(1 / det)
}
// ApproxEqual performs an element-wise approximate equality test between two matrices,
// as if FloatEqual had been used.
func (m *T) ApproxEqual(m2 *T) bool {
for i := range m {
if !math.Equal(m[i], m2[i]) {
return false
}
}
return true
}
// ApproxEqualThreshold performs an element-wise approximate equality test between two matrices
// with a given epsilon threshold, as if FloatEqualThreshold had been used.
func (m *T) ApproxEqualThreshold(m2 *T, threshold float32) bool {
for i := range m {
if !math.EqualThreshold(m[i], m2[i], threshold) {
return false
}
}
return true
}
// At returns the matrix element at the given row and column.
// This is equivalent to mat[col * numRow + row] where numRow is constant
// (E.G. for a Mat3x2 it's equal to 3)
//
// This method is garbage-in garbage-out. For instance, on a T asking for
// At(5,0) will work just like At(1,1). Or it may panic if it's out of bounds.
func (m *T) At(row, col int) float32 {
return m[col*4+row]
}
// Set sets the corresponding matrix element at the given row and column.
func (m *T) Set(row, col int, value float32) {
m[col*4+row] = value
}
// Index returns the index of the given row and column, to be used with direct
// access. E.G. Index(0,0) = 0.
func (m *T) Index(row, col int) int {
return col*4 + row
}
// Row returns a vector representing the corresponding row (starting at row 0).
// This package makes no distinction between row and column vectors, so it
// will be a normal VecM for a MxN matrix.
func (m *T) Row(row int) vec4.T {
return vec4.T{
X: m[row+0],
Y: m[row+4],
Z: m[row+8],
W: m[row+12],
}
}
// Rows decomposes a matrix into its corresponding row vectors.
// This is equivalent to calling mat.Row for each row.
func (m *T) Rows() (row0, row1, row2, row3 vec4.T) {
return m.Row(0), m.Row(1), m.Row(2), m.Row(3)
}
// Col returns a vector representing the corresponding column (starting at col 0).
// This package makes no distinction between row and column vectors, so it
// will be a normal VecN for a MxN matrix.
func (m *T) Col(col int) vec4.T {
return vec4.T{
X: m[col*4+0],
Y: m[col*4+1],
Z: m[col*4+2],
W: m[col*4+3],
}
}
// Cols decomposes a matrix into its corresponding column vectors.
// This is equivalent to calling mat.Col for each column.
func (m *T) Cols() (col0, col1, col2, col3 vec4.T) {
return m.Col(0), m.Col(1), m.Col(2), m.Col(3)
}
// Trace is a basic operation on a square matrix that simply
// sums up all elements on the main diagonal (meaning all elements such that row==col).
func (m *T) Trace() float32 {
return m[0] + m[5] + m[10] + m[15]
}
// Abs returns the element-wise absolute value of this matrix
func (m *T) Abs() T {
return T{
math.Abs(m[0]), math.Abs(m[1]), math.Abs(m[2]), math.Abs(m[3]),
math.Abs(m[4]), math.Abs(m[5]), math.Abs(m[6]), math.Abs(m[7]),
math.Abs(m[8]), math.Abs(m[9]), math.Abs(m[10]), math.Abs(m[11]),
math.Abs(m[12]), math.Abs(m[13]), math.Abs(m[14]), math.Abs(m[15]),
}
}
// String pretty prints the matrix
func (m T) String() string {
buf := new(bytes.Buffer)
w := tabwriter.NewWriter(buf, 4, 4, 1, ' ', tabwriter.AlignRight)
for i := 0; i < 4; i++ {
r := m.Row(i)
fmt.Fprintf(w, "%f\t", r.X)
fmt.Fprintf(w, "%f\t", r.Y)
fmt.Fprintf(w, "%f\t", r.Z)
fmt.Fprintf(w, "%f\t", r.W)
}
w.Flush()
return buf.String()
}
// Right extracts the right vector from a transformation matrix
func (m *T) Right() vec3.T {
return vec3.T{
X: m[4*0+0],
Y: m[4*1+0],
Z: m[4*2+0],
}
}
// Up extracts the up vector from a transformation matrix
func (m *T) Up() vec3.T {
return vec3.T{
X: m[4*0+1],
Y: m[4*1+1],
Z: m[4*2+1],
}
}
// Forward extracts the forward vector from a transformation matrix
func (m *T) Forward() vec3.T {
return vec3.T{
X: m[4*0+2],
Y: m[4*1+2],
Z: m[4*2+2],
}
}
// Origin extracts origin point of the coordinate system represented by the matrix
func (m *T) Origin() vec3.T {
return vec3.T{
X: m[4*3+0],
Y: m[4*3+1],
Z: m[4*3+2],
}
}

View File

@ -0,0 +1,11 @@
package mat4
// Ident returns a new 4x4 identity matrix
func Ident() T {
return T{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
}
}

View File

@ -0,0 +1,67 @@
package mat4
import (
"zworld/plugin/math"
"zworld/plugin/math/vec3"
)
// Orthographic generates a left-handed orthographic projection matrix.
// Outputs depth values in the range [0, 1]
func Orthographic(left, right, bottom, top, near, far float32) T {
rml, tmb, fmn := (right - left), (top - bottom), (far - near)
return T{
2 / rml, 0, 0, 0,
0, 2 / tmb, 0, 0,
0, 0, 1 / fmn, 0,
(right + left) / rml,
-(top + bottom) / tmb,
-near / fmn,
1,
}
}
// OrthographicRZ generates a left-handed orthographic projection matrix.
// Outputs depth values in the range [1, 0] (reverse Z)
func OrthographicRZ(left, right, bottom, top, near, far float32) T {
rml, tmb, fmn := (right - left), (top - bottom), (near - far)
return T{
2 / rml, 0, 0, 0,
0, 2 / tmb, 0, 0,
0, 0, 1 / fmn, 0,
-(right + left) / rml,
-(top + bottom) / tmb,
near / fmn,
1,
}
}
// Perspective generates a left-handed perspective projection matrix with reversed depth.
// Outputs depth values in the range [0, 1]
func Perspective(fovy, aspect, near, far float32) T {
fovy = math.DegToRad(fovy)
tanHalfFov := math.Tan(fovy) / 2
return T{
1 / (aspect * tanHalfFov), 0, 0, 0,
0, -1 / tanHalfFov, 0, 0,
0, 0, far / (far - near), 1,
0, 0, -(far * near) / (far - near), 0,
}
}
func LookAt(eye, center, up vec3.T) T {
f := center.Sub(eye).Normalized()
r := vec3.Cross(up, f).Normalized()
u := vec3.Cross(f, r)
M := T{
r.X, u.X, f.X, 0,
r.Y, u.Y, f.Y, 0,
r.Z, u.Z, f.Z, 0,
0, 0, 0, 1,
}
et := Translate(eye.Scaled(-1))
return M.Mul(&et)
}

View File

@ -0,0 +1,56 @@
package mat4_test
import (
"testing"
. "zworld/plugin/math/mat4"
"zworld/plugin/math/vec3"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestMat4(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "math/mat4")
}
type TransformTest struct {
Input vec3.T
Output vec3.T
}
func AssertTransforms(t *testing.T, transform T, cases []TransformTest) {
t.Helper()
for _, c := range cases {
point := transform.TransformPoint(c.Input)
if !point.ApproxEqual(c.Output) {
t.Errorf("expected %v was %v", c.Output, point)
}
}
}
func TestOrthographicRZ(t *testing.T) {
proj := OrthographicRZ(0, 10, 0, 10, -1, 1)
AssertTransforms(t, proj, []TransformTest{
{vec3.New(5, 5, 0), vec3.New(0, 0, 0.5)},
{vec3.New(5, 5, 1), vec3.New(0, 0, 0)},
{vec3.New(5, 5, -1), vec3.New(0, 0, 1)},
{vec3.New(0, 0, -1), vec3.New(-1, -1, 1)},
})
}
func TestPerspectiveVK(t *testing.T) {
proj := Perspective(45, 1, 1, 100)
AssertTransforms(t, proj, []TransformTest{
{vec3.New(0, 0, 1), vec3.New(0, 0, 0)},
{vec3.New(0, 0, 100), vec3.New(0, 0, 1)},
})
}
var _ = Describe("LookAt (LH)", func() {
It("correctly projects", func() {
proj := LookAt(vec3.Zero, vec3.UnitZ, vec3.UnitY)
Expect(proj.Forward().ApproxEqual(vec3.UnitZ)).To(BeTrue())
})
})

View File

@ -0,0 +1,20 @@
// Based on code from github.com/go-gl/mathgl:
// Copyright 2014 The go-gl Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mat4
import (
"zworld/plugin/math/vec3"
)
// Translate returns a homogeneous (4x4 for 3D-space) Translation matrix that moves a point by Tx units in the x-direction, Ty units in the y-direction,
// and Tz units in the z-direction
func Translate(translation vec3.T) T {
return T{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, translation.X, translation.Y, translation.Z, 1}
}
// Scale creates a homogeneous 3D scaling matrix
func Scale(scale vec3.T) T {
return T{scale.X, 0, 0, 0, 0, scale.Y, 0, 0, 0, 0, scale.Z, 0, 0, 0, 0, 1}
}

177
plugin/math/math32.go Normal file
View File

@ -0,0 +1,177 @@
package math
import (
"math"
"golang.org/x/exp/constraints"
)
// Various useful constants.
var (
MinNormal = float32(1.1754943508222875e-38) // 1 / 2**(127 - 1)
MinValue = float32(math.SmallestNonzeroFloat32)
MaxValue = float32(math.MaxFloat32)
InfPos = float32(math.Inf(1))
InfNeg = float32(math.Inf(-1))
NaN = float32(math.NaN())
E = float32(math.E)
Pi = float32(math.Pi)
PiOver2 = Pi / 2
PiOver4 = Pi / 4
Sqrt2 = float32(math.Sqrt2)
Epsilon = float32(1e-10)
)
// Abs returns the absolute value of a number
func Abs[T constraints.Float | constraints.Integer](v T) T {
if v < 0 {
return -v
}
return v
}
// Min returns the smaller of two numbers
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
// Max returns the greater of two numbers
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
// Clamp a value between a minimum and a maximum value
func Clamp[T constraints.Ordered](v, min, max T) T {
if v > max {
return max
}
if v < min {
return min
}
return v
}
// Ceil a number to the closest integer
func Ceil(x float32) float32 {
return float32(math.Ceil(float64(x)))
}
// Floor a number to the closest integer
func Floor(x float32) float32 {
return float32(math.Floor(float64(x)))
}
// Mod returns the remainder of a floating point division
func Mod(x, y float32) float32 {
return float32(math.Mod(float64(x), float64(y)))
}
// Sqrt returns the square root of a number
func Sqrt(x float32) float32 {
return float32(math.Sqrt(float64(x)))
}
// Sin computes the sine of x
func Sin(x float32) float32 {
return float32(math.Sin(float64(x)))
}
// Cos computes the cosine of x
func Cos(x float32) float32 {
return float32(math.Cos(float64(x)))
}
// Tan computes the tangent of x
func Tan(x float32) float32 {
return float32(math.Tan(float64(x)))
}
func Sincos(x float32) (float32, float32) {
sin, cos := math.Sincos(float64(x))
return float32(sin), float32(cos)
}
func Acos(x float32) float32 {
return float32(math.Acos(float64(x)))
}
func Asin(x float32) float32 {
return float32(math.Asin(float64(x)))
}
func Atan2(y, x float32) float32 {
return float32(math.Atan2(float64(y), float64(x)))
}
// Sign returns the sign of x (-1 or 1)
func Sign(x float32) float32 {
if x > 0 {
return 1
}
return -1
}
func Copysign(f, sign float32) float32 {
return float32(math.Copysign(float64(f), float64(sign)))
}
// DegToRad converts degrees to radians
func DegToRad(deg float32) float32 {
return Pi * deg / 180.0
}
// RadToDeg converts radians to degrees
func RadToDeg(rad float32) float32 {
return 180.0 * rad / Pi
}
// Equal checks two floats for (approximate) equality
func Equal(a, b float32) bool {
return EqualThreshold(a, b, Epsilon)
}
// EqualThreshold is a utility function to compare floats.
// It's Taken from http://floating-point-gui.de/errors/comparison/
//
// It is slightly altered to not call Abs when not needed.
//
// This differs from FloatEqual in that it lets you pass in your comparison threshold, so that you can adjust the comparison value to your specific needs
func EqualThreshold(a, b, epsilon float32) bool {
if a == b { // Handles the case of inf or shortcuts the loop when no significant error has accumulated
return true
}
diff := Abs(a - b)
if a*b == 0 || diff < MinNormal { // If a or b are 0 or both are extremely close to it
return diff < epsilon*epsilon
}
// Else compare difference
return diff/(Abs(a)+Abs(b)) < epsilon
}
// Lerp performs linear interpolation between a and b
func Lerp(a, b, f float32) float32 {
return a + f*(b-a)
}
func Round(f float32) float32 {
return float32(math.Round(float64(f)))
}
func Snap(f, multiple float32) float32 {
return Ceil(f/multiple) * multiple
}
func Pow(f, x float32) float32 {
return float32(math.Pow(float64(f), float64(x)))
}

28
plugin/math/noise.go Normal file
View File

@ -0,0 +1,28 @@
package math
import (
opensimplex "github.com/ojrac/opensimplex-go"
)
// Noise utility to sample simplex noise
type Noise struct {
opensimplex.Noise
Seed int
Freq float32
}
// NewNoise creates a new noise struct from a seed value and a frequency factor.
func NewNoise(seed int, freq float32) *Noise {
return &Noise{
Noise: opensimplex.New(int64(seed)),
Seed: seed,
Freq: freq,
}
}
// Sample the noise at a certain position
func (n *Noise) Sample(x, y, z int) float32 {
// jeez
fx, fy, fz := float64(float32(x)*n.Freq), float64(float32(y)*n.Freq), float64(float32(z)*n.Freq)
return float32(n.Eval3(fx, fy, fz))
}

553
plugin/math/quat/quat.go Normal file
View File

@ -0,0 +1,553 @@
// Based on code from github.com/go-gl/mathgl:
// Copyright 2014 The go-gl Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package quat
import (
"zworld/plugin/math"
"zworld/plugin/math/mat4"
"zworld/plugin/math/vec3"
)
// RotationOrder is the order in which rotations will be transformed for the
// purposes of AnglesToQuat.
type RotationOrder int
// The RotationOrder constants represent a series of rotations along the given
// axes for the use of AnglesToQuat.
const (
XYX RotationOrder = iota
XYZ
XZX
XZY
YXY
YXZ
YZY
YZX
ZYZ
ZYX
ZXZ
ZXY
)
// T represents a Quaternion, which is an extension of the imaginary numbers;
// there's all sorts of interesting theory behind it. In 3D graphics we mostly
// use it as a cheap way of representing rotation since quaternions are cheaper
// to multiply by, and easier to interpolate than matrices.
//
// A Quaternion has two parts: W, the so-called scalar component, and "V", the
// vector component. The vector component is considered to be the part in 3D
// space, while W (loosely interpreted) is its 4D coordinate.
type T struct {
W float32
V vec3.T
}
// Ident returns the quaternion identity: W=1; V=(0,0,0).
//
// As with all identities, multiplying any quaternion by this will yield the same
// quaternion you started with.
func Ident() T {
return T{1., vec3.New(0, 0, 0)}
}
// Rotate creates an angle from an axis and an angle relative to that axis.
//
// This is cheaper than HomogRotate3D.
func Rotate(angle float32, axis vec3.T) T {
// angle = (float32(math.Pi) * angle) / 180.0
c, s := math.Cos(angle/2), math.Sin(angle/2)
return T{c, axis.Scaled(s)}
}
// X is a convenient alias for q.V[0]
func (q T) X() float32 {
return q.V.X
}
// Y is a convenient alias for q.V[1]
func (q T) Y() float32 {
return q.V.Y
}
// Z is a convenient alias for q.V[2]
func (q T) Z() float32 {
return q.V.X
}
// Add adds two quaternions. It's no more complicated than
// adding their W and V components.
func (q1 T) Add(q2 T) T {
return T{q1.W + q2.W, q1.V.Add(q2.V)}
}
// Sub subtracts two quaternions. It's no more complicated than
// subtracting their W and V components.
func (q1 T) Sub(q2 T) T {
return T{q1.W - q2.W, q1.V.Sub(q2.V)}
}
// Mul multiplies two quaternions. This can be seen as a rotation. Note that
// Multiplication is NOT commutative, meaning q1.Mul(q2) does not necessarily
// equal q2.Mul(q1).
func (q1 T) Mul(q2 T) T {
return T{q1.W*q2.W - vec3.Dot(q1.V, q2.V), vec3.Cross(q1.V, q2.V).Add(q2.V.Scaled(q1.W)).Add(q1.V.Scaled(q2.W))}
}
// Scale every element of the quaternion by some constant factor.
func (q1 T) Scale(c float32) T {
return T{q1.W * c, vec3.New(q1.V.X*c, q1.V.Y*c, q1.V.Z*c)}
}
// Conjugate returns the conjugate of a quaternion. Equivalent to
// Quat{q1.W, q1.V.Mul(-1)}.
func (q1 T) Conjugate() T {
return T{q1.W, q1.V.Scaled(-1)}
}
// Len gives the Length of the quaternion, also known as its Norm. This is the
// same thing as the Len of a Vec4.
func (q1 T) Len() float32 {
return math.Sqrt(q1.W*q1.W + vec3.Dot(q1.V, q1.V))
}
// Norm is an alias for Len() since both are very common terms.
func (q1 T) Norm() float32 {
return q1.Len()
}
// Normalize the quaternion, returning its versor (unit quaternion).
//
// This is the same as normalizing it as a Vec4.
func (q1 T) Normalize() T {
length := q1.Len()
if math.Equal(1, length) {
return q1
}
if length == 0 {
return Ident()
}
if length == math.InfPos {
length = math.MaxValue
}
return T{q1.W * 1 / length, q1.V.Scaled(1 / length)}
}
// Inverse of a quaternion. The inverse is equivalent
// to the conjugate divided by the square of the length.
//
// This method computes the square norm by directly adding the sum
// of the squares of all terms instead of actually squaring q1.Len(),
// both for performance and precision.
func (q1 T) Inverse() T {
return q1.Conjugate().Scale(1 / q1.Dot(q1))
}
// Rotate a vector by the rotation this quaternion represents.
// This will result in a 3D vector. Strictly speaking, this is
// equivalent to q1.v.q* where the "."" is quaternion multiplication and v is interpreted
// as a quaternion with W 0 and V v. In code:
// q1.Mul(Quat{0,v}).Mul(q1.Conjugate()), and
// then retrieving the imaginary (vector) part.
//
// In practice, we hand-compute this in the general case and simplify
// to save a few operations.
func (q1 T) Rotate(v vec3.T) vec3.T {
cross := vec3.Cross(q1.V, v)
// v + 2q_w * (q_v x v) + 2q_v x (q_v x v)
return v.Add(cross.Scaled(2 * q1.W)).Add(vec3.Cross(q1.V.Scaled(2), cross))
}
// Mat4 returns the homogeneous 3D rotation matrix corresponding to the
// quaternion.
func (q1 T) Mat4() mat4.T {
w, x, y, z := q1.W, q1.V.X, q1.V.Y, q1.V.Z
return mat4.T{
1 - 2*y*y - 2*z*z, 2*x*y + 2*w*z, 2*x*z - 2*w*y, 0,
2*x*y - 2*w*z, 1 - 2*x*x - 2*z*z, 2*y*z + 2*w*x, 0,
2*x*z + 2*w*y, 2*y*z - 2*w*x, 1 - 2*x*x - 2*y*y, 0,
0, 0, 0, 1,
}
}
// Dot product between two quaternions, equivalent to if this was a Vec4.
func (q1 T) Dot(q2 T) float32 {
return q1.W*q2.W + vec3.Dot(q1.V, q2.V)
}
// ApproxEqual returns whether the quaternions are approximately equal, as if
// FloatEqual was called on each matching element
func (q1 T) ApproxEqual(q2 T) bool {
return math.Equal(q1.W, q2.W) && q1.V.ApproxEqual(q2.V)
}
// OrientationEqual returns whether the quaternions represents the same orientation
//
// Different values can represent the same orientation (q == -q) because quaternions avoid singularities
// and discontinuities involved with rotation in 3 dimensions by adding extra dimensions
func (q1 T) OrientationEqual(q2 T) bool {
return q1.OrientationEqualThreshold(q2, math.Epsilon)
}
// OrientationEqualThreshold returns whether the quaternions represents the same orientation with a given tolerence
func (q1 T) OrientationEqualThreshold(q2 T, epsilon float32) bool {
return math.Abs(q1.Normalize().Dot(q2.Normalize())) > 1-math.Epsilon
}
// Slerp is *S*pherical *L*inear Int*erp*olation, a method of interpolating
// between two quaternions. This always takes the straightest path on the sphere between
// the two quaternions, and maintains constant velocity.
//
// However, it's expensive and Slerp(q1,q2) is not the same as Slerp(q2,q1)
func Slerp(q1, q2 T, amount float32) T {
q1, q2 = q1.Normalize(), q2.Normalize()
dot := q1.Dot(q2)
// If the inputs are too close for comfort, linearly interpolate and normalize the result.
if dot > 0.9995 {
return Nlerp(q1, q2, amount)
}
// This is here for precision errors, I'm perfectly aware that *technically* the dot is bound [-1,1], but since Acos will freak out if it's not (even if it's just a liiiiitle bit over due to normal error) we need to clamp it
dot = math.Clamp(dot, -1, 1)
theta := math.Acos(dot) * amount
c, s := math.Cos(theta), math.Sin(theta)
rel := q2.Sub(q1.Scale(dot)).Normalize()
return q1.Scale(c).Add(rel.Scale(s))
}
// Lerp is a *L*inear Int*erp*olation between two Quaternions, cheap and simple.
//
// Not excessively useful, but uses can be found.
func Lerp(q1, q2 T, amount float32) T {
return q1.Add(q2.Sub(q1).Scale(amount))
}
// Nlerp is a *Normalized* *L*inear Int*erp*olation between two Quaternions. Cheaper than Slerp
// and usually just as good. This is literally Lerp with Normalize() called on it.
//
// Unlike Slerp, constant velocity isn't maintained, but it's much faster and
// Nlerp(q1,q2) and Nlerp(q2,q1) return the same path. You should probably
// use this more often unless you're suffering from choppiness due to the
// non-constant velocity problem.
func Nlerp(q1, q2 T, amount float32) T {
return Lerp(q1, q2, amount).Normalize()
}
// FromAngles performs a rotation in the specified order. If the order is not
// a valid RotationOrder, this function will panic
//
// The rotation "order" is more of an axis descriptor. For instance XZX would
// tell the function to interpret angle1 as a rotation about the X axis, angle2 about
// the Z axis, and angle3 about the X axis again.
//
// Based off the code for the Matlab function "angle2quat", though this implementation
// only supports 3 single angles as opposed to multiple angles.
func FromAngles(angle1, angle2, angle3 float32, order RotationOrder) T {
var s [3]float32
var c [3]float32
s[0], c[0] = math.Sincos(angle1 / 2)
s[1], c[1] = math.Sincos(angle2 / 2)
s[2], c[2] = math.Sincos(angle3 / 2)
ret := T{}
switch order {
case ZYX:
ret.W = c[0]*c[1]*c[2] + s[0]*s[1]*s[2]
ret.V = vec3.T{
X: c[0]*c[1]*s[2] - s[0]*s[1]*c[2],
Y: c[0]*s[1]*c[2] + s[0]*c[1]*s[2],
Z: s[0]*c[1]*c[2] - c[0]*s[1]*s[2],
}
case ZYZ:
ret.W = c[0]*c[1]*c[2] - s[0]*c[1]*s[2]
ret.V = vec3.T{
X: c[0]*s[1]*s[2] - s[0]*s[1]*c[2],
Y: c[0]*s[1]*c[2] + s[0]*s[1]*s[2],
Z: s[0]*c[1]*c[2] + c[0]*c[1]*s[2],
}
case ZXY:
ret.W = c[0]*c[1]*c[2] - s[0]*s[1]*s[2]
ret.V = vec3.T{
X: c[0]*s[1]*c[2] - s[0]*c[1]*s[2],
Y: c[0]*c[1]*s[2] + s[0]*s[1]*c[2],
Z: c[0]*s[1]*s[2] + s[0]*c[1]*c[2],
}
case ZXZ:
ret.W = c[0]*c[1]*c[2] - s[0]*c[1]*s[2]
ret.V = vec3.T{
X: c[0]*s[1]*c[2] + s[0]*s[1]*s[2],
Y: s[0]*s[1]*c[2] - c[0]*s[1]*s[2],
Z: c[0]*c[1]*s[2] + s[0]*c[1]*c[2],
}
case YXZ:
ret.W = c[0]*c[1]*c[2] + s[0]*s[1]*s[2]
ret.V = vec3.T{
X: c[0]*s[1]*c[2] + s[0]*c[1]*s[2],
Y: s[0]*c[1]*c[2] - c[0]*s[1]*s[2],
Z: c[0]*c[1]*s[2] - s[0]*s[1]*c[2],
}
case YXY:
ret.W = c[0]*c[1]*c[2] - s[0]*c[1]*s[2]
ret.V = vec3.T{
X: c[0]*s[1]*c[2] + s[0]*s[1]*s[2],
Y: s[0]*c[1]*c[2] + c[0]*c[1]*s[2],
Z: c[0]*s[1]*s[2] - s[0]*s[1]*c[2],
}
case YZX:
ret.W = c[0]*c[1]*c[2] - s[0]*s[1]*s[2]
ret.V = vec3.T{
X: c[0]*c[1]*s[2] + s[0]*s[1]*c[2],
Y: c[0]*s[1]*s[2] + s[0]*c[1]*c[2],
Z: c[0]*s[1]*c[2] - s[0]*c[1]*s[2],
}
case YZY:
ret.W = c[0]*c[1]*c[2] - s[0]*c[1]*s[2]
ret.V = vec3.T{
X: s[0]*s[1]*c[2] - c[0]*s[1]*s[2],
Y: c[0]*c[1]*s[2] + s[0]*c[1]*c[2],
Z: c[0]*s[1]*c[2] + s[0]*s[1]*s[2],
}
case XYZ:
ret.W = c[0]*c[1]*c[2] - s[0]*s[1]*s[2]
ret.V = vec3.T{
X: c[0]*s[1]*s[2] + s[0]*c[1]*c[2],
Y: c[0]*s[1]*c[2] - s[0]*c[1]*s[2],
Z: c[0]*c[1]*s[2] + s[0]*s[1]*c[2],
}
case XYX:
ret.W = c[0]*c[1]*c[2] - s[0]*c[1]*s[2]
ret.V = vec3.T{
X: c[0]*c[1]*s[2] + s[0]*c[1]*c[2],
Y: c[0]*s[1]*c[2] + s[0]*s[1]*s[2],
Z: s[0]*s[1]*c[2] - c[0]*s[1]*s[2],
}
case XZY:
ret.W = c[0]*c[1]*c[2] + s[0]*s[1]*s[2]
ret.V = vec3.T{
X: s[0]*c[1]*c[2] - c[0]*s[1]*s[2],
Y: c[0]*c[1]*s[2] - s[0]*s[1]*c[2],
Z: c[0]*s[1]*c[2] + s[0]*c[1]*s[2],
}
case XZX:
ret.W = c[0]*c[1]*c[2] - s[0]*c[1]*s[2]
ret.V = vec3.T{
X: c[0]*c[1]*s[2] + s[0]*c[1]*c[2],
Y: c[0]*s[1]*s[2] - s[0]*s[1]*c[2],
Z: c[0]*s[1]*c[2] + s[0]*s[1]*s[2],
}
default:
panic("Unsupported rotation order")
}
return ret
}
// FromMat4 converts a pure rotation matrix into a quaternion
func FromMat4(m mat4.T) T {
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
if tr := m[0] + m[5] + m[10]; tr > 0 {
s := 0.5 / math.Sqrt(tr+1.0)
return T{
0.25 / s,
vec3.T{
X: (m[6] - m[9]) * s,
Y: (m[8] - m[2]) * s,
Z: (m[1] - m[4]) * s,
},
}
}
if (m[0] > m[5]) && (m[0] > m[10]) {
s := 2.0 * math.Sqrt(1.0+m[0]-m[5]-m[10])
return T{
(m[6] - m[9]) / s,
vec3.T{
X: 0.25 * s,
Y: (m[4] + m[1]) / s,
Z: (m[8] + m[2]) / s,
},
}
}
if m[5] > m[10] {
s := 2.0 * math.Sqrt(1.0+m[5]-m[0]-m[10])
return T{
(m[8] - m[2]) / s,
vec3.T{
X: (m[4] + m[1]) / s,
Y: 0.25 * s,
Z: (m[9] + m[6]) / s,
},
}
}
s := 2.0 * math.Sqrt(1.0+m[10]-m[0]-m[5])
return T{
(m[1] - m[4]) / s,
vec3.T{
X: (m[8] + m[2]) / s,
Y: (m[9] + m[6]) / s,
Z: 0.25 * s,
},
}
}
// LookAtV creates a rotation from an eye vector to a center vector
//
// It assumes the front of the rotated object at Z- and up at Y+
func LookAtV(eye, center, up vec3.T) T {
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/#I_need_an_equivalent_of_gluLookAt__How_do_I_orient_an_object_towards_a_point__
// https://bitbucket.org/sinbad/ogre/src/d2ef494c4a2f5d6e2f0f17d3bfb9fd936d5423bb/OgreMain/src/OgreCamera.cpp?at=default#cl-161
direction := center.Sub(eye).Normalized()
// Find the rotation between the front of the object (that we assume towards Z-,
// but this depends on your model) and the desired direction
rotDir := BetweenVectors(vec3.UnitZN, direction)
// Recompute up so that it's perpendicular to the direction
// You can skip that part if you really want to force up
//right := direction.Cross(up)
//up = right.Cross(direction)
// Because of the 1rst rotation, the up is probably completely screwed up.
// Find the rotation between the "up" of the rotated object, and the desired up
upCur := rotDir.Rotate(vec3.Zero)
rotUp := BetweenVectors(upCur, up)
rotTarget := rotUp.Mul(rotDir) // remember, in reverse order.
return rotTarget.Inverse() // camera rotation should be inversed!
}
// BetweenVectors calculates the rotation between two vectors
func BetweenVectors(start, dest vec3.T) T {
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/#I_need_an_equivalent_of_gluLookAt__How_do_I_orient_an_object_towards_a_point__
// https://github.com/g-truc/glm/blob/0.9.5/glm/gtx/quaternion.inl#L225
// https://bitbucket.org/sinbad/ogre/src/d2ef494c4a2f5d6e2f0f17d3bfb9fd936d5423bb/OgreMain/include/OgreVector3.h?at=default#cl-654
start = start.Normalized()
dest = dest.Normalized()
epsilon := float32(0.001)
cosTheta := vec3.Dot(start, dest)
if cosTheta < -1.0+epsilon {
// special case when vectors in opposite directions:
// there is no "ideal" rotation axis
// So guess one; any will do as long as it's perpendicular to start
axis := vec3.Cross(vec3.UnitX, start)
if vec3.Dot(axis, axis) < epsilon {
// bad luck, they were parallel, try again!
axis = vec3.Cross(vec3.UnitY, start)
}
return Rotate(math.Pi, axis.Normalized())
}
axis := vec3.Cross(start, dest)
s := float32(math.Sqrt(float32(1.0+cosTheta) * 2.0))
return T{
s * 0.5,
axis.Scaled(1.0 / s),
}
}
func (q T) ToAngles(order RotationOrder) vec3.T {
// this function was adapted from a Go port of Three.js math, github.com/tengge1/go-three-math
// Copyright 2017-2020 The ShadowEditor Authors. All rights reserved.
// Use of e source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
te := q.Mat4()
m11, m12, m13 := te[0], te[4], te[8]
m21, m22, m23 := te[1], te[5], te[9]
m31, m32, m33 := te[2], te[6], te[10]
e := vec3.Zero
switch order {
default:
panic("unsupported rotation order")
case XYZ:
e.Y = math.Asin(math.Clamp(m13, -1, 1))
if math.Abs(m13) < 0.9999999 {
e.X = math.Atan2(-m23, m33)
e.Z = math.Atan2(-m12, m11)
} else {
e.X = math.Atan2(m32, m22)
e.Z = 0
}
case YXZ:
e.X = math.Asin(-math.Clamp(m23, -1, 1))
if math.Abs(m23) < 0.9999999 {
e.Y = math.Atan2(m13, m33)
e.Z = math.Atan2(m21, m22)
} else {
e.Y = math.Atan2(-m31, m11)
e.Z = 0
}
case ZXY:
e.X = math.Asin(math.Clamp(m32, -1, 1))
if math.Abs(m32) < 0.9999999 {
e.Y = math.Atan2(-m31, m33)
e.Z = math.Atan2(-m12, m22)
} else {
e.Y = 0
e.Z = math.Atan2(m21, m11)
}
case ZYX:
e.Y = math.Asin(-math.Clamp(m31, -1, 1))
if math.Abs(m31) < 0.9999999 {
e.X = math.Atan2(m32, m33)
e.Z = math.Atan2(m21, m11)
} else {
e.X = 0
e.Z = math.Atan2(-m12, m22)
}
case YZX:
e.Z = math.Asin(math.Clamp(m21, -1, 1))
if math.Abs(m21) < 0.9999999 {
e.X = math.Atan2(-m23, m22)
e.Y = math.Atan2(-m31, m11)
} else {
e.X = 0
e.Y = math.Atan2(m13, m33)
}
case XZY:
e.Z = math.Asin(-math.Clamp(m12, -1, 1))
if math.Abs(m12) < 0.9999999 {
e.X = math.Atan2(m32, m22)
e.Y = math.Atan2(m13, m11)
} else {
e.X = math.Atan2(-m23, m33)
e.Y = 0
}
}
return e
}
func (q T) Euler() vec3.T {
// convert radians to degrees
return q.ToAngles(YXZ).Scaled(180.0 / math.Pi)
}
func Euler(x, y, z float32) T {
return FromAngles(math.DegToRad(y), math.DegToRad(x), math.DegToRad(z), YXZ)
}

View File

@ -0,0 +1,29 @@
package quat_test
import (
. "github.com/johanhenriksson/goworld/test"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"testing"
"zworld/plugin/math/quat"
"zworld/plugin/math/vec3"
)
func TestQuat(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "math/quat")
}
var _ = Describe("quaternion", func() {
Context("euler angles", func() {
It("converts back and forth", func() {
x, y, z := float32(10), float32(20), float32(30)
q := quat.Euler(x, y, z)
r := q.Euler()
GinkgoWriter.Println(x, y, z, r)
Expect(r).To(ApproxVec3(vec3.New(x, y, z)), "wrong rotation")
})
})
})

View File

@ -0,0 +1,28 @@
package random
import (
"math/rand"
"time"
)
func init() {
seed := time.Now().Nanosecond()
Seed(seed)
}
func Seed(seed int) {
rand.Seed(int64(seed))
}
func Range(min, max float32) float32 {
return min + rand.Float32()*(max-min)
}
func Chance(chance float32) bool {
return Range(0, 1) <= chance
}
func Choice[T any](slice []T) T {
idx := rand.Intn(len(slice))
return slice[idx]
}

View File

@ -0,0 +1,107 @@
package shape
import (
"zworld/plugin/math/mat4"
"zworld/plugin/math/vec3"
)
type Plane struct {
Normal vec3.T
Distance float32
}
func (p *Plane) normalize() {
length := p.Normal.LengthSqr() + p.Distance*p.Distance
p.Normal = p.Normal.Scaled(1 / length)
p.Distance /= length
}
func (p *Plane) DistanceToPoint(point vec3.T) float32 {
return vec3.Dot(p.Normal, point) + p.Distance
}
type Frustum struct {
Front, Back, Left, Right, Top, Bottom Plane
}
func (f *Frustum) IntersectsSphere(s *Sphere) bool {
if f.Left.DistanceToPoint(s.Center) <= -s.Radius {
return false
}
if f.Right.DistanceToPoint(s.Center) <= -s.Radius {
return false
}
if f.Top.DistanceToPoint(s.Center) <= -s.Radius {
return false
}
if f.Bottom.DistanceToPoint(s.Center) <= -s.Radius {
return false
}
if f.Front.DistanceToPoint(s.Center) <= -s.Radius {
return false
}
if f.Back.DistanceToPoint(s.Center) <= -s.Radius {
return false
}
return true
}
func FrustumFromMatrix(vp mat4.T) Frustum {
f := Frustum{
Left: Plane{
Normal: vec3.T{
X: vp[0+3] + vp[0+0],
Y: vp[4+3] + vp[4+0],
Z: vp[8+3] + vp[8+0],
},
Distance: vp[12+3] + vp[12+0],
},
Right: Plane{
Normal: vec3.T{
X: vp[0+3] - vp[0+0],
Y: vp[4+3] - vp[4+0],
Z: vp[8+3] - vp[8+0],
},
Distance: vp[12+3] - vp[12+0],
},
Top: Plane{
Normal: vec3.T{
X: vp[0+3] - vp[0+1],
Y: vp[4+3] - vp[4+1],
Z: vp[8+3] - vp[8+1],
},
Distance: vp[12+3] - vp[12+1],
},
Bottom: Plane{
Normal: vec3.T{
X: vp[0+3] + vp[0+1],
Y: vp[4+3] + vp[4+1],
Z: vp[8+3] + vp[8+1],
},
Distance: vp[12+3] + vp[12+1],
},
Back: Plane{
Normal: vec3.T{
X: vp[0+3] + vp[0+2],
Y: vp[4+3] + vp[4+2],
Z: vp[8+3] + vp[8+2],
},
Distance: vp[12+3] + vp[12+2],
},
Front: Plane{
Normal: vec3.T{
X: vp[0+3] - vp[0+2],
Y: vp[4+3] - vp[4+2],
Z: vp[8+3] - vp[8+2],
},
Distance: vp[12+3] - vp[12+2],
},
}
f.Front.normalize()
f.Back.normalize()
f.Top.normalize()
f.Bottom.normalize()
f.Left.normalize()
f.Right.normalize()
return f
}

View File

@ -0,0 +1,15 @@
package shape
import "zworld/plugin/math/vec3"
type Sphere struct {
Center vec3.T
Radius float32
}
func (s *Sphere) IntersectsSphere(other *Sphere) bool {
sepAxis := s.Center.Sub(other.Center)
radiiSum := s.Radius + other.Radius
intersects := sepAxis.LengthSqr() < (radiiSum * radiiSum)
return intersects
}

21
plugin/math/vec2/array.go Normal file
View File

@ -0,0 +1,21 @@
package vec2
import "unsafe"
// Array holds an array of 2-component vectors
type Array []T
// Elements returns the number of elements in the array
func (a Array) Elements() int {
return len(a)
}
// Size return the byte size of an element
func (a Array) Size() int {
return 8
}
// Pointer returns an unsafe pointer to the first element in the array
func (a Array) Pointer() unsafe.Pointer {
return unsafe.Pointer(&a[0])
}

View File

@ -0,0 +1,37 @@
package vec2
import "zworld/plugin/math"
// New returns a vec2 from its components
func New(x, y float32) T {
return T{X: x, Y: y}
}
// NewI returns a vec2 from integer components
func NewI(x, y int) T {
return T{X: float32(x), Y: float32(y)}
}
// Dot returns the dot product of two vectors.
func Dot(a, b T) float32 {
return a.X*b.X + a.Y*b.Y
}
// Distance returns the euclidian distance between two points.
func Distance(a, b T) float32 {
return a.Sub(b).Length()
}
func Min(a, b T) T {
return T{
X: math.Min(a.X, b.X),
Y: math.Min(a.Y, b.Y),
}
}
func Max(a, b T) T {
return T{
X: math.Max(a.X, b.X),
Y: math.Max(a.Y, b.Y),
}
}

134
plugin/math/vec2/vec2.go Normal file
View File

@ -0,0 +1,134 @@
package vec2
import (
"fmt"
"zworld/plugin/math"
)
var (
// Zero is the zero vector
Zero = T{0, 0}
// One is the one vector
One = T{1, 1}
// UnitX is the unit vector in the X direction
UnitX = T{1, 0}
// UnitY is the unit vector in the Y direction
UnitY = T{0, 1}
InfPos = T{math.InfPos, math.InfPos}
InfNeg = T{math.InfNeg, math.InfNeg}
)
// T holds a 2-component vector of 32-bit floats
type T struct {
X, Y float32
}
// Slice converts the vector into a 2-element slice of float32
func (v T) Slice() [2]float32 {
return [2]float32{v.X, v.Y}
}
// Length returns the length of the vector.
// See also LengthSqr and Normalize.
func (v T) Length() float32 {
return math.Sqrt(v.LengthSqr())
}
// LengthSqr returns the squared length of the vector.
// See also Length and Normalize.
func (v T) LengthSqr() float32 {
return v.X*v.X + v.Y*v.Y
}
// Abs sets every component of the vector to its absolute value.
func (v T) Abs() T {
return T{math.Abs(v.X), math.Abs(v.Y)}
}
// Normalize normalizes the vector to unit length.
func (v *T) Normalize() {
sl := v.LengthSqr()
if sl == 0 || sl == 1 {
return
}
s := 1 / math.Sqrt(sl)
v.X *= s
v.Y *= s
}
// Normalized returns a unit length normalized copy of the vector.
func (v T) Normalized() T {
v.Normalize()
return v
}
// Scaled returns a scaled copy of the vector.
func (v T) Scaled(f float32) T {
return T{v.X * f, v.Y * f}
}
// Scale the vector by a constant (in-place)
func (v *T) Scale(f float32) {
v.X *= f
v.Y *= f
}
// Swap returns a new vector with components swapped.
func (v T) Swap() T {
return T{v.Y, v.X}
}
// Invert components in place
func (v *T) Invert() {
v.X = -v.X
v.Y = -v.Y
}
// Inverted returns a new vector with inverted components
func (v T) Inverted() T {
return T{-v.X, -v.Y}
}
// Add each element of the vector with the corresponding element of another vector
func (v T) Add(v2 T) T {
return T{v.X + v2.X, v.Y + v2.Y}
}
// Sub subtracts each element of the vector with the corresponding element of another vector
func (v T) Sub(v2 T) T {
return T{v.X - v2.X, v.Y - v2.Y}
}
// Mul multiplies each element of the vector with the corresponding element of another vector
func (v T) Mul(v2 T) T {
return T{v.X * v2.X, v.Y * v2.Y}
}
// Div divides each element of the vector with the corresponding element of another vector
func (v T) Div(v2 T) T {
return T{v.X / v2.X, v.Y / v2.Y}
}
func (v T) ApproxEqual(v2 T) bool {
epsilon := float32(0.0001)
return Distance(v, v2) < epsilon
}
func (v T) String() string {
return fmt.Sprintf("%.3f,%.3f", v.X, v.Y)
}
// Floor each components of the vector
func (v T) Floor() T {
return T{math.Floor(v.X), math.Floor(v.Y)}
}
// Ceil each component of the vector
func (v T) Ceil() T {
return T{math.Ceil(v.X), math.Ceil(v.Y)}
}

21
plugin/math/vec3/array.go Normal file
View File

@ -0,0 +1,21 @@
package vec3
import "unsafe"
// Array holds an array of 3-component vectors
type Array []T
// Elements returns the number of elements in the array
func (a Array) Elements() int {
return len(a)
}
// Size return the byte size of an element
func (a Array) Size() int {
return 12
}
// Pointer returns an unsafe pointer to the first element in the array
func (a Array) Pointer() unsafe.Pointer {
return unsafe.Pointer(&a[0])
}

View File

@ -0,0 +1,85 @@
package vec3
import (
"zworld/plugin/math"
"zworld/plugin/math/random"
"zworld/plugin/math/vec2"
)
// New returns a Vec3 from its components
func New(x, y, z float32) T {
return T{x, y, z}
}
func New1(v float32) T {
return T{v, v, v}
}
// NewI returns a Vec3 from integer components
func NewI(x, y, z int) T {
return T{float32(x), float32(y), float32(z)}
}
func NewI1(v int) T {
return T{float32(v), float32(v), float32(v)}
}
func FromSlice(v []float32) T {
if len(v) < 3 {
panic("slice must have at least 3 components")
}
return T{v[0], v[1], v[2]}
}
// Extend a vec2 to a vec3 by adding a Z component
func Extend(v vec2.T, z float32) T {
return T{v.X, v.Y, z}
}
// Dot returns the dot product of two vectors.
func Dot(a, b T) float32 {
return a.X*b.X + a.Y*b.Y + a.Z*b.Z
}
// Cross returns the cross product of two vectors.
func Cross(a, b T) T {
return T{
a.Y*b.Z - a.Z*b.Y,
a.Z*b.X - a.X*b.Z,
a.X*b.Y - a.Y*b.X,
}
}
// Distance returns the euclidian distance between two points.
func Distance(a, b T) float32 {
return a.Sub(b).Length()
}
func Mid(a, b T) T {
return a.Add(b).Scaled(0.5)
}
// Random vector, not normalized.
func Random(min, max T) T {
return T{
random.Range(min.X, max.X),
random.Range(min.Y, max.Y),
random.Range(min.Z, max.Z),
}
}
func Min(a, b T) T {
return T{
X: math.Min(a.X, b.X),
Y: math.Min(a.Y, b.Y),
Z: math.Min(a.Z, b.Z),
}
}
func Max(a, b T) T {
return T{
X: math.Max(a.X, b.X),
Y: math.Max(a.Y, b.Y),
Z: math.Max(a.Z, b.Z),
}
}

205
plugin/math/vec3/vec3.go Normal file
View File

@ -0,0 +1,205 @@
package vec3
import (
"fmt"
"zworld/plugin/math"
"zworld/plugin/math/vec2"
)
var (
// Zero is the zero vector
Zero = T{0, 0, 0}
// One is the unit vector
One = T{1, 1, 1}
// UnitX is the unit vector in the X direction (right)
UnitX = T{1, 0, 0}
Right = T{1, 0, 0}
// UnitXN is the unit vector in the negative X direction (left)
UnitXN = T{-1, 0, 0}
Left = T{-1, 0, 0}
// UnitY is the unit vector in the Y direction (up)
UnitY = T{0, 1, 0}
Up = T{0, 1, 0}
// UnitYN is the unit vector in the negative Y direction (down)
UnitYN = T{0, -1, 0}
Down = T{0, -1, 0}
// UnitZ is the unit vector in the Z direction (forward)
UnitZ = T{0, 0, 1}
Forward = T{0, 0, 1}
// UnitZN is the unit vector in the negative Z direction (backward)
UnitZN = T{0, 0, -1}
Backward = T{0, 0, 1}
InfPos = T{math.InfPos, math.InfPos, math.InfPos}
InfNeg = T{math.InfNeg, math.InfNeg, math.InfNeg}
)
// T holds a 3-component vector of 32-bit floats
type T struct {
X, Y, Z float32
}
// Slice converts the vector into a 3-element slice of float32
func (v T) Slice() [3]float32 {
return [3]float32{v.X, v.Y, v.Z}
}
// Length returns the length of the vector.
// See also LengthSqr and Normalize.
func (v T) Length() float32 {
return math.Sqrt(v.LengthSqr())
}
// LengthSqr returns the squared length of the vector.
// See also Length and Normalize.
func (v T) LengthSqr() float32 {
return v.X*v.X + v.Y*v.Y + v.Z*v.Z
}
// Abs returns a copy containing the absolute values of the vector components.
func (v T) Abs() T {
return T{math.Abs(v.X), math.Abs(v.Y), math.Abs(v.Z)}
}
// Normalize normalizes the vector to unit length.
func (v *T) Normalize() {
sl := v.LengthSqr()
if sl == 0 || sl == 1 {
return
}
s := 1 / math.Sqrt(sl)
v.X *= s
v.Y *= s
v.Z *= s
}
// Normalized returns a unit length normalized copy of the vector.
func (v T) Normalized() T {
v.Normalize()
return v
}
// Scale the vector by a constant (in-place)
func (v *T) Scale(f float32) {
v.X *= f
v.Y *= f
v.Z *= f
}
// Scaled returns a scaled vector
func (v T) Scaled(f float32) T {
return T{v.X * f, v.Y * f, v.Z * f}
}
// ScaleI returns a vector scaled by an integer factor
func (v T) ScaleI(i int) T {
return v.Scaled(float32(i))
}
// Invert the vector components
func (v *T) Invert() {
v.X = -v.X
v.Y = -v.Y
v.Z = -v.Z
}
// Inverted returns an inverted vector
func (v *T) Inverted() T {
i := *v
i.Invert()
return i
}
// Floor each components of the vector
func (v T) Floor() T {
return T{math.Floor(v.X), math.Floor(v.Y), math.Floor(v.Z)}
}
// Ceil each component of the vector
func (v T) Ceil() T {
return T{math.Ceil(v.X), math.Ceil(v.Y), math.Ceil(v.Z)}
}
// Round each component of the vector
func (v T) Round() T {
return T{math.Round(v.X), math.Round(v.Y), math.Round(v.Z)}
}
// Add each element of the vector with the corresponding element of another vector
func (v T) Add(v2 T) T {
return T{
v.X + v2.X,
v.Y + v2.Y,
v.Z + v2.Z,
}
}
// Sub subtracts each element of the vector with the corresponding element of another vector
func (v T) Sub(v2 T) T {
return T{
v.X - v2.X,
v.Y - v2.Y,
v.Z - v2.Z,
}
}
// Mul multiplies each element of the vector with the corresponding element of another vector
func (v T) Mul(v2 T) T {
return T{
v.X * v2.X,
v.Y * v2.Y,
v.Z * v2.Z,
}
}
// XY returns a 2-component vector with the X, Y components of this vector
func (v T) XY() vec2.T {
return vec2.T{X: v.X, Y: v.Y}
}
// XZ returns a 2-component vector with the X, Z components of this vector
func (v T) XZ() vec2.T {
return vec2.T{X: v.X, Y: v.Z}
}
// YZ returns a 2-component vector with the Y, Z components of this vector
func (v T) YZ() vec2.T {
return vec2.T{X: v.Y, Y: v.Z}
}
// Div divides each element of the vector with the corresponding element of another vector
func (v T) Div(v2 T) T {
return T{v.X / v2.X, v.Y / v2.Y, v.Z / v2.Z}
}
// WithX returns a new vector with the X component set to a given value
func (v T) WithX(x float32) T {
return T{x, v.Y, v.Z}
}
// WithY returns a new vector with the Y component set to a given value
func (v T) WithY(y float32) T {
return T{v.X, y, v.Z}
}
// WithZ returns a new vector with the Z component set to a given value
func (v T) WithZ(z float32) T {
return T{v.X, v.Y, z}
}
func (v T) ApproxEqual(v2 T) bool {
epsilon := float32(0.0001)
return Distance(v, v2) < epsilon
}
func (v T) String() string {
return fmt.Sprintf("%.3f,%.3f,%.3f", v.X, v.Y, v.Z)
}

21
plugin/math/vec4/array.go Normal file
View File

@ -0,0 +1,21 @@
package vec4
import "unsafe"
// Array holds an array of 4-component vectors
type Array []T
// Elements returns the number of elements in the array
func (a Array) Elements() int {
return len(a)
}
// Size return the byte size of an element
func (a Array) Size() int {
return 16
}
// Pointer returns an unsafe pointer to the first element in the array
func (a Array) Pointer() unsafe.Pointer {
return unsafe.Pointer(&a[0])
}

View File

@ -0,0 +1,56 @@
package vec4
import (
"zworld/plugin/math"
"zworld/plugin/math/random"
"zworld/plugin/math/vec2"
"zworld/plugin/math/vec3"
)
// New returns a new vec4 from its components
func New(x, y, z, w float32) T {
return T{x, y, z, w}
}
// Extend a vec3 to a vec4 by adding a W component
func Extend(v vec3.T, w float32) T {
return T{v.X, v.Y, v.Z, w}
}
// Extend2 a vec2 to a vec4 by adding the Z and W components
func Extend2(v vec2.T, z, w float32) T {
return T{v.X, v.Y, z, w}
}
// Dot returns the dot product of two vectors.
func Dot(a, b T) float32 {
return a.X*b.X + a.Y*b.Y + a.Z*b.Z + a.W*b.W
}
// Random vector, not normalized.
func Random(min, max T) T {
return T{
random.Range(min.X, max.X),
random.Range(min.Y, max.Y),
random.Range(min.Z, max.Z),
random.Range(min.W, max.W),
}
}
func Min(a, b T) T {
return T{
X: math.Min(a.X, b.X),
Y: math.Min(a.Y, b.Y),
Z: math.Min(a.Z, b.Z),
W: math.Min(a.W, b.W),
}
}
func Max(a, b T) T {
return T{
X: math.Max(a.X, b.X),
Y: math.Max(a.Y, b.Y),
Z: math.Max(a.Z, b.Z),
W: math.Max(a.W, b.W),
}
}

144
plugin/math/vec4/vec4.go Normal file
View File

@ -0,0 +1,144 @@
package vec4
import (
"fmt"
"zworld/plugin/math"
"zworld/plugin/math/vec2"
"zworld/plugin/math/vec3"
)
var (
// Zero is the zero vector
Zero = T{0, 0, 0, 0}
// One is the unit vector
One = T{1, 1, 1, 1}
// UnitX returns a unit vector in the X direction
UnitX = T{1, 0, 0, 0}
// UnitY returns a unit vector in the Y direction
UnitY = T{0, 1, 0, 0}
// UnitZ returns a unit vector in the Z direction
UnitZ = T{0, 0, 1, 0}
// UnitW returns a unit vector in the W direction
UnitW = T{0, 0, 0, 1}
InfPos = T{math.InfPos, math.InfPos, math.InfPos, math.InfPos}
InfNeg = T{math.InfNeg, math.InfNeg, math.InfNeg, math.InfNeg}
)
// T holds a 4-component vector of 32-bit floats
type T struct {
X, Y, Z, W float32
}
// Slice converts the vector into a 4-element slice of float32
func (v T) Slice() [4]float32 {
return [4]float32{v.X, v.Y, v.Z, v.W}
}
// Length returns the length of the vector.
// See also LengthSqr and Normalize.
func (v T) Length() float32 {
return math.Sqrt(v.LengthSqr())
}
// LengthSqr returns the squared length of the vector.
// See also Length and Normalize.
func (v T) LengthSqr() float32 {
return v.X*v.X + v.Y*v.Y + v.Z*v.Z + v.W*v.W
}
// Abs sets every component of the vector to its absolute value.
func (v T) Abs() T {
return T{
math.Abs(v.X),
math.Abs(v.Y),
math.Abs(v.Z),
math.Abs(v.W),
}
}
// Normalize normalizes the vector to unit length.
func (v *T) Normalize() {
sl := v.LengthSqr()
if sl == 0 || sl == 1 {
return
}
s := 1 / math.Sqrt(sl)
v.X *= s
v.Y *= s
v.Z *= s
v.W *= s
}
// Normalized returns a unit length normalized copy of the vector.
func (v T) Normalized() T {
v.Normalize()
return v
}
// Scaled the vector
func (v T) Scaled(f float32) T {
return T{v.X * f, v.Y * f, v.Z * f, v.W * f}
}
// Scale the vector by a constant (in-place)
func (v *T) Scale(f float32) {
v.X *= f
v.Y *= f
v.Z *= f
v.W *= f
}
// Invert the vector components
func (v *T) Invert() {
v.X = -v.X
v.Y = -v.Y
v.Z = -v.Z
v.W = -v.W
}
// Inverted returns an inverted vector
func (v T) Inverted() T {
v.Invert()
return v
}
// Add each element of the vector with the corresponding element of another vector
func (v T) Add(v2 T) T {
return T{v.X + v2.X, v.Y + v2.Y, v.Z + v2.Z, v.W + v2.W}
}
// Sub subtracts each element of the vector with the corresponding element of another vector
func (v T) Sub(v2 T) T {
return T{v.X - v2.X, v.Y - v2.Y, v.Z - v2.Z, v.W - v2.W}
}
// Mul multiplies each element of the vector with the corresponding element of another vector
func (v T) Mul(v2 T) T {
return T{v.X * v2.X, v.Y * v2.Y, v.Z * v2.Z, v.W * v2.W}
}
// XY returns a 2-component vector with the X, Y components
func (v T) XY() vec2.T {
return vec2.T{X: v.X, Y: v.Y}
}
// XYZ returns a 3-component vector with the X, Y, Z components
func (v T) XYZ() vec3.T {
return vec3.T{X: v.X, Y: v.Y, Z: v.Z}
}
// Div divides each element of the vector with the corresponding element of another vector
func (v T) Div(v2 T) T {
return T{v.X / v2.X, v.Y / v2.Y, v.Z / v2.Z, v.W / v2.W}
}
func (v T) String() string {
return fmt.Sprintf("%.3f,%.3f,%.3f,%.3f", v.X, v.Y, v.Z, v.W)
}

111
plugin/objloader/parser.go Normal file
View File

@ -0,0 +1,111 @@
package objloader
import (
"os"
"zworld/library/zos"
"zworld/plugin/math/vec3"
)
type FByteView = zos.FByteView
type FVertex struct {
pos int32
nor int32
tex int32
}
type FMesh struct {
Positions []vec3.T
Normals []vec3.T
TexCoords []vec3.T
Faces [][]FVertex
Indices []uint16
}
type FObjLoader struct {
reader *zos.FReader
mesh *FMesh
}
func (l *FObjLoader) GetTexCoord(bv *FByteView) error {
x := bv.GetFloat()
y := bv.GetFloat()
if err := bv.Err(); err != nil {
return err
}
w := bv.GetFloat()
if bv.Err() != nil {
w = 0
}
v := vec3.T{X: x, Y: y, Z: w}
l.mesh.TexCoords = append(l.mesh.TexCoords, v)
return nil
}
func (l *FObjLoader) GetPosition(bv *FByteView) error {
x := bv.GetFloat()
y := bv.GetFloat()
z := bv.GetFloat()
if err := bv.Err(); err != nil {
return err
}
v := vec3.T{X: x, Y: y, Z: z}
l.mesh.Positions = append(l.mesh.Positions, v)
return nil
}
func (l *FObjLoader) GetNormal(bv *FByteView) error {
x := bv.GetFloat()
y := bv.GetFloat()
z := bv.GetFloat()
if err := bv.Err(); err != nil {
return err
}
v := vec3.T{X: x, Y: y, Z: z}
l.mesh.Normals = append(l.mesh.Normals, v)
return nil
}
func (l *FObjLoader) GetFace(b *FByteView) error {
var faces []FVertex
for i := 0; i < 3; i++ {
line := b.GetToken()
bv := zos.NewByteView(line)
bv.SetSign('/')
v := FVertex{}
v.pos = bv.GetInt()
if err := bv.Err(); err != nil {
return err
}
v.nor = bv.GetInt()
v.tex = bv.GetInt()
faces = append(faces, v)
}
l.mesh.Faces = append(l.mesh.Faces, faces)
return nil
}
func (l *FObjLoader) Parse() error {
for line, err := l.reader.ReadLine(); err == nil; line, err = l.reader.ReadLine() {
bv := zos.NewByteView(line)
bv.SkipWhitespace()
token := string(bv.ReadTokenBySpace())
//zlog.Infof("line {}", string(line))
switch token {
case "v":
err = l.GetPosition(bv)
case "vt":
err = l.GetTexCoord(bv)
case "vn":
err = l.GetNormal(bv)
case "f":
err = l.GetFace(bv)
}
if err != nil {
return err
}
}
return nil
}
func LoadObj(filename string) (err error, mesh *FMesh) {
file, err := os.Open(filename)
if err != nil {
return err, nil
}
l := &FObjLoader{reader: zos.NewReader(file), mesh: &FMesh{}}
err = l.Parse()
return err, l.mesh
}

View File

@ -0,0 +1,10 @@
package test
import (
"testing"
"zworld/engine/render/vulkan/instance"
)
func TestInstance(t *testing.T) {
instance.New("hello world")
}