From 234f4f4d0cf40ada51d1b31ade994a139ecc61b5 Mon Sep 17 00:00:00 2001 From: surdeus Date: Tue, 28 May 2024 13:24:12 +0500 Subject: [PATCH] refactoring. --- animation.go | 99 ---------------- ax/ani.go | 51 +++++++++ ax/define.go | 52 +++++++++ camera.go | 70 +++--------- cmd/test/main.go | 7 +- collider.go | 107 ------------------ context.go | 13 +-- cx/collider.go | 109 ++++++++++++++++++ draw.go | 16 ++- engine.go | 194 +++++++++++--------------------- event.go | 25 +++- go.mod | 2 +- go.sum | 6 +- img.go | 30 +++-- keep.go | 56 +++++++++ line.go | 51 --------- liner.go | 55 --------- mx/circle.go | 32 ++++++ elipse.go => mx/elipse.go | 6 +- edge.go => mx/line.go | 40 +++---- mx/line_expr.go | 61 ++++++++++ mx/liner.go | 81 +++++++++++++ math.go => mx/math.go | 6 +- matrix.go => mx/matrice.go | 5 +- mx/point.go | 10 ++ ray.go => mx/ray.go | 6 +- size.go => mx/size.go | 6 +- mx/triangle.go | 106 +++++++++++++++++ vector.go => mx/vector.go | 42 ++++--- mx/vertice.go | 2 + object.go | 90 ++------------- ox/asprite.go | 56 +++++++++ ox/camera.go | 76 +++++++++++++ circle.go => ox/circle.go | 8 +- ox/feats.go | 31 +++++ ox/object.go | 12 ++ polygon.go => ox/polygon.go | 32 +++--- rect.go => ox/rect.go | 30 ++--- sprite.go => ox/sprite.go | 54 +++++---- ox/text.go | 28 +++++ transform.go => ox/transform.go | 117 ++++++++++--------- ox/tri.go | 20 ++++ point.go | 8 -- shader.go | 3 +- short.go | 1 + text.go | 52 +++------ triangle.go | 144 ------------------------ vertice.go | 25 +++- 48 files changed, 1159 insertions(+), 974 deletions(-) delete mode 100644 animation.go create mode 100644 ax/ani.go create mode 100644 ax/define.go create mode 100644 cx/collider.go create mode 100644 keep.go delete mode 100644 line.go delete mode 100644 liner.go create mode 100644 mx/circle.go rename elipse.go => mx/elipse.go (83%) rename edge.go => mx/line.go (58%) create mode 100644 mx/line_expr.go create mode 100644 mx/liner.go rename math.go => mx/math.go (90%) rename matrix.go => mx/matrice.go (51%) create mode 100644 mx/point.go rename ray.go => mx/ray.go (71%) rename size.go => mx/size.go (83%) create mode 100644 mx/triangle.go rename vector.go => mx/vector.go (72%) create mode 100644 mx/vertice.go create mode 100644 ox/asprite.go create mode 100644 ox/camera.go rename circle.go => ox/circle.go (85%) create mode 100644 ox/feats.go create mode 100644 ox/object.go rename polygon.go => ox/polygon.go (51%) rename rect.go => ox/rect.go (76%) rename sprite.go => ox/sprite.go (55%) create mode 100644 ox/text.go rename transform.go => ox/transform.go (65%) create mode 100644 ox/tri.go delete mode 100644 point.go create mode 100644 short.go delete mode 100644 triangle.go diff --git a/animation.go b/animation.go deleted file mode 100644 index 0be2332..0000000 --- a/animation.go +++ /dev/null @@ -1,99 +0,0 @@ -package gg - -import ( - "github.com/hajimehoshi/ebiten/v2" - //"fmt" -) - -type Animation []*Image - -type AnimationDefine struct{ - Id AnimationId - Indexes []ImageRectIndex -} -// Animation define -func AD(id AnimationId, indexes ...ImageRectIndex) AnimationDefine { - return AnimationDefine{ - Id: id, - Indexes: indexes, - } -} -func (ad AnimationDefine) DefRow(y int, xs ...int) AnimationDefine { - for _, x := range xs { - ad.Indexes = append(ad.Indexes, ImageRectIndex{x, y}) - } - return ad -} - -func (ad AnimationDefine) DefCol(x int, ys ...int) AnimationDefine { - for _, y := range ys { - ad.Indexes = append(ad.Indexes, ImageRectIndex{x, y}) - } - return ad -} - -type AnimationId int -type AnimationSet map[AnimationId] Animation -func AnimationSetFromImage( - img *Image, - w, h, gx, gy int, - defines ...AnimationDefine, -) (AnimationSet, error) { - set := AnimationSet{} - - r := img.Bounds() - fw, fh := r.Dx()/w, r.Dy()/h - - for _, define := range defines { - animation := make(Animation, len(define.Indexes)) - for i := range animation { - idx := define.Indexes[i] - animation[i] = ebiten.NewImageFromImage(img.SubImage( - ImageRect{ - Min: ImagePoint{idx.X*fw+gx, idx.Y*fh+gy}, - Max: ImagePoint{(idx.X+1)*fw-gx, (idx.Y+1)*fh-gy}, - }, - )) - } - set[define.Id] = animation - } - - return set, nil -} - -// The type implements animated sprites. -type AnimatedSprite struct { - Sprite - Animations AnimationSet - AnimationId AnimationId - CurrentFrame int - // This is time between animation frames, not - // engine ones. - TimeBetweenFrames Duration - duration Duration -} - -func (as *AnimatedSprite) Animate(id AnimationId) bool { - if as.AnimationId == id { - return true - } - _, ok := as.Animations[id] - if ok { - as.duration = 0 - as.AnimationId = id - } - return ok -} - -func (as *AnimatedSprite) Draw(c *Context) []EVertex { - as.duration += c.DrawDt() - frames := as.Animations[as.AnimationId] - fullTime := Duration(len(frames)) * as.TimeBetweenFrames - if as.duration > fullTime { - as.duration -= fullTime - } - as.Images[0] = frames[(as.duration/as.TimeBetweenFrames)%Duration(len(frames))] - - return as.Sprite.Draw(c) -} - diff --git a/ax/ani.go b/ax/ani.go new file mode 100644 index 0000000..9eb25d5 --- /dev/null +++ b/ax/ani.go @@ -0,0 +1,51 @@ +package ax + +import ( + "surdeus.su/core/gg" +) + +// Unique identifier for animation +// in the animation set. +type AnimationId int + +// The type describes set of animations +// to switch between. +type AnimationSet map[AnimationId] Animation + +// Make new animation set from an image. +func AnimationSetFromImage( + img *gg.Image, + // Width and height of one frame respectively. + w, h int, + // Gaps for X and Y respectively. + gx, gy int, + defines ...AnimationDefine, +) (AnimationSet, error) { + set := AnimationSet{} + + r := img.Bounds() + fw, fh := r.Dx()/w, r.Dy()/h + + for _, define := range defines { + animation := make(Animation, len(define.Indexes)) + for i := range animation { + idx := define.Indexes[i] + rect := gg.ImageRect{ + Min: gg.ImagePoint{ + idx.X*fw+gx, + idx.Y*fh+gy, + }, + Max: gg.ImagePoint{ + (idx.X+1)*fw-gx, + (idx.Y+1)*fh-gy, + }, + } + subImg := img.SubImage(rect) + animation[i] = gg.NewImageFromImage(subImg) + } + set[define.Id] = animation + } + + return set, nil +} + diff --git a/ax/define.go b/ax/define.go new file mode 100644 index 0000000..6abad44 --- /dev/null +++ b/ax/define.go @@ -0,0 +1,52 @@ +package ax + +import "surdeus.su/core/gg" + +type Animation []*gg.Image + +type RectIndex struct { + X, Y int +} + +// The type is used +// to structurely define +// animations. +type AnimationDefine struct{ + Id AnimationId + Indexes []RectIndex +} + +// Animation define shortcut. +func AD( + id AnimationId, + indexes ...RectIndex, +) AnimationDefine { + return AnimationDefine{ + Id: id, + Indexes: indexes, + } +} + +func (ad AnimationDefine) DefRow( + y int, xs ...int, +) AnimationDefine { + for _, x := range xs { + ad.Indexes = append( + ad.Indexes, + RectIndex{x, y}, + ) + } + return ad +} + +func (ad AnimationDefine) DefCol( + x int, ys ...int, +) AnimationDefine { + for _, y := range ys { + ad.Indexes = append( + ad.Indexes, + RectIndex{x, y}, + ) + } + return ad +} diff --git a/camera.go b/camera.go index f684ff8..7ba21a5 100644 --- a/camera.go +++ b/camera.go @@ -1,59 +1,19 @@ package gg -// Implements the camera component -// for the main window. -type Camera struct { - // The shaders that will be applied to everything - // that the camera shows. - ShaderOptions - Transform - buffered bool - buf Matrix - engine *Engine +import "surdeus.su/core/gg/mx" + +// The type describes what +// a camera object must implement. +type Camera interface { + // Get the matrice to apply + // camera's features. + GetRealMatrice(Context) mx.Matrice + + // The way to convert from real to absolute. + GetAbsMatrice(Context) mx.Matrice + + // The shaders to apply on + // everything on the camera. + GetShaderOptions() *ShaderOptions } -func (e *Engine) NewCamera() *Camera { - ret := &Camera{} - ret.Transform = T() - ret.engine = e - return ret -} - -// Returns the matrix satysfying camera -// position, scale and rotation to apply -// it to the objects to get the real -// transform to display on the screen. -func (c *Camera)RealMatrix() Matrix { - // Bufferization - if c.buffered { - return c.buf - } - g := Matrix{} - position := c.Position().Neg() - around := c.Around() - scale := c.Scale() - rotation := c.Rotation() - - g.Translate(-position.X, -position.Y) - g.Rotate(rotation) - - size := c.engine.AbsWinSize() - g.Translate(around.X * size.X, around.Y * size.Y) - - g.Scale(scale.X, scale.Y) - - c.buf = g - c.buffered = true - - return g -} - -// The matrix to convert things into the -// inside engine representation, -// get the position of cursor inside the world -// basing on its window position. -func (c *Camera) AbsMatrix() Matrix { - m := c.RealMatrix() - m.Invert() - return m -} diff --git a/cmd/test/main.go b/cmd/test/main.go index a83fb4a..061d9ce 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -2,6 +2,7 @@ package main import ( "surdeus.su/core/gg" + "surdeus.su/core/gg/ox" "github.com/hajimehoshi/ebiten/v2/examples/resources/images" "github.com/hajimehoshi/ebiten/v2/examples/resources/fonts" //_ "github.com/silbinarywolf/preferdiscretegpu" @@ -13,11 +14,11 @@ import ( type Context = gg.Context type Grid struct { - gg.Sprite + ox.Sprite Cage, Width gg.Float } -func (grid *Grid) Update(c *Context) { +func (grid *Grid) OnUpdate(c *Context) { //d := c.IsPressed(gg.KeyShift) for _, ev := range c.Events { switch e := ev.(type) { @@ -34,7 +35,7 @@ func (grid *Grid) Update(c *Context) { } } -func (grid *Grid) Draw(c *Context) []gg.EVertex { +func (grid *Grid) OnDraw(c *Context) []gg.EVertex { scale := c.Camera.Scale() pos := c.Camera.Position() grid.Uniforms["Zoom"] = gg.Float(scale.X) diff --git a/collider.go b/collider.go index 4248a10..a28e3fb 100644 --- a/collider.go +++ b/collider.go @@ -1,109 +1,2 @@ package gg -// Implementing the interface lets -// the engine to work faster about -// collisions because it first checks -// if the the bigger collider that -// contains more complicated (accurate) structure -// do collide. -//type ColliderSimplifier interface { - //ColliderSimplify() Triangle -//} - -type CollisionMap map[CollisionType] []Collision - -type CollisionType int -const ( - CollisionNo CollisionType = iota - CollisionStaticPhysics - CollisionTrigger - CollisionLast -) - -// The structure contains collision information. -type Collision struct { - Type CollisionType - What, With Collider - // Points of Self contained in - Points Points - Crosses []LineCross -} - - -// Implementing the interface lets the engine -// to determine if the object collides with anything. -type Collider interface { - // Checks whether the object is collidable - CollisionType() CollisionType - IsCollidable() bool - Verticer - Edger - PointContainer -} -type Collidability struct { - Type CollisionType - Collidable bool -} -func (c Collidability) CollisionType() CollisionType { - return c.Type -} -func (c Collidability) IsCollidable() bool { - return c.Collidable -} -type PointContainer interface { - ContainedPoints(Points) Points -} -type Verticer interface { - Vertices() Vertices -} -type Edger interface { - Edges() Edges -} - -// Implementing the type provides way -// to resolve collisions and other problems. -// The Resolve() is called right after the Update() of Updater. -type Resolver interface { - IsResolvable() bool - Resolve(c *Context) - GetCollisionInterest() []CollisionType - Collider -} -type Resolvability struct { - Resolvable bool - CollisionInterest []CollisionType -} -func (r Resolvability) IsResolvable() bool { - return r.Resolvable -} -func (r Resolvability) GetCollisionInterest() []CollisionType { - return r.CollisionInterest -} - -func Collide(c1, c2 Collider) (Collision, bool) { - vertices := c1.Vertices() - es1, es2 := c1.Edges(), c2.Edges() - crosses, doCross := es1.CrossWithEdges(es2) - pts := c2.ContainedPoints(vertices) - return Collision{ - Type: c2.CollisionType(), - What: c1, - With: c2, - Points: pts, - Crosses: crosses, - }, len(pts) > 0 || doCross -} - -func GetCollisions(c Collider, cs []Collider) []Collision { - ret := []Collision{} - for _, ic := range cs { - if ic == c { - continue - } - col, has := Collide(c, ic) - if has { - ret = append(ret, col) - } - } - return ret -} diff --git a/context.go b/context.go index 63f9c6e..1c71255 100644 --- a/context.go +++ b/context.go @@ -1,19 +1,8 @@ package gg -type contextType int -const ( - startContext contextType = iota - updateContext - resolveContext - eventContext - drawContext - deleteContext -) - type Context struct { - typ contextType Events []any - Collisions []Collision *Engine *Image } + diff --git a/cx/collider.go b/cx/collider.go new file mode 100644 index 0000000..f940d71 --- /dev/null +++ b/cx/collider.go @@ -0,0 +1,109 @@ +package cx + +// Implementing the interface lets +// the engine to work faster about +// collisions because it first checks +// if the the bigger collider that +// contains more complicated (accurate) structure +// do collide. +//type ColliderSimplifier interface { + //ColliderSimplify() Triangle +//} + +type CollisionMap map[CollisionType] []Collision + +type CollisionType int +const ( + CollisionNo CollisionType = iota + CollisionStaticPhysics + CollisionTrigger + CollisionLast +) + +// The structure contains collision information. +type Collision struct { + Type CollisionType + What, With Collider + // Points of Self contained in + Points Points + Crosses []LineCross +} + + +// Implementing the interface lets the engine +// to determine if the object collides with anything. +type Collider interface { + // Checks whether the object is collidable + CollisionType() CollisionType + IsCollidable() bool + Verticer + Edger + PointContainer +} +type Collidability struct { + Type CollisionType + Collidable bool +} +func (c Collidability) CollisionType() CollisionType { + return c.Type +} +func (c Collidability) IsCollidable() bool { + return c.Collidable +} +type PointContainer interface { + ContainedPoints(Points) Points +} +type Verticer interface { + Vertices() Vertices +} +type Edger interface { + Edges() Edges +} + +// Implementing the type provides way +// to resolve collisions and other problems. +// The Resolve() is called right after the Update() of Updater. +type Resolver interface { + IsResolvable() bool + Resolve(c *Context) + GetCollisionInterest() []CollisionType + Collider +} +type Resolvability struct { + Resolvable bool + CollisionInterest []CollisionType +} +func (r Resolvability) IsResolvable() bool { + return r.Resolvable +} +func (r Resolvability) GetCollisionInterest() []CollisionType { + return r.CollisionInterest +} + +func Collide(c1, c2 Collider) (Collision, bool) { + vertices := c1.Vertices() + es1, es2 := c1.Edges(), c2.Edges() + crosses, doCross := es1.CrossWithEdges(es2) + pts := c2.ContainedPoints(vertices) + return Collision{ + Type: c2.CollisionType(), + What: c1, + With: c2, + Points: pts, + Crosses: crosses, + }, len(pts) > 0 || doCross +} + +func GetCollisions(c Collider, cs []Collider) []Collision { + ret := []Collision{} + for _, ic := range cs { + if ic == c { + continue + } + col, has := Collide(c, ic) + if has { + ret = append(ret, col) + } + } + return ret +} diff --git a/draw.go b/draw.go index c8f5d80..c8dac8b 100644 --- a/draw.go +++ b/draw.go @@ -1,5 +1,15 @@ package gg -// Should implement -// some general structure -// for the returned values for the Draw call. +type Drawing struct { + EVertices []EVertice +} + +// The interface describes anything that can be +// drawn. It will be drew corresponding to +// the layers order so the layer must be returned. +type Drawer interface { + Draw(Context) Drawing + GetLayer() Layer + IsVisible() bool +} + diff --git a/engine.go b/engine.go index e4496fa..bbee5b1 100644 --- a/engine.go +++ b/engine.go @@ -3,13 +3,13 @@ package gg import ( "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" - "vultras.su/core/gods/maps" - //"fmt" + "surdeus.su/core/gods/maps" "time" "slices" - "sync" ) +import "surdeus.su/core/gg/mx" + const ( MaxVertices = 1 << 16 ) @@ -45,12 +45,13 @@ type WindowConfig struct { } type Objects struct { - store []Objecter + store []Object } -// The main structure that represents current state of [game] engine. +// The main structure that represents +// current state of [game] engine. type Engine struct { - wcfg *WindowConfig + wcfg WindowConfig // The main holder for objects. // Uses the map structure to quickly @@ -59,7 +60,7 @@ type Engine struct { // The main camera to display in window. // If is set to nil then the engine will panic. - Camera *Camera + Camera Camera drawLastTime time.Time drawdt Duration @@ -71,10 +72,9 @@ type Engine struct { // Temporary stuff keys, prevKeys []Key buttons MouseButtonMap - wheel Vector - cursorPos Vector + wheel mx.Vector + cursorPos mx.Vector outerEvents, handleEvents EventChan - wg sync.WaitGroup //bufs [LayerBufSize]*Image vertices map[Layer] []ebiten.Vertex @@ -114,11 +114,8 @@ func (e *Engine) MouseButtons() []MouseButton { // Returns new empty Engine. func NewEngine( - cfg *WindowConfig, + cfg WindowConfig, ) *Engine { - /*w := Float(cfg.Width) - h := Float(cfg.Height)*/ - ret := &Engine{} ret.wcfg = cfg @@ -131,55 +128,43 @@ func NewEngine( } // Get the real window size in the current context. -func (c *Engine) RealWinSize() Vector { +func (c *Engine) RealWinSize() mx.Vector { var w, h int if c.wcfg.Fullscreen { w, h = ebiten.ScreenSizeInFullscreen() } else { w, h = c.wcfg.Width, c.wcfg.Height } - return V( - Float(w), - Float(h), - ) + return mx.Vector{ + mx.Float(w), + mx.Float(h), + } } -func (c *Engine) AbsWinSize() Vector { - return c.RealWinSize().Div(c.Camera.Scale()) +func (c *Engine) AbsWinSize() mx.Vector { + return c.RealWinSize().Div(c.Camera.GetScale()) } func (e *Engine) EventInput() EventChan { return e.outerEvents } -// Add new object considering what -// interfaces it implements. -func (e *Engine) Spawn(b Objecter) error { +// Add new object to the Engine's view. +func (e *Engine) Spawn(o Object) error { /*if e.Objects.Has(b) { return ObjectExistErr }*/ - b.Start(&Context{Engine: e}) - obj := b.GetObject() - obj.input = make(chan *Context) - go func() { - for c := range obj.input { - switch c.typ{ - case updateContext : - b.Update(c) - case resolveContext : - b.Resolve(c) - } - e.wg.Done() - } - }() - e.Objects.store = append(e.Objects.store, b) + o.OnStart(Context{ + Engine: e, + }) + e.Objects.store = append(e.Objects.store, o) return nil } // Delete object from Engine. -func (e *Engine) Del(b Objecter) error { +func (e *Engine) Delete(b Object) error { /*if !e.Objects.Has(b) { return ObjectNotExistErr } @@ -216,28 +201,28 @@ func (e *Engine) IsButtoned(b MouseButton) bool { return ok } -func (e *Engine) Wheel() Vector { +func (e *Engine) Wheel() mx.Vector { return e.wheel } -func (e *Engine) cursorPosition() Vector { +func (e *Engine) cursorPosition() mx.Vector { x, y := ebiten.CursorPosition() - return V(Float(x), Float(y)) + return mx.Vector{mx.Float(x), mx.Float(y)} } -func (e *Engine) CursorPosition() Vector { +func (e *Engine) CursorPosition() mx.Vector { return e.cursorPos } -func (e *Engine) AbsCursorPosition() Vector { - return e.CursorPosition().Apply(e.Camera.AbsMatrix()) +func (e *Engine) AbsCursorPosition() mx.Vector { + return e.CursorPosition().Apply(e.Camera.GetAbsMatrice()) } func (e *Engine) Runes() []rune { return e.runes } -func (e *engine) UpdateEvents() []any { +func (e *engine) updateEvents() []any { eng := (*Engine)(e) e.prevKeys = e.keys @@ -261,8 +246,8 @@ func (e *engine) UpdateEvents() []any { } x, y := ebiten.Wheel() - e.wheel = V(x, y) - if !(e.wheel.Eq(ZV)) { + e.wheel = mx.Vector{x, y} + if !(e.wheel.Eq(mx.ZV)) { events = append(events, &WheelChange{ Offset: e.wheel, }) @@ -285,14 +270,14 @@ func (e *engine) UpdateEvents() []any { realPos := eng.cursorPosition() if !realPos.Eq(e.cursorPos) { - absM := eng.Camera.AbsMatrix() + absM := eng.Camera.GetAbsMatrice() absPrevPos := e.cursorPos.Apply(absM) absPos := realPos.Apply(absM) events = append(events, &MouseMove{ - Real: realPos.Sub(e.cursorPos), - Abs: absPos.Sub(absPrevPos), + RealDelta: realPos.Sub(e.cursorPos), + AbsDelta: absPos.Sub(absPrevPos), }) e.cursorPos = realPos } @@ -311,78 +296,20 @@ func (e *engine) Update() error { // Maybe should think of the better way, // but for it is simple enough. - events := e.UpdateEvents() - c := &Context{ + events := e.updateEvents() + c := Context{ Engine: eng, - typ: updateContext, Events: events, } for _, object := range e.Objects.store { - e.wg.Add(1) - object.Input() <- c + object.OnUpdate(c) } - e.wg.Wait() - e.Resolve() e.lastTime = time.Now() e.frame++ return nil } -func (e *engine) Resolve() { - eng := (*Engine)(e) - type resHold struct{ - Resolver Resolver - input chan *Context - } - colliders := map[CollisionType] []Collider{} - - resolvers := []resHold{} - for _, object := range e.Objects.store { - if object.IsResolvable() { - interests := object.GetCollisionInterest() - for _, interest := range interests { - _, ok := colliders[interest] - if !ok { - colliders[interest] = []Collider{} - } - } - resolvers = append(resolvers, resHold{ - object, - object.Input(), - }) - } - } - - for _, object := range e.Objects.store { - if object.IsCollidable() { - typ := object.CollisionType() - _, ok := colliders[typ] - if !ok { - continue - } - colliders[typ] = append(colliders[typ], object) - } - } - - for _, resolver := range resolvers { - interests := resolver.Resolver.GetCollisionInterest() - cols := []Collision{} - for _, interest := range interests { - cols = append(cols, GetCollisions(resolver.Resolver, colliders[interest])...) - } - if len(cols) > 0 { - c := &Context{ - typ: resolveContext, - Collisions: cols, - Engine: eng, - } - e.wg.Add(1) - resolver.input <- c - } - } - e.wg.Wait() -} var ( fullPageIndexes = func() [MaxVertices]uint16 { @@ -394,7 +321,7 @@ var ( }() defaultPageImg = func() *Image { img := NewImage(1, 1) - img.Set(0, 0, Rgba(1, 1, 1, 1)) + img.Set(0, 0, RGBA(1, 1, 1, 1)) return img }() defaultTriOptions = &ebiten.DrawTrianglesOptions{} @@ -423,15 +350,20 @@ func (e *engine) Draw(img *ebiten.Image) { // First drawing via the inside function // and then the returned []EVertex. layers := maps.NewSparse[Layer, []Drawer](nil, m) - c := &Context{Engine: eng, typ: drawContext, Image: img} + c := Context{Engine: eng, Image: img} for layer := range layers.Chan() { - vertices := []EVertex{} + + vertices := []EVertice{} for _, drawer := range layer { - vs := drawer.Draw(c) - if vs != nil { - vertices = append(vertices, vs...) + drawing := drawer.Draw(c) + if drawing.EVertices != nil { + vertices = append( + vertices, + drawing.EVertices..., + ) } } + pn := len(vertices) / MaxVertices mod := len(vertices) % MaxVertices for i := 0 ; i 0 { + c := &Context{ + typ: resolveContext, + Collisions: cols, + Engine: eng, + } + e.wg.Add(1) + resolver.input <- c + } + } + e.wg.Wait() +}*/ diff --git a/line.go b/line.go deleted file mode 100644 index 81c3acd..0000000 --- a/line.go +++ /dev/null @@ -1,51 +0,0 @@ -package gg - -//import "fmt" - -// The type represents mathematical equation of line and line itself. -type Line struct { - K, C, X Float - Vertical bool -} - -// Returns the line itself. Made to implement the Liner interface. -func (l Line) Line() Line { - return l -} - -func (l Line) ContainsPoint(p Point) bool { - buf := Line{0, p.Y, 0, false} - pc, ok := l.crossesLine(buf) - if !ok { - return false - } - - //Println("points:", l, p, pc) - return Neq(pc.X, p.X) && Neq(pc.Y, p.Y) -} - -func (l1 Line) crossesLine(l2 Line) (Point, bool) { - var x, y Float - if LinersParallel(l1, l2) { - return Point{}, false - } - - switch { - case l1.Vertical : - x = l1.X - y = l2.K*x + l2.C - //Println("l1-vert:", x, y) - case l2.Vertical : - x = l2.X - y = l1.K*x + l1.C - //Println("l2-vert:", x, y) - default: - x = (l1.C - l2.C) / (l2.K - l1.K) - y = l1.K*x + l1.C - } - - //Println("in:", x, y) - return Point{x, y}, true -} - - diff --git a/liner.go b/liner.go deleted file mode 100644 index 7892002..0000000 --- a/liner.go +++ /dev/null @@ -1,55 +0,0 @@ -package gg - -type Liner interface { - Line() Line -} - -type LinerPointContainer interface { - Liner - PointContainer -} - -// The type represents the cross of 2 Liners. -type LineCross struct { - Pair [2]Line - Point Point -} - -// Check if two LinerPointContainers do cross and return the -// crossing point. -func LinersCross(lp1, lp2 LinerPointContainer) (LineCross, bool) { - l1 := lp1.Line() - l2 := lp2.Line() - - crossPt, doCross := l1.crossesLine(l2) - if len(lp1.ContainedPoints([]Point{crossPt}))==0 || - len(lp2.ContainedPoints([]Point{crossPt}))==0 { - doCross = false - } - - return LineCross{ - Pair: [2]Line{l1, l2}, - Point: crossPt, - }, doCross -} - -// Check whether the liner is parallel to the other liner. -func LinersParallel(first, second Liner) bool { - l1 := first.Line() - l2 := second.Line() - - return l1.K == l2.K || l1.Vertical && l2.Vertical -} - -// Returns angle between liners in radians. -// The value fits the -Pi < Value < Pi condition. -func LinersAngle(first, second Liner) Float { - l1 := first.Line() - l2 := second.Line() - - if l1.K == l2.K { - return 0 - } - - return Atan(l1.K/l2.K) -} diff --git a/mx/circle.go b/mx/circle.go new file mode 100644 index 0000000..20c0768 --- /dev/null +++ b/mx/circle.go @@ -0,0 +1,32 @@ +package mx + +// The type describes a simple +// math circle. +type Circle struct { + Center Vector + Radius Float +} + +func (c Circle) ContainsPoint( + pt Vector, +) bool { + d := c.Center.Sub(pt) + dx, dy := d.XY() + d2 := dx*dx + dy*dy + r2 := c.Radius*c.Radius + return r2 > d2 +} + +func (c Circle) GetContainedPoints( + pts Vectors, +) Vectors { + ret := make(Vectors, 0, len(pts)) + for _, pt := range pts { + if c.ContainsPoint(pt) { + ret = append(ret, pt) + } + } + + return ret +} + diff --git a/elipse.go b/mx/elipse.go similarity index 83% rename from elipse.go rename to mx/elipse.go index 79c6e78..5799af1 100644 --- a/elipse.go +++ b/mx/elipse.go @@ -1,7 +1,7 @@ -package gg +package mx // The structure represents elipses. -type Elipse struct { +/*type Elipse struct { // In transform S.X and S.Y represent // coefficents for the corresponding axises. Transform @@ -9,5 +9,5 @@ type Elipse struct { func (e Elipse) ContainsPoint(p Point) bool { return true -} +}*/ diff --git a/edge.go b/mx/line.go similarity index 58% rename from edge.go rename to mx/line.go index fafa892..2a49091 100644 --- a/edge.go +++ b/mx/line.go @@ -1,26 +1,11 @@ -package gg - -type Edges []Edge -// Get crosses of edges. -func (what Edges) CrossWithEdges(with Edges) ([]LineCross, bool) { - ret := []LineCross{} - for i := range what { - for j := range with { - cross, doCross := LinersCross(what[i], with[j]) - if doCross { - ret = append(ret, cross) - } - } - } - - return ret, len(ret) > 0 -} +package mx // The type represents a line segment. The name is for short. -type Edge [2]Vector +type Line [2]Vector +type Lines []Line // Returns corresponding to the segment Line. -func (l Edge) Line() Line { +func (l Line) GetLineExpr() LineExpr { var ( x Float vertical bool @@ -36,11 +21,12 @@ func (l Edge) Line() Line { vertical = true } - return Line{k, c, x, vertical} + return LineExpr{k, c, x, vertical} } -func (l Edge) ContainedPoints(pts Points) (Points) { - ret := Points{} +// Returns the points that the Line contains. +func (l Line) GetContainedPoints(pts Vectors) Vectors { + ret := make(Vectors, 0, len(pts)) for i := range pts { if l.ContainsPoint(pts[i]) { ret = append(ret, pts[i]) @@ -50,9 +36,9 @@ func (l Edge) ContainedPoints(pts Points) (Points) { } // Check if the edge contains the specified point. -func (l Edge) ContainsPoint(p Point) bool { - line := l.Line() - if !line.ContainsPoint(p) { +func (l Line) ContainsPoint(p Vector) bool { + lexpr := l.GetLineExpr() + if !lexpr.ContainsPoint(p) { return false } @@ -71,13 +57,13 @@ func (l Edge) ContainsPoint(p Point) bool { } // Get square of length of line segment (for performance sometimes). -func (ls Edge) LenSqr() Float { +func (ls Line) LenSqr() Float { return Sqr(ls[0].X - ls[1].X) + Sqr(ls[0].Y - ls[1].Y) } // Get length of the line segment. -func (ls Edge) Len() Float { +func (ls Line) Len() Float { return Sqrt(ls.LenSqr()) } diff --git a/mx/line_expr.go b/mx/line_expr.go new file mode 100644 index 0000000..56119b1 --- /dev/null +++ b/mx/line_expr.go @@ -0,0 +1,61 @@ +package mx + +//import "fmt" + +// The type represents mathematical equation of line and line itself. +type LineExpr struct { + K, C, X Float + Vertical bool +} + +// Returns the line itself. Made to implement the Liner interface. +func (l LineExpr) GetLineExpr() LineExpr { + return l +} + +func (e LineExpr) GetContainedPoints(pts Vectors) Vectors { + ret := make(Vectors, 0, len(pts)) + for _, pt := range pts { + if e.ContainsPoint(pt) { + ret = append(ret, pt) + } + } + return ret +} + +func (l LineExpr) ContainsPoint(p Vector) bool { + buf := LineExpr{0, p.Y, 0, false} + pc, ok := DoLinersCross(l, buf) + if !ok { + return false + } + + //Println("points:", l, p, pc) + return Neq(pc.Point.X, p.X) && Neq(pc.Point.Y, p.Y) +} + +func (l1 LineExpr) Crosses(l2 LineExpr) (Vector, bool) { + var x, y Float + if AreLinersParallel(l1, l2) { + return ZV, false + } + + switch { + case l1.Vertical : + x = l1.X + y = l2.K*x + l2.C + //Println("l1-vert:", x, y) + case l2.Vertical : + x = l2.X + y = l1.K*x + l1.C + //Println("l2-vert:", x, y) + default: + x = (l1.C - l2.C) / (l2.K - l1.K) + y = l1.K*x + l1.C + } + + //Println("in:", x, y) + return Vector{x, y}, true +} + + diff --git a/mx/liner.go b/mx/liner.go new file mode 100644 index 0000000..118a50a --- /dev/null +++ b/mx/liner.go @@ -0,0 +1,81 @@ +package mx + +type Liner interface { + PointContainer + GetLineExpr() LineExpr +} +type Liners []Liner + +// The type represents the cross of 2 Liners. +type LinersCrossing struct { + Pair [2]LineExpr + Point Vector +} + +// Checks all possible combinations +// of Liners to cross and returns +// the crossings if are present. +func GetLinersCrossings( + what, with Liners, +) []LinersCrossing { + ret := make([]LinersCrossing, 0, len(with)) + for i := range what { + for j := range with { + cross, doCross := DoLinersCross( + what[i], + with[j], + ) + if doCross { + ret = append(ret, cross) + } + } + } + + return ret +} + +// Check if two LinerPointContainers do cross and return the +// crossing point. +func DoLinersCross( + lp1, lp2 Liner, +) (LinersCrossing, bool) { + l1 := lp1.GetLineExpr() + l2 := lp2.GetLineExpr() + + crossPt, doCross := l1.Crosses(l2) + if !lp1.ContainsPoint(crossPt) || + !lp2.ContainsPoint(crossPt) { + return LinersCrossing{}, false + } + + return LinersCrossing{ + Pair: [2]LineExpr{l1, l2}, + Point: crossPt, + }, doCross +} + +// Check whether the liner is parallel to the other liner. +func AreLinersParallel( + first, second Liner, +) bool { + l1 := first.GetLineExpr() + l2 := second.GetLineExpr() + + return l1.Vertical && l2.Vertical || + l1.K == l2.K +} + +// Returns angle between liners in radians. +// The value fits the -Pi < Value < Pi condition. +func GetAngleBetweenLiners( + first, second Liner, +) Float { + l1 := first.GetLineExpr() + l2 := second.GetLineExpr() + + if l1.K == l2.K { + return 0 + } + + return Atan(l1.K/l2.K) +} diff --git a/math.go b/mx/math.go similarity index 90% rename from math.go rename to mx/math.go index 1f3e124..d2b8ea0 100644 --- a/math.go +++ b/mx/math.go @@ -1,11 +1,13 @@ -package gg +package mx import ( "math" ) // The type is used in all Engine interactions -// where you need floating values. +// where you need floating values to keep +// it interchangable in future. +// Not much promise for this though. type Float = float64 const ( diff --git a/matrix.go b/mx/matrice.go similarity index 51% rename from matrix.go rename to mx/matrice.go index 913c143..31ec933 100644 --- a/matrix.go +++ b/mx/matrice.go @@ -1,9 +1,8 @@ -package gg +package mx import ( "github.com/hajimehoshi/ebiten/v2" - //"math" ) -type Matrix = ebiten.GeoM +type Matrice = ebiten.GeoM diff --git a/mx/point.go b/mx/point.go new file mode 100644 index 0000000..311b85a --- /dev/null +++ b/mx/point.go @@ -0,0 +1,10 @@ +package mx + +// The type provides interface +// to check if an objects +// contains points. +type PointContainer interface { + GetContainedPoints(Vectors) Vectors + ContainsPoint(Vector) bool +} + diff --git a/ray.go b/mx/ray.go similarity index 71% rename from ray.go rename to mx/ray.go index 1e7e4ed..c7260cc 100644 --- a/ray.go +++ b/mx/ray.go @@ -1,10 +1,10 @@ -package gg +package mx // The type represents math ray. type Ray struct { // The start point. - P Point + Start Vector // Rotation of the ray. - R Float + Rotaton Float } diff --git a/size.go b/mx/size.go similarity index 83% rename from size.go rename to mx/size.go index c06b113..510ba58 100644 --- a/size.go +++ b/mx/size.go @@ -1,16 +1,16 @@ -package gg +package mx // The type describes a simple // rectangle without transformations. type Size struct { // The upper left corner position point. - Position Point + Position Vector // Absolute width and height. // Both must be positive values. Width, Height Float } -func (size Size) ContainsPoint(pt Point) bool { +func (size Size) ContainsPoint(pt Vector) bool { return (size.Position.X < pt.X && (size.Position.X + size.Width) > pt.X ) && (size.Position.Y < pt.Y && (size.Position.Y + size.Height) > pt.Y ) } diff --git a/mx/triangle.go b/mx/triangle.go new file mode 100644 index 0000000..ac8b10a --- /dev/null +++ b/mx/triangle.go @@ -0,0 +1,106 @@ +package mx + +import ( + "math" + //"github.com/hajimehoshi/ebiten/v2" +) + +type Triangle [3]Vector + +// Returns the area of the triangle. +func (t Triangle) Area() Float { + x1 := t[0].X + y1 := t[0].Y + + x2 := t[1].X + y2 := t[1].Y + + x3 := t[2].X + y3 := t[2].Y + + return math.Abs( + ( x1*(y2-y3) + + x2*(y3-y1) + + x3*(y1-y2))/2 ) +} +// Return squares of lengths of sides of the triangle. +func (t Triangle) SideLengthSquares() ([3]Float) { + + l1 := Line{t[0], t[1]}.LenSqr() + l2 := Line{t[1], t[2]}.LenSqr() + l3 := Line{t[2], t[0]}.LenSqr() + + return [3]Float{l1, l2, l3} +} + +// Check whether the point is in the triangle. +func (t Triangle) ContainsPoint(p Vector) bool { + d1 := Triangle{p, t[0], t[1]}.Sgn() + d2 := Triangle{p, t[1], t[2]}.Sgn() + d3 := Triangle{p, t[2], t[0]}.Sgn() + + neg := (d1 < 0) || (d2 < 0) || (d3 < 0) + pos := (d1 > 0) || (d2 > 0) || (d3 > 0) + + return !(neg && pos) +} + +func (t Triangle) GetContainedPoints(pts Vectors) Vectors { + ret := make(Vectors, 0, len(pts)) + for i := range pts { + if t.ContainsPoint(pts[i]) { + ret = append(ret, pts[i]) + } + } + return ret +} + +func (t Triangle) Sgn() Float { + return (t[0].X - t[2].X) * (t[1].Y - t[2].Y) - + (t[1].X - t[2].X) * (t[0].Y - t[2].Y) +} + +func (t Triangle) GetEdges() Lines { + return Lines{ + Line{t[0], t[1]}, + Line{t[1], t[2]}, + Line{t[2], t[0]}, + } +} + +func (t Triangle) GetVertices() Vectors { + return Vectors{ + t[0], t[1], t[2], + } +} + +type Triangles []Triangle + +func (ts Triangles) GetContainedPoints( + pts Vectors, +) Vectors { + ret := Vectors{} + for _, t := range ts { + ps := t.GetContainedPoints(pts) + ret = append(ret, ps...) + } + + return ret +} + +func (ts Triangles) GetVertices() Vectors { + ret := make(Vectors, 0, len(ts)*3) + for _, t := range ts { + ret = append(ret, t.GetVertices()...) + } + return ret +} + +func (ts Triangles) GetEdges() Lines { + ret := make(Lines, 0, len(ts)*3) + for _, t := range ts { + ret = append(ret, t.GetEdges()...) + } + return ret +} + diff --git a/vector.go b/mx/vector.go similarity index 72% rename from vector.go rename to mx/vector.go index 36d49e5..8520475 100644 --- a/vector.go +++ b/mx/vector.go @@ -1,4 +1,4 @@ -package gg +package mx import ( //"github.com/hajimehoshi/ebiten/v2" @@ -11,11 +11,13 @@ var ( type Vector struct { X, Y Float + } +type Vectors []Vector + func (v Vector) XY() (Float, Float) { return v.X, v.Y } -type Vectors []Vector func V(x, y Float) Vector { return Vector{x, y} @@ -25,11 +27,11 @@ func V2(v Float) Vector { return Vector{v, v} } -func X(x Float) Vector { +func VX(x Float) Vector { return Vector{x, 0} } -func Y(y Float) Vector { +func VY(y Float) Vector { return Vector{0, y} } @@ -52,27 +54,23 @@ func (v Vector) Eq(o Vector) bool { } // Returns the vector with the matrix applied -func (v Vector) Apply(m Matrix) Vector { +func (v Vector) Apply(m Matrice) Vector { x, y := m.Apply(v.X, v.Y) return Vector{x, y} } // Adds the vector to other one returning the result. -func (v Vector) Add(a ...Vector) Vector { - for _, r := range a { - v.X += r.X - v.Y += r.Y - } +func (v Vector) Add(a Vector) Vector { + v.X += a.X + v.Y += a.Y return v } // Returns the subtraction of all the vectors from the current one. -func (v Vector) Sub(s ...Vector) Vector { - for _, r := range s { - v.X -= r.X - v.Y -= r.Y - } +func (v Vector) Sub(s Vector) Vector { + v.X -= s.X + v.Y -= s.Y return v } @@ -87,7 +85,7 @@ func (v Vector) Neg() Vector { // Returns the vector rotated by "a" angle in radians. func (v Vector) Rot(a Float) Vector { - m := Matrix{} + m := Matrice{} m.Rotate(a) return v.Apply(m) } @@ -98,11 +96,13 @@ func (v Vector) Norm() Vector { return V(v.X / l, v.Y / l) } -func (pts Points) ContainedIn(c PointContainer) Points { - ret := make([]Point, 0, len(pts)) +func (pts Vectors) PointsContainedIn( + c PointContainer, +) Vectors { + ret := make(Vectors, 0, len(pts)) for _, pt := range pts { - if !c.ContainedPoints(Points{pt}).Empty() { + if c.ContainsPoint(pt) { ret = append(ret, pt) } } @@ -110,7 +110,3 @@ func (pts Points) ContainedIn(c PointContainer) Points { return ret } -func (pts Points) Len() int { - return len(pts) -} - diff --git a/mx/vertice.go b/mx/vertice.go new file mode 100644 index 0000000..272aa5b --- /dev/null +++ b/mx/vertice.go @@ -0,0 +1,2 @@ +package mx + diff --git a/object.go b/object.go index 8f0082e..449e3dc 100644 --- a/object.go +++ b/object.go @@ -1,88 +1,14 @@ package gg -// Implementing the interface type -// will call the function OnStart -// when first appear on scene BEFORE -// the OnUpdate. -// The v value will be get from Add function. -type Starter interface { - Start(*Context) -} - -// Implementing the interface type -// will call the function on each -// engine iteration. -type Updater interface { - Update(*Context) -} - -// Implementing the interface type -// will call the function on deleting -// the object. -type Deleter interface { - Delete(*Context) -} - -type Antialiasity struct { - Antialias bool -} - -// Feat to embed for turning visibility on and off. -type Visibility struct { - Visible bool -} -func (v Visibility) IsVisible() bool { - return v.Visible -} - -// Feat to embed to make colorful objects. -type Colority struct { - Color Color -} - -// The interface describes anything that can be -// drawn. It will be drew corresponding to -// the layers order so the layer must be returned. -type Drawer interface { - Draw(*Context) []EVertex - GetLayer() Layer - IsVisible() bool -} - -type Objecter interface { - GetObject() *Object - Input() chan *Context - Starter - Updater - Collider - Resolver +// The interface must +// me implemented for all the +// in-game logic objects. +type Object interface { Drawer - Deleter + OnStart(Context) + OnUpdate(Context) + OnDelete(Context) + GetTags() map[string]struct{} } -// The type for embedding into engine-in types. -type Object struct { - Collidability - Resolvability - Layer - Visibility - - input chan *Context -} - -func (o *Object) GetObject() *Object { return o } -func (o *Object) Input() chan *Context { return o.input } - -func (o *Object) Start(c *Context) {} - -func (o *Object) Update(c *Context) {} - -func (o *Object) ContainedPoints(Points) Points {return Points{}} -func (o *Object) Vertices() Vertices {return Vertices{}} -func (o *Object) Edges() Edges {return Edges{}} - -func (o *Object) Resolve(c *Context) {} - -func (o *Object) Draw(c *Context) []EVertex { return nil} -func (o *Object) Delete(c *Context) {} diff --git a/ox/asprite.go b/ox/asprite.go new file mode 100644 index 0000000..6690431 --- /dev/null +++ b/ox/asprite.go @@ -0,0 +1,56 @@ +package ox + +import "surdeus.su/core/gg" +import "surdeus.su/core/gg/ax" + +// The type implements animated sprites. +type AnimatedSprite struct { + Sprite + + animations ax.AnimationSet + animationId ax.AnimationId + + currentFrame int + + // This is time between animation frames, not + // engine ones. + timeBetweenFrames gg.Duration + duration gg.Duration +} + +func NewAnimatedSprite( +) *AnimatedSprite { + ret := &AnimatedSprite{} + return ret +} + +// Change current animation to the specified one. +func (as *AnimatedSprite) Animate(id ax.AnimationId) bool { + if as.AnimationId == id { + return true + } + _, ok := as.Animations[id] + if ok { + as.duration = 0 + as.AnimationId = id + } + return ok +} + +func (as *AnimatedSprite) Draw(c Context) []EVertex { + as.duration += c.DrawDT() + + frames := as.Animations[as.AnimationId] + fullTime := Duration(len(frames)) * as.TimeBetweenFrames + if as.duration > fullTime { + as.duration -= fullTime + } + + as.Images[0] = frames[ + (as.duration/as.TimeBetweenFrames) % + Duration(len(frames)), + ] + + return as.Sprite.Draw(c) +} + diff --git a/ox/camera.go b/ox/camera.go new file mode 100644 index 0000000..1285557 --- /dev/null +++ b/ox/camera.go @@ -0,0 +1,76 @@ +package ox + +import "surdeus.su/core/gg" +import "surdeus.su/core/gg/mx" + + +// Default camera implementation. +type Camera struct { + ObjectImpl + + // The Transform to interact with + // to change camera position, rotation etc. + Transform + + // The shaders that will be applied to everything + // that the camera shows. + Shaderity + + // The bufferization implementation + // to keep everything fast. + absMatrice realMatrice +} + +// Returns the new camera +// with default settings. +func NewCamera() *Camera { + ret := &Camera{} + ret.Transform = T() + return ret +} + +// Returns the matrix satysfying camera +// position, scale and rotation to apply +// it to the objects to get the real +// transform to display on the screen. +func (c *Camera) GetRealMatrice(ctx gg.Context) mx.Matrice { + // Bufferization + // so we do not need to recalculate + // Transform again. + if !c.Transform.Dirty() { + return c.realMatrice + } + + var g mx.Matrice + position := c.GetPosition().Neg() + around := c.GetAround() + scale := c.GetScale() + rotation := c.GetRotation() + + g.Translate(-position.X, -position.Y) + g.Rotate(rotation) + + size := ctx.Engine.AbsWinSize() + g.Translate(around.X * size.X, around.Y * size.Y) + + g.Scale(scale.X, scale.Y) + + c.realMatrice = g + return g +} + +// The matrix to convert things into the +// inside engine representation, +// get the position of cursor inside the world +// basing on its window position. +func (c *Camera) GetAbsMatrice(ctx gg.Context) mx.Matrice { + if !c.Transform.Dirty() { + return c.absMatrice + } + m := c.GetRealMatrice(ctx) + m.Invert() + + c.absMatrice = m + return m +} + diff --git a/circle.go b/ox/circle.go similarity index 85% rename from circle.go rename to ox/circle.go index d35bde2..a59f41c 100644 --- a/circle.go +++ b/ox/circle.go @@ -1,12 +1,14 @@ -package gg +package ox import ( "github.com/hajimehoshi/ebiten/v2/vector" ) +import "surdeus.su/core/gg" + type Circle struct { - Object - Transform + ObjectImpl + gg.Transform Visibility Colority Antialiasity diff --git a/ox/feats.go b/ox/feats.go new file mode 100644 index 0000000..0feaabe --- /dev/null +++ b/ox/feats.go @@ -0,0 +1,31 @@ +package ox + + +// Feat to emded for turning antialias on and off. +type Antialiasity struct { + Antialias bool +} + +// Feat to embed for turning visibility on and off. +type Visibility struct { + Visible bool +} +func (v Visibility) IsVisible() bool { + return v.Visible +} + +// Feat to embed to make colorful objects. +type Colority struct { + Color Color +} + +// The structure to embed into shaderable +// objects. +type Shaderity struct { + ShaderOptions ShaderOptions +} + +func (s *Shaderity) GetShaderOptions( +) *ShaderOptions { + return &s.ShaderOptions +} diff --git a/ox/object.go b/ox/object.go new file mode 100644 index 0000000..9d2619e --- /dev/null +++ b/ox/object.go @@ -0,0 +1,12 @@ +package ox + +// The standard empty implementation +// of the Object interface to embed. +type ObjectImpl struct {} +func (o ObjectImpl) OnStart(c Context) {} +func (o ObjectImpl) OnUpdate(c Context) {} +func (o ObjectImpl) OnDelete(c Context) {} +func (o ObjectImpl) GetTags() map[string]struct{}{return nil} +func (o ObjectImpl) Draw(c Context) {} +func (o ObjectImpl) GetLayer() Layer {return 0} +func (o ObjectImpl) IsVisible() bool {return false} diff --git a/polygon.go b/ox/polygon.go similarity index 51% rename from polygon.go rename to ox/polygon.go index 6424aaa..b391a37 100644 --- a/polygon.go +++ b/ox/polygon.go @@ -1,13 +1,15 @@ -package gg +package ox import ( + "surdeus.su/core/gg" + "surdeus.su/core/gg/mx" ) // Grouped triangles type. type Polygon struct { - Object + ObjectImpl Transform - Triangles + mx.Triangles } func (p Polygon) ContainedPoints(pts Points) (Points) { @@ -15,7 +17,7 @@ func (p Polygon) ContainedPoints(pts Points) (Points) { } func (p Polygon) MakeTriangles() Triangles { - m := p.Matrix() + m := p.Transform.GetMatrice() ret := make(Triangles, len(p.Triangles)) for i, t := range p.Triangles { ret[i] = Triangle{ @@ -28,11 +30,12 @@ func (p Polygon) MakeTriangles() Triangles { return ret } -func (p Polygon) Vertices() Vertices { - return p.MakeTriangles().Vertices() +func (p Polygon) GetVertices() mx.Vectors { + return p.MakeTriangles().GetVertices() } -func (p Polygon) Edges() Edges { - return p.MakeTriangles().Edges() + +func (p Polygon) GetEdges() mx.Lines { + return p.MakeTriangles().GetEdges() } // Polygon that can be drawn. @@ -43,10 +46,13 @@ type DrawablePolygon struct { Colority } -func (p *DrawablePolygon) Draw(c *Context) []EVertex { - return (&DrawableTriangles{ - Colority: p.Colority, - Triangles: p.MakeTriangles(), - }).MakeEVertices(c) +func (p *DrawablePolygon) Draw(c *Context) gg.Drawing { + ret := gg.Drawing{ + EVertices: (&DrawableTriangles{ + Colority: p.Colority, + Triangles: p.MakeTriangles(), + }).MakeEVertices(c), + } + return ret } diff --git a/rect.go b/ox/rect.go similarity index 76% rename from rect.go rename to ox/rect.go index e4191cd..2bfb314 100644 --- a/rect.go +++ b/ox/rect.go @@ -1,4 +1,4 @@ -package gg +package ox import ( //"github.com/hajimehoshi/ebiten/v2" @@ -6,17 +6,19 @@ import ( //"fmt" //"image" ) +import "surdeus.su/core/gg" +import "surdeus.su/core/gg/mx" // The type describes rectangle geometry with // way to move, rotate and scale it. type Rectangle struct { - Object - Transform + ObjectImpl + gg.Transform Width, Height Float } // Return points of vertices of the rectangle. -func (r Rectangle) Vertices() Vertices { +func (r Rectangle) Vertices() mx.Vectors { t := r.Transform wh := V(r.Width, r.Height) t.SetAround(t.Around().Mul(wh)) @@ -25,12 +27,12 @@ func (r Rectangle) Vertices() Vertices { p2 := V(wh.X, 0).Apply(m) p3 := V(wh.X, wh.Y).Apply(m) p4 := V(0, wh.Y).Apply(m) - return Vertices{p1, p2, p3, p4} + return mx.Vectors{p1, p2, p3, p4} } -func (r Rectangle) Edges() Edges { +func (r Rectangle) Edges() mx.Lines { vs := r.Vertices() - return Edges{ + return Lines{ Edge{vs[0], vs[1]}, Edge{vs[1], vs[2]}, Edge{vs[2], vs[3]}, @@ -39,22 +41,24 @@ func (r Rectangle) Edges() Edges { } // Get 2 triangles that the rectangle consists of. -func (r Rectangle) MakeTriangles() Triangles { +func (r Rectangle) MakeTriangles() mx.Triangles { pts := r.Vertices() p1 := pts[0] p2 := pts[1] p3 := pts[2] p4 := pts[3] - return Triangles{ - Triangle{p1, p2, p3}, - Triangle{p1, p4, p3}, + return mx.Triangles{ + mx.Triangle{p1, p2, p3}, + mx.Triangle{p1, p4, p3}, } } // Check whether the rectangle contains the point. -func (r Rectangle) ContainedPoints(pts Points) (Points) { - return r.MakeTriangles().ContainedPoints(pts) +func (r Rectangle) GetContainedPoints( + pts mx.Vectors, +) (mx.Vectors) { + return r.MakeTriangles().GetContainedPoints(pts) } // The type describes rectangle that can be drawn. diff --git a/sprite.go b/ox/sprite.go similarity index 55% rename from sprite.go rename to ox/sprite.go index dec4e10..03313c3 100644 --- a/sprite.go +++ b/ox/sprite.go @@ -1,13 +1,16 @@ -package gg +package ox import ( "github.com/hajimehoshi/ebiten/v2" //"fmt" ) +import "surdeus.su/core/gg" +import "surdeus.su/core/gg/mx" + type Sprite struct { - Object - Transform + ObjectImpl + gg.Transform ShaderOptions Floating bool Visibility @@ -17,16 +20,16 @@ var ( //spritesOp DrawImageOptions ) -func (s *Sprite) Draw(c *Context) []EVertex { +func (s *Sprite) Draw(c Context) []EVertex { // Nothing to draw. if s.Images[0] == nil { return nil } - t := s.RectangleToDraw().Transform - m := t.Matrix() + t := s.GetRectangleToDraw().Transform + m := t.GetMatrice() if !s.Floating { - m.Concat(c.Camera.RealMatrix()) + m.Concat(c.Camera.GetRealMatrix()) } // Drawing without shader. @@ -39,16 +42,20 @@ func (s *Sprite) Draw(c *Context) []EVertex { w, h := s.Images[0].Size() // Drawing with shader. - c.DrawRectShader(w, h, s.Shader, &ebiten.DrawRectShaderOptions{ - Images: s.Images, - Uniforms: s.Uniforms, - GeoM: m, - }) + c.DrawRectShader( + w, h, + s.Shader, + &ebiten.DrawRectShaderOptions{ + Images: s.Images, + Uniforms: s.Uniforms, + GeoM: m, + }, + ) return nil } // Return the rectangle that contains the sprite to draw. -func (s *Sprite) RectangleToDraw() Rectangle { +func (s *Sprite) GetRectangleToDraw() Rectangle { if s.Images[0] == nil { return Rectangle{} } @@ -66,8 +73,9 @@ func (s *Sprite) RectangleToDraw() Rectangle { } } -// Return the rectangle that contains the sprite in the engine. -func (s *Sprite) Rectangle() Rectangle { +// Return the rectangle that contains +// the sprite in the engine. +func (s *Sprite) GetRectangle() Rectangle { if s.Images[0] == nil { return Rectangle{ Transform: s.Transform, @@ -90,14 +98,18 @@ func (s *Sprite) MakeTriangles() Triangles { return s.Rectangle().MakeTriangles() } -func (s *Sprite) Vertices() Vertices { - return s.Rectangle().Vertices() +func (s *Sprite) Vertices() mx.Vectors { + return s.GetRectangle().Vertices() } -func (s *Sprite) Edges() Edges { - return s.Rectangle().Edges() +func (s *Sprite) Edges() mx.Lines { + return s.GetRectangle().Edges() } -func (s *Sprite) ContainedPoints(pts Points) Points { - return s.Rectangle().ContainedPoints(pts) +func (s *Sprite) ContainsPoint(pt mx.Vector) bool { + return s.GetRectangle().ContainsPoint(pt) +} + +func (s *Sprite) GetContainedPoints(pts mx.Vectors) Points { + return s.Rectangle().GetContainedPoints(pts) } diff --git a/ox/text.go b/ox/text.go new file mode 100644 index 0000000..d3036a9 --- /dev/null +++ b/ox/text.go @@ -0,0 +1,28 @@ +package ox + +import "surdeus.su/core/gg/mx" +import "surdeus.su/core/gg" + +// The type implements basic drawable text. +// (Now needs to implement rotation, scaling etc, cause now only position) +type Text struct { + ObjectImpl + mx.Transform + Data string + Face Face + Colority + Visibility +} + +func (txt *Text) Draw(c gg.Context) []EVertex { + m := txt.Matrix() + m.Concat(c.Camera.RealMatrix()) + //x, y := txt.Position.XY() + //text.Draw(c.Image) + text.DrawWithOptions(c.Image, txt.Data, txt.Face, + &ebiten.DrawImageOptions{ + GeoM: m, + }, + ) + return nil +} diff --git a/transform.go b/ox/transform.go similarity index 65% rename from transform.go rename to ox/transform.go index 790845d..3d66014 100644 --- a/transform.go +++ b/ox/transform.go @@ -1,25 +1,23 @@ -package gg +package ox -import ( - //"github.com/hajimehoshi/ebiten/v2" - //"math" -) +import "surdeus.su/core/gg/mx" type Transformer interface { GetTransform() *Transform } // The structure represents basic transformation -// features: positioning, rotating and scaling. +// features: positioning, rotating and scaling +// in more high level and more "GAME ENGINE" style. type Transform struct { // Absolute (if no parent) position and // the scale. - position, scale Vector + position, scale mx.Vector // The object rotation in radians. - rotation Float + rotation mx.Float // The not scaled offset vector from upper left corner // which the object should be rotated around. - around Vector + around mx.Vector // If is not nil then the upper values will be relational to // the parent ones. @@ -28,7 +26,24 @@ type Transform struct { // Dirty is true if we anyhow changed matrix. dirty, parentDirty bool - matrix, parentMatrix, parentInverted Matrix + matrix, parentMatrice, parentInverted mx.Matrice +} + +// Returns the default Transform structure. +func T() Transform { + ret := Transform{ + // Rotate around + scale: mx.V2(1), + // Rotate around the center. + around: mx.V2(.5), + } + return ret +} + +// Returns whether it needs to be recalculated +// or not. +func (t *Transform) IsDirty() bool { + return t.dirty } // For implementing the Transformer on embedding. @@ -36,19 +51,8 @@ func (t *Transform) GetTransform() *Transform { return t } -// Returns the default Transform structure. -func T() Transform { - ret := Transform{ - // Rotate around - scale: Vector{1, 1}, - // Rotate around the center. - around: V(.5, .5), - } - return ret -} - // Set the absolute object position. -func (t *Transform) SetPosition(position Vector) { +func (t *Transform) SetPosition(position mx.Vector) { t.dirty = true t.parentDirty = true if t.parent == nil { @@ -56,35 +60,35 @@ func (t *Transform) SetPosition(position Vector) { return } - _, mi := t.parent.MatrixForParenting() + _, mi := t.parent.GetMatriceForParenting() t.position = position.Apply(mi) } // Set the absolute object rotation. -func (t *Transform) SetRotation(rotation Float) { +func (t *Transform) SetRotation(rotation mx.Float) { t.dirty = true t.parentDirty = true if t.parent == nil { t.rotation = rotation return } - t.rotation -= t.parent.Rotation() + t.rotation -= t.parent.GetRotation() } // Set the absolute object scale. -func (t *Transform) SetScale(scale Vector) { +func (t *Transform) SetScale(scale mx.Vector) { t.dirty = true t.parentDirty = true t.scale = scale } -func (t *Transform) AddScale(add ...Vector) { +func (t *Transform) AddScale(add mx.Vector) { t.dirty = true //t.parentDirty = true - t.scale = t.scale.Add(add...) + t.scale = t.scale.Add(add) } -func (t *Transform) SetAround(around Vector) { +func (t *Transform) SetAround(around mx.Vector) { t.dirty = true t.around = around } @@ -96,10 +100,10 @@ func (t *Transform) Abs() Transform { } ret := Transform{} - ret.position = t.Position() - ret.rotation = t.Rotation() - ret.scale = t.Scale() - ret.around = t.Around() + ret.position = t.GetPosition() + ret.rotation = t.GetRotation() + ret.scale = t.GetScale() + ret.around = t.GetAround() ret.dirty = true ret.parentDirty = true @@ -113,43 +117,44 @@ func (t *Transform) Rel() Transform { } // Get the absolute object position. -func (t *Transform) Position() Vector { +func (t *Transform) GetPosition() mx.Vector { if t.parent == nil { return t.position } - pm, _ := t.parent.MatrixForParenting() + pm, _ := t.parent.GetMatriceForParenting() return t.position.Apply(pm) } -func (t *Transform) Move(v ...Vector) { - t.SetPosition(t.Position().Add(v...)) +// Move by the specified delta. +func (t *Transform) Move(v mx.Vector) { + t.SetPosition(t.GetPosition().Add(v)) } // Get the absolute object scale. -func (t *Transform) Scale() Vector { +func (t *Transform) GetScale() mx.Vector { return t.scale } // Get the absolute object rotation. -func (t *Transform) Rotation() Float { +func (t *Transform) GetRotation() mx.Float { if t.parent == nil { return t.rotation } - return t.rotation + t.parent.Rotation() + return t.rotation + t.parent.GetRotation() } -func (t *Transform) Rotate(add Float) { +func (t *Transform) Rotate(rot mx.Float) { t.dirty = true t.parentDirty = true - t.rotation += add + t.rotation += rot } -func (t *Transform) Around() Vector { +func (t *Transform) GetAround() mx.Vector { return t.around } // Returns true if the object is connected // to some parent. -func (t *Transform) Connected() bool { +func (t *Transform) IsConnected() bool { return t.parent != nil } @@ -163,8 +168,8 @@ func (t *Transform) Connect(parent Transformer) { t.Disconnect() } - position := t.Position() - rotation := t.Rotation() + position := t.GetPosition() + rotation := t.GetRotation() t.parent = parent.GetTransform() t.SetPosition(position) @@ -180,14 +185,15 @@ func (t *Transform) Disconnect() { } // Return the matrix and the inverted one for parenting children. -func (t *Transform) MatrixForParenting() (Matrix, Matrix) { - var m, mi Matrix +func (t *Transform) GetMatriceForParenting( +) (mx.Matrice, mx.Matrice) { + var m, mi mx.Matrice if t.parentDirty { //m.Scale(t.scale.X, t.scale.Y) m.Rotate(t.rotation) m.Translate(t.position.X, t.position.Y) - t.parentMatrix = m + t.parentMatrice = m mi = m mi.Invert() @@ -195,26 +201,25 @@ func (t *Transform) MatrixForParenting() (Matrix, Matrix) { t.parentDirty = false } else { - m = t.parentMatrix + m = t.parentMatrice mi = t.parentInverted } if t.parent != nil { - pm, pmi := t.parent.MatrixForParenting() + pm, pmi := t.parent.GetMatriceForParenting() m.Concat(pm) pmi.Concat(mi) mi = pmi } return m, mi - - } // Returns the GeoM with corresponding // to the transfrom transformation. -func (t *Transform)Matrix() Matrix { - var m, pm Matrix +func (t *Transform) GetMatrice() mx.Matrice { + var m, pm mx.Matrice + // Calculating only if we changed the structure anyhow. if t.dirty { @@ -237,7 +242,7 @@ func (t *Transform)Matrix() Matrix { } if t.parent != nil { - pm, _ = t.parent.MatrixForParenting() + pm, _ = t.parent.GetMatriceForParenting() m.Concat(pm) } diff --git a/ox/tri.go b/ox/tri.go new file mode 100644 index 0000000..53d886b --- /dev/null +++ b/ox/tri.go @@ -0,0 +1,20 @@ +package ox + +type DrawableTriangles struct { + Triangles + Colority +} + +func (r *DrawableTriangles) MakeEVertices(c *Context) []EVertex { + m := c.Camera.RealMatrix() + vs := make([]ebiten.Vertex, len(r.Triangles) * 3) + var buf Vertex + buf.Color = r.Color + for i := range r.Triangles { + for j := range r.Triangles[i] { + buf.Dst = r.Triangles[i][j].Apply(m) + vs[i*3 + j] = buf.Ebiten() + } + } + return vs +} diff --git a/point.go b/point.go deleted file mode 100644 index b8080d4..0000000 --- a/point.go +++ /dev/null @@ -1,8 +0,0 @@ -package gg - -type Point = Vector -type Points []Point - -func (pts Points) Empty() bool { - return len(pts) == 0 -} diff --git a/shader.go b/shader.go index f1fcacb..81a6789 100644 --- a/shader.go +++ b/shader.go @@ -5,13 +5,14 @@ import ( //"fmt" ) -type Shader = ebiten.Shader type ShaderOptions struct { Shader *Shader Uniforms map[string] any Images [4]*Image } +type Shader = ebiten.Shader + var ( // The shader is for example only. SolidWhiteColorShader = MustNewShader([]byte(` diff --git a/short.go b/short.go new file mode 100644 index 0000000..71a9c9e --- /dev/null +++ b/short.go @@ -0,0 +1 @@ +package gg diff --git a/text.go b/text.go index 544054b..5ac62dd 100644 --- a/text.go +++ b/text.go @@ -4,8 +4,8 @@ import ( //"strings" "golang.org/x/image/font" "golang.org/x/image/font/opentype" - "github.com/hajimehoshi/ebiten/v2/text" - "github.com/hajimehoshi/ebiten/v2" + //"github.com/hajimehoshi/ebiten/v2/text" + //"github.com/hajimehoshi/ebiten/v2" "io" //"fmt" ) @@ -14,20 +14,22 @@ import ( var ( FontHintingNone = font.HintingNone ) - type Face = font.Face -type TtfFont = opentype.Font -type TtfFaceOptions = opentype.FaceOptions -type TtfFace = opentype.Face +type FontTTF = opentype.Font +type FaceOptionsTTF = opentype.FaceOptions +type FaceTTF = opentype.Face -func MakeFaceFromTtf(src io.ReaderAt, opts *TtfFaceOptions) (Face, error) { - fnt, err := ParseTtfFont(src) +func MakeFaceFromTTF( + src io.ReaderAt, + opts *FaceOptionsTTF, +) (Face, error) { + fnt, err := ParseFontTTF(src) if err != nil { return nil, err } - face, err := NewTtfFace(fnt, opts) + face, err := NewFaceTTF(fnt, opts) if err != nil { return nil, err } @@ -35,35 +37,17 @@ func MakeFaceFromTtf(src io.ReaderAt, opts *TtfFaceOptions) (Face, error) { return face, nil } -func ParseTtfFont(src io.ReaderAt) (*TtfFont, error) { +func ParseFontTTF( + src io.ReaderAt, +) (*FontTTF, error) { return opentype.ParseReaderAt(src) } -func NewTtfFace(fnt *TtfFont, opts *TtfFaceOptions) (Face, error) { +func NewFaceTTF( + fnt *FontTTF, + opts *FaceOptionsTTF, +) (Face, error) { return opentype.NewFace(fnt, opts) } -// The type implements basic drawable text. -// (Now needs to implement rotation, scaling etc, cause now only position) -type Text struct { - Object - Transform - Data string - Face Face - Colority - Visibility -} - -func (txt *Text) Draw(c *Context) []EVertex { - m := txt.Matrix() - m.Concat(c.Camera.RealMatrix()) - //x, y := txt.Position.XY() - //text.Draw(c.Image) - text.DrawWithOptions(c.Image, txt.Data, txt.Face, - &ebiten.DrawImageOptions{ - GeoM: m, - }, - ) - return nil -} diff --git a/triangle.go b/triangle.go deleted file mode 100644 index ea9983d..0000000 --- a/triangle.go +++ /dev/null @@ -1,144 +0,0 @@ -package gg - -import ( - "math" - "github.com/hajimehoshi/ebiten/v2" -) - -type EVertex = ebiten.Vertex - -// Ebitens vector in better abstractions like Vectors. -type Vertex struct { - Dst Vector - Src Vector - Colority -} - -type Triangle [3]Vector -type DrawableTriangles struct { - Triangles - Colority -} - -func (v Vertex) Ebiten() EVertex { - return EVertex { - DstX: float32(v.Dst.X), - DstY: float32(v.Dst.Y), - SrcX: float32(v.Src.X), - SrcY: float32(v.Src.Y), - ColorR: float32(v.Color.R)/(float32(MaxColorV)), - ColorG: float32(v.Color.G)/(float32(MaxColorV)), - ColorB: float32(v.Color.B)/(float32(MaxColorV)), - ColorA: float32(v.Color.A)/(float32(MaxColorV)), - } -} - -// Returns the area of the triangle. -func (t Triangle) Area() Float { - x1 := t[0].X - y1 := t[0].Y - - x2 := t[1].X - y2 := t[1].Y - - x3 := t[2].X - y3 := t[2].Y - - return math.Abs( (x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2))/2) -} -func sqr(v Float) Float { - return v * v -} -// Return squares of lengths of sides of the triangle. -func (t Triangle) SideLengthSquares() ([3]Float) { - - l1 := Edge{t[0], t[1]}.LenSqr() - l2 := Edge{t[1], t[2]}.LenSqr() - l3 := Edge{t[2], t[0]}.LenSqr() - - return [3]Float{l1, l2, l3} -} - -// Check whether the point is in the triangle. -func (t Triangle) ContainsPoint(p Point) bool { - d1 := Triangle{p, t[0], t[1]}.Sgn() - d2 := Triangle{p, t[1], t[2]}.Sgn() - d3 := Triangle{p, t[2], t[0]}.Sgn() - - neg := (d1 < 0) || (d2 < 0) || (d3 < 0) - pos := (d1 > 0) || (d2 > 0) || (d3 > 0) - - return !(neg && pos) -} - -func (t Triangle) ContainedPoints(pts Points) []Point { - ret := []Point{} - for i := range pts { - if t.ContainsPoint(pts[i]) { - ret = append(ret, pts[i]) - } - } - return ret -} - -func (t Triangle) Sgn() Float { - return (t[0].X - t[2].X) * (t[1].Y - t[2].Y) - - (t[1].X - t[2].X) * (t[0].Y - t[2].Y) -} - -func (t Triangle) Edges() Edges { - return Edges{ - Edge{t[0], t[1]}, - Edge{t[1], t[2]}, - Edge{t[2], t[0]}, - } -} - -func (t Triangle) Vertices() Vertices { - return Vertices{ - t[0], t[1], t[2], - } -} -type Triangles []Triangle - -func (ts Triangles) ContainedPoints(pts Points) (Points) { - ret := []Point{} - for _, t := range ts { - ps := t.ContainedPoints(pts) - ret = append(ret, ps...) - } - - return ret -} - -func (ts Triangles) Vertices() Vertices { - ret := make(Vertices, 0, len(ts)*3) - for _, t := range ts { - ret = append(ret, t.Vertices()...) - } - return ret -} - -func (ts Triangles) Edges() Edges { - ret := make(Edges, 0, len(ts)*3) - for _, t := range ts { - ret = append(ret, t.Edges()...) - } - return ret -} - -func (r *DrawableTriangles) MakeEVertices(c *Context) []EVertex { - m := c.Camera.RealMatrix() - vs := make([]ebiten.Vertex, len(r.Triangles) * 3) - var buf Vertex - buf.Color = r.Color - for i := range r.Triangles { - for j := range r.Triangles[i] { - buf.Dst = r.Triangles[i][j].Apply(m) - vs[i*3 + j] = buf.Ebiten() - } - } - return vs -} - - diff --git a/vertice.go b/vertice.go index c6d4cb1..71ec517 100644 --- a/vertice.go +++ b/vertice.go @@ -1,5 +1,26 @@ package gg -type Vertice = Point -type Vertices = []Vertice +import "github.com/hajimehoshi/ebiten/v2" +import "surdeus.su/core/gg/mx" +type EVertice = ebiten.Vertex + +// Ebitens vector in better abstractions like Vectors. +type Vertice struct { + Dst mx.Vector + Src mx.Vector + Color +} + +func (v Vertice) ToAPI() EVertice { + return EVertice { + DstX: float32(v.Dst.X), + DstY: float32(v.Dst.Y), + SrcX: float32(v.Src.X), + SrcY: float32(v.Src.Y), + ColorR: float32(v.Color.R)/(float32(MaxColorValue)), + ColorG: float32(v.Color.G)/(float32(MaxColorValue)), + ColorB: float32(v.Color.B)/(float32(MaxColorValue)), + ColorA: float32(v.Color.A)/(float32(MaxColorValue)), + } +}