From bc766b05fb70ac3f8693cc5094641acd14f0f0b5 Mon Sep 17 00:00:00 2001 From: SolarLune Date: Tue, 8 Nov 2022 15:24:17 -0800 Subject: [PATCH] BREAKING CHANGES!!! BREAKING: Adding x and y position to NewConvexPolygon(). This means that you may need to adjust your NewConvexPolygon() function calls. Adding moving, rotation, and scaling capabilities to ConvexPolygons / Shapes. Adding NewLine() function to easily create a new line (a ConvexPolygon) from one point to another. FIX: ConvexPolygon.IsInside() now should work when checking against multiple lines in the same polygon in the same direction. FIX: Space.Add() / Space.Remove() now will panic if the space is nil (shouldn't happen, but it can). Renaming Line > collidingLine, as this is for collision detection, mainly. Adding ToRadians() and ToDegrees() utility functions. Updating go.mod. Renaming Shape > IShape. Updating readme. --- .gitignore | 0 examples/common.go | 33 +++-- examples/go.mod | 11 +- examples/go.sum | 91 +++++++----- examples/main.go | 5 + examples/worldBouncer.go | 7 + examples/worldDirectTest.go | 179 +++++++++++++++++++++++ examples/worldLineOfSight.go | 1 + examples/worldPlatformer.go | 3 + examples/worldShapeTest.go | 15 +- go.mod | 5 +- go.sum | 79 ++++++++++ object.go | 4 +- readme.md | 16 +- shape.go | 273 +++++++++++++++++++++++++++-------- space.go | 8 + utils.go | 13 ++ 17 files changed, 609 insertions(+), 134 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 examples/common.go mode change 100644 => 100755 examples/go.mod mode change 100644 => 100755 examples/go.sum create mode 100644 examples/worldDirectTest.go mode change 100644 => 100755 examples/worldShapeTest.go mode change 100644 => 100755 shape.go create mode 100644 utils.go diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/examples/common.go b/examples/common.go old mode 100644 new mode 100755 index f3b7693..965d0dd --- a/examples/common.go +++ b/examples/common.go @@ -9,7 +9,8 @@ import ( "github.com/solarlune/resolv" ) -var circleBuffer map[resolv.Shape]*ebiten.Image = map[resolv.Shape]*ebiten.Image{} +var circleBuffer map[resolv.IShape]*ebiten.Image = map[resolv.IShape]*ebiten.Image{} +var bigDotImg *ebiten.Image func DrawPolygon(screen *ebiten.Image, shape *resolv.ConvexPolygon, color color.Color) { @@ -33,23 +34,23 @@ func DrawCircle(screen *ebiten.Image, circle *resolv.Circle, drawColor color.Col // when necessary. if _, exists := circleBuffer[circle]; !exists { - newImg := ebiten.NewImage(int(circle.Radius)*2, int(circle.Radius)*2) + newImg := ebiten.NewImage(int(circle.Radius())*2, int(circle.Radius())*2) newImg.Set(int(circle.X), int(circle.Y), color.White) stepCount := float64(32) // Half image width and height. - hw := circle.Radius - hh := circle.Radius + hw := circle.Radius() + hh := circle.Radius() for i := 0; i < int(stepCount); i++ { - x := (math.Sin(math.Pi*2*float64(i)/stepCount) * (circle.Radius - 2)) + hw - y := (math.Cos(math.Pi*2*float64(i)/stepCount) * (circle.Radius - 2)) + hh + x := (math.Sin(math.Pi*2*float64(i)/stepCount) * (circle.Radius() - 2)) + hw + y := (math.Cos(math.Pi*2*float64(i)/stepCount) * (circle.Radius() - 2)) + hh - x2 := (math.Sin(math.Pi*2*float64(i+1)/stepCount) * (circle.Radius - 2)) + hw - y2 := (math.Cos(math.Pi*2*float64(i+1)/stepCount) * (circle.Radius - 2)) + hh + x2 := (math.Sin(math.Pi*2*float64(i+1)/stepCount) * (circle.Radius() - 2)) + hw + y2 := (math.Cos(math.Pi*2*float64(i+1)/stepCount) * (circle.Radius() - 2)) + hh ebitenutil.DrawLine(newImg, x, y, x2, y2, color.White) @@ -60,7 +61,21 @@ func DrawCircle(screen *ebiten.Image, circle *resolv.Circle, drawColor color.Col drawOpt := &ebiten.DrawImageOptions{} r, g, b, _ := drawColor.RGBA() drawOpt.ColorM.Scale(float64(r)/65535, float64(g)/65535, float64(b)/65535, 1) - drawOpt.GeoM.Translate(circle.X-circle.Radius, circle.Y-circle.Radius) + drawOpt.GeoM.Translate(circle.X-circle.Radius(), circle.Y-circle.Radius()) screen.DrawImage(circleBuffer[circle], drawOpt) } + +func DrawBigDot(screen *ebiten.Image, x, y float64, drawColor color.Color) { + + if bigDotImg == nil { + bigDotImg = ebiten.NewImage(4, 4) + bigDotImg.Fill(color.White) + } + + opt := &ebiten.DrawImageOptions{} + opt.GeoM.Translate(x-2, y-2) + opt.ColorM.ScaleWithColor(drawColor) + screen.DrawImage(bigDotImg, opt) + +} diff --git a/examples/go.mod b/examples/go.mod old mode 100644 new mode 100755 index 555bd55..a55ea9d --- a/examples/go.mod +++ b/examples/go.mod @@ -2,14 +2,13 @@ module github.com/solarlune/resolv/examples go 1.16 - require ( github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/hajimehoshi/ebiten/v2 v2.1.4 - github.com/kvartborg/vector v0.0.0-20210122071920-91df40ba4054 - github.com/solarlune/resolv v0.0.0-00010101000000-000000000000 - github.com/tanema/gween v0.0.0-20200427131925-c89ae23cc63c - golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d + github.com/hajimehoshi/ebiten/v2 v2.4.12 + github.com/kvartborg/vector v0.1.2 // indirect + github.com/solarlune/resolv v0.5.1 + github.com/tanema/gween v0.0.0-20220318192052-2db1c2d931bd + golang.org/x/image v0.1.0 ) replace github.com/solarlune/resolv => ../ diff --git a/examples/go.sum b/examples/go.sum old mode 100644 new mode 100755 index 5b07ddf..258d68e --- a/examples/go.sum +++ b/examples/go.sum @@ -1,89 +1,100 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744 h1:A8UnJ/5OKzki4HBDwoRQz7I6sxKsokpMXcGh+fUxpfc= +github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad h1:kX51IjbsJPCvzV9jUoVQG9GEUqIq5hjfYzXTqQ52Rh8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/hajimehoshi/bitmapfont/v2 v2.1.3 h1:JefUkL0M4nrdVwVq7MMZxSTh6mSxOylm+C4Anoucbb0= -github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= -github.com/hajimehoshi/ebiten/v2 v2.1.4 h1:ok1sjnDUm1VHVRvI4HEHzk7CjAsnNj/dqIo2/ObGcy4= -github.com/hajimehoshi/ebiten/v2 v2.1.4/go.mod h1:mpAvpmTRbMdhQDZplZ4rfEogRhdsfAGTC0zLhxawKHY= -github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= -github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= +github.com/hajimehoshi/bitmapfont/v2 v2.2.2 h1:4z08Fk1m3pjtlO7BdoP48u5bp/Y8xmKshf44aCXgYpE= +github.com/hajimehoshi/bitmapfont/v2 v2.2.2/go.mod h1:Ua/x9Dkz7M9CU4zr1VHWOqGwjKdXbOTRsH7lWfb1Co0= +github.com/hajimehoshi/ebiten/v2 v2.4.12 h1:exd4SRImAKJkoRGV3nlYUeFGmM6U/rVD3vWlgnO2mUo= +github.com/hajimehoshi/ebiten/v2 v2.4.12/go.mod h1:BZcqCU4XHmScUi+lsKexocWcf4offMFwfp8dVGIB/G4= +github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41 h1:s01qIIRG7vN/5ndLwkDktjx44ulFk6apvAjVBYR50Yo= +github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= +github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= -github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= -github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= +github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= +github.com/jakecoffman/cp v1.2.1/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= +github.com/jezek/xgb v1.0.1 h1:YUGhxps0aR7J2Xplbs23OHnV1mWaxFVcOl9b+1RQkt8= +github.com/jezek/xgb v1.0.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/jfreymuth/oggvorbis v1.0.4/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0/go.mod h1:GAX7tMJqXx9fB1BrsTWPOXy6IBRX+J461BffVPAdpwo= github.com/kvartborg/vector v0.0.0-20210122071920-91df40ba4054 h1:qp+eSkHJ1gdPN6JIhnRWSPtF1nxasSfIL5l4wlcLik0= github.com/kvartborg/vector v0.0.0-20210122071920-91df40ba4054/go.mod h1:GAX7tMJqXx9fB1BrsTWPOXy6IBRX+J461BffVPAdpwo= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/kvartborg/vector v0.1.2 h1:HjdGr/4SVlQ7xCI6k3pvKWPdsXOCev1mS5vIueNZ99A= +github.com/kvartborg/vector v0.1.2/go.mod h1:GAX7tMJqXx9fB1BrsTWPOXy6IBRX+J461BffVPAdpwo= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/tanema/gween v0.0.0-20200417141625-072eecd4c6ed/go.mod h1:XXpz+9IVhUY5vTC5gXRNSjLDVwQWa5KM43NrH1GJa4M= github.com/tanema/gween v0.0.0-20200427131925-c89ae23cc63c h1:rZz/p7IbahA51/dieoBBfzTgkTX9C8QFrTgkMh64Khg= github.com/tanema/gween v0.0.0-20200427131925-c89ae23cc63c/go.mod h1:XXpz+9IVhUY5vTC5gXRNSjLDVwQWa5KM43NrH1GJa4M= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/tanema/gween v0.0.0-20220318192052-2db1c2d931bd h1:FuXh8GdYjBTIaZEGxF0iz5KorlN30TG4wdPhYBFo7CI= +github.com/tanema/gween v0.0.0-20220318192052-2db1c2d931bd/go.mod h1:XXpz+9IVhUY5vTC5gXRNSjLDVwQWa5KM43NrH1GJa4M= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= +golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU= -golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4= -golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/examples/main.go b/examples/main.go index 5fea078..9395d36 100755 --- a/examples/main.go +++ b/examples/main.go @@ -46,6 +46,7 @@ func NewGame() *Game { NewWorldLineTest(g), // NewWorldMultiShape(g), // MultiShapes are still buggy; gotta fix 'em up NewWorldShapeTest(g), + NewWorldDirectTest(g), } fontData, _ := truetype.Parse(excelFont) @@ -90,6 +91,10 @@ func (g *Game) Update() error { g.ShowHelpText = !g.ShowHelpText } + if inpututil.IsKeyJustPressed(ebiten.KeyF4) { + ebiten.SetFullscreen(!ebiten.IsFullscreen()) + } + if inpututil.IsKeyJustPressed(ebiten.KeyE) { g.CurrentWorld++ } diff --git a/examples/worldBouncer.go b/examples/worldBouncer.go index e06d3b3..b3929c2 100755 --- a/examples/worldBouncer.go +++ b/examples/worldBouncer.go @@ -150,6 +150,12 @@ func (world *WorldBouncer) Draw(screen *ebiten.Image) { world.Game.DrawText(screen, 16, 16, "~ Bouncer Demo ~", + "This demo showcases how objects can bounce", + "off of each other or walls at a good performance.", + "This is accomplished by each bouncer checking the cell it's", + "heading into, rather than checking each other bouncer", + "in play.", + "", "Up Arrow: Add bouncer", "Down Arrow: Remove bouncer", "", @@ -159,6 +165,7 @@ func (world *WorldBouncer) Draw(screen *ebiten.Image) { "", "F1: Toggle Debug View", "F2: Show / Hide help text", + "F4: Toggle fullscreen", "R: Restart world", "E: Next world", "Q: Previous world", diff --git a/examples/worldDirectTest.go b/examples/worldDirectTest.go new file mode 100644 index 0000000..734cba1 --- /dev/null +++ b/examples/worldDirectTest.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/kvartborg/vector" + "github.com/solarlune/resolv" +) + +type WorldDirectTest struct { + Game *Game + Geometry []*resolv.ConvexPolygon + ShowHelpText bool + LineStartPos vector.Vector +} + +func NewWorldDirectTest(game *Game) *WorldDirectTest { + + w := &WorldDirectTest{ + Game: game, + ShowHelpText: true, + LineStartPos: vector.Vector{float64(game.Width) / 2, float64(game.Height) / 2}, + } + + w.Init() + + return w +} + +func (world *WorldDirectTest) Init() { + + smallBox := resolv.NewConvexPolygon( + 0, 0, // Position + + -5, -5, // Vertices + 5, -5, + 5, 5, + -5, 5, + ) + + type boxSetup struct { + X, Y float64 + W, H float64 + Rotation float64 // in degrees + } + + boxes := []boxSetup{ + + { + X: 150, Y: 150, + }, + + { + X: 200, Y: 250, + Rotation: 45, + }, + + { + X: 220, Y: 250, + }, + + // Big boi + { + X: 300, Y: 200, + W: 20, H: 10, + Rotation: 10, + }, + + { + X: 300, + Y: 50, + }, + + { + X: 320, + Y: 60, + }, + } + + world.Geometry = []*resolv.ConvexPolygon{} + + for _, box := range boxes { + newBox := smallBox.Clone().(*resolv.ConvexPolygon) + newBox.SetPosition(box.X, box.Y) + if box.W > 0 { + newBox.SetScale(box.W, box.H) + } + newBox.SetRotation(resolv.ToRadians(box.Rotation)) + world.Geometry = append(world.Geometry, + newBox, + ) + } + +} + +func (world *WorldDirectTest) Update() { + + // Let's rotate one of them because why not + world.Geometry[0].Rotate(0.01) + + dx := 0.0 + dy := 0.0 + + if ebiten.IsKeyPressed(ebiten.KeyA) { + dx = -1 + } + if ebiten.IsKeyPressed(ebiten.KeyD) { + dx = 1 + } + + if ebiten.IsKeyPressed(ebiten.KeyW) { + dy = -1 + } + if ebiten.IsKeyPressed(ebiten.KeyS) { + dy = 1 + } + + moveSpd := 0.5 + + world.LineStartPos[0] += dx * moveSpd + world.LineStartPos[1] += dy * moveSpd + +} + +func (world *WorldDirectTest) Draw(screen *ebiten.Image) { + + lineColor := color.RGBA{255, 255, 0, 255} + mx, my := ebiten.CursorPosition() + line := resolv.NewLine(world.LineStartPos[0], world.LineStartPos[1], float64(mx), float64(my)) + + intersectionPoints := []vector.Vector{} + + for _, box := range world.Geometry { + if intersection := line.Intersection(0, 0, box); intersection != nil { + intersectionPoints = append(intersectionPoints, intersection.Points...) + lineColor = color.RGBA{255, 0, 0, 255} + } + } + + l := line.Lines()[0] + ebitenutil.DrawLine(screen, l.Start[0], l.Start[1], l.End[0], l.End[1], lineColor) + DrawBigDot(screen, world.LineStartPos[0], world.LineStartPos[1], lineColor) + + for _, o := range world.Geometry { + DrawPolygon(screen, o, color.White) + } + + for _, point := range intersectionPoints { + DrawBigDot(screen, point[0], point[1], color.RGBA{0, 255, 0, 255}) + } + + if world.Game.ShowHelpText { + + world.Game.DrawText(screen, 16, 16, + "~ Direct Test Demo ~", + "", + "This demo tests out direct collision between", + "objects. A line is cast from the center of the", + "screen and goes to the mouse position.", + "The intersection points of all objects that cross the line", + "is visualized by green dots.", + "WASD: Move line start.", + "Mouse position: Move line end.", + fmt.Sprintf("%d FPS (frames per second)", int(ebiten.CurrentFPS())), + fmt.Sprintf("%d TPS (ticks per second)", int(ebiten.CurrentTPS())), + "", + "F2: Show / Hide help text", + "F4: Toggle fullscreen", + "R: Restart world", + "E: Next world", + "Q: Previous world", + ) + + } + +} diff --git a/examples/worldLineOfSight.go b/examples/worldLineOfSight.go index 4429603..62d02f0 100755 --- a/examples/worldLineOfSight.go +++ b/examples/worldLineOfSight.go @@ -158,6 +158,7 @@ func (world *WorldLineTest) Draw(screen *ebiten.Image) { "", "F1: Toggle Debug View", "F2: Show / Hide help text", + "F4: Toggle fullscreen", "R: Restart world", "E: Next world", "Q: Previous world", diff --git a/examples/worldPlatformer.go b/examples/worldPlatformer.go index 17b601a..a3e4b4c 100755 --- a/examples/worldPlatformer.go +++ b/examples/worldPlatformer.go @@ -111,6 +111,8 @@ func (world *WorldPlatformer) Init() { // ensuring the Player is always able to stand regardless of which ramp they're standing on. rampShape := resolv.NewConvexPolygon( + 0, 0, + 0, 0, 2, 0, // The extra 2 pixels here make it so the Player doesn't get stuck for a frame or two when running up the ramp. ramp.W-2, ramp.H, // Same here; an extra 2 pixels makes it so that dismounting the ramp is nice and easy @@ -445,6 +447,7 @@ func (world *WorldPlatformer) Draw(screen *ebiten.Image) { "", "F1: Toggle Debug View", "F2: Show / Hide help text", + "F4: Toggle fullscreen", "R: Restart world", "E: Next world", "Q: Previous world", diff --git a/examples/worldShapeTest.go b/examples/worldShapeTest.go old mode 100644 new mode 100755 index 5a8ab67..571ff0e --- a/examples/worldShapeTest.go +++ b/examples/worldShapeTest.go @@ -86,12 +86,12 @@ func (world *WorldShapeTest) Draw(screen *ebiten.Image) { if world.Contact != nil { for _, p := range world.Contact.Points { - world.DrawBigDot(screen, p.X(), p.Y(), color.RGBA{255, 255, 0, 255}) + DrawBigDot(screen, p.X(), p.Y(), color.RGBA{255, 255, 0, 255}) } ebitenutil.DrawLine(screen, world.Contact.Center.X(), world.Contact.Center.Y(), world.Contact.Center.X()+world.Contact.MTV.X(), world.Contact.Center.Y()+world.Contact.MTV.Y(), color.RGBA{255, 128, 0, 255}) - world.DrawBigDot(screen, world.Contact.Center.X(), world.Contact.Center.Y(), color.RGBA{255, 128, 255, 255}) + DrawBigDot(screen, world.Contact.Center.X(), world.Contact.Center.Y(), color.RGBA{255, 128, 255, 255}) } @@ -110,6 +110,7 @@ func (world *WorldShapeTest) Draw(screen *ebiten.Image) { "This gives best results when not very far into another Shape.", "", "F2: Show / Hide help text", + "F4: Toggle fullscreen", "R: Restart world", "E: Next world", "Q: Previous world", @@ -118,13 +119,3 @@ func (world *WorldShapeTest) Draw(screen *ebiten.Image) { } } - -func (world *WorldShapeTest) DrawBigDot(screen *ebiten.Image, ix, iy float64, color color.Color) { - - newImg := ebiten.NewImage(4, 4) - newImg.Fill(color) - opt := &ebiten.DrawImageOptions{} - opt.GeoM.Translate(ix-2, iy-2) - screen.DrawImage(newImg, opt) - -} diff --git a/go.mod b/go.mod index 5f11566..267acc4 100755 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/solarlune/resolv go 1.16 -require github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0 +require ( + github.com/hajimehoshi/ebiten/v2 v2.4.12 + github.com/kvartborg/vector v0.1.2 +) diff --git a/go.sum b/go.sum index 1e36f25..e2df28d 100755 --- a/go.sum +++ b/go.sum @@ -1,2 +1,81 @@ +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744 h1:A8UnJ/5OKzki4HBDwoRQz7I6sxKsokpMXcGh+fUxpfc= +github.com/ebitengine/purego v0.0.0-20220905075623-aeed57cda744/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad h1:kX51IjbsJPCvzV9jUoVQG9GEUqIq5hjfYzXTqQ52Rh8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220806181222-55e207c401ad/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/hajimehoshi/bitmapfont/v2 v2.2.2/go.mod h1:Ua/x9Dkz7M9CU4zr1VHWOqGwjKdXbOTRsH7lWfb1Co0= +github.com/hajimehoshi/ebiten/v2 v2.4.12 h1:exd4SRImAKJkoRGV3nlYUeFGmM6U/rVD3vWlgnO2mUo= +github.com/hajimehoshi/ebiten/v2 v2.4.12/go.mod h1:BZcqCU4XHmScUi+lsKexocWcf4offMFwfp8dVGIB/G4= +github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41 h1:s01qIIRG7vN/5ndLwkDktjx44ulFk6apvAjVBYR50Yo= +github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= +github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= +github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= +github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= +github.com/jakecoffman/cp v1.2.1/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= +github.com/jezek/xgb v1.0.1 h1:YUGhxps0aR7J2Xplbs23OHnV1mWaxFVcOl9b+1RQkt8= +github.com/jezek/xgb v1.0.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/jfreymuth/oggvorbis v1.0.4/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= +github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ= github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0 h1:v8lWpj5957KtDMKu+xQtlu6G3ZoZR6Tn9bsfZCRG5Xw= github.com/kvartborg/vector v0.0.0-20200419093813-2cba0cabb4f0/go.mod h1:GAX7tMJqXx9fB1BrsTWPOXy6IBRX+J461BffVPAdpwo= +github.com/kvartborg/vector v0.1.2 h1:HjdGr/4SVlQ7xCI6k3pvKWPdsXOCev1mS5vIueNZ99A= +github.com/kvartborg/vector v0.1.2/go.mod h1:GAX7tMJqXx9fB1BrsTWPOXy6IBRX+J461BffVPAdpwo= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= +golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105 h1:3vUV5x5+3LfQbgk7paCM6INOaJG9xXQbn79xoNkwfIk= +golang.org/x/mobile v0.0.0-20220722155234-aaac322e2105/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +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/sync v0.0.0-20220722155255-886fb9371eb4/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U= +golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +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= diff --git a/object.go b/object.go index af4d753..d80e696 100755 --- a/object.go +++ b/object.go @@ -9,7 +9,7 @@ import ( // Object represents an object that can be spread across one or more Cells in a Space. An Object is essentially an AABB (Axis-Aligned Bounding Box) Rectangle. type Object struct { - Shape Shape // A shape for more specific collision-checking. + Shape IShape // A shape for more specific collision-checking. Space *Space // Reference to the Space the Object exists within X, Y, W, H float64 // Position and size of the Object in the Space TouchingCells []*Cell // An array of Cells the Object is touching @@ -140,7 +140,7 @@ func (obj *Object) Tags() []string { // SetShape sets the Shape on the Object, in case you need to use precise per-Shape intersection detection. SetShape calls Object.Update() as well, so that it's able to // update the Shape's position to match its Object as necessary. (If you don't use this, the Shape's position might not match the Object's, depending on if you set the Shape // after you added the Object to a Space and if you don't call Object.Update() yourself afterwards.) -func (obj *Object) SetShape(shape Shape) { +func (obj *Object) SetShape(shape IShape) { if obj.Shape != shape { obj.Shape = shape obj.Update() diff --git a/readme.md b/readme.md index b6d0d48..aedd5f1 100755 --- a/readme.md +++ b/readme.md @@ -168,22 +168,26 @@ func Init() { // Then we add the Object to the Space. space.Add(playerObj) + // Note that we can just use the shapes directly as well. + stairs = resolv.NewObject(96, 128, 16, 16) - // Here, we use resolvl.NewConvexPolygon() to create a new ConvexPolygon Shape. It takes + // Here, we use resolv.NewConvexPolygon() to create a new ConvexPolygon Shape. It takes // a series of float64 values indicating the X and Y positions of each vertex; the call // below, for example, creates a triangle. stairs.SetShape(resolv.NewConvexPolygon( + 0, 0, // Position of the polygon + 16, 0, // (x, y) pair for the first vertex 16, 16, // (x, y) pair for the second vertex 0, 16, // (x, y) pair for the third and last vertex )) - // 0 - // |\ - // | \ - // | \ + // 0 + // /| + // / | + // / | // 2---1 // Note that the vertices are in clockwise order. They can be in either clockwise or @@ -203,7 +207,7 @@ func Update() { if intersection := playerObj.Shape.Intersection(dx, 0, stairs.Shape); intersection != nil { // We are colliding with the stairs shape, so we can move according - // to the delta to get out of it. + // to the delta (MTV) to get out of it. dx = intersection.MTV.X() // You might want to move a bit less (say, 0.1) than the delta to diff --git a/shape.go b/shape.go old mode 100644 new mode 100755 index 8712d7f..65a9e72 --- a/shape.go +++ b/shape.go @@ -7,50 +7,80 @@ import ( "github.com/kvartborg/vector" ) -type Shape interface { +type IShape interface { // Intersection tests to see if a Shape intersects with the other given Shape. dx and dy are delta movement variables indicating // movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it // were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding // the intersection. - Intersection(dx, dy float64, other Shape) *ContactSet + Intersection(dx, dy float64, other IShape) *ContactSet // Bounds returns the top-left and bottom-right points of the Shape. Bounds() (vector.Vector, vector.Vector) // Position returns the X and Y position of the Shape. Position() (float64, float64) // SetPosition allows you to place a Shape at another location. SetPosition(x, y float64) - // Clone duplicates the Shape. - Clone() Shape + + Rotation() float64 + SetRotation(radians float64) + + // Rotate rotates the IShape by the radians provided. + // Note that the rotation goes counter-clockwise from 0 at right to pi/2 in the upwards direction, + // pi / -pi at left, -pi/2 in the downwards direction, and finally back to 0. + // This can be visualized as follows: + // + // U + // L R + // D + // + // R: 0 + // U: pi/2 + // L: pi / -pi + // D: -pi/2 + // + // This rotation scheme follows the way math.Atan2() works. + // Note that Rotate(), of course, doesn't do anything for circles for obvious reasons. + Rotate(radians float64) + + Scale() (float64, float64) // Returns the scale of the IShape (the radius for Circles). + SetScale(w, h float64) // Sets the overall scale of the IShape; 1.0 is 100% scale, 2.0 is 200%, and so on. The greater of these values is used for the radius for Circles. + + // Move moves the IShape by the x and y values provided. + Move(x, y float64) + // MoveVec moves the IShape by the movement values given in the vector provided. + MoveVec(vec vector.Vector) + + // Clone duplicates the IShape. + Clone() IShape } -// A Line is a helper shape used to determine if two ConvexPolygon lines intersect; you can't create a Line to use as a Shape. -// Instead, you can create a ConvexPolygon, specify two points, and set its Closed value to false. -type Line struct { +// A collidingLine is a helper shape used to determine if two ConvexPolygon lines intersect; you can't create a collidingLine to use as a Shape. +// Instead, you can create a ConvexPolygon, specify two points, and set its Closed value to false (or use NewLine(), as this does it for you). +type collidingLine struct { Start, End vector.Vector } -func NewLine(x, y, x2, y2 float64) *Line { - return &Line{ +func new_line(x, y, x2, y2 float64) *collidingLine { + return &collidingLine{ Start: vector.Vector{x, y}, End: vector.Vector{x2, y2}, } } -func (line *Line) Project(axis vector.Vector) vector.Vector { +func (line *collidingLine) Project(axis vector.Vector) vector.Vector { return line.Vector().Scale(axis.Dot(line.Start.Sub(line.End))) } -func (line *Line) Normal() vector.Vector { +func (line *collidingLine) Normal() vector.Vector { v := line.Vector() return vector.Vector{v[1], -v[0]}.Unit() } -func (line *Line) Vector() vector.Vector { +func (line *collidingLine) Vector() vector.Vector { return line.End.Clone().Sub(line.Start).Unit() } // IntersectionPointsLine returns the intersection point of a Line with another Line as a vector.Vector. If no intersection is found, it will return nil. -func (line *Line) IntersectionPointsLine(other *Line) vector.Vector { +func (line *collidingLine) IntersectionPointsLine(other *collidingLine) vector.Vector { det := (line.End[0]-line.Start[0])*(other.End[1]-other.Start[1]) - (other.End[0]-other.Start[0])*(line.End[1]-line.Start[1]) @@ -82,7 +112,7 @@ func (line *Line) IntersectionPointsLine(other *Line) vector.Vector { } // IntersectionPointsCircle returns a slice of vector.Vectors, each indicating the intersection point. If no intersection is found, it will return an empty slice. -func (line *Line) IntersectionPointsCircle(circle *Circle) []vector.Vector { +func (line *collidingLine) IntersectionPointsCircle(circle *Circle) []vector.Vector { points := []vector.Vector{} @@ -93,7 +123,7 @@ func (line *Line) IntersectionPointsCircle(circle *Circle) []vector.Vector { a := diff[0]*diff[0] + diff[1]*diff[1] b := 2 * ((diff[0] * lStart[0]) + (diff[1] * lStart[1])) - c := (lStart[0] * lStart[0]) + (lStart[1] * lStart[1]) - (circle.Radius * circle.Radius) + c := (lStart[0] * lStart[0]) + (lStart[1] * lStart[1]) - (circle.radius * circle.radius) det := b*b - (4 * a * c) @@ -126,29 +156,47 @@ func (line *Line) IntersectionPointsCircle(circle *Circle) []vector.Vector { } +// ConvexPolygon represents a series of points, connected by lines, constructing a convex shape. +// The polygon has a position, a scale, a rotation, and may or may not be closed. type ConvexPolygon struct { - Points []vector.Vector - X, Y float64 - Closed bool + Points []vector.Vector // Points represents the points constructing the ConvexPolygon. + X, Y float64 // X and Y are the position of the ConvexPolygon. + ScaleW, ScaleH float64 // The width and height for scaling + rotation float64 // How many radians the ConvexPolygon is rotated around in the viewing vector (Z). + Closed bool // Closed is whether the ConvexPolygon is closed or not; only takes effect if there are more than 2 points. } -// NewConvexPolygon creates a new convex polygon from the provided set of X and Y positions of 2D points (or vertices). Should generally be ordered clockwise, -// from X and Y of the first, to X and Y of the last. For example: NewConvexPolygon(0, 0, 10, 0, 10, 10, 0, 10) would create a 10x10 convex -// polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}. -func NewConvexPolygon(points ...float64) *ConvexPolygon { +// NewConvexPolygon creates a new convex polygon at the position given, from the provided set of X and Y positions of 2D points (or vertices). +// You don't need to pass any points at this stage, but if you do, you should pass whole pairs. The points should generally be ordered clockwise, +// from X and Y of the first, to X and Y of the last. +// For example: NewConvexPolygon(30, 20, 0, 0, 10, 0, 10, 10, 0, 10) would create a 10x10 convex +// polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}, with the polygon itself occupying a position of 30, 20. +// Note that the passed values are the positions of the vertices composing the shape, not the position of the shape itself. +// You can also pass the points using vectors with ConvexPolygon.AddPointsVec(). +func NewConvexPolygon(x, y float64, points ...float64) *ConvexPolygon { // if len(points)/2 < 2 { // return nil // } - cp := &ConvexPolygon{Points: []vector.Vector{}, Closed: true} + cp := &ConvexPolygon{ + X: x, + Y: y, + ScaleW: 1, + ScaleH: 1, + Points: []vector.Vector{}, + Closed: true, + } - cp.AddPoints(points...) + if len(points) > 0 { + cp.AddPoints(points...) + } return cp } -func (cp *ConvexPolygon) Clone() Shape { +// Clone returns a clone of the ConvexPolygon as an IShape. +func (cp *ConvexPolygon) Clone() IShape { points := []vector.Vector{} @@ -156,9 +204,10 @@ func (cp *ConvexPolygon) Clone() Shape { points = append(points, point.Clone()) } - newPoly := NewConvexPolygon() - newPoly.X = cp.X - newPoly.Y = cp.Y + newPoly := NewConvexPolygon(cp.X, cp.Y) + newPoly.rotation = cp.rotation + newPoly.ScaleW = cp.ScaleW + newPoly.ScaleH = cp.ScaleH newPoly.AddPointsVec(points...) newPoly.Closed = cp.Closed return newPoly @@ -172,15 +221,21 @@ func (cp *ConvexPolygon) AddPointsVec(points ...vector.Vector) { // AddPoints allows you to add points to the ConvexPolygon with a slice or selection of float64s, with each pair indicating an X or Y value for // a point / vertex (i.e. AddPoints(0, 1, 2, 3) would add two points - one at {0, 1}, and another at {2, 3}). func (cp *ConvexPolygon) AddPoints(vertexPositions ...float64) { + if len(vertexPositions) == 0 { + panic("Error: AddPoints called with 0 passed vertex positions.") + } + if len(vertexPositions)%2 == 1 { + panic("Error: AddPoints called with a non-even amount of vertex positions.") + } for v := 0; v < len(vertexPositions); v += 2 { cp.Points = append(cp.Points, vector.Vector{vertexPositions[v], vertexPositions[v+1]}) } } -// Lines returns a slice of transformed Lines composing the ConvexPolygon. -func (cp *ConvexPolygon) Lines() []*Line { +// Lines returns a slice of transformed internalLines composing the ConvexPolygon. +func (cp *ConvexPolygon) Lines() []*collidingLine { - lines := []*Line{} + lines := []*collidingLine{} vertices := cp.Transformed() @@ -190,11 +245,11 @@ func (cp *ConvexPolygon) Lines() []*Line { if i < len(vertices)-1 { end = vertices[i+1] - } else if !cp.Closed { + } else if !cp.Closed || len(cp.Points) <= 2 { break } - line := NewLine(start[0], start[1], end[0], end[1]) + line := new_line(start[0], start[1], end[0], end[1]) lines = append(lines, line) @@ -208,7 +263,11 @@ func (cp *ConvexPolygon) Lines() []*Line { func (cp *ConvexPolygon) Transformed() []vector.Vector { transformed := []vector.Vector{} for _, point := range cp.Points { - transformed = append(transformed, vector.Vector{point[0] + cp.X, point[1] + cp.Y}) + p := vector.Vector{point[0] * cp.ScaleW, point[1] * cp.ScaleH} + if cp.rotation != 0 { + vector.In(p).Rotate(-cp.rotation) + } + transformed = append(transformed, vector.Vector{p[0] + cp.X, p[1] + cp.Y}) } return transformed } @@ -320,7 +379,7 @@ func (cp *ConvexPolygon) SATAxes() []vector.Vector { // PointInside returns if a Point (a vector.Vector) is inside the ConvexPolygon. func (polygon *ConvexPolygon) PointInside(point vector.Vector) bool { - pointLine := NewLine(point[0], point[1], point[0]+999999999999, point[1]) + pointLine := new_line(point[0], point[1], point[0]+999999999999, point[1]) contactCount := 0 @@ -332,7 +391,49 @@ func (polygon *ConvexPolygon) PointInside(point vector.Vector) bool { } - return contactCount == 1 + return contactCount%2 == 1 +} + +// Rotation returns the rotation (in radians) of the ConvexPolygon. +func (polygon *ConvexPolygon) Rotation() float64 { + return polygon.rotation +} + +// SetRotation sets the rotation for the ConvexPolygon; note that the rotation goes counter-clockwise from 0 to pi, and then from -pi at 180 down, back to 0. +// This can be visualized as follows: +// +// (Pi / 2) +// | +// | +// (Pi / -Pi) ------------- (0) +// | +// | +// (-Pi / 2) +// +// This rotation scheme follows the way math.Atan2() works. +func (polygon *ConvexPolygon) SetRotation(radians float64) { + polygon.rotation = radians + if polygon.rotation > math.Pi { + polygon.rotation -= math.Pi * 2 + } else if polygon.rotation < -math.Pi { + polygon.rotation += math.Pi * 2 + } +} + +// Rotate is a helper function to rotate a ConvexPolygon by the radians given. +func (polygon *ConvexPolygon) Rotate(radians float64) { + polygon.SetRotation(polygon.Rotation() + radians) +} + +// Scale returns the scale multipliers of the ConvexPolygon. +func (polygon *ConvexPolygon) Scale() (float64, float64) { + return polygon.ScaleW, polygon.ScaleH +} + +// SetScale sets the scale multipliers of the ConvexPolygon. +func (polygon *ConvexPolygon) SetScale(w, h float64) { + polygon.ScaleW = w + polygon.ScaleH = h } type ContactSet struct { @@ -421,7 +522,7 @@ func (cs *ContactSet) BottommostPoint() vector.Vector { // movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it // were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding // the intersection. -func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet { +func (cp *ConvexPolygon) Intersection(dx, dy float64, other IShape) *ContactSet { contactSet := NewContactSet() @@ -484,7 +585,7 @@ func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet { } // calculateMTV returns the MTV, if possible, and a bool indicating whether it was possible or not. -func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape Shape) vector.Vector { +func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape IShape) vector.Vector { delta := vector.Vector{0, 0} @@ -530,7 +631,7 @@ func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape Shape) sort.Slice(verts, func(i, j int) bool { return verts[i].Sub(center).Magnitude() < verts[j].Sub(center).Magnitude() }) smallest = vector.Vector{center[0] - verts[0][0], center[1] - verts[0][1]} - smallest = smallest.Unit().Scale(smallest.Magnitude() - other.Radius) + smallest = smallest.Unit().Scale(smallest.Magnitude() - other.radius) } @@ -541,7 +642,7 @@ func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape Shape) } // ContainedBy returns if the ConvexPolygon is wholly contained by the other shape provided. -func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool { +func (cp *ConvexPolygon) ContainedBy(otherShape IShape) bool { switch other := otherShape.(type) { @@ -600,45 +701,66 @@ func (cp *ConvexPolygon) ReverseVertexOrder() { } -// NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own -// "thing" with its own optimized Intersection code check. +// NewRectangle returns a rectangular ConvexPolygon at the {x, y} position given with the vertices ordered in clockwise order, +// positioned at {0, 0}, {w, 0}, {w, h}, {0, h}. +// TODO: In actuality, an AABBRectangle should be its own "thing" with its own optimized Intersection code check. func NewRectangle(x, y, w, h float64) *ConvexPolygon { return NewConvexPolygon( x, y, - x+w, y, - x+w, y+h, - x, y+h, + + 0, 0, + w, 0, + w, h, + 0, h, + ) +} + +// NewLine is a helper function that returns a ConvexPolygon composed of a single line. The Polygon has a position of x1, y1, and has a width and height +// equivalent to x2-x1 and y2-y1 (so the end of the line is at x2, y2). +func NewLine(x1, y1, x2, y2 float64) *ConvexPolygon { + newLine := NewConvexPolygon(x1, y1, + 0, 0, + x2-x1, y2-y1, ) + newLine.Closed = false // This actually isn't necessary for a one-sided polygon + return newLine } type Circle struct { - X, Y, Radius float64 + X, Y, radius float64 + originalRadius float64 + scale float64 } // NewCircle returns a new Circle, with its center at the X and Y position given, and with the defined radius. func NewCircle(x, y, radius float64) *Circle { circle := &Circle{ - X: x, - Y: y, - Radius: radius, + X: x, + Y: y, + radius: radius, + originalRadius: radius, + scale: 1, } return circle } -func (circle *Circle) Clone() Shape { - return NewCircle(circle.X, circle.Y, circle.Radius) +func (circle *Circle) Clone() IShape { + newCircle := NewCircle(circle.X, circle.Y, circle.radius) + newCircle.originalRadius = circle.originalRadius + newCircle.scale = circle.scale + return newCircle } // Bounds returns the top-left and bottom-right corners of the Circle. func (circle *Circle) Bounds() (vector.Vector, vector.Vector) { - return vector.Vector{circle.X - circle.Radius, circle.Y - circle.Radius}, vector.Vector{circle.X + circle.Radius, circle.Y + circle.Radius} + return vector.Vector{circle.X - circle.radius, circle.Y - circle.radius}, vector.Vector{circle.X + circle.radius, circle.Y + circle.radius} } // Intersection tests to see if a Circle intersects with the other given Shape. dx and dy are delta movement variables indicating // movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it // were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding // the intersection. -func (circle *Circle) Intersection(dx, dy float64, other Shape) *ContactSet { +func (circle *Circle) Intersection(dx, dy float64, other IShape) *ContactSet { var contactSet *ContactSet @@ -669,7 +791,7 @@ func (circle *Circle) Intersection(dx, dy float64, other Shape) *ContactSet { contactSet.MTV = vector.Vector{circle.X - shape.X, circle.Y - shape.Y} dist := contactSet.MTV.Magnitude() - contactSet.MTV = contactSet.MTV.Unit().Scale(circle.Radius + shape.Radius - dist) + contactSet.MTV = contactSet.MTV.Unit().Scale(circle.radius + shape.radius - dist) for _, point := range contactSet.Points { contactSet.Center = contactSet.Center.Add(point) @@ -723,7 +845,7 @@ func (circle *Circle) Position() (float64, float64) { // PointInside returns if the given vector.Vector is inside of the circle. func (circle *Circle) PointInside(point vector.Vector) bool { - return point.Sub(vector.Vector{circle.X, circle.Y}).Magnitude() <= circle.Radius + return point.Sub(vector.Vector{circle.X, circle.Y}).Magnitude() <= circle.radius } // IntersectionPointsCircle returns the intersection points of the two circles provided. @@ -731,12 +853,12 @@ func (circle *Circle) IntersectionPointsCircle(other *Circle) []vector.Vector { d := math.Sqrt(math.Pow(other.X-circle.X, 2) + math.Pow(other.Y-circle.Y, 2)) - if d > circle.Radius+other.Radius || d < math.Abs(circle.Radius-other.Radius) || d == 0 && circle.Radius == other.Radius { + if d > circle.radius+other.radius || d < math.Abs(circle.radius-other.radius) || d == 0 && circle.radius == other.radius { return nil } - a := (math.Pow(circle.Radius, 2) - math.Pow(other.Radius, 2) + math.Pow(d, 2)) / (2 * d) - h := math.Sqrt(math.Pow(circle.Radius, 2) - math.Pow(a, 2)) + a := (math.Pow(circle.radius, 2) - math.Pow(other.radius, 2) + math.Pow(d, 2)) / (2 * d) + h := math.Sqrt(math.Pow(circle.radius, 2) - math.Pow(a, 2)) x2 := circle.X + a*(other.X-circle.X)/d y2 := circle.Y + a*(other.Y-circle.Y)/d @@ -748,6 +870,41 @@ func (circle *Circle) IntersectionPointsCircle(other *Circle) []vector.Vector { } +// Circles can't rotate, of course. This function is just a stub to make them acceptable as IShapes. +func (circle *Circle) Rotate(radians float64) {} + +// Circles can't rotate, of course. This function is just a stub to make them acceptable as IShapes. +func (circle *Circle) SetRotation(rotation float64) {} + +// Circles can't rotate, of course. This function is just a stub to make them acceptable as IShapes. +func (circle *Circle) Rotation() float64 { + return 0 +} + +// Scale returns the scale multiplier of the Circle, twice; this is to have it adhere to the +func (circle *Circle) Scale() (float64, float64) { + return circle.scale, circle.scale +} + +// SetScale sets the scale multiplier of the Circle (this is W / H to have it adhere to IShape as a +// contract; in truth, the Circle will be set to 0.5 * the maximum out of the width and height +// height values given). +func (circle *Circle) SetScale(w, h float64) { + circle.scale = math.Max(w, h) + circle.radius = circle.originalRadius * circle.scale +} + +// Radius returns the radius of the Circle. +func (circle *Circle) Radius() float64 { + return circle.radius +} + +// SetRadius sets the radius of the Circle, updating the scale multiplier to reflect this change. +func (circle *Circle) SetRadius(radius float64) { + circle.radius = radius + circle.scale = circle.radius / circle.originalRadius +} + // // MultiShape is a Shape comprised of other sub-shapes. // type MultiShape struct { // Shapes []Shape diff --git a/space.go b/space.go index aba9e68..36e8784 100755 --- a/space.go +++ b/space.go @@ -35,6 +35,10 @@ func NewSpace(spaceWidth, spaceHeight, cellWidth, cellHeight int) *Space { // Add adds the specified Objects to the Space, updating the Space's cells to refer to the Object. func (sp *Space) Add(objects ...*Object) { + if sp == nil { + panic("ERROR: space is nil") + } + for _, obj := range objects { obj.Space = sp @@ -50,6 +54,10 @@ func (sp *Space) Add(objects ...*Object) { // game. func (sp *Space) Remove(objects ...*Object) { + if sp == nil { + panic("ERROR: space is nil") + } + for _, obj := range objects { for _, cell := range obj.TouchingCells { diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..b809445 --- /dev/null +++ b/utils.go @@ -0,0 +1,13 @@ +package resolv + +import "math" + +// ToRadians is a helper function to easily convert degrees to radians. +func ToRadians(degrees float64) float64 { + return math.Pi * degrees / 180 +} + +// ToDegrees is a helper function to easily convert radians to degrees for human readability. +func ToDegrees(radians float64) float64 { + return radians / math.Pi * 180 +}